tekton weight 1660 TaskRun result attestations is currently an alpha experimental feature Currently all that is implemented is support for configuring Tekton to connect to SPIRE See TEP 0089 for details on the overall design and feature set TaskRun Result Attestation This is a work in progress SPIRE support is not yet functional

Answer:
<!--
---
linkTitle: "TaskRun Result Attestation"
weight: 1660
---
-->
⚠ This is a work in progress: SPIRE support is not yet functional

TaskRun result attestations is currently an alpha experimental feature. Currently all that is implemented is support for configuring Tekton to connect to SPIRE. See TEP-0089 for details on the overall design and feature set.

This being a large feature, this will be implemented in the following phases. This document will be updated as we implement new phases.
1.  Add a client for SPIRE (done).
2.  Add a configMap which initializes SPIRE (in progress).
3.  Modify TaskRun to sign and verify TaskRun Results using SPIRE.
4.  Modify Tekton Chains to verify the TaskRun Results.

## Architecture Overview

This feature relies on a SPIRE installation. This is how it integrates into the architecture of Tekton:

```
┌─────────────┐  Register TaskRun Workload Identity           ┌──────────┐
│             ├──────────────────────────────────────────────►│          │
│  Tekton     │                                               │  SPIRE   │
│  Controller │◄───────────┐                                  │  Server  │
│             │            │ Listen on TaskRun                │          │
└────────────┬┘            │                                  └──────────┘
 ▲           │     ┌───────┴───────────────────────────────┐     ▲
 │           │     │           Tekton TaskRun              │     │
 │           │     └───────────────────────────────────────┘     │
 │  Configure│                                          ▲        │ Attest
 │  Pod &    │                                          │        │   +
 │  check    │                                          │        │ Request
 │  ready    │     ┌───────────┐                        │        │ SVIDs
 │           └────►│  TaskRun  ├────────────────────────┘        │
 │                 │  Pod      │                                 │
 │                 └───────────┘     TaskRun Entrypointer        │
 │                   ▲               Sign Result and update      │
 │ Get               │ Get SVID      TaskRun status with         │
 │ SPIRE             │               signature + cert            │
 │ server            │                                           │
 │ Credentials       │                                           ▼
┌┴───────────────────┴─────────────────────────────────────────────────────┐
│                                                                          │
│   SPIRE Agent    ( Runs as   )                                           │
│   + CSI Driver   ( Daemonset )                                           │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘
```

Initial Setup:
1. As part of the SPIRE deployment, the SPIRE server attests the agents running on each node in the cluster.
1. The Tekton Controller is configured to have workload identity entry creation permissions to the SPIRE server.
1. As part of the Tekton Controller operations, the Tekton Controller will retrieve an identity that it can use to talk to the SPIRE server to register TaskRun workloads.

When a TaskRun is created:
1. The Tekton Controller creates a TaskRun pod and its associated resources
1. When the TaskRun pod is ready, the Tekton Controller registers an identity with the information of the pod to the SPIRE server. This will tell the SPIRE server the identity of the TaskRun to use as well as how to attest the workload/pod.
1. After the TaskRun steps complete, as part of the entrypointer code, it requests an SVID from SPIFFE workload API (via the SPIRE agent socket)
1. The SPIRE agent will attest the workload and request an SVID.
1. The entrypointer receives an x509 SVID, containing the x509 certificate and associated private key.
1. The entrypointer signs the results of the TaskRun and emits the signatures and x509 certificate to the TaskRun results for later verification.

## Enabling TaskRun result attestations

