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.
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
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.
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"
}
]