# CI values for the bulwark install-test. # # WHY THIS CHART IS TESTED RATHER THAN EXCLUDED, and what a green run is worth. # Bulwark needs no database, no cache and no broker, and it does not need a # reachable JMAP server to START - the connection is made per request, not at # boot - so a dummy URL is enough to bring the pod up. That makes this one of # the cheapest install-tests in the repo and the only place where the two # riskiest assumptions of the chart are exercised at all. Neither is observable # in a render. # # WHAT A GREEN RUN PROVES: # # 1. readOnlyRootFilesystem: true HOLDS, WITH EXACTLY THE TWO emptyDirs THE # CHART RENDERS - /tmp and /app/.next/cache. This is the measurement point # the plan for Gitea #89 left open, and it is the reason this fixture # exists: the set of writable paths was DERIVED from next.config.ts # (output: "standalone", and images.unoptimized applies only in the isLite # branch, so image optimization is active and writes under # /app/.next/cache) and could not be measured without running the image. # A missing writable path is not subtle - it is an EROFS or EACCES on the # first write - so a pod that reaches Ready and stays Ready is the proof. # A SUPERFLUOUS emptyDir would NOT be caught here; that half stays open and # is named in the chart's wiki page. # 2. THE PROBES CAN REALLY BE SATISFIED. All three are an HTTP GET on # /api/health, which has to exist in the built bundle and answer 200 # unauthenticated. If it did not, the startup probe would exhaust its budget # and the rollout would fail - and no render could have told us, because a # path only exists in the built bundle. # 3. The uid/gid 1001 read off the Dockerfile is right, and fsGroup makes a # freshly provisioned volume writable for it: Bulwark creates its four # subdirectories under /app/data itself on start, and the image's own mkdir # ran in a layer this mount covers. Wrong uid, or no fsGroup, and the # instrumentation hook fails to write admin.json. # 4. ADMIN_PASSWORD is really consumed on the first start - the hook hashes it # with scrypt and writes /app/data/admin/admin.json. That write is inside # the startup budget, so a budget that was too short would fail here. # Plus the mechanical half: the projected volume assembles, the Service port # name matches the containerPort, the Ingress is accepted with its extra # annotation alongside the cert-manager one, the resource quantities are valid, # and the schema accepts a realistic non-default values file. # # ONE CLAIM THIS FIXTURE DELIBERATELY DOES NOT MAKE, because it would be # false: it does not prove that the two FILE-form secret keys are spelled # right. The projected volume marks both sources `optional: true` on purpose - # so that a first install does not hang in ContainerCreating while the # 1Password operator catches up - and the consequence is that a misspelled # session-secret or oauth-client-secret key yields a MISSING FILE and a pod # that starts perfectly. Only `admin-password` is proven, because it arrives # through a non-optional secretKeyRef and a wrong name there leaves the # container in CreateContainerConfigError. # # WHAT IT DOES NOT PROVE, and the first two matter most: # - ANYTHING ABOUT JMAP. The URL below points at a Service that answers # nothing useful. Bulwark comes up regardless, which is precisely the # property the readiness probe cannot see (chart values.yaml, # bulwark.readinessProbe) - so this test demonstrates the limitation rather # than closing it. A green run here says nothing about mail working. # - ANYTHING ABOUT OIDC. No identity provider runs in CI, so the sign-in path # is never walked. What IS checked is that the chart's render guards fire on # a missing client id or issuer (unit-testable at render time) and that the # secret file is mounted where OAUTH_CLIENT_SECRET_FILE points. # - THE PRECEDENCE TRAP. config.json never exists in this run, so the # "volume beats the chart" behaviour - the chart's single most important # operating caveat - is not exercised. It cannot be: it needs an admin # writing an override, which needs a browser. # - anything about an upgrade, or about a volume that already carries data. # This installs once, from nothing. secrets: # Materialized by ci/bulwark/fixtures.yaml as ci-bulwark-app-secret and # ci-bulwark-oauth-secret (the OnePasswordItem stub CRD comes from ci/common/). appSecretRef: op://ci/bulwark-app oauthSecretRef: op://ci/bulwark-oauth bulwark: # A dummy JMAP endpoint. It does NOT have to answer - Bulwark connects per # request - but it does have to be SET, because an empty value puts the app # into bootstrap mode and serves the setup wizard instead. So this value also # exercises the path that keeps the chart declarative. # # It points at the selector-less Service in fixtures.yaml: a name that RESOLVES # and then refuses the connection. That is deliberately not a Deployment - # fixtures with a workload have to roll out before the chart is installed, so a # stub serving nothing would be one more thing able to fail a run that is not # about it. A refused connection and a served 404 are indistinguishable to this # chart, because nothing here contacts the JMAP server at boot. jmapServerUrl: http://jmap-stub.default.svc.cluster.local:8080 appName: Bulwark CI # OIDC LEFT ON, i.e. the chart's default, although no provider answers in CI. # That is deliberate: it is what makes the run exercise the OAuth # OnePasswordItem, the second source of the projected volume and the # OAUTH_CLIENT_SECRET_FILE path. Bulwark does not contact the issuer at boot, # so an unreachable one costs nothing here - it would only fail a sign-in, and # nothing signs in. oauth: enabled: true only: true clientId: bulwark-ci issuerUrl: https://keycloak.ci.example.com/realms/ci # ON, i.e. the chart's default, and it is what makes /app/data/settings get # created and the session secret actually read: with the sync off, a missing or # misnamed session-secret field would pass unnoticed. settingsSync: true # OFF - the chart's default, spelled out because it is load-bearing for what # this test means. With it ON, a fresh install cannot write admin.json and the # admin dashboard never comes up; the pod would still be Ready, so the run # would be green while the chart's documented rescue door was missing. Leaving # it off is what makes step 4 of "what a green run proves" an assertion. adminConfigReadOnly: false # Both off, as in the chart - and here with a second reason: a CI runner should # not send a telemetry heartbeat or an update check to a vendor endpoint on # every pull request. telemetry: false updateCheck: false # debug, so that a failed run has something to read. The install-test dumps # container logs on failure, and Bulwark's info level says very little about # why a start went wrong. logLevel: debug # text rather than the chart's json: a human reads these logs out of a failed # job, not a collector. logFormat: text resources: # Small, to stay inside the runner's budget, while still exercising the limit # derivation: 192Mi x limitFactor 3 = 576Mi. Not smaller than this on # purpose - the memory LIMIT is what V8's heap limit is derived from, and a # Next.js server with too little heap fails in a way that looks like an # application fault. requests: cpu: 100m memory: 192Mi ephemeral-storage: 64Mi storage: amount: 256Mi # k3d's dynamic provisioner. The intended production value does not exist on # the runner - and a pre-bound hostPath PV would be worse than this one, # because the kubelet does not apply fsGroup to a hostPath volume at all, so # the test would silently stop exercising the ownership path that matters most # here: Bulwark has to CREATE four subdirectories in a volume it did not make. storageClass: local-path ingress: # Left ON although no ingress controller answers in CI: the point is that the # object is accepted and that the Service port it names BY NAME ("http") # actually exists - a typo there is otherwise invisible until someone opens the # URL. enabled: true domain: bulwark.ci.example.com clusterIssuer: ci-selfsigned annotations: # Exercises the annotation merge: this has to arrive ALONGSIDE the # cert-manager annotation the chart always sets, not instead of it. It is # also the annotation this chart's NOTES.txt tells operators to add, so the # fixture is the shape a real install has. nginx.ingress.kubernetes.io/proxy-body-size: "25m" # The egress cap of this chart's NetworkPolicy (Gitea #5) allows the STANDARD # ports of its dependencies. The CI JMAP server does not: fixtures.yaml serves it over plain HTTP on 8080 rather than over HTTPS on 443 - which is exactly the asymmetry values.yaml warns about for an in-cluster jmapServerUrl. # # So this is not a workaround for the test: it is the chart's documented escape # hatch, used for exactly the case values.yaml names. The fixtures are the one # place in the repository where that case is exercised, and removing this entry # shows what the cap costs a dependency on a non-standard port. networkPolicy: egress: extraPorts: - 8080