use crate::entities::environment::RunEnvironment;
use crate::entities::github_cicd_opts::GitHubOpts;
use crate::pipelines::DescribedPipeline;
use crate::project::DeployerProjectOptions;
const TRIGGERS: &str = r#"
on:
{on-push}
{on-pull}
"#;
const GH_CICD_TEMPLATE: &str = r#"# Generated by Deployer 2.X
name: {pipeline-title}
{triggers}
env:
TERM: xterm
jobs:
{pipeline-title}:
name: {pipeline-title}
runs-on: {base-image}
steps:
- uses: actions/checkout@v4
{preflight-steps}{restore-cache}
- name: Run pipeline
run: |
chmod +x .github/workflows/{shell-script-path}
./.github/workflows/{shell-script-path}
{save-cache}{artifacts-step}
"#;
const GH_CICD_DEPL_TEMPLATE: &str = r#"# Generated by Deployer 2.X
name: {pipeline-title}
{triggers}
env:
TERM: xterm
jobs:
{pipeline-title}:
name: {pipeline-title}
runs-on: {base-image}
steps:
- uses: impulse-sw/deployer-action@0.6
- uses: actions/checkout@v4
{preflight-steps}{restore-cache}
- name: Run pipeline
run: |
depl run {pipeline-title} --current --no-clear
{save-cache}{artifacts-step}
"#;
pub fn export(
config: &DeployerProjectOptions,
pipeline: &DescribedPipeline,
gh_opts: Option<&GitHubOpts>,
env: &RunEnvironment<'_>,
shell_script: &str,
output_dir: Option<&str>,
) -> anyhow::Result<()> {
let gh_dir = if let Some(output) = output_dir {
let dir = std::path::PathBuf::from(output);
let _ = std::fs::create_dir_all(dir.as_path());
dir
} else {
let dir = env
.project_dir
.ok_or(anyhow::anyhow!("Can't get project dir!"))?
.join(".github")
.join("workflows");
let _ = std::fs::create_dir_all(dir.as_path());
dir
};
let triggers = if gh_opts
.is_none_or(|gh_opts| gh_opts.on_push_branches.is_empty() && gh_opts.on_pull_requests_branches.is_empty())
{
String::new()
} else {
let mut triggers = TRIGGERS.to_string();
let push = &gh_opts.unwrap().on_push_branches;
let pull = &gh_opts.unwrap().on_pull_requests_branches;
if !push.is_empty() {
triggers = triggers.replace(
"{on-push}",
&format!(
" push:\n branches:{}",
push
.iter()
.map(|b| format!("\n - {b}"))
.collect::<Vec<_>>()
.join("")
),
);
}
if !pull.is_empty() {
triggers = triggers.replace(
"{on-pull}",
&format!(
" pull_request:\n branches:{}",
pull
.iter()
.map(|b| format!("\n - {b}"))
.collect::<Vec<_>>()
.join("")
),
);
}
triggers
};
let artifacts_actions = if !pipeline.artifacts.is_empty() {
format!(
"\n - uses: actions/upload-artifact@v4\n with:\n name: {}\n path: |{}",
env.master_pipeline,
pipeline
.artifacts
.iter()
.map(|a| format!("\n {}", a.from))
.collect::<Vec<_>>()
.join("")
)
} else {
String::new()
};
let preflight_steps = if let Some(gh_opts) = gh_opts
&& !gh_opts.preflight_steps.is_empty()
{
format!("\n # Preflight\n{}\n", gh_opts.compile_preflight()?)
} else {
String::new()
};
let (restore_cache, save_cache) = if gh_opts.is_none_or(|gh_opts| gh_opts.enable_cache)
&& !config.cache_files.is_empty()
{
let mut cache_files: std::collections::BTreeSet<_> = config.cache_files.iter().collect();
cache_files.remove(&std::path::PathBuf::from(".git"));
let cache_files = cache_files
.iter()
.map(|cf| {
format!(
"\n {}",
cf.to_str().expect("`cache_file` contains non-UTF8 symbols!")
)
})
.collect::<Vec<_>>()
.join("");
let restore_cache = format!(
"\n - name: Extract branch name\n shell: bash\n run: echo \"branch=${{GITHUB_HEAD_REF:-${{GITHUB_REF#refs/heads/}}}}\" >> $GITHUB_OUTPUT\n id: extract_branch\n\n - name: Restore cache\n id: cache-restore\n uses: actions/cache/restore@v4\n with:\n path: |{cache_files}\n key: ${{{{ steps.extract_branch.outputs.branch }}}}-${{{{ runner.os }}}}-{}\n",
env.master_pipeline
);
let save_cache = format!(
"\n - name: Save cache\n id: cache-save\n uses: actions/cache/save@v4\n with:\n path: |{cache_files}\n key: ${{{{ steps.cache-restore.outputs.cache-primary-key }}}}\n"
);
(restore_cache, save_cache)
} else {
(String::new(), String::new())
};
if pipeline.driver.is_shell() {
let pipe_filename = format!(".pipe.{}.sh", env.master_pipeline);
std::fs::write(gh_dir.join(pipe_filename.as_str()), shell_script)?;
let cicd_script = GH_CICD_TEMPLATE
.replace("{pipeline-title}", env.master_pipeline)
.replace("{shell-script-path}", pipe_filename.as_str())
.replace("{triggers}", triggers.as_str())
.replace(
"{base-image}",
gh_opts
.map(|gh_opts| gh_opts.base_image.as_deref().unwrap_or("ubuntu-latest"))
.unwrap_or("ubuntu-latest"),
)
.replace("{artifacts-step}", artifacts_actions.as_str())
.replace("{preflight-steps}", preflight_steps.as_str())
.replace("{restore-cache}", restore_cache.as_str())
.replace("{save-cache}", save_cache.as_str());
std::fs::write(
gh_dir.join(format!("{}.yml", env.master_pipeline)),
cicd_script.as_str(),
)?;
} else {
let cicd_script = GH_CICD_DEPL_TEMPLATE
.replace("{pipeline-title}", env.master_pipeline)
.replace("{triggers}", triggers.as_str())
.replace(
"{base-image}",
gh_opts
.map(|gh_opts| gh_opts.base_image.as_deref().unwrap_or("ubuntu-latest"))
.unwrap_or("ubuntu-latest"),
)
.replace("{artifacts-step}", artifacts_actions.as_str())
.replace("{preflight-steps}", preflight_steps.as_str())
.replace("{restore-cache}", restore_cache.as_str())
.replace("{save-cache}", save_cache.as_str());
std::fs::write(
gh_dir.join(format!("{}.yml", env.master_pipeline)),
cicd_script.as_str(),
)?;
}
Ok(())
}