Skip to content

Commit

Permalink
TEP-0111 - Propagating workspaces in Taskrun
Browse files Browse the repository at this point in the history
This POC illustrates propagating workspaces defined at the `pipelinerun` stage and directly referred at the `spec`. There is no need to specify it in the `pipelinespec` followed by `tasks` and `taskspec`.
  • Loading branch information
chitrangpatel committed Sep 13, 2022
1 parent 70b3a5d commit 62f0f52
Show file tree
Hide file tree
Showing 9 changed files with 327 additions and 17 deletions.
1 change: 1 addition & 0 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@ Features currently in "alpha" are:
| [Isolated `Step` & `Sidecar` `Workspaces`](./workspaces.md#isolated-workspaces) | [TEP-0029](https://github.com/tektoncd/community/blob/main/teps/0029-step-workspaces.md) | [v0.24.0](https://github.com/tektoncd/pipeline/releases/tag/v0.24.0) | |
| [Hermetic Execution Mode](./hermetic.md) | [TEP-0025](https://github.com/tektoncd/community/blob/main/teps/0025-hermekton.md) | [v0.25.0](https://github.com/tektoncd/pipeline/releases/tag/v0.25.0) | |
| [Propagated `Parameters`](./taskruns.md#propagated-parameters) | [TEP-0107](https://github.com/tektoncd/community/blob/main/teps/0107-propagating-parameters.md) | [v0.36.0](https://github.com/tektoncd/pipeline/releases/tag/v0.36.0) | |
| [Propagated `Workspaces`](./pipelineruns.md#propagated-workspaces) | [TEP-0111](https://github.com/tektoncd/community/blob/main/teps/0111-propagating-workspaces.md) | | |
| [Windows Scripts](./tasks.md#windows-scripts) | [TEP-0057](https://github.com/tektoncd/community/blob/main/teps/0057-windows-support.md) | [v0.28.0](https://github.com/tektoncd/pipeline/releases/tag/v0.28.0) | |
| [Remote Tasks](./taskruns.md#remote-tasks) and [Remote Pipelines](./pipelineruns.md#remote-pipelines) | [TEP-0060](https://github.com/tektoncd/community/blob/main/teps/0060-remote-resolution.md) | [v0.35.0](https://github.com/tektoncd/pipeline/releases/tag/v0.35.0) | |
| [Debug](./debug.md) | [TEP-0042](https://github.com/tektoncd/community/blob/main/teps/0042-taskrun-breakpoint-on-failure.md) | [v0.26.0](https://github.com/tektoncd/pipeline/releases/tag/v0.26.0) | |
Expand Down
104 changes: 104 additions & 0 deletions docs/taskruns.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ weight: 300
- [Specifying Task-level `ComputeResources`](#specifying-task-level-computeresources)
- [Specifying a `Pod` template](#specifying-a-pod-template)
- [Specifying `Workspaces`](#specifying-workspaces)
- [Propagated Workspaces](#propagated-workspaces)
- [Specifying `Sidecars`](#specifying-sidecars)
- [Overriding `Task` `Steps` and `Sidecars`](#overriding-task-steps-and-sidecars)
- [Specifying `LimitRange` values](#specifying-limitrange-values)
Expand Down Expand Up @@ -422,6 +423,109 @@ For more information, see the following topics:
- For a list of supported `Volume` types, see [Specifying `VolumeSources` in `Workspaces`](workspaces.md#specifying-volumesources-in-workspaces).
- For an end-to-end example, see [`Workspaces` in a `TaskRun`](../examples/v1beta1/taskruns/workspace.yaml).

#### Propagated Workspaces

**([alpha only](https://github.com/tektoncd/pipeline/blob/main/docs/install.md#alpha-features))**

When using an embedded spec, workspaces from the parent `TaskRun` will be
propagated to any inlined specs without needing to be explicitly defined. This
allows authors to simplify specs by automatically propagating top-level
workspaces down to other inlined resources.
**Workspace substutions will only be made for `commands`, `args` and `script` fields of `steps`, `stepTemplates`, and `sidecars`.**

```yaml
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
generateName: propagating-workspaces-
spec:
taskSpec:
steps:
- name: simple-step
image: ubuntu
command:
- echo
args:
- $(workspaces.tr-workspace.path)
workspaces:
- emptyDir: {}
name: tr-workspace
```

Upon execution, the workspaces will be interpolated during resolution through to the taskspec.

```yaml
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
name: propagating-workspaces-ndxnc
...
spec:
...
status:
...
taskSpec:
steps:
...
workspaces:
- name: tr-workspace
```

##### Propagating Workspaces to Referenced Tasks

Workspaces can only be propagated to `embedded` task specs, not `referenced` Tasks.

```yaml
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: workspace-propagation
spec:
steps:
- name: simple-step
image: ubuntu
command:
- echo
args:
- $(workspaces.tr-workspace.path)
---
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
generateName: propagating-workspaces-
spec:
taskRef:
name: workspace-propagation
workspaces:
- emptyDir: {}
name: tr-workspace
```

Upon execution, the above taskrun will fail because the task is referenced and workspace is not propagated. It mist be explicitly defined in the `spec` of the defined Task.

```yaml
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
...
spec:
taskRef:
kind: Task
name: workspace-propagation
workspaces:
- emptyDir: {}
name: tr-workspace
status:
conditions:
- lastTransitionTime: "2022-09-13T15:12:35Z"
message: workspace binding "tr-workspace" does not match any declared workspace
reason: TaskRunValidationFailed
status: "False"
type: Succeeded
...
```

### Specifying `Sidecars`

A `Sidecar` is a container that runs alongside the containers specified
Expand Down
4 changes: 4 additions & 0 deletions docs/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,10 @@ spec:
For more information, see [Using `Workspaces` in `Tasks`](workspaces.md#using-workspaces-in-tasks)
and the [`Workspaces` in a `TaskRun`](../examples/v1beta1/taskruns/workspace.yaml) example YAML file.

### Propagated `Workspaces`

Workspaces can be propagated to embedded task specs, not referenced Tasks. For more information, see [Propagated Workspaces](taskruns.md#propagated-workspaces).

### Emitting `Results`

A Task is able to emit string results that can be viewed by users and passed to other Tasks in a Pipeline. These
Expand Down
16 changes: 16 additions & 0 deletions examples/v1beta1/taskruns/alpha/propagating_workspaces.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
generateName: propagating-workspaces-
spec:
taskSpec:
steps:
- name: simple-step
image: ubuntu
command:
- echo
args:
- $(workspaces.tr-workspace.path)
workspaces:
- emptyDir: {}
name: tr-workspace
70 changes: 57 additions & 13 deletions pkg/reconciler/taskrun/taskrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ var (
func (c *Reconciler) ReconcileKind(ctx context.Context, tr *v1beta1.TaskRun) pkgreconciler.Event {
logger := logging.FromContext(ctx)
ctx = cloudevent.ToContext(ctx, c.cloudEventClient)
// By this time, params and workspaces should not be propagated for embedded tasks so we cannot
// validate that all parameter variables and workspaces used in the TaskSpec are declared by the Task.
ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, true)

// Read the initial condition
before := tr.Status.GetCondition(apis.ConditionSucceeded)
Expand Down Expand Up @@ -389,7 +392,22 @@ func (c *Reconciler) prepare(ctx context.Context, tr *v1beta1.TaskRun) (*v1beta1
return nil, nil, controller.NewPermanentError(err)
}

if err := workspace.ValidateBindings(ctx, taskSpec.Workspaces, tr.Spec.Workspaces); err != nil {
var workspaceDeclarations []v1beta1.WorkspaceDeclaration
// Propagating workspaces allows users to skip declarations
// In order to validate the workspace bindings we create declarations based on
// the workspaces provided in the task run spec. This logic is hidden behind the
// alpha feature gate since propagating workspaces is behind that feature gate.
// In addition, we only allow this feature for embedded taskSpec.
if config.FromContextOrDefaults(ctx).FeatureFlags.EnableAPIFields == config.AlphaAPIFields && tr.Spec.TaskSpec != nil {
for _, ws := range tr.Spec.Workspaces {
wspaceDeclaration := v1beta1.WorkspaceDeclaration{Name: ws.Name}
workspaceDeclarations = append(workspaceDeclarations, wspaceDeclaration)
}
workspaceDeclarations = append(workspaceDeclarations, taskSpec.Workspaces...)
} else {
workspaceDeclarations = taskSpec.Workspaces
}
if err := workspace.ValidateBindings(ctx, workspaceDeclarations, tr.Spec.Workspaces); err != nil {
logger.Errorf("TaskRun %q workspaces are invalid: %v", tr.Name, err)
tr.Status.MarkResourceFailed(podconvert.ReasonFailedValidation, err)
return nil, nil, controller.NewPermanentError(err)
Expand Down Expand Up @@ -428,13 +446,20 @@ func (c *Reconciler) reconcile(ctx context.Context, tr *v1beta1.TaskRun, rtr *re
defer c.durationAndCountMetrics(ctx, tr)
logger := logging.FromContext(ctx)
recorder := controller.GetEventRecorder(ctx)
var err error

// Get the randomized volume names assigned to workspace bindings
workspaceVolumes := workspace.CreateVolumes(tr.Spec.Workspaces)

ts := updateTaskSpecParamsContextsResults(ctx, tr, rtr)
ts, err := applyParamsContextsResultsAndWorkspaces(ctx, tr, rtr, workspaceVolumes)
if err != nil {
logger.Errorf("Error updating task spec parameters, contexts, results and workspaces: %s", err)
return err
}
tr.Status.TaskSpec = ts

// Get the TaskRun's Pod if it should have one. Otherwise, create the Pod.
var pod *corev1.Pod
var err error

if tr.Status.PodName != "" {
pod, err = c.podLister.Pods(tr.Namespace).Get(tr.Status.PodName)
Expand Down Expand Up @@ -480,7 +505,7 @@ func (c *Reconciler) reconcile(ctx context.Context, tr *v1beta1.TaskRun, rtr *re
// This is used by createPod below. Changes to the Spec are not updated.
tr.Spec.Workspaces = taskRunWorkspaces
}
pod, err = c.createPod(ctx, ts, tr, rtr)
pod, err = c.createPod(ctx, ts, tr, rtr, workspaceVolumes)
if err != nil {
newErr := c.handlePodCreationError(tr, err)
logger.Errorf("Failed to create task run pod for taskrun %q: %v", tr.Name, newErr)
Expand Down Expand Up @@ -664,7 +689,7 @@ func (c *Reconciler) failTaskRun(ctx context.Context, tr *v1beta1.TaskRun, reaso

// createPod creates a Pod based on the Task's configuration, with pvcName as a volumeMount
// TODO(dibyom): Refactor resource setup/substitution logic to its own function in the resources package
func (c *Reconciler) createPod(ctx context.Context, ts *v1beta1.TaskSpec, tr *v1beta1.TaskRun, rtr *resources.ResolvedTaskResources) (*corev1.Pod, error) {
func (c *Reconciler) createPod(ctx context.Context, ts *v1beta1.TaskSpec, tr *v1beta1.TaskRun, rtr *resources.ResolvedTaskResources, workspaceVolumes map[string]corev1.Volume) (*corev1.Pod, error) {
logger := logging.FromContext(ctx)
inputResources, err := resourceImplBinding(rtr.Inputs, c.Images)
if err != nil {
Expand Down Expand Up @@ -700,12 +725,6 @@ func (c *Reconciler) createPod(ctx context.Context, ts *v1beta1.TaskSpec, tr *v1
ts = resources.ApplyResources(ts, inputResources, "inputs")
ts = resources.ApplyResources(ts, outputResources, "outputs")

// Get the randomized volume names assigned to workspace bindings
workspaceVolumes := workspace.CreateVolumes(tr.Spec.Workspaces)

// Apply workspace resource substitution
ts = resources.ApplyWorkspaces(ctx, ts, ts.Workspaces, tr.Spec.Workspaces, workspaceVolumes)

// By this time, params and workspaces should be propagated down so we can
// validate that all parameter variables and workspaces used in the TaskSpec are declared by the Task.
ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
Expand All @@ -715,6 +734,7 @@ func (c *Reconciler) createPod(ctx context.Context, ts *v1beta1.TaskSpec, tr *v1
}

ts, err = workspace.Apply(ctx, *ts, tr.Spec.Workspaces, workspaceVolumes)

if err != nil {
logger.Errorf("Failed to create a pod for taskrun: %s due to workspace error %v", tr.Name, err)
return nil, err
Expand Down Expand Up @@ -757,7 +777,8 @@ func (c *Reconciler) createPod(ctx context.Context, ts *v1beta1.TaskSpec, tr *v1
return pod, err
}

func updateTaskSpecParamsContextsResults(ctx context.Context, tr *v1beta1.TaskRun, rtr *resources.ResolvedTaskResources) *v1beta1.TaskSpec {
// applyParamsContextsResultsAndWorkspaces applies paramater, context, results and workspace substitutions to the TaskSpec.
func applyParamsContextsResultsAndWorkspaces(ctx context.Context, tr *v1beta1.TaskRun, rtr *resources.ResolvedTaskResources, workspaceVolumes map[string]corev1.Volume) (*v1beta1.TaskSpec, error) {
ts := rtr.TaskSpec.DeepCopy()
var defaults []v1beta1.ParamSpec
if len(ts.Params) > 0 {
Expand All @@ -775,7 +796,30 @@ func updateTaskSpecParamsContextsResults(ctx context.Context, tr *v1beta1.TaskRu
// Apply step exitCode path substitution
ts = resources.ApplyStepExitCodePath(ts)

return ts
// Apply workspace resource substitution
if config.FromContextOrDefaults(ctx).FeatureFlags.EnableAPIFields == config.AlphaAPIFields {
// propagate workspaces from taskrun to task.
twn := []string{}
for _, tw := range ts.Workspaces {
twn = append(twn, tw.Name)
}

for _, trw := range tr.Spec.Workspaces {
skip := false
for _, tw := range twn {
if tw == trw.Name {
skip = true
break
}
}
if !skip {
ts.Workspaces = append(ts.Workspaces, v1beta1.WorkspaceDeclaration{Name: trw.Name})
}
}
}
ts = resources.ApplyWorkspaces(ctx, ts, ts.Workspaces, tr.Spec.Workspaces, workspaceVolumes)

return ts, nil
}

func isExceededResourceQuotaError(err error) bool {
Expand Down
64 changes: 60 additions & 4 deletions pkg/reconciler/taskrun/taskrun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
ttesting "github.com/tektoncd/pipeline/pkg/reconciler/testing"
"github.com/tektoncd/pipeline/pkg/reconciler/volumeclaim"
resolutioncommon "github.com/tektoncd/pipeline/pkg/resolution/common"
"github.com/tektoncd/pipeline/pkg/workspace"
"github.com/tektoncd/pipeline/test"
"github.com/tektoncd/pipeline/test/diff"
eventstest "github.com/tektoncd/pipeline/test/events"
Expand Down Expand Up @@ -2467,6 +2468,50 @@ status:
}
}

func TestPropagatedWorkspaces(t *testing.T) {
taskRun := parse.MustParseTaskRun(t, `
metadata:
name: test-taskrun-propagating-workspaces
namespace: foo
spec:
taskSpec:
steps:
- args:
- replacedArgs - $(workspaces.tr-workspace.path)
command:
- echo
image: foo
name: simple-step
workspaces:
- emptyDir: {}
name: tr-workspace
`)
d := test.Data{
TaskRuns: []*v1beta1.TaskRun{taskRun},
}
d.ConfigMaps = []*corev1.ConfigMap{{
ObjectMeta: metav1.ObjectMeta{Namespace: system.Namespace(), Name: config.GetFeatureFlagsConfigName()},
Data: map[string]string{
"enable-api-fields": config.AlphaAPIFields,
},
}}
testAssets, cancel := getTaskRunController(t, d)
defer cancel()
createServiceAccount(t, testAssets, "default", taskRun.Namespace)
c := testAssets.Controller
if err := c.Reconciler.Reconcile(testAssets.Ctx, getRunName(taskRun)); err == nil {
t.Fatalf("Could not reconcile the taskrun: %v", err)
}
getTaskRun, _ := testAssets.Clients.Pipeline.TektonV1beta1().TaskRuns(taskRun.Namespace).Get(testAssets.Ctx, taskRun.Name, metav1.GetOptions{})

want := []v1beta1.WorkspaceDeclaration{{
Name: "tr-workspace",
}}
if c := cmp.Diff(want, getTaskRun.Status.TaskSpec.Workspaces); c != "" {
t.Errorf("TestPropagatedWorkspaces errored with: %s", diff.PrintWantGot(c))
}
}

func TestExpandMountPath(t *testing.T) {
expectedMountPath := "/temppath/replaced"
expectedReplacedArgs := fmt.Sprintf("replacedArgs - %s", expectedMountPath)
Expand Down Expand Up @@ -2545,8 +2590,14 @@ spec:
Kind: "Task",
TaskSpec: &v1beta1.TaskSpec{Steps: simpleTask.Spec.Steps, Workspaces: simpleTask.Spec.Workspaces},
}
taskSpec := updateTaskSpecParamsContextsResults(context.Background(), taskRun, rtr)
pod, err := r.createPod(testAssets.Ctx, taskSpec, taskRun, rtr)
ctx := config.EnableAlphaAPIFields(context.Background())
workspaceVolumes := workspace.CreateVolumes(taskRun.Spec.Workspaces)
taskSpec, err := applyParamsContextsResultsAndWorkspaces(ctx, taskRun, rtr, workspaceVolumes)
if err != nil {
t.Fatalf("update task spec threw error %v", err)
}

pod, err := r.createPod(testAssets.Ctx, taskSpec, taskRun, rtr, workspaceVolumes)

if err != nil {
t.Fatalf("create pod threw error %v", err)
Expand Down Expand Up @@ -2649,8 +2700,13 @@ spec:
TaskSpec: &v1beta1.TaskSpec{Steps: simpleTask.Spec.Steps, Workspaces: simpleTask.Spec.Workspaces},
}

taskSpec := updateTaskSpecParamsContextsResults(context.Background(), taskRun, rtr)
_, err := r.createPod(testAssets.Ctx, taskSpec, taskRun, rtr)
workspaceVolumes := workspace.CreateVolumes(taskRun.Spec.Workspaces)
ctx := config.EnableAlphaAPIFields(context.Background())
taskSpec, err := applyParamsContextsResultsAndWorkspaces(ctx, taskRun, rtr, workspaceVolumes)
if err != nil {
t.Errorf("update task spec threw an error: %v", err)
}
_, err = r.createPod(testAssets.Ctx, taskSpec, taskRun, rtr, workspaceVolumes)

if err == nil || err.Error() != expectedError {
t.Errorf("Expected to fail validation for duplicate Workspace mount paths, error was %v", err)
Expand Down
Loading

0 comments on commit 62f0f52

Please sign in to comment.