First schema
Before you start
Section titled “Before you start”- The operator installed and its CRDs established: Install, and its readiness check.
- A namespace for your own resources. This page uses
application; the chart does not create it, and it is not the release namespace. - A database the operator can reach, and a URL for it. The privileges it needs are Databases and privileges.
- Your schema published as an OCI artifact, by
ptah schema push, in a registry the cluster can read. Which Ptah build to use is Ptah compatibility; the digest that artifact prints is what the example points at. - The
examples/files named below, from the version you chose on Install. They are in the repository at that commit or tag, beside the chart, and an example from a different version can name a field this one does not have.
Create what the schema reads
Section titled “Create what the schema reads”The database URL is a namespaced Secret and the verification policy a
ConfigMap, both in the namespace the resource lives in. Store the URL in a
private file with no trailing newline, outside the repository. Set
DATABASE_URL_FILE to its path. The command below reads that file so the
credential does not become a command argument or shell-history entry:
DATABASE_URL_FILE=/path/to/private/database-urlkubectl create namespace applicationkubectl -n application create secret generic application-database \ --from-file=url="$DATABASE_URL_FILE"kubectl -n application create configmap ptah-verification-policy \ --from-file=policy.yaml=examples/verification-policy.yamlkubectl -n application patch configmap ptah-verification-policy \ --type=merge -p '{"immutable":true}'For PostgreSQL, replace the artifact placeholder in examples/ptahschema.yaml
and apply it:
kubectl apply -f examples/ptahschema.yamlkubectl -n application get ptahschema application -wFor MySQL, use a MySQL database URL in that Secret and publish the schema with
ptah schema push --dialect mysql. Replace the artifact placeholder in
examples/ptahschema-mysql.yaml with the resulting digest, then apply that
manifest instead:
kubectl apply -f examples/ptahschema-mysql.yamlkubectl -n application get ptahschema application -wBoth examples name the resource application, so the plan review and approval
commands below work for either engine. Choose one manifest for this resource.
The MySQL example manages a database of its own; see
shared realms before adding another resource
that writes to the same database.
For private registries, add a
same-namespace registryAuthFrom reference; the API supports environment-key
Secrets and standard Docker config JSON Secrets. Either representation must
contain a fixed registry key whose authority-only host[:port] value exactly
matches the OCI client’s effective request authority (registry-1.docker.io
for an oci://docker.io/... source). A Docker config Secret keeps its standard
.dockerconfigjson data in addition to that owner-controlled grant. If authenticated
plainHTTP is unavoidable, the authentication Secret must also contain
allowPlainHTTP: "true"; anonymous plain HTTP needs no Secret grant.
Verification-policy
ConfigMaps must be immutable. To change a policy, create a new ConfigMap name
and update the schema reference; delete-and-recreate is intentionally not
treated as the same policy.
What the schema reports while it converges is the status progression, and what each reason means is condition reasons.
It stops, and waits for you
Section titled “It stops, and waits for you”The example sets apply: OnApproval, which is also the default. The operator
resolves the artifact, observes the database and publishes a plan, and then
stops: ApprovalRequired becomes True and the phase is AwaitingApproval.
The operator has already connected to the database by this point — observing it is where the plan came from, and that read is what the plan is the difference against. What has not happened is the plan: not one statement it names has run, and none will until the decision below is recorded.
Read the plan before approving it. The SQL is in the controller-owned
PtahSchemaPlanChunk objects the plan names rather than in the status, and
kubectl ptah reads it
back the way the operator does. It is a plugin you
install once:
kubectl ptah plan application -n applicationWithout a selection that is the current plan, which is the one waiting for an approval. Each statement is printed under the severity and reason the plan records for it.
An approval names the schema with its UID, the plan with its UID and that
plan’s fingerprint, and admission stamps who approved and when. kubectl ptah approve shows the plan again with those identities, asks for a decision, and
creates the approval as you. --wait keeps it there until the plan has run:
kubectl ptah approve schema/application -n application --waitIt reads the schema once more before it sends anything. If a newer plan
replaced the one you read, it says so and approves nothing. The operator reads
the database again at its interval, and admission refuses an approval while it
does; an answer given then is sent when that read ends, if the same plan is
waiting again. A script that reviewed the plan passes its fingerprint with
--plan-fingerprint.
--dry-run client prints the approval as YAML instead, to keep in a
repository and create later with kubectl create -f. Admission records
whoever creates it as the approver, so create it as the person who decided:
a controller that applies it is recorded in their place.
Creating it requires a binding that grants the approval verbs, which the
chart’s approver ClusterRole carries together with read access to the plan and
its chunks; examples/approver-plan-reader-role.yaml is the reader half alone.
Exact-plan approvals is why the binding is separate
from the one that writes the desired state.
With the decision recorded the plan runs, and convergence is proved by a second
observation rather than by the Job finishing. That is what --wait waits for:
an Apply of the plan you approved, recorded after the approval, and the
operator’s reading of the database afterwards. If that takes longer than five
minutes, or the Apply fails, it says which, with the phase and the condition
holding the schema; Waiting for the operator has
the rest. Then read where the schema stands:
kubectl ptah schema application -n applicationInSync=True with reason ScopedConverged confirms convergence.
Applying=False with reason JobCompleted says only that the Apply Job
finished, which is a different claim.
Watch the whole sequence, including this one, in the recorded runs.
The next version
Section titled “The next version”A change to the schema is a new artifact. Publish it the way you published the
first, with --format reference added, point the schema at it, and review the
plan it leads to:
REF=$(ptah schema push oci://ghcr.io/example/application-schema:1.5.0 \ --schema-file schema.sql --dialect postgres --format reference)kubectl ptah set-source schema/application -n application --oci "$REF" --for approvalkubectl ptah approve schema/application -n application --wait--format reference prints the reference of the artifact the push wrote,
pinned by its digest, which is the only kind of reference set-source takes.
It came in Ptah v0.17.0; an older push prints the digest in its receipt
instead.
It changes spec.desired.ociRef and nothing else: the verification policy you
created above holds the new artifact too. --for approval waits until the
operator has planned from it, and the plan is the difference between the new
schema and the database as it is now. With approvals.requireDistinctApprover
on, someone else approves it, since set-source records you as the schema’s
last spec writer. Moving to a new artifact
has what set-source refuses and the same change made with kubectl edit.