From aef641e978d8425abda3150eda96bfe3bfd8336b Mon Sep 17 00:00:00 2001 From: Platform team Date: Mon, 2 Nov 2026 09:55:40 +0000 Subject: [PATCH] the existing infrastructure --- .../after/apps/shop-admin/kustomization.yaml | 4 ++ .../after/apps/shop-admin/manifests.yaml | 19 +++++++++ .../create/after/apps/shop-admin/quai.yaml | 7 ++++ .quai/tests/app/create/request.yaml | 5 +++ .quai/tests/app/delete/request.yaml | 7 ++++ .../configs/shop/checkout/kustomization.yaml | 4 ++ .../configs/shop/checkout/manifests.yaml | 8 ++++ .../after/configs/shop/checkout/quai.yaml | 6 +++ .../tests/config/create-checkout/request.yaml | 5 +++ .../shop/payments/kustomization.yaml | 4 ++ .../databases/shop/payments/manifests.yaml | 15 +++++++ .../after/databases/shop/payments/quai.yaml | 5 +++ .quai/tests/database/create/request.yaml | 5 +++ .../databases/shop/orders/manifests.yaml | 15 +++++++ .../after/databases/shop/orders/quai.yaml | 6 +++ .../tests/database/grow-storage/request.yaml | 4 ++ .../after/namespaces/web/kustomization.yaml | 4 ++ .../after/namespaces/web/manifests.yaml | 16 ++++++++ .../create-web/after/namespaces/web/quai.yaml | 5 +++ .quai/tests/namespace/create-web/request.yaml | 5 +++ .../after/namespaces/shop/manifests.yaml | 17 ++++++++ .../after/namespaces/shop/quai.yaml | 5 +++ .../tests/namespace/raise-quota/request.yaml | 5 +++ .../flux/web-policies/kustomization.yaml | 4 ++ .../after/flux/web-policies/manifests.yaml | 13 ++++++ .../create/after/flux/web-policies/quai.yaml | 4 ++ .quai/tests/sync/create/request.yaml | 5 +++ .quai/types/app.yaml | 29 +++++++++++++ .quai/types/config.yaml | 27 ++++++++++++ .quai/types/database.yaml | 37 +++++++++++++++++ .quai/types/namespace.yaml | 31 ++++++++++++++ .quai/types/sync.yaml | 26 ++++++++++++ README.md | 25 +++++++++++ apps/shop-web/kustomization.yaml | 4 ++ apps/shop-web/manifests.yaml | 22 ++++++++++ apps/shop-web/quai.yaml | 5 +++ configs/shop/storefront/kustomization.yaml | 4 ++ configs/shop/storefront/manifests.yaml | 8 ++++ configs/shop/storefront/quai.yaml | 6 +++ databases/shop/orders/kustomization.yaml | 4 ++ databases/shop/orders/manifests.yaml | 15 +++++++ databases/shop/orders/quai.yaml | 5 +++ flux/shop-policies/kustomization.yaml | 4 ++ flux/shop-policies/manifests.yaml | 13 ++++++ flux/shop-policies/quai.yaml | 5 +++ namespaces/shop/README.md | 1 + namespaces/shop/kustomization.yaml | 4 ++ namespaces/shop/manifests.yaml | 17 ++++++++ namespaces/shop/quai.yaml | 5 +++ platform/xrds/postgresqlinstance.yaml | 41 +++++++++++++++++++ schemas/app.schema.json | 16 ++++++++ schemas/config.schema.json | 19 +++++++++ schemas/database.schema.json | 15 +++++++ schemas/namespace.schema.json | 14 +++++++ schemas/sync.schema.json | 14 +++++++ 55 files changed, 618 insertions(+) create mode 100644 .quai/tests/app/create/after/apps/shop-admin/kustomization.yaml create mode 100644 .quai/tests/app/create/after/apps/shop-admin/manifests.yaml create mode 100644 .quai/tests/app/create/after/apps/shop-admin/quai.yaml create mode 100644 .quai/tests/app/create/request.yaml create mode 100644 .quai/tests/app/delete/request.yaml create mode 100644 .quai/tests/config/create-checkout/after/configs/shop/checkout/kustomization.yaml create mode 100644 .quai/tests/config/create-checkout/after/configs/shop/checkout/manifests.yaml create mode 100644 .quai/tests/config/create-checkout/after/configs/shop/checkout/quai.yaml create mode 100644 .quai/tests/config/create-checkout/request.yaml create mode 100644 .quai/tests/database/create/after/databases/shop/payments/kustomization.yaml create mode 100644 .quai/tests/database/create/after/databases/shop/payments/manifests.yaml create mode 100644 .quai/tests/database/create/after/databases/shop/payments/quai.yaml create mode 100644 .quai/tests/database/create/request.yaml create mode 100644 .quai/tests/database/grow-storage/after/databases/shop/orders/manifests.yaml create mode 100644 .quai/tests/database/grow-storage/after/databases/shop/orders/quai.yaml create mode 100644 .quai/tests/database/grow-storage/request.yaml create mode 100644 .quai/tests/namespace/create-web/after/namespaces/web/kustomization.yaml create mode 100644 .quai/tests/namespace/create-web/after/namespaces/web/manifests.yaml create mode 100644 .quai/tests/namespace/create-web/after/namespaces/web/quai.yaml create mode 100644 .quai/tests/namespace/create-web/request.yaml create mode 100644 .quai/tests/namespace/raise-quota/after/namespaces/shop/manifests.yaml create mode 100644 .quai/tests/namespace/raise-quota/after/namespaces/shop/quai.yaml create mode 100644 .quai/tests/namespace/raise-quota/request.yaml create mode 100644 .quai/tests/sync/create/after/flux/web-policies/kustomization.yaml create mode 100644 .quai/tests/sync/create/after/flux/web-policies/manifests.yaml create mode 100644 .quai/tests/sync/create/after/flux/web-policies/quai.yaml create mode 100644 .quai/tests/sync/create/request.yaml create mode 100644 .quai/types/app.yaml create mode 100644 .quai/types/config.yaml create mode 100644 .quai/types/database.yaml create mode 100644 .quai/types/namespace.yaml create mode 100644 .quai/types/sync.yaml create mode 100644 README.md create mode 100644 apps/shop-web/kustomization.yaml create mode 100644 apps/shop-web/manifests.yaml create mode 100644 apps/shop-web/quai.yaml create mode 100644 configs/shop/storefront/kustomization.yaml create mode 100644 configs/shop/storefront/manifests.yaml create mode 100644 configs/shop/storefront/quai.yaml create mode 100644 databases/shop/orders/kustomization.yaml create mode 100644 databases/shop/orders/manifests.yaml create mode 100644 databases/shop/orders/quai.yaml create mode 100644 flux/shop-policies/kustomization.yaml create mode 100644 flux/shop-policies/manifests.yaml create mode 100644 flux/shop-policies/quai.yaml create mode 100644 namespaces/shop/README.md create mode 100644 namespaces/shop/kustomization.yaml create mode 100644 namespaces/shop/manifests.yaml create mode 100644 namespaces/shop/quai.yaml create mode 100644 platform/xrds/postgresqlinstance.yaml create mode 100644 schemas/app.schema.json create mode 100644 schemas/config.schema.json create mode 100644 schemas/database.schema.json create mode 100644 schemas/namespace.schema.json create mode 100644 schemas/sync.schema.json diff --git a/.quai/tests/app/create/after/apps/shop-admin/kustomization.yaml b/.quai/tests/app/create/after/apps/shop-admin/kustomization.yaml new file mode 100644 index 0000000..3d57a0a --- /dev/null +++ b/.quai/tests/app/create/after/apps/shop-admin/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - manifests.yaml diff --git a/.quai/tests/app/create/after/apps/shop-admin/manifests.yaml b/.quai/tests/app/create/after/apps/shop-admin/manifests.yaml new file mode 100644 index 0000000..1ce6f2a --- /dev/null +++ b/.quai/tests/app/create/after/apps/shop-admin/manifests.yaml @@ -0,0 +1,19 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: shop-admin + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: https://git.example.com/shop/admin.git + targetRevision: v1.4.0 + path: deploy + destination: + namespace: shop + server: https://kubernetes.default.svc + syncPolicy: + syncOptions: + - CreateNamespace=true diff --git a/.quai/tests/app/create/after/apps/shop-admin/quai.yaml b/.quai/tests/app/create/after/apps/shop-admin/quai.yaml new file mode 100644 index 0000000..1e9f294 --- /dev/null +++ b/.quai/tests/app/create/after/apps/shop-admin/quai.yaml @@ -0,0 +1,7 @@ +name: shop-admin +owner: bob +repoURL: https://git.example.com/shop/admin.git +path: deploy +revision: v1.4.0 +namespace: shop +autoSync: false diff --git a/.quai/tests/app/create/request.yaml b/.quai/tests/app/create/request.yaml new file mode 100644 index 0000000..7c64ad1 --- /dev/null +++ b/.quai/tests/app/create/request.yaml @@ -0,0 +1,5 @@ +# An Application on a tag of a team's repository, synced by hand. +kind: create +params: { name: shop-admin } +owner: bob +values: { repoURL: "https://git.example.com/shop/admin.git", path: deploy, revision: v1.4.0, namespace: shop, autoSync: false } diff --git a/.quai/tests/app/delete/request.yaml b/.quai/tests/app/delete/request.yaml new file mode 100644 index 0000000..90d1500 --- /dev/null +++ b/.quai/tests/app/delete/request.yaml @@ -0,0 +1,7 @@ +# Deleting an Application removes its directory; its finalizer deletes what it deployed. +kind: delete +key: shop-web +deleted: + - apps/shop-web/kustomization.yaml + - apps/shop-web/manifests.yaml + - apps/shop-web/quai.yaml diff --git a/.quai/tests/config/create-checkout/after/configs/shop/checkout/kustomization.yaml b/.quai/tests/config/create-checkout/after/configs/shop/checkout/kustomization.yaml new file mode 100644 index 0000000..3d57a0a --- /dev/null +++ b/.quai/tests/config/create-checkout/after/configs/shop/checkout/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - manifests.yaml diff --git a/.quai/tests/config/create-checkout/after/configs/shop/checkout/manifests.yaml b/.quai/tests/config/create-checkout/after/configs/shop/checkout/manifests.yaml new file mode 100644 index 0000000..4c83a80 --- /dev/null +++ b/.quai/tests/config/create-checkout/after/configs/shop/checkout/manifests.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: checkout + namespace: shop +data: + provider: example-pay + timeout: 30s diff --git a/.quai/tests/config/create-checkout/after/configs/shop/checkout/quai.yaml b/.quai/tests/config/create-checkout/after/configs/shop/checkout/quai.yaml new file mode 100644 index 0000000..029f077 --- /dev/null +++ b/.quai/tests/config/create-checkout/after/configs/shop/checkout/quai.yaml @@ -0,0 +1,6 @@ +name: checkout +namespace: shop +owner: alice +data: + provider: example-pay + timeout: 30s diff --git a/.quai/tests/config/create-checkout/request.yaml b/.quai/tests/config/create-checkout/request.yaml new file mode 100644 index 0000000..8a66f6c --- /dev/null +++ b/.quai/tests/config/create-checkout/request.yaml @@ -0,0 +1,5 @@ +# A ConfigMap from the raw component. +kind: create +params: { namespace: shop, name: checkout } +owner: alice +values: { data: { provider: example-pay, timeout: 30s } } diff --git a/.quai/tests/database/create/after/databases/shop/payments/kustomization.yaml b/.quai/tests/database/create/after/databases/shop/payments/kustomization.yaml new file mode 100644 index 0000000..3d57a0a --- /dev/null +++ b/.quai/tests/database/create/after/databases/shop/payments/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - manifests.yaml diff --git a/.quai/tests/database/create/after/databases/shop/payments/manifests.yaml b/.quai/tests/database/create/after/databases/shop/payments/manifests.yaml new file mode 100644 index 0000000..71f31be --- /dev/null +++ b/.quai/tests/database/create/after/databases/shop/payments/manifests.yaml @@ -0,0 +1,15 @@ +apiVersion: database.example.org/v1alpha1 +kind: PostgreSQLInstance +metadata: + name: payments + namespace: shop +spec: + parameters: + highAvailability: false + storageGB: 20 + version: "17" + compositionSelector: + matchLabels: + provider: example + writeConnectionSecretToRef: + name: payments-connection diff --git a/.quai/tests/database/create/after/databases/shop/payments/quai.yaml b/.quai/tests/database/create/after/databases/shop/payments/quai.yaml new file mode 100644 index 0000000..216228a --- /dev/null +++ b/.quai/tests/database/create/after/databases/shop/payments/quai.yaml @@ -0,0 +1,5 @@ +name: payments +namespace: shop +owner: carol +version: "17" +storageGB: 20 diff --git a/.quai/tests/database/create/request.yaml b/.quai/tests/database/create/request.yaml new file mode 100644 index 0000000..0c0cd63 --- /dev/null +++ b/.quai/tests/database/create/request.yaml @@ -0,0 +1,5 @@ +# A claim of the platform's PostgreSQL API: high availability is off unless the form turns it on. +kind: create +params: { namespace: shop, name: payments } +owner: carol +values: { version: "17", storageGB: 20 } diff --git a/.quai/tests/database/grow-storage/after/databases/shop/orders/manifests.yaml b/.quai/tests/database/grow-storage/after/databases/shop/orders/manifests.yaml new file mode 100644 index 0000000..f5fdda3 --- /dev/null +++ b/.quai/tests/database/grow-storage/after/databases/shop/orders/manifests.yaml @@ -0,0 +1,15 @@ +apiVersion: database.example.org/v1alpha1 +kind: PostgreSQLInstance +metadata: + name: orders + namespace: shop +spec: + parameters: + highAvailability: true + storageGB: 100 # about a year of orders + version: "17" + compositionSelector: + matchLabels: + provider: example + writeConnectionSecretToRef: + name: orders-connection diff --git a/.quai/tests/database/grow-storage/after/databases/shop/orders/quai.yaml b/.quai/tests/database/grow-storage/after/databases/shop/orders/quai.yaml new file mode 100644 index 0000000..d0ce100 --- /dev/null +++ b/.quai/tests/database/grow-storage/after/databases/shop/orders/quai.yaml @@ -0,0 +1,6 @@ +name: orders +namespace: shop +owner: alice +version: "17" +storageGB: 100 +highAvailability: true diff --git a/.quai/tests/database/grow-storage/request.yaml b/.quai/tests/database/grow-storage/request.yaml new file mode 100644 index 0000000..6906d4a --- /dev/null +++ b/.quai/tests/database/grow-storage/request.yaml @@ -0,0 +1,4 @@ +# More storage for orders: one parameter of the claim changes, its comment stays. +kind: update +key: shop/orders +values: { name: orders, namespace: shop, owner: alice, version: "17", storageGB: 100, highAvailability: true } diff --git a/.quai/tests/namespace/create-web/after/namespaces/web/kustomization.yaml b/.quai/tests/namespace/create-web/after/namespaces/web/kustomization.yaml new file mode 100644 index 0000000..3d57a0a --- /dev/null +++ b/.quai/tests/namespace/create-web/after/namespaces/web/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - manifests.yaml diff --git a/.quai/tests/namespace/create-web/after/namespaces/web/manifests.yaml b/.quai/tests/namespace/create-web/after/namespaces/web/manifests.yaml new file mode 100644 index 0000000..36a800e --- /dev/null +++ b/.quai/tests/namespace/create-web/after/namespaces/web/manifests.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: web + labels: + team: web +--- +apiVersion: v1 +kind: ResourceQuota +metadata: + name: quota + namespace: web +spec: + hard: + requests.cpu: "4" + requests.memory: 8Gi diff --git a/.quai/tests/namespace/create-web/after/namespaces/web/quai.yaml b/.quai/tests/namespace/create-web/after/namespaces/web/quai.yaml new file mode 100644 index 0000000..d1bfac2 --- /dev/null +++ b/.quai/tests/namespace/create-web/after/namespaces/web/quai.yaml @@ -0,0 +1,5 @@ +name: web +owner: bob +team: web +cpu: "4" +memory: 8Gi diff --git a/.quai/tests/namespace/create-web/request.yaml b/.quai/tests/namespace/create-web/request.yaml new file mode 100644 index 0000000..1d106c0 --- /dev/null +++ b/.quai/tests/namespace/create-web/request.yaml @@ -0,0 +1,5 @@ +# A new team namespace: the record, the namespace and its quota, and a kustomization that lists them. +kind: create +params: { name: web } +owner: bob +values: { team: web, cpu: "4", memory: 8Gi } diff --git a/.quai/tests/namespace/raise-quota/after/namespaces/shop/manifests.yaml b/.quai/tests/namespace/raise-quota/after/namespaces/shop/manifests.yaml new file mode 100644 index 0000000..1625241 --- /dev/null +++ b/.quai/tests/namespace/raise-quota/after/namespaces/shop/manifests.yaml @@ -0,0 +1,17 @@ +# The shop team: storefront, orders and payments. +apiVersion: v1 +kind: Namespace +metadata: + name: shop + labels: + team: shop +--- +apiVersion: v1 +kind: ResourceQuota +metadata: + name: quota + namespace: shop +spec: + hard: + requests.cpu: "12" # raised for the sales, see PLAT-142 + requests.memory: 16Gi diff --git a/.quai/tests/namespace/raise-quota/after/namespaces/shop/quai.yaml b/.quai/tests/namespace/raise-quota/after/namespaces/shop/quai.yaml new file mode 100644 index 0000000..333053a --- /dev/null +++ b/.quai/tests/namespace/raise-quota/after/namespaces/shop/quai.yaml @@ -0,0 +1,5 @@ +name: shop +owner: alice +team: shop +cpu: "12" +memory: 16Gi diff --git a/.quai/tests/namespace/raise-quota/request.yaml b/.quai/tests/namespace/raise-quota/request.yaml new file mode 100644 index 0000000..6c392f9 --- /dev/null +++ b/.quai/tests/namespace/raise-quota/request.yaml @@ -0,0 +1,5 @@ +# More CPU for shop: the record and the quota change in place, the comments stay; the +# kustomization and the README are left alone. +kind: update +key: shop +values: { name: shop, owner: alice, team: shop, cpu: "12", memory: 16Gi } diff --git a/.quai/tests/sync/create/after/flux/web-policies/kustomization.yaml b/.quai/tests/sync/create/after/flux/web-policies/kustomization.yaml new file mode 100644 index 0000000..3d57a0a --- /dev/null +++ b/.quai/tests/sync/create/after/flux/web-policies/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - manifests.yaml diff --git a/.quai/tests/sync/create/after/flux/web-policies/manifests.yaml b/.quai/tests/sync/create/after/flux/web-policies/manifests.yaml new file mode 100644 index 0000000..f4dc15a --- /dev/null +++ b/.quai/tests/sync/create/after/flux/web-policies/manifests.yaml @@ -0,0 +1,13 @@ +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: web-policies + namespace: flux-system +spec: + interval: 10m + path: ./policies/web + prune: true + sourceRef: + kind: GitRepository + name: platform + targetNamespace: web diff --git a/.quai/tests/sync/create/after/flux/web-policies/quai.yaml b/.quai/tests/sync/create/after/flux/web-policies/quai.yaml new file mode 100644 index 0000000..b86ee2a --- /dev/null +++ b/.quai/tests/sync/create/after/flux/web-policies/quai.yaml @@ -0,0 +1,4 @@ +name: web-policies +owner: bob +path: ./policies/web +targetNamespace: web diff --git a/.quai/tests/sync/create/request.yaml b/.quai/tests/sync/create/request.yaml new file mode 100644 index 0000000..0e8027b --- /dev/null +++ b/.quai/tests/sync/create/request.yaml @@ -0,0 +1,5 @@ +# A Flux Kustomization of the platform source, every ten minutes unless the form says otherwise. +kind: create +params: { name: web-policies } +owner: bob +values: { path: ./policies/web, targetNamespace: web } diff --git a/.quai/types/app.yaml b/.quai/types/app.yaml new file mode 100644 index 0000000..b699498 --- /dev/null +++ b/.quai/types/app.yaml @@ -0,0 +1,29 @@ +apiVersion: quai.dev/v1beta1 +kind: ResourceType +metadata: + name: app + title: Application + description: An Argo CD Application that deploys a directory of a team's repository. + icon: rocket + category: Delivery + version: 1.0.0 +spec: + locate: { strategy: dir, path: "apps/{name}" } + params: + - { name: name, title: Name, pattern: "[a-z][a-z0-9-]{1,40}" } + schema: schemas/app.schema.json + generate: + kind: component + component: argocd.application + with: + name: ${{ params.name }} + repoURL: ${{ inputs.repoURL }} + path: ${{ inputs.path }} + targetRevision: ${{ inputs.?revision }} + destinationNamespace: ${{ inputs.namespace }} + automated: ${{ inputs.?autoSync.orValue(true) }} + syncOptions: [CreateNamespace=true] + cascadeDelete: true + ownership: { field: owner } + paramFields: { name: name } + deletion: allowed diff --git a/.quai/types/config.yaml b/.quai/types/config.yaml new file mode 100644 index 0000000..a8ae731 --- /dev/null +++ b/.quai/types/config.yaml @@ -0,0 +1,27 @@ +apiVersion: quai.dev/v1beta1 +kind: ResourceType +metadata: + name: config + title: Configuration + description: Settings for an application, as a ConfigMap in its namespace. + icon: settings + category: Kubernetes + version: 1.0.0 +spec: + locate: { strategy: dir, path: "configs/{namespace}/{name}" } + params: + - { name: namespace, title: Namespace, type: enum, discover: true } + - { name: name, title: Name, pattern: "[a-z][a-z0-9-]{1,30}" } + schema: schemas/config.schema.json + generate: + kind: component + component: raw + with: + manifests: + - apiVersion: v1 + kind: ConfigMap + metadata: { name: "${{ params.name }}", namespace: "${{ params.namespace }}" } + data: ${{ inputs.data }} + ownership: { field: owner } + paramFields: { name: name, namespace: namespace } + deletion: allowed diff --git a/.quai/types/database.yaml b/.quai/types/database.yaml new file mode 100644 index 0000000..ae8108a --- /dev/null +++ b/.quai/types/database.yaml @@ -0,0 +1,37 @@ +apiVersion: quai.dev/v1beta1 +kind: ResourceType +metadata: + name: database + title: PostgreSQL database + description: A managed PostgreSQL instance, claimed from the platform's Crossplane API. + icon: database + category: Data + version: 1.0.0 +spec: + locate: { strategy: dir, path: "databases/{namespace}/{name}" } + params: + - { name: namespace, title: Namespace, type: enum, discover: true } + - { name: name, title: Name, pattern: "[a-z][a-z0-9-]{1,30}" } + schema: schemas/database.schema.json + generate: + kind: component + component: crossplane.claim + with: + apiVersion: database.example.org/v1alpha1 + kind: PostgreSQLInstance + name: ${{ params.name }} + namespace: ${{ params.namespace }} + spec: + parameters: + version: ${{ inputs.version }} + storageGB: ${{ inputs.storageGB }} + highAvailability: ${{ inputs.?highAvailability.orValue(false) }} + compositionSelector: { matchLabels: { provider: example } } + writeConnectionSecretToRef: { name: "${{ params.name }}-connection" } + ownership: { field: owner } + paramFields: { name: name, namespace: namespace } + deletion: approval + ui: + order: [version, storageGB, highAvailability] + fields: + version: { immutable: true } diff --git a/.quai/types/namespace.yaml b/.quai/types/namespace.yaml new file mode 100644 index 0000000..de82b1f --- /dev/null +++ b/.quai/types/namespace.yaml @@ -0,0 +1,31 @@ +apiVersion: quai.dev/v1beta1 +kind: ResourceType +metadata: + name: namespace + title: Team namespace + description: A team's namespace with a quota of CPU and memory requests. + icon: box + category: Kubernetes + version: 1.0.0 +spec: + locate: { strategy: dir, path: "namespaces/{name}" } + params: + - { name: name, title: Name, pattern: "[a-z][a-z0-9-]{1,30}" } + schema: schemas/namespace.schema.json + generate: + kind: component + component: k8s.namespace + with: + name: ${{ params.name }} + labels: { team: "${{ inputs.team }}" } + quota: + requests.cpu: ${{ inputs.?cpu }} + requests.memory: ${{ inputs.?memory }} + ownership: { field: owner } + paramFields: { name: name } + deletion: approval + approval: + rules: + - name: quota + when: inputs.?cpu != previous.?cpu || inputs.?memory != previous.?memory + approvers: [group:platform-admins] diff --git a/.quai/types/sync.yaml b/.quai/types/sync.yaml new file mode 100644 index 0000000..8ee844a --- /dev/null +++ b/.quai/types/sync.yaml @@ -0,0 +1,26 @@ +apiVersion: quai.dev/v1beta1 +kind: ResourceType +metadata: + name: sync + title: Flux sync + description: A Flux Kustomization that applies a directory of the platform repository to a namespace. + icon: refresh + category: Delivery + version: 1.0.0 +spec: + locate: { strategy: dir, path: "flux/{name}" } + params: + - { name: name, title: Name, pattern: "[a-z][a-z0-9-]{1,40}" } + schema: schemas/sync.schema.json + generate: + kind: component + component: flux.kustomization + with: + name: ${{ params.name }} + source: platform + path: ${{ inputs.path }} + interval: ${{ inputs.?interval }} + targetNamespace: ${{ inputs.targetNamespace }} + ownership: { field: owner } + paramFields: { name: name } + deletion: approval diff --git a/README.md b/README.md new file mode 100644 index 0000000..5368655 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# Kubernetes example repository + +A GitOps repository for a Kubernetes platform, reconciled by Argo CD or Flux, with Quai +definitions added: a type for each built-in component of the `component` generator +(docs/design/02-resource-types-and-generators.md). The backend's tests lint it, run its golden +examples, and check its manifests against a Kubernetes API server in server-side dry runs. + +Each resource is a directory (the `dir` locator) that holds: + +- `quai.yaml`, the record of the form's values, which Quai reads back; +- `manifests.yaml`, the manifests the component renders, one YAML document each; +- `kustomization.yaml`, which lists the manifests (not the record) for Argo CD, Flux or + `kubectl apply -k`. + +The types: + +| Type | Component | Directory | Renders | +| --- | --- | --- | --- | +| `namespace` | `k8s.namespace` | `namespaces//` | a team's `Namespace` and its `ResourceQuota` | +| `config` | `raw` | `configs///` | a `ConfigMap` | +| `database` | `crossplane.claim` | `databases///` | a `PostgreSQLInstance` claim of the XRD in `platform/xrds/` | +| `app` | `argocd.application` | `apps//` | an Argo CD `Application` deploying a path of a team's repository | +| `sync` | `flux.kustomization` | `flux//` | a Flux `Kustomization` applying a path of the `platform` source | + +`schemas/` holds the forms' JSON Schemas and `.quai/tests//` the golden examples. diff --git a/apps/shop-web/kustomization.yaml b/apps/shop-web/kustomization.yaml new file mode 100644 index 0000000..3d57a0a --- /dev/null +++ b/apps/shop-web/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - manifests.yaml diff --git a/apps/shop-web/manifests.yaml b/apps/shop-web/manifests.yaml new file mode 100644 index 0000000..85e59e3 --- /dev/null +++ b/apps/shop-web/manifests.yaml @@ -0,0 +1,22 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: shop-web + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: https://git.example.com/shop/web.git + targetRevision: HEAD + path: deploy/prod + destination: + namespace: shop + server: https://kubernetes.default.svc + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/apps/shop-web/quai.yaml b/apps/shop-web/quai.yaml new file mode 100644 index 0000000..e20aaa0 --- /dev/null +++ b/apps/shop-web/quai.yaml @@ -0,0 +1,5 @@ +name: shop-web +owner: alice +repoURL: https://git.example.com/shop/web.git +path: deploy/prod +namespace: shop diff --git a/configs/shop/storefront/kustomization.yaml b/configs/shop/storefront/kustomization.yaml new file mode 100644 index 0000000..3d57a0a --- /dev/null +++ b/configs/shop/storefront/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - manifests.yaml diff --git a/configs/shop/storefront/manifests.yaml b/configs/shop/storefront/manifests.yaml new file mode 100644 index 0000000..09f603b --- /dev/null +++ b/configs/shop/storefront/manifests.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: storefront + namespace: shop +data: + currency: CHF + language: fr diff --git a/configs/shop/storefront/quai.yaml b/configs/shop/storefront/quai.yaml new file mode 100644 index 0000000..c009793 --- /dev/null +++ b/configs/shop/storefront/quai.yaml @@ -0,0 +1,6 @@ +name: storefront +namespace: shop +owner: alice +data: + currency: CHF + language: fr diff --git a/databases/shop/orders/kustomization.yaml b/databases/shop/orders/kustomization.yaml new file mode 100644 index 0000000..3d57a0a --- /dev/null +++ b/databases/shop/orders/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - manifests.yaml diff --git a/databases/shop/orders/manifests.yaml b/databases/shop/orders/manifests.yaml new file mode 100644 index 0000000..d373338 --- /dev/null +++ b/databases/shop/orders/manifests.yaml @@ -0,0 +1,15 @@ +apiVersion: database.example.org/v1alpha1 +kind: PostgreSQLInstance +metadata: + name: orders + namespace: shop +spec: + parameters: + highAvailability: false + storageGB: 50 # about a year of orders + version: "17" + compositionSelector: + matchLabels: + provider: example + writeConnectionSecretToRef: + name: orders-connection diff --git a/databases/shop/orders/quai.yaml b/databases/shop/orders/quai.yaml new file mode 100644 index 0000000..8f8938b --- /dev/null +++ b/databases/shop/orders/quai.yaml @@ -0,0 +1,5 @@ +name: orders +namespace: shop +owner: alice +version: "17" +storageGB: 50 diff --git a/flux/shop-policies/kustomization.yaml b/flux/shop-policies/kustomization.yaml new file mode 100644 index 0000000..3d57a0a --- /dev/null +++ b/flux/shop-policies/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - manifests.yaml diff --git a/flux/shop-policies/manifests.yaml b/flux/shop-policies/manifests.yaml new file mode 100644 index 0000000..4a74665 --- /dev/null +++ b/flux/shop-policies/manifests.yaml @@ -0,0 +1,13 @@ +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: shop-policies + namespace: flux-system +spec: + interval: 1h + path: ./policies/shop + prune: true + sourceRef: + kind: GitRepository + name: platform + targetNamespace: shop diff --git a/flux/shop-policies/quai.yaml b/flux/shop-policies/quai.yaml new file mode 100644 index 0000000..d128732 --- /dev/null +++ b/flux/shop-policies/quai.yaml @@ -0,0 +1,5 @@ +name: shop-policies +owner: alice +path: ./policies/shop +targetNamespace: shop +interval: 1h diff --git a/namespaces/shop/README.md b/namespaces/shop/README.md new file mode 100644 index 0000000..4fa745e --- /dev/null +++ b/namespaces/shop/README.md @@ -0,0 +1 @@ +The shop team's namespace. Its quota is reviewed by the platform team. diff --git a/namespaces/shop/kustomization.yaml b/namespaces/shop/kustomization.yaml new file mode 100644 index 0000000..3d57a0a --- /dev/null +++ b/namespaces/shop/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - manifests.yaml diff --git a/namespaces/shop/manifests.yaml b/namespaces/shop/manifests.yaml new file mode 100644 index 0000000..81da6b6 --- /dev/null +++ b/namespaces/shop/manifests.yaml @@ -0,0 +1,17 @@ +# The shop team: storefront, orders and payments. +apiVersion: v1 +kind: Namespace +metadata: + name: shop + labels: + team: shop +--- +apiVersion: v1 +kind: ResourceQuota +metadata: + name: quota + namespace: shop +spec: + hard: + requests.cpu: "8" # raised for the sales, see PLAT-142 + requests.memory: 16Gi diff --git a/namespaces/shop/quai.yaml b/namespaces/shop/quai.yaml new file mode 100644 index 0000000..5709b01 --- /dev/null +++ b/namespaces/shop/quai.yaml @@ -0,0 +1,5 @@ +name: shop +owner: alice +team: shop +cpu: "8" +memory: 16Gi diff --git a/platform/xrds/postgresqlinstance.yaml b/platform/xrds/postgresqlinstance.yaml new file mode 100644 index 0000000..8cfda50 --- /dev/null +++ b/platform/xrds/postgresqlinstance.yaml @@ -0,0 +1,41 @@ +# The platform team's PostgreSQL API: claims of kind PostgreSQLInstance, composed by the +# compositions labelled with a provider. +apiVersion: apiextensions.crossplane.io/v1 +kind: CompositeResourceDefinition +metadata: + name: xpostgresqlinstances.database.example.org +spec: + group: database.example.org + names: + kind: XPostgreSQLInstance + plural: xpostgresqlinstances + claimNames: + kind: PostgreSQLInstance + plural: postgresqlinstances + connectionSecretKeys: [username, password, endpoint, port] + versions: + - name: v1alpha1 + served: true + referenceable: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + required: [parameters] + properties: + parameters: + type: object + required: [version, storageGB] + properties: + version: + type: string + enum: ["16", "17"] + storageGB: + type: integer + minimum: 20 + maximum: 1024 + highAvailability: + type: boolean + default: false diff --git a/schemas/app.schema.json b/schemas/app.schema.json new file mode 100644 index 0000000..b3743c1 --- /dev/null +++ b/schemas/app.schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Application", + "type": "object", + "required": ["repoURL", "path", "namespace"], + "additionalProperties": false, + "properties": { + "name": { "type": "string", "title": "Name", "readOnly": true }, + "owner": { "type": "string", "title": "Owner" }, + "repoURL": { "type": "string", "title": "Repository", "description": "The Git repository of the application's manifests.", "pattern": "^https://" }, + "path": { "type": "string", "title": "Path", "description": "The directory of the manifests in that repository.", "minLength": 1 }, + "revision": { "type": "string", "title": "Revision", "description": "A branch, a tag or a commit; HEAD when empty." }, + "namespace": { "type": "string", "title": "Namespace", "description": "The namespace it deploys to.", "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" }, + "autoSync": { "type": "boolean", "title": "Sync automatically", "default": true } + } +} diff --git a/schemas/config.schema.json b/schemas/config.schema.json new file mode 100644 index 0000000..c5901e9 --- /dev/null +++ b/schemas/config.schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Configuration", + "type": "object", + "required": ["data"], + "additionalProperties": false, + "properties": { + "name": { "type": "string", "title": "Name", "readOnly": true }, + "namespace": { "type": "string", "title": "Namespace", "readOnly": true }, + "owner": { "type": "string", "title": "Owner" }, + "data": { + "type": "object", + "title": "Settings", + "minProperties": 1, + "propertyNames": { "pattern": "^[A-Za-z0-9._-]+$" }, + "additionalProperties": { "type": "string" } + } + } +} diff --git a/schemas/database.schema.json b/schemas/database.schema.json new file mode 100644 index 0000000..06029f8 --- /dev/null +++ b/schemas/database.schema.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "PostgreSQL database", + "type": "object", + "required": ["version", "storageGB"], + "additionalProperties": false, + "properties": { + "name": { "type": "string", "title": "Name", "readOnly": true }, + "namespace": { "type": "string", "title": "Namespace", "readOnly": true }, + "owner": { "type": "string", "title": "Owner" }, + "version": { "type": "string", "title": "PostgreSQL version", "enum": ["16", "17"] }, + "storageGB": { "type": "integer", "title": "Storage (GB)", "minimum": 20, "maximum": 1024 }, + "highAvailability": { "type": "boolean", "title": "High availability", "default": false } + } +} diff --git a/schemas/namespace.schema.json b/schemas/namespace.schema.json new file mode 100644 index 0000000..8a1642d --- /dev/null +++ b/schemas/namespace.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Team namespace", + "type": "object", + "required": ["team"], + "additionalProperties": false, + "properties": { + "name": { "type": "string", "title": "Name", "readOnly": true }, + "owner": { "type": "string", "title": "Owner" }, + "team": { "type": "string", "title": "Team", "pattern": "^[a-z][a-z0-9-]*$" }, + "cpu": { "type": "string", "title": "CPU requests", "description": "The namespace's quota of CPU requests, such as 8 or 500m.", "pattern": "^[0-9]+m?$" }, + "memory": { "type": "string", "title": "Memory requests", "description": "The namespace's quota of memory requests, such as 16Gi.", "pattern": "^[0-9]+(Mi|Gi)$" } + } +} diff --git a/schemas/sync.schema.json b/schemas/sync.schema.json new file mode 100644 index 0000000..1071e91 --- /dev/null +++ b/schemas/sync.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Flux sync", + "type": "object", + "required": ["path", "targetNamespace"], + "additionalProperties": false, + "properties": { + "name": { "type": "string", "title": "Name", "readOnly": true }, + "owner": { "type": "string", "title": "Owner" }, + "path": { "type": "string", "title": "Path", "description": "The directory of the platform repository that Flux applies.", "pattern": "^\\./" }, + "targetNamespace": { "type": "string", "title": "Namespace", "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" }, + "interval": { "type": "string", "title": "Interval", "enum": ["1m", "10m", "1h"] } + } +}