From ca617391552baf70bc9addb1f8709c2afb8830c0 Mon Sep 17 00:00:00 2001 From: Joris Wouter Jonkers Date: Sun, 25 Oct 2026 15:13:29 +0100 Subject: [PATCH] feat(model): admit the Release Gate's own requests to every gate member Each member's pods carry a member label and admit the gate on the port its Canary service targets; the gate's policy reaches every labelled pod. Neither half names the other's Project. Closes #602. --- CONTEXT.md | 5 ++ ...-policy-is-default-deny-and-render-only.md | 10 +++ docs/adr/model/0039-the-label-set-is-fixed.md | 8 ++ spec/v1/10-project-intent.md | 6 ++ spec/v1/16-dependencies.md | 20 +++++ spec/v1/20-resolved-deployment.md | 10 ++- spec/v1/55-delivery.md | 5 +- spec/v1/examples/auth/expected/resolved.json | 2 +- .../auth/rendered/apps/auth/auth/canary.yaml | 12 +-- .../rendered/apps/auth/auth/migration.yaml | 6 +- .../apps/auth/auth/networkpolicy.yaml | 20 +++++ .../rendered/apps/auth/auth/workload.yaml | 2 + .../examples/delivery/expected/resolved.json | 2 +- .../delivery/release-gate/networkpolicy.yaml | 7 ++ .../identity/expected/resolved.grafana.json | 2 +- .../identity/expected/resolved.identity.json | 2 +- .../identity/expected/resolved.portal.json | 2 +- .../identity/expected/resolved.whoami.json | 2 +- .../apps/identity/grafana/canary.yaml | 6 +- .../apps/identity/grafana/networkpolicy.yaml | 10 +++ .../rendered/apps/identity/grafana/vso.yaml | 4 +- .../apps/identity/grafana/workload.yaml | 1 + .../apps/identity/identity/canary.yaml | 6 +- .../apps/identity/identity/networkpolicy.yaml | 10 +++ .../apps/identity/identity/workload.yaml | 1 + .../rendered/apps/identity/portal/canary.yaml | 6 +- .../apps/identity/portal/networkpolicy.yaml | 10 +++ .../apps/identity/portal/workload.yaml | 1 + .../rendered/apps/identity/whoami/canary.yaml | 6 +- .../apps/identity/whoami/networkpolicy.yaml | 10 +++ .../apps/identity/whoami/workload.yaml | 1 + .../expected/resolved.hindsight.json | 2 +- .../knowledge-platform/hindsight/canary.yaml | 18 ++--- .../hindsight/networkpolicy.yaml | 30 ++++++++ .../knowledge-platform/hindsight/vso.yaml | 12 +-- .../hindsight/workload.yaml | 3 + .../examples/minimal/expected/resolved.json | 2 +- .../rendered/apps/notes/notes/canary.yaml | 6 +- .../apps/notes/notes/networkpolicy.yaml | 10 +++ .../rendered/apps/notes/notes/workload.yaml | 1 + .../schemas/resolved-application.schema.json | 25 ++++++- .../schemas/resolved-deployment.schema.json | 25 ++++++- src/adapters/kubernetes/render.ts | 11 +++ src/adapters/networking/render.ts | 37 +++++++--- src/adapters/shared/labels.ts | 5 ++ src/model/resolved-deployment.ts | 12 ++- src/resolve/application.ts | 32 +++++--- src/resolve/database.ts | 4 +- src/resolve/policy.ts | 49 +++++++++++- test/application/compose.test.ts | 36 ++++++++- test/model/render.test.ts | 52 +++++++++++++ test/model/resolution-derivations.test.ts | 74 +++++++++++++++++-- 52 files changed, 550 insertions(+), 91 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index d7f48ed7c..6ff1a7d77 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -404,6 +404,11 @@ admits it and names no consumer, so a consumer's edges change its own share alone ([chapter 16](spec/v1/16-dependencies.md#the-consumer-label), [0035](docs/adr/model/0035-network-policy-is-default-deny-and-render-only.md)). +**Member label**: the label every gate member's pods carry, which the +Release Gate's policy reaches on any port while the member's own policy admits +the gate on one. Neither names the other's Processes, so a member added changes +its own share alone ([chapter 16](spec/v1/16-dependencies.md#the-member-label)). + **Tier**: where the edge terminates: five facts, `audiences`, `listener`, `certificates`, `forwardAuth`, `proxiedBy`, plus the Traefik Application that is its proxy. A route's audience is the only way it reaches a tier diff --git a/docs/adr/model/0035-network-policy-is-default-deny-and-render-only.md b/docs/adr/model/0035-network-policy-is-default-deny-and-render-only.md index b17d7dc6c..9a344ffa8 100644 --- a/docs/adr/model/0035-network-policy-is-default-deny-and-render-only.md +++ b/docs/adr/model/0035-network-policy-is-default-deny-and-render-only.md @@ -83,6 +83,15 @@ that says who may. The migration runner carries the label of its datastore's Surface too, so no datastore's policy names a migration identity. +**The Release Gate reaches its members by a label too.** The gate requests each +member's new copy itself during analysis, so each member admits the gate, and +the gate reaches every pod carrying the +[member label](../../../spec/v1/16-dependencies.md#the-member-label), on any port, +the member's own ingress naming the one port it admits. Naming every member in +the gate's policy would make each new gated Application re-render the delivery +Project; admitting every pod the model manages would let the gate reach +datastores it never asks. + **Whether a delivered render applies the policy set is open.** Flux applies each Project's share of the render ([0050](0050-delivery-is-part-of-the-model.md)), and a policy file listed in an applied kustomization is enforced on first sync. @@ -100,6 +109,7 @@ proposal awaiting the maintainer's decision. | Wait for the CNI before rendering | nothing unenforced in the tree | puts a lab evaluation on the model's critical path | | Name each consumer in the provider's policy | a policy that lists exactly who reaches it | a consumer's edge re-renders, re-pins and re-releases the provider, so one Project's change moves another | | Admit a consumer by a label on its Namespace | one label per Project, not per pod | every pod in the namespace reaches the Surface, not only the Process that declared the edge | +| Let the gate reach every pod the model manages | no member label | the gate's egress admits every datastore and job in the estate, bounded only by each one's ingress | ## Reversibility diff --git a/docs/adr/model/0039-the-label-set-is-fixed.md b/docs/adr/model/0039-the-label-set-is-fixed.md index e421b4995..6d56e7f61 100644 --- a/docs/adr/model/0039-the-label-set-is-fixed.md +++ b/docs/adr/model/0039-the-label-set-is-fixed.md @@ -95,6 +95,14 @@ provider's render. It is on pod templates only, derived from an edge and never authored, and never a workload's selector, so an edge added or removed changes a template and nothing immutable. +**The member label is the same case, in the other direction.** Every gate +member's pods carry `gated.deploy-kit.jorisjonkers.dev/release-gate`, which the +Release Gate's NetworkPolicy reaches +([0035](0035-network-policy-is-default-deny-and-render-only.md)). Naming each +member in the gate's policy would put every gated Application into the delivery +Project's render. It is on pod templates only, derived from the release-gate +inputs, and never a selector. + ## Alternatives | option | cost if taken | why rejected | |---|---|---| diff --git a/spec/v1/10-project-intent.md b/spec/v1/10-project-intent.md index 796a0d635..d7e756c33 100644 --- a/spec/v1/10-project-intent.md +++ b/spec/v1/10-project-intent.md @@ -528,6 +528,12 @@ only, derived from the edges and never authored, and it is never a workload's selector, so an edge added or removed changes the pod template and nothing immutable. +A gate member's pods carry `gated.deploy-kit.jorisjonkers.dev/release-gate: "true"`, +which the Release Gate's policy admits +([chapter 16](16-dependencies.md#the-member-label)), on the same terms: on pod +templates only, derived from the release-gate inputs, never authored and never +a selector. + An estate-scoped Deliverable carries `managed-by` and nothing else: it belongs to no Process and to no Application, and `part-of` on such an object would name a Application that does not own it. diff --git a/spec/v1/16-dependencies.md b/spec/v1/16-dependencies.md index 141084826..126a8baaf 100644 --- a/spec/v1/16-dependencies.md +++ b/spec/v1/16-dependencies.md @@ -513,6 +513,8 @@ producer. | from the route tier carrying the audience | a route on the Application's `exposure` naming this Process | ingress | | from the metrics stack, to the scrape port | the Process's `scrape` surface | ingress | | from the Process's own backup identity, to the surface its backups dump | the surface the Platform document's `engines` names for the Process's `engine` | ingress | +| from the Release Gate, to a gate member's first surface | the Process being a member of its Application's release-gate inputs ([chapter 20](20-resolved-deployment.md#the-release-gate)), and the Platform document's `delivery.gate` naming the gate: every Process of the gate, on the port the member's Canary service targets, where the gate's own requests arrive ([chapter 55](55-delivery.md#the-release-gate)) | ingress | +| to every pod carrying the member label, on any port | every Process of the Application the Platform document's `delivery.gate` names ([The member label](#the-member-label)) | egress | | from Flux's notification controller, to the Reporter's `http` surface | the Platform document's `delivery.reporter` naming the Application ([chapter 14](14-platform-intent.md#delivery-policy)): the first of its Processes that provides the surface, and no other. The peer is in the namespace `bootstrap.flux.sourceRef` names and is selected by the `app` label Flux's own install gives the controller, because Flux is in the bootstrap set and no Process, so nothing renders a label for it | ingress | | to where the Kubernetes API answers, by address | the Process's `api`, and the Platform document's `apiAccess.server` ([Kubernetes API access is declared and admitted](#kubernetes-api-access-is-declared-and-admitted)) | egress | | between a provider's Instances, on the surface its engine's move method syncs over | an open Move of the provider ([chapter 55](55-delivery.md#moves)): from the target Instance to the source while the sync runs, and back while reverse replication runs | egress and ingress | @@ -565,6 +567,24 @@ starting and ending with a letter or digit. An Application whose id, joined by a dot to a surface one of its Processes provides, is no label name is refused at that Process as soon as the fragment is read: `E_SURFACE_LABEL_INVALID`. +### The member label + +The Release Gate reaches every gate member without naming one. The pods of every +member, a Process its Application's release-gate inputs name, carry +`gated.deploy-kit.jorisjonkers.dev/release-gate: "true"`. The gate's policy +admits egress to every pod carrying it, in any namespace labelled +`app.kubernetes.io/managed-by: deploy-kit`, on any port, and each member's +policy admits the gate on the one port its Canary service targets, the port of +its first surface. The gate's rule reads no Project's Processes, and a member's +rule reads the Platform document's `delivery.gate` alone, so a member added, +changed or removed changes its own share and never the gate's +([0116](../../docs/adr/model/0116-a-project-owns-every-object-it-delivers.md)). + +The gate's rule names no port because members serve on many and no one fact of +the delivery Project holds them; the member's own ingress is what bounds it. The +label is rendered only on a Process the release-gate inputs name, on pod +templates only, never in a selector, the same trust as the consumer label's. + ### The baseline Two rules are in the rendered set for every Process and appear in no diff --git a/spec/v1/20-resolved-deployment.md b/spec/v1/20-resolved-deployment.md index f17834eba..c4746dfca 100644 --- a/spec/v1/20-resolved-deployment.md +++ b/spec/v1/20-resolved-deployment.md @@ -92,14 +92,16 @@ adapter reads the Resolved Deployment and nothing else are spelled; its `surfaces`, each a name and a port, which a route, a scrape and a policy name it by; and the peers its policy admits beyond its own edges: its `ingress`, each peer tagged with the allow-set rule that admits it (`tier-proxy`, -`metrics-stack`, `backup` or `flux-events`, each naming a namespace and a +`metrics-stack`, `backup`, `flux-events` or `release-gate`, each naming a namespace and a Process, or `consumer`, naming instead the surface whose [consumer label](16-dependencies.md#the-consumer-label) it admits); the surfaces it `consumes`, one per edge to an Application, as that label names them; and its `egress`, the `cluster-dns` baseline and, for a Process that holds a grant, the `secret-store`: the Process of the Secret Store's Application that answers on its `http` surface -([chapter 16](16-dependencies.md#the-derived-allow-set)), and, for a Process that +([chapter 16](16-dependencies.md#the-derived-allow-set)), and, for a Process of +the Release Gate, `gate-members`, which names no peer: every pod carrying the +[member label](16-dependencies.md#the-member-label); and, for a Process that declares it, its `internetEgress`, the reason it reaches the public internet ([chapter 16](16-dependencies.md#internet-egress)). An Application records its `scrape`, the monitored surface at the Platform document's cadence; an @@ -1980,6 +1982,9 @@ classDiagram +string process +int port } + class GateMembersPeer { + +string rule + } class InternetEgress { +string reason } @@ -2174,6 +2179,7 @@ classDiagram ResolvedProcess "1" *-- "0..*" ResolvedSurface : surfaces ResolvedProcess "1" *-- "0..*" IngressPeer : ingress ResolvedProcess "1" *-- "0..*" EgressPeer : egress + ResolvedProcess "1" *-- "0..1" GateMembersPeer : egress ResolvedProcess "1" *-- "0..1" InternetEgress : internetEgress ResolvedVolume "1" *-- "0..1" BackupPlan : backup ResolvedProcess "1" *-- "0..1" ResolvedApiAccess : api diff --git a/spec/v1/55-delivery.md b/spec/v1/55-delivery.md index f6dea8e18..4eb55afb8 100644 --- a/spec/v1/55-delivery.md +++ b/spec/v1/55-delivery.md @@ -967,8 +967,9 @@ closes this, because the address is not in the name. A warmup therefore: - never follows a redirect: a `3xx` is an answer, and it passes. A check during analysis asks the member's Canary service, which the gate -names itself and no project file does. The gate reads everything else from -the Resolved Deployment. +names itself and no project file does, and the derived allow set admits that +request at both ends ([the member label](16-dependencies.md#the-member-label)). +The gate reads everything else from the Resolved Deployment. **A member that did not change starts no analysis.** Flagger analyses a Canary only when its target's pod template, or a configuration object it tracks, diff --git a/spec/v1/examples/auth/expected/resolved.json b/spec/v1/examples/auth/expected/resolved.json index 41e4caaaf..1a17bb12e 100644 --- a/spec/v1/examples/auth/expected/resolved.json +++ b/spec/v1/examples/auth/expected/resolved.json @@ -1 +1 @@ -{"alertClass":"page","apiVersion":"resolved.jorisjonkers.dev/v1","exposure":[{"certificates":"acme","host":"auth.jorisjonkers.dev","listener":"tls","name":"public","proxy":{"application":"traefik-public","namespace":"edge-system"},"routes":[{"audience":"anonymous","match":"exact","middleware":[{"contentPolicy":"strict","kind":"security-headers"},{"allowance":10,"burst":10,"clientAddress":"cloudflare","kind":"rate-limit","per":"1h"}],"path":"/api/v1/users/register","precedence":1,"process":"auth-api","surface":"http"},{"audience":"anonymous","match":"prefix","middleware":[{"contentPolicy":"strict","kind":"security-headers"}],"path":"/api","precedence":2,"process":"auth-api","surface":"http"},{"audience":"anonymous","match":"prefix","middleware":[{"contentPolicy":"strict","kind":"security-headers"}],"path":"/","precedence":3,"process":"auth-ui","surface":"http"}],"tier":"public-frankfurt"}],"id":"auth","kind":"ResolvedApplication","migration":{"consumes":["platform-postgres.postgres"],"cpu":"100m","credential":{"delivery":"self","engine":"database","paths":[{"allows":["read"],"path":"database/creds/auth-owner"}]},"database":{"host":"postgres.data-system.svc.cluster.local","name":"auth_db","port":5432},"deadline":"10m","egress":[{"namespace":"data-system","port":5432,"process":"postgres","rule":"datastore"},{"namespace":"secrets-system","port":8200,"process":"vault","rule":"secret-store"},{"namespace":"kube-system","port":53,"rule":"cluster-dns"}],"gid":1000,"identity":"auth-migration","memory":"256Mi","nonTransactional":false,"runner":"ghcr.io/jorisjonkers-dev/auth/auth-migration@sha256:5c7e9a1b3d5f7c9e1a3b5d7f9c1e3a5b7d9f1c3e5a7b9d1f3c5e7a9b1d3f5c7e","runnerVersion":"2.3.0","scratch":"64Mi","testedAgainst":"sha256:2f4a6c8e0b2d4f6a8c0e2b4d6f8a0c2e4b6d8f0a2c4e6b8d0f2a4c6e8b0d2f4a","uid":1000},"namespace":"auth-system","processes":[{"consumes":["platform-postgres.postgres","platform-valkey.redis","platform-rabbitmq.amqp"],"cpu":"250m","cutover":"continuous","deadline":"1920s","dependencies":[{"address":"platform-postgres:5432","application":"platform-postgres","peers":[{"namespace":"data-system","port":5432,"process":"postgres"}],"surface":"postgres","target":"postgres.data-system.svc.cluster.local"},{"address":"platform-valkey:6379","application":"platform-valkey","peers":[{"namespace":"data-system","port":6379,"process":"valkey"}],"surface":"redis","target":"valkey.data-system.svc.cluster.local"},{"address":"platform-rabbitmq:5672","application":"platform-rabbitmq","peers":[{"namespace":"data-system","port":5672,"process":"rabbitmq"}],"surface":"amqp","target":"rabbitmq.data-system.svc.cluster.local"},{"address":"10.0.0.12:25","application":"stalwart","surface":"smtp"}],"egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"},{"namespace":"secrets-system","port":8200,"process":"vault","rule":"secret-store"}],"environment":[{"name":"AUTH_ISSUER","value":"https://auth.jorisjonkers.dev"},{"name":"AUTH_LOGIN_URL","value":"https://auth.jorisjonkers.dev/login"},{"name":"CONFIRMATION_URL","value":"https://auth.jorisjonkers.dev/confirm"},{"name":"DB_HOST","value":"platform-postgres"},{"name":"DB_PORT","value":"5432"},{"name":"DEPLOYMENT_ENVIRONMENT","value":"production"},{"name":"MAIL_HOST","value":"10.0.0.12"},{"name":"MAIL_PORT","value":"25"},{"name":"OTEL_EXPORTER_OTLP_ENDPOINT","value":"http://otel-collector.observability-system.svc.cluster.local:4317"},{"name":"OTEL_SERVICE_NAME","value":"auth-api"},{"name":"PORT","value":"8080"},{"name":"SESSION_COOKIE_SECURE","value":"true"},{"name":"SPRING_CONFIG_IMPORT","value":"vault://"},{"name":"SPRING_PROFILES_ACTIVE","value":"prod"},{"name":"SPRING_RABBITMQ_HOST","value":"platform-rabbitmq"},{"name":"SPRING_RABBITMQ_PORT","value":"5672"},{"name":"VALKEY_HOST","value":"platform-valkey"},{"name":"VALKEY_PORT","value":"6379"},{"name":"VAULT_AUTHENTICATION","value":"KUBERNETES"},{"name":"VAULT_ENABLED","value":"true"},{"name":"VAULT_KUBERNETES_ROLE","value":"auth-system-auth-api"}],"gid":1000,"hardening":"restricted","identity":"auth-api","identityToken":true,"image":"ghcr.io/jorisjonkers-dev/auth/auth-api@sha256:3e5a7c9b1d3f5e7a9c1b3d5f7e9a1c3b5d7f9e1a3c5b7d9f1e3a5c7b9d1f3e5a","imageVersion":"2.3.0","ingress":[{"namespace":"edge-system","port":8080,"process":"traefik-public","rule":"tier-proxy"},{"namespace":"observability-system","port":8080,"process":"prometheus","rule":"metrics-stack"},{"consumes":"auth.http","port":8080,"rule":"consumer"}],"lifecycle":"application","liveness":{"failures":3,"path":"/api/actuator/health/liveness","period":"10s","port":8081,"timeout":"5s"},"memory":"768Mi","name":"auth-api","placement":{"eligibleNodes":["frankfurt-contabo-1"]},"readiness":{"failures":3,"path":"/api/actuator/health/readiness","period":"10s","port":8081,"timeout":"5s"},"replicas":2,"runtime":"jvm","secrets":[{"access":"read","delivery":"self","keys":["user","password"],"path":"secret/data/platform/postgres/auth"},{"access":"read","delivery":"self","keys":["mail.password","valkey.password","rabbitmq.user","rabbitmq.password"],"path":"secret/data/auth-api"},{"delivery":"self","engine":"transit","paths":[{"allows":["update"],"path":"transit/sign/auth-api-jwt"},{"allows":["update"],"path":"transit/keys/auth-api-jwt/rotate"},{"allows":["read"],"path":"transit/keys/auth-api-jwt"}]},{"delivery":"self","engine":"database","paths":[{"allows":["read"],"path":"database/creds/auth-data"}]}],"startup":{"failures":120,"path":"/api/actuator/health/liveness","period":"5s","port":8081,"timeout":"5s"},"surfaces":[{"name":"http","port":8080}],"switchover":"blue-green","uid":1000,"writablePaths":[{"path":"/tmp","size":"64Mi"}]},{"cpu":"10m","cutover":"continuous","deadline":"210s","egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"}],"gid":101,"hardening":"restricted","identity":"auth-ui","identityToken":false,"image":"ghcr.io/jorisjonkers-dev/auth/auth-ui@sha256:8b0d2f4a6c8e0b2d4f6a8c0e2b4d6f8a0c2e4b6d8f0a2c4e6b8d0f2a4c6e8b0d","imageVersion":"2.3.0","ingress":[{"namespace":"edge-system","port":80,"process":"traefik-public","rule":"tier-proxy"},{"consumes":"auth.http","port":80,"rule":"consumer"}],"lifecycle":"application","liveness":{"failures":3,"path":"/","period":"10s","port":80,"timeout":"5s"},"memory":"64Mi","name":"auth-ui","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3","frankfurt-contabo-1"]},"readiness":{"failures":3,"path":"/","period":"10s","port":80,"timeout":"5s"},"replicas":1,"runtime":"static","startup":{"failures":6,"path":"/","period":"5s","port":80,"timeout":"5s"},"surfaces":[{"name":"http","port":80}],"switchover":"blue-green","uid":101,"writablePaths":[{"path":"/var/cache/nginx","size":"64Mi"},{"path":"/var/run","size":"64Mi"}]}],"project":"auth","provenance":{"inputDigests":[{"digest":"sha256:a8ffae4a90542ed792d9e285333f706f9964bd0d3888e6e9ca686932f0c44a5c","input":"intent-fragment","name":"auth"},{"digest":"sha256:338533f83f590c641c3b7c3855483358ce99da5e6ca37512da9340a849c38fa0","input":"intent-fragment","name":"data"},{"digest":"sha256:ebb5134deeb55f320e28609054a0aca85a359f3869501a3a8b7560d7d05a4277","input":"intent-fragment","name":"delivery"},{"digest":"sha256:9a29fae42c0c6cd4f9824d7af3d9ecc68c068a3a00d1f9488bfbb70c882ea565","input":"intent-fragment","name":"edge"},{"digest":"sha256:9de2ffb5ab977b42af31000e571e8042380d213ed480defdff50eef41c08e68c","input":"intent-fragment","name":"identity"},{"digest":"sha256:84b3b1a8e81fc41b549a34715713cae3354cfc475e9add64d322b7c631f5b816","input":"intent-fragment","name":"observability"},{"digest":"sha256:31309996835674cc4f037f8e19897ddbe4a7fa8a487a3efc613e251caeed0853","input":"intent-fragment","name":"secrets"},{"digest":"sha256:9f60e75b7b449edb3e0f44485a7ed8fa423a5436ab2b4ca9a1df14bafe76beea","input":"platform-intent","name":"jorisjonkers.dev"},{"digest":"sha256:a989e80337efb294fa1a7175b3ad3eea8bed9bf13fbd503a26691c9265575fc2","input":"node-contract","name":"production"},{"digest":"sha256:179f17834dd006548bfdbdd623e8f74b25be6eedde214f8e00e8cb4295807d57","input":"images-lock","name":"estate"},{"digest":"sha256:202e5e2a22ab20cfe5b6e85cbd8139cb13ed3b199b2f1af4380068d21d54ff1f","input":"cluster-state","name":"production"}],"renderHash":"sha256:d41c3cb31616cf8d8b707d871d4a228d45137c47066871aeaa65f2c0bfc4fea5","schemaPackageIntegrity":"sha256:5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b"},"reconcileAfter":["apps-data","apps-edge","apps-secrets"],"reconcileUnit":"apps-auth","releaseGate":{"analysis":{"errorRate":"1%","interval":"30s","iterations":4,"latency":"500ms","threshold":3},"deadline":"1920s","endpoint":"http://release-gate.delivery-system.svc.cluster.local:8080","members":[{"checks":["error-rate","latency"],"process":"auth-api","readiness":{"path":"/api/actuator/health/readiness","port":8081}},{"process":"auth-ui","readiness":{"path":"/","port":80}}],"migration":{"identity":"auth-migration","nonTransactional":false,"testedAgainst":"sha256:2f4a6c8e0b2d4f6a8c0e2b4d6f8a0c2e4b6d8f0a2c4e6b8d0f2a4c6e8b0d2f4a"}},"revision":"sha256:c4ffd08d2bfec44918ad8fffde13123799ce7e6c50b1eae44973b40cf481cfc3","scrape":{"interval":"30s","path":"/api/actuator/prometheus","process":"auth-api","surface":"http","timeout":"10s"},"secretStore":"http://vault.secrets-system.svc.cluster.local:8200"} \ No newline at end of file +{"alertClass":"page","apiVersion":"resolved.jorisjonkers.dev/v1","exposure":[{"certificates":"acme","host":"auth.jorisjonkers.dev","listener":"tls","name":"public","proxy":{"application":"traefik-public","namespace":"edge-system"},"routes":[{"audience":"anonymous","match":"exact","middleware":[{"contentPolicy":"strict","kind":"security-headers"},{"allowance":10,"burst":10,"clientAddress":"cloudflare","kind":"rate-limit","per":"1h"}],"path":"/api/v1/users/register","precedence":1,"process":"auth-api","surface":"http"},{"audience":"anonymous","match":"prefix","middleware":[{"contentPolicy":"strict","kind":"security-headers"}],"path":"/api","precedence":2,"process":"auth-api","surface":"http"},{"audience":"anonymous","match":"prefix","middleware":[{"contentPolicy":"strict","kind":"security-headers"}],"path":"/","precedence":3,"process":"auth-ui","surface":"http"}],"tier":"public-frankfurt"}],"id":"auth","kind":"ResolvedApplication","migration":{"consumes":["platform-postgres.postgres"],"cpu":"100m","credential":{"delivery":"self","engine":"database","paths":[{"allows":["read"],"path":"database/creds/auth-owner"}]},"database":{"host":"postgres.data-system.svc.cluster.local","name":"auth_db","port":5432},"deadline":"10m","egress":[{"namespace":"data-system","port":5432,"process":"postgres","rule":"datastore"},{"namespace":"secrets-system","port":8200,"process":"vault","rule":"secret-store"},{"namespace":"kube-system","port":53,"rule":"cluster-dns"}],"gid":1000,"identity":"auth-migration","memory":"256Mi","nonTransactional":false,"runner":"ghcr.io/jorisjonkers-dev/auth/auth-migration@sha256:5c7e9a1b3d5f7c9e1a3b5d7f9c1e3a5b7d9f1c3e5a7b9d1f3c5e7a9b1d3f5c7e","runnerVersion":"2.3.0","scratch":"64Mi","testedAgainst":"sha256:2f4a6c8e0b2d4f6a8c0e2b4d6f8a0c2e4b6d8f0a2c4e6b8d0f2a4c6e8b0d2f4a","uid":1000},"namespace":"auth-system","processes":[{"consumes":["platform-postgres.postgres","platform-valkey.redis","platform-rabbitmq.amqp"],"cpu":"250m","cutover":"continuous","deadline":"1920s","dependencies":[{"address":"platform-postgres:5432","application":"platform-postgres","peers":[{"namespace":"data-system","port":5432,"process":"postgres"}],"surface":"postgres","target":"postgres.data-system.svc.cluster.local"},{"address":"platform-valkey:6379","application":"platform-valkey","peers":[{"namespace":"data-system","port":6379,"process":"valkey"}],"surface":"redis","target":"valkey.data-system.svc.cluster.local"},{"address":"platform-rabbitmq:5672","application":"platform-rabbitmq","peers":[{"namespace":"data-system","port":5672,"process":"rabbitmq"}],"surface":"amqp","target":"rabbitmq.data-system.svc.cluster.local"},{"address":"10.0.0.12:25","application":"stalwart","surface":"smtp"}],"egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"},{"namespace":"secrets-system","port":8200,"process":"vault","rule":"secret-store"}],"environment":[{"name":"AUTH_ISSUER","value":"https://auth.jorisjonkers.dev"},{"name":"AUTH_LOGIN_URL","value":"https://auth.jorisjonkers.dev/login"},{"name":"CONFIRMATION_URL","value":"https://auth.jorisjonkers.dev/confirm"},{"name":"DB_HOST","value":"platform-postgres"},{"name":"DB_PORT","value":"5432"},{"name":"DEPLOYMENT_ENVIRONMENT","value":"production"},{"name":"MAIL_HOST","value":"10.0.0.12"},{"name":"MAIL_PORT","value":"25"},{"name":"OTEL_EXPORTER_OTLP_ENDPOINT","value":"http://otel-collector.observability-system.svc.cluster.local:4317"},{"name":"OTEL_SERVICE_NAME","value":"auth-api"},{"name":"PORT","value":"8080"},{"name":"SESSION_COOKIE_SECURE","value":"true"},{"name":"SPRING_CONFIG_IMPORT","value":"vault://"},{"name":"SPRING_PROFILES_ACTIVE","value":"prod"},{"name":"SPRING_RABBITMQ_HOST","value":"platform-rabbitmq"},{"name":"SPRING_RABBITMQ_PORT","value":"5672"},{"name":"VALKEY_HOST","value":"platform-valkey"},{"name":"VALKEY_PORT","value":"6379"},{"name":"VAULT_AUTHENTICATION","value":"KUBERNETES"},{"name":"VAULT_ENABLED","value":"true"},{"name":"VAULT_KUBERNETES_ROLE","value":"auth-system-auth-api"}],"gid":1000,"hardening":"restricted","identity":"auth-api","identityToken":true,"image":"ghcr.io/jorisjonkers-dev/auth/auth-api@sha256:3e5a7c9b1d3f5e7a9c1b3d5f7e9a1c3b5d7f9e1a3c5b7d9f1e3a5c7b9d1f3e5a","imageVersion":"2.3.0","ingress":[{"namespace":"edge-system","port":8080,"process":"traefik-public","rule":"tier-proxy"},{"namespace":"observability-system","port":8080,"process":"prometheus","rule":"metrics-stack"},{"consumes":"auth.http","port":8080,"rule":"consumer"},{"namespace":"delivery-system","port":8080,"process":"release-gate","rule":"release-gate"}],"lifecycle":"application","liveness":{"failures":3,"path":"/api/actuator/health/liveness","period":"10s","port":8081,"timeout":"5s"},"memory":"768Mi","name":"auth-api","placement":{"eligibleNodes":["frankfurt-contabo-1"]},"readiness":{"failures":3,"path":"/api/actuator/health/readiness","period":"10s","port":8081,"timeout":"5s"},"replicas":2,"runtime":"jvm","secrets":[{"access":"read","delivery":"self","keys":["user","password"],"path":"secret/data/platform/postgres/auth"},{"access":"read","delivery":"self","keys":["mail.password","valkey.password","rabbitmq.user","rabbitmq.password"],"path":"secret/data/auth-api"},{"delivery":"self","engine":"transit","paths":[{"allows":["update"],"path":"transit/sign/auth-api-jwt"},{"allows":["update"],"path":"transit/keys/auth-api-jwt/rotate"},{"allows":["read"],"path":"transit/keys/auth-api-jwt"}]},{"delivery":"self","engine":"database","paths":[{"allows":["read"],"path":"database/creds/auth-data"}]}],"startup":{"failures":120,"path":"/api/actuator/health/liveness","period":"5s","port":8081,"timeout":"5s"},"surfaces":[{"name":"http","port":8080}],"switchover":"blue-green","uid":1000,"writablePaths":[{"path":"/tmp","size":"64Mi"}]},{"cpu":"10m","cutover":"continuous","deadline":"210s","egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"}],"gid":101,"hardening":"restricted","identity":"auth-ui","identityToken":false,"image":"ghcr.io/jorisjonkers-dev/auth/auth-ui@sha256:8b0d2f4a6c8e0b2d4f6a8c0e2b4d6f8a0c2e4b6d8f0a2c4e6b8d0f2a4c6e8b0d","imageVersion":"2.3.0","ingress":[{"namespace":"edge-system","port":80,"process":"traefik-public","rule":"tier-proxy"},{"consumes":"auth.http","port":80,"rule":"consumer"},{"namespace":"delivery-system","port":80,"process":"release-gate","rule":"release-gate"}],"lifecycle":"application","liveness":{"failures":3,"path":"/","period":"10s","port":80,"timeout":"5s"},"memory":"64Mi","name":"auth-ui","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3","frankfurt-contabo-1"]},"readiness":{"failures":3,"path":"/","period":"10s","port":80,"timeout":"5s"},"replicas":1,"runtime":"static","startup":{"failures":6,"path":"/","period":"5s","port":80,"timeout":"5s"},"surfaces":[{"name":"http","port":80}],"switchover":"blue-green","uid":101,"writablePaths":[{"path":"/var/cache/nginx","size":"64Mi"},{"path":"/var/run","size":"64Mi"}]}],"project":"auth","provenance":{"inputDigests":[{"digest":"sha256:a8ffae4a90542ed792d9e285333f706f9964bd0d3888e6e9ca686932f0c44a5c","input":"intent-fragment","name":"auth"},{"digest":"sha256:338533f83f590c641c3b7c3855483358ce99da5e6ca37512da9340a849c38fa0","input":"intent-fragment","name":"data"},{"digest":"sha256:ebb5134deeb55f320e28609054a0aca85a359f3869501a3a8b7560d7d05a4277","input":"intent-fragment","name":"delivery"},{"digest":"sha256:9a29fae42c0c6cd4f9824d7af3d9ecc68c068a3a00d1f9488bfbb70c882ea565","input":"intent-fragment","name":"edge"},{"digest":"sha256:9de2ffb5ab977b42af31000e571e8042380d213ed480defdff50eef41c08e68c","input":"intent-fragment","name":"identity"},{"digest":"sha256:84b3b1a8e81fc41b549a34715713cae3354cfc475e9add64d322b7c631f5b816","input":"intent-fragment","name":"observability"},{"digest":"sha256:31309996835674cc4f037f8e19897ddbe4a7fa8a487a3efc613e251caeed0853","input":"intent-fragment","name":"secrets"},{"digest":"sha256:9f60e75b7b449edb3e0f44485a7ed8fa423a5436ab2b4ca9a1df14bafe76beea","input":"platform-intent","name":"jorisjonkers.dev"},{"digest":"sha256:a989e80337efb294fa1a7175b3ad3eea8bed9bf13fbd503a26691c9265575fc2","input":"node-contract","name":"production"},{"digest":"sha256:179f17834dd006548bfdbdd623e8f74b25be6eedde214f8e00e8cb4295807d57","input":"images-lock","name":"estate"},{"digest":"sha256:202e5e2a22ab20cfe5b6e85cbd8139cb13ed3b199b2f1af4380068d21d54ff1f","input":"cluster-state","name":"production"}],"renderHash":"sha256:d41c3cb31616cf8d8b707d871d4a228d45137c47066871aeaa65f2c0bfc4fea5","schemaPackageIntegrity":"sha256:5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b"},"reconcileAfter":["apps-data","apps-edge","apps-secrets"],"reconcileUnit":"apps-auth","releaseGate":{"analysis":{"errorRate":"1%","interval":"30s","iterations":4,"latency":"500ms","threshold":3},"deadline":"1920s","endpoint":"http://release-gate.delivery-system.svc.cluster.local:8080","members":[{"checks":["error-rate","latency"],"process":"auth-api","readiness":{"path":"/api/actuator/health/readiness","port":8081}},{"process":"auth-ui","readiness":{"path":"/","port":80}}],"migration":{"identity":"auth-migration","nonTransactional":false,"testedAgainst":"sha256:2f4a6c8e0b2d4f6a8c0e2b4d6f8a0c2e4b6d8f0a2c4e6b8d0f2a4c6e8b0d2f4a"}},"revision":"sha256:eaa82b6a19f5d4500a8d409f2c3add5c75c7370e460573689099c119003d3a2b","scrape":{"interval":"30s","path":"/api/actuator/prometheus","process":"auth-api","surface":"http","timeout":"10s"},"secretStore":"http://vault.secrets-system.svc.cluster.local:8200"} \ No newline at end of file diff --git a/spec/v1/examples/auth/rendered/apps/auth/auth/canary.yaml b/spec/v1/examples/auth/rendered/apps/auth/auth/canary.yaml index 0bcb8ae1d..571a9e564 100644 --- a/spec/v1/examples/auth/rendered/apps/auth/auth/canary.yaml +++ b/spec/v1/examples/auth/rendered/apps/auth/auth/canary.yaml @@ -37,7 +37,7 @@ spec: metadata: application: auth process: auth-api - revision: sha256:c4ffd08d2bfec44918ad8fffde13123799ce7e6c50b1eae44973b40cf481cfc3 + revision: sha256:eaa82b6a19f5d4500a8d409f2c3add5c75c7370e460573689099c119003d3a2b paths: /api /api/v1/users/register readiness: /api/actuator/health/readiness hostnames: auth.jorisjonkers.dev @@ -49,7 +49,7 @@ spec: metadata: application: auth process: auth-api - revision: sha256:c4ffd08d2bfec44918ad8fffde13123799ce7e6c50b1eae44973b40cf481cfc3 + revision: sha256:eaa82b6a19f5d4500a8d409f2c3add5c75c7370e460573689099c119003d3a2b paths: /api /api/v1/users/register readiness: /api/actuator/health/readiness hostnames: auth.jorisjonkers.dev @@ -61,7 +61,7 @@ spec: metadata: application: auth process: auth-api - revision: sha256:c4ffd08d2bfec44918ad8fffde13123799ce7e6c50b1eae44973b40cf481cfc3 + revision: sha256:eaa82b6a19f5d4500a8d409f2c3add5c75c7370e460573689099c119003d3a2b paths: /api /api/v1/users/register readiness: /api/actuator/health/readiness hostnames: auth.jorisjonkers.dev @@ -120,7 +120,7 @@ spec: metadata: application: auth process: auth-ui - revision: sha256:c4ffd08d2bfec44918ad8fffde13123799ce7e6c50b1eae44973b40cf481cfc3 + revision: sha256:eaa82b6a19f5d4500a8d409f2c3add5c75c7370e460573689099c119003d3a2b paths: / readiness: / hostnames: auth.jorisjonkers.dev @@ -132,7 +132,7 @@ spec: metadata: application: auth process: auth-ui - revision: sha256:c4ffd08d2bfec44918ad8fffde13123799ce7e6c50b1eae44973b40cf481cfc3 + revision: sha256:eaa82b6a19f5d4500a8d409f2c3add5c75c7370e460573689099c119003d3a2b paths: / readiness: / hostnames: auth.jorisjonkers.dev @@ -144,7 +144,7 @@ spec: metadata: application: auth process: auth-ui - revision: sha256:c4ffd08d2bfec44918ad8fffde13123799ce7e6c50b1eae44973b40cf481cfc3 + revision: sha256:eaa82b6a19f5d4500a8d409f2c3add5c75c7370e460573689099c119003d3a2b paths: / readiness: / hostnames: auth.jorisjonkers.dev diff --git a/spec/v1/examples/auth/rendered/apps/auth/auth/migration.yaml b/spec/v1/examples/auth/rendered/apps/auth/auth/migration.yaml index 80fafd676..35bb951af 100644 --- a/spec/v1/examples/auth/rendered/apps/auth/auth/migration.yaml +++ b/spec/v1/examples/auth/rendered/apps/auth/auth/migration.yaml @@ -15,7 +15,7 @@ metadata: apiVersion: batch/v1 kind: Job metadata: - name: auth-migration-c4ffd08d2bfe + name: auth-migration-eaa82b6a19f5 namespace: auth-system labels: app.kubernetes.io/name: auth-migration @@ -55,7 +55,7 @@ spec: image: ghcr.io/jorisjonkers-dev/auth/auth-migration@sha256:5c7e9a1b3d5f7c9e1a3b5d7f9c1e3a5b7d9f1c3e5a7b9d1f3c5e7a9b1d3f5c7e args: - up - - c4ffd08d2bfe + - eaa82b6a19f5 env: - name: DATABASE_HOST value: postgres.data-system.svc.cluster.local @@ -92,7 +92,7 @@ spec: apiVersion: batch/v1 kind: Job metadata: - name: auth-migration-down-c4ffd08d2bfe + name: auth-migration-down-eaa82b6a19f5 namespace: auth-system labels: app.kubernetes.io/name: auth-migration diff --git a/spec/v1/examples/auth/rendered/apps/auth/auth/networkpolicy.yaml b/spec/v1/examples/auth/rendered/apps/auth/auth/networkpolicy.yaml index bd52cec4e..b4a76fdd6 100644 --- a/spec/v1/examples/auth/rendered/apps/auth/auth/networkpolicy.yaml +++ b/spec/v1/examples/auth/rendered/apps/auth/auth/networkpolicy.yaml @@ -49,6 +49,16 @@ spec: ports: - protocol: TCP port: 8080 + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: delivery-system + podSelector: + matchLabels: + app.kubernetes.io/instance: release-gate + ports: + - protocol: TCP + port: 8080 egress: - to: - namespaceSelector: @@ -141,6 +151,16 @@ spec: ports: - protocol: TCP port: 80 + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: delivery-system + podSelector: + matchLabels: + app.kubernetes.io/instance: release-gate + ports: + - protocol: TCP + port: 80 egress: - to: - namespaceSelector: diff --git a/spec/v1/examples/auth/rendered/apps/auth/auth/workload.yaml b/spec/v1/examples/auth/rendered/apps/auth/auth/workload.yaml index 8805c3973..713def26c 100644 --- a/spec/v1/examples/auth/rendered/apps/auth/auth/workload.yaml +++ b/spec/v1/examples/auth/rendered/apps/auth/auth/workload.yaml @@ -33,6 +33,7 @@ spec: consumes.deploy-kit.jorisjonkers.dev/platform-postgres.postgres: "true" consumes.deploy-kit.jorisjonkers.dev/platform-valkey.redis: "true" consumes.deploy-kit.jorisjonkers.dev/platform-rabbitmq.amqp: "true" + gated.deploy-kit.jorisjonkers.dev/release-gate: "true" annotations: image.jorisjonkers.dev/auth-api: 2.3.0 spec: @@ -175,6 +176,7 @@ spec: app.kubernetes.io/part-of: auth app.kubernetes.io/managed-by: deploy-kit app.kubernetes.io/component: static + gated.deploy-kit.jorisjonkers.dev/release-gate: "true" annotations: image.jorisjonkers.dev/auth-ui: 2.3.0 spec: diff --git a/spec/v1/examples/delivery/expected/resolved.json b/spec/v1/examples/delivery/expected/resolved.json index 9f1fea72e..effd9e207 100644 --- a/spec/v1/examples/delivery/expected/resolved.json +++ b/spec/v1/examples/delivery/expected/resolved.json @@ -1 +1 @@ -{"apiVersion":"resolved.jorisjonkers.dev/v1","id":"release-gate","kind":"ResolvedApplication","namespace":"delivery-system","processes":[{"api":{"reason":"the Release Gate answers Flagger from what the cluster holds, so it reads each gated Application's inputs, Canaries, Deployments, Jobs and pods; it finds the gated Applications by listing ConfigMaps, keeps each one's release record in a ConfigMap of its own, unsuspends the migration Jobs it starts, and deletes a gated Application's Vault sync objects of an older revision once it serves the new one","rules":[{"group":"core","objects":["configmaps"],"verbs":["get","list","create","update"]},{"group":"core","objects":["pods"],"verbs":["list"]},{"group":"apps","objects":["deployments"],"verbs":["get"]},{"group":"batch","objects":["jobs"],"verbs":["list","patch"]},{"group":"flagger.app","objects":["canaries"],"verbs":["get"]},{"group":"secrets.hashicorp.com","objects":["vaultauths","vaultstaticsecrets","vaultdynamicsecrets"],"verbs":["list","delete"]}],"server":[{"cidr":"10.43.0.1/32","port":443}]},"cpu":"25m","cutover":"continuous","deadline":"150s","egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"}],"gid":1000,"hardening":"restricted","identity":"release-gate","identityToken":true,"image":"ghcr.io/jorisjonkers-dev/delivery/release-gate@sha256:3e7a9c1d5b2f8e4a6c0d9b7e5f3a1c8d6e4b2a0f9c7e5d3b1a8f6e4c2d0b9a7e","imageVersion":"1.0.0","ingress":[{"consumes":"release-gate.http","port":8080,"rule":"consumer"}],"lifecycle":"application","liveness":{"failures":3,"path":"/healthz","period":"10s","port":8080,"timeout":"5s"},"memory":"64Mi","name":"release-gate","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3","frankfurt-contabo-1"]},"readiness":{"failures":3,"path":"/readyz","period":"10s","port":8080,"timeout":"5s"},"replicas":1,"runtime":"none","startup":{"failures":2,"path":"/healthz","period":"5s","port":8080,"timeout":"5s"},"surfaces":[{"name":"http","port":8080}],"switchover":"rolling","uid":1000}],"project":"delivery","provenance":{"inputDigests":[{"digest":"sha256:ebb5134deeb55f320e28609054a0aca85a359f3869501a3a8b7560d7d05a4277","input":"intent-fragment","name":"delivery"},{"digest":"sha256:9a29fae42c0c6cd4f9824d7af3d9ecc68c068a3a00d1f9488bfbb70c882ea565","input":"intent-fragment","name":"edge"},{"digest":"sha256:9de2ffb5ab977b42af31000e571e8042380d213ed480defdff50eef41c08e68c","input":"intent-fragment","name":"identity"},{"digest":"sha256:35d5e3643093422537ea72def0f4b19e93ed319a2d0192fc7846342970ce8797","input":"intent-fragment","name":"notes"},{"digest":"sha256:84b3b1a8e81fc41b549a34715713cae3354cfc475e9add64d322b7c631f5b816","input":"intent-fragment","name":"observability"},{"digest":"sha256:31309996835674cc4f037f8e19897ddbe4a7fa8a487a3efc613e251caeed0853","input":"intent-fragment","name":"secrets"},{"digest":"sha256:9f60e75b7b449edb3e0f44485a7ed8fa423a5436ab2b4ca9a1df14bafe76beea","input":"platform-intent","name":"jorisjonkers.dev"},{"digest":"sha256:a989e80337efb294fa1a7175b3ad3eea8bed9bf13fbd503a26691c9265575fc2","input":"node-contract","name":"production"},{"digest":"sha256:179f17834dd006548bfdbdd623e8f74b25be6eedde214f8e00e8cb4295807d57","input":"images-lock","name":"estate"},{"digest":"sha256:202e5e2a22ab20cfe5b6e85cbd8139cb13ed3b199b2f1af4380068d21d54ff1f","input":"cluster-state","name":"production"}],"renderHash":"sha256:450201cedc3a21468e702d1cddfde2059cc79304c1b88250ec8eba494c5b5678","schemaPackageIntegrity":"sha256:5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b"},"reconcileUnit":"apps-delivery","revision":"sha256:a246139575b5cc2b27adf6c62bf82d433e72cc6c902c7569afa0341f37c552be"} \ No newline at end of file +{"apiVersion":"resolved.jorisjonkers.dev/v1","id":"release-gate","kind":"ResolvedApplication","namespace":"delivery-system","processes":[{"api":{"reason":"the Release Gate answers Flagger from what the cluster holds, so it reads each gated Application's inputs, Canaries, Deployments, Jobs and pods; it finds the gated Applications by listing ConfigMaps, keeps each one's release record in a ConfigMap of its own, unsuspends the migration Jobs it starts, and deletes a gated Application's Vault sync objects of an older revision once it serves the new one","rules":[{"group":"core","objects":["configmaps"],"verbs":["get","list","create","update"]},{"group":"core","objects":["pods"],"verbs":["list"]},{"group":"apps","objects":["deployments"],"verbs":["get"]},{"group":"batch","objects":["jobs"],"verbs":["list","patch"]},{"group":"flagger.app","objects":["canaries"],"verbs":["get"]},{"group":"secrets.hashicorp.com","objects":["vaultauths","vaultstaticsecrets","vaultdynamicsecrets"],"verbs":["list","delete"]}],"server":[{"cidr":"10.43.0.1/32","port":443}]},"cpu":"25m","cutover":"continuous","deadline":"150s","egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"},{"rule":"gate-members"}],"gid":1000,"hardening":"restricted","identity":"release-gate","identityToken":true,"image":"ghcr.io/jorisjonkers-dev/delivery/release-gate@sha256:3e7a9c1d5b2f8e4a6c0d9b7e5f3a1c8d6e4b2a0f9c7e5d3b1a8f6e4c2d0b9a7e","imageVersion":"1.0.0","ingress":[{"consumes":"release-gate.http","port":8080,"rule":"consumer"}],"lifecycle":"application","liveness":{"failures":3,"path":"/healthz","period":"10s","port":8080,"timeout":"5s"},"memory":"64Mi","name":"release-gate","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3","frankfurt-contabo-1"]},"readiness":{"failures":3,"path":"/readyz","period":"10s","port":8080,"timeout":"5s"},"replicas":1,"runtime":"none","startup":{"failures":2,"path":"/healthz","period":"5s","port":8080,"timeout":"5s"},"surfaces":[{"name":"http","port":8080}],"switchover":"rolling","uid":1000}],"project":"delivery","provenance":{"inputDigests":[{"digest":"sha256:ebb5134deeb55f320e28609054a0aca85a359f3869501a3a8b7560d7d05a4277","input":"intent-fragment","name":"delivery"},{"digest":"sha256:9a29fae42c0c6cd4f9824d7af3d9ecc68c068a3a00d1f9488bfbb70c882ea565","input":"intent-fragment","name":"edge"},{"digest":"sha256:9de2ffb5ab977b42af31000e571e8042380d213ed480defdff50eef41c08e68c","input":"intent-fragment","name":"identity"},{"digest":"sha256:35d5e3643093422537ea72def0f4b19e93ed319a2d0192fc7846342970ce8797","input":"intent-fragment","name":"notes"},{"digest":"sha256:84b3b1a8e81fc41b549a34715713cae3354cfc475e9add64d322b7c631f5b816","input":"intent-fragment","name":"observability"},{"digest":"sha256:31309996835674cc4f037f8e19897ddbe4a7fa8a487a3efc613e251caeed0853","input":"intent-fragment","name":"secrets"},{"digest":"sha256:9f60e75b7b449edb3e0f44485a7ed8fa423a5436ab2b4ca9a1df14bafe76beea","input":"platform-intent","name":"jorisjonkers.dev"},{"digest":"sha256:a989e80337efb294fa1a7175b3ad3eea8bed9bf13fbd503a26691c9265575fc2","input":"node-contract","name":"production"},{"digest":"sha256:179f17834dd006548bfdbdd623e8f74b25be6eedde214f8e00e8cb4295807d57","input":"images-lock","name":"estate"},{"digest":"sha256:202e5e2a22ab20cfe5b6e85cbd8139cb13ed3b199b2f1af4380068d21d54ff1f","input":"cluster-state","name":"production"}],"renderHash":"sha256:450201cedc3a21468e702d1cddfde2059cc79304c1b88250ec8eba494c5b5678","schemaPackageIntegrity":"sha256:5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b"},"reconcileUnit":"apps-delivery","revision":"sha256:f0a021a23812d67a973c378888a1cfe367ce166187154f27f0cc86dd1544c467"} \ No newline at end of file diff --git a/spec/v1/examples/delivery/rendered/apps/delivery/release-gate/networkpolicy.yaml b/spec/v1/examples/delivery/rendered/apps/delivery/release-gate/networkpolicy.yaml index 1dbc31fe4..9043497c8 100644 --- a/spec/v1/examples/delivery/rendered/apps/delivery/release-gate/networkpolicy.yaml +++ b/spec/v1/examples/delivery/rendered/apps/delivery/release-gate/networkpolicy.yaml @@ -39,6 +39,13 @@ spec: port: 53 - protocol: TCP port: 53 + - to: + - namespaceSelector: + matchLabels: + app.kubernetes.io/managed-by: deploy-kit + podSelector: + matchLabels: + gated.deploy-kit.jorisjonkers.dev/release-gate: "true" - to: - ipBlock: cidr: 10.43.0.1/32 diff --git a/spec/v1/examples/identity/expected/resolved.grafana.json b/spec/v1/examples/identity/expected/resolved.grafana.json index fa6ed612d..815a974d7 100644 --- a/spec/v1/examples/identity/expected/resolved.grafana.json +++ b/spec/v1/examples/identity/expected/resolved.grafana.json @@ -1 +1 @@ -{"apiVersion":"resolved.jorisjonkers.dev/v1","client":{"credential":"secret","description":"Metrics and dashboards","host":"grafana.jorisjonkers.dev","iconUrl":"https://grafana.jorisjonkers.dev/public/img/grafana_icon.svg","label":"Grafana","launchUrl":"https://grafana.jorisjonkers.dev/","redirectUris":["https://grafana.jorisjonkers.dev/login/generic_oauth"],"roles":["user","viewer","editor","admin"],"secretPath":"secret/data/identity-system/grafana/client","signIn":"native"},"exposure":[{"certificates":"acme","host":"grafana.jorisjonkers.dev","listener":"tls","name":"public","proxy":{"application":"traefik-public","namespace":"edge-system"},"routes":[{"audience":"authenticated","match":"prefix","middleware":[{"kind":"security-headers"}],"path":"/","precedence":1,"process":"grafana","surface":"http"}],"tier":"public-frankfurt"}],"id":"grafana","kind":"ResolvedApplication","namespace":"identity-system","processes":[{"cpu":"50m","cutover":"continuous","deadline":"210s","egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"},{"namespace":"secrets-system","port":8200,"process":"vault","rule":"secret-store"}],"environment":[{"name":"GF_AUTH_DISABLE_LOGIN_FORM","value":"true"},{"name":"GF_AUTH_GENERIC_OAUTH_API_URL","value":"https://id.jorisjonkers.dev/userinfo"},{"name":"GF_AUTH_GENERIC_OAUTH_AUTH_URL","value":"https://id.jorisjonkers.dev/oauth2/authorize"},{"name":"GF_AUTH_GENERIC_OAUTH_CLIENT_ID","value":"grafana"},{"name":"GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET","secret":{"key":"secret","path":"secret/data/identity-system/grafana/client"}},{"name":"GF_AUTH_GENERIC_OAUTH_ENABLED","value":"true"},{"name":"GF_AUTH_GENERIC_OAUTH_NAME","value":"jorisjonkers.dev"},{"name":"GF_AUTH_GENERIC_OAUTH_SCOPES","value":"openid,profile,email"},{"name":"GF_AUTH_GENERIC_OAUTH_TOKEN_URL","value":"https://id.jorisjonkers.dev/oauth2/token"},{"name":"GF_SERVER_ROOT_URL","value":"https://grafana.jorisjonkers.dev"}],"gid":472,"hardening":"restricted","identity":"grafana","identityToken":false,"image":"docker.io/grafana/grafana@sha256:2c4e6a8b0d2f4c6e8a0b2d4f6c8e0a2b4d6f8c0e2a4b6d8f0c2e4a6b8d0f2c4e","imageVersion":"12.2.0","ingress":[{"namespace":"edge-system","port":3000,"process":"traefik-public","rule":"tier-proxy"},{"consumes":"grafana.http","port":3000,"rule":"consumer"}],"lifecycle":"application","memory":"256Mi","name":"grafana","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3","frankfurt-contabo-1"]},"readiness":{"failures":3,"path":"/api/health","period":"10s","port":3000,"timeout":"5s"},"replicas":1,"runtime":"none","secrets":[{"access":"read","delivery":"env","destination":"grafana-identity-system-grafana-client","keys":["id","secret"],"path":"secret/data/identity-system/grafana/client","restartTargets":["grafana"]}],"surfaces":[{"name":"http","port":3000}],"switchover":"blue-green","uid":472,"writablePaths":[{"path":"/var/lib/grafana","size":"64Mi"}]}],"project":"identity","provenance":{"inputDigests":[{"digest":"sha256:ebb5134deeb55f320e28609054a0aca85a359f3869501a3a8b7560d7d05a4277","input":"intent-fragment","name":"delivery"},{"digest":"sha256:9a29fae42c0c6cd4f9824d7af3d9ecc68c068a3a00d1f9488bfbb70c882ea565","input":"intent-fragment","name":"edge"},{"digest":"sha256:9de2ffb5ab977b42af31000e571e8042380d213ed480defdff50eef41c08e68c","input":"intent-fragment","name":"identity"},{"digest":"sha256:35d5e3643093422537ea72def0f4b19e93ed319a2d0192fc7846342970ce8797","input":"intent-fragment","name":"notes"},{"digest":"sha256:84b3b1a8e81fc41b549a34715713cae3354cfc475e9add64d322b7c631f5b816","input":"intent-fragment","name":"observability"},{"digest":"sha256:31309996835674cc4f037f8e19897ddbe4a7fa8a487a3efc613e251caeed0853","input":"intent-fragment","name":"secrets"},{"digest":"sha256:9f60e75b7b449edb3e0f44485a7ed8fa423a5436ab2b4ca9a1df14bafe76beea","input":"platform-intent","name":"jorisjonkers.dev"},{"digest":"sha256:a989e80337efb294fa1a7175b3ad3eea8bed9bf13fbd503a26691c9265575fc2","input":"node-contract","name":"production"},{"digest":"sha256:179f17834dd006548bfdbdd623e8f74b25be6eedde214f8e00e8cb4295807d57","input":"images-lock","name":"estate"},{"digest":"sha256:202e5e2a22ab20cfe5b6e85cbd8139cb13ed3b199b2f1af4380068d21d54ff1f","input":"cluster-state","name":"production"}],"renderHash":"sha256:450201cedc3a21468e702d1cddfde2059cc79304c1b88250ec8eba494c5b5678","schemaPackageIntegrity":"sha256:5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b"},"reconcileAfter":["apps-edge","apps-secrets"],"reconcileUnit":"apps-identity","releaseGate":{"analysis":{"errorRate":"1%","interval":"30s","iterations":4,"latency":"500ms","threshold":3},"deadline":"210s","endpoint":"http://release-gate.delivery-system.svc.cluster.local:8080","members":[{"process":"grafana","readiness":{"path":"/api/health","port":3000}}],"protectedPath":"/api/user"},"revision":"sha256:1cebd3ade7af52f394bef3f99c64f5e188e895345bc91a53c24b4e37dfa3cf3f","secretStore":"http://vault.secrets-system.svc.cluster.local:8200"} \ No newline at end of file +{"apiVersion":"resolved.jorisjonkers.dev/v1","client":{"credential":"secret","description":"Metrics and dashboards","host":"grafana.jorisjonkers.dev","iconUrl":"https://grafana.jorisjonkers.dev/public/img/grafana_icon.svg","label":"Grafana","launchUrl":"https://grafana.jorisjonkers.dev/","redirectUris":["https://grafana.jorisjonkers.dev/login/generic_oauth"],"roles":["user","viewer","editor","admin"],"secretPath":"secret/data/identity-system/grafana/client","signIn":"native"},"exposure":[{"certificates":"acme","host":"grafana.jorisjonkers.dev","listener":"tls","name":"public","proxy":{"application":"traefik-public","namespace":"edge-system"},"routes":[{"audience":"authenticated","match":"prefix","middleware":[{"kind":"security-headers"}],"path":"/","precedence":1,"process":"grafana","surface":"http"}],"tier":"public-frankfurt"}],"id":"grafana","kind":"ResolvedApplication","namespace":"identity-system","processes":[{"cpu":"50m","cutover":"continuous","deadline":"210s","egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"},{"namespace":"secrets-system","port":8200,"process":"vault","rule":"secret-store"}],"environment":[{"name":"GF_AUTH_DISABLE_LOGIN_FORM","value":"true"},{"name":"GF_AUTH_GENERIC_OAUTH_API_URL","value":"https://id.jorisjonkers.dev/userinfo"},{"name":"GF_AUTH_GENERIC_OAUTH_AUTH_URL","value":"https://id.jorisjonkers.dev/oauth2/authorize"},{"name":"GF_AUTH_GENERIC_OAUTH_CLIENT_ID","value":"grafana"},{"name":"GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET","secret":{"key":"secret","path":"secret/data/identity-system/grafana/client"}},{"name":"GF_AUTH_GENERIC_OAUTH_ENABLED","value":"true"},{"name":"GF_AUTH_GENERIC_OAUTH_NAME","value":"jorisjonkers.dev"},{"name":"GF_AUTH_GENERIC_OAUTH_SCOPES","value":"openid,profile,email"},{"name":"GF_AUTH_GENERIC_OAUTH_TOKEN_URL","value":"https://id.jorisjonkers.dev/oauth2/token"},{"name":"GF_SERVER_ROOT_URL","value":"https://grafana.jorisjonkers.dev"}],"gid":472,"hardening":"restricted","identity":"grafana","identityToken":false,"image":"docker.io/grafana/grafana@sha256:2c4e6a8b0d2f4c6e8a0b2d4f6c8e0a2b4d6f8c0e2a4b6d8f0c2e4a6b8d0f2c4e","imageVersion":"12.2.0","ingress":[{"namespace":"edge-system","port":3000,"process":"traefik-public","rule":"tier-proxy"},{"consumes":"grafana.http","port":3000,"rule":"consumer"},{"namespace":"delivery-system","port":3000,"process":"release-gate","rule":"release-gate"}],"lifecycle":"application","memory":"256Mi","name":"grafana","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3","frankfurt-contabo-1"]},"readiness":{"failures":3,"path":"/api/health","period":"10s","port":3000,"timeout":"5s"},"replicas":1,"runtime":"none","secrets":[{"access":"read","delivery":"env","destination":"grafana-identity-system-grafana-client","keys":["id","secret"],"path":"secret/data/identity-system/grafana/client","restartTargets":["grafana"]}],"surfaces":[{"name":"http","port":3000}],"switchover":"blue-green","uid":472,"writablePaths":[{"path":"/var/lib/grafana","size":"64Mi"}]}],"project":"identity","provenance":{"inputDigests":[{"digest":"sha256:ebb5134deeb55f320e28609054a0aca85a359f3869501a3a8b7560d7d05a4277","input":"intent-fragment","name":"delivery"},{"digest":"sha256:9a29fae42c0c6cd4f9824d7af3d9ecc68c068a3a00d1f9488bfbb70c882ea565","input":"intent-fragment","name":"edge"},{"digest":"sha256:9de2ffb5ab977b42af31000e571e8042380d213ed480defdff50eef41c08e68c","input":"intent-fragment","name":"identity"},{"digest":"sha256:35d5e3643093422537ea72def0f4b19e93ed319a2d0192fc7846342970ce8797","input":"intent-fragment","name":"notes"},{"digest":"sha256:84b3b1a8e81fc41b549a34715713cae3354cfc475e9add64d322b7c631f5b816","input":"intent-fragment","name":"observability"},{"digest":"sha256:31309996835674cc4f037f8e19897ddbe4a7fa8a487a3efc613e251caeed0853","input":"intent-fragment","name":"secrets"},{"digest":"sha256:9f60e75b7b449edb3e0f44485a7ed8fa423a5436ab2b4ca9a1df14bafe76beea","input":"platform-intent","name":"jorisjonkers.dev"},{"digest":"sha256:a989e80337efb294fa1a7175b3ad3eea8bed9bf13fbd503a26691c9265575fc2","input":"node-contract","name":"production"},{"digest":"sha256:179f17834dd006548bfdbdd623e8f74b25be6eedde214f8e00e8cb4295807d57","input":"images-lock","name":"estate"},{"digest":"sha256:202e5e2a22ab20cfe5b6e85cbd8139cb13ed3b199b2f1af4380068d21d54ff1f","input":"cluster-state","name":"production"}],"renderHash":"sha256:450201cedc3a21468e702d1cddfde2059cc79304c1b88250ec8eba494c5b5678","schemaPackageIntegrity":"sha256:5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b"},"reconcileAfter":["apps-edge","apps-secrets"],"reconcileUnit":"apps-identity","releaseGate":{"analysis":{"errorRate":"1%","interval":"30s","iterations":4,"latency":"500ms","threshold":3},"deadline":"210s","endpoint":"http://release-gate.delivery-system.svc.cluster.local:8080","members":[{"process":"grafana","readiness":{"path":"/api/health","port":3000}}],"protectedPath":"/api/user"},"revision":"sha256:d975601386366257bbd05e11266a79265d9582ea6a59c0327641795f2c93deba","secretStore":"http://vault.secrets-system.svc.cluster.local:8200"} \ No newline at end of file diff --git a/spec/v1/examples/identity/expected/resolved.identity.json b/spec/v1/examples/identity/expected/resolved.identity.json index b316e80d0..ad90b5769 100644 --- a/spec/v1/examples/identity/expected/resolved.identity.json +++ b/spec/v1/examples/identity/expected/resolved.identity.json @@ -1 +1 @@ -{"apiVersion":"resolved.jorisjonkers.dev/v1","exposure":[{"certificates":"acme","host":"id.jorisjonkers.dev","listener":"tls","name":"public","proxy":{"application":"traefik-public","namespace":"edge-system"},"routes":[{"audience":"anonymous","match":"prefix","middleware":[{"contentPolicy":"strict","kind":"security-headers"}],"path":"/","precedence":1,"process":"identity","surface":"http"}],"tier":"public-frankfurt"}],"id":"identity","kind":"ResolvedApplication","namespace":"identity-system","processes":[{"cpu":"50m","cutover":"continuous","deadline":"210s","egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"},{"namespace":"secrets-system","port":8200,"process":"vault","rule":"secret-store"}],"gid":65532,"hardening":"restricted","identity":"identity","identityToken":true,"image":"ghcr.io/jorisjonkers-dev/identity/identity@sha256:1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a3c","imageVersion":"1.0.0","ingress":[{"namespace":"edge-system","port":8080,"process":"traefik-public","rule":"tier-proxy"},{"consumes":"identity.http","port":8080,"rule":"consumer"}],"lifecycle":"application","liveness":{"failures":3,"path":"/health/live","period":"10s","port":8080,"timeout":"5s"},"memory":"256Mi","name":"identity","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3","frankfurt-contabo-1"]},"readiness":{"failures":3,"path":"/health/ready","period":"10s","port":8080,"timeout":"5s"},"replicas":1,"runtime":"none","secrets":[{"client":"grafana","delivery":"self","paths":[{"allows":["create","read","update"],"path":"secret/data/identity-system/grafana/client"}]}],"startup":{"failures":6,"path":"/health/live","period":"5s","port":8080,"timeout":"5s"},"surfaces":[{"name":"http","port":8080}],"switchover":"blue-green","uid":65532}],"project":"identity","provenance":{"inputDigests":[{"digest":"sha256:ebb5134deeb55f320e28609054a0aca85a359f3869501a3a8b7560d7d05a4277","input":"intent-fragment","name":"delivery"},{"digest":"sha256:9a29fae42c0c6cd4f9824d7af3d9ecc68c068a3a00d1f9488bfbb70c882ea565","input":"intent-fragment","name":"edge"},{"digest":"sha256:9de2ffb5ab977b42af31000e571e8042380d213ed480defdff50eef41c08e68c","input":"intent-fragment","name":"identity"},{"digest":"sha256:35d5e3643093422537ea72def0f4b19e93ed319a2d0192fc7846342970ce8797","input":"intent-fragment","name":"notes"},{"digest":"sha256:84b3b1a8e81fc41b549a34715713cae3354cfc475e9add64d322b7c631f5b816","input":"intent-fragment","name":"observability"},{"digest":"sha256:31309996835674cc4f037f8e19897ddbe4a7fa8a487a3efc613e251caeed0853","input":"intent-fragment","name":"secrets"},{"digest":"sha256:9f60e75b7b449edb3e0f44485a7ed8fa423a5436ab2b4ca9a1df14bafe76beea","input":"platform-intent","name":"jorisjonkers.dev"},{"digest":"sha256:a989e80337efb294fa1a7175b3ad3eea8bed9bf13fbd503a26691c9265575fc2","input":"node-contract","name":"production"},{"digest":"sha256:179f17834dd006548bfdbdd623e8f74b25be6eedde214f8e00e8cb4295807d57","input":"images-lock","name":"estate"},{"digest":"sha256:202e5e2a22ab20cfe5b6e85cbd8139cb13ed3b199b2f1af4380068d21d54ff1f","input":"cluster-state","name":"production"}],"renderHash":"sha256:450201cedc3a21468e702d1cddfde2059cc79304c1b88250ec8eba494c5b5678","schemaPackageIntegrity":"sha256:5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b"},"reconcileAfter":["apps-edge","apps-secrets"],"reconcileUnit":"apps-identity","releaseGate":{"analysis":{"errorRate":"1%","interval":"30s","iterations":4,"latency":"500ms","threshold":3},"deadline":"210s","endpoint":"http://release-gate.delivery-system.svc.cluster.local:8080","members":[{"process":"identity","readiness":{"path":"/health/ready","port":8080}}]},"revision":"sha256:48167384b9f53f61980a4d337516e352c41d2e3e2d710cd90cddcc8e2c6ee94d","secretStore":"http://vault.secrets-system.svc.cluster.local:8200"} \ No newline at end of file +{"apiVersion":"resolved.jorisjonkers.dev/v1","exposure":[{"certificates":"acme","host":"id.jorisjonkers.dev","listener":"tls","name":"public","proxy":{"application":"traefik-public","namespace":"edge-system"},"routes":[{"audience":"anonymous","match":"prefix","middleware":[{"contentPolicy":"strict","kind":"security-headers"}],"path":"/","precedence":1,"process":"identity","surface":"http"}],"tier":"public-frankfurt"}],"id":"identity","kind":"ResolvedApplication","namespace":"identity-system","processes":[{"cpu":"50m","cutover":"continuous","deadline":"210s","egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"},{"namespace":"secrets-system","port":8200,"process":"vault","rule":"secret-store"}],"gid":65532,"hardening":"restricted","identity":"identity","identityToken":true,"image":"ghcr.io/jorisjonkers-dev/identity/identity@sha256:1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a3c","imageVersion":"1.0.0","ingress":[{"namespace":"edge-system","port":8080,"process":"traefik-public","rule":"tier-proxy"},{"consumes":"identity.http","port":8080,"rule":"consumer"},{"namespace":"delivery-system","port":8080,"process":"release-gate","rule":"release-gate"}],"lifecycle":"application","liveness":{"failures":3,"path":"/health/live","period":"10s","port":8080,"timeout":"5s"},"memory":"256Mi","name":"identity","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3","frankfurt-contabo-1"]},"readiness":{"failures":3,"path":"/health/ready","period":"10s","port":8080,"timeout":"5s"},"replicas":1,"runtime":"none","secrets":[{"client":"grafana","delivery":"self","paths":[{"allows":["create","read","update"],"path":"secret/data/identity-system/grafana/client"}]}],"startup":{"failures":6,"path":"/health/live","period":"5s","port":8080,"timeout":"5s"},"surfaces":[{"name":"http","port":8080}],"switchover":"blue-green","uid":65532}],"project":"identity","provenance":{"inputDigests":[{"digest":"sha256:ebb5134deeb55f320e28609054a0aca85a359f3869501a3a8b7560d7d05a4277","input":"intent-fragment","name":"delivery"},{"digest":"sha256:9a29fae42c0c6cd4f9824d7af3d9ecc68c068a3a00d1f9488bfbb70c882ea565","input":"intent-fragment","name":"edge"},{"digest":"sha256:9de2ffb5ab977b42af31000e571e8042380d213ed480defdff50eef41c08e68c","input":"intent-fragment","name":"identity"},{"digest":"sha256:35d5e3643093422537ea72def0f4b19e93ed319a2d0192fc7846342970ce8797","input":"intent-fragment","name":"notes"},{"digest":"sha256:84b3b1a8e81fc41b549a34715713cae3354cfc475e9add64d322b7c631f5b816","input":"intent-fragment","name":"observability"},{"digest":"sha256:31309996835674cc4f037f8e19897ddbe4a7fa8a487a3efc613e251caeed0853","input":"intent-fragment","name":"secrets"},{"digest":"sha256:9f60e75b7b449edb3e0f44485a7ed8fa423a5436ab2b4ca9a1df14bafe76beea","input":"platform-intent","name":"jorisjonkers.dev"},{"digest":"sha256:a989e80337efb294fa1a7175b3ad3eea8bed9bf13fbd503a26691c9265575fc2","input":"node-contract","name":"production"},{"digest":"sha256:179f17834dd006548bfdbdd623e8f74b25be6eedde214f8e00e8cb4295807d57","input":"images-lock","name":"estate"},{"digest":"sha256:202e5e2a22ab20cfe5b6e85cbd8139cb13ed3b199b2f1af4380068d21d54ff1f","input":"cluster-state","name":"production"}],"renderHash":"sha256:450201cedc3a21468e702d1cddfde2059cc79304c1b88250ec8eba494c5b5678","schemaPackageIntegrity":"sha256:5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b"},"reconcileAfter":["apps-edge","apps-secrets"],"reconcileUnit":"apps-identity","releaseGate":{"analysis":{"errorRate":"1%","interval":"30s","iterations":4,"latency":"500ms","threshold":3},"deadline":"210s","endpoint":"http://release-gate.delivery-system.svc.cluster.local:8080","members":[{"process":"identity","readiness":{"path":"/health/ready","port":8080}}]},"revision":"sha256:95b1497778d70241c248922d49f9820c298ba56ec62ddd1a3d21b29db2988689","secretStore":"http://vault.secrets-system.svc.cluster.local:8200"} \ No newline at end of file diff --git a/spec/v1/examples/identity/expected/resolved.portal.json b/spec/v1/examples/identity/expected/resolved.portal.json index d4b735bd4..84c893fec 100644 --- a/spec/v1/examples/identity/expected/resolved.portal.json +++ b/spec/v1/examples/identity/expected/resolved.portal.json @@ -1 +1 @@ -{"apiVersion":"resolved.jorisjonkers.dev/v1","client":{"credential":"pkce","description":"Every application you can open","host":"home.jorisjonkers.dev","label":"Home","launchUrl":"https://home.jorisjonkers.dev/apps","redirectUris":["https://home.jorisjonkers.dev/callback","https://home.jorisjonkers.dev/silent-renew"],"roles":["user"],"signIn":"native"},"exposure":[{"certificates":"acme","host":"home.jorisjonkers.dev","listener":"tls","name":"public","proxy":{"application":"traefik-public","namespace":"edge-system"},"routes":[{"audience":"authenticated","match":"prefix","middleware":[{"kind":"security-headers"}],"path":"/","precedence":1,"process":"portal","surface":"http"}],"tier":"public-frankfurt"}],"id":"portal","kind":"ResolvedApplication","namespace":"identity-system","processes":[{"cpu":"10m","cutover":"continuous","deadline":"720s","egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"}],"gid":101,"hardening":"restricted","identity":"portal","identityToken":false,"image":"ghcr.io/jorisjonkers-dev/identity/portal@sha256:3d5f7b9c1e3a5d7f9b1c3e5a7d9f1b3c5e7a9d1f3b5c7e9a1d3f5b7c9e1a3d5f","imageVersion":"1.0.0","ingress":[{"namespace":"edge-system","port":8080,"process":"traefik-public","rule":"tier-proxy"},{"consumes":"portal.http","port":8080,"rule":"consumer"}],"lifecycle":"application","memory":"64Mi","name":"portal","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3","frankfurt-contabo-1"]},"readiness":{"failures":3,"path":"/","period":"10s","port":8080,"timeout":"5s"},"replicas":1,"runtime":"none","surfaces":[{"name":"http","port":8080}],"switchover":"blue-green","uid":101}],"project":"identity","provenance":{"inputDigests":[{"digest":"sha256:ebb5134deeb55f320e28609054a0aca85a359f3869501a3a8b7560d7d05a4277","input":"intent-fragment","name":"delivery"},{"digest":"sha256:9a29fae42c0c6cd4f9824d7af3d9ecc68c068a3a00d1f9488bfbb70c882ea565","input":"intent-fragment","name":"edge"},{"digest":"sha256:9de2ffb5ab977b42af31000e571e8042380d213ed480defdff50eef41c08e68c","input":"intent-fragment","name":"identity"},{"digest":"sha256:35d5e3643093422537ea72def0f4b19e93ed319a2d0192fc7846342970ce8797","input":"intent-fragment","name":"notes"},{"digest":"sha256:84b3b1a8e81fc41b549a34715713cae3354cfc475e9add64d322b7c631f5b816","input":"intent-fragment","name":"observability"},{"digest":"sha256:31309996835674cc4f037f8e19897ddbe4a7fa8a487a3efc613e251caeed0853","input":"intent-fragment","name":"secrets"},{"digest":"sha256:9f60e75b7b449edb3e0f44485a7ed8fa423a5436ab2b4ca9a1df14bafe76beea","input":"platform-intent","name":"jorisjonkers.dev"},{"digest":"sha256:a989e80337efb294fa1a7175b3ad3eea8bed9bf13fbd503a26691c9265575fc2","input":"node-contract","name":"production"},{"digest":"sha256:179f17834dd006548bfdbdd623e8f74b25be6eedde214f8e00e8cb4295807d57","input":"images-lock","name":"estate"},{"digest":"sha256:202e5e2a22ab20cfe5b6e85cbd8139cb13ed3b199b2f1af4380068d21d54ff1f","input":"cluster-state","name":"production"}],"renderHash":"sha256:450201cedc3a21468e702d1cddfde2059cc79304c1b88250ec8eba494c5b5678","schemaPackageIntegrity":"sha256:5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b"},"reconcileAfter":["apps-edge"],"reconcileUnit":"apps-identity","releaseGate":{"analysis":{"errorRate":"1%","interval":"30s","iterations":4,"latency":"500ms","threshold":3},"deadline":"720s","endpoint":"http://release-gate.delivery-system.svc.cluster.local:8080","members":[{"process":"portal","readiness":{"path":"/","port":8080}}],"protectedPath":"/api/me"},"revision":"sha256:86bcd4b890a455119e66e576b32661c625cd9de4c9f4cabbbe9e306793d0e8b6"} \ No newline at end of file +{"apiVersion":"resolved.jorisjonkers.dev/v1","client":{"credential":"pkce","description":"Every application you can open","host":"home.jorisjonkers.dev","label":"Home","launchUrl":"https://home.jorisjonkers.dev/apps","redirectUris":["https://home.jorisjonkers.dev/callback","https://home.jorisjonkers.dev/silent-renew"],"roles":["user"],"signIn":"native"},"exposure":[{"certificates":"acme","host":"home.jorisjonkers.dev","listener":"tls","name":"public","proxy":{"application":"traefik-public","namespace":"edge-system"},"routes":[{"audience":"authenticated","match":"prefix","middleware":[{"kind":"security-headers"}],"path":"/","precedence":1,"process":"portal","surface":"http"}],"tier":"public-frankfurt"}],"id":"portal","kind":"ResolvedApplication","namespace":"identity-system","processes":[{"cpu":"10m","cutover":"continuous","deadline":"720s","egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"}],"gid":101,"hardening":"restricted","identity":"portal","identityToken":false,"image":"ghcr.io/jorisjonkers-dev/identity/portal@sha256:3d5f7b9c1e3a5d7f9b1c3e5a7d9f1b3c5e7a9d1f3b5c7e9a1d3f5b7c9e1a3d5f","imageVersion":"1.0.0","ingress":[{"namespace":"edge-system","port":8080,"process":"traefik-public","rule":"tier-proxy"},{"consumes":"portal.http","port":8080,"rule":"consumer"},{"namespace":"delivery-system","port":8080,"process":"release-gate","rule":"release-gate"}],"lifecycle":"application","memory":"64Mi","name":"portal","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3","frankfurt-contabo-1"]},"readiness":{"failures":3,"path":"/","period":"10s","port":8080,"timeout":"5s"},"replicas":1,"runtime":"none","surfaces":[{"name":"http","port":8080}],"switchover":"blue-green","uid":101}],"project":"identity","provenance":{"inputDigests":[{"digest":"sha256:ebb5134deeb55f320e28609054a0aca85a359f3869501a3a8b7560d7d05a4277","input":"intent-fragment","name":"delivery"},{"digest":"sha256:9a29fae42c0c6cd4f9824d7af3d9ecc68c068a3a00d1f9488bfbb70c882ea565","input":"intent-fragment","name":"edge"},{"digest":"sha256:9de2ffb5ab977b42af31000e571e8042380d213ed480defdff50eef41c08e68c","input":"intent-fragment","name":"identity"},{"digest":"sha256:35d5e3643093422537ea72def0f4b19e93ed319a2d0192fc7846342970ce8797","input":"intent-fragment","name":"notes"},{"digest":"sha256:84b3b1a8e81fc41b549a34715713cae3354cfc475e9add64d322b7c631f5b816","input":"intent-fragment","name":"observability"},{"digest":"sha256:31309996835674cc4f037f8e19897ddbe4a7fa8a487a3efc613e251caeed0853","input":"intent-fragment","name":"secrets"},{"digest":"sha256:9f60e75b7b449edb3e0f44485a7ed8fa423a5436ab2b4ca9a1df14bafe76beea","input":"platform-intent","name":"jorisjonkers.dev"},{"digest":"sha256:a989e80337efb294fa1a7175b3ad3eea8bed9bf13fbd503a26691c9265575fc2","input":"node-contract","name":"production"},{"digest":"sha256:179f17834dd006548bfdbdd623e8f74b25be6eedde214f8e00e8cb4295807d57","input":"images-lock","name":"estate"},{"digest":"sha256:202e5e2a22ab20cfe5b6e85cbd8139cb13ed3b199b2f1af4380068d21d54ff1f","input":"cluster-state","name":"production"}],"renderHash":"sha256:450201cedc3a21468e702d1cddfde2059cc79304c1b88250ec8eba494c5b5678","schemaPackageIntegrity":"sha256:5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b"},"reconcileAfter":["apps-edge"],"reconcileUnit":"apps-identity","releaseGate":{"analysis":{"errorRate":"1%","interval":"30s","iterations":4,"latency":"500ms","threshold":3},"deadline":"720s","endpoint":"http://release-gate.delivery-system.svc.cluster.local:8080","members":[{"process":"portal","readiness":{"path":"/","port":8080}}],"protectedPath":"/api/me"},"revision":"sha256:61dda23ed8351a52286ac9f6efe625418b34925d93c29f09e019152e30fb517d"} \ No newline at end of file diff --git a/spec/v1/examples/identity/expected/resolved.whoami.json b/spec/v1/examples/identity/expected/resolved.whoami.json index c45ad688c..9a0647ad2 100644 --- a/spec/v1/examples/identity/expected/resolved.whoami.json +++ b/spec/v1/examples/identity/expected/resolved.whoami.json @@ -1 +1 @@ -{"apiVersion":"resolved.jorisjonkers.dev/v1","client":{"host":"whoami.jorisjonkers.dev","iconUrl":"https://whoami.jorisjonkers.dev/favicon.ico","label":"Who am I","launchUrl":"https://whoami.jorisjonkers.dev/","roles":["user"],"signIn":"gated"},"exposure":[{"certificates":"acme","host":"whoami.jorisjonkers.dev","listener":"tls","name":"public","proxy":{"application":"traefik-public","namespace":"edge-system"},"routes":[{"audience":"authenticated","match":"prefix","middleware":[{"endpoint":"http://identity.identity-system.svc.cluster.local:8080/gate/forward","kind":"forward-auth"},{"kind":"security-headers"}],"path":"/","precedence":3,"process":"whoami","surface":"http"},{"audience":"anonymous","match":"exact","middleware":[{"kind":"security-headers"}],"path":"/health","precedence":1,"process":"whoami","surface":"http"},{"audience":"authenticated","gate":{"name":"identity","namespace":"identity-system","port":8080},"match":"prefix","middleware":[{"kind":"security-headers"}],"path":"/.auth/","precedence":2}],"tier":"public-frankfurt"}],"id":"whoami","kind":"ResolvedApplication","namespace":"identity-system","processes":[{"cpu":"10m","cutover":"continuous","deadline":"720s","egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"}],"gid":65532,"hardening":"restricted","identity":"whoami","identityToken":false,"image":"docker.io/traefik/whoami@sha256:4e6a8c0d2f4b6e8a0c2d4f6b8e0a2c4d6f8b0e2a4c6d8f0b2e4a6c8d0f2b4e6a","imageVersion":"v1.11.0","ingress":[{"namespace":"edge-system","port":8080,"process":"traefik-public","rule":"tier-proxy"},{"consumes":"whoami.http","port":8080,"rule":"consumer"}],"lifecycle":"application","memory":"32Mi","name":"whoami","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3","frankfurt-contabo-1"]},"readiness":{"failures":3,"path":"/health","period":"10s","port":8080,"timeout":"5s"},"replicas":1,"runtime":"none","surfaces":[{"name":"http","port":8080}],"switchover":"blue-green","uid":65532}],"project":"identity","provenance":{"inputDigests":[{"digest":"sha256:ebb5134deeb55f320e28609054a0aca85a359f3869501a3a8b7560d7d05a4277","input":"intent-fragment","name":"delivery"},{"digest":"sha256:9a29fae42c0c6cd4f9824d7af3d9ecc68c068a3a00d1f9488bfbb70c882ea565","input":"intent-fragment","name":"edge"},{"digest":"sha256:9de2ffb5ab977b42af31000e571e8042380d213ed480defdff50eef41c08e68c","input":"intent-fragment","name":"identity"},{"digest":"sha256:35d5e3643093422537ea72def0f4b19e93ed319a2d0192fc7846342970ce8797","input":"intent-fragment","name":"notes"},{"digest":"sha256:84b3b1a8e81fc41b549a34715713cae3354cfc475e9add64d322b7c631f5b816","input":"intent-fragment","name":"observability"},{"digest":"sha256:31309996835674cc4f037f8e19897ddbe4a7fa8a487a3efc613e251caeed0853","input":"intent-fragment","name":"secrets"},{"digest":"sha256:9f60e75b7b449edb3e0f44485a7ed8fa423a5436ab2b4ca9a1df14bafe76beea","input":"platform-intent","name":"jorisjonkers.dev"},{"digest":"sha256:a989e80337efb294fa1a7175b3ad3eea8bed9bf13fbd503a26691c9265575fc2","input":"node-contract","name":"production"},{"digest":"sha256:179f17834dd006548bfdbdd623e8f74b25be6eedde214f8e00e8cb4295807d57","input":"images-lock","name":"estate"},{"digest":"sha256:202e5e2a22ab20cfe5b6e85cbd8139cb13ed3b199b2f1af4380068d21d54ff1f","input":"cluster-state","name":"production"}],"renderHash":"sha256:450201cedc3a21468e702d1cddfde2059cc79304c1b88250ec8eba494c5b5678","schemaPackageIntegrity":"sha256:5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b"},"reconcileAfter":["apps-edge"],"reconcileUnit":"apps-identity","releaseGate":{"analysis":{"errorRate":"1%","interval":"30s","iterations":4,"latency":"500ms","threshold":3},"deadline":"720s","endpoint":"http://release-gate.delivery-system.svc.cluster.local:8080","members":[{"process":"whoami","readiness":{"path":"/health","port":8080}}]},"revision":"sha256:a70708253ea6e3042f5bd24a23a25352808eeb837740ed17218c9a838f662aa6"} \ No newline at end of file +{"apiVersion":"resolved.jorisjonkers.dev/v1","client":{"host":"whoami.jorisjonkers.dev","iconUrl":"https://whoami.jorisjonkers.dev/favicon.ico","label":"Who am I","launchUrl":"https://whoami.jorisjonkers.dev/","roles":["user"],"signIn":"gated"},"exposure":[{"certificates":"acme","host":"whoami.jorisjonkers.dev","listener":"tls","name":"public","proxy":{"application":"traefik-public","namespace":"edge-system"},"routes":[{"audience":"authenticated","match":"prefix","middleware":[{"endpoint":"http://identity.identity-system.svc.cluster.local:8080/gate/forward","kind":"forward-auth"},{"kind":"security-headers"}],"path":"/","precedence":3,"process":"whoami","surface":"http"},{"audience":"anonymous","match":"exact","middleware":[{"kind":"security-headers"}],"path":"/health","precedence":1,"process":"whoami","surface":"http"},{"audience":"authenticated","gate":{"name":"identity","namespace":"identity-system","port":8080},"match":"prefix","middleware":[{"kind":"security-headers"}],"path":"/.auth/","precedence":2}],"tier":"public-frankfurt"}],"id":"whoami","kind":"ResolvedApplication","namespace":"identity-system","processes":[{"cpu":"10m","cutover":"continuous","deadline":"720s","egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"}],"gid":65532,"hardening":"restricted","identity":"whoami","identityToken":false,"image":"docker.io/traefik/whoami@sha256:4e6a8c0d2f4b6e8a0c2d4f6b8e0a2c4d6f8b0e2a4c6d8f0b2e4a6c8d0f2b4e6a","imageVersion":"v1.11.0","ingress":[{"namespace":"edge-system","port":8080,"process":"traefik-public","rule":"tier-proxy"},{"consumes":"whoami.http","port":8080,"rule":"consumer"},{"namespace":"delivery-system","port":8080,"process":"release-gate","rule":"release-gate"}],"lifecycle":"application","memory":"32Mi","name":"whoami","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3","frankfurt-contabo-1"]},"readiness":{"failures":3,"path":"/health","period":"10s","port":8080,"timeout":"5s"},"replicas":1,"runtime":"none","surfaces":[{"name":"http","port":8080}],"switchover":"blue-green","uid":65532}],"project":"identity","provenance":{"inputDigests":[{"digest":"sha256:ebb5134deeb55f320e28609054a0aca85a359f3869501a3a8b7560d7d05a4277","input":"intent-fragment","name":"delivery"},{"digest":"sha256:9a29fae42c0c6cd4f9824d7af3d9ecc68c068a3a00d1f9488bfbb70c882ea565","input":"intent-fragment","name":"edge"},{"digest":"sha256:9de2ffb5ab977b42af31000e571e8042380d213ed480defdff50eef41c08e68c","input":"intent-fragment","name":"identity"},{"digest":"sha256:35d5e3643093422537ea72def0f4b19e93ed319a2d0192fc7846342970ce8797","input":"intent-fragment","name":"notes"},{"digest":"sha256:84b3b1a8e81fc41b549a34715713cae3354cfc475e9add64d322b7c631f5b816","input":"intent-fragment","name":"observability"},{"digest":"sha256:31309996835674cc4f037f8e19897ddbe4a7fa8a487a3efc613e251caeed0853","input":"intent-fragment","name":"secrets"},{"digest":"sha256:9f60e75b7b449edb3e0f44485a7ed8fa423a5436ab2b4ca9a1df14bafe76beea","input":"platform-intent","name":"jorisjonkers.dev"},{"digest":"sha256:a989e80337efb294fa1a7175b3ad3eea8bed9bf13fbd503a26691c9265575fc2","input":"node-contract","name":"production"},{"digest":"sha256:179f17834dd006548bfdbdd623e8f74b25be6eedde214f8e00e8cb4295807d57","input":"images-lock","name":"estate"},{"digest":"sha256:202e5e2a22ab20cfe5b6e85cbd8139cb13ed3b199b2f1af4380068d21d54ff1f","input":"cluster-state","name":"production"}],"renderHash":"sha256:450201cedc3a21468e702d1cddfde2059cc79304c1b88250ec8eba494c5b5678","schemaPackageIntegrity":"sha256:5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b"},"reconcileAfter":["apps-edge"],"reconcileUnit":"apps-identity","releaseGate":{"analysis":{"errorRate":"1%","interval":"30s","iterations":4,"latency":"500ms","threshold":3},"deadline":"720s","endpoint":"http://release-gate.delivery-system.svc.cluster.local:8080","members":[{"process":"whoami","readiness":{"path":"/health","port":8080}}]},"revision":"sha256:0cc8c3c12f9f6f623505c3eb07a16f4d6a17786619fe8c4e7ff44de69d2c8ee5"} \ No newline at end of file diff --git a/spec/v1/examples/identity/rendered/apps/identity/grafana/canary.yaml b/spec/v1/examples/identity/rendered/apps/identity/grafana/canary.yaml index 294168efe..a4001a400 100644 --- a/spec/v1/examples/identity/rendered/apps/identity/grafana/canary.yaml +++ b/spec/v1/examples/identity/rendered/apps/identity/grafana/canary.yaml @@ -33,7 +33,7 @@ spec: metadata: application: grafana process: grafana - revision: sha256:1cebd3ade7af52f394bef3f99c64f5e188e895345bc91a53c24b4e37dfa3cf3f + revision: sha256:d975601386366257bbd05e11266a79265d9582ea6a59c0327641795f2c93deba paths: / readiness: /api/health hostnames: grafana.jorisjonkers.dev @@ -45,7 +45,7 @@ spec: metadata: application: grafana process: grafana - revision: sha256:1cebd3ade7af52f394bef3f99c64f5e188e895345bc91a53c24b4e37dfa3cf3f + revision: sha256:d975601386366257bbd05e11266a79265d9582ea6a59c0327641795f2c93deba paths: / readiness: /api/health hostnames: grafana.jorisjonkers.dev @@ -57,7 +57,7 @@ spec: metadata: application: grafana process: grafana - revision: sha256:1cebd3ade7af52f394bef3f99c64f5e188e895345bc91a53c24b4e37dfa3cf3f + revision: sha256:d975601386366257bbd05e11266a79265d9582ea6a59c0327641795f2c93deba paths: / readiness: /api/health hostnames: grafana.jorisjonkers.dev diff --git a/spec/v1/examples/identity/rendered/apps/identity/grafana/networkpolicy.yaml b/spec/v1/examples/identity/rendered/apps/identity/grafana/networkpolicy.yaml index 5af587180..c0d322af5 100644 --- a/spec/v1/examples/identity/rendered/apps/identity/grafana/networkpolicy.yaml +++ b/spec/v1/examples/identity/rendered/apps/identity/grafana/networkpolicy.yaml @@ -39,6 +39,16 @@ spec: ports: - protocol: TCP port: 3000 + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: delivery-system + podSelector: + matchLabels: + app.kubernetes.io/instance: release-gate + ports: + - protocol: TCP + port: 3000 egress: - to: - namespaceSelector: diff --git a/spec/v1/examples/identity/rendered/apps/identity/grafana/vso.yaml b/spec/v1/examples/identity/rendered/apps/identity/grafana/vso.yaml index aa5eb85fc..a2160a906 100644 --- a/spec/v1/examples/identity/rendered/apps/identity/grafana/vso.yaml +++ b/spec/v1/examples/identity/rendered/apps/identity/grafana/vso.yaml @@ -12,7 +12,7 @@ metadata: app.kubernetes.io/managed-by: deploy-kit app.kubernetes.io/component: none annotations: - estate.jorisjonkers.dev/revision: sha256:1cebd3ade7af52f394bef3f99c64f5e188e895345bc91a53c24b4e37dfa3cf3f + estate.jorisjonkers.dev/revision: sha256:d975601386366257bbd05e11266a79265d9582ea6a59c0327641795f2c93deba kustomize.toolkit.fluxcd.io/prune: disabled spec: vaultConnectionRef: secret-store @@ -34,7 +34,7 @@ metadata: app.kubernetes.io/managed-by: deploy-kit app.kubernetes.io/component: none annotations: - estate.jorisjonkers.dev/revision: sha256:1cebd3ade7af52f394bef3f99c64f5e188e895345bc91a53c24b4e37dfa3cf3f + estate.jorisjonkers.dev/revision: sha256:d975601386366257bbd05e11266a79265d9582ea6a59c0327641795f2c93deba kustomize.toolkit.fluxcd.io/prune: disabled spec: vaultAuthRef: grafana diff --git a/spec/v1/examples/identity/rendered/apps/identity/grafana/workload.yaml b/spec/v1/examples/identity/rendered/apps/identity/grafana/workload.yaml index 63eb7f897..1ebdb0727 100644 --- a/spec/v1/examples/identity/rendered/apps/identity/grafana/workload.yaml +++ b/spec/v1/examples/identity/rendered/apps/identity/grafana/workload.yaml @@ -30,6 +30,7 @@ spec: app.kubernetes.io/part-of: grafana app.kubernetes.io/managed-by: deploy-kit app.kubernetes.io/component: none + gated.deploy-kit.jorisjonkers.dev/release-gate: "true" annotations: image.jorisjonkers.dev/grafana: 12.2.0 spec: diff --git a/spec/v1/examples/identity/rendered/apps/identity/identity/canary.yaml b/spec/v1/examples/identity/rendered/apps/identity/identity/canary.yaml index 61e93c64c..b36917ce8 100644 --- a/spec/v1/examples/identity/rendered/apps/identity/identity/canary.yaml +++ b/spec/v1/examples/identity/rendered/apps/identity/identity/canary.yaml @@ -33,7 +33,7 @@ spec: metadata: application: identity process: identity - revision: sha256:48167384b9f53f61980a4d337516e352c41d2e3e2d710cd90cddcc8e2c6ee94d + revision: sha256:95b1497778d70241c248922d49f9820c298ba56ec62ddd1a3d21b29db2988689 paths: / readiness: /health/ready hostnames: id.jorisjonkers.dev @@ -45,7 +45,7 @@ spec: metadata: application: identity process: identity - revision: sha256:48167384b9f53f61980a4d337516e352c41d2e3e2d710cd90cddcc8e2c6ee94d + revision: sha256:95b1497778d70241c248922d49f9820c298ba56ec62ddd1a3d21b29db2988689 paths: / readiness: /health/ready hostnames: id.jorisjonkers.dev @@ -57,7 +57,7 @@ spec: metadata: application: identity process: identity - revision: sha256:48167384b9f53f61980a4d337516e352c41d2e3e2d710cd90cddcc8e2c6ee94d + revision: sha256:95b1497778d70241c248922d49f9820c298ba56ec62ddd1a3d21b29db2988689 paths: / readiness: /health/ready hostnames: id.jorisjonkers.dev diff --git a/spec/v1/examples/identity/rendered/apps/identity/identity/networkpolicy.yaml b/spec/v1/examples/identity/rendered/apps/identity/identity/networkpolicy.yaml index 1a156c0d7..f4bfe8d4e 100644 --- a/spec/v1/examples/identity/rendered/apps/identity/identity/networkpolicy.yaml +++ b/spec/v1/examples/identity/rendered/apps/identity/identity/networkpolicy.yaml @@ -39,6 +39,16 @@ spec: ports: - protocol: TCP port: 8080 + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: delivery-system + podSelector: + matchLabels: + app.kubernetes.io/instance: release-gate + ports: + - protocol: TCP + port: 8080 egress: - to: - namespaceSelector: diff --git a/spec/v1/examples/identity/rendered/apps/identity/identity/workload.yaml b/spec/v1/examples/identity/rendered/apps/identity/identity/workload.yaml index 37aa6afe4..ed2d74bdb 100644 --- a/spec/v1/examples/identity/rendered/apps/identity/identity/workload.yaml +++ b/spec/v1/examples/identity/rendered/apps/identity/identity/workload.yaml @@ -30,6 +30,7 @@ spec: app.kubernetes.io/part-of: identity app.kubernetes.io/managed-by: deploy-kit app.kubernetes.io/component: none + gated.deploy-kit.jorisjonkers.dev/release-gate: "true" annotations: image.jorisjonkers.dev/identity: 1.0.0 spec: diff --git a/spec/v1/examples/identity/rendered/apps/identity/portal/canary.yaml b/spec/v1/examples/identity/rendered/apps/identity/portal/canary.yaml index 328f3e157..44164578e 100644 --- a/spec/v1/examples/identity/rendered/apps/identity/portal/canary.yaml +++ b/spec/v1/examples/identity/rendered/apps/identity/portal/canary.yaml @@ -33,7 +33,7 @@ spec: metadata: application: portal process: portal - revision: sha256:86bcd4b890a455119e66e576b32661c625cd9de4c9f4cabbbe9e306793d0e8b6 + revision: sha256:61dda23ed8351a52286ac9f6efe625418b34925d93c29f09e019152e30fb517d paths: / readiness: / hostnames: home.jorisjonkers.dev @@ -45,7 +45,7 @@ spec: metadata: application: portal process: portal - revision: sha256:86bcd4b890a455119e66e576b32661c625cd9de4c9f4cabbbe9e306793d0e8b6 + revision: sha256:61dda23ed8351a52286ac9f6efe625418b34925d93c29f09e019152e30fb517d paths: / readiness: / hostnames: home.jorisjonkers.dev @@ -57,7 +57,7 @@ spec: metadata: application: portal process: portal - revision: sha256:86bcd4b890a455119e66e576b32661c625cd9de4c9f4cabbbe9e306793d0e8b6 + revision: sha256:61dda23ed8351a52286ac9f6efe625418b34925d93c29f09e019152e30fb517d paths: / readiness: / hostnames: home.jorisjonkers.dev diff --git a/spec/v1/examples/identity/rendered/apps/identity/portal/networkpolicy.yaml b/spec/v1/examples/identity/rendered/apps/identity/portal/networkpolicy.yaml index d78ab4ade..6cfa89637 100644 --- a/spec/v1/examples/identity/rendered/apps/identity/portal/networkpolicy.yaml +++ b/spec/v1/examples/identity/rendered/apps/identity/portal/networkpolicy.yaml @@ -39,6 +39,16 @@ spec: ports: - protocol: TCP port: 8080 + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: delivery-system + podSelector: + matchLabels: + app.kubernetes.io/instance: release-gate + ports: + - protocol: TCP + port: 8080 egress: - to: - namespaceSelector: diff --git a/spec/v1/examples/identity/rendered/apps/identity/portal/workload.yaml b/spec/v1/examples/identity/rendered/apps/identity/portal/workload.yaml index b9a966c02..64e275230 100644 --- a/spec/v1/examples/identity/rendered/apps/identity/portal/workload.yaml +++ b/spec/v1/examples/identity/rendered/apps/identity/portal/workload.yaml @@ -30,6 +30,7 @@ spec: app.kubernetes.io/part-of: portal app.kubernetes.io/managed-by: deploy-kit app.kubernetes.io/component: none + gated.deploy-kit.jorisjonkers.dev/release-gate: "true" annotations: image.jorisjonkers.dev/portal: 1.0.0 spec: diff --git a/spec/v1/examples/identity/rendered/apps/identity/whoami/canary.yaml b/spec/v1/examples/identity/rendered/apps/identity/whoami/canary.yaml index 681ac07b6..e0fa4ebdc 100644 --- a/spec/v1/examples/identity/rendered/apps/identity/whoami/canary.yaml +++ b/spec/v1/examples/identity/rendered/apps/identity/whoami/canary.yaml @@ -33,7 +33,7 @@ spec: metadata: application: whoami process: whoami - revision: sha256:a70708253ea6e3042f5bd24a23a25352808eeb837740ed17218c9a838f662aa6 + revision: sha256:0cc8c3c12f9f6f623505c3eb07a16f4d6a17786619fe8c4e7ff44de69d2c8ee5 paths: / /health readiness: /health hostnames: whoami.jorisjonkers.dev @@ -45,7 +45,7 @@ spec: metadata: application: whoami process: whoami - revision: sha256:a70708253ea6e3042f5bd24a23a25352808eeb837740ed17218c9a838f662aa6 + revision: sha256:0cc8c3c12f9f6f623505c3eb07a16f4d6a17786619fe8c4e7ff44de69d2c8ee5 paths: / /health readiness: /health hostnames: whoami.jorisjonkers.dev @@ -57,7 +57,7 @@ spec: metadata: application: whoami process: whoami - revision: sha256:a70708253ea6e3042f5bd24a23a25352808eeb837740ed17218c9a838f662aa6 + revision: sha256:0cc8c3c12f9f6f623505c3eb07a16f4d6a17786619fe8c4e7ff44de69d2c8ee5 paths: / /health readiness: /health hostnames: whoami.jorisjonkers.dev diff --git a/spec/v1/examples/identity/rendered/apps/identity/whoami/networkpolicy.yaml b/spec/v1/examples/identity/rendered/apps/identity/whoami/networkpolicy.yaml index a175b9ad3..6b2b90b3b 100644 --- a/spec/v1/examples/identity/rendered/apps/identity/whoami/networkpolicy.yaml +++ b/spec/v1/examples/identity/rendered/apps/identity/whoami/networkpolicy.yaml @@ -39,6 +39,16 @@ spec: ports: - protocol: TCP port: 8080 + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: delivery-system + podSelector: + matchLabels: + app.kubernetes.io/instance: release-gate + ports: + - protocol: TCP + port: 8080 egress: - to: - namespaceSelector: diff --git a/spec/v1/examples/identity/rendered/apps/identity/whoami/workload.yaml b/spec/v1/examples/identity/rendered/apps/identity/whoami/workload.yaml index ee2691264..ae18c95dc 100644 --- a/spec/v1/examples/identity/rendered/apps/identity/whoami/workload.yaml +++ b/spec/v1/examples/identity/rendered/apps/identity/whoami/workload.yaml @@ -30,6 +30,7 @@ spec: app.kubernetes.io/part-of: whoami app.kubernetes.io/managed-by: deploy-kit app.kubernetes.io/component: none + gated.deploy-kit.jorisjonkers.dev/release-gate: "true" annotations: image.jorisjonkers.dev/whoami: v1.11.0 spec: diff --git a/spec/v1/examples/knowledge-platform/expected/resolved.hindsight.json b/spec/v1/examples/knowledge-platform/expected/resolved.hindsight.json index 8fa1f8981..df81ea17b 100644 --- a/spec/v1/examples/knowledge-platform/expected/resolved.hindsight.json +++ b/spec/v1/examples/knowledge-platform/expected/resolved.hindsight.json @@ -1 +1 @@ -{"apiVersion":"resolved.jorisjonkers.dev/v1","exposure":[{"certificates":"acme","host":"memory.jorisjonkers.dev","listener":"tls","name":"console","proxy":{"application":"traefik-public","namespace":"edge-system"},"routes":[{"audience":"authenticated","match":"prefix","middleware":[{"endpoint":"http://identity.identity-system.svc.cluster.local:8080/gate/forward","kind":"forward-auth"},{"contentPolicy":"strict","kind":"security-headers"}],"path":"/","precedence":1,"process":"hindsight-control-plane","surface":"console"}],"tier":"public-frankfurt"},{"certificates":"acme","host":"memory-api.jorisjonkers.dev","listener":"tls","name":"api","proxy":{"application":"traefik-public","namespace":"edge-system"},"routes":[{"audience":"authenticated","match":"prefix","middleware":[{"endpoint":"http://identity.identity-system.svc.cluster.local:8080/gate/forward","kind":"forward-auth"},{"contentPolicy":"strict","kind":"security-headers"}],"path":"/","precedence":1,"process":"hindsight-api","surface":"api"}],"tier":"public-frankfurt"}],"id":"hindsight","kind":"ResolvedApplication","namespace":"knowledge-platform-system","processes":[{"consumes":["platform-postgres.postgres","hindsight-embedding.embeddings"],"cpu":"500m","cutover":"continuous","deadline":"480s","dependencies":[{"address":"platform-postgres:5432","application":"platform-postgres","peers":[{"namespace":"data-system","port":5432,"process":"postgres"}],"surface":"postgres","target":"postgres.data-system.svc.cluster.local"},{"address":"hindsight-tei-embedding:8091","application":"hindsight-embedding","peers":[{"namespace":"knowledge-platform-system","port":8091,"process":"hindsight-tei-embedding"}],"surface":"embeddings"}],"egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"},{"namespace":"secrets-system","port":8200,"process":"vault","rule":"secret-store"}],"environment":[{"name":"DEPLOYMENT_ENVIRONMENT","value":"production"},{"name":"HINDSIGHT_API_EMBEDDINGS_PROVIDER","value":"tei"},{"name":"HINDSIGHT_API_LLM_API_KEY","secret":{"key":"llm-api-key","path":"secret/data/platform/hindsight"}},{"name":"HINDSIGHT_API_LLM_BASE_URL","value":"https://eu.openrouter.ai/api/v1"},{"name":"HINDSIGHT_API_LLM_MODEL","value":"mistralai/mistral-small-2603"},{"name":"HINDSIGHT_API_LLM_PROVIDER","value":"openai"},{"name":"HINDSIGHT_API_PORT","value":"8888"},{"name":"HINDSIGHT_API_WORKER_ENABLED","value":"false"},{"name":"OTEL_EXPORTER_OTLP_ENDPOINT","value":"http://otel-collector.observability-system.svc.cluster.local:4317"},{"name":"OTEL_SERVICE_NAME","value":"hindsight-api"},{"name":"PGPASSWORD","secret":{"key":"password","path":"database/creds/knowledge-platform-owner"}},{"name":"PGUSER","secret":{"key":"username","path":"database/creds/knowledge-platform-owner"}},{"name":"PORT","value":"8888"}],"gid":1000,"hardening":"restricted","identity":"hindsight-api","identityToken":false,"image":"ghcr.io/vectorize-io/hindsight-api@sha256:7b14a1f4062252992d0176758753615e0a2071d9a269995be007be223ab01812","imageVersion":"0.4.0","ingress":[{"namespace":"edge-system","port":8888,"process":"traefik-public","rule":"tier-proxy"},{"consumes":"hindsight.api","port":8888,"rule":"consumer"}],"lifecycle":"application","liveness":{"failures":3,"path":"/health/live","period":"10s","port":8888,"timeout":"5s"},"memory":"1Gi","name":"hindsight-api","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3"]},"readiness":{"failures":3,"path":"/health","period":"10s","port":8888,"timeout":"5s"},"replicas":1,"runtime":"python","secrets":[{"access":"read","delivery":"env","destination":"hindsight-api-platform-hindsight","keys":["llm-api-key"],"path":"secret/data/platform/hindsight","restartTargets":["hindsight-api"]},{"delivery":"env","destination":"hindsight-api-database-creds-knowledge-platform-owner","engine":"database","paths":[{"allows":["read"],"path":"database/creds/knowledge-platform-owner"}],"restartTargets":["hindsight-api"]}],"startup":{"failures":24,"path":"/health/live","period":"5s","port":8888,"timeout":"5s"},"surfaces":[{"name":"api","port":8888}],"switchover":"blue-green","uid":1000},{"consumes":["platform-postgres.postgres","hindsight-embedding.embeddings"],"cpu":"500m","cutover":"continuous","deadline":"480s","dependencies":[{"address":"platform-postgres:5432","application":"platform-postgres","peers":[{"namespace":"data-system","port":5432,"process":"postgres"}],"surface":"postgres","target":"postgres.data-system.svc.cluster.local"},{"address":"hindsight-tei-embedding:8091","application":"hindsight-embedding","peers":[{"namespace":"knowledge-platform-system","port":8091,"process":"hindsight-tei-embedding"}],"surface":"embeddings"}],"egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"},{"namespace":"secrets-system","port":8200,"process":"vault","rule":"secret-store"}],"environment":[{"name":"DEPLOYMENT_ENVIRONMENT","value":"production"},{"name":"HINDSIGHT_API_EMBEDDINGS_PROVIDER","value":"tei"},{"name":"HINDSIGHT_API_LLM_API_KEY","secret":{"key":"llm-api-key","path":"secret/data/platform/hindsight"}},{"name":"HINDSIGHT_API_LLM_BASE_URL","value":"https://eu.openrouter.ai/api/v1"},{"name":"HINDSIGHT_API_LLM_MODEL","value":"mistralai/mistral-small-2603"},{"name":"HINDSIGHT_API_LLM_PROVIDER","value":"openai"},{"name":"HINDSIGHT_API_PORT","value":"8889"},{"name":"HINDSIGHT_API_WORKER_BATCH_SIZE","value":"10"},{"name":"HINDSIGHT_API_WORKER_HTTP_PORT","value":"8889"},{"name":"HINDSIGHT_API_WORKER_MAX_RETRIES","value":"3"},{"name":"HINDSIGHT_API_WORKER_POLL_INTERVAL_MS","value":"500"},{"name":"OTEL_EXPORTER_OTLP_ENDPOINT","value":"http://otel-collector.observability-system.svc.cluster.local:4317"},{"name":"OTEL_SERVICE_NAME","value":"hindsight-worker"},{"name":"PORT","value":"8889"}],"gid":1000,"hardening":"restricted","identity":"hindsight-worker","identityToken":false,"image":"ghcr.io/vectorize-io/hindsight-api@sha256:7b14a1f4062252992d0176758753615e0a2071d9a269995be007be223ab01812","imageVersion":"0.4.0","ingress":[{"consumes":"hindsight.worker","port":8889,"rule":"consumer"}],"lifecycle":"application","liveness":{"failures":3,"path":"/health/live","period":"10s","port":8889,"timeout":"5s"},"memory":"1Gi","name":"hindsight-worker","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3"]},"readiness":{"failures":3,"path":"/health","period":"10s","port":8889,"timeout":"5s"},"replicas":1,"runtime":"python","secrets":[{"access":"read","delivery":"env","destination":"hindsight-worker-platform-hindsight","keys":["llm-api-key"],"path":"secret/data/platform/hindsight","restartTargets":["hindsight-worker"]},{"delivery":"file","destination":"hindsight-worker-database-creds-knowledge-platform-owner","engine":"database","fileMode":"0400","mountAt":"/run/secrets/database","paths":[{"allows":["read"],"path":"database/creds/knowledge-platform-owner"}],"restartTargets":["hindsight-worker"]}],"startup":{"failures":24,"path":"/health/live","period":"5s","port":8889,"timeout":"5s"},"surfaces":[{"name":"worker","port":8889}],"switchover":"blue-green","uid":1000},{"consumes":["hindsight.api"],"cpu":"250m","cutover":"continuous","deadline":"300s","dependencies":[{"address":"hindsight-api:8888","application":"hindsight","peers":[{"namespace":"knowledge-platform-system","port":8888,"process":"hindsight-api"}],"surface":"api"}],"egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"}],"environment":[{"name":"DEPLOYMENT_ENVIRONMENT","value":"production"},{"name":"HINDSIGHT_CP_HOSTNAME","value":"0.0.0.0"},{"name":"HINDSIGHT_CP_PORT","value":"3000"},{"name":"NODE_ENV","value":"production"},{"name":"OTEL_EXPORTER_OTLP_ENDPOINT","value":"http://otel-collector.observability-system.svc.cluster.local:4317"},{"name":"OTEL_SERVICE_NAME","value":"hindsight-control-plane"},{"name":"PORT","value":"3000"}],"gid":1000,"hardening":"restricted","identity":"hindsight-control-plane","identityToken":false,"image":"ghcr.io/vectorize-io/hindsight-control-plane@sha256:d9bf57083f7cb0a53ee5011bdfde6aae2490dbf5c360d2d1bff10473b45eb112","imageVersion":"0.4.0","ingress":[{"namespace":"edge-system","port":3000,"process":"traefik-public","rule":"tier-proxy"},{"consumes":"hindsight.console","port":3000,"rule":"consumer"}],"lifecycle":"application","liveness":{"failures":3,"period":"10s","tcp":3000,"timeout":"5s"},"memory":"512Mi","name":"hindsight-control-plane","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3"]},"readiness":{"failures":3,"period":"10s","tcp":3000,"timeout":"5s"},"replicas":1,"runtime":"node","startup":{"failures":12,"period":"5s","tcp":3000,"timeout":"5s"},"surfaces":[{"name":"console","port":3000}],"switchover":"blue-green","uid":1000}],"project":"knowledge-platform","provenance":{"inputDigests":[{"digest":"sha256:338533f83f590c641c3b7c3855483358ce99da5e6ca37512da9340a849c38fa0","input":"intent-fragment","name":"data"},{"digest":"sha256:ebb5134deeb55f320e28609054a0aca85a359f3869501a3a8b7560d7d05a4277","input":"intent-fragment","name":"delivery"},{"digest":"sha256:9a29fae42c0c6cd4f9824d7af3d9ecc68c068a3a00d1f9488bfbb70c882ea565","input":"intent-fragment","name":"edge"},{"digest":"sha256:9de2ffb5ab977b42af31000e571e8042380d213ed480defdff50eef41c08e68c","input":"intent-fragment","name":"identity"},{"digest":"sha256:fe5cda61657d8aaff540b0739cf622cac4ed8353a912f3bca30044fa0e92e3a2","input":"intent-fragment","name":"knowledge-platform"},{"digest":"sha256:84b3b1a8e81fc41b549a34715713cae3354cfc475e9add64d322b7c631f5b816","input":"intent-fragment","name":"observability"},{"digest":"sha256:31309996835674cc4f037f8e19897ddbe4a7fa8a487a3efc613e251caeed0853","input":"intent-fragment","name":"secrets"},{"digest":"sha256:9f60e75b7b449edb3e0f44485a7ed8fa423a5436ab2b4ca9a1df14bafe76beea","input":"platform-intent","name":"jorisjonkers.dev"},{"digest":"sha256:a989e80337efb294fa1a7175b3ad3eea8bed9bf13fbd503a26691c9265575fc2","input":"node-contract","name":"production"},{"digest":"sha256:179f17834dd006548bfdbdd623e8f74b25be6eedde214f8e00e8cb4295807d57","input":"images-lock","name":"estate"},{"digest":"sha256:202e5e2a22ab20cfe5b6e85cbd8139cb13ed3b199b2f1af4380068d21d54ff1f","input":"cluster-state","name":"production"}],"renderHash":"sha256:b517a31f62fbcccc341e609859f14203e5fde2db8a6a6f174a8f01297151f997","schemaPackageIntegrity":"sha256:5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b"},"reconcileAfter":["apps-data","apps-edge","apps-secrets"],"reconcileUnit":"apps-knowledge-platform","releaseGate":{"analysis":{"errorRate":"1%","interval":"30s","iterations":4,"latency":"500ms","threshold":3},"deadline":"480s","endpoint":"http://release-gate.delivery-system.svc.cluster.local:8080","members":[{"checks":["error-rate","latency"],"process":"hindsight-api","readiness":{"path":"/health","port":8888},"waitsFor":[{"application":"hindsight-embedding","process":"hindsight-tei-embedding"}]},{"checks":["error-rate","latency"],"process":"hindsight-worker","readiness":{"path":"/health","port":8889},"waitsFor":[{"application":"hindsight-embedding","process":"hindsight-tei-embedding"}]},{"checks":["error-rate","latency"],"process":"hindsight-control-plane","readiness":{"tcp":3000}}]},"revision":"sha256:cfae06929f68a61982f64b3eb2f716c86aba6361fb42d7452bd22e5c72b7896f","secretStore":"http://vault.secrets-system.svc.cluster.local:8200"} \ No newline at end of file +{"apiVersion":"resolved.jorisjonkers.dev/v1","exposure":[{"certificates":"acme","host":"memory.jorisjonkers.dev","listener":"tls","name":"console","proxy":{"application":"traefik-public","namespace":"edge-system"},"routes":[{"audience":"authenticated","match":"prefix","middleware":[{"endpoint":"http://identity.identity-system.svc.cluster.local:8080/gate/forward","kind":"forward-auth"},{"contentPolicy":"strict","kind":"security-headers"}],"path":"/","precedence":1,"process":"hindsight-control-plane","surface":"console"}],"tier":"public-frankfurt"},{"certificates":"acme","host":"memory-api.jorisjonkers.dev","listener":"tls","name":"api","proxy":{"application":"traefik-public","namespace":"edge-system"},"routes":[{"audience":"authenticated","match":"prefix","middleware":[{"endpoint":"http://identity.identity-system.svc.cluster.local:8080/gate/forward","kind":"forward-auth"},{"contentPolicy":"strict","kind":"security-headers"}],"path":"/","precedence":1,"process":"hindsight-api","surface":"api"}],"tier":"public-frankfurt"}],"id":"hindsight","kind":"ResolvedApplication","namespace":"knowledge-platform-system","processes":[{"consumes":["platform-postgres.postgres","hindsight-embedding.embeddings"],"cpu":"500m","cutover":"continuous","deadline":"480s","dependencies":[{"address":"platform-postgres:5432","application":"platform-postgres","peers":[{"namespace":"data-system","port":5432,"process":"postgres"}],"surface":"postgres","target":"postgres.data-system.svc.cluster.local"},{"address":"hindsight-tei-embedding:8091","application":"hindsight-embedding","peers":[{"namespace":"knowledge-platform-system","port":8091,"process":"hindsight-tei-embedding"}],"surface":"embeddings"}],"egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"},{"namespace":"secrets-system","port":8200,"process":"vault","rule":"secret-store"}],"environment":[{"name":"DEPLOYMENT_ENVIRONMENT","value":"production"},{"name":"HINDSIGHT_API_EMBEDDINGS_PROVIDER","value":"tei"},{"name":"HINDSIGHT_API_LLM_API_KEY","secret":{"key":"llm-api-key","path":"secret/data/platform/hindsight"}},{"name":"HINDSIGHT_API_LLM_BASE_URL","value":"https://eu.openrouter.ai/api/v1"},{"name":"HINDSIGHT_API_LLM_MODEL","value":"mistralai/mistral-small-2603"},{"name":"HINDSIGHT_API_LLM_PROVIDER","value":"openai"},{"name":"HINDSIGHT_API_PORT","value":"8888"},{"name":"HINDSIGHT_API_WORKER_ENABLED","value":"false"},{"name":"OTEL_EXPORTER_OTLP_ENDPOINT","value":"http://otel-collector.observability-system.svc.cluster.local:4317"},{"name":"OTEL_SERVICE_NAME","value":"hindsight-api"},{"name":"PGPASSWORD","secret":{"key":"password","path":"database/creds/knowledge-platform-owner"}},{"name":"PGUSER","secret":{"key":"username","path":"database/creds/knowledge-platform-owner"}},{"name":"PORT","value":"8888"}],"gid":1000,"hardening":"restricted","identity":"hindsight-api","identityToken":false,"image":"ghcr.io/vectorize-io/hindsight-api@sha256:7b14a1f4062252992d0176758753615e0a2071d9a269995be007be223ab01812","imageVersion":"0.4.0","ingress":[{"namespace":"edge-system","port":8888,"process":"traefik-public","rule":"tier-proxy"},{"consumes":"hindsight.api","port":8888,"rule":"consumer"},{"namespace":"delivery-system","port":8888,"process":"release-gate","rule":"release-gate"}],"lifecycle":"application","liveness":{"failures":3,"path":"/health/live","period":"10s","port":8888,"timeout":"5s"},"memory":"1Gi","name":"hindsight-api","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3"]},"readiness":{"failures":3,"path":"/health","period":"10s","port":8888,"timeout":"5s"},"replicas":1,"runtime":"python","secrets":[{"access":"read","delivery":"env","destination":"hindsight-api-platform-hindsight","keys":["llm-api-key"],"path":"secret/data/platform/hindsight","restartTargets":["hindsight-api"]},{"delivery":"env","destination":"hindsight-api-database-creds-knowledge-platform-owner","engine":"database","paths":[{"allows":["read"],"path":"database/creds/knowledge-platform-owner"}],"restartTargets":["hindsight-api"]}],"startup":{"failures":24,"path":"/health/live","period":"5s","port":8888,"timeout":"5s"},"surfaces":[{"name":"api","port":8888}],"switchover":"blue-green","uid":1000},{"consumes":["platform-postgres.postgres","hindsight-embedding.embeddings"],"cpu":"500m","cutover":"continuous","deadline":"480s","dependencies":[{"address":"platform-postgres:5432","application":"platform-postgres","peers":[{"namespace":"data-system","port":5432,"process":"postgres"}],"surface":"postgres","target":"postgres.data-system.svc.cluster.local"},{"address":"hindsight-tei-embedding:8091","application":"hindsight-embedding","peers":[{"namespace":"knowledge-platform-system","port":8091,"process":"hindsight-tei-embedding"}],"surface":"embeddings"}],"egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"},{"namespace":"secrets-system","port":8200,"process":"vault","rule":"secret-store"}],"environment":[{"name":"DEPLOYMENT_ENVIRONMENT","value":"production"},{"name":"HINDSIGHT_API_EMBEDDINGS_PROVIDER","value":"tei"},{"name":"HINDSIGHT_API_LLM_API_KEY","secret":{"key":"llm-api-key","path":"secret/data/platform/hindsight"}},{"name":"HINDSIGHT_API_LLM_BASE_URL","value":"https://eu.openrouter.ai/api/v1"},{"name":"HINDSIGHT_API_LLM_MODEL","value":"mistralai/mistral-small-2603"},{"name":"HINDSIGHT_API_LLM_PROVIDER","value":"openai"},{"name":"HINDSIGHT_API_PORT","value":"8889"},{"name":"HINDSIGHT_API_WORKER_BATCH_SIZE","value":"10"},{"name":"HINDSIGHT_API_WORKER_HTTP_PORT","value":"8889"},{"name":"HINDSIGHT_API_WORKER_MAX_RETRIES","value":"3"},{"name":"HINDSIGHT_API_WORKER_POLL_INTERVAL_MS","value":"500"},{"name":"OTEL_EXPORTER_OTLP_ENDPOINT","value":"http://otel-collector.observability-system.svc.cluster.local:4317"},{"name":"OTEL_SERVICE_NAME","value":"hindsight-worker"},{"name":"PORT","value":"8889"}],"gid":1000,"hardening":"restricted","identity":"hindsight-worker","identityToken":false,"image":"ghcr.io/vectorize-io/hindsight-api@sha256:7b14a1f4062252992d0176758753615e0a2071d9a269995be007be223ab01812","imageVersion":"0.4.0","ingress":[{"consumes":"hindsight.worker","port":8889,"rule":"consumer"},{"namespace":"delivery-system","port":8889,"process":"release-gate","rule":"release-gate"}],"lifecycle":"application","liveness":{"failures":3,"path":"/health/live","period":"10s","port":8889,"timeout":"5s"},"memory":"1Gi","name":"hindsight-worker","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3"]},"readiness":{"failures":3,"path":"/health","period":"10s","port":8889,"timeout":"5s"},"replicas":1,"runtime":"python","secrets":[{"access":"read","delivery":"env","destination":"hindsight-worker-platform-hindsight","keys":["llm-api-key"],"path":"secret/data/platform/hindsight","restartTargets":["hindsight-worker"]},{"delivery":"file","destination":"hindsight-worker-database-creds-knowledge-platform-owner","engine":"database","fileMode":"0400","mountAt":"/run/secrets/database","paths":[{"allows":["read"],"path":"database/creds/knowledge-platform-owner"}],"restartTargets":["hindsight-worker"]}],"startup":{"failures":24,"path":"/health/live","period":"5s","port":8889,"timeout":"5s"},"surfaces":[{"name":"worker","port":8889}],"switchover":"blue-green","uid":1000},{"consumes":["hindsight.api"],"cpu":"250m","cutover":"continuous","deadline":"300s","dependencies":[{"address":"hindsight-api:8888","application":"hindsight","peers":[{"namespace":"knowledge-platform-system","port":8888,"process":"hindsight-api"}],"surface":"api"}],"egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"}],"environment":[{"name":"DEPLOYMENT_ENVIRONMENT","value":"production"},{"name":"HINDSIGHT_CP_HOSTNAME","value":"0.0.0.0"},{"name":"HINDSIGHT_CP_PORT","value":"3000"},{"name":"NODE_ENV","value":"production"},{"name":"OTEL_EXPORTER_OTLP_ENDPOINT","value":"http://otel-collector.observability-system.svc.cluster.local:4317"},{"name":"OTEL_SERVICE_NAME","value":"hindsight-control-plane"},{"name":"PORT","value":"3000"}],"gid":1000,"hardening":"restricted","identity":"hindsight-control-plane","identityToken":false,"image":"ghcr.io/vectorize-io/hindsight-control-plane@sha256:d9bf57083f7cb0a53ee5011bdfde6aae2490dbf5c360d2d1bff10473b45eb112","imageVersion":"0.4.0","ingress":[{"namespace":"edge-system","port":3000,"process":"traefik-public","rule":"tier-proxy"},{"consumes":"hindsight.console","port":3000,"rule":"consumer"},{"namespace":"delivery-system","port":3000,"process":"release-gate","rule":"release-gate"}],"lifecycle":"application","liveness":{"failures":3,"period":"10s","tcp":3000,"timeout":"5s"},"memory":"512Mi","name":"hindsight-control-plane","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3"]},"readiness":{"failures":3,"period":"10s","tcp":3000,"timeout":"5s"},"replicas":1,"runtime":"node","startup":{"failures":12,"period":"5s","tcp":3000,"timeout":"5s"},"surfaces":[{"name":"console","port":3000}],"switchover":"blue-green","uid":1000}],"project":"knowledge-platform","provenance":{"inputDigests":[{"digest":"sha256:338533f83f590c641c3b7c3855483358ce99da5e6ca37512da9340a849c38fa0","input":"intent-fragment","name":"data"},{"digest":"sha256:ebb5134deeb55f320e28609054a0aca85a359f3869501a3a8b7560d7d05a4277","input":"intent-fragment","name":"delivery"},{"digest":"sha256:9a29fae42c0c6cd4f9824d7af3d9ecc68c068a3a00d1f9488bfbb70c882ea565","input":"intent-fragment","name":"edge"},{"digest":"sha256:9de2ffb5ab977b42af31000e571e8042380d213ed480defdff50eef41c08e68c","input":"intent-fragment","name":"identity"},{"digest":"sha256:fe5cda61657d8aaff540b0739cf622cac4ed8353a912f3bca30044fa0e92e3a2","input":"intent-fragment","name":"knowledge-platform"},{"digest":"sha256:84b3b1a8e81fc41b549a34715713cae3354cfc475e9add64d322b7c631f5b816","input":"intent-fragment","name":"observability"},{"digest":"sha256:31309996835674cc4f037f8e19897ddbe4a7fa8a487a3efc613e251caeed0853","input":"intent-fragment","name":"secrets"},{"digest":"sha256:9f60e75b7b449edb3e0f44485a7ed8fa423a5436ab2b4ca9a1df14bafe76beea","input":"platform-intent","name":"jorisjonkers.dev"},{"digest":"sha256:a989e80337efb294fa1a7175b3ad3eea8bed9bf13fbd503a26691c9265575fc2","input":"node-contract","name":"production"},{"digest":"sha256:179f17834dd006548bfdbdd623e8f74b25be6eedde214f8e00e8cb4295807d57","input":"images-lock","name":"estate"},{"digest":"sha256:202e5e2a22ab20cfe5b6e85cbd8139cb13ed3b199b2f1af4380068d21d54ff1f","input":"cluster-state","name":"production"}],"renderHash":"sha256:b517a31f62fbcccc341e609859f14203e5fde2db8a6a6f174a8f01297151f997","schemaPackageIntegrity":"sha256:5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b"},"reconcileAfter":["apps-data","apps-edge","apps-secrets"],"reconcileUnit":"apps-knowledge-platform","releaseGate":{"analysis":{"errorRate":"1%","interval":"30s","iterations":4,"latency":"500ms","threshold":3},"deadline":"480s","endpoint":"http://release-gate.delivery-system.svc.cluster.local:8080","members":[{"checks":["error-rate","latency"],"process":"hindsight-api","readiness":{"path":"/health","port":8888},"waitsFor":[{"application":"hindsight-embedding","process":"hindsight-tei-embedding"}]},{"checks":["error-rate","latency"],"process":"hindsight-worker","readiness":{"path":"/health","port":8889},"waitsFor":[{"application":"hindsight-embedding","process":"hindsight-tei-embedding"}]},{"checks":["error-rate","latency"],"process":"hindsight-control-plane","readiness":{"tcp":3000}}]},"revision":"sha256:ff109f223f30d9ee31339f4aefbb188cc8b813a07ee0e09cc5551f53461980eb","secretStore":"http://vault.secrets-system.svc.cluster.local:8200"} \ No newline at end of file diff --git a/spec/v1/examples/knowledge-platform/rendered/apps/knowledge-platform/hindsight/canary.yaml b/spec/v1/examples/knowledge-platform/rendered/apps/knowledge-platform/hindsight/canary.yaml index 6954ca148..cbe05a077 100644 --- a/spec/v1/examples/knowledge-platform/rendered/apps/knowledge-platform/hindsight/canary.yaml +++ b/spec/v1/examples/knowledge-platform/rendered/apps/knowledge-platform/hindsight/canary.yaml @@ -33,7 +33,7 @@ spec: metadata: application: hindsight process: hindsight-api - revision: sha256:cfae06929f68a61982f64b3eb2f716c86aba6361fb42d7452bd22e5c72b7896f + revision: sha256:ff109f223f30d9ee31339f4aefbb188cc8b813a07ee0e09cc5551f53461980eb paths: / readiness: /health hostnames: memory-api.jorisjonkers.dev @@ -45,7 +45,7 @@ spec: metadata: application: hindsight process: hindsight-api - revision: sha256:cfae06929f68a61982f64b3eb2f716c86aba6361fb42d7452bd22e5c72b7896f + revision: sha256:ff109f223f30d9ee31339f4aefbb188cc8b813a07ee0e09cc5551f53461980eb paths: / readiness: /health hostnames: memory-api.jorisjonkers.dev @@ -57,7 +57,7 @@ spec: metadata: application: hindsight process: hindsight-api - revision: sha256:cfae06929f68a61982f64b3eb2f716c86aba6361fb42d7452bd22e5c72b7896f + revision: sha256:ff109f223f30d9ee31339f4aefbb188cc8b813a07ee0e09cc5551f53461980eb paths: / readiness: /health hostnames: memory-api.jorisjonkers.dev @@ -97,7 +97,7 @@ spec: metadata: application: hindsight process: hindsight-worker - revision: sha256:cfae06929f68a61982f64b3eb2f716c86aba6361fb42d7452bd22e5c72b7896f + revision: sha256:ff109f223f30d9ee31339f4aefbb188cc8b813a07ee0e09cc5551f53461980eb readiness: /health errorRate: 1% latency: 500ms @@ -107,7 +107,7 @@ spec: metadata: application: hindsight process: hindsight-worker - revision: sha256:cfae06929f68a61982f64b3eb2f716c86aba6361fb42d7452bd22e5c72b7896f + revision: sha256:ff109f223f30d9ee31339f4aefbb188cc8b813a07ee0e09cc5551f53461980eb readiness: /health errorRate: 1% latency: 500ms @@ -117,7 +117,7 @@ spec: metadata: application: hindsight process: hindsight-worker - revision: sha256:cfae06929f68a61982f64b3eb2f716c86aba6361fb42d7452bd22e5c72b7896f + revision: sha256:ff109f223f30d9ee31339f4aefbb188cc8b813a07ee0e09cc5551f53461980eb readiness: /health errorRate: 1% latency: 500ms @@ -155,7 +155,7 @@ spec: metadata: application: hindsight process: hindsight-control-plane - revision: sha256:cfae06929f68a61982f64b3eb2f716c86aba6361fb42d7452bd22e5c72b7896f + revision: sha256:ff109f223f30d9ee31339f4aefbb188cc8b813a07ee0e09cc5551f53461980eb paths: / hostnames: memory.jorisjonkers.dev errorRate: 1% @@ -166,7 +166,7 @@ spec: metadata: application: hindsight process: hindsight-control-plane - revision: sha256:cfae06929f68a61982f64b3eb2f716c86aba6361fb42d7452bd22e5c72b7896f + revision: sha256:ff109f223f30d9ee31339f4aefbb188cc8b813a07ee0e09cc5551f53461980eb paths: / hostnames: memory.jorisjonkers.dev errorRate: 1% @@ -177,7 +177,7 @@ spec: metadata: application: hindsight process: hindsight-control-plane - revision: sha256:cfae06929f68a61982f64b3eb2f716c86aba6361fb42d7452bd22e5c72b7896f + revision: sha256:ff109f223f30d9ee31339f4aefbb188cc8b813a07ee0e09cc5551f53461980eb paths: / hostnames: memory.jorisjonkers.dev errorRate: 1% diff --git a/spec/v1/examples/knowledge-platform/rendered/apps/knowledge-platform/hindsight/networkpolicy.yaml b/spec/v1/examples/knowledge-platform/rendered/apps/knowledge-platform/hindsight/networkpolicy.yaml index 64c928be9..2fb358534 100644 --- a/spec/v1/examples/knowledge-platform/rendered/apps/knowledge-platform/hindsight/networkpolicy.yaml +++ b/spec/v1/examples/knowledge-platform/rendered/apps/knowledge-platform/hindsight/networkpolicy.yaml @@ -39,6 +39,16 @@ spec: ports: - protocol: TCP port: 8888 + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: delivery-system + podSelector: + matchLabels: + app.kubernetes.io/instance: release-gate + ports: + - protocol: TCP + port: 8888 egress: - to: - namespaceSelector: @@ -111,6 +121,16 @@ spec: ports: - protocol: TCP port: 8889 + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: delivery-system + podSelector: + matchLabels: + app.kubernetes.io/instance: release-gate + ports: + - protocol: TCP + port: 8889 egress: - to: - namespaceSelector: @@ -193,6 +213,16 @@ spec: ports: - protocol: TCP port: 3000 + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: delivery-system + podSelector: + matchLabels: + app.kubernetes.io/instance: release-gate + ports: + - protocol: TCP + port: 3000 egress: - to: - namespaceSelector: diff --git a/spec/v1/examples/knowledge-platform/rendered/apps/knowledge-platform/hindsight/vso.yaml b/spec/v1/examples/knowledge-platform/rendered/apps/knowledge-platform/hindsight/vso.yaml index df68a3926..1fc014a96 100644 --- a/spec/v1/examples/knowledge-platform/rendered/apps/knowledge-platform/hindsight/vso.yaml +++ b/spec/v1/examples/knowledge-platform/rendered/apps/knowledge-platform/hindsight/vso.yaml @@ -12,7 +12,7 @@ metadata: app.kubernetes.io/managed-by: deploy-kit app.kubernetes.io/component: python annotations: - estate.jorisjonkers.dev/revision: sha256:cfae06929f68a61982f64b3eb2f716c86aba6361fb42d7452bd22e5c72b7896f + estate.jorisjonkers.dev/revision: sha256:ff109f223f30d9ee31339f4aefbb188cc8b813a07ee0e09cc5551f53461980eb kustomize.toolkit.fluxcd.io/prune: disabled spec: vaultConnectionRef: secret-store @@ -34,7 +34,7 @@ metadata: app.kubernetes.io/managed-by: deploy-kit app.kubernetes.io/component: python annotations: - estate.jorisjonkers.dev/revision: sha256:cfae06929f68a61982f64b3eb2f716c86aba6361fb42d7452bd22e5c72b7896f + estate.jorisjonkers.dev/revision: sha256:ff109f223f30d9ee31339f4aefbb188cc8b813a07ee0e09cc5551f53461980eb kustomize.toolkit.fluxcd.io/prune: disabled spec: vaultAuthRef: hindsight-api @@ -64,7 +64,7 @@ metadata: app.kubernetes.io/managed-by: deploy-kit app.kubernetes.io/component: python annotations: - estate.jorisjonkers.dev/revision: sha256:cfae06929f68a61982f64b3eb2f716c86aba6361fb42d7452bd22e5c72b7896f + estate.jorisjonkers.dev/revision: sha256:ff109f223f30d9ee31339f4aefbb188cc8b813a07ee0e09cc5551f53461980eb kustomize.toolkit.fluxcd.io/prune: disabled spec: vaultAuthRef: hindsight-api @@ -91,7 +91,7 @@ metadata: app.kubernetes.io/managed-by: deploy-kit app.kubernetes.io/component: python annotations: - estate.jorisjonkers.dev/revision: sha256:cfae06929f68a61982f64b3eb2f716c86aba6361fb42d7452bd22e5c72b7896f + estate.jorisjonkers.dev/revision: sha256:ff109f223f30d9ee31339f4aefbb188cc8b813a07ee0e09cc5551f53461980eb kustomize.toolkit.fluxcd.io/prune: disabled spec: vaultConnectionRef: secret-store @@ -113,7 +113,7 @@ metadata: app.kubernetes.io/managed-by: deploy-kit app.kubernetes.io/component: python annotations: - estate.jorisjonkers.dev/revision: sha256:cfae06929f68a61982f64b3eb2f716c86aba6361fb42d7452bd22e5c72b7896f + estate.jorisjonkers.dev/revision: sha256:ff109f223f30d9ee31339f4aefbb188cc8b813a07ee0e09cc5551f53461980eb kustomize.toolkit.fluxcd.io/prune: disabled spec: vaultAuthRef: hindsight-worker @@ -143,7 +143,7 @@ metadata: app.kubernetes.io/managed-by: deploy-kit app.kubernetes.io/component: python annotations: - estate.jorisjonkers.dev/revision: sha256:cfae06929f68a61982f64b3eb2f716c86aba6361fb42d7452bd22e5c72b7896f + estate.jorisjonkers.dev/revision: sha256:ff109f223f30d9ee31339f4aefbb188cc8b813a07ee0e09cc5551f53461980eb kustomize.toolkit.fluxcd.io/prune: disabled spec: vaultAuthRef: hindsight-worker diff --git a/spec/v1/examples/knowledge-platform/rendered/apps/knowledge-platform/hindsight/workload.yaml b/spec/v1/examples/knowledge-platform/rendered/apps/knowledge-platform/hindsight/workload.yaml index ab64dc480..cf9f88288 100644 --- a/spec/v1/examples/knowledge-platform/rendered/apps/knowledge-platform/hindsight/workload.yaml +++ b/spec/v1/examples/knowledge-platform/rendered/apps/knowledge-platform/hindsight/workload.yaml @@ -32,6 +32,7 @@ spec: app.kubernetes.io/component: python consumes.deploy-kit.jorisjonkers.dev/platform-postgres.postgres: "true" consumes.deploy-kit.jorisjonkers.dev/hindsight-embedding.embeddings: "true" + gated.deploy-kit.jorisjonkers.dev/release-gate: "true" annotations: image.jorisjonkers.dev/hindsight-api: 0.4.0 spec: @@ -167,6 +168,7 @@ spec: app.kubernetes.io/component: python consumes.deploy-kit.jorisjonkers.dev/platform-postgres.postgres: "true" consumes.deploy-kit.jorisjonkers.dev/hindsight-embedding.embeddings: "true" + gated.deploy-kit.jorisjonkers.dev/release-gate: "true" annotations: image.jorisjonkers.dev/hindsight-worker: 0.4.0 spec: @@ -311,6 +313,7 @@ spec: app.kubernetes.io/managed-by: deploy-kit app.kubernetes.io/component: node consumes.deploy-kit.jorisjonkers.dev/hindsight.api: "true" + gated.deploy-kit.jorisjonkers.dev/release-gate: "true" annotations: image.jorisjonkers.dev/hindsight-control-plane: 0.4.0 spec: diff --git a/spec/v1/examples/minimal/expected/resolved.json b/spec/v1/examples/minimal/expected/resolved.json index b7f044ba5..915ab4cf5 100644 --- a/spec/v1/examples/minimal/expected/resolved.json +++ b/spec/v1/examples/minimal/expected/resolved.json @@ -1 +1 @@ -{"alertClass":"business-hours","apiVersion":"resolved.jorisjonkers.dev/v1","exposure":[{"certificates":"acme","host":"notes.jorisjonkers.dev","listener":"tls","name":"public","proxy":{"application":"traefik-public","namespace":"edge-system"},"routes":[{"audience":"anonymous","match":"prefix","middleware":[{"contentPolicy":"strict","kind":"security-headers"}],"path":"/","precedence":1,"process":"notes-api","surface":"http"}],"tier":"public-frankfurt"}],"id":"notes","kind":"ResolvedApplication","namespace":"notes-system","processes":[{"cpu":"50m","cutover":"continuous","deadline":"180s","egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"}],"environment":[{"name":"DEPLOYMENT_ENVIRONMENT","value":"production"},{"name":"NODE_ENV","value":"production"},{"name":"NOTES_PAGE_SIZE","value":"50"},{"name":"OTEL_EXPORTER_OTLP_ENDPOINT","value":"http://otel-collector.observability-system.svc.cluster.local:4317"},{"name":"OTEL_SERVICE_NAME","value":"notes-api"},{"name":"PORT","value":"8080"}],"gid":1000,"gpu":{"resource":"nvidia.com/gpu","runtimeClass":"nvidia-cdi"},"hardening":"restricted","identity":"notes-api","identityToken":false,"image":"ghcr.io/jorisjonkers-dev/notes/notes-api@sha256:9c1e5a4b7d2f8e3a6b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a","imageVersion":"1.4.0","ingress":[{"namespace":"edge-system","port":8080,"process":"traefik-public","rule":"tier-proxy"},{"namespace":"observability-system","port":8080,"process":"prometheus","rule":"metrics-stack"},{"consumes":"notes.http","port":8080,"rule":"consumer"}],"libraries":[{"access":"write","claim":"library-media","gid":3001,"library":"media","mountAt":"/attachments","node":"enschede-t1000-1","path":"/srv/media","subPath":"Notes","volume":"notes-system-media"}],"lifecycle":"application","liveness":{"failures":3,"path":"/healthz/live","period":"10s","port":8080,"timeout":"5s"},"memory":"256Mi","name":"notes-api","placement":{"eligibleNodes":["enschede-t1000-1"]},"readiness":{"failures":3,"path":"/healthz/ready","period":"10s","port":8080,"timeout":"5s"},"replicas":1,"runtime":"node","startup":{"failures":4,"path":"/healthz/live","period":"5s","port":8080,"timeout":"5s"},"surfaces":[{"name":"http","port":8080}],"switchover":"blue-green","uid":1000}],"project":"notes","provenance":{"inputDigests":[{"digest":"sha256:ebb5134deeb55f320e28609054a0aca85a359f3869501a3a8b7560d7d05a4277","input":"intent-fragment","name":"delivery"},{"digest":"sha256:9a29fae42c0c6cd4f9824d7af3d9ecc68c068a3a00d1f9488bfbb70c882ea565","input":"intent-fragment","name":"edge"},{"digest":"sha256:9de2ffb5ab977b42af31000e571e8042380d213ed480defdff50eef41c08e68c","input":"intent-fragment","name":"identity"},{"digest":"sha256:35d5e3643093422537ea72def0f4b19e93ed319a2d0192fc7846342970ce8797","input":"intent-fragment","name":"notes"},{"digest":"sha256:84b3b1a8e81fc41b549a34715713cae3354cfc475e9add64d322b7c631f5b816","input":"intent-fragment","name":"observability"},{"digest":"sha256:31309996835674cc4f037f8e19897ddbe4a7fa8a487a3efc613e251caeed0853","input":"intent-fragment","name":"secrets"},{"digest":"sha256:9f60e75b7b449edb3e0f44485a7ed8fa423a5436ab2b4ca9a1df14bafe76beea","input":"platform-intent","name":"jorisjonkers.dev"},{"digest":"sha256:a989e80337efb294fa1a7175b3ad3eea8bed9bf13fbd503a26691c9265575fc2","input":"node-contract","name":"production"},{"digest":"sha256:179f17834dd006548bfdbdd623e8f74b25be6eedde214f8e00e8cb4295807d57","input":"images-lock","name":"estate"},{"digest":"sha256:202e5e2a22ab20cfe5b6e85cbd8139cb13ed3b199b2f1af4380068d21d54ff1f","input":"cluster-state","name":"production"}],"renderHash":"sha256:450201cedc3a21468e702d1cddfde2059cc79304c1b88250ec8eba494c5b5678","schemaPackageIntegrity":"sha256:5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b"},"reconcileAfter":["apps-edge"],"reconcileUnit":"apps-notes","releaseGate":{"analysis":{"errorRate":"1%","interval":"30s","iterations":4,"latency":"500ms","threshold":3},"deadline":"180s","endpoint":"http://release-gate.delivery-system.svc.cluster.local:8080","members":[{"checks":["error-rate","latency"],"process":"notes-api","readiness":{"path":"/healthz/ready","port":8080}}]},"revision":"sha256:0a8b69a6274d430cb7b3d73355003f5b8bfb4e115e9f38500ecc2d0482045c46","scrape":{"interval":"30s","path":"/metrics","process":"notes-api","surface":"http","timeout":"10s"}} \ No newline at end of file +{"alertClass":"business-hours","apiVersion":"resolved.jorisjonkers.dev/v1","exposure":[{"certificates":"acme","host":"notes.jorisjonkers.dev","listener":"tls","name":"public","proxy":{"application":"traefik-public","namespace":"edge-system"},"routes":[{"audience":"anonymous","match":"prefix","middleware":[{"contentPolicy":"strict","kind":"security-headers"}],"path":"/","precedence":1,"process":"notes-api","surface":"http"}],"tier":"public-frankfurt"}],"id":"notes","kind":"ResolvedApplication","namespace":"notes-system","processes":[{"cpu":"50m","cutover":"continuous","deadline":"180s","egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"}],"environment":[{"name":"DEPLOYMENT_ENVIRONMENT","value":"production"},{"name":"NODE_ENV","value":"production"},{"name":"NOTES_PAGE_SIZE","value":"50"},{"name":"OTEL_EXPORTER_OTLP_ENDPOINT","value":"http://otel-collector.observability-system.svc.cluster.local:4317"},{"name":"OTEL_SERVICE_NAME","value":"notes-api"},{"name":"PORT","value":"8080"}],"gid":1000,"gpu":{"resource":"nvidia.com/gpu","runtimeClass":"nvidia-cdi"},"hardening":"restricted","identity":"notes-api","identityToken":false,"image":"ghcr.io/jorisjonkers-dev/notes/notes-api@sha256:9c1e5a4b7d2f8e3a6b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a","imageVersion":"1.4.0","ingress":[{"namespace":"edge-system","port":8080,"process":"traefik-public","rule":"tier-proxy"},{"namespace":"observability-system","port":8080,"process":"prometheus","rule":"metrics-stack"},{"consumes":"notes.http","port":8080,"rule":"consumer"},{"namespace":"delivery-system","port":8080,"process":"release-gate","rule":"release-gate"}],"libraries":[{"access":"write","claim":"library-media","gid":3001,"library":"media","mountAt":"/attachments","node":"enschede-t1000-1","path":"/srv/media","subPath":"Notes","volume":"notes-system-media"}],"lifecycle":"application","liveness":{"failures":3,"path":"/healthz/live","period":"10s","port":8080,"timeout":"5s"},"memory":"256Mi","name":"notes-api","placement":{"eligibleNodes":["enschede-t1000-1"]},"readiness":{"failures":3,"path":"/healthz/ready","period":"10s","port":8080,"timeout":"5s"},"replicas":1,"runtime":"node","startup":{"failures":4,"path":"/healthz/live","period":"5s","port":8080,"timeout":"5s"},"surfaces":[{"name":"http","port":8080}],"switchover":"blue-green","uid":1000}],"project":"notes","provenance":{"inputDigests":[{"digest":"sha256:ebb5134deeb55f320e28609054a0aca85a359f3869501a3a8b7560d7d05a4277","input":"intent-fragment","name":"delivery"},{"digest":"sha256:9a29fae42c0c6cd4f9824d7af3d9ecc68c068a3a00d1f9488bfbb70c882ea565","input":"intent-fragment","name":"edge"},{"digest":"sha256:9de2ffb5ab977b42af31000e571e8042380d213ed480defdff50eef41c08e68c","input":"intent-fragment","name":"identity"},{"digest":"sha256:35d5e3643093422537ea72def0f4b19e93ed319a2d0192fc7846342970ce8797","input":"intent-fragment","name":"notes"},{"digest":"sha256:84b3b1a8e81fc41b549a34715713cae3354cfc475e9add64d322b7c631f5b816","input":"intent-fragment","name":"observability"},{"digest":"sha256:31309996835674cc4f037f8e19897ddbe4a7fa8a487a3efc613e251caeed0853","input":"intent-fragment","name":"secrets"},{"digest":"sha256:9f60e75b7b449edb3e0f44485a7ed8fa423a5436ab2b4ca9a1df14bafe76beea","input":"platform-intent","name":"jorisjonkers.dev"},{"digest":"sha256:a989e80337efb294fa1a7175b3ad3eea8bed9bf13fbd503a26691c9265575fc2","input":"node-contract","name":"production"},{"digest":"sha256:179f17834dd006548bfdbdd623e8f74b25be6eedde214f8e00e8cb4295807d57","input":"images-lock","name":"estate"},{"digest":"sha256:202e5e2a22ab20cfe5b6e85cbd8139cb13ed3b199b2f1af4380068d21d54ff1f","input":"cluster-state","name":"production"}],"renderHash":"sha256:450201cedc3a21468e702d1cddfde2059cc79304c1b88250ec8eba494c5b5678","schemaPackageIntegrity":"sha256:5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b"},"reconcileAfter":["apps-edge"],"reconcileUnit":"apps-notes","releaseGate":{"analysis":{"errorRate":"1%","interval":"30s","iterations":4,"latency":"500ms","threshold":3},"deadline":"180s","endpoint":"http://release-gate.delivery-system.svc.cluster.local:8080","members":[{"checks":["error-rate","latency"],"process":"notes-api","readiness":{"path":"/healthz/ready","port":8080}}]},"revision":"sha256:03fb3597d42b9c43b69f27717f4ec23d690a67ba7bfa1b07a6442579461f4e3f","scrape":{"interval":"30s","path":"/metrics","process":"notes-api","surface":"http","timeout":"10s"}} \ No newline at end of file diff --git a/spec/v1/examples/minimal/rendered/apps/notes/notes/canary.yaml b/spec/v1/examples/minimal/rendered/apps/notes/notes/canary.yaml index b87213ed2..938701eca 100644 --- a/spec/v1/examples/minimal/rendered/apps/notes/notes/canary.yaml +++ b/spec/v1/examples/minimal/rendered/apps/notes/notes/canary.yaml @@ -33,7 +33,7 @@ spec: metadata: application: notes process: notes-api - revision: sha256:0a8b69a6274d430cb7b3d73355003f5b8bfb4e115e9f38500ecc2d0482045c46 + revision: sha256:03fb3597d42b9c43b69f27717f4ec23d690a67ba7bfa1b07a6442579461f4e3f paths: / readiness: /healthz/ready hostnames: notes.jorisjonkers.dev @@ -45,7 +45,7 @@ spec: metadata: application: notes process: notes-api - revision: sha256:0a8b69a6274d430cb7b3d73355003f5b8bfb4e115e9f38500ecc2d0482045c46 + revision: sha256:03fb3597d42b9c43b69f27717f4ec23d690a67ba7bfa1b07a6442579461f4e3f paths: / readiness: /healthz/ready hostnames: notes.jorisjonkers.dev @@ -57,7 +57,7 @@ spec: metadata: application: notes process: notes-api - revision: sha256:0a8b69a6274d430cb7b3d73355003f5b8bfb4e115e9f38500ecc2d0482045c46 + revision: sha256:03fb3597d42b9c43b69f27717f4ec23d690a67ba7bfa1b07a6442579461f4e3f paths: / readiness: /healthz/ready hostnames: notes.jorisjonkers.dev diff --git a/spec/v1/examples/minimal/rendered/apps/notes/notes/networkpolicy.yaml b/spec/v1/examples/minimal/rendered/apps/notes/notes/networkpolicy.yaml index 022173354..a3d5d3020 100644 --- a/spec/v1/examples/minimal/rendered/apps/notes/notes/networkpolicy.yaml +++ b/spec/v1/examples/minimal/rendered/apps/notes/notes/networkpolicy.yaml @@ -49,6 +49,16 @@ spec: ports: - protocol: TCP port: 8080 + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: delivery-system + podSelector: + matchLabels: + app.kubernetes.io/instance: release-gate + ports: + - protocol: TCP + port: 8080 egress: - to: - namespaceSelector: diff --git a/spec/v1/examples/minimal/rendered/apps/notes/notes/workload.yaml b/spec/v1/examples/minimal/rendered/apps/notes/notes/workload.yaml index d80383f80..44927babf 100644 --- a/spec/v1/examples/minimal/rendered/apps/notes/notes/workload.yaml +++ b/spec/v1/examples/minimal/rendered/apps/notes/notes/workload.yaml @@ -30,6 +30,7 @@ spec: app.kubernetes.io/part-of: notes app.kubernetes.io/managed-by: deploy-kit app.kubernetes.io/component: node + gated.deploy-kit.jorisjonkers.dev/release-gate: "true" annotations: image.jorisjonkers.dev/notes-api: 1.4.0 spec: diff --git a/spec/v1/schemas/resolved-application.schema.json b/spec/v1/schemas/resolved-application.schema.json index 1a3458bda..b6cad98c2 100644 --- a/spec/v1/schemas/resolved-application.schema.json +++ b/spec/v1/schemas/resolved-application.schema.json @@ -1155,7 +1155,14 @@ "egress": { "type": "array", "items": { - "$ref": "#/$defs/EgressPeer" + "anyOf": [ + { + "$ref": "#/$defs/EgressPeer" + }, + { + "$ref": "#/$defs/GateMembersPeer" + } + ] } } }, @@ -2211,8 +2218,22 @@ "tier-proxy", "metrics-stack", "backup", - "flux-events" + "flux-events", + "release-gate" ] + }, + "GateMembersPeer": { + "type": "object", + "properties": { + "rule": { + "type": "string", + "const": "gate-members" + } + }, + "required": [ + "rule" + ], + "additionalProperties": false } } } diff --git a/spec/v1/schemas/resolved-deployment.schema.json b/spec/v1/schemas/resolved-deployment.schema.json index 336348be7..1927a9c22 100644 --- a/spec/v1/schemas/resolved-deployment.schema.json +++ b/spec/v1/schemas/resolved-deployment.schema.json @@ -1260,7 +1260,14 @@ "egress": { "type": "array", "items": { - "$ref": "#/$defs/EgressPeer" + "anyOf": [ + { + "$ref": "#/$defs/EgressPeer" + }, + { + "$ref": "#/$defs/GateMembersPeer" + } + ] } } }, @@ -2316,9 +2323,23 @@ "tier-proxy", "metrics-stack", "backup", - "flux-events" + "flux-events", + "release-gate" ] }, + "GateMembersPeer": { + "type": "object", + "properties": { + "rule": { + "type": "string", + "const": "gate-members" + } + }, + "required": [ + "rule" + ], + "additionalProperties": false + }, "ResolvedPolicyJob": { "type": "object", "properties": { diff --git a/src/adapters/kubernetes/render.ts b/src/adapters/kubernetes/render.ts index c38a6ad88..6160d4499 100644 --- a/src/adapters/kubernetes/render.ts +++ b/src/adapters/kubernetes/render.ts @@ -53,6 +53,7 @@ import { import { applicationLabels, consumerLabelsOf, + MEMBER_LABEL, instanceOf, labelsOf, managedOnly, @@ -482,6 +483,15 @@ const affinityOf = ({ placement }: ResolvedProcess): NodeAffinity => : [placement.boundTo], ); +/** Whether the Process is a member of its Application's release gate. */ +const memberOf = ( + process: ResolvedProcess, + application: ResolvedApplicationDocument, +): boolean => + application.releaseGate?.members.some( + (member) => member.process === process.name, + ) === true; + function deploymentOf( process: ResolvedProcess, application: ResolvedApplicationDocument, @@ -508,6 +518,7 @@ function deploymentOf( // A missing list and an empty one carry no label alike. // Stryker disable next-line ArrayDeclaration ...consumerLabelsOf(process.consumes ?? []), + ...(memberOf(process, application) ? MEMBER_LABEL : {}), }, [ [process.name, process.imageVersion], diff --git a/src/adapters/networking/render.ts b/src/adapters/networking/render.ts index 3325227b3..fc3990cc3 100644 --- a/src/adapters/networking/render.ts +++ b/src/adapters/networking/render.ts @@ -19,6 +19,7 @@ import type { import { consumerLabelsOf, instanceOf, + MEMBER_LABEL, labelsOf, managedOnly, namespaceSelector, @@ -65,6 +66,16 @@ const consumerPeerOf = (consumes: string): PolicyPeer => ({ podSelector: { matchLabels: consumerLabelsOf([consumes]) }, }); +/** + * Every gate member's pods, in any namespace deploy-kit manages: the label is + * rendered only on a Process the release-gate inputs name + * (spec/v1/16-dependencies.md#the-member-label). + */ +const membersPeer: PolicyPeer = { + namespaceSelector: { matchLabels: managedOnly() }, + podSelector: { matchLabels: { ...MEMBER_LABEL } }, +}; + /** * Flux's notification controller is no Process: nothing renders it, and its * pods carry the labels Flux's own install gives them, of which `app` names @@ -147,16 +158,22 @@ function policyOf( ); // The baseline admits UDP and TCP alike, since the cluster's DNS answers on // both; only its peers other than DNS are reached through the mesh. - const baseline = (process.egress ?? []).map((peer) => ({ - to: [peerOf(peer.namespace, peer.process)], - ports: [ - { protocol: "UDP" as const, port: peer.port }, - tcp(peer.port), - ...(mesh === undefined || peer.rule === DNS - ? [] - : [tcp(mesh.tunnelPort)]), - ], - })); + const baseline = (process.egress ?? []).map((peer) => + // The gate's own requests reach every member's pods, on whichever port + // the member's own policy admits them. + peer.rule === "gate-members" + ? { to: [membersPeer] } + : { + to: [peerOf(peer.namespace, peer.process)], + ports: [ + { protocol: "UDP" as const, port: peer.port }, + tcp(peer.port), + ...(mesh === undefined || peer.rule === DNS + ? [] + : [tcp(mesh.tunnelPort)]), + ], + }, + ); // Where the Kubernetes API answers, for a Process that holds access to it: // an address, because the API server is no pod a selector reaches. // A missing block and an empty list admit the same nowhere. diff --git a/src/adapters/shared/labels.ts b/src/adapters/shared/labels.ts index 479468dfd..81056976a 100644 --- a/src/adapters/shared/labels.ts +++ b/src/adapters/shared/labels.ts @@ -35,6 +35,11 @@ export const consumerLabelsOf = (consumes: readonly string[]): Labels => /** The prefix of every consumer label (spec/v1/16-dependencies.md#the-consumer-label). */ export const CONSUMER_LABEL = "consumes.deploy-kit.jorisjonkers.dev/"; +/** The label every gate member's pods carry, which the Release Gate's policy admits (spec/v1/16-dependencies.md#the-member-label). */ +export const MEMBER_LABEL = { + "gated.deploy-kit.jorisjonkers.dev/release-gate": "true", +} as const; + /** The selector every rendered object but a disruption budget uses. */ export const instanceOf = (process: string): Labels => ({ "app.kubernetes.io/instance": process, diff --git a/src/model/resolved-deployment.ts b/src/model/resolved-deployment.ts index 2757d99ff..2932590ae 100644 --- a/src/model/resolved-deployment.ts +++ b/src/model/resolved-deployment.ts @@ -68,6 +68,7 @@ export const INGRESS_RULES = [ "metrics-stack", "backup", "flux-events", + "release-gate", ] as const; /** Which rule admits an outbound peer beyond an edge: the baseline, or a grant. */ @@ -277,6 +278,13 @@ const egressPeer = z }) .meta({ id: "EgressPeer" }); +// Every gate member's pods, wherever they run, on any port: what the Release +// Gate's own requests reach (spec/v1/16-dependencies.md#the-member-label). The +// member's own policy names the port it admits them on. +const gateMembersPeer = z + .strictObject({ rule: z.literal("gate-members") }) + .meta({ id: "GateMembersPeer" }); + // An address range a policy admits by address, on one port, because no // namespace or pod is the peer: where an off-cluster copy goes // (spec/v1/14-platform-intent.md#durability-policy), or where the Kubernetes @@ -573,7 +581,7 @@ const resolvedProcess = stated( environment: z.array(envEntry).exactOptional(), surfaces: z.array(resolvedSurface).exactOptional(), ingress: z.array(ingressPeer).exactOptional(), - egress: z.array(egressPeer).exactOptional(), + egress: z.array(z.union([egressPeer, gateMembersPeer])).exactOptional(), }), "ResolvedProcess", switchoverFollowsCutover, @@ -1004,6 +1012,8 @@ export const resolvedApplicationDocument = stated( export type ResolvedDeploymentDocument = z.output; export type ResolvedPolicyJob = z.output; export type ResolvedCatalogJob = z.output; +/** An outbound peer named by namespace, and by Process where one Process is the peer. */ +export type EgressPeer = z.output; export type ResolvedEvents = z.output; export type ResolvedApplicationDocument = z.output< typeof resolvedApplicationDocument diff --git a/src/resolve/application.ts b/src/resolve/application.ts index 66bcacb8c..62f9afaa9 100644 --- a/src/resolve/application.ts +++ b/src/resolve/application.ts @@ -35,7 +35,7 @@ import { clientGrantsOf, resolveClient } from "./client.ts"; import { resolveMesh } from "./mesh.ts"; import { managed } from "./database.ts"; import { resolveMigration } from "./migration.ts"; -import { egressOf, ingressOf } from "./policy.ts"; +import { egressOf, gateEgressOf, gateIngressOf, ingressOf } from "./policy.ts"; import { resolveProcess, type ProcessContext } from "./process.ts"; /** An Application's element: the projection without its document header and provenance. */ @@ -321,20 +321,32 @@ export function resolveApplication( `${context.projectOf.get(application.id) as string}/${application.id}`, ) === true; const pairs = application.processes.map((process) => { - const ingress = ingressOf(process, application, context); const clientGrants = clientGrantsOf(process, application, context); + const resolved = resolveProcess(process, { + ...context, + machinery, + migratesItself: application.migration === "self", + application: application.id, + clientGrants, + }); + // A blue-green Process that publishes readiness is a gate member, and + // admits the gate's own requests. + const member = + resolved.switchover === "blue-green" && + readinessOf(process) !== undefined; + const ingress = [ + ...ingressOf(process, application, context), + ...gateIngressOf(resolved, member, context), + ]; return { process, resolved: { - ...resolveProcess(process, { - ...context, - machinery, - migratesItself: application.migration === "self", - application: application.id, - clientGrants, - }), + ...resolved, ...(ingress.length === 0 ? {} : { ingress }), - egress: egressOf(process, context, clientGrants.length > 0), + egress: [ + ...egressOf(process, context, clientGrants.length > 0), + ...gateEgressOf(application, context), + ], }, }; }); diff --git a/src/resolve/database.ts b/src/resolve/database.ts index 4f6f98ef9..5c8141540 100644 --- a/src/resolve/database.ts +++ b/src/resolve/database.ts @@ -8,12 +8,12 @@ import type { EffectiveProject, } from "../model/effective-intent.ts"; import { ownsDatabases } from "../model/project-intent-queries.ts"; -import type { ResolvedProcess } from "../model/resolved-deployment.ts"; +import type { EgressPeer } from "../model/resolved-deployment.ts"; import type { PlatformIntentDocument } from "../model/platform-intent.ts"; import { homeOf } from "../model/runtime-profiles.ts"; import { consumedOf } from "../model/consumer-label.ts"; -type Egress = NonNullable[number]; +type Egress = EgressPeer; /** Whether an Application moves its schema with a changelog the platform's runner applies. */ export const managed = (application: EffectiveApplication): boolean => diff --git a/src/resolve/policy.ts b/src/resolve/policy.ts index 1bf201423..7d682d40a 100644 --- a/src/resolve/policy.ts +++ b/src/resolve/policy.ts @@ -15,7 +15,10 @@ import { holds, type PlatformIntentDocument, } from "../model/platform-intent.ts"; -import type { ResolvedProcess } from "../model/resolved-deployment.ts"; +import type { + EgressPeer, + ResolvedProcess, +} from "../model/resolved-deployment.ts"; import { consumedOf } from "../model/consumer-label.ts"; import { backupIdentityOf, @@ -25,7 +28,11 @@ import { import { dumpedSurfaceOf } from "../model/backup.ts"; type Ingress = NonNullable[number]; -type Egress = NonNullable[number]; +type Egress = EgressPeer; +type GateMembers = Extract< + NonNullable[number], + { rule: "gate-members" } +>; /** The DNS port every egress policy admits (spec/v1/16-dependencies.md#the-baseline). */ export const DNS_PORT = 53; @@ -157,6 +164,44 @@ export function ingressOf( return distinct([...routes, ...metrics, ...consumers, ...backups, ...events]); } +/** + * The Release Gate's own requests to a gate member's new copy + * (spec/v1/16-dependencies.md#the-member-label): every Process of the gate the + * platform names, on the port of the member's first surface, which its Canary + * service targets. + */ +export function gateIngressOf( + resolved: Pick, + member: boolean, + context: PolicyContext, +): Ingress[] { + if (!member) return []; + // A member serves on its first surface, or E_RELEASE_UNIT_NO_READINESS and + // the surface checks refused it. + const [served] = resolved.surfaces as [ + NonNullable[number], + ]; + // A member switches under a delivery policy, or E_NO_DELIVERY_POLICY + // refused it. + const { gate } = context.platform.delivery as NonNullable< + PlatformIntentDocument["delivery"] + >; + return peersOf(context.platform, gate, context.union).map((peer) => ({ + rule: "release-gate", + ...peer, + port: served.port, + })); +} + +/** Every gate member's pods, for each Process of the Release Gate the platform names. */ +export const gateEgressOf = ( + application: Pick, + context: PolicyContext, +): GateMembers[] => + holds(context.platform.delivery?.gate, context.project, application.id) + ? [{ rule: "gate-members" }] + : []; + /** The surface the Secret Store answers every grant on. */ const STORE_SURFACE = "http"; diff --git a/test/application/compose.test.ts b/test/application/compose.test.ts index 879e7838c..e1f841927 100644 --- a/test/application/compose.test.ts +++ b/test/application/compose.test.ts @@ -290,7 +290,7 @@ describe("composeEstate", () => { ]); expect(FIRST.lock.spec.fragments.notes?.revisions).toEqual({ notes: - "sha256:0a8b69a6274d430cb7b3d73355003f5b8bfb4e115e9f38500ecc2d0482045c46", + "sha256:03fb3597d42b9c43b69f27717f4ec23d690a67ba7bfa1b07a6442579461f4e3f", }); expect(FIRST.lock.spec.fragments["jorisjonkers.dev"]?.revisions).toEqual( {}, @@ -1103,6 +1103,40 @@ describe("composeEstate, for an artifact whose pin moves", () => { ).toStrictEqual(["edge"]); }); + // deploy-kit#602: a member admits the gate, and the gate reaches every + // member by a label in the member's own share, so neither moves the other. + it("moves only the Project whose Process stops being a gate member or becomes one again, never the gate's", () => { + const first = delivered(); + const stopStart = release("notes", "1.1.0", (body) => + body.replace("cutover: continuous", "cutover: interrupted"), + ); + const left = delivered({ + fragments: RELEASES.map((fragment) => + fragment.manifest.spec.project === "notes" ? stopStart : fragment, + ), + held: RELEASES, + pins: pinned(first), + previous: { lock: first.lock, commit: hex("estate-commit", 40) }, + }); + const back = delivered({ + fragments: RELEASES.map((fragment) => + fragment.manifest.spec.project === "notes" + ? release("notes", "1.2.0") + : fragment, + ), + held: [...RELEASES, stopStart], + pins: pinned(left), + previous: { lock: left.lock, commit: hex("estate-commit-2", 40) }, + }); + const moved = (composition: Composition) => + composition.artifacts + .filter(({ moves }) => moves) + .map(({ name }) => name); + + expect(moved(left)).toStrictEqual(["notes"]); + expect(moved(back)).toStrictEqual(["notes"]); + }); + // deploy-kit#552: a provider admits its consumers by a label each consumer // carries in its own share, so neither side's change moves the other. it("moves only the consumer when its edges change, and no consumer when its provider changes and keeps its Surfaces", () => { diff --git a/test/model/render.test.ts b/test/model/render.test.ts index 1309181c7..28255d582 100644 --- a/test/model/render.test.ts +++ b/test/model/render.test.ts @@ -848,6 +848,58 @@ describe("renderIntentSet", () => { ]); }); + // deploy-kit#602: only a member's pods carry the label the gate's policy + // reaches; a blue-green Process that publishes no readiness is no member. + it("labels a gate member's pods for the Release Gate, and no other Process's", () => { + const worker = ` processes: + - name: notes-worker + lifecycle: application + image: notes-api + runtime: node + provides: { http: 8081 } + placement: { memory: 64Mi, cpu: 10m } + probes: none + startupBudget: 20s + cutover: continuous +`; + const tree = asTree( + artifact( + render({ + "minimal/notes.project.yml": (document) => + document.replace(" processes:\n", worker), + }), + "notes", + ), + ); + const podLabels = Object.fromEntries( + parseAllDocuments(tree["apps/notes/notes/workload.yaml"] ?? "") + .map((document) => document.toJS() as unknown) + .filter( + ( + object, + ): object is { + kind: string; + metadata: { name: string }; + spec: { + template: { metadata: { labels: Record } }; + }; + } => (object as { kind?: string } | null)?.kind === "Deployment", + ) + .map(({ metadata, spec }) => [ + metadata.name, + spec.template.metadata.labels, + ]), + ); + const member = "gated.deploy-kit.jorisjonkers.dev/release-gate"; + + expect(Object.keys(podLabels).sort()).toStrictEqual([ + "notes-api", + "notes-worker", + ]); + expect(podLabels["notes-api"]?.[member]).toBe("true"); + expect(podLabels["notes-worker"]?.[member]).toBeUndefined(); + }); + it("puts a path under estate/ in the _estate artifact, and a Project named for an estate directory in its own", () => { const result = renderIntentSet(files(), { ...OPTIONS, diff --git a/test/model/resolution-derivations.test.ts b/test/model/resolution-derivations.test.ts index 339d65526..bcfab5f9f 100644 --- a/test/model/resolution-derivations.test.ts +++ b/test/model/resolution-derivations.test.ts @@ -1786,9 +1786,10 @@ describe("what a grant derives", () => { it("admits the Secret Store's http surface to a Process that holds a grant, and to no other", () => { const rules = (applications: string) => - processOf(applications).egress?.map( - ({ rule, namespace, process, port }) => - `${rule} ${namespace} ${process ?? "*"} ${String(port)}`, + processOf(applications).egress?.map((peer) => + peer.rule === "gate-members" + ? peer.rule + : `${peer.rule} ${peer.namespace} ${peer.process ?? "*"} ${String(peer.port)}`, ); expect(rules(granted(SELF))).toStrictEqual([ @@ -2982,9 +2983,16 @@ ${serving("notes-api")}`, }); }); +/** Every peer but the gate's own, which every member admits and its own block tests. */ +const beyondTheGate = ( + peers: readonly T[] | undefined, +): T[] | undefined => peers?.filter(({ rule }) => rule !== "release-gate"); + describe("the peers a policy admits", () => { const ingressOf = (all: ResolvedProject[], project: string, index = 0) => - projectNamed(all, project).applications[0]?.processes[index]?.ingress; + beyondTheGate( + projectNamed(all, project).applications[0]?.processes[index]?.ingress, + ); /** The peers a policy names by namespace and Process: every one but a consumer's. */ const named = (all: ResolvedProject[], project: string, index = 0) => ingressOf(all, project, index)?.filter(({ rule }) => rule !== "consumer"); @@ -3037,11 +3045,13 @@ ${serving("notes-other").replace("provides: { http: 8080 }", "provides: { store: projectNamed(all, "notes").applications[0]?.processes ?? []; const store = projectNamed(all, "notes").applications[1]?.processes[0]; const other = projectNamed(all, "notes").applications[2]?.processes[0]; - expect(store?.ingress).toStrictEqual([ + expect(beyondTheGate(store?.ingress)).toStrictEqual([ consumer("notes-store.store", 7000), consumer("notes-store.http", 8080), ]); - expect(other?.ingress).toStrictEqual([consumer("notes-other.store", 7000)]); + expect(beyondTheGate(other?.ingress)).toStrictEqual([ + consumer("notes-other.store", 7000), + ]); expect(api?.consumes).toStrictEqual(["notes-store.store"]); expect(web?.consumes).toBeUndefined(); }); @@ -3121,18 +3131,66 @@ ${serving("notes-api")}${serving("notes-web")}`); projectNamed(all, "notes").applications[0]?.processes ?? []; expect( - api?.ingress?.flatMap((peer) => + beyondTheGate(api?.ingress)?.flatMap((peer) => peer.rule === "consumer" ? [] : [peer.process], ), ).toStrictEqual(["traefik-public"]); expect( - web?.ingress?.flatMap((peer) => + beyondTheGate(web?.ingress)?.flatMap((peer) => peer.rule === "consumer" ? [] : [peer.process], ), ).toStrictEqual(["traefik-lan"]); }); }); +// deploy-kit#602: the gate's own requests reach a member's new copy, and +// neither half names the other's Project. +describe("the Release Gate's own requests", () => { + const fromTheGate = (port: number) => ({ + rule: "release-gate", + namespace: "delivery-system", + process: "release-gate", + port, + }); + const gated = (peers: readonly { readonly rule: string }[] | undefined) => + peers?.filter(({ rule }) => rule === "release-gate"); + + it("admits the gate on the first surface of every member, and on no Process that is none", () => { + const all = projects(` - id: notes + processes: +${serving("notes-api").replace("provides: { http: 8080 }", "provides: { web: 3000, http: 8080 }")}${serving("notes-web")} - id: notes-batch + processes: +${serving("notes-batch").replace("cutover: continuous", "cutover: interrupted")}`); + const [api, web] = + projectNamed(all, "notes").applications[0]?.processes ?? []; + const batch = projectNamed(all, "notes").applications[1]?.processes[0]; + + expect(gated(api?.ingress)).toStrictEqual([fromTheGate(3000)]); + expect(gated(web?.ingress)).toStrictEqual([fromTheGate(8080)]); + // A stop-start Process is no member: Flagger never switches it. + expect(batch?.switchover).toBe("stop-start"); + expect(gated(batch?.ingress)).toStrictEqual([]); + }); + + it("reaches every member from the gate's own Processes, and from nothing else", () => { + const all = projects(` - id: notes + processes: +${serving("notes-api")}`); + const reaches = (project: string, id: string) => + projectNamed(all, project) + .applications.find((candidate) => candidate.id === id) + ?.processes.flatMap(({ egress }) => + (egress ?? []).filter(({ rule }) => rule === "gate-members"), + ); + + expect(reaches("delivery", "release-gate")).toStrictEqual([ + { rule: "gate-members" }, + ]); + expect(reaches("delivery", "flagger")).toStrictEqual([]); + expect(reaches("notes", "notes")).toStrictEqual([]); + }); +}); + describe("a volume and what its class derives", () => { // The Process provides the surface the platform's rabbitmq method dumps. const holding = (volumes: string, engine = "") =>