Skip to content

Instantly share code, notes, and snippets.

@JustinKuli
Last active July 5, 2024 12:59
Show Gist options
  • Save JustinKuli/5ec272d19f0153255ce22cbfe5196123 to your computer and use it in GitHub Desktop.
Save JustinKuli/5ec272d19f0153255ce22cbfe5196123 to your computer and use it in GitHub Desktop.
How to use Results in Tekton Tasks and Pipelines, including a full example.

Tekton Task Results

When writing a Tekton Pipeline, it's common to want to pass information from one Task to another. One way to get information into a Task is via parameters. Then, either inside the a step's script, or via environment variables, it is possible to use variable substitution with those params.

Getting information out of a task/step is more complex, since there are multiple methods. It is possible to store information in a file in a workspace, but that generally requires a PersistentVolume, and you must carefully ensure the Tasks run in the correct order. Results, as we will see, are a better way to pass small bits of information out of a Task.

NOTE: Results can not store more than 4096 bytes. The documentation notes that this limit is per-step, but I have a suspicion that it could be per-task. Regardless, it's not per-result, so a large number of results could cause problems. Generally, if you need more than a kilobyte, consider using another technique.

Writing to a result

First, you must define the name of the result in the Task. You can optionally supply a description, to help users of the Task. As an example, here are the important parts from the pick-a-pokemon Task below:

kind: Task
spec:
  results:
  - name: pokemon
    description: the chosen pokemon

Tekton will automatically create a /tekton/results directory, mounted to the containers running the Task's steps. To emit a result, we must write a file in that directory, matching the name of our result. We could do something like echo 'snorlax' > /tekton/results/pokemon in the container. However, I don't think this is ideal, for a few reasons.

First, hardcoding the results path could be brittle - it's possible that Tekton might change that path in a future version. For that reason, we can use $(results.pokemon.path) and Tekton's variable substitution. With this format, it's also possible that linters would help ensure the named result exists - especially helpful if you ever edit the Task and rename a result.

Second, echo adds a newline to the end of the file, which Tekton preserves in the result. With the command above, the result is more like snorlax\n, which might not be what you want.

Finally, I prefer scripts in Tasks to be executable as-is so I can test them more easily. That means, I don't like to use variable substitution inside of the script itself. Instead, I use an environment variable to store the result path.

So, I think a best practice minimal example is:

kind: Task
spec:
  steps:
  - name: pick
    image: fedora:34
    script: |
      echo -n 'ditto' > "${POKE_FILE}"
    env:
    - name: POKE_FILE
      value: $(results.pokemon.path)
  results:
  - name: pokemon
    description: the chosen pokemon

Using a result in another Task

Results from previous Tasks can be used via variable substitution in Pipelines. The format is $(tasks.<task-name>.results.<result-name>) For example:

kind: Pipeline
spec:
  tasks:
  - name: pick-one
    taskRef:
      name: pick-a-pokemon
  - name: pick-winner
    taskRef:
      name: winner
    params:
    - name: POKEMON_ONE
      value: $(tasks.pick-one.results.pokemon)
    - name: POKEMON_TWO
      value: 'pidgey'

Tekton will automatically determine which tasks require results from other tasks, and run them in the correct order. So in the example above, the pick-winner task will run after the pick-one task, not because of its position in the tasks list, but because of the result dependency.

Demo

Did you know, you can clone gists? After logging in to a Kubernetes cluster with Tekton Pipelines installed, you can apply the resources from this gist like this:

git clone https://gist.github.com/5ec272d19f0153255ce22cbfe5196123.git tekton-using-results
cd tekton-using-results
kubectl apply -f task-pick-a-pokemon.yaml
kubectl apply -f task-winner.yaml
kubectl apply -f pipeline.yaml

To run the pipeline, you could use the Tekton CLI, but I prefer to create a PipelineRun "by hand." To help, I've included a sample pipelinerun in this gist. Create it, wait for it to complete, and get the result with these commands:

run_name="$(kubectl create -f zz_pipelinerun.yaml -o name)"
echo $run_name
kubectl wait --for=condition=Succeeded $run_name
kubectl get $run_name -o json | jq .status.pipelineResults

As a bonus, this Pipeline emits the winner as a Pipeline Result - accessible in the status of the PipelineRun after it completes. Here's an example result:

[
  {
    "name": "best-pokemon",
    "value": "charizard"
  }
]

Here's another result, from a run where a pokemon from the list won:

[
  {
    "name": "best-pokemon",
    "value": "charmander"
  }
]
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: poke-battle
spec:
params:
- name: POKEMON_TWO_OVERRIDE
type: string
default: pikachu
tasks:
- name: pick-one
taskRef:
name: pick-a-pokemon
- name: pick-two
taskRef:
name: pick-a-pokemon
params:
- name: POKEMON_OVERRIDE
value: $(params.POKEMON_TWO_OVERRIDE)
- name: pick-winner
taskRef:
name: winner
params:
- name: POKEMON_ONE
value: $(tasks.pick-one.results.pokemon)
- name: POKEMON_TWO
value: $(tasks.pick-two.results.pokemon)
results:
- name: best-pokemon
value: $(tasks.pick-winner.results.winner)
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: pick-a-pokemon
spec:
params:
- name: POKEMON_LIST
description: the potential pokemon to choose from
default: |-
bulbasaur
charmander
squirtle
- name: POKEMON_OVERRIDE
description: override the randomness and pick this pokemon
default: ""
steps:
- name: pick
image: fedora:34
# This example uses environment variables to avoid variable
# substitution inside the script. This makes it more testable.
script: |
if [[ -z "${CHOSEN_POKEMON}" ]]; then
CHOSEN_POKEMON="$(echo "${POKE_LIST}" | shuf -n 1)"
fi
echo -n "${CHOSEN_POKEMON}" > "${POKE_FILE}"
env:
- name: POKE_LIST
value: $(params.POKEMON_LIST)
- name: CHOSEN_POKEMON
value: $(params.POKEMON_OVERRIDE)
- name: POKE_FILE
value: $(results.pokemon.path)
results:
- name: pokemon
description: the chosen pokemon
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: winner
spec:
params:
- name: POKEMON_ONE
- name: POKEMON_TWO
steps:
- name: pick
image: fedora:34
# This example uses variable substitution in the script, which I dislike,
# but it makes it much more compact.
script: |
COIN=$((RANDOM%2))
if [[ "${COIN}" == "0" ]]; then
echo -n "$(params.POKEMON_ONE)" > $(results.winner.path)
echo -n "$(params.POKEMON_TWO)" > $(results.loser.path)
else
echo -n "$(params.POKEMON_ONE)" > $(results.loser.path)
echo -n "$(params.POKEMON_TWO)" > $(results.winner.path)
fi
results:
- name: winner
description: the winning pokemon
- name: loser
description: the losing pokemon
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
generateName: poke-battle-
spec:
pipelineRef:
name: poke-battle
params:
- name: POKEMON_TWO_OVERRIDE
value: charizard
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment