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 12, 2022
1 parent 11e5c37 commit bad5e4b
Show file tree
Hide file tree
Showing 10 changed files with 267 additions and 18 deletions.
1 change: 1 addition & 0 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,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-resolutiond.md) | | |
| [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
51 changes: 51 additions & 0 deletions docs/taskruns.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,57 @@ 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
```

Workspaces can be propagated to embedded task specs, not referenced Tasks. For more information, see [Referenced TaskRuns within Embedded PipelineRuns](pipelineruns.md#referenced-taskruns-within-embedded-pipelineruns).

### Specifying `Sidecars`

A `Sidecar` is a container that runs alongside the containers specified
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
1 change: 0 additions & 1 deletion pkg/apis/pipeline/v1beta1/pipelinerun_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ func (ps *PipelineRunSpec) validatePipelineRunParameters(ctx context.Context) (e
}
}
}

return errs
}

Expand Down
1 change: 1 addition & 0 deletions pkg/reconciler/pipelinerun/pipelinerun.go
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,7 @@ func getTaskrunWorkspaces(pr *v1beta1.PipelineRun, rpt *resources.ResolvedPipeli
for _, binding := range pr.Spec.Workspaces {
pipelineRunWorkspaces[binding.Name] = binding
}

for _, ws := range rpt.PipelineTask.Workspaces {
taskWorkspaceName, pipelineTaskSubPath, pipelineWorkspaceName := ws.Name, ws.SubPath, ws.Workspace

Expand Down
66 changes: 53 additions & 13 deletions pkg/reconciler/taskrun/taskrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,21 @@ 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.
if config.FromContextOrDefaults(ctx).FeatureFlags.EnableAPIFields == config.AlphaAPIFields {
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 @@ -427,13 +441,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

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

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 @@ -479,7 +500,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 @@ -658,7 +679,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 @@ -694,12 +715,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 @@ -709,6 +724,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 @@ -750,7 +766,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 @@ -768,7 +785,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
13 changes: 13 additions & 0 deletions pkg/workspace/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,19 @@ func Apply(ctx context.Context, ts v1beta1.TaskSpec, wb []v1beta1.WorkspaceBindi
}

for i := range wb {
if alphaAPIEnabled {
// Propagate missing Workspaces
addWorkspace := true
for _, ws := range ts.Workspaces {
if ws.Name == wb[i].Name {
addWorkspace = false
break
}
}
if addWorkspace {
ts.Workspaces = append(ts.Workspaces, v1beta1.WorkspaceDeclaration{Name: wb[i].Name})
}
}
w, err := getDeclaredWorkspace(wb[i].Name, ts.Workspaces)
if err != nil {
return nil, err
Expand Down
Loading

0 comments on commit bad5e4b

Please sign in to comment.