diff --git a/tests/e2e-ui/.gitignore b/tests/e2e-ui/.gitignore index 4849ea6f..55df461d 100644 --- a/tests/e2e-ui/.gitignore +++ b/tests/e2e-ui/.gitignore @@ -14,4 +14,5 @@ e2e/.auth playwright-report test-results blob-report -gitops/cluster/e2e-identity/grants.yaml +# Local secrets are synchronized into the Cluster Vault and never committed. +secrets/vault/ diff --git a/tests/e2e-ui/.gitops/README.md b/tests/e2e-ui/.gitops/README.md new file mode 100644 index 00000000..e0ec994e --- /dev/null +++ b/tests/e2e-ui/.gitops/README.md @@ -0,0 +1,50 @@ +# e2e-ui local GitOps + +This directory is the project-level Local Workbench contract: + +```text +.gitops/ + local/ + cluster.yaml # durable named Cluster + environment.yaml # reusable definition for this checkout/worktree + cluster/ # shared Cluster desired state +``` + +Application roots own their own charts: + +```text +api/.gitops/local/ # editable local workload (default deploy chart) +api/.gitops/deploy/ # packaged cloud workload +api/.gitops/promote/ # optional cloud promotion action +ui/.gitops/local/ +ui/.gitops/deploy/ +ui/.gitops/promote/ +ui/.gitops/test-users/ # explicit optional Environment deploy +``` + +```bash +# Terminal 1 +hops local gitops cluster ./.gitops/local/cluster.yaml + +# Terminal 2 +hops local gitops environment ./.gitops/local/environment.yaml --name e2e +``` + +Both definitions are Kubernetes-shaped YAML. The Cluster controller watches +the Cluster definition and tree, registered Environment definitions, each +resolved deploy chart, and source paths emitted by local workloads. A deploy +path names an application root; its chart defaults to `.gitops/local`. + +Another Git worktree uses the same committed `environment.yaml` and a distinct +runtime name: + +```bash +cd .worktrees/feature-auth/tests/e2e-ui +hops local gitops environment ./.gitops/local/environment.yaml --name feature-auth +``` + +Unregister it without editing committed files: + +```bash +hops local gitops environment --name feature-auth --down +``` diff --git a/tests/e2e-ui/.gitops/local/cluster.yaml b/tests/e2e-ui/.gitops/local/cluster.yaml new file mode 100644 index 00000000..19f486d5 --- /dev/null +++ b/tests/e2e-ui/.gitops/local/cluster.yaml @@ -0,0 +1,14 @@ +apiVersion: hops.local/v1alpha1 +kind: Cluster +metadata: + name: e2e-ui +spec: + clusterProvider: kind + dockerProvider: dory + # The definition is tests/e2e-ui/.gitops/local/cluster.yaml; ../../../.. + # is the Distributed checkout root. Local charts mount that shared source tree. + mountRoot: ../../../.. + manifests: + path: tests/e2e-ui/.gitops/local/cluster + secretSync: + path: tests/e2e-ui/secrets/vault diff --git a/tests/e2e-ui/.gitops/local/cluster/README.md b/tests/e2e-ui/.gitops/local/cluster/README.md new file mode 100644 index 00000000..c512cf52 --- /dev/null +++ b/tests/e2e-ui/.gitops/local/cluster/README.md @@ -0,0 +1,53 @@ +# `.gitops/local/cluster` — shared Cluster desired state + +This tree is reconciled once for the named `e2e-ui` Cluster. It owns shared +platform resources; registered Environments own application workloads in their +own namespaces. + +```text +.gitops/local/ + cluster.yaml # Cluster identity, providers, mountRoot, manifest path + environment.yaml # reusable checkout/worktree Environment + cluster/ # this shared desired-state tree + providers/ # Crossplane Provider packages and runtime configs + providerconfigs/ # non-secret provider configuration + configurations/ # Crossplane Configuration packages + stacks/ # shared platform XRs + psql/ # one shared PSQLCluster + secrets/ # Vault/ESO stack and RBAC + auth/ # auth residual resources +``` + +Start or resume the Cluster from `tests/e2e-ui`: + +```bash +hops local gitops cluster ./.gitops/local/cluster.yaml +``` + +The Cluster definition declares `clusterProvider: kind`, +`dockerProvider: dory`, and a same-path `mountRoot` for this project. The +Cluster controller applies this tree, waits for dependencies, watches changes, +and owns exact-inventory pruning. No committed apply waves, source generation, +or restart counters are required. + +Register an application Environment separately: + +```bash +hops local gitops environment ./.gitops/local/environment.yaml --name alice +``` + +`environment.yaml` lists application roots. Each root defaults to its +`.gitops/local` chart; the explicit test-user deploy selects +`ui/.gitops/test-users`. Cloud `.gitops/promote` and `.gitops/deploy` charts do +not participate in ordinary local reconciliation. + +## Ownership boundary + +- Cluster: Providers, Configurations, ProviderConfigs, shared Auth/Secret/PSQL + stacks, and the shared PSQLCluster. +- Environment: rendered local application charts and explicit optional charts. +- Hops substrate: container engine, bare Kubernetes API, controller process, + kubeconfig, project mount, inotify, and host networking. + +Local credentials come only from the gitignored `secrets/vault/` input selected +by `Cluster.spec.secretSync`. See [SECRETS.md](./SECRETS.md). diff --git a/tests/e2e-ui/.gitops/local/cluster/SECRETS.md b/tests/e2e-ui/.gitops/local/cluster/SECRETS.md new file mode 100644 index 00000000..155662a2 --- /dev/null +++ b/tests/e2e-ui/.gitops/local/cluster/SECRETS.md @@ -0,0 +1,41 @@ +# Local secrets + +Committed GitOps YAML is non-secret. The Cluster definition selects the +gitignored `secrets/vault/` directory: + +```yaml +spec: + secretSync: + path: secrets/vault +``` + +No SOPS step is required locally. Hops reads only that bounded input, writes +values into the Cluster's local Vault, and does not copy secret values into its +state or logs. Environment charts consume them through normal ExternalSecret +resources. + +Expected logical values for this fixture include: + +| Vault value | Kubernetes consumer | +|---|---| +| Zitadel masterkey | `auth/zitadel-masterkey` ExternalSecret | +| demo human passwords | `default/e2e-human-passwords` ExternalSecret | +| Environment session/login credentials | `/e2e-ui-oidc` ExternalSecret | + +The Zitadel provider's generated OIDC client ID and secret do not enter the +local secret directory. The Oidc managed resource writes those directly to its +generation-specific connection Secret (`attribute.client_id` and +`attribute.client_secret`). + +The Cluster tree owns `SecretStack`, Vault/ESO RBAC, and the masterkey +ExternalSecret under `.gitops/local/cluster/{secrets,auth}`. The explicit +`ui/.gitops/test-users` deploy owns test identity ExternalSecrets and managed +resources. The API and UI workload charts only consume Secrets. + +Rules: + +1. Never commit passwords, PATs, client secrets, masterkeys, or Vault exports. +2. Keep local secret input below the configured gitignored directory. +3. Keep secret names and key references declarative in GitOps. +4. Use SOPS or an external secret manager for cloud workflows; it is not part + of the Local Workbench input path. diff --git a/tests/e2e-ui/.gitops/local/cluster/auth/README.md b/tests/e2e-ui/.gitops/local/cluster/auth/README.md new file mode 100644 index 00000000..ae33f94a --- /dev/null +++ b/tests/e2e-ui/.gitops/local/cluster/auth/README.md @@ -0,0 +1,15 @@ +# Auth (shared local Cluster) + +The Cluster tree owns the shared AuthStack, Zitadel provider package and +ProviderConfig, and the AuthStack masterkey ExternalSecret. The reusable +Environment owns only per-Environment OIDC/test identity resources through the +explicit `ui/.gitops/test-users` deploy. + +```bash +hops local gitops cluster ./.gitops/local/cluster.yaml +hops local gitops environment ./.gitops/local/environment.yaml --name e2e +``` + +No live organization, project, user, or client IDs are committed. Provider +credentials and the masterkey originate in the gitignored local secret input +and reach Kubernetes through Vault/ExternalSecret resources. diff --git a/tests/e2e-ui/gitops/cluster/auth/external-secret-masterkey.yaml b/tests/e2e-ui/.gitops/local/cluster/auth/external-secret-masterkey.yaml similarity index 87% rename from tests/e2e-ui/gitops/cluster/auth/external-secret-masterkey.yaml rename to tests/e2e-ui/.gitops/local/cluster/auth/external-secret-masterkey.yaml index 3ed7a699..1237a8a5 100644 --- a/tests/e2e-ui/gitops/cluster/auth/external-secret-masterkey.yaml +++ b/tests/e2e-ui/.gitops/local/cluster/auth/external-secret-masterkey.yaml @@ -1,4 +1,4 @@ -# Materialize AuthStack masterkey from Vault so it survives ns/worktree churn. +# Cluster-owned: materialize AuthStack masterkey from Vault so it survives Environment churn. # Requires ClusterSecretStore vault Ready (Vault Helm postStart + vault-auth-delegator) # and path auth/zitadel-masterkey written via: hops secrets sync vault # See SECRETS.md. diff --git a/tests/e2e-ui/gitops/cluster/configurations/auth-stack.yaml b/tests/e2e-ui/.gitops/local/cluster/configurations/auth-stack.yaml similarity index 82% rename from tests/e2e-ui/gitops/cluster/configurations/auth-stack.yaml rename to tests/e2e-ui/.gitops/local/cluster/configurations/auth-stack.yaml index 81732ea2..7cc09382 100644 --- a/tests/e2e-ui/gitops/cluster/configurations/auth-stack.yaml +++ b/tests/e2e-ui/.gitops/local/cluster/configurations/auth-stack.yaml @@ -1,4 +1,4 @@ -# Crossplane Configuration for AuthStack (Zitadel + auth XRDs). +# Cluster-owned Crossplane Configuration for AuthStack (Zitadel + auth XRDs). # This fixture exercises Grant references that are newer than the published # package. Build/push this content-derived dev tag before starting cluster # GitOps (see ../README.md). The watched pin then matches the source install @@ -9,5 +9,5 @@ kind: Configuration metadata: name: auth-stack spec: - package: registry.crossplane-system.svc.cluster.local:5000/hops-ops/auth-stack:dev-15db94d94e7b + package: registry.crossplane-system.svc.cluster.local:5000/hops-ops/auth-stack:dev-9c0d1aff25e6 packagePullPolicy: Always diff --git a/tests/e2e-ui/gitops/cluster/configurations/psql-stack.yaml b/tests/e2e-ui/.gitops/local/cluster/configurations/psql-stack.yaml similarity index 72% rename from tests/e2e-ui/gitops/cluster/configurations/psql-stack.yaml rename to tests/e2e-ui/.gitops/local/cluster/configurations/psql-stack.yaml index 2427526f..499954a4 100644 --- a/tests/e2e-ui/gitops/cluster/configurations/psql-stack.yaml +++ b/tests/e2e-ui/.gitops/local/cluster/configurations/psql-stack.yaml @@ -1,4 +1,4 @@ -# Crossplane Configuration for the PostgreSQL platform stack. +# Cluster-owned Crossplane Configuration for the PostgreSQL platform stack. # Prefer building/pushing from source into the local registry: # hops config install /xrs/stacks/k8s/psql # Or set package to a published tag when available. @@ -8,5 +8,5 @@ metadata: name: psql-stack spec: package: ghcr.io/hops-ops/psql-stack:v0.9.1 - # Local registry after hops local start + config install: + # Local registry package after the Cluster bootstrap publishes it: # package: registry.crossplane-system.svc.cluster.local:5000/hops-ops/psql-stack: diff --git a/tests/e2e-ui/gitops/cluster/configurations/secret-stack.yaml b/tests/e2e-ui/.gitops/local/cluster/configurations/secret-stack.yaml similarity index 84% rename from tests/e2e-ui/gitops/cluster/configurations/secret-stack.yaml rename to tests/e2e-ui/.gitops/local/cluster/configurations/secret-stack.yaml index a69929ce..159df86a 100644 --- a/tests/e2e-ui/gitops/cluster/configurations/secret-stack.yaml +++ b/tests/e2e-ui/.gitops/local/cluster/configurations/secret-stack.yaml @@ -1,4 +1,4 @@ -# Crossplane Configuration for platform SecretStack (ESO + AWS|Vault backends). +# Cluster-owned Configuration for SecretStack (ESO + AWS|Vault backends). # Build and publish the source package to the control plane's local registry first: # hops config install --path /xrs/stacks/aws/secret \ # --cluster-provider kind --docker-provider dory \ diff --git a/tests/e2e-ui/gitops/cluster/providerconfigs/helm.yaml b/tests/e2e-ui/.gitops/local/cluster/providerconfigs/helm.yaml similarity index 79% rename from tests/e2e-ui/gitops/cluster/providerconfigs/helm.yaml rename to tests/e2e-ui/.gitops/local/cluster/providerconfigs/helm.yaml index 148cb4fc..f3b87000 100644 --- a/tests/e2e-ui/gitops/cluster/providerconfigs/helm.yaml +++ b/tests/e2e-ui/.gitops/local/cluster/providerconfigs/helm.yaml @@ -1,3 +1,4 @@ +# Cluster-owned provider configuration. apiVersion: helm.m.crossplane.io/v1beta1 kind: ProviderConfig metadata: diff --git a/tests/e2e-ui/gitops/cluster/providerconfigs/kubernetes.yaml b/tests/e2e-ui/.gitops/local/cluster/providerconfigs/kubernetes.yaml similarity index 80% rename from tests/e2e-ui/gitops/cluster/providerconfigs/kubernetes.yaml rename to tests/e2e-ui/.gitops/local/cluster/providerconfigs/kubernetes.yaml index 95029bcc..dfba7b3b 100644 --- a/tests/e2e-ui/gitops/cluster/providerconfigs/kubernetes.yaml +++ b/tests/e2e-ui/.gitops/local/cluster/providerconfigs/kubernetes.yaml @@ -1,3 +1,4 @@ +# Cluster-owned provider configuration. apiVersion: kubernetes.m.crossplane.io/v1alpha1 kind: ProviderConfig metadata: diff --git a/tests/e2e-ui/gitops/cluster/providerconfigs/zitadel.yaml b/tests/e2e-ui/.gitops/local/cluster/providerconfigs/zitadel.yaml similarity index 94% rename from tests/e2e-ui/gitops/cluster/providerconfigs/zitadel.yaml rename to tests/e2e-ui/.gitops/local/cluster/providerconfigs/zitadel.yaml index b601e2b9..18fa4180 100644 --- a/tests/e2e-ui/gitops/cluster/providerconfigs/zitadel.yaml +++ b/tests/e2e-ui/.gitops/local/cluster/providerconfigs/zitadel.yaml @@ -1,3 +1,4 @@ +# Cluster-owned provider configuration. apiVersion: zitadel.m.crossplane.io/v1beta1 kind: ProviderConfig metadata: diff --git a/tests/e2e-ui/gitops/cluster/providers/helm-drc.yaml b/tests/e2e-ui/.gitops/local/cluster/providers/helm-drc.yaml similarity index 91% rename from tests/e2e-ui/gitops/cluster/providers/helm-drc.yaml rename to tests/e2e-ui/.gitops/local/cluster/providers/helm-drc.yaml index c9953f16..c86d9636 100644 --- a/tests/e2e-ui/gitops/cluster/providers/helm-drc.yaml +++ b/tests/e2e-ui/.gitops/local/cluster/providers/helm-drc.yaml @@ -1,4 +1,4 @@ -# Per-provider DeploymentRuntimeConfig for provider-helm (local dev). +# Cluster-owned DeploymentRuntimeConfig for provider-helm (local dev). # Colocated with providers/helm.yaml (runtimeConfigRef: local-dev-helm). # # Each provider gets its OWN uniquely-named DRC + cluster-admin ServiceAccount. diff --git a/tests/e2e-ui/gitops/cluster/providers/helm.yaml b/tests/e2e-ui/.gitops/local/cluster/providers/helm.yaml similarity index 89% rename from tests/e2e-ui/gitops/cluster/providers/helm.yaml rename to tests/e2e-ui/.gitops/local/cluster/providers/helm.yaml index 44d33b64..7ee3f540 100644 --- a/tests/e2e-ui/gitops/cluster/providers/helm.yaml +++ b/tests/e2e-ui/.gitops/local/cluster/providers/helm.yaml @@ -1,3 +1,4 @@ +# Cluster-owned provider package. apiVersion: pkg.crossplane.io/v1 kind: Provider metadata: diff --git a/tests/e2e-ui/gitops/cluster/providers/kubernetes-drc.yaml b/tests/e2e-ui/.gitops/local/cluster/providers/kubernetes-drc.yaml similarity index 91% rename from tests/e2e-ui/gitops/cluster/providers/kubernetes-drc.yaml rename to tests/e2e-ui/.gitops/local/cluster/providers/kubernetes-drc.yaml index 76f6f5a6..aed4b3d2 100644 --- a/tests/e2e-ui/gitops/cluster/providers/kubernetes-drc.yaml +++ b/tests/e2e-ui/.gitops/local/cluster/providers/kubernetes-drc.yaml @@ -1,4 +1,4 @@ -# Per-provider DeploymentRuntimeConfig for provider-kubernetes (local dev). +# Cluster-owned DeploymentRuntimeConfig for provider-kubernetes (local dev). # Colocated with providers/kubernetes.yaml (runtimeConfigRef: local-dev-kubernetes). # # Each provider gets its OWN uniquely-named DRC + cluster-admin ServiceAccount. diff --git a/tests/e2e-ui/gitops/cluster/providers/kubernetes.yaml b/tests/e2e-ui/.gitops/local/cluster/providers/kubernetes.yaml similarity index 90% rename from tests/e2e-ui/gitops/cluster/providers/kubernetes.yaml rename to tests/e2e-ui/.gitops/local/cluster/providers/kubernetes.yaml index c400c387..34796fa2 100644 --- a/tests/e2e-ui/gitops/cluster/providers/kubernetes.yaml +++ b/tests/e2e-ui/.gitops/local/cluster/providers/kubernetes.yaml @@ -1,3 +1,4 @@ +# Cluster-owned provider package. apiVersion: pkg.crossplane.io/v1 kind: Provider metadata: diff --git a/tests/e2e-ui/gitops/cluster/providers/zitadel.yaml b/tests/e2e-ui/.gitops/local/cluster/providers/zitadel.yaml similarity index 85% rename from tests/e2e-ui/gitops/cluster/providers/zitadel.yaml rename to tests/e2e-ui/.gitops/local/cluster/providers/zitadel.yaml index 41d45afd..0d1bf684 100644 --- a/tests/e2e-ui/gitops/cluster/providers/zitadel.yaml +++ b/tests/e2e-ui/.gitops/local/cluster/providers/zitadel.yaml @@ -1,3 +1,4 @@ +# Cluster-owned provider package. apiVersion: pkg.crossplane.io/v1 kind: Provider metadata: diff --git a/tests/e2e-ui/.gitops/local/cluster/psql/cluster.yaml b/tests/e2e-ui/.gitops/local/cluster/psql/cluster.yaml new file mode 100644 index 00000000..f992b492 --- /dev/null +++ b/tests/e2e-ui/.gitops/local/cluster/psql/cluster.yaml @@ -0,0 +1,12 @@ +# One shared local database cluster. Environment charts may create databases or +# preview branches, but do not create another PSQLCluster by default. +apiVersion: hops.ops.com.ai/v1alpha1 +kind: PSQLCluster +metadata: + name: e2e-ui + namespace: default +spec: + clusterName: e2e-ui + storage: + size: 2Gi + class: psql diff --git a/tests/e2e-ui/gitops/cluster/secrets/stack.yaml b/tests/e2e-ui/.gitops/local/cluster/secrets/stack.yaml similarity index 97% rename from tests/e2e-ui/gitops/cluster/secrets/stack.yaml rename to tests/e2e-ui/.gitops/local/cluster/secrets/stack.yaml index 33870bea..95bc87de 100644 --- a/tests/e2e-ui/gitops/cluster/secrets/stack.yaml +++ b/tests/e2e-ui/.gitops/local/cluster/secrets/stack.yaml @@ -1,9 +1,9 @@ -# Platform SecretStack for local CP (dory workbench). +# Cluster-owned SecretStack for the local control plane. # # Install Configuration from source first (breaking API: hops.ops.com.ai): # hops config install --path /xrs/stacks/aws/secret --context dory # -# Then: hops local gitops cluster ./gitops/cluster +# Then: hops local gitops cluster ./.gitops/local/cluster.yaml # # backend=vault + vault.install for local (no AWS PodIdentity required). # @@ -20,7 +20,7 @@ metadata: name: external-secrets namespace: default spec: - clusterName: dory + clusterName: e2e-ui backend: vault namespace: external-secrets helmProviderConfigRef: diff --git a/tests/e2e-ui/gitops/cluster/secrets/vault-auth-delegator.yaml b/tests/e2e-ui/.gitops/local/cluster/secrets/vault-auth-delegator.yaml similarity index 84% rename from tests/e2e-ui/gitops/cluster/secrets/vault-auth-delegator.yaml rename to tests/e2e-ui/.gitops/local/cluster/secrets/vault-auth-delegator.yaml index 7b9e772a..ed1040d2 100644 --- a/tests/e2e-ui/gitops/cluster/secrets/vault-auth-delegator.yaml +++ b/tests/e2e-ui/.gitops/local/cluster/secrets/vault-auth-delegator.yaml @@ -1,4 +1,4 @@ -# Allow the Vault server SA to call TokenReview (required for kubernetes auth). +# Cluster-owned RBAC: allow Vault to call TokenReview for Kubernetes auth. # Independent of the Vault pod lifecycle — apply with cluster gitops. apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/tests/e2e-ui/gitops/cluster/stacks/auth.yaml b/tests/e2e-ui/.gitops/local/cluster/stacks/auth.yaml similarity index 93% rename from tests/e2e-ui/gitops/cluster/stacks/auth.yaml rename to tests/e2e-ui/.gitops/local/cluster/stacks/auth.yaml index ca70689d..e494b101 100644 --- a/tests/e2e-ui/gitops/cluster/stacks/auth.yaml +++ b/tests/e2e-ui/.gitops/local/cluster/stacks/auth.yaml @@ -1,4 +1,4 @@ -# Local AuthStack: Zitadel + embedded PSQLCluster (owned by this XR). +# Cluster-owned AuthStack: Zitadel + embedded PSQLCluster (owned by this XR). # # No ingress/gateway for local first step — Service is ClusterIP only. # Helm chart fullname is release+chart → Service **zitadel-zitadel** (default). @@ -13,7 +13,7 @@ metadata: name: zitadel namespace: default spec: - clusterName: dory + clusterName: e2e-ui helmProviderConfigRef: name: default kubernetesProviderConfigRef: diff --git a/tests/e2e-ui/gitops/cluster/stacks/psql.yaml b/tests/e2e-ui/.gitops/local/cluster/stacks/psql.yaml similarity index 86% rename from tests/e2e-ui/gitops/cluster/stacks/psql.yaml rename to tests/e2e-ui/.gitops/local/cluster/stacks/psql.yaml index dc1a1378..8f0fe305 100644 --- a/tests/e2e-ui/gitops/cluster/stacks/psql.yaml +++ b/tests/e2e-ui/.gitops/local/cluster/stacks/psql.yaml @@ -1,4 +1,4 @@ -# Platform layer: CNPG + Atlas + StorageClass named `psql`. +# Cluster-owned platform layer: CNPG + Atlas + StorageClass named `psql`. # Install package first: `hops config install ` (or apply configurations/psql-stack.yaml). # # Dory/k3s uses rancher.io/local-path (default SC). Snapshot class off (no CSI snapshotter). @@ -8,7 +8,7 @@ metadata: name: psql namespace: default spec: - clusterName: dory + clusterName: e2e-ui helmProviderConfigRef: name: default kubernetesProviderConfigRef: diff --git a/tests/e2e-ui/.gitops/local/environment.yaml b/tests/e2e-ui/.gitops/local/environment.yaml new file mode 100644 index 00000000..4cbe09a6 --- /dev/null +++ b/tests/e2e-ui/.gitops/local/environment.yaml @@ -0,0 +1,41 @@ +apiVersion: hops.local/v1alpha1 +kind: Environment +metadata: + name: local +spec: + clusterRef: + name: e2e-ui + # Paths resolve from the Cluster mount root (the Distributed checkout). + # The resolved runtime Environment name defaults its namespace. + root: tests/e2e-ui + values: + local: true + preview: false + deploys: + # A deploy path names an application root. Its chart defaults to + # .gitops/local; no promotion chart participates in local reconciliation. + - path: api + values: + identity: + enabled: true + oidcGeneration: 1 + - path: ui + values: + identity: + enabled: true + oidcGeneration: 1 + # Test identities are an explicit, independently rendered deploy. + - path: ui + chart: .gitops/test-users + values: + identity: + enabled: true + oidcGeneration: 1 + projectName: e2e-ui + projectNamespace: default + humansNamespace: default + instanceLoginV2: true + demoUsers: true + providerConfigRef: + name: default + kind: ClusterProviderConfig diff --git a/tests/e2e-ui/README.md b/tests/e2e-ui/README.md index be0293a3..abc016fa 100644 --- a/tests/e2e-ui/README.md +++ b/tests/e2e-ui/README.md @@ -4,29 +4,40 @@ A copyable Distributed service and SvelteKit UI demonstrating one modeled projection from aggregate transition to server read model, generated GraphQL client, optimistic replica update, and causal confirmation. -## Option A — local cluster + workspace GitOps +## Option A — Local Workbench Cluster + Environments -One-time: start the kind control plane on Dory's Docker engine. Then run the -two GitOps processes in separate terminals: +The committed Cluster definition starts or reuses the kind control plane on +Dory and owns the single watcher. Start it in one terminal, then register this +checkout as an Environment from another: ```bash cd tests/e2e-ui -hops local gitops cluster ./gitops/cluster \ - --cluster-provider kind --docker-provider dory \ - --cluster-name hops --context kind-hops +# Terminal 1 +hops local gitops cluster ./.gitops/local/cluster.yaml -hops local gitops worktree ./gitops/envs/local --name e2e \ - --cluster-provider kind --docker-provider dory \ - --cluster-name hops --context kind-hops +# Terminal 2 +hops local gitops environment ./.gitops/local/environment.yaml --name e2e ``` -Both commands watch by default; use `--once` for CI or a single diagnostic -reconcile. Platform XRs and application workloads must be changed in their -respective GitOps trees, not applied over the watcher with `kubectl`. +The Cluster controller watches shared manifests, registered Environment +definitions, resolved charts, and delivered source. Platform XRs and workloads +must be changed in their respective GitOps trees, not applied over the +controller with `kubectl`. -Charts: `api/.gitops/deploy`, `ui/.gitops/deploy`. -App Applications: `gitops/envs/local/`. -Control plane: `gitops/cluster/` (`stacks/`, `configurations/`, …). +Local charts: `api/.gitops/local`, `ui/.gitops/local`. +Cloud charts: `api/.gitops/deploy`, `ui/.gitops/deploy`. +Optional charts: `.gitops/promote` and `ui/.gitops/test-users`. +Definitions and shared control plane: `.gitops/local/`. + +To run another Git worktree in the same Cluster, enter that checkout and give +the reusable Environment definition a distinct runtime name: + +```bash +hops local gitops environment ./.gitops/local/environment.yaml --name feature-auth +``` + +Remove only that Environment with +`hops local gitops environment --name feature-auth --down`. ## Option B — compose + host processes diff --git a/tests/e2e-ui/api/.gitops/deploy/Chart.yaml b/tests/e2e-ui/api/.gitops/deploy/Chart.yaml index fde17c03..84fa0125 100644 --- a/tests/e2e-ui/api/.gitops/deploy/Chart.yaml +++ b/tests/e2e-ui/api/.gitops/deploy/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: e2e-ui-api -description: Local workbench dogfood chart for e2e-ui API (cluster-dev / package postures) +description: Packaged cloud deployment for the e2e-ui API type: application version: 0.1.0 appVersion: "0.1.0" diff --git a/tests/e2e-ui/api/.gitops/deploy/README.md b/tests/e2e-ui/api/.gitops/deploy/README.md index f2d8964b..29c02e82 100644 --- a/tests/e2e-ui/api/.gitops/deploy/README.md +++ b/tests/e2e-ui/api/.gitops/deploy/README.md @@ -1,16 +1,5 @@ -# e2e-ui API chart (workbench) +# e2e-ui API cloud chart -Renders: - -- **Deployment / Service** (`appRuntime: cluster-dev` \| `package`) -- **Optional PSQLCluster** when `database.enabled: true` - -OIDC project / demo humans / web app live on the **UI** chart -(`ui/.gitops/deploy` `identity.*`). When `identity.enabled`, this chart derives -the same generation-specific OIDC connection Secret from the workspace -namespace. Both `OIDC_AUDIENCE` and `OIDC_CLIENT_ID` read its generated -`attribute.client_id`; only the service-user token remains in the residual -`e2e-ui-oidc` Secret. Keep `identity.oidcGeneration` aligned with the UI -Application so an OIDC rotation rolls both workloads onto matching credentials. - -Platform AuthStack + ProviderConfig stay under `gitops/cluster/`. +This chart renders the packaged API image for cloud Environments. It is +independent from `.gitops/local` and rejects `local: true` so local development +cannot accidentally select the cloud workload posture. diff --git a/tests/e2e-ui/api/.gitops/deploy/templates/deployment-package.yaml b/tests/e2e-ui/api/.gitops/deploy/templates/deployment-package.yaml index 679bb3a9..2d4a47d2 100644 --- a/tests/e2e-ui/api/.gitops/deploy/templates/deployment-package.yaml +++ b/tests/e2e-ui/api/.gitops/deploy/templates/deployment-package.yaml @@ -1,5 +1,6 @@ -{{- /* Package Deployment: production/CI image posture. Omitted for local/cluster-dev. */ -}} -{{- if and (not .Values.local) (ne .Values.appRuntime "cluster-dev") (ne .Values.appRuntime "host") }} +{{- if .Values.local }} +{{- fail "api/.gitops/deploy is cloud-only; use api/.gitops/local for local=true" }} +{{- end }} apiVersion: apps/v1 kind: Deployment metadata: @@ -28,4 +29,3 @@ spec: env: - name: BIND value: {{ .Values.env.BIND | quote }} -{{- end }} diff --git a/tests/e2e-ui/api/.gitops/deploy/templates/psqlcluster.yaml b/tests/e2e-ui/api/.gitops/deploy/templates/psqlcluster.yaml deleted file mode 100644 index 12eb9f56..00000000 --- a/tests/e2e-ui/api/.gitops/deploy/templates/psqlcluster.yaml +++ /dev/null @@ -1,15 +0,0 @@ -{{- if and .Values.database.enabled (or .Values.local (eq .Values.appRuntime "cluster-dev") (eq .Values.appRuntime "host")) }} -# Local-capable PSQLCluster XR. Storage class defaults to values.database.storage.class -# (local-path for k3s/dory; override for other provisioners). -apiVersion: hops.ops.com.ai/v1alpha1 -kind: PSQLCluster -metadata: - name: {{ .Values.database.name }} - labels: - {{- include "e2e-ui-api.labels" . | nindent 4 }} -spec: - clusterName: {{ .Values.database.clusterName }} - storage: - size: {{ .Values.database.storage.size }} - class: {{ .Values.database.storage.class }} -{{- end }} diff --git a/tests/e2e-ui/api/.gitops/deploy/values.yaml b/tests/e2e-ui/api/.gitops/deploy/values.yaml index eb034ac0..9e93289c 100644 --- a/tests/e2e-ui/api/.gitops/deploy/values.yaml +++ b/tests/e2e-ui/api/.gitops/deploy/values.yaml @@ -1,8 +1,6 @@ -# Local workbench values for e2e-ui API. -# Storage class for PSQLCluster: override per local CP (e.g. local-path, hostpath, psql). - +# Cloud workload values for e2e-ui API. Local development is .gitops/local. local: false -appRuntime: package # host | cluster-dev | package +preview: false name: e2e-ui-api image: @@ -10,65 +8,24 @@ image: tag: latest pullPolicy: IfNotPresent -# Source tree is the *distributed* monorepo root; API Cargo workspace is tests/e2e-ui. -clusterDev: - image: rust:1.94-bookworm - # Bump in the local Application after host-mounted Rust/framework changes. - # This stays declarative while restarting the one-shot cargo process. - sourceGeneration: 0 - # bash -c keeps Docker ENV (PATH includes /usr/local/cargo/bin). -lc can drop it. - command: - - /bin/bash - - -c - - | - set -e - cd /workspace/tests/e2e-ui - cargo run -p e2e-runner --bin e2e-ui - workingDir: /workspace/tests/e2e-ui - sourcePath: /workspace - -# The API validates Zitadel access tokens against the generated OIDC client. -# Keep this generation aligned with the UI chart's identity.oidcGeneration. -identity: - enabled: false - oidcGeneration: 0 - connectionSecretName: "" - service: port: 8791 targetPort: 8791 env: BIND: "0.0.0.0:8791" - # Offline-friendly default until PSQL platform pack is installed. DATABASE_URL: "sqlite:/tmp/e2e-ui.db?mode=rwc" # OidcBearer — requires issuer **and** audience (project id). If either is empty # the API falls back to DevHeaders and live user surfaces misbehave. OIDC_ISSUER: "http://zitadel-zitadel.auth.svc.cluster.local:8080" # Residual after Zitadel Project Ready (external-name / status.atProvider.id). - # Override from Application values (gitops/envs/local/api.yaml) for local dogfood. OIDC_AUDIENCE: "" OIDC_JWKS_URI: "http://zitadel-zitadel.auth.svc.cluster.local:8080/oauth/v2/keys" ZITADEL_PROJECT_ID: "" -# Residual secrets (same e2e-ui-oidc object as the UI chart, in app namespace). -# OIDC app + humans live on the UI chart (identity.*); API only consumes tokens. +# Residual secrets live in the app namespace. Test identities and the OIDC app +# are provisioned separately by ui/.gitops/test-users; the API only consumes. secretEnv: ZITADEL_SERVICE_USER_TOKEN: secretName: e2e-ui-oidc key: ZITADEL_SERVICE_USER_TOKEN - -database: - # Dogfood local CP often lacks PSQLCluster CRD; disable by default for up path. - # Enable when PSQL pack is installed: --set database.enabled=true via Application values. - enabled: false - name: e2e-ui-api-psql - clusterName: local - storage: - size: 2Gi - class: local-path - -sourceDelivery: - mode: none # none | hostPath | sync - hostPath: "" - mountPath: /workspace diff --git a/tests/e2e-ui/api/.gitops/local/Chart.yaml b/tests/e2e-ui/api/.gitops/local/Chart.yaml new file mode 100644 index 00000000..fb4959e7 --- /dev/null +++ b/tests/e2e-ui/api/.gitops/local/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: e2e-ui-api +description: Editable local workload for the e2e-ui API +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/tests/e2e-ui/api/.gitops/local/README.md b/tests/e2e-ui/api/.gitops/local/README.md new file mode 100644 index 00000000..c77770a2 --- /dev/null +++ b/tests/e2e-ui/api/.gitops/local/README.md @@ -0,0 +1,17 @@ +# e2e-ui API local chart + +This standalone chart renders the editable API Deployment and Service for a +Local Workbench Environment. Hops supplies source delivery and watches this +chart; `cargo watch` owns process reload for Rust source changes. + +OIDC project / demo humans / web app live in the explicit **UI test-users** +chart (`ui/.gitops/test-users`). When `identity.enabled`, this chart derives +the same generation-specific OIDC connection Secret from the workspace +namespace. Both `OIDC_AUDIENCE` and `OIDC_CLIENT_ID` read its generated +`attribute.client_id`; only the service-user token remains in the residual +`e2e-ui-oidc` Secret. Keep `identity.oidcGeneration` aligned with the UI +Application so an OIDC rotation rolls both workloads onto matching credentials. + +Platform AuthStack, ProviderConfig, and the shared PSQLCluster stay under the +project `.gitops/local/cluster/` tree. Cloud images belong to the independent +`.gitops/deploy` chart. diff --git a/tests/e2e-ui/api/.gitops/local/templates/_helpers.tpl b/tests/e2e-ui/api/.gitops/local/templates/_helpers.tpl new file mode 100644 index 00000000..092d363d --- /dev/null +++ b/tests/e2e-ui/api/.gitops/local/templates/_helpers.tpl @@ -0,0 +1,43 @@ +{{- define "e2e-ui-api.name" -}} +{{- default .Chart.Name .Values.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "e2e-ui-api.labels" -}} +app.kubernetes.io/name: {{ include "e2e-ui-api.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{- define "e2e-ui-api.workspace" -}} +{{- coalesce .Values.environment.name .Values.environment.namespace .Release.Namespace "default" -}} +{{- end -}} + +{{- define "e2e-ui-api.oidcConnectionSecretName" -}} +{{- if .Values.identity.connectionSecretName -}} +{{- .Values.identity.connectionSecretName | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $prefix := printf "e2e-ui-%s" (include "e2e-ui-api.workspace" .) -}} +{{- $generation := int (.Values.identity.oidcGeneration | default 0) -}} +{{- if gt $generation 0 -}} +{{- printf "%s-oidc-conn-g%d" $prefix $generation | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-oidc-conn" $prefix | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{- define "e2e-ui-api.oidcClientEnv" -}} +{{- if .Values.identity.enabled }} +{{- $secret := include "e2e-ui-api.oidcConnectionSecretName" . }} +- name: OIDC_AUDIENCE + valueFrom: + secretKeyRef: + name: {{ $secret | quote }} + key: attribute.client_id +- name: OIDC_CLIENT_ID + valueFrom: + secretKeyRef: + name: {{ $secret | quote }} + key: attribute.client_id +{{- end }} +{{- end -}} diff --git a/tests/e2e-ui/api/.gitops/deploy/templates/deployment-cluster-dev.yaml b/tests/e2e-ui/api/.gitops/local/templates/deployment.yaml similarity index 85% rename from tests/e2e-ui/api/.gitops/deploy/templates/deployment-cluster-dev.yaml rename to tests/e2e-ui/api/.gitops/local/templates/deployment.yaml index 68e1614b..6860d967 100644 --- a/tests/e2e-ui/api/.gitops/deploy/templates/deployment-cluster-dev.yaml +++ b/tests/e2e-ui/api/.gitops/local/templates/deployment.yaml @@ -1,4 +1,6 @@ -{{- if or (eq .Values.appRuntime "cluster-dev") (and .Values.local (eq .Values.appRuntime "cluster-dev")) }} +{{- if not .Values.local }} +{{- fail "api/.gitops/local requires local=true" }} +{{- end }} apiVersion: apps/v1 kind: Deployment metadata: @@ -13,11 +15,10 @@ spec: app.kubernetes.io/name: {{ include "e2e-ui-api.name" . }} template: metadata: + {{- if .Values.identity.enabled }} annotations: - hops.ops.com.ai/source-generation: {{ .Values.clusterDev.sourceGeneration | default 0 | quote }} - {{- if .Values.identity.enabled }} hops.ops.com.ai/oidc-generation: {{ .Values.identity.oidcGeneration | default 0 | quote }} - {{- end }} + {{- end }} labels: app.kubernetes.io/name: {{ include "e2e-ui-api.name" . }} hops.ops.com.ai/runtime: cluster-dev @@ -50,7 +51,16 @@ spec: command -v cargo rustc --version cd {{ .Values.clusterDev.workingDir }} - cargo run -p e2e-runner --bin e2e-ui + if ! command -v cargo-watch >/dev/null 2>&1; then + cargo install cargo-watch --locked + fi + cargo watch \ + -w /workspace/tests/e2e-ui \ + -w /workspace/src \ + -w /workspace/distributed_cli \ + -w /workspace/distributed_macros \ + -w /workspace/Cargo.toml \ + -x 'run -p e2e-runner --bin e2e-ui' {{- else }} command: {{- toYaml .Values.clusterDev.command | nindent 12 }} @@ -92,4 +102,3 @@ spec: {{- end }} - name: cargo-target emptyDir: {} -{{- end }} diff --git a/tests/e2e-ui/api/.gitops/local/templates/service.yaml b/tests/e2e-ui/api/.gitops/local/templates/service.yaml new file mode 100644 index 00000000..b6ff9833 --- /dev/null +++ b/tests/e2e-ui/api/.gitops/local/templates/service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "e2e-ui-api.name" . }} + labels: + {{- include "e2e-ui-api.labels" . | nindent 4 }} +spec: + selector: + app.kubernetes.io/name: {{ include "e2e-ui-api.name" . }} + ports: + - name: http + port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} diff --git a/tests/e2e-ui/api/.gitops/local/values.yaml b/tests/e2e-ui/api/.gitops/local/values.yaml new file mode 100644 index 00000000..45df3b2a --- /dev/null +++ b/tests/e2e-ui/api/.gitops/local/values.yaml @@ -0,0 +1,54 @@ +local: true +preview: false + +environment: + name: local + namespace: default + +name: e2e-ui-api +clusterDev: + image: rust:1.94-bookworm + workingDir: /workspace/tests/e2e-ui + command: + - /bin/bash + - -c + - | + set -e + cd /workspace/tests/e2e-ui + if ! command -v cargo-watch >/dev/null 2>&1; then + cargo install cargo-watch --locked + fi + cargo watch \ + -w /workspace/tests/e2e-ui \ + -w /workspace/src \ + -w /workspace/distributed_cli \ + -w /workspace/distributed_macros \ + -w /workspace/Cargo.toml \ + -x 'run -p e2e-runner --bin e2e-ui' + +identity: + enabled: false + oidcGeneration: 0 + connectionSecretName: "" + +service: + port: 8791 + targetPort: 8791 + +env: + BIND: "0.0.0.0:8791" + DATABASE_URL: "sqlite:/tmp/e2e-ui.db?mode=rwc" + OIDC_ISSUER: "http://zitadel-zitadel.auth.svc.cluster.local:8080" + OIDC_AUDIENCE: "" + OIDC_JWKS_URI: "http://zitadel-zitadel.auth.svc.cluster.local:8080/oauth/v2/keys" + ZITADEL_PROJECT_ID: "" + +secretEnv: + ZITADEL_SERVICE_USER_TOKEN: + secretName: e2e-ui-oidc + key: ZITADEL_SERVICE_USER_TOKEN + +sourceDelivery: + mode: none + hostPath: "" + mountPath: /workspace diff --git a/tests/e2e-ui/api/.gitops/promote/Chart.yaml b/tests/e2e-ui/api/.gitops/promote/Chart.yaml new file mode 100644 index 00000000..ec42ea05 --- /dev/null +++ b/tests/e2e-ui/api/.gitops/promote/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: e2e-ui-api-promote +description: Optional cloud promotion action for the e2e-ui API +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/tests/e2e-ui/api/.gitops/promote/templates/application.yaml b/tests/e2e-ui/api/.gitops/promote/templates/application.yaml new file mode 100644 index 00000000..5f6f096a --- /dev/null +++ b/tests/e2e-ui/api/.gitops/promote/templates/application.yaml @@ -0,0 +1,31 @@ +{{- if .Values.local }} +{{- fail ".gitops/promote is cloud-only; local Environments render .gitops/local directly" }} +{{- end }} +{{- $environment := required "environment.name is required" .Values.environment.name }} +{{- $namespace := required "environment.namespace is required" .Values.environment.namespace }} +{{- $repoURL := required "source.repoURL is required" .Values.source.repoURL }} +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: {{ printf "%s-e2e-ui-api" $environment }} + namespace: {{ .Values.argocd.namespace }} +spec: + project: {{ .Values.argocd.project }} + source: + repoURL: {{ $repoURL | quote }} + targetRevision: {{ .Values.source.targetRevision | quote }} + path: tests/e2e-ui/api/.gitops/deploy + helm: + valuesObject: + local: false + preview: {{ .Values.preview }} + {{- with .Values.deploy.values }} + {{- toYaml . | nindent 8 }} + {{- end }} + destination: + server: https://kubernetes.default.svc + namespace: {{ $namespace }} + syncPolicy: + automated: + prune: true + selfHeal: true diff --git a/tests/e2e-ui/api/.gitops/promote/values.yaml b/tests/e2e-ui/api/.gitops/promote/values.yaml new file mode 100644 index 00000000..34a16f02 --- /dev/null +++ b/tests/e2e-ui/api/.gitops/promote/values.yaml @@ -0,0 +1,13 @@ +local: false +preview: false +environment: + name: "" + namespace: "" +source: + repoURL: "" + targetRevision: HEAD +argocd: + namespace: argocd + project: default +deploy: + values: {} diff --git a/tests/e2e-ui/gitops/README.md b/tests/e2e-ui/gitops/README.md deleted file mode 100644 index 45cf822d..00000000 --- a/tests/e2e-ui/gitops/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# e2e-ui local gitops - -```text -gitops/ - cluster/ # fixture CP tree (real meta repos: put this at meta root) - envs/ - local/ # app Applications → worktree namespaces -``` - -```bash -# Terminal 1: cluster apply/watch -hops local gitops cluster ./gitops/cluster \ - --cluster-provider kind --docker-provider dory \ - --cluster-name hops --context kind-hops - -# Terminal 2: per-workspace apps apply/watch -hops local gitops worktree ./gitops/envs/local --name alice \ - --cluster-provider kind --docker-provider dory \ - --cluster-name hops --context kind-hops - -# One-shot (CI / scripts): add --once -# hops local gitops cluster ./gitops/cluster --once -``` - -On multi-project metas: put `gitops/cluster` at the **meta root**; projects keep -`.gitops/deploy` charts; env Application YAMLs point at those charts. diff --git a/tests/e2e-ui/gitops/cluster/README.md b/tests/e2e-ui/gitops/cluster/README.md deleted file mode 100644 index 26df8c24..00000000 --- a/tests/e2e-ui/gitops/cluster/README.md +++ /dev/null @@ -1,80 +0,0 @@ -# `gitops/cluster` — local control-plane configuration - -> **Real meta repos:** put this tree at the **meta root** (`gitops/cluster/`), -> not inside a single project. One local CP (dory) serves every worktree; apps -> only get different **namespaces**. This e2e-ui copy is a self-contained -> fixture — same files, nested under the test app. Point hops with -> `hops local gitops cluster ./gitops/cluster` or `HOPS_LOCAL_CLUSTER` when -> applying a meta-root cluster tree. - -Platform / CP resources for the **shared** local control plane: - -```text -/ - gitops/cluster/ # ← this tree (AuthStack, PSQLStack, configurations) - gitops/envs/local/ # Application YAMLs → namespace = --name - clients/foo/.gitops/deploy/ - platform/api/.gitops/deploy/ -``` - -| Path | Contents | -|------|----------| -| `configurations/` | Crossplane `Configuration` installs (psql-stack, auth-stack, secret-stack) | -| `stacks/psql.yaml` | **`PSQLStack` XR** — CNPG + SC `psql` (local-path) | -| `stacks/auth.yaml` | **`AuthStack` XR** — Zitadel + embedded Postgres | -| `secrets/` | **`SecretStack`** + vault-auth-delegator CRB (ESO + Vault; k8s auth via Helm postStart) | -| `auth/` | Auth residuals (e.g. masterkey ExternalSecret), not the XR claim | -| `providers/` | Provider installs + per-provider DRCs (`helm.yaml` / `helm-drc.yaml`, …) | -| `providerconfigs/` | ProviderConfig shapes (`secretRef` only) | -| `e2e-identity/` | **Moved** — worktree UI chart `identity.*` values (pointer README only) | - -## Apply + watch (local gitops vibes) - -This fixture pins the auth stack's content-derived local dev image because its -Grant reference support is newer than the published package. Build and push -that package once before the cluster watcher. Re-run the source install and -update `configurations/auth-stack.yaml` when the auth package content changes. - -```bash -# One-time package build/push for the pin in configurations/auth-stack.yaml -hops config install --path /xrs/stacks/k8s/auth \ - --cluster-provider kind --docker-provider dory \ - --cluster-name hops --context kind-hops - -# Terminal 1: shared control-plane tree (apply + watch) -hops local gitops cluster ./gitops/cluster \ - --cluster-provider kind --docker-provider dory \ - --cluster-name hops --context kind-hops - -# Terminal 2: per-workspace Applications + charts (apply + watch) -hops local gitops worktree ./gitops/envs/local --name alice \ - --cluster-provider kind --docker-provider dory \ - --cluster-name hops --context kind-hops -``` - -Crossplane reconciles XRs (`PSQLStack`, `AuthStack`, `SecretStack`, …) after each apply. -Edit `stacks/auth.yaml`, `stacks/psql.yaml`, or `secrets/stack.yaml` → saved → applied → CP converges. - -**SecretStack (local):** install the Configuration from source first -(`hops config install --path …/xrs/stacks/aws/secret`), then apply this cluster tree -so `secrets/stack.yaml` is not stuck Unpacking. - -## vs `gitops/envs/` - -| Tree | Purpose | -|------|---------| -| **cluster/** | CP packages + platform XRs (PSQLStack, AuthStack) | -| **envs/\-web` — redirects + Login V2 baseUri) | - -| Secret | Purpose | -|--------|---------| -| `e2e-human-passwords` | Keys `alice`, `bob`, `admin` (cluster-shared; local chart seeds when `local: true`) | -| `zitadel-credentials` | ProviderConfig / ClusterProviderConfig (`hops local zitadel`) | -| Oidc connection Secret | Oidc MR `writeConnectionSecretToRef` → `attribute.client_id` / `attribute.client_secret` (UI mounts these) | -| `e2e-ui-oidc` | `AUTH_SECRET` (+ optional `ZITADEL_SERVICE_USER_TOKEN` from AuthStack `auth/login-client`) | - -```bash -# Humans (if not using chart local seed): -kubectl -n default create secret generic e2e-human-passwords \ - --from-literal=alice=Password1! \ - --from-literal=bob=Password1! \ - --from-literal=admin=Password1! - -# Login V2 service user (after AuthStack Ready): -kubectl -n create secret generic e2e-ui-oidc \ - --from-literal=AUTH_SECRET='local-workbench-dev-auth-secret-not-for-prod' \ - --from-literal=ZITADEL_SERVICE_USER_TOKEN="$(kubectl -n auth get secret login-client -o jsonpath='{.data.pat}' | base64 -d)" \ - --dry-run=client -o yaml | kubectl apply -f - -# OIDC_CLIENT_ID/SECRET: do not hand-copy — Deployment reads Oidc connection Secret. -``` - -Demo login (every env): **alice / bob / admin · Password1!** - -Do **not** commit live `orgId`, Project external ids, or OIDC client ids into Application values. MRs use `projectIdRef` and ProviderConfig org default. -## e2e-ui app (`ns: --name`) - -| Secret | Keys | Used by | -|--------|------|---------| -| `e2e-ui-oidc` | `AUTH_SECRET`, `ZITADEL_SERVICE_USER_TOKEN` | UI session/login residuals | -| `e2e-ui--oidc-conn` | `attribute.client_id`, `attribute.client_secret` | Oidc MR connection secret; UI client env | - -```bash -# Login-client PAT: AuthStack residual secret auth/login-client key pat -kubectl -n dogfood create secret generic e2e-ui-oidc \ - --from-literal=AUTH_SECRET='local-workbench-dogfood-auth-secret-not-for-prod' \ - --from-literal=ZITADEL_SERVICE_USER_TOKEN="$(kubectl -n auth get secret login-client -o jsonpath='{.data.pat}' | base64 -d)" -``` - -Do not copy OIDC client IDs or secrets into this Secret. The Oidc managed -resource writes them to `e2e-ui--oidc-conn`, and the Deployment -references `attribute.client_id` / `attribute.client_secret` there. - -Non-secret OIDC config (issuer, AUTH_URL) stays in `ui/.gitops/deploy/values.yaml`. - -### Login V2 (required for browser sign-in) - -Gitops (UI chart when `identity.enabled`): - -1. **Oidc** app MR — redirects + `loginVersion.loginV2.baseUri` from release ns -2. **Features** MR (`instance.zitadel…/Features`) — instance `loginV2.required` - + `baseUri` so authorize redirects to this UI’s `/login` - -Both use `http://e2e-ui-ui..svc.cluster.local:5180`. Instance Features -is global per AuthStack (last applied worktree wins). Do not hand-`PUT` -`/v2/features/instance` — re-reconcile the UI chart instead. - -Prefer app-level baseUri over instance-wide -defaults so multiple worktrees can coexist. - -## SecretStack / ESO + Vault (`secrets/`) - -Local fixture uses **`backend: vault`** with **`vault.install: true`** (in-cluster -Vault Helm + ESO). No AWS PodIdentity on dory. - -| Resource | Purpose | -|----------|---------| -| `secrets/stack.yaml` | SecretStack XR — ESO + Vault Helm + ClusterSecretStore | -| `secrets/vault-auth-delegator.yaml` | CRB: Vault SA → `system:auth-delegator` (TokenReview) | -| Helm `vault` `server.postStart` | Init/unseal (once), KV mount, k8s auth + ESO role | -| PVC (`dataStorage` 1Gi) | File storage under `/vault/data` (survives pod restarts) | -| ClusterSecretStore `vault` | ESO → Vault (Ready after postStart + CRB) | - -### Persistence - -`server.dev` is **off**. Vault runs standalone with `storage "file"` on a PVC -(default StorageClass, dory: `local-path`). First start initializes and writes: - -```text -/vault/data/.hops-init # Unseal Key 1 + Initial Root Token (chmod 600) -``` - -Later starts: unseal from that file, re-apply k8s auth (idempotent). **KV data -survives pod restarts**; deleting the PVC is a full reset. - -Root token is **not** the fixed string `root` (that was dev-only). For CLI writes: - -```bash -export VAULT_TOKEN="$( - kubectl -n vault exec vault-0 -- \ - awk '/^Initial Root Token:/{print $NF}' /vault/data/.hops-init -)" -export VAULT_ADDR=http://127.0.0.1:8200 -# kubectl port-forward -n vault svc/vault 8200:8200 # if needed -hops secrets sync vault -y -``` - -### Declarative trust path - -1. Apply cluster gitops (SecretStack + CRB). -2. postStart: init (once) → unseal → `secret/` KV v2 → k8s auth for ESO. -3. ClusterSecretStore `vault` Ready. -4. **Write** with `hops secrets sync vault`. -5. **Read** via ExternalSecrets (auth masterkey, UI oidc/humans). - -Install package from source before first apply: - -```bash -hops config install --path /xrs/stacks/aws/secret --context dory -hops local gitops cluster ./gitops/cluster -``` - -Switching an existing **dev/inmem** Vault release to file storage may need a -one-time recreate of the Helm release / PVC (data was ephemeral anyway). - -Do **not** put the SecretStack claim under meta `local/` — that folder is for -ad-hoc colima-style one-off claims, not the workbench cluster gitops path. - -## Rules - -1. **Never** put passwords, PATs, client secrets, or masterkeys in committed YAML. -2. Gitops may name Secrets and keys (`secretRef` / `secretEnv`). -3. Create Secrets before the XRs/Deployments that need them (or accept CrashLoop until present). -4. Cloud: prefer ESO / SOPS; local: `kubectl create` or `hops local zitadel|aws|github`. -5. Platform secrets engine for local CP lives in **`gitops/cluster/secrets/`**, not `local/`. diff --git a/tests/e2e-ui/gitops/cluster/auth/README.md b/tests/e2e-ui/gitops/cluster/auth/README.md deleted file mode 100644 index 7da694c5..00000000 --- a/tests/e2e-ui/gitops/cluster/auth/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# Auth (local) - -| File | Kind | -|------|------| -| [`../stacks/auth.yaml`](../stacks/auth.yaml) | `AuthStack` XR — Zitadel + embedded `PSQLCluster` | -| `external-secret-masterkey.yaml` | Optional ESO materialize of `zitadel-masterkey` from Vault | - -## Local addressing (no ingress) - -| | Value | -|--|--------| -| Install namespace | **`auth`** | -| AuthStack XR name | **`zitadel`** | -| Helm Service (default fullname) | **`zitadel-zitadel.auth.svc.cluster.local:8080`** | -| OIDC issuer / `domain` | `http://zitadel-zitadel.auth.svc.cluster.local:8080` | -| Gateway / ingress | disabled — ClusterIP only for now | - -## Apply order - -```bash -hops local start --cluster-provider kind --docker-provider dory \ - --cluster-name hops --gitops ./gitops/cluster -# or after configurations: -hops local gitops cluster ./gitops/cluster - -kubectl get authstack zitadel -n default -kubectl get svc -n auth -# expect: zitadel-zitadel (API) — not a separate short-name alias -``` - -## e2e-ui identity (worktree) - -App Project / humans / OIDC app render from the **UI** chart when -`gitops/envs/local/ui.yaml` sets `identity.enabled: true` (not cluster gitops). - -ProviderConfig residual (cluster): - -```bash -hops local zitadel --source-context dory --source-namespace auth \ - --domain zitadel-zitadel.auth.svc.cluster.local --port 8080 --insecure -``` - -## Secrets - -Masterkey is **not** inline — `secretRef` to `zitadel-masterkey` in ns `auth`: - -```bash -kubectl -n auth create secret generic zitadel-masterkey \ - --from-literal=masterkey='hops-like-basketball-but-for-ops' # exactly 32 bytes -``` - -Create **before** AuthStack becomes Ready (or re-apply after). Full table: -[../SECRETS.md](../SECRETS.md). - -## Residual - -- Host browser access without ingress is later (gateway / map / promote) diff --git a/tests/e2e-ui/gitops/envs/local/api.yaml b/tests/e2e-ui/gitops/envs/local/api.yaml deleted file mode 100644 index 358c46a3..00000000 --- a/tests/e2e-ui/gitops/envs/local/api.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: hops.local/v1alpha1 -kind: Application -metadata: - name: e2e-ui-api -spec: - source: - path: ../../../api/.gitops/deploy - helm: - values: - local: true - appRuntime: cluster-dev - clusterDev: - sourceGeneration: 1 - database: - enabled: false - # No live project/client UUIDs. The API chart derives the matching - # workspace Secret and reads the generated client id as its audience. - identity: - enabled: true - oidcGeneration: 1 - secretEnv: - ZITADEL_SERVICE_USER_TOKEN: - secretName: e2e-ui-oidc - key: ZITADEL_SERVICE_USER_TOKEN - syncPolicy: - prune: true diff --git a/tests/e2e-ui/gitops/envs/local/ui.yaml b/tests/e2e-ui/gitops/envs/local/ui.yaml deleted file mode 100644 index 204baf99..00000000 --- a/tests/e2e-ui/gitops/envs/local/ui.yaml +++ /dev/null @@ -1,31 +0,0 @@ -apiVersion: hops.local/v1alpha1 -kind: Application -metadata: - name: e2e-ui-ui -spec: - source: - path: ../../../ui/.gitops/deploy - helm: - values: - local: true - appRuntime: cluster-dev - clusterDev: - sourceGeneration: 1 - # Chart identity MRs: Project/roles/humans + Oidc. - # - projectIdRef / labels (no live UUIDs) - # - writeConnectionSecretToRef for client id/secret - # - orgId omitted (ProviderConfig PAT org) - identity: - enabled: true - # Declarative client-secret rotation; bump and let prune replace OIDC. - oidcGeneration: 1 - projectName: e2e-ui - projectNamespace: default - humansNamespace: default - instanceLoginV2: true - demoUsers: true - providerConfigRef: - name: default - kind: ClusterProviderConfig - syncPolicy: - prune: true diff --git a/tests/e2e-ui/scripts/dual-worktree-suite.sh b/tests/e2e-ui/scripts/dual-worktree-suite.sh index d64b6458..a8dd7121 100755 --- a/tests/e2e-ui/scripts/dual-worktree-suite.sh +++ b/tests/e2e-ui/scripts/dual-worktree-suite.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash -# Local dual-workspace suite: two git worktrees, hops gitops for each, HMR title check. +# Local dual-Environment suite: two Git worktrees in one Cluster, plus HMR. # # Prerequisites: -# - local CP with hops (dory/colima/kind) and kubectl context +# - e2e-ui Cluster controller already running # - AuthStack optional for identity; HMR does not require login # - hops binary on PATH or HOPS=path # - helm, git, node, npm @@ -22,10 +22,8 @@ export KUBECONFIG ALICE_NAME="${ALICE_NAME:-alice}" BOB_NAME="${BOB_NAME:-bob}" SKIP_GITOPS="${SKIP_GITOPS:-0}" -CLUSTER_PROVIDER="${CLUSTER_PROVIDER:-kind}" -DOCKER_PROVIDER="${DOCKER_PROVIDER:-dory}" -CLUSTER_NAME="${CLUSTER_NAME:-hops}" -CONTEXT="${CONTEXT:-kind-hops}" +REGISTERED_ALICE=0 +REGISTERED_BOB=0 log() { printf '%s\n' "$*"; } die() { printf 'FAIL: %s\n' "$*" >&2; exit 1; } @@ -62,6 +60,12 @@ BRANCH_ALICE="suite/dual-${ALICE_NAME}-$$" BRANCH_BOB="suite/dual-${BOB_NAME}-$$" cleanup_worktrees() { + if [[ "$REGISTERED_ALICE" == "1" ]] && command -v "$HOPS" >/dev/null 2>&1; then + "$HOPS" local gitops environment --name "$ALICE_NAME" --down >/dev/null 2>&1 || true + fi + if [[ "$REGISTERED_BOB" == "1" ]] && command -v "$HOPS" >/dev/null 2>&1; then + "$HOPS" local gitops environment --name "$BOB_NAME" --down >/dev/null 2>&1 || true + fi git -C "$DIST_ROOT" worktree remove --force "$ALICE_WT" 2>/dev/null || true git -C "$DIST_ROOT" worktree remove --force "$BOB_WT" 2>/dev/null || true git -C "$DIST_ROOT" branch -D "$BRANCH_ALICE" "$BRANCH_BOB" 2>/dev/null || true @@ -85,15 +89,15 @@ overlay_e2e "$ALICE_WT" overlay_e2e "$BOB_WT" # Bob: do not own instance Features -BOB_UI_APP="$BOB_WT/tests/e2e-ui/gitops/envs/local/ui.yaml" -if [[ -f "$BOB_UI_APP" ]]; then - if rg -q 'instanceLoginV2:' "$BOB_UI_APP"; then - sed -i.bak 's/instanceLoginV2: true/instanceLoginV2: false/' "$BOB_UI_APP" || true +BOB_ENVIRONMENT="$BOB_WT/tests/e2e-ui/.gitops/local/environment.yaml" +if [[ -f "$BOB_ENVIRONMENT" ]]; then + if rg -q 'instanceLoginV2:' "$BOB_ENVIRONMENT"; then + sed -i.bak 's/instanceLoginV2: true/instanceLoginV2: false/' "$BOB_ENVIRONMENT" || true else - # inject under identity: + # Inject into the explicit test-users deploy values. python3 - </dev/null 2>&1; then - die "kubectl cannot talk to a cluster (set KUBECONFIG); dual gitops requires local CP" + die "kubectl cannot talk to the e2e-ui Cluster (start: hops local gitops cluster ./.gitops/local/cluster.yaml)" fi - log "reconciling workspace $ALICE_NAME from $ALICE_E2E" - (cd "$ALICE_E2E" && "$HOPS" local gitops worktree ./gitops/envs/local \ - --name "$ALICE_NAME" --cluster-provider "$CLUSTER_PROVIDER" \ - --docker-provider "$DOCKER_PROVIDER" --cluster-name "$CLUSTER_NAME" \ - --context "$CONTEXT" --once) \ + log "registering Environment $ALICE_NAME from $ALICE_E2E" + (cd "$ALICE_E2E" && "$HOPS" local gitops environment ./.gitops/local/environment.yaml \ + --name "$ALICE_NAME") \ | tee "$SCRATCH/gitops-alice.log" \ - || die "workspace gitops alice failed — see $SCRATCH/gitops-alice.log" - log "reconciling workspace $BOB_NAME from $BOB_E2E" - (cd "$BOB_E2E" && "$HOPS" local gitops worktree ./gitops/envs/local \ - --name "$BOB_NAME" --cluster-provider "$CLUSTER_PROVIDER" \ - --docker-provider "$DOCKER_PROVIDER" --cluster-name "$CLUSTER_NAME" \ - --context "$CONTEXT" --once) \ + || die "Environment registration for alice failed — see $SCRATCH/gitops-alice.log" + REGISTERED_ALICE=1 + log "registering Environment $BOB_NAME from $BOB_E2E" + (cd "$BOB_E2E" && "$HOPS" local gitops environment ./.gitops/local/environment.yaml \ + --name "$BOB_NAME") \ | tee "$SCRATCH/gitops-bob.log" \ - || die "workspace gitops bob failed — see $SCRATCH/gitops-bob.log" + || die "Environment registration for bob failed — see $SCRATCH/gitops-bob.log" + REGISTERED_BOB=1 fi wait_http "$ALICE_URL" alice 120 diff --git a/tests/e2e-ui/scripts/helm-contract-test.sh b/tests/e2e-ui/scripts/helm-contract-test.sh index 4cf5b798..31be72e9 100755 --- a/tests/e2e-ui/scripts/helm-contract-test.sh +++ b/tests/e2e-ui/scripts/helm-contract-test.sh @@ -1,12 +1,8 @@ #!/usr/bin/env bash -# Chart contract tests for multi-workspace identity scopes. -# No cluster required — helm template only. -# -# cd tests/e2e-ui && ./scripts/helm-contract-test.sh +# Render-only contract for the Local Workbench fixture. No cluster required. set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" -CHART="$ROOT/ui/.gitops/deploy" fail=0 need() { @@ -18,153 +14,164 @@ need() { need helm need rg -render() { - local ns="$1" - local features="${2:-true}" - local oidc_generation="${3:-0}" - local source_generation="${4:-0}" - helm template "e2e-ui-ui-${ns}" "$CHART" \ - --namespace "$ns" \ - --set local=true \ - --set appRuntime=cluster-dev \ - --set "namespace=${ns}" \ - --set "clusterDev.sourceGeneration=${source_generation}" \ - --set identity.enabled=true \ - --set "identity.oidcGeneration=${oidc_generation}" \ - --set identity.projectName=e2e-ui \ - --set identity.projectNamespace=default \ - --set identity.humansNamespace=default \ - --set identity.mrNamespace= \ - --set "identity.instanceLoginV2=${features}" \ - --set identity.providerConfigRef.name=default \ - --set identity.providerConfigRef.kind=ClusterProviderConfig -} - assert_contains() { - local hay="$1" needle="$2" msg="$3" - if ! printf '%s' "$hay" | rg -q --fixed-strings "$needle"; then - echo "FAIL: $msg (missing: $needle)" >&2 - fail=1 + local hay="$1" needle="$2" message="$3" + if printf '%s' "$hay" | rg -q --fixed-strings -- "$needle"; then + echo "ok: $message" else - echo "ok: $msg" + echo "FAIL: $message (missing: $needle)" >&2 + fail=1 fi } assert_not_contains() { - local hay="$1" needle="$2" msg="$3" - if printf '%s' "$hay" | rg -q --fixed-strings "$needle"; then - echo "FAIL: $msg (unexpected: $needle)" >&2 + local hay="$1" needle="$2" message="$3" + if printf '%s' "$hay" | rg -q --fixed-strings -- "$needle"; then + echo "FAIL: $message (unexpected: $needle)" >&2 fail=1 else - echo "ok: $msg" + echo "ok: $message" fi } -check_workspace() { - local ns="$1" - local features="$2" - echo "=== workspace ${ns} (instanceLoginV2=${features}) ===" - local out - out="$(render "$ns" "$features")" - - assert_contains "$out" "name: e2e-ui-${ns}-web" "OIDC app name is worktree-scoped" - assert_contains "$out" "namespace: ${ns}" "worktree resources reference ${ns}" - assert_contains "$out" "baseUri: \"http://e2e-ui-ui.${ns}.svc.cluster.local:5180\"" \ - "Login V2 / OIDC baseUri uses release namespace" - assert_contains "$out" "http://e2e-ui-ui.${ns}.svc.cluster.local:5180/auth/callback/oidc" \ - "OIDC redirect uses release FQDN" - assert_contains "$out" "namespace: default" "shared identity keeps default namespace" - assert_contains "$out" "name: e2e-ui" "shared Project name e2e-ui" - assert_contains "$out" "projectRoleCheck: true" \ - "Project requires a GitOps-owned role grant" - assert_contains "$out" "name: e2e-role-user" "shared role e2e-role-user" - assert_contains "$out" "name: e2e-alice" "shared human e2e-alice" - assert_contains "$out" "name: e2e-alice-e2e-ui" "shared Grant for alice" - assert_contains "$out" "name: e2e-bob-e2e-ui" "shared Grant for bob" - assert_contains "$out" "name: e2e-admin-e2e-ui" "shared Grant for admin" - assert_contains "$out" "userIdRef:" "Grant resolves HumanUser by reference" - assert_contains "$out" "projectIdRef:" "Grant resolves Project by reference" - assert_contains "$out" 'roles: ["user","admin"]' "admin receives user and admin roles" - assert_not_contains "$out" "oidc-local-seed" \ - "GitOps does not overwrite the residual OIDC session/PAT Secret" - assert_contains "$out" "name: \"e2e-ui-${ns}-oidc-conn\"" \ - "OIDC connection secret is worktree-scoped" - assert_contains "$out" "key: attribute.client_id" \ - "UI reads client id from Oidc connection secret" - assert_contains "$out" "key: attribute.client_secret" \ - "UI reads client secret from Oidc connection secret" - assert_contains "$out" "namespace: default" "projectIdRef targets shared Project ns" - assert_contains "$out" "projectIdRef:" "OIDC references Project" - # Project MR must declare default ns (not worktree) - if ! printf '%s' "$out" | rg -U -q 'kind: Project\nmetadata:\n name: e2e-ui\n namespace: default'; then - # tolerate key reorder from helm - if ! printf '%s' "$out" | awk '/kind: Project/{p=1} p&&/namespace: default/{found=1} p&&/^---/{exit} END{exit !found}'; then - echo "FAIL: Project not clearly in default namespace" >&2 - fail=1 - else - echo "ok: Project in default namespace" - fi - else - echo "ok: Project in default namespace" - fi - - if [ "$features" = "true" ]; then - assert_contains "$out" "kind: Features" "Features MR rendered when instanceLoginV2" - assert_contains "$out" "name: e2e-ui-login-v2" "Features name stable" +assert_render_fails() { + local message="$1" + shift + if "$@" >/dev/null 2>&1; then + echo "FAIL: $message" >&2 + fail=1 else - assert_not_contains "$out" "kind: Features" "no Features when instanceLoginV2=false" + echo "ok: $message" fi - - assert_not_contains "$out" "hops-wt-" "no legacy hops-wt- prefix" } -check_workspace alice true -check_workspace bob false - -# Distinct OIDC names across workspaces -alice_out="$(render alice true)" -bob_out="$(render bob false)" -assert_contains "$alice_out" "e2e-ui-alice-web" "alice OIDC name" -assert_contains "$bob_out" "e2e-ui-bob-web" "bob OIDC name" -assert_not_contains "$alice_out" "e2e-ui-bob-web" "alice render has no bob OIDC" -assert_not_contains "$bob_out" "e2e-ui-alice-web" "bob render has no alice OIDC" +render_local_ui() { + local namespace="$1" preview="${2:-false}" generation="${3:-0}" + helm template "e2e-ui-ui-${namespace}" "$ROOT/ui/.gitops/local" \ + --namespace "$namespace" \ + --set local=true \ + --set "preview=${preview}" \ + --set "environment.name=${namespace}" \ + --set "environment.namespace=${namespace}" \ + --set identity.enabled=true \ + --set "identity.oidcGeneration=${generation}" +} -# Rotation changes the managed OIDC resource and connection Secret generation. -# The new UI pod cannot start until Crossplane publishes that generation's -# credentials, avoiding a race with stale data under a stable Secret name. -rotated_out="$(render alice true 1)" -assert_contains "$rotated_out" "name: e2e-ui-alice-web-g1" "OIDC generation rotates MR name" -assert_contains "$rotated_out" 'hops.ops.com.ai/oidc-generation: "1"' \ - "OIDC generation rolls UI pod template" -assert_contains "$rotated_out" 'name: "e2e-ui-alice-oidc-conn-g1"' \ - "OIDC rotation uses matching connection secret generation" +render_test_users() { + local namespace="$1" features="${2:-true}" generation="${3:-0}" + helm template "e2e-ui-test-users-${namespace}" "$ROOT/ui/.gitops/test-users" \ + --namespace "$namespace" \ + --set local=true \ + --set preview=false \ + --set "environment.name=${namespace}" \ + --set "environment.namespace=${namespace}" \ + --set identity.enabled=true \ + --set "identity.oidcGeneration=${generation}" \ + --set "identity.instanceLoginV2=${features}" \ + --set identity.providerConfigRef.name=default \ + --set identity.providerConfigRef.kind=ClusterProviderConfig +} -ui_source_out="$(render alice true 1 7)" -assert_contains "$ui_source_out" 'hops.ops.com.ai/source-generation: "7"' \ - "UI source generation rolls the Vite dev process" +echo "=== project definitions ===" +cluster_definition="$(<"$ROOT/.gitops/local/cluster.yaml")" +environment_definition="$(<"$ROOT/.gitops/local/environment.yaml")" +assert_contains "$cluster_definition" "kind: Cluster" "Cluster definition is Kubernetes-shaped" +assert_contains "$cluster_definition" "path: tests/e2e-ui/.gitops/local/cluster" "Cluster selects the project manifest tree from the checkout root" +assert_contains "$cluster_definition" "mountRoot: ../../../.." "Cluster mounts the Distributed checkout root" +assert_contains "$environment_definition" "kind: Environment" "Environment definition is Kubernetes-shaped" +assert_contains "$environment_definition" "- path: api" "Environment deploys the API application root" +assert_contains "$environment_definition" "- path: ui" "Environment deploys the UI application root" +assert_contains "$environment_definition" "chart: .gitops/test-users" "test users are an explicit deploy chart" +assert_not_contains "$environment_definition" ".gitops/promote" "local Environment does not require promotion" +assert_not_contains "$environment_definition" "appRuntime" "Environment has no runtime mode switch" +assert_not_contains "$environment_definition" "sourceGeneration" "Environment has no manual restart counter" -# API derives the same connection Secret and uses the generated client id as -# both the JWT audience and client id. No Zitadel UUID belongs in values. -api_out="$(helm template e2e-ui-api-alice "$ROOT/api/.gitops/deploy" \ +echo "=== direct local charts ===" +ui_local="$(render_local_ui alice false 1)" +ui_preview="$(render_local_ui alice true 1)" +api_local="$(helm template e2e-ui-api-alice "$ROOT/api/.gitops/local" \ --namespace alice \ --set local=true \ - --set appRuntime=cluster-dev \ - --set namespace=alice \ - --set clusterDev.sourceGeneration=7 \ + --set preview=false \ + --set environment.name=alice \ + --set environment.namespace=alice \ --set identity.enabled=true \ --set identity.oidcGeneration=1)" -assert_contains "$api_out" 'hops.ops.com.ai/source-generation: "7"' \ - "API source generation rolls the one-shot dev process" -assert_contains "$api_out" 'hops.ops.com.ai/oidc-generation: "1"' \ - "API OIDC generation rolls pod template" -assert_contains "$api_out" 'name: "e2e-ui-alice-oidc-conn-g1"' \ - "API uses matching generated OIDC connection secret" -assert_contains "$api_out" "name: OIDC_AUDIENCE" \ - "API audience comes from the generated connection secret" -assert_contains "$api_out" "key: attribute.client_id" \ - "API audience/client id use the generated client id key" -if [ "$fail" -ne 0 ]; then +for rendered in "$ui_local" "$ui_preview" "$api_local"; do + assert_contains "$rendered" "kind: Deployment" "local chart renders its workload directly" + assert_contains "$rendered" "kind: Service" "local chart renders its Service directly" + assert_not_contains "$rendered" "kind: Application" "local chart does not require an Application wrapper" + assert_not_contains "$rendered" "kind: PSQLCluster" "application charts do not own the shared PSQLCluster" + assert_not_contains "$rendered" "source-generation" "source changes do not use restart annotations" + assert_not_contains "$rendered" "appRuntime" "render has no mixed runtime selector" +done +assert_contains "$ui_local" "http://e2e-ui-ui.alice.svc.cluster.local:5180" "UI AUTH_URL follows the Environment namespace" +assert_contains "$ui_local" 'name: "e2e-ui-alice-oidc-conn-g1"' "UI consumes the Environment OIDC connection Secret" +assert_contains "$api_local" 'name: "e2e-ui-alice-oidc-conn-g1"' "API consumes the same OIDC connection Secret" +assert_contains "$api_local" "cargo watch" "API process owns Rust source reload" +assert_render_fails "local UI chart rejects local=false" \ + helm template invalid "$ROOT/ui/.gitops/local" --set local=false +assert_render_fails "local API chart rejects local=false" \ + helm template invalid "$ROOT/api/.gitops/local" --set local=false + +echo "=== separate test identities ===" +alice_users="$(render_test_users alice true 1)" +bob_users="$(render_test_users bob false 0)" +assert_contains "$alice_users" "kind: Project" "test-users chart renders the shared Project" +assert_contains "$alice_users" "kind: HumanUser" "test-users chart renders demo humans" +assert_contains "$alice_users" "kind: Grant" "test-users chart renders role grants" +assert_contains "$alice_users" "kind: Oidc" "test-users chart renders the Environment OIDC app" +assert_contains "$alice_users" "kind: Features" "test-users chart may own Login V2 features" +assert_not_contains "$bob_users" "kind: Features" "features can be disabled for a second Environment" +assert_contains "$alice_users" "name: e2e-ui-alice-web-g1" "alice identity is Environment-scoped" +assert_contains "$bob_users" "name: e2e-ui-bob-web" "bob identity is Environment-scoped" +assert_not_contains "$alice_users" "kind: Deployment" "test-users chart contains no workload Deployment" +assert_not_contains "$alice_users" "kind: Service" "test-users chart contains no workload Service" + +echo "=== independent cloud charts ===" +ui_cloud="$(helm template e2e-ui-ui "$ROOT/ui/.gitops/deploy" --namespace staging --set local=false --set preview=false)" +api_cloud="$(helm template e2e-ui-api "$ROOT/api/.gitops/deploy" --namespace staging --set local=false --set preview=false)" +for rendered in "$ui_cloud" "$api_cloud"; do + assert_contains "$rendered" "kind: Deployment" "cloud chart renders the packaged workload" + assert_contains "$rendered" "kind: Service" "cloud chart renders the packaged Service" + assert_not_contains "$rendered" "hostPath:" "cloud chart has no local source mount" + assert_not_contains "$rendered" "kind: HumanUser" "cloud workload chart has no test identities" +done +assert_render_fails "cloud UI chart rejects local=true" \ + helm template invalid "$ROOT/ui/.gitops/deploy" --set local=true +assert_render_fails "cloud API chart rejects local=true" \ + helm template invalid "$ROOT/api/.gitops/deploy" --set local=true + +echo "=== optional cloud promotion ===" +ui_promote="$(helm template promote-ui "$ROOT/ui/.gitops/promote" \ + --set local=false \ + --set preview=true \ + --set environment.name=preview-42 \ + --set environment.namespace=preview-42 \ + --set source.repoURL=https://example.invalid/distributed.git \ + --set source.targetRevision=revision-sentinel \ + --set deploy.values.image.tag=preview-42 \ + --set workflowOnly=do-not-forward)" +api_promote="$(helm template promote-api "$ROOT/api/.gitops/promote" \ + --set local=false \ + --set preview=false \ + --set environment.name=staging \ + --set environment.namespace=staging \ + --set source.repoURL=https://example.invalid/distributed.git \ + --set deploy.values.image.tag=release-1)" +assert_contains "$ui_promote" "path: tests/e2e-ui/ui/.gitops/deploy" "UI promotion targets only the cloud deploy chart" +assert_contains "$api_promote" "path: tests/e2e-ui/api/.gitops/deploy" "API promotion targets only the cloud deploy chart" +assert_contains "$ui_promote" "tag: preview-42" "promotion forwards deploy.values" +assert_not_contains "$ui_promote" "workflowOnly" "promotion workflow values do not leak into workload values" +assert_render_fails "promotion chart rejects local=true" \ + helm template invalid "$ROOT/ui/.gitops/promote" \ + --set local=true \ + --set environment.name=local \ + --set environment.namespace=local \ + --set source.repoURL=https://example.invalid/distributed.git + +if [[ "$fail" -ne 0 ]]; then echo "helm-contract-test: FAILED" >&2 exit 1 fi diff --git a/tests/e2e-ui/ui/.gitops/deploy/Chart.yaml b/tests/e2e-ui/ui/.gitops/deploy/Chart.yaml index 88e9cbc9..7e3843b7 100644 --- a/tests/e2e-ui/ui/.gitops/deploy/Chart.yaml +++ b/tests/e2e-ui/ui/.gitops/deploy/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: e2e-ui-ui -description: Local workbench dogfood chart for e2e-ui SvelteKit UI +description: Packaged cloud deployment for the e2e-ui SvelteKit UI type: application version: 0.1.0 appVersion: "0.1.0" diff --git a/tests/e2e-ui/ui/.gitops/deploy/README.md b/tests/e2e-ui/ui/.gitops/deploy/README.md index 0ce8c35b..8da548bb 100644 --- a/tests/e2e-ui/ui/.gitops/deploy/README.md +++ b/tests/e2e-ui/ui/.gitops/deploy/README.md @@ -1,76 +1,5 @@ -# e2e-ui UI chart (workbench) +# e2e-ui UI cloud chart -Renders: - -- **Deployment / Service** (`appRuntime: cluster-dev` \| `package`) -- **Optional identity** when `identity.enabled: true`: - - **Cluster-shared:** Project, roles (`user` / `admin`), demo humans, grants - - **Per worktree:** OIDC web app only (redirects + Login V2 `baseUri`) - -The OIDC web app + Login V2 `baseUri` belong with the UI (browser sign-in), -not the API. - -## Identity scope - -| Resource | Scope | K8s name example | -|----------|--------|------------------| -| Project | **Cluster** | `e2e-ui` | -| Roles | **Cluster** | `e2e-role-user`, `e2e-role-admin` | -| HumanUsers | **Cluster** | `e2e-alice` (login `alice`) | -| Grants | **Cluster** | `e2e-alice-e2e-ui` (`user`) | -| Password secret | **Cluster** | `e2e-human-passwords` | -| OIDC app | **Worktree** | `e2e-ui-dogfood-web` | - -Demo login (every local env): **alice / bob / admin · Password1!** - -## Values - -| Value | Purpose | -|-------|---------| -| `identity.enabled` | Gate identity templates | -| `identity.projectName` | Shared Project name (default `e2e-ui`) | -| `identity.workspace` | Worktree id for OIDC app names only | -| `identity.oidcGeneration` | Bump to rotate generated OIDC client credentials through GitOps prune | -| `identity.demoUsers` | Cluster-shared alice / bob / admin | -| `identity.projectNamespace` | Namespace for Project + Role MRs | -| `identity.humansNamespace` | Namespace for HumanUser MRs | -| `identity.mrNamespace` | OIDC app ns (empty = release / hops `--name`) | -| `identity.instanceLoginV2` | Gitops instance Features (global; primary worktree only) | -| `identity.seedLocalOidcSecret` | Optional explicit residual seed; disabled by default so GitOps cannot erase the login PAT | -| `identity.uiBaseURL` | Optional; else `http://e2e-ui-ui..svc…:5180` | -| `identity.passwordSecret.name` | Shared password secret (default `e2e-human-passwords`) | - -```yaml -# gitops/envs/local/ui.yaml -identity: - enabled: true - demoUsers: true - providerConfigRef: - name: default - kind: ClusterProviderConfig -``` - -The Project keeps `projectRoleCheck` enabled. Grant XRs assign `user` to alice -and bob, and `user + admin` to admin. Each Grant resolves its HumanUser and -Project by name from `status.atProvider`; the referenced resources and Grant -must share `identity.projectNamespace`. The chart adds the auth-stack's stable -reference-name label to the Project and HumanUsers. Prefer a -**ClusterProviderConfig** so all identity resources share credentials. - -Project, role, human, Grant, and OIDC resources omit live -org/project/user/client UUIDs. -The provider credential selects the organization; `projectIdRef` selects the -shared Project. The OIDC client id/secret come from the per-workspace connection -secret written by the Oidc managed resource. - -If a generated OIDC client secret becomes stale, bump -`identity.oidcGeneration`. With the Application's `syncPolicy.prune: true`, -worktree GitOps deletes the previous OIDC MR from its exact-object inventory, -creates the new generation, and rolls the UI onto the matching -`e2e-ui--oidc-conn-g` Secret. The generation-specific -name makes the replacement pod wait for Crossplane's new credentials instead -of starting with data from the previous generation's Secret. - -`OIDC_AUDIENCE` stays empty for generic Zitadel role scopes. The residual -`e2e-ui-oidc` secret contains only `AUTH_SECRET` and -`ZITADEL_SERVICE_USER_TOKEN`. +This chart renders the packaged UI image for cloud Environments. It is +independent from `.gitops/local` and rejects `local: true` so local development +cannot accidentally select the cloud workload posture. diff --git a/tests/e2e-ui/ui/.gitops/deploy/templates/_helpers.tpl b/tests/e2e-ui/ui/.gitops/deploy/templates/_helpers.tpl index 50e247e8..d15cfe4b 100644 --- a/tests/e2e-ui/ui/.gitops/deploy/templates/_helpers.tpl +++ b/tests/e2e-ui/ui/.gitops/deploy/templates/_helpers.tpl @@ -7,88 +7,3 @@ app.kubernetes.io/name: {{ include "e2e-ui-ui.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end -}} - -{{/* -Workspace id for multi-worktree OIDC apps (redirects / Login V2 baseUri). -Prefers identity.workspace; else release/workspace namespace (= hops --name). -*/}} -{{- define "e2e-ui-ui.workspace" -}} -{{- if .Values.identity.workspace -}} -{{- .Values.identity.workspace -}} -{{- else -}} -{{- include "e2e-ui-ui.releaseNamespace" . -}} -{{- end -}} -{{- end -}} - -{{/* App namespace: hops injects values.namespace; helm --namespace is Release.Namespace. */}} -{{- define "e2e-ui-ui.releaseNamespace" -}} -{{- coalesce .Values.namespace .Release.Namespace "default" -}} -{{- end -}} - -{{/* Worktree-scoped OIDC app name prefix: e2e-ui- */}} -{{- define "e2e-ui-ui.identityPrefix" -}} -{{- printf "e2e-ui-%s" (include "e2e-ui-ui.workspace" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -OIDC MR name. Bump identity.oidcGeneration to rotate provider-generated client -credentials. Worktree GitOps prune deletes the previous generation by inventory. -*/}} -{{- define "e2e-ui-ui.oidcResourceName" -}} -{{- $prefix := include "e2e-ui-ui.identityPrefix" . -}} -{{- $generation := int (.Values.identity.oidcGeneration | default 0) -}} -{{- if gt $generation 0 -}} -{{- printf "%s-web-g%d" $prefix $generation | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-web" $prefix | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} - -{{/* Cluster-shared Zitadel Project MR name (one per control plane). */}} -{{- define "e2e-ui-ui.clusterProjectName" -}} -{{- default "e2e-ui" .Values.identity.projectName -}} -{{- end -}} - -{{- define "e2e-ui-ui.identityLabels" -}} -app.kubernetes.io/name: e2e-ui -app.kubernetes.io/component: identity -hops.ops.com.ai/app: e2e-ui -hops.ops.com.ai/workspace: {{ include "e2e-ui-ui.workspace" . | quote }} -{{- end -}} - -{{- define "e2e-ui-ui.identityClusterLabels" -}} -app.kubernetes.io/name: e2e-ui -app.kubernetes.io/component: identity -hops.ops.com.ai/app: e2e-ui -hops.ops.com.ai/identity-scope: cluster -{{- end -}} - -{{/* -UI public base for OIDC redirects + app Login V2 baseUri. -Uses the release/workspace namespace so --name dogfood → e2e-ui-ui.dogfood.svc… -*/}} -{{- define "e2e-ui-ui.uiBaseURL" -}} -{{- if .Values.identity.uiBaseURL -}} -{{- .Values.identity.uiBaseURL | trimSuffix "/" -}} -{{- else -}} -{{- $ns := include "e2e-ui-ui.releaseNamespace" . -}} -{{- $svc := default (include "e2e-ui-ui.name" .) .Values.identity.uiService -}} -{{- $port := default .Values.service.port .Values.identity.uiPort -}} -{{- printf "http://%s.%s.svc.cluster.local:%v" $svc $ns $port -}} -{{- end -}} -{{- end -}} - -{{/* -Crossplane connection secret written by the worktree Oidc MR -(writeConnectionSecretToRef). Holds attribute.client_id / attribute.client_secret -— never commit live client credentials to git. -*/}} -{{- define "e2e-ui-ui.oidcConnectionSecretName" -}} -{{- $prefix := include "e2e-ui-ui.identityPrefix" . -}} -{{- $generation := int (.Values.identity.oidcGeneration | default 0) -}} -{{- if gt $generation 0 -}} -{{- printf "%s-oidc-conn-g%d" $prefix $generation | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-oidc-conn" $prefix | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} diff --git a/tests/e2e-ui/ui/.gitops/deploy/templates/deployment-package.yaml b/tests/e2e-ui/ui/.gitops/deploy/templates/deployment-package.yaml index 403b1cbc..e9b27ed0 100644 --- a/tests/e2e-ui/ui/.gitops/deploy/templates/deployment-package.yaml +++ b/tests/e2e-ui/ui/.gitops/deploy/templates/deployment-package.yaml @@ -1,4 +1,6 @@ -{{- if and (not .Values.local) (ne .Values.appRuntime "cluster-dev") (ne .Values.appRuntime "host") }} +{{- if .Values.local }} +{{- fail "ui/.gitops/deploy is cloud-only; use ui/.gitops/local for local=true" }} +{{- end }} apiVersion: apps/v1 kind: Deployment metadata: @@ -24,4 +26,3 @@ spec: ports: - containerPort: {{ .Values.service.targetPort }} name: http -{{- end }} diff --git a/tests/e2e-ui/ui/.gitops/deploy/values.yaml b/tests/e2e-ui/ui/.gitops/deploy/values.yaml index 507749de..afddc92f 100644 --- a/tests/e2e-ui/ui/.gitops/deploy/values.yaml +++ b/tests/e2e-ui/ui/.gitops/deploy/values.yaml @@ -1,8 +1,6 @@ -# Local workbench values for e2e-ui UI. -# Secrets live in K8s Secrets (secretEnv / envFromSecrets) — not here. - +# Cloud workload values. Local development is ui/.gitops/local. local: false -appRuntime: package # host | cluster-dev | package +preview: false name: e2e-ui-ui image: @@ -10,140 +8,23 @@ image: tag: latest pullPolicy: IfNotPresent -# Source tree is the *distributed* monorepo root (needs tests/e2e-ui/ui + js/). -# Application deliveryPath points there; workingDir is the Svelte app. -clusterDev: - image: node:22-bookworm - # Bump in the local Application after rebuilding shared JS framework output. - # This restarts the Vite process through the watched GitOps deployment. - sourceGeneration: 0 - # Use bash -c (not -lc) so container ENV/PATH stay intact. - command: - - /bin/bash - - -c - - | - set -e - cd /workspace/js && npm install && npm run build - cd /workspace/tests/e2e-ui/ui - npm install - # Skip the distributed CLI/cargo: generated clients are committed under src/lib/generated. - export DISTRIBUTED_SKIP_CLIENT_COMPILE=1 - # In-cluster API service (not host localhost). - export E2E_API_ORIGIN="${E2E_API_ORIGIN:-http://e2e-ui-api:8791}" - export E2E_BASE_URL="$E2E_API_ORIGIN" - npm run dev -- --host 0.0.0.0 --port 5180 - workingDir: /workspace/tests/e2e-ui/ui - sourcePath: /workspace - service: port: 5180 targetPort: 5180 -# Non-secret env only (safe in git). env: HOST: "0.0.0.0" PORT: "5180" - DISTRIBUTED_SKIP_CLIENT_COMPILE: "1" E2E_API_ORIGIN: "http://e2e-ui-api:8791" - E2E_BASE_URL: "http://e2e-ui-api:8791" - PUBLIC_E2E_API_ORIGIN: "http://e2e-ui-api:8791" - # Overridden at render when hops injects namespace (= --name). - AUTH_URL: "http://e2e-ui-ui.default.svc.cluster.local:5180" AUTH_TRUST_HOST: "true" - AUTH_USE_SECURE_COOKIES: "false" - # AuthStack issuer (Helm default Service fullname) - OIDC_ISSUER: "http://zitadel-zitadel.auth.svc.cluster.local:8080" - # Optional project id for role-claim scopes. Prefer empty in git — use - # Project status.atProvider.id only if you must pin audience (not in MR git). - OIDC_AUDIENCE: "" - ZITADEL_PROJECT_ID: "" -# Secret-backed env (never live client ids in git). -# - OIDC_CLIENT_* → Oidc writeConnectionSecretToRef (attribute.client_id/secret) -# - AUTH_SECRET / login PAT → residual e2e-ui-oidc (see SECRETS.md) secretEnv: AUTH_SECRET: secretName: e2e-ui-oidc key: AUTH_SECRET - # Overridden to Oidc connection secret when identity.enabled. OIDC_CLIENT_ID: secretName: e2e-ui-oidc key: OIDC_CLIENT_ID OIDC_CLIENT_SECRET: secretName: e2e-ui-oidc key: OIDC_CLIENT_SECRET - # login-client PAT (IAM_LOGIN_CLIENT) for custom /login Session API + CreateCallback - ZITADEL_SERVICE_USER_TOKEN: - secretName: e2e-ui-oidc - key: ZITADEL_SERVICE_USER_TOKEN - -# Optional: mount entire secrets as env (unused by default) -envFromSecrets: [] - -# Zitadel identity on the UI chart (OIDC web client + Login V2 host). -# -# Scope (all gitops when identity.enabled): -# - Project + roles + demo humans — cluster-shared (projectNamespace) -# - OIDC web app — per worktree (redirects + app loginV2.baseUri) -# - Instance Features loginV2 — points authorize → this UI's /login -# (instance-global in Zitadel; last applied worktree wins on one AuthStack) -# -# Enable from Application values when the env needs OIDC dogfood. -identity: - enabled: false - # Worktree id for OIDC app name (e2e-ui--web). Default: release ns. - workspace: "" - # Bump to rotate provider-generated client credentials. Requires Application - # syncPolicy.prune=true so the previous OIDC MR is removed by GitOps inventory. - oidcGeneration: 0 - # Shared Project MR name — Roles/Oidc use projectIdRef, not live UUIDs. - projectName: e2e-ui - projectNamespace: default - humansNamespace: default - demoUsers: true - instanceLoginV2: true - # Intentionally disabled: the chart cannot know the AuthStack login-client PAT, - # and must not overwrite the residual Secret on every GitOps reconcile. - seedLocalOidcSecret: false - localOidcSeed: {} - seedLocalHumanPasswords: true - localHumanPasswords: {} - uiService: "" - uiPort: "" - uiBaseURL: "" - extraRedirectUris: - - http://127.0.0.1:5180/auth/callback/oidc - - http://localhost:5180/auth/callback/oidc - - http://127.0.0.1:18101/auth/callback/oidc - - http://localhost:18101/auth/callback/oidc - extraPostLogoutRedirectUris: - - http://127.0.0.1:5180/ - - http://localhost:5180/ - - http://127.0.0.1:18101/ - - http://localhost:18101/ - passwordSecret: - name: e2e-human-passwords - providerConfigRef: - name: default - kind: ClusterProviderConfig - -# Materialize residual secrets from Vault (ClusterSecretStore "vault"). -# Enable after SecretStack CSS is Ready + hops secrets sync vault. -externalSecrets: - enabled: false - refreshInterval: 1m - secretStoreRef: - name: vault - kind: ClusterSecretStore - oidc: - vaultPath: "" # default e2e-ui//oidc - targetName: e2e-ui-oidc - humanPasswords: - enabled: true - vaultPath: "" # default e2e-ui//human-passwords - targetName: "" # default e2e-human-passwords- - -sourceDelivery: - mode: none # none | hostPath | sync - hostPath: "" - mountPath: /workspace diff --git a/tests/e2e-ui/ui/.gitops/local/Chart.yaml b/tests/e2e-ui/ui/.gitops/local/Chart.yaml new file mode 100644 index 00000000..1e183203 --- /dev/null +++ b/tests/e2e-ui/ui/.gitops/local/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: e2e-ui-ui +description: Editable local workload for the e2e-ui SvelteKit UI +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/tests/e2e-ui/ui/.gitops/local/templates/_helpers.tpl b/tests/e2e-ui/ui/.gitops/local/templates/_helpers.tpl new file mode 100644 index 00000000..b7d370bf --- /dev/null +++ b/tests/e2e-ui/ui/.gitops/local/templates/_helpers.tpl @@ -0,0 +1,31 @@ +{{- define "e2e-ui-ui.name" -}} +{{- default .Chart.Name .Values.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "e2e-ui-ui.labels" -}} +app.kubernetes.io/name: {{ include "e2e-ui-ui.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{- define "e2e-ui-ui.releaseNamespace" -}} +{{- coalesce .Values.environment.namespace .Release.Namespace "default" -}} +{{- end -}} + +{{- define "e2e-ui-ui.workspace" -}} +{{- coalesce .Values.environment.name (include "e2e-ui-ui.releaseNamespace" .) -}} +{{- end -}} + +{{- define "e2e-ui-ui.identityPrefix" -}} +{{- printf "e2e-ui-%s" (include "e2e-ui-ui.workspace" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "e2e-ui-ui.oidcConnectionSecretName" -}} +{{- $prefix := include "e2e-ui-ui.identityPrefix" . -}} +{{- $generation := int (.Values.identity.oidcGeneration | default 0) -}} +{{- if gt $generation 0 -}} +{{- printf "%s-oidc-conn-g%d" $prefix $generation | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-oidc-conn" $prefix | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} diff --git a/tests/e2e-ui/ui/.gitops/deploy/templates/deployment-cluster-dev.yaml b/tests/e2e-ui/ui/.gitops/local/templates/deployment.yaml similarity index 93% rename from tests/e2e-ui/ui/.gitops/deploy/templates/deployment-cluster-dev.yaml rename to tests/e2e-ui/ui/.gitops/local/templates/deployment.yaml index 2bc1481e..49d8c227 100644 --- a/tests/e2e-ui/ui/.gitops/deploy/templates/deployment-cluster-dev.yaml +++ b/tests/e2e-ui/ui/.gitops/local/templates/deployment.yaml @@ -1,4 +1,6 @@ -{{- if eq .Values.appRuntime "cluster-dev" }} +{{- if not .Values.local }} +{{- fail "ui/.gitops/local requires local=true" }} +{{- end }} apiVersion: apps/v1 kind: Deployment metadata: @@ -13,11 +15,10 @@ spec: app.kubernetes.io/name: {{ include "e2e-ui-ui.name" . }} template: metadata: + {{- if .Values.identity.enabled }} annotations: - hops.ops.com.ai/source-generation: {{ .Values.clusterDev.sourceGeneration | default 0 | quote }} - {{- if .Values.identity.enabled }} hops.ops.com.ai/oidc-generation: {{ .Values.identity.oidcGeneration | default 0 | quote }} - {{- end }} + {{- end }} labels: app.kubernetes.io/name: {{ include "e2e-ui-ui.name" . }} hops.ops.com.ai/runtime: cluster-dev @@ -69,9 +70,9 @@ spec: name: http env: {{- range $k, $v := .Values.env }} - {{- if and (eq $k "AUTH_URL") $.Values.namespace }} + {{- if eq $k "AUTH_URL" }} - name: AUTH_URL - value: {{ printf "http://e2e-ui-ui.%s.svc.cluster.local:5180" $.Values.namespace | quote }} + value: {{ printf "http://e2e-ui-ui.%s.svc.cluster.local:5180" (include "e2e-ui-ui.releaseNamespace" $) | quote }} {{- else if and $v (ne $v "") }} - name: {{ $k }} value: {{ $v | quote }} @@ -122,4 +123,3 @@ spec: emptyDir: {} - name: js-node-modules emptyDir: {} -{{- end }} diff --git a/tests/e2e-ui/ui/.gitops/local/templates/service.yaml b/tests/e2e-ui/ui/.gitops/local/templates/service.yaml new file mode 100644 index 00000000..1bc71f23 --- /dev/null +++ b/tests/e2e-ui/ui/.gitops/local/templates/service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "e2e-ui-ui.name" . }} + labels: + {{- include "e2e-ui-ui.labels" . | nindent 4 }} +spec: + selector: + app.kubernetes.io/name: {{ include "e2e-ui-ui.name" . }} + ports: + - name: http + port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} diff --git a/tests/e2e-ui/ui/.gitops/local/values.yaml b/tests/e2e-ui/ui/.gitops/local/values.yaml new file mode 100644 index 00000000..528d2270 --- /dev/null +++ b/tests/e2e-ui/ui/.gitops/local/values.yaml @@ -0,0 +1,68 @@ +local: true +preview: false + +environment: + name: local + namespace: default + +name: e2e-ui-ui +clusterDev: + image: node:22-bookworm + workingDir: /workspace/tests/e2e-ui/ui + command: + - /bin/bash + - -c + - | + set -e + cd /workspace/js && npm install && npm run build + cd /workspace/tests/e2e-ui/ui + npm install + export DISTRIBUTED_SKIP_CLIENT_COMPILE=1 + export E2E_API_ORIGIN="${E2E_API_ORIGIN:-http://e2e-ui-api:8791}" + export E2E_BASE_URL="$E2E_API_ORIGIN" + npm run dev -- --host 0.0.0.0 --port 5180 + +service: + port: 5180 + targetPort: 5180 + +env: + HOST: "0.0.0.0" + PORT: "5180" + DISTRIBUTED_SKIP_CLIENT_COMPILE: "1" + E2E_API_ORIGIN: "http://e2e-ui-api:8791" + E2E_BASE_URL: "http://e2e-ui-api:8791" + PUBLIC_E2E_API_ORIGIN: "http://e2e-ui-api:8791" + AUTH_URL: "http://e2e-ui-ui.default.svc.cluster.local:5180" + AUTH_TRUST_HOST: "true" + AUTH_USE_SECURE_COOKIES: "false" + OIDC_ISSUER: "http://zitadel-zitadel.auth.svc.cluster.local:8080" + OIDC_AUDIENCE: "" + ZITADEL_PROJECT_ID: "" + +secretEnv: + AUTH_SECRET: + secretName: e2e-ui-oidc + key: AUTH_SECRET + OIDC_CLIENT_ID: + secretName: e2e-ui-oidc + key: OIDC_CLIENT_ID + OIDC_CLIENT_SECRET: + secretName: e2e-ui-oidc + key: OIDC_CLIENT_SECRET + ZITADEL_SERVICE_USER_TOKEN: + secretName: e2e-ui-oidc + key: ZITADEL_SERVICE_USER_TOKEN + +envFromSecrets: [] + +# The workload only consumes identity output. Provisioning lives in the +# explicit .gitops/test-users Environment deploy. +identity: + enabled: false + oidcGeneration: 0 + +sourceDelivery: + mode: none + hostPath: "" + mountPath: /workspace diff --git a/tests/e2e-ui/ui/.gitops/promote/Chart.yaml b/tests/e2e-ui/ui/.gitops/promote/Chart.yaml new file mode 100644 index 00000000..eb9faded --- /dev/null +++ b/tests/e2e-ui/ui/.gitops/promote/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: e2e-ui-ui-promote +description: Optional cloud promotion action for the e2e-ui UI +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/tests/e2e-ui/ui/.gitops/promote/templates/application.yaml b/tests/e2e-ui/ui/.gitops/promote/templates/application.yaml new file mode 100644 index 00000000..d02ae21f --- /dev/null +++ b/tests/e2e-ui/ui/.gitops/promote/templates/application.yaml @@ -0,0 +1,31 @@ +{{- if .Values.local }} +{{- fail ".gitops/promote is cloud-only; local Environments render .gitops/local directly" }} +{{- end }} +{{- $environment := required "environment.name is required" .Values.environment.name }} +{{- $namespace := required "environment.namespace is required" .Values.environment.namespace }} +{{- $repoURL := required "source.repoURL is required" .Values.source.repoURL }} +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: {{ printf "%s-e2e-ui-ui" $environment }} + namespace: {{ .Values.argocd.namespace }} +spec: + project: {{ .Values.argocd.project }} + source: + repoURL: {{ $repoURL | quote }} + targetRevision: {{ .Values.source.targetRevision | quote }} + path: tests/e2e-ui/ui/.gitops/deploy + helm: + valuesObject: + local: false + preview: {{ .Values.preview }} + {{- with .Values.deploy.values }} + {{- toYaml . | nindent 8 }} + {{- end }} + destination: + server: https://kubernetes.default.svc + namespace: {{ $namespace }} + syncPolicy: + automated: + prune: true + selfHeal: true diff --git a/tests/e2e-ui/ui/.gitops/promote/values.yaml b/tests/e2e-ui/ui/.gitops/promote/values.yaml new file mode 100644 index 00000000..34a16f02 --- /dev/null +++ b/tests/e2e-ui/ui/.gitops/promote/values.yaml @@ -0,0 +1,13 @@ +local: false +preview: false +environment: + name: "" + namespace: "" +source: + repoURL: "" + targetRevision: HEAD +argocd: + namespace: argocd + project: default +deploy: + values: {} diff --git a/tests/e2e-ui/ui/.gitops/test-users/Chart.yaml b/tests/e2e-ui/ui/.gitops/test-users/Chart.yaml new file mode 100644 index 00000000..cd379e07 --- /dev/null +++ b/tests/e2e-ui/ui/.gitops/test-users/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: e2e-ui-test-users +description: Optional local browser and smoke-test identities for e2e-ui +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/tests/e2e-ui/ui/.gitops/test-users/README.md b/tests/e2e-ui/ui/.gitops/test-users/README.md new file mode 100644 index 00000000..e37f53bd --- /dev/null +++ b/tests/e2e-ui/ui/.gitops/test-users/README.md @@ -0,0 +1,77 @@ +# e2e-ui test-users chart + +This optional chart keeps test identity provisioning separate from both local +and cloud workloads. When `identity.enabled: true`, it renders: + +- **Cluster-shared:** Project, roles (`user` / `admin`), demo humans, grants +- **Per Environment:** OIDC web app only (redirects + Login V2 `baseUri`) + +The OIDC web app + Login V2 `baseUri` belong with the UI (browser sign-in), +not the API. + +## Identity scope + +| Resource | Scope | K8s name example | +|----------|--------|------------------| +| Project | **Cluster** | `e2e-ui` | +| Roles | **Cluster** | `e2e-role-user`, `e2e-role-admin` | +| HumanUsers | **Cluster** | `e2e-alice` (login `alice`) | +| Grants | **Cluster** | `e2e-alice-e2e-ui` (`user`) | +| Password secret | **Cluster** | `e2e-human-passwords` | +| OIDC app | **Environment** | `e2e-ui-dogfood-web` | + +Demo login (every local env): **alice / bob / admin · Password1!** + +## Values + +| Value | Purpose | +|-------|---------| +| `identity.enabled` | Gate identity templates | +| `identity.projectName` | Shared Project name (default `e2e-ui`) | +| `identity.workspace` | Optional Environment-name override for OIDC app names | +| `identity.oidcGeneration` | Bump to rotate generated OIDC client credentials through GitOps prune | +| `identity.demoUsers` | Cluster-shared alice / bob / admin | +| `identity.projectNamespace` | Namespace for Project + Role MRs | +| `identity.humansNamespace` | Namespace for HumanUser MRs | +| `identity.mrNamespace` | OIDC app ns (empty = release / hops `--name`) | +| `identity.instanceLoginV2` | Gitops instance Features (global; primary worktree only) | +| `identity.seedLocalOidcSecret` | Optional explicit residual seed; disabled by default so GitOps cannot erase the login PAT | +| `identity.uiBaseURL` | Optional; else `http://e2e-ui-ui..svc…:5180` | +| `identity.passwordSecret.name` | Shared password secret (default `e2e-human-passwords`) | + +```yaml +# .gitops/local/environment.yaml deploys[] entry +- path: ui + chart: .gitops/test-users + values: + identity: + enabled: true + demoUsers: true + providerConfigRef: + name: default + kind: ClusterProviderConfig +``` + +The Project keeps `projectRoleCheck` enabled. Grant XRs assign `user` to alice +and bob, and `user + admin` to admin. Each Grant resolves its HumanUser and +Project by name from `status.atProvider`; the referenced resources and Grant +must share `identity.projectNamespace`. The chart adds the auth-stack's stable +reference-name label to the Project and HumanUsers. Prefer a +**ClusterProviderConfig** so all identity resources share credentials. + +Project, role, human, Grant, and OIDC resources omit live +org/project/user/client UUIDs. +The provider credential selects the organization; `projectIdRef` selects the +shared Project. The OIDC client id/secret come from the per-workspace connection +secret written by the Oidc managed resource. + +If a generated OIDC client secret becomes stale, bump +`identity.oidcGeneration`. The Environment controller prunes the previous OIDC +MR from its exact-object inventory, creates the new generation, and rolls the +UI onto the matching +`e2e-ui--oidc-conn-g` Secret. The generation-specific +name makes the replacement pod wait for Crossplane's new credentials instead +of starting with data from the previous generation's Secret. + +`OIDC_AUDIENCE` stays empty for generic Zitadel role scopes. The workload +charts only consume generated or externally synchronized Secrets. diff --git a/tests/e2e-ui/ui/.gitops/test-users/templates/_helpers.tpl b/tests/e2e-ui/ui/.gitops/test-users/templates/_helpers.tpl new file mode 100644 index 00000000..aa822628 --- /dev/null +++ b/tests/e2e-ui/ui/.gitops/test-users/templates/_helpers.tpl @@ -0,0 +1,95 @@ +{{- /* Identity helpers owned by the explicit test-users chart. */ -}} +{{- define "e2e-ui-ui.name" -}} +{{- default .Chart.Name .Values.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "e2e-ui-ui.labels" -}} +app.kubernetes.io/name: {{ include "e2e-ui-ui.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Workspace id for multi-worktree OIDC apps (redirects / Login V2 baseUri). +Prefers identity.workspace; else the resolved Environment name. +*/}} +{{- define "e2e-ui-ui.workspace" -}} +{{- if .Values.identity.workspace -}} +{{- .Values.identity.workspace -}} +{{- else -}} +{{- coalesce .Values.environment.name (include "e2e-ui-ui.releaseNamespace" .) -}} +{{- end -}} +{{- end -}} + +{{/* App namespace: Hops injects environment.namespace; Helm also owns Release.Namespace. */}} +{{- define "e2e-ui-ui.releaseNamespace" -}} +{{- coalesce .Values.environment.namespace .Release.Namespace "default" -}} +{{- end -}} + +{{/* Worktree-scoped OIDC app name prefix: e2e-ui- */}} +{{- define "e2e-ui-ui.identityPrefix" -}} +{{- printf "e2e-ui-%s" (include "e2e-ui-ui.workspace" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +OIDC MR name. Bump identity.oidcGeneration to rotate provider-generated client +credentials. Environment GitOps prune deletes the previous generation by inventory. +*/}} +{{- define "e2e-ui-ui.oidcResourceName" -}} +{{- $prefix := include "e2e-ui-ui.identityPrefix" . -}} +{{- $generation := int (.Values.identity.oidcGeneration | default 0) -}} +{{- if gt $generation 0 -}} +{{- printf "%s-web-g%d" $prefix $generation | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-web" $prefix | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* Cluster-shared Zitadel Project MR name (one per control plane). */}} +{{- define "e2e-ui-ui.clusterProjectName" -}} +{{- default "e2e-ui" .Values.identity.projectName -}} +{{- end -}} + +{{- define "e2e-ui-ui.identityLabels" -}} +app.kubernetes.io/name: e2e-ui +app.kubernetes.io/component: identity +hops.ops.com.ai/app: e2e-ui +hops.ops.com.ai/workspace: {{ include "e2e-ui-ui.workspace" . | quote }} +{{- end -}} + +{{- define "e2e-ui-ui.identityClusterLabels" -}} +app.kubernetes.io/name: e2e-ui +app.kubernetes.io/component: identity +hops.ops.com.ai/app: e2e-ui +hops.ops.com.ai/identity-scope: cluster +{{- end -}} + +{{/* +UI public base for OIDC redirects + app Login V2 baseUri. +Uses the release/workspace namespace so --name dogfood → e2e-ui-ui.dogfood.svc… +*/}} +{{- define "e2e-ui-ui.uiBaseURL" -}} +{{- if .Values.identity.uiBaseURL -}} +{{- .Values.identity.uiBaseURL | trimSuffix "/" -}} +{{- else -}} +{{- $ns := include "e2e-ui-ui.releaseNamespace" . -}} +{{- $svc := default (include "e2e-ui-ui.name" .) .Values.identity.uiService -}} +{{- $port := default .Values.service.port .Values.identity.uiPort -}} +{{- printf "http://%s.%s.svc.cluster.local:%v" $svc $ns $port -}} +{{- end -}} +{{- end -}} + +{{/* +Crossplane connection secret written by the worktree Oidc MR +(writeConnectionSecretToRef). Holds attribute.client_id / attribute.client_secret +— never commit live client credentials to git. +*/}} +{{- define "e2e-ui-ui.oidcConnectionSecretName" -}} +{{- $prefix := include "e2e-ui-ui.identityPrefix" . -}} +{{- $generation := int (.Values.identity.oidcGeneration | default 0) -}} +{{- if gt $generation 0 -}} +{{- printf "%s-oidc-conn-g%d" $prefix $generation | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-oidc-conn" $prefix | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} diff --git a/tests/e2e-ui/ui/.gitops/deploy/templates/external-secret-human-passwords.yaml b/tests/e2e-ui/ui/.gitops/test-users/templates/external-secret-human-passwords.yaml similarity index 95% rename from tests/e2e-ui/ui/.gitops/deploy/templates/external-secret-human-passwords.yaml rename to tests/e2e-ui/ui/.gitops/test-users/templates/external-secret-human-passwords.yaml index 3d4dd4b4..a07e5a64 100644 --- a/tests/e2e-ui/ui/.gitops/deploy/templates/external-secret-human-passwords.yaml +++ b/tests/e2e-ui/ui/.gitops/test-users/templates/external-secret-human-passwords.yaml @@ -1,3 +1,4 @@ +{{- /* Optional test identity materialization from the Cluster Vault. */ -}} {{- if and .Values.externalSecrets .Values.externalSecrets.enabled .Values.externalSecrets.humanPasswords .Values.externalSecrets.humanPasswords.enabled }} {{- $path := .Values.externalSecrets.humanPasswords.vaultPath | default "e2e-ui/shared/human-passwords" -}} {{- $target := .Values.externalSecrets.humanPasswords.targetName | default "e2e-human-passwords" -}} diff --git a/tests/e2e-ui/ui/.gitops/deploy/templates/external-secret-oidc.yaml b/tests/e2e-ui/ui/.gitops/test-users/templates/external-secret-oidc.yaml similarity index 94% rename from tests/e2e-ui/ui/.gitops/deploy/templates/external-secret-oidc.yaml rename to tests/e2e-ui/ui/.gitops/test-users/templates/external-secret-oidc.yaml index b4b21ef7..ae577041 100644 --- a/tests/e2e-ui/ui/.gitops/deploy/templates/external-secret-oidc.yaml +++ b/tests/e2e-ui/ui/.gitops/test-users/templates/external-secret-oidc.yaml @@ -1,3 +1,4 @@ +{{- /* Optional test identity materialization from the Cluster Vault. */ -}} {{- if and .Values.externalSecrets .Values.externalSecrets.enabled }} {{- $ws := include "e2e-ui-ui.workspace" . -}} {{- $path := .Values.externalSecrets.oidc.vaultPath | default (printf "e2e-ui/%s/oidc" $ws) -}} diff --git a/tests/e2e-ui/ui/.gitops/deploy/templates/identity-grants.yaml b/tests/e2e-ui/ui/.gitops/test-users/templates/identity-grants.yaml similarity index 98% rename from tests/e2e-ui/ui/.gitops/deploy/templates/identity-grants.yaml rename to tests/e2e-ui/ui/.gitops/test-users/templates/identity-grants.yaml index 0dd90871..d3472e7a 100644 --- a/tests/e2e-ui/ui/.gitops/deploy/templates/identity-grants.yaml +++ b/tests/e2e-ui/ui/.gitops/test-users/templates/identity-grants.yaml @@ -1,4 +1,4 @@ -{{- /* +{{- /* Test-user chart ownership. Cluster-shared role assignments for the demo humans. Grant XRs resolve HumanUser + Project IDs from status.atProvider, keeping diff --git a/tests/e2e-ui/ui/.gitops/deploy/templates/identity-humans.yaml b/tests/e2e-ui/ui/.gitops/test-users/templates/identity-humans.yaml similarity index 98% rename from tests/e2e-ui/ui/.gitops/deploy/templates/identity-humans.yaml rename to tests/e2e-ui/ui/.gitops/test-users/templates/identity-humans.yaml index 316a705d..10798e87 100644 --- a/tests/e2e-ui/ui/.gitops/deploy/templates/identity-humans.yaml +++ b/tests/e2e-ui/ui/.gitops/test-users/templates/identity-humans.yaml @@ -1,4 +1,4 @@ -{{- /* +{{- /* Test-user chart ownership. Cluster-shared demo humans (alice / bob / admin). One set per local control plane — not per worktree. Login names stay plain diff --git a/tests/e2e-ui/ui/.gitops/deploy/templates/identity-instance-features.yaml b/tests/e2e-ui/ui/.gitops/test-users/templates/identity-instance-features.yaml similarity index 96% rename from tests/e2e-ui/ui/.gitops/deploy/templates/identity-instance-features.yaml rename to tests/e2e-ui/ui/.gitops/test-users/templates/identity-instance-features.yaml index 2b7f8833..b33ec34c 100644 --- a/tests/e2e-ui/ui/.gitops/deploy/templates/identity-instance-features.yaml +++ b/tests/e2e-ui/ui/.gitops/test-users/templates/identity-instance-features.yaml @@ -1,4 +1,4 @@ -{{- /* +{{- /* Test-user chart ownership. Instance Login V2 features (gitops). Zitadel honors instance loginV2.baseUri when required=true. That is what diff --git a/tests/e2e-ui/ui/.gitops/deploy/templates/identity-oidc.yaml b/tests/e2e-ui/ui/.gitops/test-users/templates/identity-oidc.yaml similarity index 98% rename from tests/e2e-ui/ui/.gitops/deploy/templates/identity-oidc.yaml rename to tests/e2e-ui/ui/.gitops/test-users/templates/identity-oidc.yaml index 6fabad83..229d1ea5 100644 --- a/tests/e2e-ui/ui/.gitops/deploy/templates/identity-oidc.yaml +++ b/tests/e2e-ui/ui/.gitops/test-users/templates/identity-oidc.yaml @@ -1,4 +1,4 @@ -{{- /* +{{- /* Test-user chart ownership. Worktree OIDC web app. - projectIdRef → shared Project MR (by name + labels), no live UUID in git diff --git a/tests/e2e-ui/ui/.gitops/deploy/templates/identity-project.yaml b/tests/e2e-ui/ui/.gitops/test-users/templates/identity-project.yaml similarity index 96% rename from tests/e2e-ui/ui/.gitops/deploy/templates/identity-project.yaml rename to tests/e2e-ui/ui/.gitops/test-users/templates/identity-project.yaml index 248b68ec..0fe973d5 100644 --- a/tests/e2e-ui/ui/.gitops/deploy/templates/identity-project.yaml +++ b/tests/e2e-ui/ui/.gitops/test-users/templates/identity-project.yaml @@ -1,4 +1,4 @@ -{{- /* +{{- /* Test-user chart ownership. Cluster-shared Zitadel Project (one e2e-ui project for the whole CP). Roles and demo humans attach here. OIDC apps stay worktree-scoped and diff --git a/tests/e2e-ui/ui/.gitops/deploy/templates/identity-roles.yaml b/tests/e2e-ui/ui/.gitops/test-users/templates/identity-roles.yaml similarity index 97% rename from tests/e2e-ui/ui/.gitops/deploy/templates/identity-roles.yaml rename to tests/e2e-ui/ui/.gitops/test-users/templates/identity-roles.yaml index 26b65d64..e9eb17cc 100644 --- a/tests/e2e-ui/ui/.gitops/deploy/templates/identity-roles.yaml +++ b/tests/e2e-ui/ui/.gitops/test-users/templates/identity-roles.yaml @@ -1,4 +1,4 @@ -{{- /* +{{- /* Test-user chart ownership. Cluster-shared project roles (user / admin) on the shared e2e-ui Project. orgId omitted — defaults to ProviderConfig credential org (see humans template). */ -}} diff --git a/tests/e2e-ui/ui/.gitops/deploy/templates/local-human-passwords.yaml b/tests/e2e-ui/ui/.gitops/test-users/templates/local-human-passwords.yaml similarity index 96% rename from tests/e2e-ui/ui/.gitops/deploy/templates/local-human-passwords.yaml rename to tests/e2e-ui/ui/.gitops/test-users/templates/local-human-passwords.yaml index 83af5e9c..beb245db 100644 --- a/tests/e2e-ui/ui/.gitops/deploy/templates/local-human-passwords.yaml +++ b/tests/e2e-ui/ui/.gitops/test-users/templates/local-human-passwords.yaml @@ -1,4 +1,4 @@ -{{- /* +{{- /* Test-user chart ownership. Cluster-shared demo human passwords (alice/bob/admin). Declarative local seed so HumanUser MRs can create without manual kubectl. Not for production — ExternalSecrets when externalSecrets.humanPasswords.enabled. diff --git a/tests/e2e-ui/ui/.gitops/deploy/templates/local-oidc-secret.yaml b/tests/e2e-ui/ui/.gitops/test-users/templates/local-oidc-secret.yaml similarity index 97% rename from tests/e2e-ui/ui/.gitops/deploy/templates/local-oidc-secret.yaml rename to tests/e2e-ui/ui/.gitops/test-users/templates/local-oidc-secret.yaml index 17aa5992..0ee5532a 100644 --- a/tests/e2e-ui/ui/.gitops/deploy/templates/local-oidc-secret.yaml +++ b/tests/e2e-ui/ui/.gitops/test-users/templates/local-oidc-secret.yaml @@ -1,4 +1,4 @@ -{{- /* +{{- /* Test-user chart ownership. Local residual Secret for non-MR credentials only. OIDC client id/secret come from the Oidc MR connection Secret diff --git a/tests/e2e-ui/ui/.gitops/test-users/values.yaml b/tests/e2e-ui/ui/.gitops/test-users/values.yaml new file mode 100644 index 00000000..481457b6 --- /dev/null +++ b/tests/e2e-ui/ui/.gitops/test-users/values.yaml @@ -0,0 +1,53 @@ +local: true +preview: false + +environment: + name: local + namespace: default + +name: e2e-ui-ui +service: + port: 5180 + +identity: + enabled: false + workspace: "" + oidcGeneration: 0 + projectName: e2e-ui + projectNamespace: default + humansNamespace: default + mrNamespace: "" + demoUsers: true + instanceLoginV2: true + seedLocalOidcSecret: false + localOidcSeed: {} + seedLocalHumanPasswords: true + localHumanPasswords: {} + uiService: e2e-ui-ui + uiPort: 5180 + uiBaseURL: "" + extraRedirectUris: + - http://127.0.0.1:5180/auth/callback/oidc + - http://localhost:5180/auth/callback/oidc + extraPostLogoutRedirectUris: + - http://127.0.0.1:5180/ + - http://localhost:5180/ + passwordSecret: + name: e2e-human-passwords + providerConfigRef: + name: default + kind: ClusterProviderConfig + +externalSecrets: + enabled: false + refreshInterval: 1m + secretStoreRef: + name: vault + kind: ClusterSecretStore + oidc: + vaultPath: "" + targetName: e2e-ui-oidc + humanPasswords: + enabled: true + vaultPath: "" + targetName: ""