diff --git a/bin/genesis b/bin/genesis index 38ff5e19..7025e892 100755 --- a/bin/genesis +++ b/bin/genesis @@ -254,7 +254,37 @@ define_command("create", { "repository for the environment.", "force|f" => - "Create a new environment file even if it already exists." + "Create a new environment file even if it already exists.", + + "prior-env=s" => + "Name of the prior environment in the pipeline chain (the environment ". + "that must succeed before this one deploys). Only used when a CI ". + "provider is configured in #C{.genesis/config}. Omit or leave blank ". + "to designate this environment as a pipeline entrypoint.", + + "require-pr" => + "Require a pull-request gate before this environment deploys. ". + "Only used when a CI provider is configured in #C{.genesis/config}.", + + "manual" => + "Require a manual CI trigger before this environment deploys. ". + "Only used when a CI provider is configured in #C{.genesis/config}.", + + "no-commit" => + "Stage the new environment file but skip the git commit and ". + "environment branch creation. Only applies when a CI ". + "provider is configured.", + + "reason=s" => + "Commit message for the environment commit. Defaults to ". + "#C{Add environment }. Only applies when a CI ". + "provider is configured and #C{--no-commit} is not set.", + + "no-fetch" => + "Skip the pre-create refresh of pipeline environment branches from ". + "remote. By default all env branches are fetched in one round-trip ". + "before checking for existing branches. Use this flag when working ". + "offline or when the remote is temporarily unavailable.", ], deprecated_options => [ "^prefix=s" => @@ -466,11 +496,34 @@ define_command("deploy", { "specifically in the case of a failed partial deployments.\n\n". "This option will allow you to specify a specfic state file to use for ". "the deploy. You can use the `info` command to retrieve the state file", + + 'no-propagate' => + "On manual-provider pipelines, `genesis deploy` automatically runs ". + "`genesis propagate ` after a successful deploy to push the ". + "freshly certified state to downstream environments. Use this ". + "flag to skip that propagation. Non-manual providers (concourse, ". + "github-actions) own their own propagation and are unaffected by ". + "this flag.", + + 'pull!' => + "Pull propagated files from the prior environment's last successful ". + "deploy (or control HEAD for pipeline entry points) onto this ". + "environment branch before deploying. No-op when the environment ". + "is already current. Implied by #y{-F}/#y{--fix-checks}; use ". + "#y{--no-pull} to opt out. No-op for non-pipeline repositories.", + + 'no-fetch' => + "Skip the pre-deploy refresh of pipeline environment branches from ". + "remote. By default all env branches are fetched in one round-trip ". + "before reading branch state. Use this flag when working offline or ". + "when the remote is temporarily unavailable.", ], args => { 'reason' => "An optional reason for deploying the environment. This will be ". - "recorded in the exodus data for the deployment." + "recorded in the exodus data for the deployment. When omitted and ". + "the env is on a pipeline-managed branch, genesis derives the reason ". + "from the control commit range being deployed." } }); @@ -1191,13 +1244,15 @@ define_command("remove-secrets", { ### Repository Management commands {{{ # genesis init - initialize a new Genesis repository {{{ -define_command("init", { +define_command("repo-init", { summary => "Initialize a new Genesis deployment repository.", - usage => "init [-k KIT/VERSION|-l path/to/local/kit] [-d directory] [--vault target] [name]", + usage => "repo-init [-k KIT] [] [name]", + alias => 'init', description => "Create a new Genesis deployment repository, specific to the given kit type.", function_group => Genesis::Commands::REPOSITORY, scope => 'empty', + no_vault => 1, option_group => Genesis::Commands::REPO_OPTIONS, options => [ "kit|k=s" => @@ -1234,17 +1289,41 @@ define_command("init", { "the '-deployments' suffix, but the option is still available for ". "backward compatibility by setting the #Y{legacy_repo_suffix} option in ". "the Genesis configuration file \$HOME/.genesis/config.yml to true.", + + "no-commit" => + "Stage the new repository contents but skip the initial commit. ". + "By default, #C{repo-init} commits the new repository automatically.", + + "reason=s" => + "Commit message for the initial commit. Defaults to a message ". + "describing the new Genesis deployment. Ignored if #C{--no-commit} ". + "is set.", + + 'force|f' => + "If the target directory already exists, remove it and recreate ". + "(without this flag, you will be prompted to confirm ". + "replacement). Also bypasses the check that the enclosing git ". + "repository is clean when creating the new repo as a ". + "subdirectory; use with care.", + + 'skip-vault' => + "Defer vault configuration. The repo will be created without a ". + "secrets provider. You must configure one via #C{genesis secrets-provider} ". + "before creating environments.", + + 'with-ci' => + "Enable CI pipeline support. Sets up the control branch and ". + "configures the repo for branch-based pipeline topology. ". + "Environments created with #C{genesis new} will be prompted ". + "for pipeline metadata (prior_env, gates). Connect an ". + "automated provider later with #C{genesis repo config ci}.", ], - option_passthrough => 1, + extended_handlers => ['Genesis::Kit::Provider'], arguments => [ "name?" => "If the name argument is not specified, it will default to the same ". "name as the kit. You must specify either name or kit." ], - extended_usage => sub { - require Genesis::Kit::Provider; - Genesis::Kit::Provider->opts_help(); - } }); # }}} @@ -1302,12 +1381,86 @@ define_command("kit-provider", { "export-config" => "Export the current kit provider information." ], - extended_usage => sub { - require Genesis::Kit::Provider; - Genesis::Kit::Provider->opts_help(); - } + extended_handlers => ['Genesis::Kit::Provider'], }); +# }}} +# PARKED: Tristan's CI-only repo-init define_command block -- kept +# verbatim here for the upcoming merge meeting, but commented out so +# it does NOT override our phased repo-init definition earlier in this +# file. The handler sub has been renamed in lib/Genesis/Commands/Repo.pm +# to repo_configure_ci. If this block is ever re-enabled, the final +# dispatcher arg below must be updated to match that new name. +# +# genesis repo-init - one-time CI provider setup {{{ +# define_command("repo-init", { +# summary => "Initialize CI configuration for this Genesis deployment repository.", +# usage => "repo-init [--ci-provider PROVIDER] [--git-uri URI] [--git-branch BRANCH] [--vault-url URL] [--pipeline-name NAME]", +# description => +# "One-time setup that writes a #C{ci:} section to #C{.genesis/config} and ". +# "generates the #C{.genesis/ci/} scaffold files required by the pipeline ". +# "compiler.\n". +# "\n". +# "Errors if CI is already configured for this repository. Use ". +# "#C{genesis repo-update} to modify an existing configuration.\n". +# "\n". +# "When required flags are omitted an interactive wizard collects them.", +# function_group => Genesis::Commands::REPOSITORY, +# scope => 'repo', +# option_group => Genesis::Commands::REPO_OPTIONS, +# options => [ +# 'ci-provider|P=s' => +# "CI provider to configure: #C{concourse} (default), ". +# "#C{github-actions}, or #C{none}.", +# +# 'git-uri|g=s' => +# "Git repository URI (e.g. #C{git\@github.com:org/repo.git}).", +# +# 'git-branch|b=s' => +# "Default branch for the source-control integration. Defaults to #C{main}.", +# +# 'vault-url|V=s' => +# "Vault URL for the secrets integration ". +# "(e.g. #C{https://vault.example.com:8200}).", +# +# 'pipeline-name|n=s' => +# "Name written into #C{pipeline.yml} metadata. Defaults to the ". +# "deployment type.", +# ], +# }, 'Genesis::Commands::Repo::repo_configure_ci'); +# }}} +# genesis repo-update - idempotent CI config update {{{ +define_command("repo-update", { + summary => "Update CI configuration for this Genesis deployment repository.", + usage => "repo-update [--ci-provider PROVIDER] [--git-uri URI] [--git-branch BRANCH] [--vault-url URL] [--pipeline-name NAME]", + description => + "Idempotent counterpart to #C{genesis repo-init}. Updates the CI ". + "configuration in #C{.genesis/config} and regenerates missing scaffold ". + "files in #C{.genesis/ci/}.\n". + "\n". + "When called with flags, only the specified values are changed; ". + "everything else is left as-is. A bare invocation (no flags) launches ". + "an interactive wizard pre-populated with the current configuration.", + function_group => Genesis::Commands::REPOSITORY, + scope => 'repo', + option_group => Genesis::Commands::REPO_OPTIONS, + options => [ + 'ci-provider|P=s' => + "CI provider to configure: #C{concourse}, #C{github-actions}, or #C{none}.", + + 'git-uri|g=s' => + "Git repository URI.", + + 'git-branch|b=s' => + "Default branch for the source-control integration.", + + 'vault-url|V=s' => + "Vault URL for the secrets integration.", + + 'pipeline-name|n=s' => + "Name written into #C{pipeline.yml} metadata.", + ], +}, 'Genesis::Commands::Repo::repo_update'); # }}} # }}} @@ -1904,7 +2057,7 @@ define_command("pipeline-apply", { 'debug-dir=s' => "Write intermediate compiler artifacts to this directory.", ], -}, 'Genesis::Commands::Pipeline::apply'); +}, 'Genesis::Commands::Pipelines::apply'); # }}} # genesis pipeline-graph - write Mermaid pipeline.md {{{ define_command("pipeline-graph", { @@ -1923,7 +2076,67 @@ define_command("pipeline-graph", { 'platform|provider|p=s' => "CI provider: 'concourse' (default) or 'github-actions'.", ], -}, 'Genesis::Commands::Pipeline::graph'); +}, 'Genesis::Commands::Pipelines::pipeline_graph'); +# }}} +# genesis pipeline-status - show propagation state across all environments {{{ +define_command("pipeline-status", { + summary => "Show propagation status for all pipeline environments.", + usage => "pipeline-status", + description => + "Displays the propagation state of each environment in the pipeline ". + "DAG: last sync point, number of pending changes, and whether the ". + "environment is blocked by an ancestor that needs propagation first.", + function_group => Genesis::Commands::PIPELINE, + scope => 'repo', + option_group => Genesis::Commands::REPO_OPTIONS, + options => [ + 'no-fetch' => + "Skip the pre-status refresh of pipeline environment branches from ". + "remote. By default all env branches are fetched in one round-trip ". + "before reading branch state. Use this flag when working offline or ". + "when the remote is temporarily unavailable.", + ], +}, 'Genesis::Commands::Pipelines::pipeline_status'); +# }}} +# genesis propagate - propagate control branch changes to environment branches {{{ +define_command("propagate", { + summary => "Propagate changes from the control branch to environment branches.", + usage => "propagate [options] [env]", + description => + "Must be run from the control branch. Identifies files that have ". + "changed since each environment branch was last synced, and copies ". + "them to the appropriate environment branches.\n\n". + "Without arguments, determines entry points by walking the DAG and ". + "finding the first environment that uses each changed file.\n\n". + "With an environment name, scopes propagation to that environment's ". + "children, sourced from the named env's last-deployed control commit.", + function_group => Genesis::Commands::PIPELINE, + scope => 'repo', + option_group => Genesis::Commands::REPO_OPTIONS, + options => [ + 'dry-run|n' => + "Show what would be propagated without making changes.", + + 'commit=s' => + "Control branch commit SHA to propagate from. Defaults to ". + "HEAD of the control branch.", + + 'no-push' => + "Skip pushing control and env branches to the remote after ". + "propagation. By default, propagated branches are pushed.", + + 'no-fetch' => + "Skip the pre-propagate refresh of pipeline environment branches ". + "from remote. By default all env branches are fetched in one ". + "round-trip before computing diffs. Use this flag when working ". + "offline or when the remote is temporarily unavailable.", + ], + arguments => [ + 'env?' => + "Environment that was just deployed. Scopes propagation to ". + "its downstream children only.", + ], +}, 'Genesis::Commands::Pipelines::propagate'); # }}} # genesis pipeline-describe - human-readable pipeline description {{{ define_command("pipeline-describe", { @@ -1942,7 +2155,7 @@ define_command("pipeline-describe", { 'platform|provider|p=s' => "CI provider: 'concourse' (default) or 'github-actions'.", ], -}, 'Genesis::Commands::Pipeline::describe'); +}, 'Genesis::Commands::Pipelines::pipeline_describe'); # }}} # genesis pipeline-diff - show compiled vs live pipeline delta {{{ define_command("pipeline-diff", { @@ -1968,12 +2181,12 @@ define_command("pipeline-diff", { 'skip-vault' => "Skip vault connectivity when compiling.", ], -}, 'Genesis::Commands::Pipeline::diff'); +}, 'Genesis::Commands::Pipelines::diff'); # }}} -# genesis pipeline-status - show per-environment job health {{{ -define_command("pipeline-status", { - summary => "Show per-environment pipeline job health.", - usage => "pipeline-status []", +# genesis pipeline-jobs - show per-environment job health (Concourse) {{{ +define_command("pipeline-jobs", { + summary => "Show per-environment pipeline job health (Concourse).", + usage => "pipeline-jobs []", description => "Queries `fly jobs` and displays the status of each environment's ". "deployment job. Pass an environment name to filter to a single job.", @@ -1996,7 +2209,7 @@ define_command("pipeline-status", { 'skip-vault' => "Skip vault connectivity when compiling.", ], -}, 'Genesis::Commands::Pipeline::status'); +}, 'Genesis::Commands::Pipelines::status'); # }}} # genesis pipeline-pause - pause an environment job or entire pipeline {{{ define_command("pipeline-pause", { @@ -2024,7 +2237,7 @@ define_command("pipeline-pause", { 'skip-vault' => "Skip vault connectivity when compiling.", ], -}, 'Genesis::Commands::Pipeline::pause'); +}, 'Genesis::Commands::Pipelines::pause'); # }}} # genesis pipeline-resume - resume an environment job or entire pipeline {{{ define_command("pipeline-resume", { @@ -2052,7 +2265,7 @@ define_command("pipeline-resume", { 'skip-vault' => "Skip vault connectivity when compiling.", ], -}, 'Genesis::Commands::Pipeline::resume'); +}, 'Genesis::Commands::Pipelines::resume'); # }}} # }}} diff --git a/docs/workflows/auto-update.md b/docs/workflows/auto-update.md new file mode 100644 index 00000000..292bd24e --- /dev/null +++ b/docs/workflows/auto-update.md @@ -0,0 +1,291 @@ +# Auto-Update: update-genesis-assets + +The `update-genesis-assets` job automatically tracks new Genesis kit releases and +Genesis binary releases, bumps the version file in your repository, and commits +the result. This removes the manual step of editing `kit.yml` every time the +upstream kit or binary publishes a new version. + +--- + +## When to enable + +Enable `update-genesis-assets` when: + +- Your kit version is pinned in a file that the pipeline checks out (e.g. + `kit.yml`, `.genesis/kits/cf.yml`) +- You want each environment to pick up new versions automatically rather than + waiting for a manual commit +- You operate a standard GitOps flow where pipeline-created commits on a branch + trigger downstream jobs + +Do **not** enable it when: + +- The kit version is managed by another system (e.g. Dependabot, a vendor + lock-in policy) +- Your pipeline branch is frozen between releases and drift would cause + unexpected re-deploys + +--- + +## Configuration + +```yaml +configuration: + auto_update: + enabled: true + kit: cf # kit name (cf → cf-genesis-kit on GitHub) + org: genesis-community + file: kit.yml # repo-relative path to the version file + label: concourse # CI_LABEL in commit messages (default: concourse) + period: 24h # release check frequency (default: 24h) + update_kit: true # bump kit version (default: true) + update_genesis: true # bump genesis binary (default: true) + target_branch: auto-update # push to this branch instead of the control branch + auth_token: ((github-token)) # GitHub token for release checks (shared) + kit_auth_token: ((kit-token)) # per-kit override of auth_token + genesis_auth_token: ((genesis-token)) # per-genesis override of auth_token + api_url: https://github.example.com/api/v3 # GitHub Enterprise only +``` + +### Required fields + +| Field | Description | +|-------|-------------| +| `enabled: true` | Opt-in — the job is not emitted without this | +| `kit` | Kit name without the `-genesis-kit` suffix | +| `org` | GitHub organization that owns the kit repository | +| `file` | Path (relative to repo root) of the file Genesis reads for the kit version | + +All other fields have defaults or are omitted when not needed. + +--- + +## How it works + +### Resources emitted + +When `update_kit: true` (default): + +```yaml +- name: kit-release + type: github-release + icon: package-variant + check_every: 24h + source: + user: genesis-community + repository: cf-genesis-kit + access_token: ((github-token)) +``` + +When `update_genesis: true` (default): + +```yaml +- name: genesis-release + type: github-release + icon: leaf + check_every: 24h + source: + user: genesis-community + repository: genesis + access_token: ((github-token)) +``` + +When `target_branch` differs from the control branch: + +```yaml +- name: git-autoupdate + type: git + icon: source-branch + source: + uri: https://github.com/org/repo.git + branch: auto-update +``` + +### Job structure + +```yaml +- name: update-genesis-assets + plan: + - in_parallel: + - get: git + - get: kit-release + trigger: true + - get: genesis-release + trigger: true + - task: list-kits + ... + - task: update-genesis + ... + - task: fetch-kit + ... + - put: git # or git-autoupdate when target_branch is set + params: + repository: git + rebase: true +``` + +Both `kit-release` and `genesis-release` trigger the job independently — a +kit release does not need to coincide with a genesis release to trigger an +update run. + +### Task scripts + +**`list-kits`** — calls `genesis list-kits` and writes available versions. + +**`update-genesis`** — runs `genesis fetch` to update the genesis binary in +the worktree and commits: + +``` +[concourse] bump genesis to 2.8.14 +``` + +**`fetch-kit`** — runs `genesis fetch-kit` to download the new kit version, +updates `kit.yml`, and commits: + +``` +[concourse] bump kit cf to 2.3.1 +``` + +The label in square brackets is controlled by `configuration.auto_update.label` +(or the legacy `commit_label` key). + +--- + +## Selective updates + +### Kit only (no genesis binary) + +```yaml +configuration: + auto_update: + enabled: true + kit: cf + org: genesis-community + file: kit.yml + update_genesis: false +``` + +`genesis-release` is not checked out, the `update-genesis` task is not run, +and no `genesis-release` resource is emitted. + +### Genesis binary only (no kit bump) + +```yaml +configuration: + auto_update: + enabled: true + kit: cf + org: genesis-community + file: kit.yml + update_kit: false +``` + +`kit-release` is not checked out and `list-kits`/`fetch-kit` are not run. + +--- + +## Pushing to a separate branch + +By default the auto-commit is pushed back to the control branch (the same +branch the pipeline monitors). To send it to a feature or auto-update branch: + +```yaml +configuration: + auto_update: + enabled: true + kit: cf + org: genesis-community + file: kit.yml + target_branch: auto-update +``` + +A `git-autoupdate` resource is emitted that tracks `auto-update`. The final +`put:` step in the job uses `git-autoupdate` instead of `git`, so the +auto-commits never land directly on the control branch. A PR or manual merge +from `auto-update` → control branch then triggers the normal pipeline. + +--- + +## Auth + +### Shared token + +```yaml +auth_token: ((github-token)) +``` + +Used for both `kit-release` and `genesis-release`. + +### Per-resource overrides + +```yaml +auth_token: ((shared-token)) +kit_auth_token: ((kit-specific-token)) +genesis_auth_token: ((genesis-specific-token)) +``` + +`kit_auth_token` overrides `auth_token` for the kit resource only; +`genesis_auth_token` overrides it for the genesis resource only. + +### GitHub Enterprise + +```yaml +api_url: https://github.example.com/api/v3 +``` + +Applied only to the `kit-release` resource (the genesis binary is always +fetched from github.com). + +--- + +## Legacy key mapping + +The following keys from earlier pipeline formats are accepted and normalized: + +| Legacy key | Current key | +|------------|-------------| +| `kit_version_file` | `file` | +| `commit_label` | `label` | + +When `file` is present and `enabled` is not declared, `enabled` defaults to +`true` for backwards compatibility. + +--- + +## Auditing auto-commits + +Every commit the pipeline pushes follows the pattern: + +``` +[