Logo

Documentation

Using Params

Overview

In this tutorial we’ll explore how to use params to pass information that isn’t anticipated by the standard workload fields. We’ll see how params can either be set or delegated to other objects in the supply chain.

Environment setup

For this tutorial you will need a kubernetes cluster with Cartographer installed. You may follow the installation instructions here.

Alternatively, you may choose to use the ./hack/setup.sh script to install a kind cluster with Cartographer. This script is meant for our end-to-end testing and while we rely on it working in that role, no user guarantees are made about the script.

Command to run from the Cartographer directory:

./hack/setup.sh cluster cartographer-latest

If you later wish to tear down this generated cluster, run

./hack/setup.sh teardown

Scenario

As in the tutorial “Build Your First Supply Chain”, we will act as both the app dev and app operator in a company creating hello world web applications. Our applications will again have been built and stored in a registry. But now each image will be in some private registry and each app dev will need to provide the appropriate service account with imagePullCredentials for our deployment. We will assume that the app devs have already created the necessary service account in their namespace. We will see how to write our templates to accept a parameter with the service account name as well as how to write a workload to supply that value.

Steps

App Operator Steps

A new field must be added to our template of a deployment:

apiVersion: carto.run/v1alpha1
kind: ClusterTemplate
metadata:
  name: app-deploy
spec:
  template:
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: $(workload.metadata.name)$-deployment
      labels:
        app: $(workload.metadata.name)$
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: $(workload.metadata.name)$
      template:
        metadata:
          labels:
            app: $(workload.metadata.name)$
        spec:
          serviceAccountName: # <=== NEW FIELD
          containers:
            - name: $(workload.metadata.name)$
              image: $(workload.spec.image)$

Let’s assume that the app operator has created an image registry in which they have expansive read credentials. The operator can reasonably expect that many devs will use this registry. So the operator can be responsible for the creation of a service account with the correct imagePullCredentials and can make sure this object is in the expected namespaces with the expected name. Let’s set this expected name as “expected-service-account”.

While this will be the default value, we will allow the developer to override this choice. We’ll do this by setting the name as a param for the ClusterTemplate:

apiVersion: carto.run/v1alpha1
kind: ClusterTemplate
metadata:
  ...
spec:
  template:
    ...
    spec:
      ...
      template:
        ...
        spec:
          serviceAccountName: $(params.image-pull-sa-name)$
          ...
  params:
    - name: image-pull-sa-name
      default: expected-service-account

Here we see two changes to the ClusterTemplate’s spec:

  • The first is a field in the template. The field has been filled with a reference to a param: $(params.image-pull-sa-name)$
  • Second, we see that a new field has been introduced to the spec: params. This is a list of params, each of which requires a name and a default value. Here, the app operator indicates that many devs will have a service account named expected-service-account in their namespace.

We can see here the full object we’ll apply to the cluster:

---
apiVersion: carto.run/v1alpha1
kind: ClusterTemplate
metadata:
  name: app-deploy
spec:
  template:
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: $(workload.metadata.name)$-deployment
      labels:
        app: $(workload.metadata.name)$
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: $(workload.metadata.name)$
      template:
        metadata:
          labels:
            app: $(workload.metadata.name)$
        spec:
          serviceAccountName: $(params.image-pull-sa-name)$
          containers:
            - name: $(workload.metadata.name)$
              image: $(workload.spec.image)$
  params:
    - name: image-pull-sa-name
      default: expected-service-account

We apply this template to the cluster along with the same supply chain from the “Build Your First Supply Chain” tutorial.

App Dev Steps

As app devs, we’ve decided not to follow the app operator service account naming convention. We’ve created a service account named “unconventionally-named-service-account” which has the imagePullSecrets to get our app image. In the workload, we’ll create a param. Params in workloads require two fields, a name and a value. We must give our param the same name as the param in the template, image-pull-sa-name. And for the value, we’ll provide our chosen service account’s name.

spec:
  params:
    - name: image-pull-sa-name
      value: unconventionally-named-service-account

We can now apply this workload to the cluster:

---
apiVersion: carto.run/v1alpha1
kind: Workload
metadata:
  name: hello-again
  labels:
    workload-type: pre-built
spec:
  image: 0.0.0.0:5000/hello-world # <=== Change this to some proper registry and image
  serviceAccountName: cartographer-pre-built-sa
  params:
    - name: image-pull-sa-name
      value: unconventionally-named-service-account

Observe

When we observe the created deployment, we can see that the value specified by the workload is present:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-again-deployment
  ...
spec:
  template:
    metadata: ...
    spec:
      Containers: ...
      dnsPolicy: ...
      restartPolicy: ...
      schedulerName: ...
      securityContext: {}
      serviceAccount: unconventionally-named-service-account     # <=== huzzah!
      serviceAccountName: unconventionally-named-service-account
      terminationGracePeriodSeconds: 30
  ...
status: ...

Further information

Params are created to allow delegation between different personas. The individual writing a template may be different from the person writing the supply chain. Perhaps the template author is unsure of a parameter’s value, but the supply chain author knows exactly the desired value and does not want the workload author to be able to override their choice. The supply chain can be altered to specify a value.

---
apiVersion: carto.run/v1alpha1
kind: ClusterSupplyChain
metadata:
  name: supply-chain
spec:
  selector:
    workload-type: pre-built

  resources:
    - name: deploy
      templateRef:
        kind: ClusterTemplate
        name: app-deploy
      params: # <=== New field
        - name: image-pull-sa-name
          value: inevitable-service-account

If the supply chain is redeployed with this definition, we can observe that the deployment changes:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-deployment
  ...
spec:
  template:
    metadata: ...
    spec:
      Containers: ...
      dnsPolicy: ...
      restartPolicy: ...
      schedulerName: ...
      securityContext: {}
      serviceAccount: inevitable-service-account
      serviceAccountName: inevitable-service-account
      terminationGracePeriodSeconds: 30
  ...
status: ...

Further information about params and the order of precedence can be found in the “Parameter Heirarchy” architecture documentation.

In the Cartographer tests you can find an example of creating an object with numerous parameters which demonstrates the precedence rules:

Wrap Up

Congratulations, you’ve used parameters in your supply chain! You’ve learned:

  • How a template requests information from a workload not available in the standard workload fields
  • How to provide default values for params
  • How the supply chain can provide mandatory values for params
  • How to find more information on params