depl 2.4.3

Toolkit for a bunch of local and remote CI/CD actions
Documentation
//! Deployer
//!
//! Deployer is a relatively simple, yet powerful localhost CI/CD instrument.
//!
//! ## Features
//!
//! 1. Local and remote pipeline execution.
//! 2. Several pipeline drivers: standard (Deployer), shell.
//! 3. **Auto-generate** Docker/Podman and Ansible configs and run pipelines inside images or at remote hosts.
//! 4. Export pipeline to shell scripts, **GitHub Actions** & **GitLab CI** configurations.
//! 5. Resolve variables for actions with `.env`-files, any shell command or even **HashiCorp Vault KV2 storage**.
//! 6. Move build & run caches away of your project's directory. Only artifacts and `.depl/config.yaml` file.
//! 7. Watch and rebuild your project automatically via `depl watch`.
//! 8. Use internal versioned content storage and autopatching system on pipeline runs.
//!
//! ## Usability features
//!
//! 1. YAML configuration format, but you may use only your terminal with Deployer's TUI.
//! 2. Global localhost action and pipeline registries.
//! 3. Built-in documentation via `depl docs`.
//! 4. Automatic checks of actions requirements before pipeline execution.
//! 5. Automatic artifacts delivery to `./artifacts` folder.
//! 6. Wide selection of settings and launch options.
//!
//! For simple user guides, check [`README.md`](https://github.com/impulse-sw/deployer/blob/unstable/README.md) and [`DOCS.md`](https://github.com/impulse-sw/deployer/blob/unstable/DOCS.md). For software developer look, check [DeployerProjectOptions](crate::project::DeployerProjectOptions).

#![deny(warnings, missing_docs, clippy::todo, clippy::unimplemented)]
#![allow(async_fn_in_trait)]

use clap::Parser;
use dirs::{cache_dir, config_dir, data_local_dir};
use mimalloc::MiMalloc;
use std::path::PathBuf;

pub use depl::*;

use crate::actions::{cat_action, edit_action, list_actions, new_action, remove_action};
use crate::cmd::{CatType, Cli, DeployerExecType, EditType, ExportType, ImportType, ListType, NewType, RemoveType};
use crate::entities::runs::Runs;
use crate::entities::skipper::Skipper;
use crate::globals::DeployerGlobalConfig;
use crate::pipelines::{
  assign_pipeline_to_project, cat_pipeline, check_project_pipeline, edit_pipeline, export_project_pipeline,
  list_pipelines, new_pipeline, remove_pipeline,
};
use crate::project::DeployerProjectOptions;
use crate::project::{cat_project, clean_runs, edit_project, init_project};
use crate::remote::{cat_remote, edit_remote, list_remote, new_remote, remove_remote};
use crate::rw::{VERBOSE, read, read_versioned, write, write_merge};
use crate::storage::{list_content, new_content, remove_content, use_content};
use crate::tui::docs;

use crate::cd::cd;
use crate::run::run;
use crate::watch::watch;

#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;

#[cfg(not(unix))]
compile_error!("`deployer` can't work with non-Unix systems.");

