Products and Orders
A Product defines an application completely: its source, how it renders, its base values and patches, what happens to its images, and what its Orders may change. An Order is one variant of a Product, such as one environment, and declares only what differs. A Product on its own is never rendered or deployed; its Orders are.
A common split: a platform team owns the Product, application teams own its Orders, and the Product’s overrides decide what the teams may change.
The Product
apiVersion: delivery.kokumi.dev/v1alpha1
kind: Product
metadata:
name: podinfo
spec:
# A Helm chart pushed to an OCI registry.
source:
url: oci://ghcr.io/stefanprodan/charts/podinfo
version: "6.14.1"
# Render the chart with these base values. The release name and namespace
# default to each Order's name and namespace.
render:
helm:
values:
ui:
color: "#EF6461"
# Orders of this Product may set these values and nothing else. Without
# overrides, Orders may not change anything.
overrides:
values:
policy: Listed
paths:
- ui.message
- replicaCountThe Product resolves its source tag to a digest, verifies its signature when
required (see Source Verification), and publishes
the result in status.source: URL, version, digest and verified signer.
Orders render that digest, and only once the Product is Ready for its
current generation, so every Order renders the same artifact even if the tag
moves upstream. The Product resolves the tag again only when its source or
vendor location changes.
kubectl get products -n shop
# NAME READY REASON SOURCE VERSION VERIFIED AGE
# podinfo True Resolved oci://ghcr.io/stefanprodan/charts/podinfo 6.14.1 1mA vendored Product reports Vendored instead of Resolved.
The Product’s registryRefs name the Registries that authenticate its
source, its images and its vendored copy. See
Registries and Repositories.
Sources
Kokumi renders four kinds of OCI artifacts:
| Source | The Product sets | A Preparation holds | Example |
|---|---|---|---|
| A Helm chart | spec.render.helm | manifest.yaml, the rendered chart | getting-started |
| YAML files at the artifact’s root | nothing | one manifest.yaml, every top-level *.yaml and *.yml file merged | manifest-bundle |
| YAML files at the artifact’s root | spec.render.manifest.layout: Multi | the files, as they are | multi-file-manifests |
A kustomization.yaml at the artifact’s root | nothing | the files, as they are; Argo CD builds the kustomization when it deploys | kustomize |
Patches apply to every kind; Helm values only to charts.
apiVersion: delivery.kokumi.dev/v1alpha1
kind: Product
metadata:
name: external-secrets
spec:
# An OCI artifact with a single manifest.yaml at its root. Several files are
# merged into one manifest.yaml unless render.manifest.layout is Multi.
source:
url: oci://ghcr.io/kokumi-dev/testdata/external-secrets
version: "0.1.0"
# Orders of this Product may only scale the webhook Deployment.
overrides:
patches:
policy: Listed
targets:
- target:
kind: Deployment
name: external-secrets-webhook
paths:
- .spec.replicasapiVersion: delivery.kokumi.dev/v1alpha1
kind: Order
metadata:
name: external-secrets
spec:
productRef:
name: external-secrets
environmentRef:
name: local
# Patches set fields of the rendered manifests, by path. Values are strings.
patches:
- target:
kind: Deployment
name: external-secrets-webhook
set:
.spec.replicas: "2"A Helm chart renders with these options of spec.render.helm:
| Field | Description | Default |
|---|---|---|
releaseName | The Helm release name | The Order’s name |
includeCRDs | Include the chart’s CRDs in the output | false |
values | Base values; each Order’s spec.values are deep-merged over them | none |
Helm renders into the Order’s target namespace.
A kustomization is built by Argo CD, not by Kokumi, so Kokumi never sees the
resources it produces. Patches still apply to the files, but the Pin and
Vendor image policies need rendered manifests and fail; see
Container Images.
To publish a source of your own, with the annotations that link each Preparation back to its commit, see Producing Base Artifacts.
The Order
apiVersion: delivery.kokumi.dev/v1alpha1
kind: Order
metadata:
name: podinfo
spec:
productRef:
name: podinfo
# The Environment it deploys to, in the Order's namespace. Kokumi keeps the
# Environment local in its own namespace, kokumi: the cluster it runs in.
# The Environment cannot be changed later; another Environment is another
# Order.
environmentRef:
name: local
# Deep-merged over the Product's values; the Order's win.
values:
ui:
message: "Hello from Kokumi"
replicaCount: 2
# Every change renders a new Preparation, an immutable OCI artifact in the
# bundled registry. Nothing deploys until you promote a Preparation.
promotion:
mode: Manual| Field | What it does |
|---|---|
productRef | The Product, in the Order’s namespace. It cannot be changed after creation. |
environmentRef | The Environment the Order deploys to, in its namespace. Required, and it cannot be changed: another Environment is another Order. |
values | Helm values deep-merged over the Product’s render.helm.values; the Order wins. Only valid for a Helm source. |
patches | Patches merged over the Product’s, per target; on the same path the Order wins. |
edits | Patches the UI writes when you edit a rendered file, applied after patches. They change rendered resources; the editor refuses added or renamed ones. |
targetNamespace | Where the workload lands on the clusters. See The target namespace. |
destination | Where Preparations are pushed. Defaults to <bundled registry>/<namespace>/<order>. |
registryRefs | The Registries that authenticate the destination, and only the destination. |
promotion | How Preparations are promoted: the mode, and the gates. See Promotion and Rollback. |
An Order’s name is at most 63 characters. Each Order has its own Preparation
history, its own Serving, and its own gates, so a new Product version can
deploy to podinfo-dev automatically while podinfo-prod waits for
approvals.
kubectl get orders -n shop
# NAME READY REASON PRODUCT ENVIRONMENT LATEST PREPARATION AGE
# podinfo True Ready podinfo local podinfo-d7ce0c46a686 1mWhile its Product is missing, not Ready, or being deleted, an Order reports
Ready=False with reason ProductNotFound, ProductNotReady or
ProductDeleting and waits; the UI shows it as Waiting. An Order that
sets what its Product does not allow, or whose render fails, reports
ProcessingFailed with the reason in the message. A failed render is
retried at growing intervals, up to about 17 minutes apart, and keeps
reporting its failure meanwhile; a change of the Order, its Product or its
Registries renders at once. An Order whose patches or edits do
not apply reports PatchFailed instead, and is not retried: it renders
again once the Order or its Product changes.
Describing a change
The annotation delivery.kokumi.dev/commit-message on the Order becomes the
description of the next Preparation, like a commit message. Kokumi removes it
after the render. Without it, the first Preparation is described as
Initial commit and later ones as Automatically generated.
kubectl annotate order podinfo -n shop delivery.kokumi.dev/commit-message="Scale to 3 replicas"
kubectl patch order podinfo -n shop --type=merge -p '{"spec":{"values":{"replicaCount":3}}}'Patches
A patch sets fields of the resources of one target, by path:
spec:
patches:
- target:
kind: Deployment
name: podinfo
# namespace: shop # optional; without it, any namespace matches
set:
.spec.replicas: "3"
.spec.template.spec.containers[0].imagePullPolicy: Always- A path is dot-separated, with
[n]for a list index. Missing fields are created; a missing list, an index out of bounds, or a path through a scalar or a list without an index fails. - A patch that does not apply fails the render: a path that cannot be set,
or a target that matches no resource in any file of the source. The Order
reports
PatchFailed, naming the target and the path, and no Preparation is made; its earlier Preparations and its Serving stay. The same holds for edits, so an edit whose resource a new Product version renamed shows up, and you drop it from the Order. A Product patch on a resource an Order turns off through its values fails that Order too. - Values are strings in the Order, and are written as booleans or numbers
when they read as one:
"3"becomes3,"true"becomestrue. - Every document is written back with 2-space indentation, whether a patch matches it or not, so the same input always renders the same bytes. A document that is not valid YAML fails the render with its file and document number.
Restrict what Orders may change
spec.overrides on the Product decides what its Orders may change,
separately for Helm values and for patches. The patch policy covers edits
made in the UI too.
policy | Orders may set |
|---|---|
None (default) | nothing |
Listed | only the listed Helm value paths (leaf paths, dot-separated), or the listed patch targets and paths |
All | anything |
spec:
overrides:
values:
policy: Listed
paths:
- replicaCount
- ui.message
patches:
policy: Listed
targets:
- target:
kind: Deployment
name: podinfo
paths:
- .spec.replicaspaths and targets are required with Listed and rejected otherwise. A
target without a namespace matches the resource in any namespace; when
several targets match, the paths of all of them are allowed. An Order that
sets anything else fails with ProcessingFailed, naming the offending value
or patch.
The target namespace
The target namespace is where the workload lands on every cluster of the
Order’s Environment: the Order’s targetNamespace, else the Product’s, else
the Order’s own namespace. Helm renders into it, and Argo CD deploys
resources without a namespace there, creating the namespace when it is
missing.
The target namespace is part of the intent, so a Preparation deploys to the same namespace wherever it is promoted. Changing it renders a new Preparation, and deploying that one moves the workload: Argo CD deletes it in the old namespace.
In the example target-namespace, the Product deploys into storefront, one
Order keeps that namespace, and the other picks its own:
apiVersion: delivery.kokumi.dev/v1alpha1
kind: Product
metadata:
name: storefront
spec:
source:
url: oci://ghcr.io/stefanprodan/charts/podinfo
version: "6.14.1"
# Every Order of the Product deploys into the namespace storefront, not
# into its own namespace, unless the Order names another one. Helm renders
# the release into it, and Argo CD creates it when it is missing.
targetNamespace: storefront
render:
helm:
values:
ui:
message: "Hello from the storefront"apiVersion: delivery.kokumi.dev/v1alpha1
kind: Order
metadata:
name: storefront
spec:
productRef:
name: storefront
environmentRef:
name: local
# No targetNamespace: the Order deploys into the Product's, storefront.
# Every Ready Preparation deploys at once.
promotion:
mode: AutomaticapiVersion: delivery.kokumi.dev/v1alpha1
kind: Order
metadata:
name: storefront-review
spec:
productRef:
name: storefront
environmentRef:
name: local
# An Order may always pick its own target namespace, here one next to the
# Product's. The target namespace is part of the intent: changing it renders
# a new Preparation, and deploying that one moves the workload.
targetNamespace: storefront-review
promotion:
mode: Automatickubectl apply -n shop -k "https://github.com/kokumi-dev/kokumi//examples/target-namespace?ref=0.17.0"What renders a new Preparation
Kokumi merges an Order over its Product into the intent, the effective configuration that determines the artifact. Whenever the intent changes, the Order renders a new Preparation; while it stays the same, nothing is rendered.
| Part of the intent | Not part of the intent |
|---|---|
| The source: URL, version and resolved digest | The promotion mode |
| The merged render config and Helm values | The overrides |
| The merged patches, and the edits | The Environment |
| The target namespace, always | The commit message |
The image policy and field specs, unless they are the defaults, and the vendor location under Vendor | The digests images resolve to: a tag that moves upstream renders nothing |
| The gates: approvals, upstream stages and checks | The Registries, their Secrets, and the Kitchen’s image mirrors |
| The destination | |
| The Product revision |
Changing the gates renders a new Preparation that needs fresh approvals. Because the intent holds the Product revision, a change to the Product’s source, render config, patches or image handling renders every Order again, also an Order whose own values or patches hide the change.
Artifacts in the Registry explains how the intent is stored and hashed.
Vendoring the source
A Product can copy its source, unchanged, to another OCI registry, for example to prepare an air-gapped installation. Kokumi copies the source by the digest that verified, together with its referrers, so the copy keeps the publisher’s signatures and attestations and still verifies against the publisher’s key. Orders then render from the copy:
spec:
source:
url: oci://ghcr.io/kokumi-dev/testdata/external-secrets
version: "0.1.0"
vendor:
url: oci://harbor.corp/mirror/external-secrets # vendor: {} copies to <namespace>/vendor/<name> in the bundled registry
registryRefs:
- name: harbor-mirror # its url covers the vendor locationThe Product’s Registries authenticate both the upstream source and the copy,
and status.source names the copy, so Orders need no knowledge of the
vendoring. Signatures that cosign stored under legacy sha256-<digest>.sig
tags are not referrers and are not copied. The Product’s Artifact tab
draws the source and the copy side by side.
The images of the rendered manifests can be vendored below the copy too; see Container Images.
Deleting
- A Product cannot be deleted while Orders reference it. The finalizer
delivery.kokumi.dev/product-protectionkeeps it, and itsReadycondition reportsInUsewith the remaining Orders. The UI shows it as Deleting and offers no new Orders for it. - An Order takes its Preparations and its Serving with it, and the
Serving deletes its Argo CD Applications. The workloads keep running: the
Applications carry no
resources-finalizer.argocd.argoproj.io, so Argo CD leaves their resources in place. The artifacts stay in the registry.kubectl delete order <name> --cascade=orphankeeps the Serving, and a later Order of the same name adopts it. - Preparations are never pruned while their Order exists, so every version can be promoted again.