To enable TaskRun attestations:
1. Make sure `enforce-nonfalsifiability` is set to `"spire"`. See [`additional-configs.md`](./additional-configs.md#customizing-the-pipelines-controller-behavior) for details
1. Create a SPIRE deployment containing a SPIRE server, SPIRE agents and the SPIRE CSI driver, for convenience, [this sample single cluster deployment](https://github.com/spiffe/spiffe-csi/tree/main/example/config) can be used.
1. Register the SPIRE workload entry for Tekton with the "Admin" flag, which will allow the Tekton controller to communicate with the SPIRE server to manage the TaskRun identities dynamically.
    ```

    # This example is assuming use of the above SPIRE deployment
    # Example where trust domain is "example.org" and cluster name is "example-cluster"

    # Register a node alias for all nodes of which the Tekton Controller may reside
    kubectl -n spire exec -it \
        deployment/spire-server -- \
        /opt/spire/bin/spire-server entry create \
            -node \
            -spiffeID spiffe://example.org/allnodes \
            -selector k8s_psat:cluster:example-cluster

    # Register the tekton controller workload to have access to creating entries in the SPIRE server
    kubectl -n spire exec -it \
        deployment/spire-server -- \
        /opt/spire/bin/spire-server entry create \
            -admin \
            -spiffeID spiffe://example.org/tekton/controller \
            -parentID spiffe://example.org/allnode \
            -selector k8s:ns:tekton-pipelines \
            -selector k8s:pod-label:app:tekton-pipelines-controller \
            -selector k8s:sa:tekton-pipelines-controller

    ```

1. Modify the controller (`config/controller.yaml`) to provide access to the SPIRE agent socket.
    ```yaml
    # Add the following the volumeMounts of the "tekton-pipelines-controller" container
    - name: spiffe-workload-api
      mountPath: /spiffe-workload-api
      readOnly: true

    # Add the following to the volumes of the controller pod
    - name: spiffe-workload-api
      csi:
        driver: "csi.spiffe.io"
    ```
1. (Optional) Modify the configmap (`config/config-spire.yaml`) to configure non-default SPIRE options.
    ```yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: config-spire
      namespace: tekton-pipelines
    labels:
      app.kubernetes.io/instance: default
      app.kubernetes.io/part-of: tekton-pipelines
    data:
      # More explanation about the fields is at the SPIRE Server Configuration file
      # https://spiffe.io/docs/latest/deploying/spire_server/#server-configuration-file
      # spire-trust-domain specifies the SPIRE trust domain to use.
      spire-trust-domain: "example.org"
      # spire-socket-path specifies the SPIRE agent socket for SPIFFE workload API.
      spire-socket-path: "unix:///spiffe-workload-api/spire-agent.sock"
      # spire-server-addr specifies the SPIRE server address for workload/node registration.
      spire-server-addr: "spire-server.spire.svc.cluster.local:8081"
      # spire-node-alias-prefix specifies the SPIRE node alias prefix to use.
      spire-node-alias-prefix: "/tekton-node/"
    ```

linkTitle   TaskRun Result Attestation  weight  1660            This is a work in progress  SPIRE support is not yet functional  TaskRun result attestations is currently an alpha experimental feature  Currently all that is implemented is support for configuring Tekton to connect to SPIRE  See TEP 0089 for details on the overall design and feature set   This being a large feature  this will be implemented in the following phases  This document will be updated as we implement new phases  1   Add a client for SPIRE  done   2   Add a configMap which initializes SPIRE  in progress   3   Modify TaskRun to sign and verify TaskRun Results using SPIRE  4   Modify Tekton Chains to verify the TaskRun Results      Architecture Overview  This feature relies on a SPIRE installation  This is how it integrates into the architecture of Tekton                        Register TaskRun Workload Identity                                                                                Tekton                                                        SPIRE        Controller                                                    Server                                 Listen on TaskRun                                                                                Tekton TaskRun                                                                                Configure                                                      Attest     Pod                                                                    check                                                          Request     ready                                                          SVIDs                       TaskRun                                                            Pod                                                                               TaskRun Entrypointer                                               Sign Result and update           Get                 Get SVID      TaskRun status with              SPIRE                             signature   cert                 server                                                             Credentials                                                                                SPIRE Agent      Runs as                                                       CSI Driver     Daemonset                                                                                Initial Setup  1  As part of the SPIRE deployment  the SPIRE server attests the agents running on each node in the cluster  1  The Tekton Controller is configured to have workload identity entry creation permissions to the SPIRE server  1  As part of the Tekton Controller operations  the Tekton Controller will retrieve an identity that it can use to talk to the SPIRE server to register TaskRun workloads   When a TaskRun is created  1  The Tekton Controller creates a TaskRun pod and its associated resources 1  When the TaskRun pod is ready  the Tekton Controller registers an identity with the information of the pod to the SPIRE server  This will tell the SPIRE server the identity of the TaskRun to use as well as how to attest the workload pod  1  After the TaskRun steps complete  as part of the entrypointer code  it requests an SVID from SPIFFE workload API  via the SPIRE agent socket  1  The SPIRE agent will attest the workload and request an SVID  1  The entrypointer receives an x509 SVID  containing the x509 certificate and associated private key   1  The entrypointer signs the results of the TaskRun and emits the signatures and x509 certificate to the TaskRun results for later verification      Enabling TaskRun result attestations  To enable TaskRun attestations  1  Make sure  enforce nonfalsifiability  is set to   spire    See   additional configs md     additional configs md customizing the pipelines controller behavior  for details 1  Create a SPIRE deployment containing a SPIRE server  SPIRE agents and the SPIRE CSI driver  for convenience   this sample single cluster deployment  https   github com spiffe spiffe csi tree main example config  can be used  1  Register the SPIRE workload entry for Tekton with the  Admin  flag  which will allow the Tekton controller to communicate with the SPIRE server to manage the TaskRun identities dynamically                 This example is assuming use of the above SPIRE deployment       Example where trust domain is  example org  and cluster name is  example cluster             Register a node alias for all nodes of which the Tekton Controller may reside     kubectl  n spire exec  it           deployment spire server               opt spire bin spire server entry create                node                spiffeID spiffe   example org allnodes                selector k8s psat cluster example cluster            Register the tekton controller workload to have access to creating entries in the SPIRE server     kubectl  n spire exec  it           deployment spire server               opt spire bin spire server entry create                admin                spiffeID spiffe   example org tekton controller                parentID spiffe   example org allnode                selector k8s ns tekton pipelines                selector k8s pod label app tekton pipelines controller                selector k8s sa tekton pipelines controller                   1  Modify the controller   config controller yaml   to provide access to the SPIRE agent socket         yaml       Add the following the volumeMounts of the  tekton pipelines controller  container       name  spiffe workload api       mountPath   spiffe workload api       readOnly  true            Add the following to the volumes of the controller pod       name  spiffe workload api       csi          driver   csi spiffe io          1   Optional  Modify the configmap   config config spire yaml   to configure non default SPIRE options         yaml     apiVersion  v1     kind  ConfigMap     metadata        name  config spire       namespace  tekton pipelines     labels        app kubernetes io instance  default       app kubernetes io part of  tekton pipelines     data          More explanation about the fields is at the SPIRE Server Configuration file         https   spiffe io docs latest deploying spire server  server configuration file         spire trust domain specifies the SPIRE trust domain to use        spire trust domain   example org          spire socket path specifies the SPIRE agent socket for SPIFFE workload API        spire socket path   unix    spiffe workload api spire agent sock          spire server addr specifies the SPIRE server address for workload node registration        spire server addr   spire server spire svc cluster local 8081          spire node alias prefix specifies the SPIRE node alias prefix to use        spire node alias prefix    tekton node


tekton StepActions StepActions weight 201

Answer:
<!--
---
linkTitle: "StepActions"
weight: 201
---
-->

# StepActions

- [Overview](#overview)
- [Configuring a StepAction](#configuring-a-stepaction)
  - [Declaring Parameters](#declaring-parameters)
    - [Passing Params to StepAction](#passing-params-to-stepaction)
  - [Emitting Results](#emitting-results)
    - [Fetching Emitted Results from StepActions](#fetching-emitted-results-from-stepactions)
  - [Declaring WorkingDir](#declaring-workingdir)
  - [Declaring SecurityContext](#declaring-securitycontext)
  - [Declaring VolumeMounts](#declaring-volumemounts)
  - [Referencing a StepAction](#referencing-a-stepaction)
    - [Specifying Remote StepActions](#specifying-remote-stepactions)
  - [Controlling Step Execution with when Expressions](#controlling-step-execution-with-when-expressions)

## Overview
> :seedling: **`StepActions` is an [beta](additional-configs.md#beta-features) feature.**
> The `enable-step-actions` feature flag must be set to `"true"` to specify a `StepAction` in a `Step`.

A `StepAction` is the reusable and scriptable unit of work that is performed by a `Step`.

A `Step` is not reusable, the work it performs is reusable and referenceable. `Steps` are in-lined in the `Task` definition and either perform work directly or perform a `StepAction`. A `StepAction` cannot be run stand-alone (unlike a `TaskRun` or a `PipelineRun`). It has to be referenced by a `Step`. Another way to think about this is that a `Step` is not composed of `StepActions` (unlike a `Task` being composed of `Steps` and `Sidecars`). Instead, a `Step` is an actionable component, meaning that it has the ability to refer to a `StepAction`. The author of the `StepAction` must be able to compose a `Step` using a `StepAction` and provide all the necessary context (or orchestration) to it.

## Configuring a `StepAction`

A `StepAction` definition supports the following fields:

- Required
  - [`apiVersion`][kubernetes-overview] - Specifies the API version. For example,
    `tekton.dev/v1alpha1`.
  - [`kind`][kubernetes-overview] - Identifies this resource object as a `StepAction` object.
  - [`metadata`][kubernetes-overview] - Specifies metadata that uniquely identifies the
    `StepAction` resource object. For example, a `name`.
  - [`spec`][kubernetes-overview] - Specifies the configuration information for this `StepAction` resource object.
  - `image` - Specifies the image to use for the `Step`.
    - The container image must abide by the [container contract](./container-contract.md).
- Optional
  - `command`
    - cannot be used at the same time as using `script`.
  - `args`
  - `script`
    - cannot be used at the same time as using `command`.
  - `env`
  - [`params`](#declaring-parameters)
  - [`results`](#emitting-results)
  - [`workingDir`](#declaring-workingdir)
  - [`securityContext`](#declaring-securitycontext)
  - [`volumeMounts`](#declaring-volumemounts)

[kubernetes-overview]:
  https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/#required-fields

The example below demonstrates the use of most of the above-mentioned fields:

```yaml
apiVersion: tekton.dev/v1beta1
kind: StepAction
metadata:
  name: example-stepaction-name
spec:
  env:
    - name: HOME
      value: /home
  image: ubuntu
  command: ["ls"]
  args: ["-lh"]
```

### Declaring Parameters

Like with `Tasks`, a `StepAction` must declare all the parameters that it uses. The same rules for `Parameter` [name](./tasks.md/#parameter-name), [type](./tasks.md/#parameter-type) (including [object](./tasks.md/#object-type), [array](./tasks.md/#array-type) and [string](./tasks.md/#string-type)) apply as when declaring them in `Tasks`. A `StepAction` can also provide [default value](./tasks.md/#default-value) to a `Parameter`.

 `Parameters` are passed to the `StepAction` from its corresponding `Step` referencing it.

```yaml
apiVersion: tekton.dev/v1beta1
kind: StepAction
metadata:
  name: stepaction-using-params
spec:
  params:
    - name: gitrepo
      type: object
      properties:
        url:
          type: string
        commit:
          type: string
    - name: flags
      type: array
    - name: outputPath
      type: string
      default: "/workspace"
  image: some-git-image
  args: [
    "-url=$(params.gitrepo.url)",
    "-revision=$(params.gitrepo.commit)",
    "-output=$(params.outputPath)",
    "$(params.flags[*])",
  ]
```

> :seedling: **`params` cannot be directly used in a `script` in `StepActions`.**
> Directly substituting `params` in `scripts` makes the workload prone to shell attacks. Therefore, we do not allow direct usage of `params` in `scripts` in `StepActions`. Instead, rely on passing `params` to `env` variables and reference them in `scripts`. We cannot do the same for `inlined-steps` because it breaks `v1 API` compatibility for existing users.

#### Passing Params to StepAction

A `StepAction` may require [params](#declaring-parameters). In this case, a `Task` needs to ensure that the `StepAction` has access to all the required `params`.
When referencing a `StepAction`, a `Step` can also provide it with `params`, just like how a `TaskRun` provides params to the underlying `Task`.

```yaml
apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: step-action
spec:
  params:
    - name: param-for-step-action
      description: "this is a param that the step action needs."
  steps:
    - name: action-runner
      ref:
        name: step-action
      params:
        - name: step-action-param
          value: $(params.param-for-step-action)
```

**Note:** If a `Step` declares `params` for an `inlined Step`, it will also lead to a validation error. This is because an `inlined Step` gets its `params` from the `TaskRun`.

### Emitting Results

A `StepAction` also declares the results that it will emit.

```yaml
apiVersion: tekton.dev/v1alpha1
kind: StepAction
metadata:
  name: stepaction-declaring-results
spec:
  results:
    - name: current-date-unix-timestamp
      description: The current date in unix timestamp format
    - name: current-date-human-readable
      description: The current date in human readable format
  image: bash:latest
  script: |
    #!/usr/bin/env bash
    date +%s | tee $(results.current-date-unix-timestamp.path)
    date | tee $(results.current-date-human-readable.path)
```

It is possible that a `StepAction` with `Results` is used multiple times in the same `Task` or multiple `StepActions` in the same `Task` produce `Results` with the same name. Resolving the `Result` names becomes critical otherwise there could be unexpected outcomes. The `Task` needs to be able to resolve these `Result` names clashes by mapping it to a different `Result` name. For this reason, we introduce the capability to store results on a `Step` level.

`StepActions` can also emit `Results` to `$(step.results.<resultName>.path)`.

```yaml
apiVersion: tekton.dev/v1alpha1
kind: StepAction
metadata:
  name: stepaction-declaring-results
spec:
  results:
    - name: current-date-unix-timestamp
      description: The current date in unix timestamp format
    - name: current-date-human-readable
      description: The current date in human readable format
  image: bash:latest
  script: |
    #!/usr/bin/env bash
    date +%s | tee $(step.results.current-date-unix-timestamp.path)
    date | tee $(step.results.current-date-human-readable.path)
```

`Results` from the above `StepAction` can be [fetched by the `Task`](#fetching-emitted-results-from-stepactions) or in [another `Step/StepAction`](#passing-step-results-between-steps) via `$(steps.<stepName>.results.<resultName>)`.

#### Fetching Emitted Results from StepActions

A `Task` can fetch `Results` produced by the `StepActions` (i.e. only `Results` emitted to `$(step.results.<resultName>.path)`, NOT `$(results.<resultName>.path)`) using variable replacement syntax. We introduce a field to [`Task Results`](./tasks.md#emitting-results) called `Value` whose value can be set to the variable `$(steps.<stepName>.results.<resultName>)`.

```yaml
apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: task-fetching-results
spec:
  results:
    - name: git-url
      description: "url of git repo"
      value: $(steps.git-clone.results.url)
    - name: registry-url
      description: "url of docker registry"
      value: $(steps.kaniko.results.url)
  steps:
    - name: git-clone
      ref:
        name: clone-step-action
    - name: kaniko
      ref:
        name: kaniko-step-action
```

`Results` emitted to `$(step.results.<resultName>.path)` are not automatically available as `TaskRun Results`. The `Task` must explicitly fetch it from the underlying `Step` referencing `StepActions`.

For example, lets assume that in the previous example, the "kaniko" `StepAction` also produced a `Result` named "digest". In that case, the `Task` should also fetch the "digest" from "kaniko" `Step`.

```yaml
apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: task-fetching-results
spec:
  results:
    - name: git-url
      description: "url of git repo"
      value: $(steps.git-clone.results.url)
    - name: registry-url
      description: "url of docker registry"
      value: $(steps.kaniko.results.url)
    - name: digest
      description: "digest of the image"
      value: $(steps.kaniko.results.digest)
  steps:
    - name: git-clone
      ref:
        name: clone-step-action
    - name: kaniko
      ref:
        name: kaniko-step-action
```

#### Passing Results between Steps

`StepResults` (i.e. results written to `$(step.results.<result-name>.path)`, NOT `$(results.<result-name>.path)`) can be shared with following steps via replacement variable `$(steps.<step-name>.results.<result-name>)`.

Pipeline supports two new types of results and parameters: array `[]string` and object `map[string]string`.

| Result Type | Parameter Type | Specification                                    | `enable-api-fields` |
|-------------|----------------|--------------------------------------------------|---------------------|
| string      | string         | `$(steps.<step-name>.results.<result-name>)`     | stable              |
| array       | array          | `$(steps.<step-name>.results.<result-name>[*])`  | alpha or beta       |
| array       | string         | `$(steps.<step-name>.results.<result-name>[i])`  | alpha or beta       |
| object      | string         | `$(tasks.<task-name>.results.<result-name>.key)` | alpha or beta       |

**Note:** Whole Array `Results` (using star notation) cannot be referred in `script` and `env`.

The example below shows how you could pass `step results` from a `step` into following steps, in this case, into a `StepAction`.

```yaml
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
  name: step-action-run
spec:
  TaskSpec:
    steps:
      - name: inline-step
        results:
          - name: result1
            type: array
          - name: result2
            type: string
          - name: result3
            type: object
            properties:
              IMAGE_URL:
                type: string
              IMAGE_DIGEST:
                type: string
        image: alpine
        script: |
          echo -n "[\"image1\", \"image2\", \"image3\"]" | tee $(step.results.result1.path)
          echo -n "foo" | tee $(step.results.result2.path)
          echo -n "{\"IMAGE_URL\":\"ar.com\", \"IMAGE_DIGEST\":\"sha234\"}" | tee $(step.results.result3.path)
      - name: action-runner
        ref:
          name: step-action
        params:
          - name: param1
            value: $(steps.inline-step.results.result1[*])
          - name: param2
            value: $(steps.inline-step.results.result2)
          - name: param3
            value: $(steps.inline-step.results.result3[*])
```

**Note:** `Step Results` can only be referenced in a `Step's/StepAction's` `env`, `command` and `args`. Referencing in any other field will throw an error.

### Declaring WorkingDir

You can declare `workingDir` in a `StepAction`:

```yaml
apiVersion: tekton.dev/v1alpha1
kind: StepAction
metadata:
  name: example-stepaction-name
spec:
  image:  gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:latest
  workingDir: /workspace
  script: |
    # clone the repo
    ...
```

The `Task` using the `StepAction` has more context about how the `Steps` have been orchestrated. As such, the `Task` should be able to update the `workingDir` of the `StepAction` so that the `StepAction` is executed from the correct location.
The `StepAction` can parametrize the `workingDir` and work relative to it. This way, the `Task` does not really need control over the workingDir, it just needs to pass the path as a parameter.

```yaml
apiVersion: tekton.dev/v1alpha1
kind: StepAction
metadata:
  name: example-stepaction-name
spec:
  image: ubuntu
  params:
    - name: source
      description: "The path to the source code."
  workingDir: $(params.source)
```

### Declaring SecurityContext

You can declare `securityContext` in a `StepAction`:

```yaml
apiVersion: tekton.dev/v1alpha1
kind: StepAction
metadata:
  name: example-stepaction-name
spec:
  image:  gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:latest
  securityContext:
      runAsUser: 0
  script: |
    # clone the repo
    ...
```

Note that the `securityContext` from `StepAction` will overwrite the `securityContext` from [`TaskRun`](./taskruns.md/#example-of-running-step-containers-as-a-non-root-user).

### Declaring VolumeMounts

You can define `VolumeMounts` in `StepActions`. The `name` of the `VolumeMount` MUST be a single reference to a string `Parameter`. For example, `$(params.registryConfig)` is valid while `$(params.registryConfig)-foo` and `"unparametrized-name"` are invalid. This is to ensure reusability of `StepActions` such that `Task` authors have control of which `Volumes` they bind to the `VolumeMounts`.

```yaml
apiVersion: tekton.dev/v1alpha1
kind: StepAction
metadata:
  name: myStep
spec:
  params:
    - name: registryConfig
    - name: otherConfig
  volumeMounts:
    - name: $(params.registryConfig)
      mountPath: /registry-config
    - name: $(params.otherConfig)
      mountPath: /other-config
  image: ...
  script: ...
```

### Referencing a StepAction

`StepActions` can be referenced from the `Step` using the `ref` field, as follows:

```yaml
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
  name: step-action-run
spec:
  taskSpec:
    steps:
      - name: action-runner
        ref:
          name: step-action
```

Upon resolution and execution of the `TaskRun`, the `Status` will look something like:

```yaml
status:
  completionTime: "2023-10-24T20:28:42Z"
  conditions:
  - lastTransitionTime: "2023-10-24T20:28:42Z"
    message: All Steps have completed executing
    reason: Succeeded
    status: "True"
    type: Succeeded
  podName: step-action-run-pod
  provenance:
    featureFlags:
      EnableStepActions: true
      ...
  startTime: "2023-10-24T20:28:32Z"
  steps:
  - container: step-action-runner
    imageID: docker.io/library/alpine@sha256:eece025e432126ce23f223450a0326fbebde39cdf496a85d8c016293fc851978
    name: action-runner
    terminationReason: Completed
    terminated:
      containerID: containerd://46a836588967202c05b594696077b147a0eb0621976534765478925bb7ce57f6
      exitCode: 0
      finishedAt: "2023-10-24T20:28:42Z"
      reason: Completed
      startedAt: "2023-10-24T20:28:42Z"
  taskSpec:
    steps:
    - computeResources: {}
      image: alpine
      name: action-runner
```

If a `Step` is referencing a `StepAction`, it cannot contain the fields supported by `StepActions`. This includes:
- `image`
- `command`
- `args`
- `script`
- `env`
- `volumeMounts`

Using any of the above fields and referencing a `StepAction` in the same `Step` is not allowed and will cause a validation error.

```yaml
# This is not allowed and will result in a validation error
# because the image is expected to be provided by the StepAction
# and not inlined.
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
  name: step-action-run
spec:
  taskSpec:
    steps:
      - name: action-runner
        ref:
          name: step-action
        image: ubuntu
```
Executing the above `TaskRun` will result in an error that looks like:

```
Error from server (BadRequest): error when creating "STDIN": admission webhook "validation.webhook.pipeline.tekton.dev" denied the request: validation failed: image cannot be used with Ref: spec.taskSpec.steps[0].image
```

When a `Step` is referencing a `StepAction`, it can contain the following fields:
- `computeResources`
- `workspaces` (Isolated workspaces)
- `volumeDevices`
- `imagePullPolicy`
- `onError`
- `stdoutConfig`
- `stderrConfig`
- `securityContext`
- `envFrom`
- `timeout`
- `ref`
- `params`

Using any of the above fields and referencing a `StepAction` is allowed and will not cause an error. For example, the `TaskRun` below will execute without any errors:

```yaml
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
  name: step-action-run
spec:
  taskSpec:
    steps:
      - name: action-runner
        ref:
          name: step-action
        params:
          - name: step-action-param
            value: hello
        computeResources:
          requests:
            memory: 1Gi
            cpu: 500m
        timeout: 1h
        onError: continue
```

#### Specifying Remote StepActions

A `ref` field may specify a `StepAction` in a remote location such as git.
Support for specific types of remote will depend on the `Resolvers` your
cluster's operator has installed. For more information including a tutorial, please check [resolution docs](resolution.md). The below example demonstrates referencing a `StepAction` in git:

```yaml
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
  generateName: step-action-run-
spec:
  taskSpec:
    steps:
      - name: action-runner
        ref:
          resolver: git
          params:
            - name: url
              value: https://github.com/repo/repo.git
            - name: revision
              value: main
            - name: pathInRepo
              value: remote_step.yaml
```

The default resolver type can be configured by the `default-resolver-type` field in the `config-defaults` ConfigMap (`alpha` feature). See [additional-configs.md](./additional-configs.md) for details.

### Controlling Step Execution with when Expressions

You can define `when` in a `step` to control its execution.

The components of `when` expressions are `input`, `operator`, `values`, `cel`:

| Component  | Description                                                                                | Syntax                                                                                |
|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `input`    | Input for the `when` expression, defaults to an empty string if not provided.                                                                                | * Static values e.g. `"ubuntu"`<br/> * Variables (parameters or results) e.g. `"$(params.image)"` or `"$(tasks.task1.results.image)"` or `"$(tasks.task1.results.array-results[1])"`                                                                                |
| `operator` | `operator` represents an `input`'s relationship to a set of `values`, a valid `operator` must be provided.                                                                                | `in` or `notin`                                                                                |
| `values`   | An array of string values, the `values` array must be provided and has to be non-empty.                                                                                | * An array param e.g. `["$(params.images[*])"]`<br/> * An array result of a task `["$(tasks.task1.results.array-results[*])"]`<br/> * An array result of a step`["(steps.step1.results.array-results[*])"]`<br/>* `values` can contain static values e.g. `"ubuntu"`<br/> * `values` can contain variables (parameters or results) or a Workspaces's `bound` state e.g. `["$(params.image)"]` or `["$(steps.step1.results.image)"]` or `["$(tasks.task1.results.array-results[1])"]` or `["$(steps.step1.results.array-results[1])"]` |
| `cel`      | The Common Expression Language (CEL) implements common semantics for expression evaluation, enabling different applications to more easily interoperate. This is an `alpha` feature, `enable-cel-in-whenexpression` needs to be set to true to use this feature. |  [cel-syntax](https://github.com/google/cel-spec/blob/master/doc/langdef.md#syntax)

The below example shows how to use when expressions to control step executions:

```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc-2
spec:
  resources:
    requests:
      storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
---
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
  generateName: step-when-example
spec:
  workspaces:
    - name: custom
      persistentVolumeClaim:
        claimName: my-pvc-2
  taskSpec:
    description: |
      A simple task that shows how to use when determine if a step should be executed
    steps:
      - name: should-execute
        image: bash:latest
        script: |
          #!/usr/bin/env bash
          echo "executed..."
        when:
          - input: "$(workspaces.custom.bound)"
            operator: in
            values: [ "true" ]
      - name: should-skip
        image: bash:latest
        script: |
          #!/usr/bin/env bash
          echo skipskipskip
        when:
          - input: "$(workspaces.custom2.bound)"
            operator: in
            values: [ "true" ]
      - name: should-continue
        image: bash:latest
        script: |
          #!/usr/bin/env bash
          echo blabalbaba
      - name: produce-step
        image: alpine
        results:
          - name: result2
            type: string
        script: |
          echo -n "foo" | tee $(step.results.result2.path)
      - name: run-based-on-step-results
        image: alpine
        script: |
          echo "wooooooo"
        when:
          - input: "$(steps.produce-step.results.result2)"
            operator: in
            values: [ "bar" ]
    workspaces:
      - name: custom
```

The StepState for a skipped step looks like something similar to the below:
```yaml
      {
        "container": "step-run-based-on-step-results",
        "imageID": "docker.io/library/alpine@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b",
        "name": "run-based-on-step-results",
        "terminated": {
          "containerID": "containerd://bf81162e79cf66a2bbc03e3654942d3464db06ff368c0be263a8a70f363a899b",
          "exitCode": 0,
          "finishedAt": "2024-03-26T03:57:47Z",
          "reason": "Completed",
          "startedAt": "2024-03-26T03:57:47Z"
        },
        "terminationReason": "Skipped"
      }
```
Where `terminated.exitCode` is `0` and `terminationReason` is `Skipped` to indicate the Step exited successfully and was skipped.

linkTitle   StepActions  weight  201            StepActions     Overview   overview     Configuring a StepAction   configuring a stepaction       Declaring Parameters   declaring parameters         Passing Params to StepAction   passing params to stepaction       Emitting Results   emitting results         Fetching Emitted Results from StepActions   fetching emitted results from stepactions       Declaring WorkingDir   declaring workingdir       Declaring SecurityContext   declaring securitycontext       Declaring VolumeMounts   declaring volumemounts       Referencing a StepAction   referencing a stepaction         Specifying Remote StepActions   specifying remote stepactions       Controlling Step Execution with when Expressions   controlling step execution with when expressions      Overview    seedling     StepActions  is an  beta  additional configs md beta features  feature      The  enable step actions  feature flag must be set to   true   to specify a  StepAction  in a  Step    A  StepAction  is the reusable and scriptable unit of work that is performed by a  Step    A  Step  is not reusable  the work it performs is reusable and referenceable   Steps  are in lined in the  Task  definition and either perform work directly or perform a  StepAction   A  StepAction  cannot be run stand alone  unlike a  TaskRun  or a  PipelineRun    It has to be referenced by a  Step   Another way to think about this is that a  Step  is not composed of  StepActions   unlike a  Task  being composed of  Steps  and  Sidecars    Instead  a  Step  is an actionable component  meaning that it has the ability to refer to a  StepAction   The author of the  StepAction  must be able to compose a  Step  using a  StepAction  and provide all the necessary context  or orchestration  to it       Configuring a  StepAction   A  StepAction  definition supports the following fields     Required       apiVersion   kubernetes overview    Specifies the API version  For example       tekton dev v1alpha1         kind   kubernetes overview    Identifies this resource object as a  StepAction  object        metadata   kubernetes overview    Specifies metadata that uniquely identifies the      StepAction  resource object  For example  a  name         spec   kubernetes overview    Specifies the configuration information for this  StepAction  resource object       image    Specifies the image to use for the  Step         The container image must abide by the  container contract    container contract md     Optional      command        cannot be used at the same time as using  script        args       script        cannot be used at the same time as using  command        env        params    declaring parameters        results    emitting results        workingDir    declaring workingdir        securityContext    declaring securitycontext        volumeMounts    declaring volumemounts    kubernetes overview     https   kubernetes io docs concepts overview working with objects kubernetes objects  required fields  The example below demonstrates the use of most of the above mentioned fields      yaml apiVersion  tekton dev v1beta1 kind  StepAction metadata    name  example stepaction name spec    env        name  HOME       value   home   image  ubuntu   command    ls     args     lh            Declaring Parameters  Like with  Tasks   a  StepAction  must declare all the parameters that it uses  The same rules for  Parameter   name    tasks md  parameter name    type    tasks md  parameter type   including  object    tasks md  object type    array    tasks md  array type  and  string    tasks md  string type   apply as when declaring them in  Tasks   A  StepAction  can also provide  default value    tasks md  default value  to a  Parameter      Parameters  are passed to the  StepAction  from its corresponding  Step  referencing it      yaml apiVersion  tekton dev v1beta1 kind  StepAction metadata    name  stepaction using params spec    params        name  gitrepo       type  object       properties          url            type  string         commit            type  string       name  flags       type  array       name  outputPath       type  string       default    workspace    image  some git image   args          url   params gitrepo url          revision   params gitrepo commit          output   params outputPath           params flags                   seedling     params  cannot be directly used in a  script  in  StepActions       Directly substituting  params  in  scripts  makes the workload prone to shell attacks  Therefore  we do not allow direct usage of  params  in  scripts  in  StepActions   Instead  rely on passing  params  to  env  variables and reference them in  scripts   We cannot do the same for  inlined steps  because it breaks  v1 API  compatibility for existing users        Passing Params to StepAction  A  StepAction  may require  params   declaring parameters   In this case  a  Task  needs to ensure that the  StepAction  has access to all the required  params   When referencing a  StepAction   a  Step  can also provide it with  params   just like how a  TaskRun  provides params to the underlying  Task       yaml apiVersion  tekton dev v1 kind  Task metadata    name  step action spec    params        name  param for step action       description   this is a param that the step action needs     steps        name  action runner       ref          name  step action       params            name  step action param           value    params param for step action         Note    If a  Step  declares  params  for an  inlined Step   it will also lead to a validation error  This is because an  inlined Step  gets its  params  from the  TaskRun        Emitting Results  A  StepAction  also declares the results that it will emit      yaml apiVersion  tekton dev v1alpha1 kind  StepAction metadata    name  stepaction declaring results spec    results        name  current date unix timestamp       description  The current date in unix timestamp format       name  current date human readable       description  The current date in human readable format   image  bash latest   script           usr bin env bash     date   s   tee   results current date unix timestamp path      date   tee   results current date human readable path       It is possible that a  StepAction  with  Results  is used multiple times in the same  Task  or multiple  StepActions  in the same  Task  produce  Results  with the same name  Resolving the  Result  names becomes critical otherwise there could be unexpected outcomes  The  Task  needs to be able to resolve these  Result  names clashes by mapping it to a different  Result  name  For this reason  we introduce the capability to store results on a  Step  level    StepActions  can also emit  Results  to    step results  resultName  path        yaml apiVersion  tekton dev v1alpha1 kind  StepAction metadata    name  stepaction declaring results spec    results        name  current date unix timestamp       description  The current date in unix timestamp format       name  current date human readable       description  The current date in human readable format   image  bash latest   script           usr bin env bash     date   s   tee   step results current date unix timestamp path      date   tee   step results current date human readable path        Results  from the above  StepAction  can be  fetched by the  Task    fetching emitted results from stepactions  or in  another  Step StepAction    passing step results between steps  via    steps  stepName  results  resultName           Fetching Emitted Results from StepActions  A  Task  can fetch  Results  produced by the  StepActions   i e  only  Results  emitted to    step results  resultName  path    NOT    results  resultName  path    using variable replacement syntax  We introduce a field to   Task Results     tasks md emitting results  called  Value  whose value can be set to the variable    steps  stepName  results  resultName         yaml apiVersion  tekton dev v1 kind  Task metadata    name  task fetching results spec    results        name  git url       description   url of git repo        value    steps git clone results url        name  registry url       description   url of docker registry        value    steps kaniko results url    steps        name  git clone       ref          name  clone step action       name  kaniko       ref          name  kaniko step action       Results  emitted to    step results  resultName  path   are not automatically available as  TaskRun Results   The  Task  must explicitly fetch it from the underlying  Step  referencing  StepActions    For example  lets assume that in the previous example  the  kaniko   StepAction  also produced a  Result  named  digest   In that case  the  Task  should also fetch the  digest  from  kaniko   Step       yaml apiVersion  tekton dev v1 kind  Task metadata    name  task fetching results spec    results        name  git url       description   url of git repo        value    steps git clone results url        name  registry url       description   url of docker registry        value    steps kaniko results url        name  digest       description   digest of the image        value    steps kaniko results digest    steps        name  git clone       ref          name  clone step action       name  kaniko       ref          name  kaniko step action           Passing Results between Steps   StepResults   i e  results written to    step results  result name  path    NOT    results  result name  path    can be shared with following steps via replacement variable    steps  step name  results  result name      Pipeline supports two new types of results and parameters  array    string  and object  map string string      Result Type   Parameter Type   Specification                                       enable api fields                                                                                string        string              steps  step name  results  result name          stable                  array         array               steps  step name  results  result name          alpha or beta           array         string              steps  step name  results  result name  i       alpha or beta           object        string              tasks  task name  results  result name  key     alpha or beta            Note    Whole Array  Results   using star notation  cannot be referred in  script  and  env    The example below shows how you could pass  step results  from a  step  into following steps  in this case  into a  StepAction       yaml apiVersion  tekton dev v1 kind  TaskRun metadata    name  step action run spec    TaskSpec      steps          name  inline step         results              name  result1             type  array             name  result2             type  string             name  result3             type  object             properties                IMAGE URL                  type  string               IMAGE DIGEST                  type  string         image  alpine         script              echo  n     image1      image2      image3       tee   step results result1 path            echo  n  foo    tee   step results result2 path            echo  n     IMAGE URL     ar com      IMAGE DIGEST     sha234       tee   step results result3 path          name  action runner         ref            name  step action         params              name  param1             value    steps inline step results result1                 name  param2             value    steps inline step results result2              name  param3             value    steps inline step results result3            Note     Step Results  can only be referenced in a  Step s StepAction s   env    command  and  args   Referencing in any other field will throw an error       Declaring WorkingDir  You can declare  workingDir  in a  StepAction       yaml apiVersion  tekton dev v1alpha1 kind  StepAction metadata    name  example stepaction name spec    image   gcr io tekton releases github com tektoncd pipeline cmd git init latest   workingDir   workspace   script          clone the repo              The  Task  using the  StepAction  has more context about how the  Steps  have been orchestrated  As such  the  Task  should be able to update the  workingDir  of the  StepAction  so that the  StepAction  is executed from the correct location  The  StepAction  can parametrize the  workingDir  and work relative to it  This way  the  Task  does not really need control over the workingDir  it just needs to pass the path as a parameter      yaml apiVersion  tekton dev v1alpha1 kind  StepAction metadata    name  example stepaction name spec    image  ubuntu   params        name  source       description   The path to the source code     workingDir    params source            Declaring SecurityContext  You can declare  securityContext  in a  StepAction       yaml apiVersion  tekton dev v1alpha1 kind  StepAction metadata    name  example stepaction name spec    image   gcr io tekton releases github com tektoncd pipeline cmd git init latest   securityContext        runAsUser  0   script          clone the repo              Note that the  securityContext  from  StepAction  will overwrite the  securityContext  from   TaskRun     taskruns md  example of running step containers as a non root user        Declaring VolumeMounts  You can define  VolumeMounts  in  StepActions   The  name  of the  VolumeMount  MUST be a single reference to a string  Parameter   For example     params registryConfig   is valid while    params registryConfig  foo  and   unparametrized name   are invalid  This is to ensure reusability of  StepActions  such that  Task  authors have control of which  Volumes  they bind to the  VolumeMounts       yaml apiVersion  tekton dev v1alpha1 kind  StepAction metadata    name  myStep spec    params        name  registryConfig       name  otherConfig   volumeMounts        name    params registryConfig        mountPath   registry config       name    params otherConfig        mountPath   other config   image        script                Referencing a StepAction   StepActions  can be referenced from the  Step  using the  ref  field  as follows      yaml apiVersion  tekton dev v1 kind  TaskRun metadata    name  step action run spec    taskSpec      steps          name  action runner         ref            name  step action      Upon resolution and execution of the  TaskRun   the  Status  will look something like      yaml status    completionTime   2023 10 24T20 28 42Z    conditions      lastTransitionTime   2023 10 24T20 28 42Z      message  All Steps have completed executing     reason  Succeeded     status   True      type  Succeeded   podName  step action run pod   provenance      featureFlags        EnableStepActions  true             startTime   2023 10 24T20 28 32Z    steps      container  step action runner     imageID  docker io library alpine sha256 eece025e432126ce23f223450a0326fbebde39cdf496a85d8c016293fc851978     name  action runner     terminationReason  Completed     terminated        containerID  containerd   46a836588967202c05b594696077b147a0eb0621976534765478925bb7ce57f6       exitCode  0       finishedAt   2023 10 24T20 28 42Z        reason  Completed       startedAt   2023 10 24T20 28 42Z    taskSpec      steps        computeResources           image  alpine       name  action runner      If a  Step  is referencing a  StepAction   it cannot contain the fields supported by  StepActions   This includes     image     command     args     script     env     volumeMounts   Using any of the above fields and referencing a  StepAction  in the same  Step  is not allowed and will cause a validation error      yaml   This is not allowed and will result in a validation error   because the image is expected to be provided by the StepAction   and not inlined  apiVersion  tekton dev v1 kind  TaskRun metadata    name  step action run spec    taskSpec      steps          name  action runner         ref            name  step action         image  ubuntu     Executing the above  TaskRun  will result in an error that looks like       Error from server  BadRequest   error when creating  STDIN   admission webhook  validation webhook pipeline tekton dev  denied the request  validation failed  image cannot be used with Ref  spec taskSpec steps 0  image      When a  Step  is referencing a  StepAction   it can contain the following fields     computeResources     workspaces   Isolated workspaces     volumeDevices     imagePullPolicy     onError     stdoutConfig     stderrConfig     securityContext     envFrom     timeout     ref     params   Using any of the above fields and referencing a  StepAction  is allowed and will not cause an error  For example  the  TaskRun  below will execute without any errors      yaml apiVersion  tekton dev v1 kind  TaskRun metadata    name  step action run spec    taskSpec      steps          name  action runner         ref            name  step action         params              name  step action param             value  hello         computeResources            requests              memory  1Gi             cpu  500m         timeout  1h         onError  continue           Specifying Remote StepActions  A  ref  field may specify a  StepAction  in a remote location such as git  Support for specific types of remote will depend on the  Resolvers  your cluster s operator has installed  For more information including a tutorial  please check  resolution docs  resolution md   The below example demonstrates referencing a  StepAction  in git      yaml apiVersion  tekton dev v1 kind  TaskRun metadata    generateName  step action run  spec    taskSpec      steps          name  action runner         ref            resolver  git           params                name  url               value  https   github com repo repo git               name  revision               value  main               name  pathInRepo               value  remote step yaml      The default resolver type can be configured by the  default resolver type  field in the  config defaults  ConfigMap   alpha  feature   See  additional configs md    additional configs md  for details       Controlling Step Execution with when Expressions  You can define  when  in a  step  to control its execution    The components of  when  expressions are  input    operator    values    cel      Component    Description                                                                                Syntax                                                                                input       Input for the  when  expression  defaults to an empty string if not provided                                                                                Static values e g    ubuntu   br     Variables  parameters or results  e g      params image    or     tasks task1 results image    or     tasks task1 results array results 1                                                                                operator     operator  represents an  input  s relationship to a set of  values   a valid  operator  must be provided                                                                                in  or  notin                                                                                values      An array of string values  the  values  array must be provided and has to be non empty                                                                                An array param e g       params images        br     An array result of a task      tasks task1 results array results        br     An array result of a step    steps step1 results array results        br     values  can contain static values e g    ubuntu   br      values  can contain variables  parameters or results  or a Workspaces s  bound  state e g       params image     or      steps step1 results image     or      tasks task1 results array results 1      or      steps step1 results array results 1           cel         The Common Expression Language  CEL  implements common semantics for expression evaluation  enabling different applications to more easily interoperate  This is an  alpha  feature   enable cel in whenexpression  needs to be set to true to use this feature      cel syntax  https   github com google cel spec blob master doc langdef md syntax   The below example shows how to use when expressions to control step executions      yaml apiVersion  v1 kind  PersistentVolumeClaim metadata    name  my pvc 2 spec    resources      requests        storage  5Gi   volumeMode  Filesystem   accessModes        ReadWriteOnce     apiVersion  tekton dev v1 kind  TaskRun metadata    generateName  step when example spec    workspaces        name  custom       persistentVolumeClaim          claimName  my pvc 2   taskSpec      description          A simple task that shows how to use when determine if a step should be executed     steps          name  should execute         image  bash latest         script                 usr bin env bash           echo  executed             when              input     workspaces custom bound               operator  in             values     true            name  should skip         image  bash latest         script                 usr bin env bash           echo skipskipskip         when              input     workspaces custom2 bound               operator  in             values     true            name  should continue         image  bash latest         script                 usr bin env bash           echo blabalbaba         name  produce step         image  alpine         results              name  result2             type  string         script              echo  n  foo    tee   step results result2 path          name  run based on step results         image  alpine         script              echo  wooooooo          when              input     steps produce step results result2               operator  in             values     bar        workspaces          name  custom      The StepState for a skipped step looks like something similar to the below     yaml                  container    step run based on step results            imageID    docker io library alpine sha256 c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b            name    run based on step results            terminated                containerID    containerd   bf81162e79cf66a2bbc03e3654942d3464db06ff368c0be263a8a70f363a899b              exitCode   0             finishedAt    2024 03 26T03 57 47Z              reason    Completed              startedAt    2024 03 26T03 57 47Z                      terminationReason    Skipped              Where  terminated exitCode  is  0  and  terminationReason  is  Skipped  to indicate the Step exited successfully and was skipped


tekton This how to will outline the steps a developer needs to take when creating weight 104 How to write a Resolver How to write a Resolver

Answer:
<!--
---
linkTitle: "How to write a Resolver"
weight: 104
---
-->

# How to write a Resolver

This how-to will outline the steps a developer needs to take when creating
a new (very basic) Resolver. Rather than focus on support for a particular version
control system or cloud platform this Resolver will simply respond with
some hard-coded YAML.

If you aren't yet familiar with the meaning of "resolution" when it
comes to Tekton, a short summary follows. You might also want to read a
little bit into Tekton Pipelines, particularly [the docs on specifying a
target Pipeline to
run](./pipelineruns.md#specifying-the-target-pipeline)
and, if you're feeling particularly brave or bored, the [really long
design doc describing Tekton
Resolution](https://github.com/tektoncd/community/blob/main/teps/0060-remote-resource-resolution.md).

## What's a Resolver?

A Resolver is a program that runs in a Kubernetes cluster alongside
[Tekton Pipelines](https://github.com/tektoncd/pipeline) and "resolves"
requests for `Tasks` and `Pipelines` from remote locations. More
concretely: if a user submitted a `PipelineRun` that needed a Pipeline
YAML stored in a git repo, then it would be a `Resolver` that's
responsible for fetching the YAML file from git and returning it to
Tekton Pipelines.

This pattern extends beyond just git, allowing a developer to integrate
support for other version control systems, cloud buckets, or storage systems
without having to modify Tekton Pipelines itself.

## Just want to see the working example?

If you'd prefer to look at the end result of this howto you can take a
visit the
[`./resolver-template`](./resolver-template)
in the Tekton Resolution repo. That template is built on the code from
this howto to get you up and running quickly.

## Pre-requisites

Before getting started with this howto you'll need to be comfortable
developing in Go and have a general understanding of how Tekton
Resolution works.

You'll also need the following:

- A computer with
  [`kubectl`](https://kubernetes.io/docs/tasks/tools/#kubectl) and
  [`ko`](https://github.com/google/ko) installed.
- A Kubernetes cluster running at least Kubernetes 1.28. A [`kind`
  cluster](https://kind.sigs.k8s.io/docs/user/quick-start/#installation)
  should work fine for following the guide on your local machine.
- An image registry that you can push images to. If you're using `kind`
  make sure your `KO_DOCKER_REPO` environment variable is set to
  `kind.local`.
- Tekton Pipelines and remote resolvers installed in your Kubernetes
  cluster. See [the installation
  guide](./install.md#installing-and-configuring-remote-task-and-pipeline-resolution) for
  instructions on installing it.

## First Steps

The first thing to do is create an initial directory structure for your
project. For this example we'll create a directory and initialize a new
go module with a few subdirectories for our code:

```bash
$ mkdir demoresolver

$ cd demoresolver

$ go mod init example.com/demoresolver

$ mkdir -p cmd/demoresolver

$ mkdir config
```

The `cmd/demoresolver` directory will contain code for the resolver and the
`config` directory will eventually contain a yaml file for deploying the
resolver to Kubernetes.

## Initializing the resolver's binary

A Resolver is ultimately just a program running in your cluster, so the
first step is to fill out the initial code for starting that program.
Our resolver here is going to be extremely simple and doesn't need any
flags or special environment variables, so we'll just initialize it with
a little bit of boilerplate.

Create `cmd/demoresolver/main.go` with the following setup code:

```go
package main

import (
  "context"
  "github.com/tektoncd/pipeline/pkg/remoteresolution/resolver/framework"
  "knative.dev/pkg/injection/sharedmain"
)

func main() {
  sharedmain.Main("controller",
    framework.NewController(context.Background(), &resolver{}),
  )
}

type resolver struct {}
```

```go
package main

import (
  "context"
  "github.com/tektoncd/pipeline/pkg/resolution/resolver/framework"
  "knative.dev/pkg/injection/sharedmain"
)

func main() {
  sharedmain.Main("controller",
    framework.NewController(context.Background(), &resolver{}),
  )
}

type resolver struct {}
```

This won't compile yet but you can download the dependencies by running:

```bash
# Depending on your go version you might not need the -compat flag
$ go mod tidy -compat=1.17
```

## Writing the Resolver

If you try to build the binary right now you'll receive the following
error:

```bash
$ go build -o /dev/null ./cmd/demoresolver

cmd/demoresolver/main.go:11:78: cannot use &resolver{} (type *resolver) as
type framework.Resolver in argument to framework.NewController:
        *resolver does not implement framework.Resolver (missing GetName method)
```

We've already defined our own `resolver` type but in order to get the
resolver running you'll need to add the methods defined in [the
`framework.Resolver` interface](../pkg/resolution/resolver/framework/interface.go)
to your `main.go` file. Going through each method in turn:

## The `Initialize` method

This method is used to start any libraries or otherwise setup any
prerequisites your resolver needs. For this example we won't need
anything so this method can just return `nil`.

```go
// Initialize sets up any dependencies needed by the resolver. None atm.
func (r *resolver) Initialize(context.Context) error {
  return nil
}
```

## The `GetName` method

This method returns a string name that will be used to refer to this
resolver. You'd see this name show up in places like logs. For this
simple example we'll return `"Demo"`:

```go
// GetName returns a string name to refer to this resolver by.
func (r *resolver) GetName(context.Context) string {
  return "Demo"
}
```

## The `GetSelector` method

This method should return a map of string labels and their values that
will be used to direct requests to this resolver. For this example the
only label we're interested in matching on is defined by
`tektoncd/resolution`:

```go
// GetSelector returns a map of labels to match requests to this resolver.
func (r *resolver) GetSelector(context.Context) map[string]string {
  return map[string]string{
    common.LabelKeyResolverType: "demo",
  }
}
```

What this does is tell the resolver framework that any
`ResolutionRequest` object with a label of
`"resolution.tekton.dev/type": "demo"` should be routed to our
example resolver.

We'll also need to add another import for this package at the top:

```go
import (
  "context"

  // Add this one; it defines LabelKeyResolverType we use in GetSelector
  "github.com/tektoncd/pipeline/pkg/resolution/common"
  "github.com/tektoncd/pipeline/pkg/remoteresolution/resolver/framework"
  "knative.dev/pkg/injection/sharedmain"
  pipelinev1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
)
```

```go
import (
  "context"

  // Add this one; it defines LabelKeyResolverType we use in GetSelector
  "github.com/tektoncd/pipeline/pkg/resolution/common"
  "github.com/tektoncd/pipeline/pkg/resolution/resolver/framework"
  "knative.dev/pkg/injection/sharedmain"
  pipelinev1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
)
```

## The `Validate` method

The `Validate` method checks that the resolution-spec submitted as part of
a resolution request are valid. Our example resolver doesn't expect
any params in the spec so we'll simply ensure that the there are no params.
Our example resolver also expects format for the `url` to be `demoscheme://<path>` so we'll validate this format.
In the previous version, this was instead called `ValidateParams` method. See below
for the differences.

```go
// Validate ensures that the resolution spec from a request is as expected.
func (r *resolver) Validate(ctx context.Context, req *v1beta1.ResolutionRequestSpec) error {
  if len(req.Params) > 0 {
    return errors.New("no params allowed")
  }
  url := req.URL
  u, err := neturl.ParseRequestURI(url)
  if err != nil {
    return err
  }
  if u.Scheme != "demoscheme" {
    return fmt.Errorf("Invalid Scheme. Want %s, Got %s", "demoscheme", u.Scheme)
  }
  if u.Path == "" {
    return errors.New("Empty path.")
  }
  return nil
}
```

You'll also need to add the `net/url` as `neturl` and `"errors"` package to your list of imports at
the top of the file.

```

```go
// ValidateParams ensures that the params from a request are as expected.
func (r *resolver) ValidateParams(ctx context.Context, params []pipelinev1.Param) error {
  if len(req.Params) > 0 {
    return errors.New("no params allowed")
  }
  return nil
}
```

You'll also need to add the `"errors"` package to your list of imports at
the top of the file.

## The `Resolve` method

We implement the `Resolve` method to do the heavy lifting of fetching
the contents of a file and returning them.  It takes in the resolution request spec as input.
For this example we're just going to return a hard-coded string of YAML. Since Tekton Pipelines
currently only supports fetching Pipeline resources via remote
resolution that's what we'll return.

The method signature we're implementing here has a
`framework.ResolvedResource` interface as one of its return values. This
is another type we have to implement but it has a small footprint:

```go
// Resolve uses the given resolution spec to resolve the requested file or resource.
func (r *resolver) Resolve(ctx context.Context, req *v1beta1.ResolutionRequestSpec) (framework.ResolvedResource, error) {
  return &myResolvedResource{}, nil
}

// our hard-coded resolved file to return
const pipeline = `
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: my-pipeline
spec:
  tasks:
  - name: hello-world
    taskSpec:
      steps:
      - image: alpine:3.15.1
        script: |
          echo "hello world"
`

// myResolvedResource wraps the data we want to return to Pipelines
type myResolvedResource struct {}

// Data returns the bytes of our hard-coded Pipeline
func (*myResolvedResource) Data() []byte {
  return []byte(pipeline)
}

// Annotations returns any metadata needed alongside the data. None atm.
func (*myResolvedResource) Annotations() map[string]string {
  return nil
}

// RefSource is the source reference of the remote data that records where the remote
// file came from including the url, digest and the entrypoint. None atm.
func (*myResolvedResource) RefSource() *pipelinev1.RefSource {
	return nil
}
```

```go
// Resolve uses the given resolution spec to resolve the requested file or resource.
func (r *resolver) Resolve(ctx context.Context, params []pipelinev1.Param) (framework.ResolvedResource, error) {
  return &myResolvedResource{}, nil
}

// our hard-coded resolved file to return
const pipeline = `
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: my-pipeline
spec:
  tasks:
  - name: hello-world
    taskSpec:
      steps:
      - image: alpine:3.15.1
        script: |
          echo "hello world"
`

// myResolvedResource wraps the data we want to return to Pipelines
type myResolvedResource struct {}

// Data returns the bytes of our hard-coded Pipeline
func (*myResolvedResource) Data() []byte {
  return []byte(pipeline)
}

// Annotations returns any metadata needed alongside the data. None atm.
func (*myResolvedResource) Annotations() map[string]string {
  return nil
}

// RefSource is the source reference of the remote data that records where the remote
// file came from including the url, digest and the entrypoint. None atm.
func (*myResolvedResource) RefSource() *pipelinev1.RefSource {
	return nil
}
```

```go
// Resolve uses the given resolution spec to resolve the requested file or resource.
func (r *resolver) Resolve(ctx context.Context, req *v1beta1.ResolutionRequestSpec) (framework.ResolvedResource, error) {
  return &myResolvedResource{}, nil
}

// our hard-coded resolved file to return
const pipeline = `
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: my-pipeline
spec:
  tasks:
  - name: hello-world
    taskSpec:
      steps:
      - image: alpine:3.15.1
        script: |
          echo "hello world"
`

// myResolvedResource wraps the data we want to return to Pipelines
type myResolvedResource struct {}

// Data returns the bytes of our hard-coded Pipeline
func (*myResolvedResource) Data() []byte {
  return []byte(pipeline)
}

// Annotations returns any metadata needed alongside the data. None atm.
func (*myResolvedResource) Annotations() map[string]string {
  return nil
}

// RefSource is the source reference of the remote data that records where the remote
// file came from including the url, digest and the entrypoint. None atm.
func (*myResolvedResource) RefSource() *pipelinev1.RefSource {
	return nil
}
```

Best practice: In order to enable Tekton Chains to record the source
information of the remote data in the SLSA provenance, the resolver should
implement the `RefSource()` method to return a correct RefSource value. See the
following example.

```go
// RefSource is the source reference of the remote data that records where the remote
// file came from including the url, digest and the entrypoint.
func (*myResolvedResource) RefSource() *pipelinev1.RefSource {
	return &v1.RefSource{
		URI: "https://github.com/user/example",
		Digest: map[string]string{
			"sha1": "example",
		},
		EntryPoint: "foo/bar/task.yaml",
	}
}
```

## The deployment configuration

Finally, our resolver needs some deployment configuration so that it can
run in Kubernetes.

A full description of the config is beyond the scope of a short howto
but in summary we'll tell Kubernetes to run our resolver application
along with some environment variables and other configuration that the
underlying `knative` framework expects. The deployed application is put
in the `tekton-pipelines` namespace and uses `ko` to build its
container image. Finally the `ServiceAccount` our deployment uses is
`tekton-pipelines-resolvers`, which is the default `ServiceAccount` shared by all
resolvers in the `tekton-pipelines-resolvers` namespace.

The full configuration follows:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demoresolver
  namespace: tekton-pipelines-resolvers
spec:
  replicas: 1
  selector:
    matchLabels:
      app: demoresolver
  template:
    metadata:
      labels:
        app: demoresolver
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchLabels:
                  app: demoresolver
              topologyKey: kubernetes.io/hostname
            weight: 100
      serviceAccountName: tekton-pipelines-resolvers
      containers:
      - name: controller
        image: ko://example.com/demoresolver/cmd/demoresolver
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
          limits:
            cpu: 1000m
            memory: 1000Mi
        ports:
        - name: metrics
          containerPort: 9090
        env:
        - name: SYSTEM_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: CONFIG_LOGGING_NAME
          value: config-logging
        - name: CONFIG_OBSERVABILITY_NAME
          value: config-observability
        - name: METRICS_DOMAIN
          value: tekton.dev/resolution
        securityContext:
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
          runAsNonRoot: true
          capabilities:
            drop:
            - all
```

Phew, ok, put all that in a file at `config/demo-resolver-deployment.yaml`
and you'll be ready to deploy your application to Kubernetes and see it
work!

## Trying it out

Now that all the code is written your new resolver should be ready to
deploy to a Kubernetes cluster. We'll use `ko` to build and deploy the
application:

```bash
$ ko apply -f ./config/demo-resolver-deployment.yaml
```

Assuming the resolver deployed successfully you should be able to see it
in the output from the following command:

```bash
$ kubectl get deployments -n tekton-pipelines

# And here's approximately what you should see when you run this command:
NAME          READY   UP-TO-DATE   AVAILABLE   AGE
controller    1/1     1            1           2d21h
demoresolver  1/1     1            1           91s
webhook       1/1     1            1           2d21
```

To exercise your new resolver, let's submit a request for its hard-coded
pipeline. Create a file called `test-request.yaml` with the following
content:

```yaml
apiVersion: resolution.tekton.dev/v1beta1
kind: ResolutionRequest
metadata:
  name: test-request
  labels:
    resolution.tekton.dev/type: demo
```

And submit this request with the following command:

```bash
$ kubectl apply -f ./test-request.yaml && kubectl get --watch resolutionrequests
```

You should soon see your ResolutionRequest printed to screen with a True
value in its SUCCEEDED column:

```bash
resolutionrequest.resolution.tekton.dev/test-request created
NAME           SUCCEEDED   REASON
test-request   True
```

Press Ctrl-C to get back to the command line.

If you now take a look at the ResolutionRequest's YAML you'll see the
hard-coded pipeline yaml in its `status.data` field. It won't be totally
recognizable, though, because it's encoded as base64. Have a look with the
following command:

```bash
$ kubectl get resolutionrequest test-request -o yaml
```

You can convert that base64 data back into yaml with the following
command:

```bash
$ kubectl get resolutionrequest test-request -o jsonpath="{$.status.data}" | base64 -d
```

Great work, you've successfully written a Resolver from scratch!

## Next Steps

At this point you could start to expand the `Resolve()` method in your
Resolver to fetch data from your storage backend of choice.

Or if you prefer to take a look at a more fully-realized example of a
Resolver, see the [code for the `gitresolver` hosted in the Tekton
Pipeline repo](https://github.com/tektoncd/pipeline/tree/main/pkg/resolution/resolver/git/).

Finally, another direction you could take this would be to try writing a
`PipelineRun` for Tekton Pipelines that speaks to your Resolver. Can
you get a `PipelineRun` to execute successfully that uses the hard-coded
`Pipeline` your Resolver returns?

---

Except as otherwise noted, the content of this page is licensed under the
[Creative Commons Attribution 4.0 License](https://creativecommons.org/licenses/by/4.0/),
and code samples are licensed under the
[Apache 2.0 License](https://www.apache.org/licenses/LICENSE-2.0).

linkTitle   How to write a Resolver  weight  104            How to write a Resolver  This how to will outline the steps a developer needs to take when creating a new  very basic  Resolver  Rather than focus on support for a particular version control system or cloud platform this Resolver will simply respond with some hard coded YAML   If you aren t yet familiar with the meaning of  resolution  when it comes to Tekton  a short summary follows  You might also want to read a little bit into Tekton Pipelines  particularly  the docs on specifying a target Pipeline to run    pipelineruns md specifying the target pipeline  and  if you re feeling particularly brave or bored  the  really long design doc describing Tekton Resolution  https   github com tektoncd community blob main teps 0060 remote resource resolution md       What s a Resolver   A Resolver is a program that runs in a Kubernetes cluster alongside  Tekton Pipelines  https   github com tektoncd pipeline  and  resolves  requests for  Tasks  and  Pipelines  from remote locations  More concretely  if a user submitted a  PipelineRun  that needed a Pipeline YAML stored in a git repo  then it would be a  Resolver  that s responsible for fetching the YAML file from git and returning it to Tekton Pipelines   This pattern extends beyond just git  allowing a developer to integrate support for other version control systems  cloud buckets  or storage systems without having to modify Tekton Pipelines itself      Just want to see the working example   If you d prefer to look at the end result of this howto you can take a visit the     resolver template     resolver template  in the Tekton Resolution repo  That template is built on the code from this howto to get you up and running quickly      Pre requisites  Before getting started with this howto you ll need to be comfortable developing in Go and have a general understanding of how Tekton Resolution works   You ll also need the following     A computer with     kubectl   https   kubernetes io docs tasks tools  kubectl  and     ko   https   github com google ko  installed    A Kubernetes cluster running at least Kubernetes 1 28  A   kind    cluster  https   kind sigs k8s io docs user quick start  installation    should work fine for following the guide on your local machine    An image registry that you can push images to  If you re using  kind    make sure your  KO DOCKER REPO  environment variable is set to    kind local     Tekton Pipelines and remote resolvers installed in your Kubernetes   cluster  See  the installation   guide    install md installing and configuring remote task and pipeline resolution  for   instructions on installing it      First Steps  The first thing to do is create an initial directory structure for your project  For this example we ll create a directory and initialize a new go module with a few subdirectories for our code      bash   mkdir demoresolver    cd demoresolver    go mod init example com demoresolver    mkdir  p cmd demoresolver    mkdir config      The  cmd demoresolver  directory will contain code for the resolver and the  config  directory will eventually contain a yaml file for deploying the resolver to Kubernetes      Initializing the resolver s binary  A Resolver is ultimately just a program running in your cluster  so the first step is to fill out the initial code for starting that program  Our resolver here is going to be extremely simple and doesn t need any flags or special environment variables  so we ll just initialize it with a little bit of boilerplate   Create  cmd demoresolver main go  with the following setup code         go package main  import      context     github com tektoncd pipeline pkg remoteresolution resolver framework     knative dev pkg injection sharedmain     func main       sharedmain Main  controller       framework NewController context Background     resolver            type resolver struct              go package main  import      context     github com tektoncd pipeline pkg resolution resolver framework     knative dev pkg injection sharedmain     func main       sharedmain Main  controller       framework NewController context Background     resolver            type resolver struct             This won t compile yet but you can download the dependencies by running      bash   Depending on your go version you might not need the  compat flag   go mod tidy  compat 1 17         Writing the Resolver  If you try to build the binary right now you ll receive the following error      bash   go build  o  dev null   cmd demoresolver  cmd demoresolver main go 11 78  cannot use  resolver    type  resolver  as type framework Resolver in argument to framework NewController           resolver does not implement framework Resolver  missing GetName method       We ve already defined our own  resolver  type but in order to get the resolver running you ll need to add the methods defined in  the  framework Resolver  interface     pkg resolution resolver framework interface go  to your  main go  file  Going through each method in turn      The  Initialize  method  This method is used to start any libraries or otherwise setup any prerequisites your resolver needs  For this example we won t need anything so this method can just return  nil       go    Initialize sets up any dependencies needed by the resolver  None atm  func  r  resolver  Initialize context Context  error     return nil           The  GetName  method  This method returns a string name that will be used to refer to this resolver  You d see this name show up in places like logs  For this simple example we ll return   Demo        go    GetName returns a string name to refer to this resolver by  func  r  resolver  GetName context Context  string     return  Demo            The  GetSelector  method  This method should return a map of string labels and their values that will be used to direct requests to this resolver  For this example the only label we re interested in matching on is defined by  tektoncd resolution       go    GetSelector returns a map of labels to match requests to this resolver  func  r  resolver  GetSelector context Context  map string string     return map string string      common LabelKeyResolverType   demo              What this does is tell the resolver framework that any  ResolutionRequest  object with a label of   resolution tekton dev type    demo   should be routed to our example resolver   We ll also need to add another import for this package at the top         go import      context          Add this one  it defines LabelKeyResolverType we use in GetSelector    github com tektoncd pipeline pkg resolution common     github com tektoncd pipeline pkg remoteresolution resolver framework     knative dev pkg injection sharedmain    pipelinev1  github com tektoncd pipeline pkg apis pipeline v1               go import      context          Add this one  it defines LabelKeyResolverType we use in GetSelector    github com tektoncd pipeline pkg resolution common     github com tektoncd pipeline pkg resolution resolver framework     knative dev pkg injection sharedmain    pipelinev1  github com tektoncd pipeline pkg apis pipeline v1                The  Validate  method  The  Validate  method checks that the resolution spec submitted as part of a resolution request are valid  Our example resolver doesn t expect any params in the spec so we ll simply ensure that the there are no params  Our example resolver also expects format for the  url  to be  demoscheme    path   so we ll validate this format  In the previous version  this was instead called  ValidateParams  method  See below  for the differences          go    Validate ensures that the resolution spec from a request is as expected  func  r  resolver  Validate ctx context Context  req  v1beta1 ResolutionRequestSpec  error     if len req Params    0       return errors New  no params allowed         url    req URL   u  err    neturl ParseRequestURI url    if err    nil       return err       if u Scheme     demoscheme        return fmt Errorf  Invalid Scheme  Want  s  Got  s    demoscheme   u Scheme        if u Path             return errors New  Empty path          return nil        You ll also need to add the  net url  as  neturl  and   errors   package to your list of imports at the top of the file              go    ValidateParams ensures that the params from a request are as expected  func  r  resolver  ValidateParams ctx context Context  params   pipelinev1 Param  error     if len req Params    0       return errors New  no params allowed         return nil            You ll also need to add the   errors   package to your list of imports at the top of the file      The  Resolve  method  We implement the  Resolve  method to do the heavy lifting of fetching the contents of a file and returning them   It takes in the resolution request spec as input  For this example we re just going to return a hard coded string of YAML  Since Tekton Pipelines currently only supports fetching Pipeline resources via remote resolution that s what we ll return    The method signature we re implementing here has a  framework ResolvedResource  interface as one of its return values  This is another type we have to implement but it has a small footprint           go    Resolve uses the given resolution spec to resolve the requested file or resource  func  r  resolver  Resolve ctx context Context  req  v1beta1 ResolutionRequestSpec   framework ResolvedResource  error      return  myResolvedResource    nil       our hard coded resolved file to return const pipeline     apiVersion  tekton dev v1beta1 kind  Pipeline metadata    name  my pipeline spec    tasks      name  hello world     taskSpec        steps          image  alpine 3 15 1         script              echo  hello world        myResolvedResource wraps the data we want to return to Pipelines type myResolvedResource struct        Data returns the bytes of our hard coded Pipeline func   myResolvedResource  Data     byte     return   byte pipeline        Annotations returns any metadata needed alongside the data  None atm  func   myResolvedResource  Annotations   map string string     return nil       RefSource is the source reference of the remote data that records where the remote     file came from including the url  digest and the entrypoint  None atm  func   myResolvedResource  RefSource    pipelinev1 RefSource    return nil                go    Resolve uses the given resolution spec to resolve the requested file or resource  func  r  resolver  Resolve ctx context Context  params   pipelinev1 Param   framework ResolvedResource  error      return  myResolvedResource    nil       our hard coded resolved file to return const pipeline     apiVersion  tekton dev v1beta1 kind  Pipeline metadata    name  my pipeline spec    tasks      name  hello world     taskSpec        steps          image  alpine 3 15 1         script              echo  hello world        myResolvedResource wraps the data we want to return to Pipelines type myResolvedResource struct        Data returns the bytes of our hard coded Pipeline func   myResolvedResource  Data     byte     return   byte pipeline        Annotations returns any metadata needed alongside the data  None atm  func   myResolvedResource  Annotations   map string string     return nil       RefSource is the source reference of the remote data that records where the remote     file came from including the url  digest and the entrypoint  None atm  func   myResolvedResource  RefSource    pipelinev1 RefSource    return nil                go    Resolve uses the given resolution spec to resolve the requested file or resource  func  r  resolver  Resolve ctx context Context  req  v1beta1 ResolutionRequestSpec   framework ResolvedResource  error      return  myResolvedResource    nil       our hard coded resolved file to return const pipeline     apiVersion  tekton dev v1beta1 kind  Pipeline metadata    name  my pipeline spec    tasks      name  hello world     taskSpec        steps          image  alpine 3 15 1         script              echo  hello world        myResolvedResource wraps the data we want to return to Pipelines type myResolvedResource struct        Data returns the bytes of our hard coded Pipeline func   myResolvedResource  Data     byte     return   byte pipeline        Annotations returns any metadata needed alongside the data  None atm  func   myResolvedResource  Annotations   map string string     return nil       RefSource is the source reference of the remote data that records where the remote     file came from including the url  digest and the entrypoint  None atm  func   myResolvedResource  RefSource    pipelinev1 RefSource    return nil        Best practice  In order to enable Tekton Chains to record the source  information of the remote data in the SLSA provenance  the resolver should  implement the  RefSource    method to return a correct RefSource value  See the  following example      go    RefSource is the source reference of the remote data that records where the remote     file came from including the url  digest and the entrypoint  func   myResolvedResource  RefSource    pipelinev1 RefSource    return  v1 RefSource    URI   https   github com user example     Digest  map string string      sha1    example          EntryPoint   foo bar task yaml                 The deployment configuration  Finally  our resolver needs some deployment configuration so that it can run in Kubernetes   A full description of the config is beyond the scope of a short howto but in summary we ll tell Kubernetes to run our resolver application along with some environment variables and other configuration that the underlying  knative  framework expects  The deployed application is put in the  tekton pipelines  namespace and uses  ko  to build its container image  Finally the  ServiceAccount  our deployment uses is  tekton pipelines resolvers   which is the default  ServiceAccount  shared by all resolvers in the  tekton pipelines resolvers  namespace   The full configuration follows      yaml apiVersion  apps v1 kind  Deployment metadata    name  demoresolver   namespace  tekton pipelines resolvers spec    replicas  1   selector      matchLabels        app  demoresolver   template      metadata        labels          app  demoresolver     spec        affinity          podAntiAffinity            preferredDuringSchedulingIgnoredDuringExecution              podAffinityTerm                labelSelector                  matchLabels                    app  demoresolver               topologyKey  kubernetes io hostname             weight  100       serviceAccountName  tekton pipelines resolvers       containers          name  controller         image  ko   example com demoresolver cmd demoresolver         resources            requests              cpu  100m             memory  100Mi           limits              cpu  1000m             memory  1000Mi         ports            name  metrics           containerPort  9090         env            name  SYSTEM NAMESPACE           valueFrom              fieldRef                fieldPath  metadata namespace           name  CONFIG LOGGING NAME           value  config logging           name  CONFIG OBSERVABILITY NAME           value  config observability           name  METRICS DOMAIN           value  tekton dev resolution         securityContext            allowPrivilegeEscalation  false           readOnlyRootFilesystem  true           runAsNonRoot  true           capabilities              drop                all      Phew  ok  put all that in a file at  config demo resolver deployment yaml  and you ll be ready to deploy your application to Kubernetes and see it work      Trying it out  Now that all the code is written your new resolver should be ready to deploy to a Kubernetes cluster  We ll use  ko  to build and deploy the application      bash   ko apply  f   config demo resolver deployment yaml      Assuming the resolver deployed successfully you should be able to see it in the output from the following command      bash   kubectl get deployments  n tekton pipelines    And here s approximately what you should see when you run this command  NAME          READY   UP TO DATE   AVAILABLE   AGE controller    1 1     1            1           2d21h demoresolver  1 1     1            1           91s webhook       1 1     1            1           2d21      To exercise your new resolver  let s submit a request for its hard coded pipeline  Create a file called  test request yaml  with the following content      yaml apiVersion  resolution tekton dev v1beta1 kind  ResolutionRequest metadata    name  test request   labels      resolution tekton dev type  demo      And submit this request with the following command      bash   kubectl apply  f   test request yaml    kubectl get   watch resolutionrequests      You should soon see your ResolutionRequest printed to screen with a True value in its SUCCEEDED column      bash resolutionrequest resolution tekton dev test request created NAME           SUCCEEDED   REASON test request   True      Press Ctrl C to get back to the command line   If you now take a look at the ResolutionRequest s YAML you ll see the hard coded pipeline yaml in its  status data  field  It won t be totally recognizable  though  because it s encoded as base64  Have a look with the following command      bash   kubectl get resolutionrequest test request  o yaml      You can convert that base64 data back into yaml with the following command      bash   kubectl get resolutionrequest test request  o jsonpath     status data     base64  d      Great work  you ve successfully written a Resolver from scratch      Next Steps  At this point you could start to expand the  Resolve    method in your Resolver to fetch data from your storage backend of choice   Or if you prefer to take a look at a more fully realized example of a Resolver  see the  code for the  gitresolver  hosted in the Tekton Pipeline repo  https   github com tektoncd pipeline tree main pkg resolution resolver git     Finally  another direction you could take this would be to try writing a  PipelineRun  for Tekton Pipelines that speaks to your Resolver  Can you get a  PipelineRun  to execute successfully that uses the hard coded  Pipeline  your Resolver returns        Except as otherwise noted  the content of this page is licensed under the  Creative Commons Attribution 4 0 License  https   creativecommons org licenses by 4 0    and code samples are licensed under the  Apache 2 0 License  https   www apache org licenses LICENSE 2 0