Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TEP-0111 - Propagating workspaces in taskruns #5081

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
chitrangpatel marked this conversation as resolved.
Show resolved Hide resolved
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
chitrangpatel marked this conversation as resolved.
Show resolved Hide resolved
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) {
chitrangpatel marked this conversation as resolved.
Show resolved Hide resolved
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())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would you mind creating an issue to refactor these tests to call Reconcile rather than all of these helpers?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I can do that.

workspaceVolumes := workspace.CreateVolumes(taskRun.Spec.Workspaces)
chitrangpatel marked this conversation as resolved.
Show resolved Hide resolved
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)
chitrangpatel marked this conversation as resolved.
Show resolved Hide resolved
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