From 54a271e6dc060f2cf9ef54201d18c7ff981bd733 Mon Sep 17 00:00:00 2001 From: Fernando Davis Date: Mon, 10 Aug 2026 18:25:34 -0400 Subject: [PATCH 1/2] delete prod acc and leave prd only --- .github/scripts/select_appsets.py | 2 +- .github/workflows/deploy-applicationset.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/select_appsets.py b/.github/scripts/select_appsets.py index 25b2c87..0c8aaf0 100644 --- a/.github/scripts/select_appsets.py +++ b/.github/scripts/select_appsets.py @@ -71,7 +71,7 @@ def environment_to_directory(environment): mapping = { "dev": "dev", "uat": "uat", - "prd": "prod", + "prd": "prd", } if environment not in mapping: diff --git a/.github/workflows/deploy-applicationset.yaml b/.github/workflows/deploy-applicationset.yaml index 018c8d9..6870b25 100644 --- a/.github/workflows/deploy-applicationset.yaml +++ b/.github/workflows/deploy-applicationset.yaml @@ -436,7 +436,7 @@ jobs: uat) echo "uat" ;; - prod|prd) + prd) echo "prd" ;; *) From 7f3a1b326356f742349fd6eb00e98741ff304880 Mon Sep 17 00:00:00 2001 From: Fernando Davis Date: Mon, 10 Aug 2026 20:36:12 -0400 Subject: [PATCH 2/2] add env to clusters validations --- .github/config/environment-clusters.yaml | 27 ++++ .github/config/tribe-projects.yaml | 7 +- .github/scripts/resolve-cluster.py | 121 ++++++++++++++++++ .github/scripts/select_appsets.py | 42 +++--- .github/workflows/deploy-applicationset.yaml | 128 ++++++++++--------- 5 files changed, 243 insertions(+), 82 deletions(-) create mode 100644 .github/config/environment-clusters.yaml create mode 100644 .github/scripts/resolve-cluster.py diff --git a/.github/config/environment-clusters.yaml b/.github/config/environment-clusters.yaml new file mode 100644 index 0000000..169a2e3 --- /dev/null +++ b/.github/config/environment-clusters.yaml @@ -0,0 +1,27 @@ +allowed_clusters: + - npe + - uat + - prd + +environments: + + dev: + cluster: npe + + uat: + cluster: uat + + prd: + cluster: prd + + int-au: + cluster: npe + + int-br: + cluster: npe + + int-ca: + cluster: npe + + int-in: + cluster: npe \ No newline at end of file diff --git a/.github/config/tribe-projects.yaml b/.github/config/tribe-projects.yaml index 115dc4c..b7a3bd9 100644 --- a/.github/config/tribe-projects.yaml +++ b/.github/config/tribe-projects.yaml @@ -78,4 +78,9 @@ tribes: enabled: false allowed_projects: - dobsy - - core-sre \ No newline at end of file + - core-sre + + xump: + enabled: true + allowed_projects: + - xump \ No newline at end of file diff --git a/.github/scripts/resolve-cluster.py b/.github/scripts/resolve-cluster.py new file mode 100644 index 0000000..32fca76 --- /dev/null +++ b/.github/scripts/resolve-cluster.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 + +import argparse +from pathlib import Path +import sys + +import yaml + + +def main(): + parser = argparse.ArgumentParser() + + parser.add_argument( + "--config", + required=True, + help="Environment to cluster config file", + ) + + parser.add_argument( + "--environment", + required=True, + help="Environment name or directory name", + ) + + args = parser.parse_args() + + config_file = Path(args.config) + environment = args.environment.strip() + + if not config_file.exists(): + print( + f"ERROR: Config file does not exist: {config_file}", + file=sys.stderr, + ) + sys.exit(1) + + if not environment: + print("ERROR: Environment cannot be empty", file=sys.stderr) + sys.exit(1) + + if "/" in environment or "\\" in environment: + print( + f"ERROR: Invalid environment '{environment}'. " + "Environment must be a directory name only.", + file=sys.stderr, + ) + sys.exit(1) + + if environment in [".", ".."]: + print( + f"ERROR: Invalid environment '{environment}'.", + file=sys.stderr, + ) + sys.exit(1) + + with open(config_file, "r", encoding="utf-8") as f: + document = yaml.safe_load(f) or {} + + allowed_clusters = document.get("allowed_clusters", []) + environments = document.get("environments", {}) + + if allowed_clusters and not isinstance(allowed_clusters, list): + print( + "ERROR: 'allowed_clusters' must be a YAML list", + file=sys.stderr, + ) + sys.exit(1) + + if not isinstance(environments, dict): + print( + "ERROR: 'environments' must be a YAML mapping", + file=sys.stderr, + ) + sys.exit(1) + + if environment not in environments: + print( + f"ERROR: Environment '{environment}' is not configured in {config_file}", + file=sys.stderr, + ) + print(file=sys.stderr) + print("Configured environments:", file=sys.stderr) + + for configured_environment in sorted(environments.keys()): + print(f" - {configured_environment}", file=sys.stderr) + + sys.exit(1) + + environment_config = environments[environment] + + if not isinstance(environment_config, dict): + print( + f"ERROR: Environment '{environment}' must be a mapping", + file=sys.stderr, + ) + sys.exit(1) + + cluster = environment_config.get("cluster") + + if not cluster: + print( + f"ERROR: Environment '{environment}' does not define a cluster", + file=sys.stderr, + ) + sys.exit(1) + + cluster = str(cluster).strip() + + if allowed_clusters and cluster not in allowed_clusters: + print( + f"ERROR: Cluster '{cluster}' for environment '{environment}' " + "is not listed in allowed_clusters", + file=sys.stderr, + ) + sys.exit(1) + + print(cluster) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/.github/scripts/select_appsets.py b/.github/scripts/select_appsets.py index 0c8aaf0..a05ad04 100644 --- a/.github/scripts/select_appsets.py +++ b/.github/scripts/select_appsets.py @@ -68,19 +68,23 @@ def load_changed_files(filename): def environment_to_directory(environment): - mapping = { - "dev": "dev", - "uat": "uat", - "prd": "prd", - } + environment = environment.strip() + + if not environment: + raise ValueError("Environment cannot be empty") - if environment not in mapping: + if "/" in environment or "\\" in environment: raise ValueError( f"Invalid environment '{environment}'. " - "Expected one of: dev, uat, prd" + "Environment must be a directory name only." ) - return mapping[environment] + if environment in [".", ".."]: + raise ValueError( + f"Invalid environment '{environment}'." + ) + + return environment def application_sets_for_tribe(tribe): @@ -177,7 +181,7 @@ def main(): parser.add_argument( "--target-environment", required=False, - help="Select only one environment: dev, uat, prd", + help="Select only one environment", ) args = parser.parse_args() @@ -209,15 +213,6 @@ def main(): selected = set() - # Manual workflow_dispatch path: - # - # --target-tribe dfa - # --target-environment dev - # - # selects exactly: - # - # dfa/dev/applicationset.yaml - if args.target_tribe or args.target_environment: if not args.target_tribe or not args.target_environment: print() @@ -252,10 +247,13 @@ def main(): print("ERROR: Selected ApplicationSet file does not exist.") print(f"Expected file: {target_file}") print() - print("Current mapping:") - print(" environment dev -> directory dev") - print(" environment uat -> directory uat") - print(" environment prd -> directory prod") + print("Environment and directory names are expected to match.") + print() + print("Examples:") + print(" target_environment dev -> directory dev") + print(" target_environment uat -> directory uat") + print(" target_environment prd -> directory prd") + print(" target_environment int-au -> directory int-au") sys.exit(1) print(f"SELECTED: {target_file}") diff --git a/.github/workflows/deploy-applicationset.yaml b/.github/workflows/deploy-applicationset.yaml index 6870b25..e91e82f 100644 --- a/.github/workflows/deploy-applicationset.yaml +++ b/.github/workflows/deploy-applicationset.yaml @@ -10,7 +10,7 @@ on: - 'feature/**' paths: - '*/*/applicationset.yaml' - - '.github/config/tribe-projects.yaml' + - '.github/config/**' - '.github/scripts/**' - '.github/workflows/deploy-applicationset.yaml' @@ -22,7 +22,7 @@ on: - 'feature/**' paths: - '*/*/applicationset.yaml' - - '.github/config/tribe-projects.yaml' + - '.github/config/**' - '.github/scripts/**' - '.github/workflows/deploy-applicationset.yaml' @@ -36,9 +36,10 @@ on: options: - dfa - ige + - xump target_environment: - description: "Environment. prd maps to the prod directory." + description: "Environment" required: true default: dev type: choice @@ -46,6 +47,10 @@ on: - dev - uat - prd + - int-au + - int-br + - int-ca + - int-in clusterName: description: "Target Kubernetes cluster" @@ -130,7 +135,7 @@ jobs: shell: bash run: | - set -e + set -euo pipefail BASE_SHA="${{ steps.commits.outputs.base }}" HEAD_SHA="${{ steps.commits.outputs.head }}" @@ -177,6 +182,33 @@ jobs: -- '*/*/applicationset.yaml' \ > /tmp/appset-files-all.txt + if git diff \ + --name-only \ + "${BASE_SHA}" \ + "${HEAD_SHA}" \ + -- '.github/config/environment-clusters.yaml' \ + '.github/scripts/resolve_cluster.py' \ + '.github/workflows/deploy-applicationset.yaml' \ + | grep -q .; then + + echo + echo "Environment cluster config, resolver script, or workflow changed." + echo "Adding all ApplicationSets from enabled tribes as validation candidates." + + find . \ + -mindepth 3 \ + -maxdepth 3 \ + -type f \ + -name applicationset.yaml \ + | sed 's#^\./##' \ + >> /tmp/appset-files-all.txt + + sort -u \ + /tmp/appset-files-all.txt \ + -o /tmp/appset-files-all.txt + + fi + fi echo @@ -184,20 +216,6 @@ jobs: cat /tmp/appset-files-all.txt || true - # ----------------------------------------------------- - # Manual branch policy: - # - # main: - # validate_appset, apply_appset, describe_appset, - # list_appset, delete_appset are allowed. - # - # dev and feature branches: - # validate_appset only. - # - # GitHub workflow_dispatch choices are static, so this - # guard enforces the rule at runtime. - # ----------------------------------------------------- - - name: Enforce manual action branch policy if: github.event_name == 'workflow_dispatch' shell: bash @@ -346,23 +364,14 @@ jobs: shell: bash run: | - set -e + set -euo pipefail - case "${{ inputs.target_environment }}" in - dev) - EXPECTED_CLUSTER="npe" - ;; - uat) - EXPECTED_CLUSTER="uat" - ;; - prd) - EXPECTED_CLUSTER="prd" - ;; - *) - echo "Invalid environment: ${{ inputs.target_environment }}" - exit 1 - ;; - esac + EXPECTED_CLUSTER="$( + python \ + .github/scripts/resolve_cluster.py \ + --config .github/config/environment-clusters.yaml \ + --environment "${{ inputs.target_environment }}" + )" if [ "${{ inputs.clusterName }}" != "${EXPECTED_CLUSTER}" ]; then @@ -376,6 +385,8 @@ jobs: fi echo "Environment/cluster match validated." + echo "Environment: ${{ inputs.target_environment }}" + echo "Cluster: ${EXPECTED_CLUSTER}" - name: Kubernetes server-side dry-run @@ -429,20 +440,10 @@ jobs: local environment_directory="$1" - case "${environment_directory}" in - dev) - echo "npe" - ;; - uat) - echo "uat" - ;; - prd) - echo "prd" - ;; - *) - echo "ERROR" - ;; - esac + python \ + .github/scripts/resolve_cluster.py \ + --config .github/config/environment-clusters.yaml \ + --environment "${environment_directory}" } @@ -459,9 +460,7 @@ jobs: else - cluster="$(cluster_for_environment_directory "${environment_directory}")" - - if [ "${cluster}" = "ERROR" ]; then + if ! cluster="$(cluster_for_environment_directory "${environment_directory}")"; then echo "ERROR: Cannot infer cluster from path: ${file}" echo "Environment directory: ${environment_directory}" exit 1 @@ -479,12 +478,14 @@ jobs: echo "Cluster: ${cluster}" echo "==========================================" - # configure_cluster "${cluster}" + configure_cluster "${cluster}" - # kubectl cluster-info - # kubectl get crd applicationsets.argoproj.io + kubectl cluster-info + kubectl get crd applicationsets.argoproj.io - # kubectl apply --dry-run=server -f "$file" + kubectl apply \ + --dry-run=server \ + -f "$file" done < /tmp/appset-files.txt @@ -607,28 +608,37 @@ jobs: echo "Running: kubectl apply -f ${file}" - kubectl apply -f "$file" + kubectl apply \ + -f "$file" + ;; describe_appset) echo "Running: kubectl describe -f ${file}" - kubectl describe -f "$file" + kubectl describe \ + -f "$file" + ;; list_appset) echo "Running: kubectl get -f ${file} -o wide" - kubectl get -f "$file" -o wide + kubectl get \ + -f "$file" \ + -o wide + ;; delete_appset) echo "Running: kubectl delete -f ${file}" - kubectl delete -f "$file" + kubectl delete \ + -f "$file" + ;; *)