# Reusable kind-based e2e pipeline, split into two jobs so the disk-heavy build # and the kind test run never share a runner. The build job compiles the # controller + e2e images and exports them as tarballs; the test job loads those # tarballs into kind and runs the suite, needing no Go toolchain or buildx. name: e2e reusable on: workflow_call: secrets: GCP_SERVICE_ACCOUNT_KEY: required: false GCP_FED_REGION: required: false GCP_GSA_NAME: required: false GCP_KSA_NAME: required: false GCP_FED_PROJECT_ID: required: false AWS_OIDC_ROLE_ARN: required: false AWS_SA_NAME: required: false AWS_SA_NAMESPACE: required: false TFC_AZURE_CLIENT_ID: required: false TFC_AZURE_CLIENT_SECRET: required: false TFC_AZURE_TENANT_ID: required: false TFC_AZURE_SUBSCRIPTION_ID: required: false TFC_VAULT_URL: required: false SCALEWAY_API_URL: required: false SCALEWAY_REGION: required: false SCALEWAY_PROJECT_ID: required: false SCALEWAY_ACCESS_KEY: required: false SCALEWAY_SECRET_KEY: required: false DELINEA_TLD: required: false DELINEA_URL_TEMPLATE: required: false DELINEA_TENANT: required: false DELINEA_CLIENT_ID: required: false DELINEA_CLIENT_SECRET: required: false SECRETSERVER_USERNAME: required: false SECRETSERVER_PASSWORD: required: false SECRETSERVER_URL: required: false GRAFANA_URL: required: false GRAFANA_TOKEN: required: false AKEYLESS_ACCESS_ID: required: false AKEYLESS_ACCESS_TYPE: required: false AKEYLESS_ACCESS_TYPE_PARAM: required: false GITLAB_TOKEN: required: false GITLAB_PROJECT_ID: required: false GITLAB_ENVIRONMENT: required: false ORACLE_USER_OCID: required: false ORACLE_TENANCY_OCID: required: false ORACLE_REGION: required: false ORACLE_FINGERPRINT: required: false ORACLE_KEY: required: false ORACLE_VAULT_OCID: required: false ORACLE_COMPARTMENT_OCID: required: false ORACLE_ENCRYPTION_KEY_OCID: required: false permissions: contents: read env: KIND_VERSION: 'v0.30.0' KIND_IMAGE: 'kindest/node:v1.33.4' AWS_REGION: "eu-central-1" # SHA under test on the fork path. Populated by a dispatcher: an explicit # `/ok-to-test sha=` comment (ok-to-test.yml), or the reviewed commit_id # of a PR review carrying /ok-to-test (ok-to-test-review.yml). Empty on the # trusted pull_request path, where the checkout falls back to github.sha (the # PR merge ref). TARGET_SHA: ${{ github.event.client_payload.slash_command.args.named.sha }} # Ephemeral tag: images are only ever loaded into kind, never pushed, so a # fixed tag keeps the build and test jobs in sync without passing a version. VERSION: "e2e" jobs: # Turn e2e/matrix.yaml into the test job's strategy matrix. Validating here # (check-matrix.sh) fails the run early if a provider was added to the suite # without a covering leg, rather than letting it go silently untested. prepare-matrix: runs-on: ubuntu-latest permissions: contents: read outputs: matrix: ${{ steps.set.outputs.matrix }} steps: - uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 with: egress-policy: audit - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ env.TARGET_SHA || github.sha }} persist-credentials: false - name: Validate and build the e2e matrix id: set # This job has no secrets in scope. matrix.py reads only matrix.yaml and # the workflow text, so the plan below proves per-leg credential scoping # without ever touching a secret value. run: | ./e2e/matrix.py check ./e2e/matrix.py plan matrix="$(./e2e/matrix.py json)" echo "matrix=${matrix}" >> "$GITHUB_OUTPUT" build: runs-on: ubuntu-latest permissions: contents: read steps: - uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 with: egress-policy: audit - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ env.TARGET_SHA || github.sha }} persist-credentials: false - name: Fetch History run: git fetch --prune --unshallow - name: Free Disk Space uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 with: tool-cache: true android: true dotnet: true haskell: true large-packages: false docker-images: false swap-storage: false - name: Setup Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod - name: Find the Go Cache id: go run: | echo "build-cache=$(go env GOCACHE)" >> "$GITHUB_OUTPUT" echo "mod-cache=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT" - name: Cache the Go Build Cache uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ${{ steps.go.outputs.build-cache }} key: ${{ runner.os }}-build-unit-tests-${{ github.sha }}-${{ hashFiles('**/go.sum') }} restore-keys: ${{ runner.os }}-build-unit-tests- - name: Cache Go Dependencies uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ${{ steps.go.outputs.mod-cache }} key: ${{ runner.os }}-pkg-${{ github.sha }}-${{ hashFiles('**/go.sum') }} restore-keys: ${{ runner.os }}-pkg- - name: Setup Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 with: install: true - name: Build images env: DOCKER_BUILD_ARGS: --load run: make -C e2e test.build - name: Upload image tarballs uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: e2e-images path: e2e/image-artifacts/*.tar retention-days: 1 test: needs: [build, prepare-matrix] # One leg per enabled area in e2e/matrix.yaml. Each leg builds its own kind # cluster and runs a single suite under one label filter, so a flaky addon # in one provider cannot fail the others. fail-fast is off so one red leg # does not cancel the rest. strategy: fail-fast: false matrix: ${{ fromJSON(needs.prepare-matrix.outputs.matrix) }} name: test (${{ matrix.name }}) runs-on: ubuntu-latest permissions: id-token: write # for oidc auth with aws/gcp/azure contents: read # for checkout env: # AWS_OIDC_ROLE_ARN is an identifier, not a credential, but it is still # injected only for legs whose secret_groups include "aws", so the # Configure AWS step (and AWS auth) is skipped on every other leg. The # per-provider credentials are scoped the same way, per leg, in the Run # e2e step below: a vault or core-smoke leg receives no cloud secrets. AWS_OIDC_ROLE_ARN: ${{ contains(matrix.secret_groups, 'aws') && secrets.AWS_OIDC_ROLE_ARN || '' }} # Selects the suite binary and label filter for this leg. run.sh forwards # both into the e2e pod; entrypoint.sh runs ginkgo with them. TEST_SUITES: ${{ matrix.suite }} GINKGO_LABELS: ${{ matrix.labels }} # The kind cluster goes away with the runner, so uninstalling the global # addons costs about a minute and buys nothing. Safe because TEST_SUITES # above is a single suite; the helper refuses this for several. E2E_SKIP_GLOBAL_TEARDOWN: "true" steps: - uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 with: egress-policy: audit - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ env.TARGET_SHA || github.sha }} persist-credentials: false - name: Configure AWS Credentials if: env.AWS_OIDC_ROLE_ARN != '' uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3 with: role-to-assume: ${{ env.AWS_OIDC_ROLE_ARN }} aws-region: ${{ env.AWS_REGION }} - name: Setup kind uses: engineerd/setup-kind@aa272fe2a7309878ffc2a81c56cfe3ef108ae7d0 # v0.5.0 with: version: ${{ env.KIND_VERSION }} wait: 10m image: ${{ env.KIND_IMAGE }} name: external-secrets - name: Download image tarballs uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: e2e-images path: e2e/image-artifacts - name: Run e2e # Each provider's secrets are injected only when this leg's # secret_groups (from e2e/matrix.yaml) lists that group; otherwise the # value is empty. So a leg receives exactly the credentials it needs and # nothing else, instead of every leg seeing every secret. env: GCP_SERVICE_ACCOUNT_KEY: ${{ contains(matrix.secret_groups, 'gcp') && secrets.GCP_SERVICE_ACCOUNT_KEY || '' }} GCP_FED_REGION: ${{ contains(matrix.secret_groups, 'gcp') && secrets.GCP_FED_REGION || '' }} GCP_GSA_NAME: ${{ contains(matrix.secret_groups, 'gcp') && secrets.GCP_GSA_NAME || '' }} GCP_KSA_NAME: ${{ contains(matrix.secret_groups, 'gcp') && secrets.GCP_KSA_NAME || '' }} GCP_FED_PROJECT_ID: ${{ contains(matrix.secret_groups, 'gcp') && secrets.GCP_FED_PROJECT_ID || '' }} AWS_SA_NAME: ${{ contains(matrix.secret_groups, 'aws') && secrets.AWS_SA_NAME || '' }} AWS_SA_NAMESPACE: ${{ contains(matrix.secret_groups, 'aws') && secrets.AWS_SA_NAMESPACE || '' }} TFC_AZURE_CLIENT_ID: ${{ contains(matrix.secret_groups, 'azure') && secrets.TFC_AZURE_CLIENT_ID || '' }} TFC_AZURE_CLIENT_SECRET: ${{ contains(matrix.secret_groups, 'azure') && secrets.TFC_AZURE_CLIENT_SECRET || '' }} TFC_AZURE_TENANT_ID: ${{ contains(matrix.secret_groups, 'azure') && secrets.TFC_AZURE_TENANT_ID || '' }} TFC_AZURE_SUBSCRIPTION_ID: ${{ contains(matrix.secret_groups, 'azure') && secrets.TFC_AZURE_SUBSCRIPTION_ID || '' }} TFC_VAULT_URL: ${{ contains(matrix.secret_groups, 'azure') && secrets.TFC_VAULT_URL || '' }} SCALEWAY_API_URL: ${{ contains(matrix.secret_groups, 'scaleway') && secrets.SCALEWAY_API_URL || '' }} SCALEWAY_REGION: ${{ contains(matrix.secret_groups, 'scaleway') && secrets.SCALEWAY_REGION || '' }} SCALEWAY_PROJECT_ID: ${{ contains(matrix.secret_groups, 'scaleway') && secrets.SCALEWAY_PROJECT_ID || '' }} SCALEWAY_ACCESS_KEY: ${{ contains(matrix.secret_groups, 'scaleway') && secrets.SCALEWAY_ACCESS_KEY || '' }} SCALEWAY_SECRET_KEY: ${{ contains(matrix.secret_groups, 'scaleway') && secrets.SCALEWAY_SECRET_KEY || '' }} DELINEA_TLD: ${{ contains(matrix.secret_groups, 'delinea') && secrets.DELINEA_TLD || '' }} DELINEA_URL_TEMPLATE: ${{ contains(matrix.secret_groups, 'delinea') && secrets.DELINEA_URL_TEMPLATE || '' }} DELINEA_TENANT: ${{ contains(matrix.secret_groups, 'delinea') && secrets.DELINEA_TENANT || '' }} DELINEA_CLIENT_ID: ${{ contains(matrix.secret_groups, 'delinea') && secrets.DELINEA_CLIENT_ID || '' }} DELINEA_CLIENT_SECRET: ${{ contains(matrix.secret_groups, 'delinea') && secrets.DELINEA_CLIENT_SECRET || '' }} SECRETSERVER_USERNAME: ${{ contains(matrix.secret_groups, 'secretserver') && secrets.SECRETSERVER_USERNAME || '' }} SECRETSERVER_PASSWORD: ${{ contains(matrix.secret_groups, 'secretserver') && secrets.SECRETSERVER_PASSWORD || '' }} SECRETSERVER_URL: ${{ contains(matrix.secret_groups, 'secretserver') && secrets.SECRETSERVER_URL || '' }} GRAFANA_URL: ${{ contains(matrix.secret_groups, 'grafana') && secrets.GRAFANA_URL || '' }} GRAFANA_TOKEN: ${{ contains(matrix.secret_groups, 'grafana') && secrets.GRAFANA_TOKEN || '' }} AKEYLESS_ACCESS_ID: ${{ contains(matrix.secret_groups, 'akeyless') && secrets.AKEYLESS_ACCESS_ID || '' }} AKEYLESS_ACCESS_TYPE: ${{ contains(matrix.secret_groups, 'akeyless') && secrets.AKEYLESS_ACCESS_TYPE || '' }} AKEYLESS_ACCESS_TYPE_PARAM: ${{ contains(matrix.secret_groups, 'akeyless') && secrets.AKEYLESS_ACCESS_TYPE_PARAM || '' }} GITLAB_TOKEN: ${{ contains(matrix.secret_groups, 'gitlab') && secrets.GITLAB_TOKEN || '' }} GITLAB_PROJECT_ID: ${{ contains(matrix.secret_groups, 'gitlab') && secrets.GITLAB_PROJECT_ID || '' }} GITLAB_ENVIRONMENT: ${{ contains(matrix.secret_groups, 'gitlab') && secrets.GITLAB_ENVIRONMENT || '' }} ORACLE_USER_OCID: ${{ contains(matrix.secret_groups, 'oracle') && secrets.ORACLE_USER_OCID || '' }} ORACLE_TENANCY_OCID: ${{ contains(matrix.secret_groups, 'oracle') && secrets.ORACLE_TENANCY_OCID || '' }} ORACLE_REGION: ${{ contains(matrix.secret_groups, 'oracle') && secrets.ORACLE_REGION || '' }} ORACLE_FINGERPRINT: ${{ contains(matrix.secret_groups, 'oracle') && secrets.ORACLE_FINGERPRINT || '' }} ORACLE_KEY: ${{ contains(matrix.secret_groups, 'oracle') && secrets.ORACLE_KEY || '' }} ORACLE_VAULT_OCID: ${{ contains(matrix.secret_groups, 'oracle') && secrets.ORACLE_VAULT_OCID || '' }} ORACLE_COMPARTMENT_OCID: ${{ contains(matrix.secret_groups, 'oracle') && secrets.ORACLE_COMPARTMENT_OCID || '' }} ORACLE_ENCRYPTION_KEY_OCID: ${{ contains(matrix.secret_groups, 'oracle') && secrets.ORACLE_ENCRYPTION_KEY_OCID || '' }} run: make -C e2e test.run