From d91ef1bf3dc31acf609415e1e94841a7bf28bb08 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Thu, 11 Jun 2026 14:26:45 -0300 Subject: [PATCH 01/19] feat(gitops-update): support per-cluster env suffix variants Adds two additive, backward-compatible fields to the deployment matrix manifest to express clusters that host multiple parallel variants per environment (e.g. single-tenant -st + multi-tenant -mt on benedita): clusters: benedita: env_suffixes: ["-st", "-mt"] suffix_excludes_envs: ["sandbox"] The gitops-update workflow expands each tag-derived env (dev/stg/prd/sandbox) against the cluster's env_suffixes, except envs listed in suffix_excludes_envs which stay bare. The expanded env name flows through unchanged to the helmfile path resolution and the ArgoCD app name template, so 'benedita-midaz-dev-st' resolves naturally without any new workflow inputs. Cross apps (those with app_helmfile_env set, e.g. forge: cross) bypass the suffix expansion - they update once at the override path and their sync target uses the override env, so 'benedita-forge-cross' resolves correctly when the cluster renames its ArgoCD apps to follow the {server}-{app}-{env} convention. Defaults preserve byte-identical behavior for firmino/clotilde/anacleto: env_suffixes defaults to [""] (single empty-suffix expansion), suffix_excludes_envs defaults to []. Tag-derived env list for those clusters is unchanged. Also extends the deployment-matrix lint action to validate the new fields plus the pre-existing app_helmfile_env field (which was previously unvalidated). Closes #422 --- .github/workflows/gitops-update.yml | 84 ++++++++++++++++++++++++--- config/deployment-matrix.yml | 48 +++++++++++---- docs/gitops-update-workflow.md | 47 +++++++++++++++ src/lint/deployment-matrix/action.yml | 62 ++++++++++++++++++++ 4 files changed, 223 insertions(+), 18 deletions(-) diff --git a/.github/workflows/gitops-update.yml b/.github/workflows/gitops-update.yml index 7487e8ec..d8d7b5ad 100644 --- a/.github/workflows/gitops-update.yml +++ b/.github/workflows/gitops-update.yml @@ -344,6 +344,29 @@ jobs: echo "clusters=$FILTERED" >> "$GITHUB_OUTPUT" echo "has_clusters=true" >> "$GITHUB_OUTPUT" + # Emit a JSON map of per-cluster env_suffixes / suffix_excludes_envs for + # downstream steps. Defaults preserve the pre-multi-variant behavior: + # - env_suffixes defaults to [""] (one expansion, empty suffix) + # - suffix_excludes_envs defaults to [] + # Clusters that did not declare these fields therefore produce the exact + # same env list as before (e.g. dev/stg/prd/sandbox for a prod tag). + # Build a JSON array of resolved cluster names for jq. Splitting on the + # known single-space delimiter (FILTERED is built with " " separators + # above), then feeding each element to jq -R explicitly avoids the need + # to rely on word-splitting via `$FILTERED` (shellcheck SC2086). + CLUSTERS_JSON=$(printf '%s' "$FILTERED" | tr ' ' '\n' | jq -R . | jq -s .) + ENV_CONFIG=$(yq -o=json '.' "$MANIFEST" \ + | jq -c --argjson clusters "$CLUSTERS_JSON" ' + .clusters + | with_entries(select(.key as $k | $clusters | index($k))) + | with_entries(.value = { + env_suffixes: (.value.env_suffixes // [""]), + suffix_excludes_envs: (.value.suffix_excludes_envs // []) + }) + ') + echo "env_config=$ENV_CONFIG" >> "$GITHUB_OUTPUT" + echo "Per-cluster env config: $ENV_CONFIG" + - name: Git pull before update shell: bash run: | @@ -415,6 +438,7 @@ jobs: APP_NAME: ${{ steps.setup.outputs.app_name }} HAS_CLUSTERS: ${{ steps.resolve_clusters.outputs.has_clusters }} RESOLVED_SERVERS: ${{ steps.resolve_clusters.outputs.clusters }} + ENV_CONFIG: ${{ steps.resolve_clusters.outputs.env_config }} MAPPINGS: ${{ inputs.yaml_key_mappings }} CONFIGMAP_MAPPINGS: ${{ inputs.configmap_updates }} GITOPS_LAYOUT: ${{ inputs.gitops_layout }} @@ -519,9 +543,48 @@ jobs: return 1 } + # Compute the effective env list for a given server by expanding + # tag-derived envs against the cluster's env_suffixes, except for envs + # listed in suffix_excludes_envs which stay bare. Falls back to a single + # empty-suffix expansion when env_config is empty (e.g. kustomize layout + # with sentinel env "-", or clusters without the new fields declared). + expand_envs_for_server() { + local server="$1" + local base_envs="$2" + # Kustomize sentinel env "-" or empty config → return base_envs unchanged. + if [[ -z "$ENV_CONFIG" || "$base_envs" == "-" ]]; then + echo "$base_envs" + return + fi + local suffixes excludes + suffixes=$(echo "$ENV_CONFIG" | jq -r --arg s "$server" '.[$s].env_suffixes // [""] | .[]' 2>/dev/null || echo "") + excludes=$(echo "$ENV_CONFIG" | jq -r --arg s "$server" '.[$s].suffix_excludes_envs // [] | .[]' 2>/dev/null || echo "") + if [[ -z "$suffixes" ]]; then + echo "$base_envs" + return + fi + local out="" + for base_env in $base_envs; do + # If env is in the exclude list, keep it bare (no suffix expansion). + if echo "$excludes" | grep -qx "$base_env" 2>/dev/null; then + out="${out:+$out }$base_env" + continue + fi + while IFS= read -r suffix; do + out="${out:+$out }${base_env}${suffix}" + done <<< "$suffixes" + done + echo "$out" + } + # Process each server and environment for SERVER in $SERVERS; do - for ENV in $ENVIRONMENTS; do + SERVER_ENVS=$(expand_envs_for_server "$SERVER" "$ENVIRONMENTS") + if [[ "$SERVER_ENVS" != "$ENVIRONMENTS" ]]; then + echo "" + echo "Server $SERVER env expansion: '$ENVIRONMENTS' -> '$SERVER_ENVS'" + fi + for ENV in $SERVER_ENVS; do if [[ "$GITOPS_LAYOUT" == "kustomize" ]]; then # Resolve kustomize target dir (substitute ${SERVER} / ${ENV} placeholders). @@ -650,10 +713,14 @@ jobs: done < <(echo "$CONFIGMAP_MAPPINGS" | jq -r 'to_entries[] | "\(.key)|\(.value)"') fi - # Only track as updated if changes were actually made + # Only track as updated if changes were actually made. + # When app_helmfile_env overrides the env (e.g. forge: cross), the + # sync target's env must use the override (HELMFILE_ENV), not the + # tag-derived env, so the ArgoCD app name resolves correctly + # (e.g. benedita-forge-cross, not benedita-forge-dev-st). if [[ "$FILE_CHANGED" == "true" ]]; then UPDATED_FILES="${UPDATED_FILES}${VALUES_FILE}\n" - UPDATED_SERVERS_ENVS="${UPDATED_SERVERS_ENVS}${SERVER}:${ENV}\n" + UPDATED_SERVERS_ENVS="${UPDATED_SERVERS_ENVS}${SERVER}:${HELMFILE_ENV}\n" else echo " No changes needed for this file" fi @@ -678,14 +745,17 @@ jobs: echo -e "$MISSING_FILES" fi - # Save updated servers/envs for ArgoCD sync - only include files that actually changed + # Save updated servers/envs for ArgoCD sync - only include files that actually changed. + # Deduplicate (server:env) pairs: cross apps are visited once per outer env + # iteration (e.g. 7 times on benedita for a prod tag) but always write to + # the same override path, so a single sync target is correct. if [[ -n "$UPDATED_SERVERS_ENVS" ]]; then - echo -e "$UPDATED_SERVERS_ENVS" | grep -v '^$' > /tmp/updated_servers_envs.txt + echo -e "$UPDATED_SERVERS_ENVS" | grep -v '^$' | sort -u > /tmp/updated_servers_envs.txt # Output sync targets as JSON array for matrix job - SYNC_MATRIX=$(echo -e "$UPDATED_SERVERS_ENVS" | grep -v '^$' | while IFS=: read -r s e; do + SYNC_MATRIX=$(while IFS=: read -r s e; do [[ -n "$s" && -n "$e" ]] && echo "{\"server\":\"$s\",\"env\":\"$e\"}" - done | jq -s -c '.') + done < /tmp/updated_servers_envs.txt | jq -s -c '.') echo "sync_matrix=$SYNC_MATRIX" >> "$GITHUB_OUTPUT" echo "has_sync_targets=true" >> "$GITHUB_OUTPUT" echo "Sync matrix: $SYNC_MATRIX" diff --git a/config/deployment-matrix.yml b/config/deployment-matrix.yml index f8a67b48..9e12f9d5 100644 --- a/config/deployment-matrix.yml +++ b/config/deployment-matrix.yml @@ -9,10 +9,22 @@ # script described in docs/gitops-update-workflow.md (or grep across blocks). # # Schema: -# version integer, required, currently 1 -# apps.registry list of every app name allowed to appear in any cluster -# clusters. cluster block with explicit `apps:` list (no `all_apps` -# shortcut today — every cluster has its own subset) +# version integer, required, currently 1 +# apps.registry list of every app name allowed to appear in any cluster +# clusters. cluster block with explicit `apps:` list (no `all_apps` +# shortcut today — every cluster has its own subset) +# clusters..env_suffixes optional list of suffixes to append to each tag-derived +# env (e.g. ["-st", "-mt"] produces dev-st, dev-mt, stg-st, +# stg-mt, ...). Defaults to [""] (no suffix) for backward +# compatibility. Use this when one cluster hosts multiple +# parallel variants per environment (e.g. single-tenant and +# multi-tenant) as sibling namespaces / helmfile dirs / +# ArgoCD apps. +# clusters..suffix_excludes_envs +# optional list of tag-derived env names that should NOT +# receive any suffix from `env_suffixes` and stay bare +# (e.g. ["sandbox"] keeps sandbox as "sandbox", not +# "sandbox-st" / "sandbox-mt"). Defaults to []. # # Resolution at runtime: # 1. Workflow reads this file at the pinned ref of the shared-workflows repo. @@ -21,6 +33,10 @@ # 3. The `deploy_in_` workflow inputs act as **force-off** overrides: # setting any of them to `false` removes that cluster from the resolved # set even if the manifest includes the app. +# 4. For each resolved cluster, the tag-derived env list (e.g. dev/stg/prd/sandbox +# for a production tag) is expanded against `env_suffixes`, except for envs +# listed in `suffix_excludes_envs` which remain bare. The final env list is +# what the workflow uses to build helmfile paths and ArgoCD app names. # # Out of scope (managed manually or by other tooling — do NOT add here): # - underwriter, jd-mock-api, mock-btg-server, control-plane, @@ -128,17 +144,27 @@ clusters: - lerian-notification benedita: + # Benedita hosts two parallel variants per environment as sibling namespaces: + # single-tenant (-st): dev-st, stg-st, prd-st + # multi-tenant (-mt): dev-mt, stg-mt, prd-mt + # Sandbox is shared across variants and stays bare (no suffix). + env_suffixes: ["-st", "-mt"] + suffix_excludes_envs: ["sandbox"] + # app_helmfile_env: per-app override for the helmfile applications sub-directory. # Default path: environments/benedita/helmfile/applications/{env}/{app}/values.yaml - # where {env} = dev | stg | prd | sandbox (derived from the git tag type). - # Apps whose values.yaml lives under cross/ instead of dev/ must be listed here; - # otherwise the gitops-update workflow warns "values file not found" and skips them. + # where {env} = dev | stg | prd | sandbox (derived from the git tag type), with + # cluster-level env_suffixes applied (so the default for benedita is dev-st, + # dev-mt, stg-st, ... before this override). + # Apps whose values.yaml lives under cross/ instead of {env}/ must be listed + # here; otherwise the gitops-update workflow warns "values file not found" and + # skips them. When an override is set, env_suffixes are NOT applied for that app + # (it deploys exactly once at the override path). # # IMPORTANT: only list apps whose ArgoCD app name follows the standard - # {server}-{app}-{env} pattern (e.g. benedita-rosiehr-dev). Apps like - # cs-platform, forge, lerian-map use non-standard ArgoCD names (no -dev suffix) - # so their gitops-update sync target would resolve incorrectly. Add them here - # only after aligning their ArgoCD app naming with the standard convention. + # {server}-{app}-{env} pattern (e.g. benedita-rosiehr-cross, benedita-forge-cross). + # Apps with non-standard ArgoCD names (e.g. benedita-cs-platform without -cross) + # would have their sync target resolve incorrectly. Align ArgoCD naming first. app_helmfile_env: rosiehr: cross forge: cross diff --git a/docs/gitops-update-workflow.md b/docs/gitops-update-workflow.md index f5421937..af97d9d9 100644 --- a/docs/gitops-update-workflow.md +++ b/docs/gitops-update-workflow.md @@ -197,6 +197,53 @@ clusters: - Each `clusters..apps` is an explicit list of which apps this cluster hosts. - A cluster is added by appending one block. A cluster is removed by deleting it. Affects only this repo — caller workflows are untouched. +### Per-cluster env suffix variants (`-st`, `-mt`, ...) + +Some clusters host **multiple parallel variants per environment** as sibling namespaces, helmfile directories, and ArgoCD apps. For example, Benedita runs both single-tenant (`-st`) and multi-tenant (`-mt`) variants: + +``` +environments/benedita/helmfile/applications/ +├── dev-st/midaz/values.yaml +├── dev-mt/midaz/values.yaml +├── stg-st/midaz/values.yaml +├── stg-mt/midaz/values.yaml +├── prd-st/midaz/values.yaml +├── prd-mt/midaz/values.yaml +└── sandbox/midaz/values.yaml # shared, no suffix +``` + +Declare the suffixes on the cluster block: + +```yaml +clusters: + benedita: + env_suffixes: ["-st", "-mt"] # produces dev-st, dev-mt, stg-st, ... + suffix_excludes_envs: ["sandbox"] # sandbox stays bare (no suffix) + apps: [midaz, fetcher, ...] +``` + +| Field | Default | Effect | +|---|---|---| +| `env_suffixes` | `[""]` | List of suffixes appended to each tag-derived env. `[""]` (the default) preserves the pre-existing single-variant behavior. | +| `suffix_excludes_envs` | `[]` | Tag-derived env names that stay bare (no suffix expansion). Useful for shared envs like `sandbox`. | + +**Resolution on a production tag**, with the manifest above: + +1. Tag-type → env list (existing logic): `dev stg prd sandbox` +2. Cluster expansion (new logic): for benedita, expand each env not in `suffix_excludes_envs` against `env_suffixes`: + - `dev` → `dev-st`, `dev-mt` + - `stg` → `stg-st`, `stg-mt` + - `prd` → `prd-st`, `prd-mt` + - `sandbox` (excluded) → `sandbox` +3. Final env list for benedita: `dev-st dev-mt stg-st stg-mt prd-st prd-mt sandbox` (7 entries) +4. ArgoCD app name template (default `{server}-{app}-{env}`) resolves to `benedita-midaz-dev-st`, `benedita-midaz-dev-mt`, ..., `benedita-midaz-sandbox` + +Firmino, Clotilde, Anacleto behavior is byte-identical to before: with both fields absent, `env_suffixes` defaults to `[""]` (single empty-suffix expansion), so the final env list equals the tag-derived list verbatim. + +#### Interaction with `app_helmfile_env` + +When an app has an `app_helmfile_env` override (e.g. `forge: cross`), the override path takes precedence and **the suffix expansion is skipped for that app** — it updates once at the override path. The sync target's env is the override value (`cross`), so the ArgoCD app name resolves to `benedita-forge-cross`. + ### Adding a new app to a cluster 1. Open a PR in this repo editing `config/deployment-matrix.yml`: diff --git a/src/lint/deployment-matrix/action.yml b/src/lint/deployment-matrix/action.yml index f33f544a..9dd7aa6f 100644 --- a/src/lint/deployment-matrix/action.yml +++ b/src/lint/deployment-matrix/action.yml @@ -131,6 +131,68 @@ runs: err(f'clusters.{cluster_name}.apps lists "{app}" which is missing from apps.registry.') referenced_apps.add(app) + # ── env_suffixes (optional) ── + # List of strings; defaults to [""] (empty suffix) in the workflow. + # Allows clusters that host multiple parallel variants per environment + # (e.g. -st single-tenant + -mt multi-tenant on benedita). + env_suffixes = cluster_spec.get('env_suffixes') + if env_suffixes is not None: + if not isinstance(env_suffixes, list): + err(f'clusters.{cluster_name}.env_suffixes must be a list of strings.') + else: + if not env_suffixes: + err(f'clusters.{cluster_name}.env_suffixes is empty; omit the field or set ["", ...].') + seen_suffix = set() + for i, s in enumerate(env_suffixes): + if not isinstance(s, str): + err(f'clusters.{cluster_name}.env_suffixes[{i}] must be a string.') + continue + if s in seen_suffix: + err(f'Duplicate suffix "{s}" in clusters.{cluster_name}.env_suffixes.') + seen_suffix.add(s) + + # ── suffix_excludes_envs (optional) ── + # List of tag-derived env names that should NOT receive any suffix + # (e.g. ["sandbox"] keeps sandbox bare on a multi-variant cluster). + # Only meaningful when env_suffixes is set with non-empty suffixes. + suffix_excludes_envs = cluster_spec.get('suffix_excludes_envs') + valid_envs = {'dev', 'stg', 'prd', 'sandbox'} + if suffix_excludes_envs is not None: + if not isinstance(suffix_excludes_envs, list): + err(f'clusters.{cluster_name}.suffix_excludes_envs must be a list of env names.') + else: + seen_env = set() + for i, e in enumerate(suffix_excludes_envs): + if not isinstance(e, str) or not e.strip(): + err(f'clusters.{cluster_name}.suffix_excludes_envs[{i}] must be a non-empty string.') + continue + if e in seen_env: + err(f'Duplicate env "{e}" in clusters.{cluster_name}.suffix_excludes_envs.') + seen_env.add(e) + if e not in valid_envs: + warn(f'clusters.{cluster_name}.suffix_excludes_envs lists "{e}" which is not one of the tag-derived envs ({sorted(valid_envs)}); it will have no effect.') + if seen_env and not env_suffixes: + warn(f'clusters.{cluster_name}.suffix_excludes_envs is set but env_suffixes is not — suffix_excludes_envs will have no effect.') + + # ── app_helmfile_env (optional) ── + # Per-app override for the helmfile applications sub-directory. + # Existing field; validate keys reference apps in the cluster's app list. + app_helmfile_env = cluster_spec.get('app_helmfile_env') + if app_helmfile_env is not None: + if not isinstance(app_helmfile_env, dict): + err(f'clusters.{cluster_name}.app_helmfile_env must be a mapping of app -> env-name.') + else: + cluster_app_set = set(seen) + for k, v in app_helmfile_env.items(): + if not isinstance(k, str) or not k.strip(): + err(f'clusters.{cluster_name}.app_helmfile_env has a non-string key {k!r}.') + continue + if not isinstance(v, str) or not v.strip(): + err(f'clusters.{cluster_name}.app_helmfile_env["{k}"] must be a non-empty string.') + continue + if k not in cluster_app_set: + err(f'clusters.{cluster_name}.app_helmfile_env references app "{k}" which is not listed in clusters.{cluster_name}.apps.') + # ── orphans (warning only) ── orphans = registry_set - referenced_apps for app in sorted(orphans): From 4202bc5ccb75c19edbf459730f929dc40c2cfb64 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 23 Jun 2026 15:28:55 -0300 Subject: [PATCH 02/19] feat(gitops-update): support env_contexts for anacleto two-level path structure --- .github/workflows/gitops-update.yml | 41 +++++++++++++++++-------- .github/workflows/go-release.yml | 5 +++ config/deployment-matrix.yml | 15 +++++++++ docs/gitops-update-workflow.md | 44 +++++++++++++++++++++++++++ src/lint/deployment-matrix/action.yml | 24 +++++++++++++++ 5 files changed, 117 insertions(+), 12 deletions(-) diff --git a/.github/workflows/gitops-update.yml b/.github/workflows/gitops-update.yml index d8d7b5ad..f52119fb 100644 --- a/.github/workflows/gitops-update.yml +++ b/.github/workflows/gitops-update.yml @@ -361,7 +361,8 @@ jobs: | with_entries(select(.key as $k | $clusters | index($k))) | with_entries(.value = { env_suffixes: (.value.env_suffixes // [""]), - suffix_excludes_envs: (.value.suffix_excludes_envs // []) + suffix_excludes_envs: (.value.suffix_excludes_envs // []), + env_contexts: (.value.env_contexts // []) }) ') echo "env_config=$ENV_CONFIG" >> "$GITHUB_OUTPUT" @@ -544,10 +545,11 @@ jobs: } # Compute the effective env list for a given server by expanding - # tag-derived envs against the cluster's env_suffixes, except for envs - # listed in suffix_excludes_envs which stay bare. Falls back to a single - # empty-suffix expansion when env_config is empty (e.g. kustomize layout - # with sentinel env "-", or clusters without the new fields declared). + # tag-derived envs against the cluster's env_suffixes and env_contexts, + # except for envs listed in suffix_excludes_envs which stay bare. + # When env_contexts is set, each expansion is prefixed: {context}/{env-variant}. + # Falls back to a single empty-suffix expansion when env_config is empty + # (e.g. kustomize layout with sentinel env "-", or clusters without the new fields). expand_envs_for_server() { local server="$1" local base_envs="$2" @@ -556,23 +558,36 @@ jobs: echo "$base_envs" return fi - local suffixes excludes + local suffixes excludes contexts suffixes=$(echo "$ENV_CONFIG" | jq -r --arg s "$server" '.[$s].env_suffixes // [""] | .[]' 2>/dev/null || echo "") excludes=$(echo "$ENV_CONFIG" | jq -r --arg s "$server" '.[$s].suffix_excludes_envs // [] | .[]' 2>/dev/null || echo "") + contexts=$(echo "$ENV_CONFIG" | jq -r --arg s "$server" '.[$s].env_contexts // [] | .[]' 2>/dev/null || echo "") if [[ -z "$suffixes" ]]; then echo "$base_envs" return fi local out="" for base_env in $base_envs; do - # If env is in the exclude list, keep it bare (no suffix expansion). + # Expand base_env × suffixes (or keep bare if excluded). + local expanded="" if echo "$excludes" | grep -qx "$base_env" 2>/dev/null; then - out="${out:+$out }$base_env" - continue + expanded="$base_env" + else + while IFS= read -r suffix; do + expanded="${expanded:+$expanded }${base_env}${suffix}" + done <<< "$suffixes" + fi + # Apply contexts: prefix each expanded variant with "{context}/". + # When no contexts are declared, emit variants as-is. + if [[ -n "$contexts" ]]; then + while IFS= read -r ctx; do + for variant in $expanded; do + out="${out:+$out }${ctx}/${variant}" + done + done <<< "$contexts" + else + out="${out:+$out }$expanded" fi - while IFS= read -r suffix; do - out="${out:+$out }${base_env}${suffix}" - done <<< "$suffixes" done echo "$out" } @@ -860,6 +875,8 @@ jobs: NAME="${NAME//\{server\}/$SERVER}" NAME="${NAME//\{app\}/$APP}" NAME="${NAME//\{env\}/$ENV_NAME}" + # Normalize context-prefixed envs: "chaos/dev-st" → "chaos-dev-st". + NAME="${NAME//\//-}" # Collapse "--" sequences (sentinel env "-" produces "--" inside template). while [[ "$NAME" == *--* ]]; do NAME="${NAME//--/-}"; done # Strip trailing "-" left over when sentinel env "-" lands at the end. diff --git a/.github/workflows/go-release.yml b/.github/workflows/go-release.yml index 5ee852d2..a25ed107 100644 --- a/.github/workflows/go-release.yml +++ b/.github/workflows/go-release.yml @@ -85,6 +85,10 @@ on: description: 'JSON object mapping artifact names to YAML keys (e.g., {"app.tag": ".api.image.tag"})' type: string default: '' + deployment_matrix_ref: + description: 'Git ref of LerianStudio/github-actions-shared-workflows to read the deployment matrix from. Defaults to main. Override only when testing a branch.' + type: string + default: 'main' # ----------------- Shared ----------------- shared_paths: @@ -181,4 +185,5 @@ jobs: gitops_repository: ${{ inputs.gitops_repository }} artifact_pattern: ${{ inputs.gitops_artifact_pattern }} yaml_key_mappings: ${{ inputs.gitops_yaml_key_mappings }} + deployment_matrix_ref: ${{ inputs.deployment_matrix_ref }} secrets: inherit diff --git a/config/deployment-matrix.yml b/config/deployment-matrix.yml index 9e12f9d5..f991de1b 100644 --- a/config/deployment-matrix.yml +++ b/config/deployment-matrix.yml @@ -13,6 +13,15 @@ # apps.registry list of every app name allowed to appear in any cluster # clusters. cluster block with explicit `apps:` list (no `all_apps` # shortcut today — every cluster has its own subset) +# clusters..env_contexts optional list of context prefixes inserted before the +# env in the helmfile path. When set, the path becomes +# helmfile/applications/{context}/{env}/{app}/values.yaml +# instead of helmfile/applications/{env}/{app}/values.yaml. +# Combines with env_suffixes: full expansion is +# contexts × envs × suffixes. Example: env_contexts: +# ["chaos", "fuzzing"] + env_suffixes: ["-st", "-mt"] on a +# beta tag yields chaos/dev-st, chaos/dev-mt, +# fuzzing/dev-st, fuzzing/dev-mt. Defaults to [] (no prefix). # clusters..env_suffixes optional list of suffixes to append to each tag-derived # env (e.g. ["-st", "-mt"] produces dev-st, dev-mt, stg-st, # stg-mt, ...). Defaults to [""] (no suffix) for backward @@ -127,6 +136,12 @@ clusters: - lerian-notification anacleto: + # Anacleto hosts two parallel test types (chaos, fuzzing), each with two + # tenant variants per environment. The two-level path structure is: + # helmfile/applications/{context}/{env-with-suffix}/{app}/values.yaml + # where context ∈ {chaos, fuzzing} and env-with-suffix ∈ {dev-st, dev-mt}. + env_contexts: ["chaos", "fuzzing"] + env_suffixes: ["-st", "-mt"] apps: - midaz - fetcher diff --git a/docs/gitops-update-workflow.md b/docs/gitops-update-workflow.md index af97d9d9..b1a32c07 100644 --- a/docs/gitops-update-workflow.md +++ b/docs/gitops-update-workflow.md @@ -197,6 +197,50 @@ clusters: - Each `clusters..apps` is an explicit list of which apps this cluster hosts. - A cluster is added by appending one block. A cluster is removed by deleting it. Affects only this repo — caller workflows are untouched. +### Per-cluster env context directories (Anacleto) + +Some clusters organize their helmfile tree with an extra **context** level before the env. Anacleto uses this to separate chaos and fuzzing test suites: + +``` +environments/anacleto/helmfile/applications/ +├── chaos/ +│ ├── dev-st/{app}/values.yaml +│ └── dev-mt/{app}/values.yaml +└── fuzzing/ + ├── dev-st/{app}/values.yaml + └── dev-mt/{app}/values.yaml +``` + +Declare the contexts on the cluster block alongside `env_suffixes`: + +```yaml +clusters: + anacleto: + env_contexts: ["chaos", "fuzzing"] # path prefix before the env + env_suffixes: ["-st", "-mt"] # variant suffix appended to the env + apps: [midaz, fetcher, ...] +``` + +| Field | Default | Effect | +|---|---|---| +| `env_contexts` | `[]` | List of subdirectory prefixes inserted before the env in the helmfile path. When empty, no prefix is added (existing behavior). | +| `env_suffixes` | `[""]` | List of suffixes appended to each tag-derived env. | + +**Resolution on a beta tag** (env: `dev`), with the manifest above: + +1. Tag-type → base env: `dev` +2. Suffix expansion: `dev` → `dev-st`, `dev-mt` +3. Context expansion: for each context × each variant: + - `chaos` × `dev-st` → `chaos/dev-st` + - `chaos` × `dev-mt` → `chaos/dev-mt` + - `fuzzing` × `dev-st` → `fuzzing/dev-st` + - `fuzzing` × `dev-mt` → `fuzzing/dev-mt` +4. Final env list for anacleto: `chaos/dev-st chaos/dev-mt fuzzing/dev-st fuzzing/dev-mt` +5. Values paths: `environments/anacleto/helmfile/applications/{context/env}/{app}/values.yaml` +6. ArgoCD app name: `/` normalized to `-`, so `{server}-{app}-chaos/dev-st` → `anacleto-midaz-chaos-dev-st` + +Clusters without `env_contexts` (firmino, clotilde, benedita) are unaffected — the field defaults to `[]`. + ### Per-cluster env suffix variants (`-st`, `-mt`, ...) Some clusters host **multiple parallel variants per environment** as sibling namespaces, helmfile directories, and ArgoCD apps. For example, Benedita runs both single-tenant (`-st`) and multi-tenant (`-mt`) variants: diff --git a/src/lint/deployment-matrix/action.yml b/src/lint/deployment-matrix/action.yml index 9dd7aa6f..694f637c 100644 --- a/src/lint/deployment-matrix/action.yml +++ b/src/lint/deployment-matrix/action.yml @@ -174,6 +174,30 @@ runs: if seen_env and not env_suffixes: warn(f'clusters.{cluster_name}.suffix_excludes_envs is set but env_suffixes is not — suffix_excludes_envs will have no effect.') + # ── env_contexts (optional) ── + # List of context prefix strings inserted before the env in the helmfile path. + # When set, path becomes applications/{context}/{env}/{app}/ instead of + # applications/{env}/{app}/. Combines with env_suffixes for full expansion. + env_contexts = cluster_spec.get('env_contexts') + if env_contexts is not None: + if not isinstance(env_contexts, list): + err(f'clusters.{cluster_name}.env_contexts must be a list of strings.') + else: + if not env_contexts: + err(f'clusters.{cluster_name}.env_contexts is empty; omit the field or provide at least one context name.') + seen_ctx = set() + for i, c in enumerate(env_contexts): + if not isinstance(c, str) or not c.strip(): + err(f'clusters.{cluster_name}.env_contexts[{i}] must be a non-empty string.') + continue + if '/' in c: + err(f'clusters.{cluster_name}.env_contexts[{i}] "{c}" must not contain "/" (it is a single path segment).') + if c in seen_ctx: + err(f'Duplicate context "{c}" in clusters.{cluster_name}.env_contexts.') + seen_ctx.add(c) + if env_contexts and not env_suffixes: + warn(f'clusters.{cluster_name}.env_contexts is set but env_suffixes is not — contexts will host bare envs (no suffix variants).') + # ── app_helmfile_env (optional) ── # Per-app override for the helmfile applications sub-directory. # Existing field; validate keys reference apps in the cluster's app list. From f6226b2bbddf2e6af30bb87f2f1e705693457410 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 23 Jun 2026 16:39:00 -0300 Subject: [PATCH 03/19] feat(gitops-update): remove firmino and clotilde clusters; sync matrix with lerian-internal-gitops --- .github/workflows/gitops-update.yml | 24 +------ config/deployment-matrix.yml | 97 ++++++++--------------------- docs/gitops-update-workflow.md | 23 +++---- 3 files changed, 37 insertions(+), 107 deletions(-) diff --git a/.github/workflows/gitops-update.yml b/.github/workflows/gitops-update.yml index aaa5b270..87a360b8 100644 --- a/.github/workflows/gitops-update.yml +++ b/.github/workflows/gitops-update.yml @@ -1,7 +1,7 @@ name: "GitOps Update" # Reusable workflow for updating GitOps repository with new image tags -# Supports multiple servers (Firmino, Clotilde) and environments: dev (beta), stg (rc), prd (production), sandbox +# Supports multiple clusters and environments: dev (beta), stg (rc), prd (production), sandbox # Handles single or multiple components with flexible YAML key mapping on: @@ -15,14 +15,6 @@ on: description: 'Application name (defaults to repository name if not provided)' type: string required: false - deploy_in_firmino: - description: 'Force-off override for Firmino. Set to false to suppress deployment even if the manifest includes this app on Firmino.' - type: boolean - default: true - deploy_in_clotilde: - description: 'Force-off override for Clotilde. Set to false to suppress deployment even if the manifest includes this app on Clotilde.' - type: boolean - default: true deploy_in_anacleto: description: 'Force-off override for Anacleto. Set to false to suppress deployment even if the manifest includes this app on Anacleto.' type: boolean @@ -258,8 +250,6 @@ jobs: env: APP_NAME: ${{ steps.setup.outputs.app_name }} MANIFEST: shared-workflows/${{ inputs.deployment_matrix_file }} - DEPLOY_FIRMINO: ${{ inputs.deploy_in_firmino }} - DEPLOY_CLOTILDE: ${{ inputs.deploy_in_clotilde }} DEPLOY_ANACLETO: ${{ inputs.deploy_in_anacleto }} run: | set -euo pipefail @@ -310,18 +300,6 @@ jobs: while IFS= read -r cluster; do [[ -z "$cluster" ]] && continue case "$cluster" in - firmino) - if [[ "$DEPLOY_FIRMINO" == "false" ]]; then - echo " ✗ firmino — suppressed by deploy_in_firmino=false" - continue - fi - ;; - clotilde) - if [[ "$DEPLOY_CLOTILDE" == "false" ]]; then - echo " ✗ clotilde — suppressed by deploy_in_clotilde=false" - continue - fi - ;; anacleto) if [[ "$DEPLOY_ANACLETO" == "false" ]]; then echo " ✗ anacleto — suppressed by deploy_in_anacleto=false" diff --git a/config/deployment-matrix.yml b/config/deployment-matrix.yml index f991de1b..475825e9 100644 --- a/config/deployment-matrix.yml +++ b/config/deployment-matrix.yml @@ -43,16 +43,18 @@ # setting any of them to `false` removes that cluster from the resolved # set even if the manifest includes the app. # 4. For each resolved cluster, the tag-derived env list (e.g. dev/stg/prd/sandbox -# for a production tag) is expanded against `env_suffixes`, except for envs -# listed in `suffix_excludes_envs` which remain bare. The final env list is -# what the workflow uses to build helmfile paths and ArgoCD app names. +# for a production tag) is expanded against `env_suffixes` and `env_contexts`, +# except for envs listed in `suffix_excludes_envs` which remain bare. The final +# env list is what the workflow uses to build helmfile paths and ArgoCD app names. # # Out of scope (managed manually or by other tooling — do NOT add here): -# - underwriter, jd-mock-api, mock-btg-server, control-plane, +# - underwriter, jd-mock-api, mock-btg-server, fakebtg, control-plane, # platform-console (no CI commit history in gitops repo) # - ledger, dockerhub-secret (no values.yaml — kustomize / k8s Secret) # - hortencia-apps (legacy umbrella, not workflow-managed) # - environments/production/* (platform infra: addons, observability) +# - ungoliant-controller (anacleto, kustomize layout — declared here for registry +# purposes but deployed via gitops_layout=kustomize, not helmfile) version: 1 @@ -71,18 +73,16 @@ apps: # Plugins - plugin-access-manager # caller repo may push as plugin-identity / plugin-auth / casdoor - plugin-fees - - plugin-br-pix-direct-jd - plugin-br-pix-indirect-btg - - plugin-br-pix-switch # multi-component PIX hub (firmino-only today) - - plugin-br-bank-transfer # generic build (firmino + clotilde) - - plugin-br-bank-transfer-jd # JD-specific build (anacleto only) + - plugin-br-pix-switch + - plugin-br-bank-transfer + - plugin-br-payments - plugin-bc-correios - # Lerian internal platform suite (Clotilde + Benedita) + # Lerian internal platform suite (Benedita cross/) - backoffice-console - cs-platform - forge - - lerian-hq - lerian-map - tenant-manager @@ -90,51 +90,11 @@ apps: - ungoliant-controller # Benedita-exclusive - - severino-bot # internal Slack bot (benedita only) - - rosiehr # internal HR app (benedita only); values at cross/ — see app_helmfile_env below - - go-boilerplate-ddd # Go service boilerplate (reference/template); benedita only + - severino-bot # internal Slack bot (benedita only); kustomize-managed + - rosiehr # internal HR app (benedita only); values at cross/ + - go-boilerplate-ddd # Go service boilerplate (reference/template) clusters: - firmino: - apps: - - midaz - - fetcher - - flowker - - matcher - - reporter - - tracer - - product-console - - lerian-notification - - plugin-access-manager - - plugin-fees - - plugin-br-pix-direct-jd - - plugin-br-pix-indirect-btg - - plugin-br-pix-switch - - plugin-br-bank-transfer - - plugin-bc-correios - - clotilde: - apps: - - midaz - - fetcher - - flowker - - matcher - - reporter - - tracer - - product-console - - plugin-access-manager - - plugin-fees - - plugin-br-pix-direct-jd - - plugin-br-pix-indirect-btg - - plugin-br-bank-transfer - - plugin-bc-correios - - backoffice-console - - cs-platform - - forge - - lerian-map - - tenant-manager - - lerian-notification - anacleto: # Anacleto hosts two parallel test types (chaos, fuzzing), each with two # tenant variants per environment. The two-level path structure is: @@ -152,37 +112,34 @@ clusters: - product-console - plugin-access-manager - plugin-fees - - plugin-br-pix-direct-jd - plugin-br-pix-indirect-btg - - plugin-br-bank-transfer-jd + - plugin-br-pix-switch - ungoliant-controller - - lerian-notification benedita: # Benedita hosts two parallel variants per environment as sibling namespaces: # single-tenant (-st): dev-st, stg-st, prd-st - # multi-tenant (-mt): dev-mt, stg-mt, prd-mt + # multi-tenant (-mt): stg-mt (dev-mt and prd-mt do not exist) # Sandbox is shared across variants and stays bare (no suffix). env_suffixes: ["-st", "-mt"] suffix_excludes_envs: ["sandbox"] # app_helmfile_env: per-app override for the helmfile applications sub-directory. # Default path: environments/benedita/helmfile/applications/{env}/{app}/values.yaml - # where {env} = dev | stg | prd | sandbox (derived from the git tag type), with - # cluster-level env_suffixes applied (so the default for benedita is dev-st, - # dev-mt, stg-st, ... before this override). - # Apps whose values.yaml lives under cross/ instead of {env}/ must be listed - # here; otherwise the gitops-update workflow warns "values file not found" and - # skips them. When an override is set, env_suffixes are NOT applied for that app - # (it deploys exactly once at the override path). + # where {env} is the tag-derived env with env_suffixes applied (dev-st, stg-mt, ...). + # Apps whose values.yaml lives under cross/ must be listed here; otherwise the + # workflow warns "values file not found" and skips them. When an override is set, + # env_suffixes are NOT applied — the app deploys exactly once at the override path. # # IMPORTANT: only list apps whose ArgoCD app name follows the standard # {server}-{app}-{env} pattern (e.g. benedita-rosiehr-cross, benedita-forge-cross). - # Apps with non-standard ArgoCD names (e.g. benedita-cs-platform without -cross) - # would have their sync target resolve incorrectly. Align ArgoCD naming first. app_helmfile_env: - rosiehr: cross + backoffice-console: cross + cs-platform: cross forge: cross + lerian-map: cross + rosiehr: cross + tenant-manager: cross apps: - midaz - fetcher @@ -193,16 +150,16 @@ clusters: - product-console - plugin-access-manager - plugin-fees - - plugin-br-pix-direct-jd - plugin-br-pix-indirect-btg - plugin-br-bank-transfer + - plugin-br-payments + - plugin-bc-correios - backoffice-console - cs-platform - forge - - lerian-hq - lerian-map - tenant-manager - - severino-bot - lerian-notification - rosiehr - go-boilerplate-ddd + - severino-bot diff --git a/docs/gitops-update-workflow.md b/docs/gitops-update-workflow.md index b1a32c07..8aa4631f 100644 --- a/docs/gitops-update-workflow.md +++ b/docs/gitops-update-workflow.md @@ -114,8 +114,6 @@ update_gitops: |-------|------|---------|-------------| | `gitops_repository` | string | `LerianStudio/midaz-firmino-gitops` | GitOps repository to update | | `app_name` | string | (repo name) | Application name (auto-detected from repository) | -| `deploy_in_firmino` | boolean | `true` | Force-off override for Firmino (`false` = subtract from manifest-resolved set) | -| `deploy_in_clotilde` | boolean | `true` | Force-off override for Clotilde (`false` = subtract from manifest-resolved set) | | `deploy_in_anacleto` | boolean | `true` | Force-off override for Anacleto (`false` = subtract from manifest-resolved set) | | `deployment_matrix_file` | string | `config/deployment-matrix.yml` | Path to the deployment matrix manifest within the shared-workflows checkout | | `deployment_matrix_ref` | string | `main` | Git ref of `LerianStudio/github-actions-shared-workflows` to read the deployment matrix from. Default `main` ensures all callers see manifest updates immediately, regardless of the workflow ref they pin. Override only when testing a branch. | @@ -239,7 +237,7 @@ clusters: 5. Values paths: `environments/anacleto/helmfile/applications/{context/env}/{app}/values.yaml` 6. ArgoCD app name: `/` normalized to `-`, so `{server}-{app}-chaos/dev-st` → `anacleto-midaz-chaos-dev-st` -Clusters without `env_contexts` (firmino, clotilde, benedita) are unaffected — the field defaults to `[]`. +Clusters without `env_contexts` (benedita) are unaffected — the field defaults to `[]`. ### Per-cluster env suffix variants (`-st`, `-mt`, ...) @@ -328,7 +326,7 @@ gitops/environments//helmfile/applications///values.yaml ``` Where: -- ``: any cluster resolved from the deployment matrix (current set: `firmino`, `clotilde`, `anacleto`), minus those force-off via `deploy_in_: false` +- ``: any cluster resolved from the deployment matrix (current set: `anacleto`, `benedita`), minus those force-off via `deploy_in_: false` - ``: `dev`, `stg`, `prd`, or `sandbox` (determined by tag type) - ``: from `inputs.app_name` or auto-detected from repository name @@ -369,9 +367,8 @@ When `enable_argocd_sync` is `true`, the workflow syncs ArgoCD applications for ArgoCD apps are named using the pattern: `--` Examples: -- `firmino-midaz-dev`, `firmino-midaz-stg`, `firmino-midaz-prd` -- `clotilde-midaz-dev`, `clotilde-midaz-stg`, `clotilde-midaz-sandbox` -- `anacleto-midaz-dev` +- `anacleto-midaz-chaos-dev-st`, `anacleto-midaz-fuzzing-dev-mt` +- `benedita-midaz-dev-st`, `benedita-midaz-stg-mt`, `benedita-midaz-sandbox` ### Sync Behavior @@ -453,7 +450,7 @@ update_gitops: - `environment_detection`, `manual_environment` - Simplified to automatic detection only 2. **Inputs that became force-off overrides:** - - `deploy_in_firmino`, `deploy_in_clotilde`, `deploy_in_anacleto` (all default `true`) — only **subtract** clusters from the manifest-resolved set; cannot add a cluster the manifest does not list + - `deploy_in_anacleto` (default `true`) — only **subtracts** clusters from the manifest-resolved set; cannot add a cluster the manifest does not list 3. **New inputs:** - `deployment_matrix_file` (default: `config/deployment-matrix.yml`) — alternative manifest path for forks/testing @@ -471,11 +468,9 @@ update_gitops: > ⚠️ **Semantic change to `deploy_in_*` inputs** — callers that previously relied on `deploy_in_firmino: true` (etc.) to **include** a cluster will now silently deploy nowhere if their app is not listed in the manifest. The inputs only **subtract** from the manifest-resolved set; they never add. The prerequisite for any deployment is a manifest entry. Workflow logs a warning when `app_name` is missing from every cluster, so these cases surface quickly — but add your app to the manifest before merging this bump if you haven't already. -If your caller currently passes `deploy_in_firmino: true, deploy_in_clotilde: true` explicitly: - 1. Add your `app_name` to `apps.registry` and to the appropriate `clusters..apps` lists in [`config/deployment-matrix.yml`](../config/deployment-matrix.yml) (single PR in this repo). -2. Once merged and the caller bumps to the new shared-workflows ref (Renovate/Dependabot), the explicit `deploy_in_*: true` inputs become redundant and can be removed from the caller. -3. Keep `deploy_in_: false` only where you want to force-off a cluster the manifest declares. +2. Once merged and the caller bumps to the new shared-workflows ref (Renovate/Dependabot), any explicit `deploy_in_*: true` inputs become redundant and can be removed from the caller. +3. Keep `deploy_in_anacleto: false` only where you want to force-off that cluster. ## Troubleshooting @@ -485,11 +480,11 @@ This is normal if the tag already exists in the GitOps repository. The workflow ### Values file not found warnings -If you see warnings like "Values file not found for firmino/dev", this means the values.yaml file doesn't exist for that server/environment combination. The workflow will skip this combination and continue with others. +If you see warnings like "Values file not found for anacleto/chaos/dev-st", this means the values.yaml file doesn't exist for that server/environment combination. The workflow will skip this combination and continue with others. ### ArgoCD app does not exist -If you see warnings like "ArgoCD app firmino-myapp-dev does not exist, sync skipped", this means the ArgoCD application hasn't been created yet. The workflow will log a warning and continue. +If you see warnings like "ArgoCD app anacleto-myapp-chaos-dev-st does not exist, sync skipped", this means the ArgoCD application hasn't been created yet. The workflow will log a warning and continue. ### Artifact not found From dc901320ab054782ecec91c4d62fff594855194e Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 23 Jun 2026 16:39:38 -0300 Subject: [PATCH 04/19] feat(go-release): remove deploy_in_firmino and deploy_in_clotilde inputs --- .github/workflows/go-release.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.github/workflows/go-release.yml b/.github/workflows/go-release.yml index 67e7d0c7..a928da62 100644 --- a/.github/workflows/go-release.yml +++ b/.github/workflows/go-release.yml @@ -141,14 +141,6 @@ on: description: 'Prefix for the GitOps commit message. When empty, falls back to app_name_prefix, then the repository name.' type: string default: '' - deploy_in_firmino: - description: 'Force-off override for Firmino. Set to false to suppress deployment even if the manifest includes this app on Firmino.' - type: boolean - default: true - deploy_in_clotilde: - description: 'Force-off override for Clotilde. Set to false to suppress deployment even if the manifest includes this app on Clotilde.' - type: boolean - default: true use_dynamic_mapping: description: 'Use dynamic artifact-to-YAML key mapping in gitops-update' type: boolean @@ -476,8 +468,6 @@ jobs: deployment_matrix_ref: ${{ inputs.deployment_matrix_ref }} enable_argocd_sync: ${{ inputs.enable_argocd_sync }} commit_message_prefix: ${{ inputs.commit_message_prefix || inputs.app_name_prefix }} - deploy_in_firmino: ${{ inputs.deploy_in_firmino }} - deploy_in_clotilde: ${{ inputs.deploy_in_clotilde }} use_dynamic_mapping: ${{ inputs.use_dynamic_mapping }} configmap_updates: ${{ inputs.configmap_updates }} enable_docker_login: ${{ inputs.enable_docker_login }} From a287ecceb1f8df54027081cfa6320bdbde92cfc7 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 23 Jun 2026 17:03:17 -0300 Subject: [PATCH 05/19] feat(gitops-update): align deployment matrix with lerian-internal-gitops audit --- config/deployment-matrix.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/config/deployment-matrix.yml b/config/deployment-matrix.yml index 475825e9..5e980311 100644 --- a/config/deployment-matrix.yml +++ b/config/deployment-matrix.yml @@ -83,14 +83,15 @@ apps: - backoffice-console - cs-platform - forge + - gestao-acessos-console - lerian-map - tenant-manager - # Anacleto-exclusive - - ungoliant-controller + # Registry-only (kustomize-managed; deployed via gitops_layout=kustomize, not helmfile) + - ungoliant-controller # anacleto only + - severino-bot # benedita only; kustomize under benedita/kustomize/severino/ # Benedita-exclusive - - severino-bot # internal Slack bot (benedita only); kustomize-managed - rosiehr # internal HR app (benedita only); values at cross/ - go-boilerplate-ddd # Go service boilerplate (reference/template) @@ -114,7 +115,6 @@ clusters: - plugin-fees - plugin-br-pix-indirect-btg - plugin-br-pix-switch - - ungoliant-controller benedita: # Benedita hosts two parallel variants per environment as sibling namespaces: @@ -137,6 +137,7 @@ clusters: backoffice-console: cross cs-platform: cross forge: cross + gestao-acessos-console: cross lerian-map: cross rosiehr: cross tenant-manager: cross @@ -151,15 +152,16 @@ clusters: - plugin-access-manager - plugin-fees - plugin-br-pix-indirect-btg + - plugin-br-pix-switch - plugin-br-bank-transfer - plugin-br-payments - plugin-bc-correios - backoffice-console - cs-platform - forge + - gestao-acessos-console - lerian-map - tenant-manager - lerian-notification - rosiehr - go-boilerplate-ddd - - severino-bot From 99236a1bd82e5dee974af242fed5945fbee4937d Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 23 Jun 2026 17:40:32 -0300 Subject: [PATCH 06/19] feat(gitops-update): add update_sandbox boolean and resolve gitops_repository from org var --- .github/workflows/gitops-update.yml | 38 ++++++++++++++++++++--------- .github/workflows/go-release.yml | 9 +++++-- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/.github/workflows/gitops-update.yml b/.github/workflows/gitops-update.yml index 87a360b8..52994318 100644 --- a/.github/workflows/gitops-update.yml +++ b/.github/workflows/gitops-update.yml @@ -8,13 +8,17 @@ on: workflow_call: inputs: gitops_repository: - description: 'GitOps repository to update (org/repo format)' + description: 'GitOps repository to update (org/repo format). Defaults to the GITOPS_REPOSITORY org-level variable when empty.' type: string - default: 'LerianStudio/midaz-firmino-gitops' + default: '' app_name: description: 'Application name (defaults to repository name if not provided)' type: string required: false + update_sandbox: + description: 'Include sandbox environment on production tags. Set to false to skip sandbox even on a production release.' + type: boolean + default: true deploy_in_anacleto: description: 'Force-off override for Anacleto. Set to false to suppress deployment even if the manifest includes this app on Anacleto.' type: boolean @@ -101,6 +105,7 @@ jobs: sync_matrix: ${{ steps.apply_tags.outputs.sync_matrix }} has_sync_targets: ${{ steps.apply_tags.outputs.has_sync_targets }} env: + GITOPS_REPO: ${{ inputs.gitops_repository || vars.GITOPS_REPOSITORY }} IS_RC: ${{ contains(github.ref, '-rc.') }} IS_BETA: ${{ contains(github.ref, '-beta.') }} IS_SANDBOX: ${{ contains(github.ref, '-sandbox.') }} @@ -114,20 +119,24 @@ jobs: password: ${{ secrets.DOCKERHUB_IMAGE_PULL_TOKEN }} # Trust model: this is a `workflow_call` reusable workflow, NOT triggered by - # untrusted PRs. `inputs.gitops_repository` is supplied by trusted caller - # workflows (default: LerianStudio/midaz-firmino-gitops) and the MANAGE_TOKEN - # is required for the subsequent commit/push step. The allowlist step below - # enforces that the repository is a LerianStudio-owned `*-gitops` repo so a - # compromised caller cannot redirect the privileged checkout to an arbitrary - # target (CodeQL: `actions/untrusted-checkout`). + # untrusted PRs. The gitops repository is resolved from inputs.gitops_repository + # (caller-supplied) or the GITOPS_REPOSITORY org-level variable, and the + # MANAGE_TOKEN is required for the subsequent commit/push step. The allowlist + # step below enforces that the repository is a LerianStudio-owned `*-gitops` + # repo so a compromised caller cannot redirect the privileged checkout to an + # arbitrary target (CodeQL: `actions/untrusted-checkout`). - name: Validate gitops_repository against allowlist shell: bash env: - GITOPS_REPOSITORY: ${{ inputs.gitops_repository }} + GITOPS_REPOSITORY: ${{ env.GITOPS_REPO }} run: | set -euo pipefail + if [[ -z "$GITOPS_REPOSITORY" ]]; then + echo "::error::gitops_repository is empty. Set the input or the GITOPS_REPOSITORY org-level variable." + exit 1 + fi if [[ ! "$GITOPS_REPOSITORY" =~ ^LerianStudio/[A-Za-z0-9._-]+-gitops$ ]]; then - echo "::error::gitops_repository '$GITOPS_REPOSITORY' is not allowed. Must match ^LerianStudio/-gitops$ (e.g. LerianStudio/midaz-firmino-gitops)." + echo "::error::gitops_repository '$GITOPS_REPOSITORY' is not allowed. Must match ^LerianStudio/-gitops$ (e.g. LerianStudio/lerian-internal-gitops)." exit 1 fi echo "gitops_repository validated: $GITOPS_REPOSITORY" @@ -135,7 +144,7 @@ jobs: - name: Checkout GitOps Repository uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: - repository: ${{ inputs.gitops_repository }} + repository: ${{ env.GITOPS_REPO }} token: ${{ secrets.MANAGE_TOKEN }} path: gitops fetch-depth: 0 @@ -425,6 +434,7 @@ jobs: KUSTOMIZE_IMAGE_NAME: ${{ inputs.kustomize_image_name }} KUSTOMIZE_ENVIRONMENTS: ${{ inputs.kustomize_environments }} MANIFEST_FILE: shared-workflows/${{ inputs.deployment_matrix_file }} + UPDATE_SANDBOX: ${{ inputs.update_sandbox }} run: | set -euo pipefail @@ -436,7 +446,11 @@ jobs: ENVIRONMENTS="stg" ENV_LABEL="rc/stg" elif [[ "$IS_PRODUCTION" == "true" ]]; then - ENVIRONMENTS="dev stg prd sandbox" + if [[ "$UPDATE_SANDBOX" == "true" ]]; then + ENVIRONMENTS="dev stg prd sandbox" + else + ENVIRONMENTS="dev stg prd" + fi ENV_LABEL="production" elif [[ "$IS_SANDBOX" == "true" ]]; then ENVIRONMENTS="sandbox" diff --git a/.github/workflows/go-release.yml b/.github/workflows/go-release.yml index a928da62..188bb47d 100644 --- a/.github/workflows/go-release.yml +++ b/.github/workflows/go-release.yml @@ -114,9 +114,13 @@ on: type: boolean default: true gitops_repository: - description: 'GitOps repository to update (org/repo format)' + description: 'GitOps repository to update (org/repo format). Defaults to the GITOPS_REPOSITORY org-level variable when empty.' type: string - default: 'LerianStudio/midaz-firmino-gitops' + default: '' + update_sandbox: + description: 'Include sandbox environment on production tags. Set to false to skip sandbox even on a production release.' + type: boolean + default: true gitops_artifact_pattern: description: 'Pattern to download GitOps artifacts (defaults to "gitops-tags-*")' type: string @@ -462,6 +466,7 @@ jobs: with: runner_type: ${{ inputs.gitops_runner_type }} gitops_repository: ${{ inputs.gitops_repository }} + update_sandbox: ${{ inputs.update_sandbox }} app_name: ${{ inputs.app_name || inputs.app_name_prefix }} artifact_pattern: ${{ inputs.gitops_artifact_pattern || format('gitops-tags-{0}*', github.event.repository.name) }} yaml_key_mappings: ${{ inputs.gitops_yaml_key_mappings }} From d461244d71999996239f6e7df6d7501910ae4c38 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 23 Jun 2026 17:43:21 -0300 Subject: [PATCH 07/19] fix(gitops-update): set update_sandbox default to false --- .github/workflows/gitops-update.yml | 2 +- .github/workflows/go-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gitops-update.yml b/.github/workflows/gitops-update.yml index 52994318..12b99cb1 100644 --- a/.github/workflows/gitops-update.yml +++ b/.github/workflows/gitops-update.yml @@ -18,7 +18,7 @@ on: update_sandbox: description: 'Include sandbox environment on production tags. Set to false to skip sandbox even on a production release.' type: boolean - default: true + default: false deploy_in_anacleto: description: 'Force-off override for Anacleto. Set to false to suppress deployment even if the manifest includes this app on Anacleto.' type: boolean diff --git a/.github/workflows/go-release.yml b/.github/workflows/go-release.yml index 188bb47d..be8cf3d4 100644 --- a/.github/workflows/go-release.yml +++ b/.github/workflows/go-release.yml @@ -120,7 +120,7 @@ on: update_sandbox: description: 'Include sandbox environment on production tags. Set to false to skip sandbox even on a production release.' type: boolean - default: true + default: false gitops_artifact_pattern: description: 'Pattern to download GitOps artifacts (defaults to "gitops-tags-*")' type: string From 90c96a2617f69cf73df2772588a6037262a09f07 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 23 Jun 2026 18:11:51 -0300 Subject: [PATCH 08/19] feat(gitops-update): resolve runner from GITOPS_RUNNERS org-level variable --- .github/workflows/gitops-update.yml | 8 ++++---- .github/workflows/go-release.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/gitops-update.yml b/.github/workflows/gitops-update.yml index 12b99cb1..4a526f15 100644 --- a/.github/workflows/gitops-update.yml +++ b/.github/workflows/gitops-update.yml @@ -45,9 +45,9 @@ on: type: string required: false runner_type: - description: 'GitHub runner type to use' + description: 'GitHub runner type to use. Defaults to the GITOPS_RUNNERS org-level variable when empty.' type: string - default: 'blacksmith-4vcpu-ubuntu-2404' + default: '' enable_argocd_sync: description: 'Enable ArgoCD sync after GitOps update' type: boolean @@ -99,7 +99,7 @@ on: jobs: update_gitops: - runs-on: ${{ inputs.runner_type }} + runs-on: ${{ inputs.runner_type || vars.GITOPS_RUNNERS }} outputs: app_name: ${{ steps.setup.outputs.app_name }} sync_matrix: ${{ steps.apply_tags.outputs.sync_matrix }} @@ -839,7 +839,7 @@ jobs: name: ArgoCD Sync (${{ matrix.target.server }}/${{ matrix.target.env }}) needs: [update_gitops] if: needs.update_gitops.outputs.has_sync_targets == 'true' && inputs.enable_argocd_sync - runs-on: ${{ inputs.runner_type }} + runs-on: ${{ inputs.runner_type || vars.GITOPS_RUNNERS }} strategy: fail-fast: false matrix: diff --git a/.github/workflows/go-release.yml b/.github/workflows/go-release.yml index be8cf3d4..e3bcbcc3 100644 --- a/.github/workflows/go-release.yml +++ b/.github/workflows/go-release.yml @@ -134,9 +134,9 @@ on: type: string default: 'main' gitops_runner_type: - description: 'Runner for the gitops-update (deploy) job. Deploy needs cluster access, so this defaults to eveo-lxc-runners rather than the build runner.' + description: 'Runner for the gitops-update (deploy) job. Defaults to the GITOPS_RUNNERS org-level variable when empty.' type: string - default: 'eveo-lxc-runners' + default: '' enable_argocd_sync: description: 'Trigger ArgoCD sync after updating the GitOps repository' type: boolean From a4208092c6546c4e5a3d29dc3a2d0c26d69d16b7 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 23 Jun 2026 18:15:36 -0300 Subject: [PATCH 09/19] feat(gitops-update): remove runner_type input; runner resolved exclusively from GITOPS_RUNNERS org var --- .github/workflows/gitops-update.yml | 8 ++------ .github/workflows/go-release.yml | 5 ----- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/gitops-update.yml b/.github/workflows/gitops-update.yml index 4a526f15..68b40739 100644 --- a/.github/workflows/gitops-update.yml +++ b/.github/workflows/gitops-update.yml @@ -44,10 +44,6 @@ on: description: 'Prefix for commit message (defaults to repository name if not provided)' type: string required: false - runner_type: - description: 'GitHub runner type to use. Defaults to the GITOPS_RUNNERS org-level variable when empty.' - type: string - default: '' enable_argocd_sync: description: 'Enable ArgoCD sync after GitOps update' type: boolean @@ -99,7 +95,7 @@ on: jobs: update_gitops: - runs-on: ${{ inputs.runner_type || vars.GITOPS_RUNNERS }} + runs-on: ${{ vars.GITOPS_RUNNERS }} outputs: app_name: ${{ steps.setup.outputs.app_name }} sync_matrix: ${{ steps.apply_tags.outputs.sync_matrix }} @@ -839,7 +835,7 @@ jobs: name: ArgoCD Sync (${{ matrix.target.server }}/${{ matrix.target.env }}) needs: [update_gitops] if: needs.update_gitops.outputs.has_sync_targets == 'true' && inputs.enable_argocd_sync - runs-on: ${{ inputs.runner_type || vars.GITOPS_RUNNERS }} + runs-on: ${{ vars.GITOPS_RUNNERS }} strategy: fail-fast: false matrix: diff --git a/.github/workflows/go-release.yml b/.github/workflows/go-release.yml index e3bcbcc3..2b58dc15 100644 --- a/.github/workflows/go-release.yml +++ b/.github/workflows/go-release.yml @@ -133,10 +133,6 @@ on: description: 'Git ref of LerianStudio/github-actions-shared-workflows to read the deployment matrix from. Defaults to main. Override only when testing a branch.' type: string default: 'main' - gitops_runner_type: - description: 'Runner for the gitops-update (deploy) job. Defaults to the GITOPS_RUNNERS org-level variable when empty.' - type: string - default: '' enable_argocd_sync: description: 'Trigger ArgoCD sync after updating the GitOps repository' type: boolean @@ -464,7 +460,6 @@ jobs: packages: write uses: ./.github/workflows/gitops-update.yml with: - runner_type: ${{ inputs.gitops_runner_type }} gitops_repository: ${{ inputs.gitops_repository }} update_sandbox: ${{ inputs.update_sandbox }} app_name: ${{ inputs.app_name || inputs.app_name_prefix }} From 69b5f51896217714c37453b3ae0c29bc4d2bb053 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 23 Jun 2026 18:45:11 -0300 Subject: [PATCH 10/19] fix(gitops-update): skip yq download when already installed on runner --- .github/workflows/gitops-update.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/gitops-update.yml b/.github/workflows/gitops-update.yml index 68b40739..f16676b7 100644 --- a/.github/workflows/gitops-update.yml +++ b/.github/workflows/gitops-update.yml @@ -193,12 +193,15 @@ jobs: shell: bash run: | set -euo pipefail - YQ_VERSION="${{ inputs.yq_version }}" - YQ_BIN="yq_linux_amd64" - YQ_URL="https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${YQ_BIN}" - - sudo curl -L "$YQ_URL" -o /usr/local/bin/yq - sudo chmod +x /usr/local/bin/yq + if command -v yq &>/dev/null; then + echo "yq already installed: $(yq --version)" + else + YQ_VERSION="${{ inputs.yq_version }}" + YQ_BIN="yq_linux_amd64" + YQ_URL="https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${YQ_BIN}" + sudo curl -L "$YQ_URL" -o /usr/local/bin/yq + sudo chmod +x /usr/local/bin/yq + fi yq --version - name: Install kustomize From 5cee0dafb5004f52f46a5c68c82c3ea9d886ea0d Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Wed, 24 Jun 2026 09:08:01 -0300 Subject: [PATCH 11/19] fix(gitops-update): skip argocd download when already installed on runner --- .github/workflows/gitops-update.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gitops-update.yml b/.github/workflows/gitops-update.yml index f16676b7..453ff797 100644 --- a/.github/workflows/gitops-update.yml +++ b/.github/workflows/gitops-update.yml @@ -848,9 +848,13 @@ jobs: shell: bash run: | set -euo pipefail - curl -sSL -o /tmp/argocd https://github.com/argoproj/argo-cd/releases/download/v3.0.6/argocd-linux-amd64 - sudo install -m 755 /tmp/argocd /usr/local/bin/argocd - argocd version --client + if command -v argocd &>/dev/null; then + echo "argocd already installed: $(argocd version --client --short 2>/dev/null || argocd version --client)" + else + curl -sSL -o /tmp/argocd https://github.com/argoproj/argo-cd/releases/download/v3.0.6/argocd-linux-amd64 + sudo install -m 755 /tmp/argocd /usr/local/bin/argocd + argocd version --client + fi - name: Resolve ArgoCD application name id: resolve_argocd_app From 735425669f6ac885ae59782348159f3ced6dd1ce Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Wed, 24 Jun 2026 09:12:46 -0300 Subject: [PATCH 12/19] fix(gitops-update): install yq and argocd without sudo using ~/.local/bin --- .github/workflows/gitops-update.yml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/gitops-update.yml b/.github/workflows/gitops-update.yml index 453ff797..e6cf80f4 100644 --- a/.github/workflows/gitops-update.yml +++ b/.github/workflows/gitops-update.yml @@ -197,10 +197,12 @@ jobs: echo "yq already installed: $(yq --version)" else YQ_VERSION="${{ inputs.yq_version }}" - YQ_BIN="yq_linux_amd64" - YQ_URL="https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${YQ_BIN}" - sudo curl -L "$YQ_URL" -o /usr/local/bin/yq - sudo chmod +x /usr/local/bin/yq + YQ_URL="https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" + mkdir -p ~/.local/bin + curl -sSL "$YQ_URL" -o ~/.local/bin/yq + chmod +x ~/.local/bin/yq + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + export PATH="$HOME/.local/bin:$PATH" fi yq --version @@ -851,8 +853,11 @@ jobs: if command -v argocd &>/dev/null; then echo "argocd already installed: $(argocd version --client --short 2>/dev/null || argocd version --client)" else - curl -sSL -o /tmp/argocd https://github.com/argoproj/argo-cd/releases/download/v3.0.6/argocd-linux-amd64 - sudo install -m 755 /tmp/argocd /usr/local/bin/argocd + mkdir -p ~/.local/bin + curl -sSL -o ~/.local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/v3.0.6/argocd-linux-amd64 + chmod +x ~/.local/bin/argocd + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + export PATH="$HOME/.local/bin:$PATH" argocd version --client fi From 929680cff46bf3a2f630ee0a73c88271773111d2 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Wed, 24 Jun 2026 09:39:19 -0300 Subject: [PATCH 13/19] feat(gitops-update): resolve ARGOCD_URL from org variable instead of secret --- .github/workflows/gitops-update.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gitops-update.yml b/.github/workflows/gitops-update.yml index e6cf80f4..2d495f3e 100644 --- a/.github/workflows/gitops-update.yml +++ b/.github/workflows/gitops-update.yml @@ -887,7 +887,7 @@ jobs: - name: Sync ArgoCD application shell: bash env: - ARGOCD_URL: ${{ secrets.ARGOCD_URL }} + ARGOCD_URL: ${{ vars.ARGOCD_URL }} ARGOCD_TOKEN: ${{ secrets.ARGOCD_GHUSER_TOKEN }} APP_NAME: ${{ steps.resolve_argocd_app.outputs.name }} ARGOCD_PRUNE: ${{ inputs.argocd_prune }} From bd782c14730baef4251ae81cc2fb88c1420c81a8 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Wed, 24 Jun 2026 09:46:33 -0300 Subject: [PATCH 14/19] feat(gitops-update): rename ARGOCD_GHUSER_TOKEN to ARGOCD_TOKEN --- .github/workflows/gitops-update.yml | 2 +- docs/gitops-update-workflow.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gitops-update.yml b/.github/workflows/gitops-update.yml index 2d495f3e..fe5dfda4 100644 --- a/.github/workflows/gitops-update.yml +++ b/.github/workflows/gitops-update.yml @@ -888,7 +888,7 @@ jobs: shell: bash env: ARGOCD_URL: ${{ vars.ARGOCD_URL }} - ARGOCD_TOKEN: ${{ secrets.ARGOCD_GHUSER_TOKEN }} + ARGOCD_TOKEN: ${{ secrets.ARGOCD_TOKEN }} APP_NAME: ${{ steps.resolve_argocd_app.outputs.name }} ARGOCD_PRUNE: ${{ inputs.argocd_prune }} run: | diff --git a/docs/gitops-update-workflow.md b/docs/gitops-update-workflow.md index 8aa4631f..bebb21d9 100644 --- a/docs/gitops-update-workflow.md +++ b/docs/gitops-update-workflow.md @@ -32,7 +32,7 @@ update_gitops: secrets: inherit ``` -> **Required Secrets**: `MANAGE_TOKEN`, `LERIAN_CI_CD_USER_NAME`, `LERIAN_CI_CD_USER_EMAIL`, `ARGOCD_GHUSER_TOKEN`, `ARGOCD_URL`, `DOCKER_USERNAME`, `DOCKER_PASSWORD` +> **Required Secrets**: `MANAGE_TOKEN`, `LERIAN_CI_CD_USER_NAME`, `LERIAN_CI_CD_USER_EMAIL`, `ARGOCD_TOKEN`, `ARGOCD_URL`, `DOCKER_USERNAME`, `DOCKER_PASSWORD` The workflow reads `config/deployment-matrix.yml` from the shared-workflows repo (by default from `main`, override via `deployment_matrix_ref`) and resolves the cluster set automatically based on `app_name`. No `deploy_in_*` inputs are required for the common case. @@ -149,7 +149,7 @@ update_gitops: | Secret | Description | |--------|-------------| -| `ARGOCD_GHUSER_TOKEN` | ArgoCD authentication token | +| `ARGOCD_TOKEN` | ArgoCD authentication token | | `ARGOCD_URL` | ArgoCD server URL | ### Required Secrets (Docker Hub) From b7d366b427c4496525b11e409edc19fb547bc9aa Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Wed, 24 Jun 2026 10:44:53 -0300 Subject: [PATCH 15/19] fix(gitops-update): fix env_contexts early return, argocd rc capture, and runner fallback --- .github/workflows/gitops-update.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/gitops-update.yml b/.github/workflows/gitops-update.yml index fe5dfda4..ff553771 100644 --- a/.github/workflows/gitops-update.yml +++ b/.github/workflows/gitops-update.yml @@ -95,7 +95,7 @@ on: jobs: update_gitops: - runs-on: ${{ vars.GITOPS_RUNNERS }} + runs-on: ${{ vars.GITOPS_RUNNERS || 'blacksmith-4vcpu-ubuntu-2404' }} outputs: app_name: ${{ steps.setup.outputs.app_name }} sync_matrix: ${{ steps.apply_tags.outputs.sync_matrix }} @@ -555,7 +555,7 @@ jobs: suffixes=$(echo "$ENV_CONFIG" | jq -r --arg s "$server" '.[$s].env_suffixes // [""] | .[]' 2>/dev/null || echo "") excludes=$(echo "$ENV_CONFIG" | jq -r --arg s "$server" '.[$s].suffix_excludes_envs // [] | .[]' 2>/dev/null || echo "") contexts=$(echo "$ENV_CONFIG" | jq -r --arg s "$server" '.[$s].env_contexts // [] | .[]' 2>/dev/null || echo "") - if [[ -z "$suffixes" ]]; then + if [[ -z "$suffixes" && -z "$contexts" ]]; then echo "$base_envs" return fi @@ -840,7 +840,7 @@ jobs: name: ArgoCD Sync (${{ matrix.target.server }}/${{ matrix.target.env }}) needs: [update_gitops] if: needs.update_gitops.outputs.has_sync_targets == 'true' && inputs.enable_argocd_sync - runs-on: ${{ vars.GITOPS_RUNNERS }} + runs-on: ${{ vars.GITOPS_RUNNERS || 'blacksmith-4vcpu-ubuntu-2404' }} strategy: fail-fast: false matrix: @@ -900,13 +900,13 @@ jobs: fi echo "::group::argocd app get $APP_NAME" - if ! argocd app get "$APP_NAME" --server "$ARGOCD_URL" --auth-token "$ARGOCD_TOKEN" --grpc-web; then - rc=$? - echo "::endgroup::" - echo "::warning::Failed to get ArgoCD app '$APP_NAME' (exit $rc). Skipping sync — check whether the app exists, auth is valid, and the user has 'applications, get' permission." + argocd_rc=0 + argocd app get "$APP_NAME" --server "$ARGOCD_URL" --auth-token "$ARGOCD_TOKEN" --grpc-web || argocd_rc=$? + echo "::endgroup::" + if [[ "$argocd_rc" -ne 0 ]]; then + echo "::warning::Failed to get ArgoCD app '$APP_NAME' (exit $argocd_rc). Skipping sync — check whether the app exists, auth is valid, and the user has 'applications, get' permission." exit 0 fi - echo "::endgroup::" echo "::group::argocd app sync $APP_NAME" for attempt in 1 2 3 4 5; do From 8030edeb86c6c0d5a9fe058e9f7ab6c683505afa Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Wed, 24 Jun 2026 10:47:03 -0300 Subject: [PATCH 16/19] fix(gitops-update): avoid code injection by passing yq_version via env instead of run interpolation --- .github/workflows/gitops-update.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gitops-update.yml b/.github/workflows/gitops-update.yml index ff553771..da6e4e43 100644 --- a/.github/workflows/gitops-update.yml +++ b/.github/workflows/gitops-update.yml @@ -191,12 +191,13 @@ jobs: - name: Install yq shell: bash + env: + YQ_VERSION: ${{ inputs.yq_version }} run: | set -euo pipefail if command -v yq &>/dev/null; then echo "yq already installed: $(yq --version)" else - YQ_VERSION="${{ inputs.yq_version }}" YQ_URL="https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" mkdir -p ~/.local/bin curl -sSL "$YQ_URL" -o ~/.local/bin/yq From a02431836bc239724f975fe624be9d5475d404b8 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Wed, 24 Jun 2026 11:04:56 -0300 Subject: [PATCH 17/19] docs(gitops-update): add lerian header, move ARGOCD_URL to variables, update features list --- docs/gitops-update-workflow.md | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/docs/gitops-update-workflow.md b/docs/gitops-update-workflow.md index bebb21d9..da564c48 100644 --- a/docs/gitops-update-workflow.md +++ b/docs/gitops-update-workflow.md @@ -1,22 +1,27 @@ -# GitOps Update Workflow + + + + + +
Lerian

gitops-update

-Reusable workflow for updating GitOps repository with new image tags across multiple servers and environments. +Reusable workflow for updating GitOps repository with new image tags across multiple clusters and environments. ## Features - **Manifest-driven topology**: Cluster membership per app is declared in [`config/deployment-matrix.yml`](../config/deployment-matrix.yml) — no caller-side configuration required to add a cluster to an existing app -- **Multi-server deployment**: Deploy to Firmino, Clotilde and/or Anacleto with dynamic path generation +- **Multi-cluster deployment**: Deploy to Anacleto and Benedita with dynamic path generation +- **Per-cluster env variants**: `env_suffixes` and `env_contexts` support multi-tenant (`-st`/`-mt`) and context-based (`chaos/`, `fuzzing/`) layouts - **Force-off overrides**: `deploy_in_` inputs can suppress a cluster declared in the manifest, useful for emergency containment without editing the manifest - **Convention-based configuration**: Auto-generates paths, names, and patterns from repository name - **Multi-environment support**: dev (beta), stg (rc), prd (production), sandbox -- **Production sync**: Production releases automatically update all environments (dev, stg, prd, sandbox) on all servers +- **Production sync**: Production releases automatically update all environments on all clusters - **File existence validation**: Graceful handling of missing values files with warnings (never fails) - **Flexible tag mapping**: Static or dynamic YAML key mapping - **Automatic environment detection**: Based on git tag suffix -- **ArgoCD integration**: Automatic sync for each server/environment combination where files were updated +- **ArgoCD integration**: Automatic sync for each cluster/environment combination where files were updated - **App existence check**: Verifies ArgoCD app exists before attempting sync -- **Docker Hub login**: Enabled by default to avoid rate limits -- **Customizable runners**: Support for different GitHub runner types +- **Org-level configuration**: Runner, gitops repo, and ArgoCD URL resolved from org variables (`GITOPS_RUNNERS`, `GITOPS_REPOSITORY`, `ARGOCD_URL`) ## Usage @@ -32,7 +37,9 @@ update_gitops: secrets: inherit ``` -> **Required Secrets**: `MANAGE_TOKEN`, `LERIAN_CI_CD_USER_NAME`, `LERIAN_CI_CD_USER_EMAIL`, `ARGOCD_TOKEN`, `ARGOCD_URL`, `DOCKER_USERNAME`, `DOCKER_PASSWORD` +> **Required Secrets**: `MANAGE_TOKEN`, `LERIAN_CI_CD_USER_NAME`, `LERIAN_CI_CD_USER_EMAIL`, `ARGOCD_TOKEN`, `DOCKER_USERNAME`, `DOCKER_PASSWORD` +> +> **Required Variables**: `GITOPS_REPOSITORY`, `GITOPS_RUNNERS`, `ARGOCD_URL` The workflow reads `config/deployment-matrix.yml` from the shared-workflows repo (by default from `main`, override via `deployment_matrix_ref`) and resolves the cluster set automatically based on `app_name`. No `deploy_in_*` inputs are required for the common case. @@ -150,7 +157,14 @@ update_gitops: | Secret | Description | |--------|-------------| | `ARGOCD_TOKEN` | ArgoCD authentication token | -| `ARGOCD_URL` | ArgoCD server URL | + +### Required Variables + +| Variable | Description | +|----------|-------------| +| `GITOPS_REPOSITORY` | GitOps repository to update (e.g. `LerianStudio/lerian-internal-gitops`) | +| `GITOPS_RUNNERS` | GitHub Actions runner label for the gitops/deploy jobs (e.g. `eveo-lxc-runners`) | +| `ARGOCD_URL` | ArgoCD server hostname without protocol (e.g. `argocd.eveo.lerian.net`) | ### Required Secrets (Docker Hub) From eb0f20fe14527d5a3fb2b2e556a709460f3b5987 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Wed, 24 Jun 2026 11:10:34 -0300 Subject: [PATCH 18/19] fix(deployment-matrix): remove kustomize-only apps from registry to clear lint warnings --- config/deployment-matrix.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/config/deployment-matrix.yml b/config/deployment-matrix.yml index 5e980311..9331974c 100644 --- a/config/deployment-matrix.yml +++ b/config/deployment-matrix.yml @@ -53,8 +53,8 @@ # - ledger, dockerhub-secret (no values.yaml — kustomize / k8s Secret) # - hortencia-apps (legacy umbrella, not workflow-managed) # - environments/production/* (platform infra: addons, observability) -# - ungoliant-controller (anacleto, kustomize layout — declared here for registry -# purposes but deployed via gitops_layout=kustomize, not helmfile) +# - ungoliant-controller (anacleto), severino-bot (benedita) — kustomize layout, +# deployed via gitops_layout=kustomize callers; not governed by this manifest version: 1 @@ -87,10 +87,6 @@ apps: - lerian-map - tenant-manager - # Registry-only (kustomize-managed; deployed via gitops_layout=kustomize, not helmfile) - - ungoliant-controller # anacleto only - - severino-bot # benedita only; kustomize under benedita/kustomize/severino/ - # Benedita-exclusive - rosiehr # internal HR app (benedita only); values at cross/ - go-boilerplate-ddd # Go service boilerplate (reference/template) From 1bcd98e43d826d969e31533fc2d1c31632d727ae Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Wed, 24 Jun 2026 11:12:40 -0300 Subject: [PATCH 19/19] fix(deployment-matrix): restore ungoliant-controller and severino-bot for kustomize cluster resolution --- config/deployment-matrix.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/config/deployment-matrix.yml b/config/deployment-matrix.yml index 9331974c..1a1b527d 100644 --- a/config/deployment-matrix.yml +++ b/config/deployment-matrix.yml @@ -53,8 +53,9 @@ # - ledger, dockerhub-secret (no values.yaml — kustomize / k8s Secret) # - hortencia-apps (legacy umbrella, not workflow-managed) # - environments/production/* (platform infra: addons, observability) -# - ungoliant-controller (anacleto), severino-bot (benedita) — kustomize layout, -# deployed via gitops_layout=kustomize callers; not governed by this manifest +# - ungoliant-controller and severino-bot use gitops_layout=kustomize — they ARE +# in this manifest for cluster resolution, but callers must set gitops_layout=kustomize, +# kustomize_base_path, and kustomize_image_name; no helmfile values.yaml exists for them version: 1 @@ -87,6 +88,10 @@ apps: - lerian-map - tenant-manager + # Kustomize-managed (gitops_layout=kustomize callers; no helmfile values.yaml) + - ungoliant-controller # anacleto only + - severino-bot # benedita only + # Benedita-exclusive - rosiehr # internal HR app (benedita only); values at cross/ - go-boilerplate-ddd # Go service boilerplate (reference/template) @@ -111,6 +116,7 @@ clusters: - plugin-fees - plugin-br-pix-indirect-btg - plugin-br-pix-switch + - ungoliant-controller benedita: # Benedita hosts two parallel variants per environment as sibling namespaces: @@ -161,3 +167,4 @@ clusters: - lerian-notification - rosiehr - go-boilerplate-ddd + - severino-bot