---
title: 0023-Implicit-parameter-mapping
authors:
  - "@Peaorl"
  - "@wlynch"
creation-date: 2020-10-01
last-updated: 2021-12-15
status: implemented
superseded-by:
- TEP-0107
---

# TEP-0023: Implicit Parameter Mapping for Embedded Specs

<!-- toc -->
- [Summary](#summary)
- [Motivation](#motivation)
  - [Goal](#goal)
  - [Non Goals](#non-goals)
- [Requirements](#requirements)
- [Proposal](#proposal)
  - [Admission Controller](#admission-controller)
  - [Caveats](#caveats)
    - [Naming / type conflicts](#naming--type-conflicts)
    - [Extra parameters](#extra-parameters)
    - [Common parameter names](#common-parameter-names)
  - [User Experience](#user-experience)
  - [Performance](#performance)
- [Design Details](#design-details)
  - [Alternatives](#alternatives)
    - [Resolve ref params as well](#resolve-ref-params-as-well)
    - [Don't allow implicit params at the Pipeline/TaskRun level](#dont-allow-implicit-params-at-the-pipelinetaskrun-level)
- [Test Plan](#test-plan)
- [Drawbacks](#drawbacks)
  - [Unused Parameter Noise](#unused-parameter-noise)
- [Future Work](#future-work)
- [References](#references)
<!-- /toc -->

## Summary

Pipeline specs can sometimes feel unnecessarily verbose, particularly when the
same fields need to be passed around different Tasks again and again.

In this TEP, we propose introducing implicit parameter mapping to allow authors
to reference params in embedded specs that are not explicitly defined. Instead,
users will rely on Tekton to automatically resolve these values at admission
time.

## Motivation

We want to make it easier for users to get started with Tekton by reducing some
of the complexity in writing specs. One way we wish to do this is to reduce the
amount of repetition in Pipeline specs, inferring information as much as
possible. Our hope is that this will make it easier to get started with simple
PipelineRun definitions to let users focus on the core functionality of their
Pipelines and not overwhelm users with config verbosity. A analogy here is
imitating how many people get started writing code - first with a single
`main()` function, then breaking out to different functions/packages later as
needed to make things reusable.

Params stick out as a particularly good target here, since they need to be
passed through the PipelineRun -> Pipeline -> Tasks (often completely
unmodified), and adding additional params does not inherently modify Task
behavior if they happen to be unused.

### Goal

- Simplify authored Pipeline YAML by allowing users to omit parameters from
  embedded resources.
  - In particular, simplify the getting started experience by reducing the
    complexity of authored configs.

### Non Goals

- Support implicit mapping for PipelineResources, Workspaces, Task/PipelineRefs.

## Requirements

<!--
List the requirements for this TEP.
-->

1. Resource authors can optionally omit parameters that can be inferred from a
   parent scope.
2. Resources should be fully resolved before they are stored in etcd.
3. Clients should receive back resolved responses from API requests to avoid
   ambiguity.

## Proposal

We propose allowing for implicit parameters in a resource spec, by passing
through parameters from parents. For example:

```yaml
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: pipelinerun-with-taskspec-to-echo-message
spec:
  pipelineSpec:
    params:
      - name: MESSAGE
        type: string
    tasks:
      - name: echo-message
        taskSpec:
          params:
            - name: MESSAGE
              type: string
          steps:
            - name: echo
              image: ubuntu
              script: |
                #!/usr/bin/env bash
                echo "$(params.MESSAGE)"
        params:
          - name: MESSAGE
            value: $(params.MESSAGE)
  params:
    - name: MESSAGE
      value: "Good Morning!"
```

We want users to be able to optionally shorten this config by removing param
definitions in embedded specs:

```yaml
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: pipelinerun-with-taskspec-to-echo-message
spec:
  pipelineSpec:
    tasks:
      - name: echo-message
        taskSpec:
          steps:
            - name: echo
              image: ubuntu
              script: |
                #!/usr/bin/env bash
                echo "$(params.MESSAGE)"
  params:
    - name: MESSAGE
      value: "Good Morning!"
```

With the shortened syntax, we can derive the same information we need from the
top-most parameter definition -

1. We have a param named `MESSAGE`
2. The param value is of type `string`
3. No default value is required since we already have a value.

Since these are all embedded specs within the same resource, our expectation is
that this shortened syntax will not take away from the context knowledge of
parameters and how they should be used. A good analogy here is variable scoping
found in most programming languages - e.g.

```go
x := "Hi!"
func() {
  // x is still reachable, even though it's not explicitly defined parameter.
  fmt.Println(x)
}()
```

### Admission Controller

This logic should live within the Pipelines mutating admission controller.

Although the resource author is using a shortcut, we should still store and
validate a fully resolved spec. This spec should look very similar to the
explicit config, using the information we derived:

```yaml
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: pipelinerun-with-taskspec-to-echo-message
spec:
  pipelineSpec:
    params:
      - name: MESSAGE
        type: string
    tasks:
      - name: echo-message
        taskSpec:
          params:
            - name: MESSAGE
              type: string
          steps:
            - name: echo
              image: ubuntu
              script: |
                #!/usr/bin/env bash
                echo "$(params.MESSAGE)"
        params:
          - name: MESSAGE
            value: $(params.MESSAGE)
  params:
    - name: MESSAGE
      value: "Good Morning!"
```

[Extra params are safe to include in Tasks](https://github.com/tektoncd/pipeline/blob/0593c7bef395505fae8b2e611cf4632e2e980e1d/examples/v1beta1/pipelineruns/pipelinerun-with-extra-params.yaml)
since they don't modify behavior unless they are actually used - because of this
there is little risk in passing through all parent param values to embedded
specs.

To avoid issues of additional webhook latency or reliability, we will not
support remote TaskRefs or PipelineRefs.

### Caveats

#### Naming / type conflicts

In cases of parameter naming conflicts, the innermost definition should win.
e.g. for the following config:

```yaml
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: pipelinerun-with-taskspec-to-echo-message
spec:
  pipelineSpec:
    tasks:
      - name: echo-message
        taskSpec:
          params:
            - name: MESSAGE
              type: string
          steps:
            - name: echo
              image: ubuntu
              script: |
                #!/usr/bin/env bash
                echo "$(params.MESSAGE)"
  params:
    - name: MESSAGE
      value: ["Good Morning!"]
```

This would resolve to something like:

```yaml
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: pipelinerun-with-taskspec-to-echo-message
spec:
  pipelineSpec:
    params:
      - name: MESSAGE
        type: array
    tasks:
      - name: echo-message
        taskSpec:
          params:
            - name: MESSAGE
              type: string
          steps:
            - name: echo
              image: ubuntu
              script: |
                #!/usr/bin/env bash
                echo "$(params.MESSAGE)"
        params:
          - name: MESSAGE
            value: $(params.MESSAGE[*])
  params:
    - name: MESSAGE
      value: ["Good Morning!"]
```

The innermost Task definition should take precedence and make the config
**invalid**, since the PipelineRun array param should not be able to override
the param definition of the task.

#### Extra parameters

Extra parameters may be passed down to embedded spec definitions, even if they
are not actually used.

e.g.

```yaml
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: pipelinerun-with-taskspec-to-echo-message
spec:
  pipelineSpec:
    params:
      - name: MESSAGE
        type: string
    tasks:
      - name: echo-message
        taskSpec:
          steps:
            - name: echo
              image: ubuntu
              script: |
                #!/usr/bin/env bash
                echo "$(params.MESSAGE)"
  params:
    - name: MESSAGE
      value: "Good Morning!"
    - name: UNUSED
      value: "unused message"
```

Would be resolved to:

```yaml
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: pipelinerun-with-taskspec-to-echo-message
spec:
  pipelineSpec:
    params:
      - name: MESSAGE
        type: string
      - name: UNUSED
        type: string
    tasks:
      - name: echo-message
        taskSpec:
          params:
            - name: MESSAGE
              type: string
            - name: UNUSED
              type: string
          steps:
            - name: echo
              image: ubuntu
              script: |
                #!/usr/bin/env bash
                echo "$(params.MESSAGE)"
        params:
          - name: MESSAGE
            value: $(params.MESSAGE)
          - name: UNUSED
            type: $(params.UNUSED)
  params:
    - name: MESSAGE
      value: "Good Morning!"
    - name: UNUSED
      value: "unused message"
```

Although it is never used, the `UNUSED` param will be plumbed through to the
underlying embedded Task. Since the Task steps do not actually use the param,
this is unlikely to affect any behavior in execution (unless the Task is doing
some sort of introspection).

#### Common parameter names

Certain parameter names may be relatively common and therefore carry a different
meaning between different `Tasks` (e.g. `url`, `path`, etc). `Tasks` with the
same parameter names that require different input could therefore inadvertently
acquire the wrong value.

We consider it the `Pipeline` author's responsibility to guard against this.

Our expectation here is that if authors are choosing to embed specs, then the
context of the variables should be known to avoid problems like this. If they
require a separate/different mapping then it would be up to the author to
explicitly assign these params, either in line or in its own Task definition.

e.g.

```yaml
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: pipelinerun-with-taskspec-to-echo-message
spec:
  pipelineSpec:
    # Spec params are implicit
    tasks:
      - name: echo-message
        taskSpec:
          # Spec params are implicit
          steps:
            - name: echo
              image: ubuntu
              script: |
                #!/usr/bin/env bash
                echo "$(params.OTHERMESSAGE)"
        # Rename MESSAGE -> OTHERMESSAGE
        params:
          - name: OTHERMESSAGE
            value: $(params.MESSAGE)
  params:
    - name: MESSAGE
      value: "Good Morning!"
```

### User Experience

Our goal with this change is to make it easier to write PipelineRuns. We
particularly see value in getting started use cases where users may want to
start with a single-file PipelineRun with embedded specs before refactoring into
different Pipelines/Tasks for reusability later (similar to how one might write
a binary with a single `main` before refactoring to different funcs/packages).

### Performance

We expect this to add negligible overhead to Tekton's validating/mutating
webhook. This will only be supported for embedded specs, so there will be no
remote calls to look up additional resources.

## Design Details

The Tekton Pipeline validating/mutating webhook will resolve the parameter in
embedded fields contained in `Pipeline` and `PipelineRun`. This transforms the
implicitly specified `Pipeline` specification into an explicitly defined
`Pipeline` specification. After the transformation, the webhook performs the
regular `Pipeline` specification validation checks.

To implement this, we propose adding additional data into the
[`SetDefaults` context](https://github.com/tektoncd/pipeline/blob/a593e3225a57d874426f40ba929a885e8604aaa9/pkg/apis/pipeline/v1beta1/pipeline_defaults.go#L31)
to keep track of seen PipelineRun/TaskRun parameters. If a Param is defined at
the current spec level we will use that first, else we use the context Params to
propagate implicit values down the spec stack (e.g. PipelineRun -> Pipeline ->
Task) for additional resolution. Because we are introducing additional params
into the resolved spec, we will need to remove
[restrictions around unused params](https://github.com/tektoncd/pipeline/blob/a593e3225a57d874426f40ba929a885e8604aaa9/pkg/reconciler/taskrun/validate_resources.go#L91-L94).

Since this is strictly an additive change, any application that depends on the
`Pipeline` specification stored in a cluster does not need to be updated. We do
not anticipate needing to block this behind a feature gate.

### Alternatives

#### Resolve ref params as well

This proposal does not do much to address some of the pain for the original
motivating issues (https://github.com/tektoncd/pipeline/issues/3050,
https://github.com/tektoncd/pipeline/issues/1484), where TaskRef parameters /
resources were equally hard to work with.

We suspect that this will require additional work to resolve, particularly
because of the remote resources that complicate validation. We are considering
this out of scope for now, focusing on what we think will be a small but
meaningful improvement. (We suspect we will want to look at these as a follow up
though).

#### Don't allow implicit params at the Pipeline/TaskRun level

A drawback of this approach is making things easier for users makes things more
complex for platforms, particularly when it comes to conformance. e.g. this
change adds more rules for how param fields need to be processed, which would
need to be replicated across every Tekton conformant implementation if they are
not leveraging the base library. We could take a stance that the fields for
Pipeline/TaskRun primatives should remain explicit in order to simplify logic
for platform implementations, and instead rely on higher-level types built on
top of Pipelines/Tasks to handle this kind of user logic.

As of today we don't
[yet have this kind of higher-level type in vanilla Tekton](https://github.com/tektoncd/community/issues/464),
but even if/when we did there would likely still be value in adding features
that simplify the user authoring process for Pipeline/TaskRuns if they continue
to be types that are directly used.

## Test Plan

- Unit tests for checking that the webhook correctly transforms an implicit
  specification into an explicit resolved specs.

## Drawbacks

### Unused Parameter Noise

A consequence of this design is that all parameters starting from the
PipelineRun will propagate down to every embedded Task, regardless if the Task
asked for these values or not. While this is okay from an execution standpoint
(i.e. if extra params are passed in, they're never actually used and shouldn't
affect execution), this might create noise when looking at Run history.

For now we will consider this out of scope - if this really bothered someone
they could explicitly define a Task definition to prune out unneeded params.
e.g. [rewriting the example from earlier](#extra-parameters):

```yaml
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: echo
spec:
  params:
    - name: MESSAGE
      type: string
  steps:
    - name: echo
      image: ubuntu
      script: |
        #!/usr/bin/env bash
        echo "$(params.MESSAGE)"
---
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: pipelinerun-with-taskspec-to-echo-message
spec:
  pipelineSpec:
    tasks:
      - name: echo-message
        taskRef:
          name: echo
        params:
          - name: MESSAGE
            value: $(params.MESSAGE)
  params:
    - name: MESSAGE
      value: "Good Morning!"
    - name: UNUSED
      value: "unused message"
```

This would ensure that only the `MESSAGE` param is passed to the `echo` Task.

We could look into pruning the child params based on which are actually used,
but this is complexity we are not interested in adding at the moment.

## Future Work

- Can we do better with TaskRefs/PipelineRefs?
- Can we extend this behavior to Triggers as well for inlined TriggerTemplates?

## References

1. [Passing parameters and resource `Pipeline` -> `Task`](https://github.com/tektoncd/pipeline/issues/1484)
2. [Add support for implicit param mapping](https://github.com/tektoncd/pipeline/issues/3050)
3. [Non-standardized parameter and resource names](https://github.com/tektoncd/pipeline/issues/1484)
4. [Common parameter and resource names](https://github.com/tektoncd/pipeline/issues/1484#issuecomment-546697625)
5. [tektoncd/pipeline PR #4127 - Implement implicit parameter resolution](https://github.com/tektoncd/pipeline/pull/4127)