Writing a Promise
Pre-requisites
You need an installation of Kratix for this section. Click here for instructions
The simplest way to do so is by running the quick-start script from within the Kratix directory. The script will create two KinD clusters, install, and configure Kratix.
./scripts/quick-start.sh --recreate
You can run Kratix either with a multi-cluster or a single-cluster setup. The commands on the remainder of this document assume that two environment variables are set:
PLATFORM
representing the platform cluster Kubernetes contextWORKER
representing the worker cluster Kubernetes context
If you ran the quick-start script above, do:
export PLATFORM="kind-platform"
export WORKER="kind-worker"
For single cluster setups, the two variables should be set to the same value. You can find your cluster context by running:
kubectl config get-contexts
Refer back to Installing Kratix for more details.
In Installing and using a Promise you learned about what a Kratix Promise is, and how to install an existing Promise. In this guide, we are about to write our very first promise and use it.
Promise Architecture
Before jumping into writing a Promise, here is a quick review of the Promise Architecture.
At a very high-level, a Promise is made up of four parts:
- The Promise API: The Promise API is what the users of the Platform will interact with when requesting a new Resource from the Promised Service
- The Imperative Workflow: A series of steps where Platform teams can codify all of their business requirements.
- The Declarative State: The Workflow executes a series of imperative steps to generate a declarative state that is then persisted to the State Store. Other systems will then converge on that state.
- The Dependencies: A dependency is anything that must be installed or made available on Destinations to enable the promised service to run.
As you go through building your own Promise, you will explore each of these four sections in detail.
Promise API
In this tutorial, you are going to develop a database Promise that delivers Postgres as a service. We will use the Kratix CLI for promise writing. Please install the CLI before continuing.
We will first start by initializing the Promise directory. Create an empty directory and run the following Kratix CLI command:
kratix init promise database --group demo.kratix.io --kind Database
This will scaffold a Promise definition promise.yaml
and a resource request example-resource.yaml
within the directory.
Next, we can start thinking about the promise by designing the Promise API. For example, you might want to require parameters, validate users input, and set certain defaults. For now, we are going to start with a simple API by asking users to give us the size of the database they are requesting.
Run kratix update api
command to add this new property to the database Promise API.
kratix update api --property size:string
If you inspect your promise definition now, you can see the field size
has been added to the API:
apiVersion: platform.kratix.io/v1alpha1
kind: Promise
metadata:
creationTimestamp: null
labels:
kratix.io/promise-version: v0.0.1
name: database
spec:
api:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
creationTimestamp: null
name: databases.test.kratix.io
spec:
group: test.kratix.io
names:
kind: Database
plural: databases
singular: database
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
properties:
spec:
properties:
size:
type: string
type: object
type: object
# ...
workflows:
promise: {}
resource: {}
That's all we need for Promise API in this guide. Let's continue to talk about Promise dependencies.
Promise Dependencies
We will use an OSS Postgres Operator to deploy a Postgres database. First, let's download the dependencies into a local directory.
# run within your Promise development directory
mkdir deps
curl -o deps/operator.yaml --silent https://raw.githubusercontent.com/syntasso/promise-postgresql/refs/heads/main/internal/configure-pipeline/dependencies/operator.yaml
curl -o deps/operatorconfiguration.crd.yaml --silent https://raw.githubusercontent.com/syntasso/promise-postgresql/refs/heads/main/internal/configure-pipeline/dependencies/operatorconfiguration.crd.yaml
curl -o deps/postgresql.crd.yaml --silent https://raw.githubusercontent.com/syntasso/promise-postgresql/refs/heads/main/internal/configure-pipeline/dependencies/postgresql.crd.yaml
curl -o deps/postgresteam.crd.yaml --silent https://raw.githubusercontent.com/syntasso/promise-postgresql/refs/heads/main/internal/configure-pipeline/dependencies/postgresteam.crd.yaml
Next, we will use the Kratix CLI to add a Promise Configure workflow to our database Promise, so these dependencies are installed when we install the Promise.
kratix update dependencies ./deps/ --image kratix-guide/database-promise-pipeline:v0.1.0
We also need to build and load this image into our local environment:
docker build -t kratix-guide/database-promise-pipeline:v0.1.0 workflows/promise/configure/dependencies/configure-deps
kind load docker-image kratix-guide/database-promise-pipeline:v0.1.0 --name platform
Always remember to load new images into your environment when using KinD!
Resource Configure Workflows
With our Promise dependencies in place, we are now ready to fulfill resource requests. When a user makes a request of this database Promise, the Resource Configure workflow will execute, and we will need to provision a Postgres database in this workflow.
To start, we can use the Kratix CLI again to add a container to the Resource Configure workflow:
kratix add container resource/configure/database-configure --image kratix-guide/database-resource-pipeline:v0.1.0 --language python
Take a look at the file workflows/resource/configure/database-configure/kratix-guide-database-resource-pipeline/scripts/pipeline.py
This is our Resource Configure script, and it's written in Python using the Kratix Python SDK. Let's replace the boilerplate in the script so it generates a Postgres database for us:
import kratix_sdk as ks
import yaml
def main():
sdk = ks.KratixSDK()
resource = sdk.read_resource_input()
name = resource.get_name()
size = resource.get_value("spec.size")
manifest = {
"apiVersion": "acid.zalan.do/v1",
"kind": "postgresql",
"metadata": {"name": name, "namespace": "default"},
"spec": {
"teamId": "kratix",
"enableLogicalBackup": True,
"volume": {
"size": size
},
"numberOfInstances": 2,
"users": {
"team-a": ["superuser", "createdb"]
},
"postgresql": {
"version": "16"
}
}
}
data = yaml.safe_dump(manifest).encode("utf-8")
sdk.write_output("database.yaml", data)
status = ks.Status()
status.set("teamId", "kratix")
sdk.write_status(status)
if __name__ == '__main__':
main()
Let's build and load this image into our local environment:
docker build -t kratix-guide/database-resource-pipeline:v0.1.0 workflows/resource/configure/database-configure/kratix-guide-database-resource-pipeline
kind load docker-image kratix-guide/database-resource-pipeline:v0.1.0 --name platform
Again, always remember to load new images into your environment when using KinD!
Using the Promise
We are ready to install our database Promise! Install the Promise by running:
kubectl apply -f promise.yaml --context $PLATFORM
You can check on the installation of the Promise by running:
$ kubectl get promise database --context $PLATFORM -w
NAME STATUS KIND API VERSION VERSION
database Unavailable Database test.kratix.io/v1alpha1 v0.0.1
database Available Database test.kratix.io/v1alpha1 v0.0.1
Once this Promise becomes available, we can make a resource request.
Take a look at example-resource.yaml
and set a size for this Postgres database:
apiVersion: test.kratix.io/v1alpha1
kind: Database
metadata:
name: example-database
spec:
size: "1Gi"
You can now make a request:
kubectl apply -f example-resource.yaml --context $PLATFORM
Now that the request is made, let's check the status of this resource:
$ kubectl describe databases.test.kratix.io example-database --context $PLATFORM
Spec:
Size: 1Gi
Status:
Conditions:
Message: Reconciled
Reason: Reconciled
Status: True
Type: Reconciled
...
Message: Resource requested
Observed Generation: 1
Team Id: kratix
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal PipelineStarted 9s ResourceRequestController Configure Pipeline started: database-configure
Normal WorksSucceeded 1s ResourceRequestController All works associated with this resource are ready
Normal ReconcileSucceeded 1s ResourceRequestController Successfully reconciled
Finally, you can also take a look at the provisioned database by targeting the destination
$ kubectl get pods --context $WORKER
NAME READY STATUS RESTARTS AGE
example-database-0 1/1 Running 0 2m35s
example-database-1 1/1 Running 0 2m12s
postgres-operator-578ff5d886-mkqxg 1/1 Running 0 4m35s
🎉 Congratulations
✅ You have created your first Promise!.
👉🏾 You can now explore Compound Promises.