#[tokio::main(flavor = "current_thread")]
async fn main() {
  std::panic::set_hook(Box::new(|e| {
    let err = e
      .to_string()
      .replace("called `Result::unwrap()` on an `Err` value: ", "");
    eprintln!("{}", err.trim());
    std::process::exit(1);
  }));
  depl::install_ctrlc_handler().unwrap();

  let args = Cli::parse();
  VERBOSE.set(args.verbose).unwrap();

  let cache_folder = if let Some(cache_folder) = &args.cache_folder {
    let cf = PathBuf::from(cache_folder);
    if cf.is_absolute() {
      cf
    } else {
      let path = PathBuf::new();
      path.join(cf)
    }
  } else {
    cache_dir().expect("Can't get `cache` directory's location automatically, please specify one.")
  };
  let config_folder = if let Some(config_folder) = &args.config_folder {
    let cf = PathBuf::from(config_folder);
    if cf.is_absolute() {
      cf
    } else {
      let path = PathBuf::new();
      path.join(cf)
    }
  } else {
    config_dir().expect("Can't get `config` directory's location automatically, please specify one.")
  };
  let storage_folder = if let Some(storage_folder) = &args.storage_folder {
    let sf = PathBuf::from(storage_folder);
    if sf.is_absolute() {
      sf
    } else {
      let path = PathBuf::new();
      path.join(sf)
    }
  } else {
    data_local_dir().expect("Can't get `storage` directory's location automatically, please specify one.")
  };

  let mut globals = read_versioned::<DeployerGlobalConfig>(
    &config_folder,
    GLOBAL_CONF,
    crate::globals::get_default_global_conf_version(),
  );
  DeployerGlobalConfig::make_sure_contain_defaults(&mut globals.actions_registry);

  let config_path = args.config.as_deref().unwrap_or(PROJECT_CONF);
  let mut config = read_versioned::<DeployerProjectOptions>(
    &std::env::current_dir().unwrap(),
    config_path,
    crate::project::get_default_project_conf_version(),
  );
  config.version = crate::project::get_default_project_conf_version();

  let mut runs = read::<Runs>(&cache_folder, BUILD_CACHE_LIST);
  let r#type = args.r#type.unwrap_or(DeployerExecType::Run(Default::default()));

  match r#type {
    DeployerExecType::Ls(ListType::Actions) => list_actions(&globals),
    DeployerExecType::New(NewType::Action(args)) => {
      let _ = new_action(&mut globals, &args).unwrap();
      write(&config_folder, GLOBAL_CONF, &globals);
    }
    DeployerExecType::Cat(CatType::Action(args)) => cat_action(&globals, args).unwrap(),
    DeployerExecType::Edit(EditType::Action(args)) => {
      edit_action(&mut globals, args).await.unwrap();
      write(&config_folder, GLOBAL_CONF, &globals);
    }
    DeployerExecType::Rm(RemoveType::Action(args)) => {
      remove_action(&mut globals, args).unwrap();
      write(&config_folder, GLOBAL_CONF, &globals);
    }

    DeployerExecType::Ls(ListType::Pipelines) => list_pipelines(&globals).unwrap(),
    DeployerExecType::New(NewType::Pipeline(args)) => {
      new_pipeline(&mut globals, &args).unwrap();
      write(&config_folder, GLOBAL_CONF, &globals);
    }
    DeployerExecType::Cat(CatType::Pipeline(args)) => cat_pipeline(&globals, args).unwrap(),
    DeployerExecType::Edit(EditType::Pipeline(args)) => {
      edit_pipeline(&mut globals, args).await.unwrap();
      write(&config_folder, GLOBAL_CONF, &globals);
    }
    DeployerExecType::Rm(RemoveType::Pipeline(args)) => {
      remove_pipeline(&mut globals, args).unwrap();
      write(&config_folder, GLOBAL_CONF, &globals);
    }

    DeployerExecType::Export(ExportType::Registries(args)) => {
      globals.export_registries(&args.output).unwrap();
    }
    DeployerExecType::Import(ImportType::Registries(args)) => {
      globals.import_registries(&args.input).unwrap();
      write(&config_folder, GLOBAL_CONF, &globals);
    }

    DeployerExecType::Ls(ListType::Content(args)) => list_content(&storage_folder, args).unwrap(),
    DeployerExecType::New(NewType::Content(args)) => new_content(&storage_folder, args).unwrap(),
    DeployerExecType::Rm(RemoveType::Content(args)) => remove_content(&storage_folder, args).unwrap(),

    DeployerExecType::Ls(ListType::Remote) => list_remote(&globals),
    DeployerExecType::New(NewType::Remote(args)) => {
      let _ = new_remote(&mut globals, args).unwrap();
      write(&config_folder, GLOBAL_CONF, &globals);
    }
    DeployerExecType::Cat(CatType::Remote(args)) => cat_remote(&globals, args).unwrap(),
    DeployerExecType::Edit(EditType::Remote(args)) => {
      edit_remote(&mut globals, args).unwrap();
      write(&config_folder, GLOBAL_CONF, &globals);
    }
    DeployerExecType::Rm(RemoveType::Remote(args)) => {
      remove_remote(&mut globals, args).unwrap();
      write(&config_folder, GLOBAL_CONF, &globals);
    }

    DeployerExecType::Init => {
      init_project(&mut globals, &mut config).unwrap();
      write(&config_folder, GLOBAL_CONF, &globals);
      write(std::env::current_dir().unwrap(), config_path, &config);
    }
    DeployerExecType::With(args) => {
      assign_pipeline_to_project(&mut globals, &mut config, &args).unwrap();
      write(&config_folder, GLOBAL_CONF, &globals);
      write(std::env::current_dir().unwrap(), config_path, &config);
    }
    DeployerExecType::Cat(CatType::Project(args)) => cat_project(&config, args).unwrap(),
    DeployerExecType::Export(ExportType::Pipeline(args)) => {
      export_project_pipeline(&config, &globals, &cache_folder, &config_folder, &storage_folder, args)
        .await
        .unwrap()
    }
    DeployerExecType::Edit(EditType::Project) => {
      edit_project(&mut globals, &mut config).await.unwrap();
      write(&config_folder, GLOBAL_CONF, &globals);
      write(std::env::current_dir().unwrap(), config_path, &config);
    }

    DeployerExecType::Check(args) => {
      check_project_pipeline(&config, &globals, &cache_folder, &config_folder, &storage_folder, args).await;
    }
    DeployerExecType::Run(args) => {
      if !args.no_clear {
        clearscreen::clear().expect("Failed to clear screen");
      }
      let skipper = Skipper::new(args.skip.unwrap_or(0usize));
      let success = run(
        &config,
        &globals,
        &mut runs,
        &cache_folder,
        &config_folder,
        &storage_folder,
        &args,
        None,
        skipper,
        None,
      )
      .await
      .unwrap();
      write_merge(&cache_folder, BUILD_CACHE_LIST, &runs);
      if !success {
        std::process::exit(1);
      }
    }
    DeployerExecType::Watch(args) => {
      watch(
        &config,
        &globals,
        &mut runs,
        &cache_folder,
        &config_folder,
        &storage_folder,
        &args,
      )
      .await
      .unwrap();
      write_merge(&cache_folder, BUILD_CACHE_LIST, &runs);
    }
    DeployerExecType::Clean(args) => {
      clean_runs(&config, &mut runs, &cache_folder, &args).unwrap();
      write(&cache_folder, BUILD_CACHE_LIST, &runs);
    }

    DeployerExecType::Cd(args) => {
      cd(&config, &runs, &args).await.unwrap();
    }

    DeployerExecType::Use(args) => use_content(&storage_folder, args).unwrap(),
    DeployerExecType::Docs => docs::read_docs().unwrap(),
    DeployerExecType::Migrations => docs::read_migrations().unwrap(),
  }
}