|
|
2 weeks ago | |
|---|---|---|
| .. | ||
| framework | 2 weeks ago | |
| k8s | 1 month ago | |
| suites | 2 weeks ago | |
| Dockerfile | 1 month ago | |
| Makefile | 2 weeks ago | |
| README.md | 2 weeks ago | |
| entrypoint.sh | 9 months ago | |
| go.mod | 2 weeks ago | |
| go.sum | 2 weeks ago | |
| kind.yaml | 4 years ago | |
| matrix.py | 2 weeks ago | |
| matrix.yaml | 2 weeks ago | |
| run.sh | 2 weeks ago | |
| tools.go | 5 months ago | |
This document explains how the e2e suite is built and run in CI after the fan-out change: what the moving parts are, how a run flows, how credentials are scoped per provider, and how to add or enable a provider.
| File | Role |
|---|---|
e2e/suites/<suite>/ |
Ginkgo suites, compiled to <suite>.test binaries: provider, generator, flux, argocd. |
e2e/suites/provider/cases/import.go |
Blank-imports every provider case into the single provider.test binary. Providers are told apart at run time by Ginkgo label. |
e2e/matrix.yaml |
Source of truth for the fan-out: one area (leg) per provider, with its suite, label filter, secret groups, and trigger paths. |
e2e/matrix.py |
Validates the matrix (check), emits the CI matrix JSON (json), and prints the per-leg credential plan (plan). |
e2e/run.sh |
Host-side launcher. Runs kubectl run to start the e2e pod, forwarding TEST_SUITES, GINKGO_LABELS, E2E_SKIP_GLOBAL_TEARDOWN, and the (scoped) credentials as pod env. |
e2e/entrypoint.sh |
In-pod entry (image CMD). Loops over TEST_SUITES and runs ginkgo -label-filter="$GINKGO_LABELS" against each <suite>.test. |
.github/workflows/e2e.yml |
Non-managed e2e. Fans out into per-provider legs. Owns the e2e-required gate. |
.github/workflows/e2e-reusable.yml |
The reusable build + matrix-test pipeline that e2e.yml calls. |
.github/workflows/e2e-managed.yml |
Managed e2e (real cloud IRSA / workload-identity), run on demand via /ok-to-test-managed. Already per-provider. |
flowchart TD
T[pull_request or /ok-to-test] --> P[prepare-matrix: matrix.py check + plan + json]
T --> B[build: compile controller + e2e images once, upload tarball]
P --> M{fan out over enabled areas}
B --> M
M --> L1[test core-smoke]
M --> L2[test vault]
M --> L3[test aws]
M --> Ln[test ...]
L1 --> R[e2e-required]
L2 --> R
L3 --> R
Ln --> R
R --> G[single stable green/red status]
matrix.py check (fail early if the matrix is
inconsistent), prints the credential plan, and emits the enabled-areas
matrix as JSON. This job has no secrets in scope.fail-fast: false matrix over the enabled areas. Each leg gets
its own runner and its own kind cluster, loads the shared image tarball, and
runs one suite under one label filter (TEST_SUITES + GINKGO_LABELS).e2e/matrix.yaml)Each area is one leg:
- name: aws # leg id, shown as "test (aws)"
suite: provider # which suite binary (TEST_SUITES)
labels: "aws && !managed" # Ginkgo -label-filter (GINKGO_LABELS)
providers: [aws] # for the coverage check only
secret_groups: [aws] # which credential groups this leg receives
needs_secrets: true # mirror of "secret_groups is non-empty"
paths: # phase 2 seed (affected-only), unused today
- "providers/v1/aws/**"
- "e2e/suites/provider/cases/aws/**"
enabled: true # whether the phase 1 matrix runs it now
Notes:
suite: provider; generator, flux, and
argocd are their own suites.!managed everywhere. This workflow runs only the non-managed specs; the
managed IRSA / workload-identity specs run in e2e-managed.yml.enabled lets the matrix grow gradually. Disabled areas still count for
coverage and document the intended full matrix; flip to true to run them.A leg receives a provider's secrets only when its secret_groups lists that
group. In e2e-reusable.yml every secret is gated:
GCP_SERVICE_ACCOUNT_KEY: ${{ contains(matrix.secret_groups, 'gcp') && secrets.GCP_SERVICE_ACCOUNT_KEY || '' }}
So a vault or core-smoke leg (secret_groups: []) gets empty strings for
every cloud credential, and the aws leg gets only the aws group. The
group -> variable mapping lives in e2e-reusable.yml.
Scoping is proven without reading any secret: matrix.py plan runs in
prepare-matrix (which has no secrets in scope) and derives each leg's
credential list from matrix.yaml plus the group mapping parsed out of the
workflow text. It never references the secrets context, so nothing depends on
GitHub's log masking. Example:
core-smoke: groups=[] -> (none: in-cluster only)
vault: groups=[] -> (none: in-cluster only)
aws: groups=['aws'] -> AWS_OIDC_ROLE_ARN, AWS_SA_NAME, AWS_SA_NAMESPACE
Which providers actually need external credentials: fake, kubernetes,
template, crd, vault, openbao, conjur, and infisical run against
in-cluster addons (or, for crd and kubernetes, the cluster's own API) and
need none. The rest hit real APIs and are scoped to their group.
The generator suite is split across two legs by label. The generator leg
runs every generator except grafana (!managed && !grafana) and is scoped to
aws, because the ecr and sts generators mint tokens against real AWS. The
grafana leg (grafana && !managed, scoped to grafana) is isolated on its
own because the grafana generator depends on a live external Grafana Cloud
instance; keeping it separate means its external flakiness is attributable and
never masks the other generators.
e2e-required gateThe individual leg names change as the matrix grows, which makes them a poor
target for branch protection. e2e-required (in e2e.yml) is one job that
needs the trusted and fork callers and reports a single status:
Point branch protection at e2e-required and it stays stable regardless of how
many legs exist.
integration-trusted): runs automatically with secrets./ok-to-test sha=<40-char-sha> (or submits
a review whose body contains /ok-to-test, which pins the reviewed commit).
guard-fork rejects a bare command without a pinned SHA; integration-fork
then runs. Note that the fork path runs the workflow from main, not from the
PR branch.e2e-managed.yml): /ok-to-test-managed, one job per cloud
provider, GINKGO_LABELS="<provider> && managed".# validate the matrix (coverage, secret-scoping consistency, wiring)
make -C e2e matrix.check
# show which credentials each enabled leg will receive (reads no secrets)
make -C e2e matrix.plan
# run a single provider locally (overrides the Makefile defaults)
make -C e2e test.run TEST_SUITES=provider GINKGO_LABELS="vault && !managed"
# leave the global addons installed, for a cluster you are about to delete.
# Saves about a minute; the kind legs set it, e2e-managed.yml does not.
# Refused (stderr) when TEST_SUITES names several suites, and that guard sees
# only its own process, so two single-suite runs on one cluster still collide.
make -C e2e test.run TEST_SUITES=provider GINKGO_LABELS="vault && !managed" \
E2E_SKIP_GLOBAL_TEARDOWN=true
e2e/suites/provider/cases/<name>/ and blank
import it in import.go.area for it in matrix.yaml (its label, providers: [<name>], and
paths).secret_groups
and wire that group's env vars in e2e-reusable.yml.enabled: true when you want CI to run it.matrix.py check (run in prepare-matrix) enforces steps 1-3: it fails the
build if a provider is compiled into the suite but not covered by an area, if
needs_secrets disagrees with secret_groups, or if an area names a secret
group that the workflow does not wire.
The oracle suite runs against a real OCI tenancy and takes eight variables:
ORACLE_TENANCY_OCID, ORACLE_USER_OCID, ORACLE_REGION,
ORACLE_FINGERPRINT, ORACLE_KEY (the PEM signing key itself, which must have
no passphrase), plus ORACLE_VAULT_OCID, ORACLE_COMPARTMENT_OCID and
ORACLE_ENCRYPTION_KEY_OCID. The last three identify where secrets are created;
the Vault API requires all of them and the SecretStore requires the vault.
Note that ORACLE_KEY and ORACLE_ENCRYPTION_KEY_OCID are unrelated. The first
is an RSA key that authenticates the caller; the second is the OCID of a
symmetric AES key inside the vault that encrypts secrets at rest.
This leg is not enabled in CI, and that is a decision rather than a gap. An
always-free OCI tenancy caps secrets at 150, and a deleted secret cannot return
to that pool for 24 hours; the floor is enforced, so values below it are
rejected outright. One run of this suite creates 15 secrets, which allows about
ten runs a rolling day before creates start failing for reasons unrelated to the
change under test. --flake-attempts=2 and -p -nodes=5 in entrypoint.sh
lower that further, the latter also being enough concurrency for the Vaults
service to return 429. Enabling this leg needs a tenancy without that cap.
Until then the suite is a pre-submit tool for contributors with their own tenancy:
make -C e2e test.run TEST_SUITES=provider GINKGO_LABELS="oracle && !managed"