From 209581e52521aa36449e97837741fc256e355c5f Mon Sep 17 00:00:00 2001 From: Daniel Helfand Date: Thu, 9 Jul 2020 11:58:06 -0500 Subject: [PATCH] support VolumeClaimTemplates with --workspace --- pkg/cmd/pipeline/start.go | 6 +- pkg/cmd/task/start.go | 2 +- pkg/file/file.go | 12 +- pkg/workspaces/testdata/pvc-typo.yaml | 25 +++++ pkg/workspaces/testdata/pvc.yaml | 25 +++++ pkg/workspaces/workspaces.go | 115 ++++++++++++++------ pkg/workspaces/workspaces_test.go | 68 ++++++++++-- test/e2e/cli.go | 10 ++ test/e2e/pipeline/pipeline_test.go | 34 +++++- test/e2e/task/start_test.go | 31 +++++- test/resources/pipeline-with-workspace.yaml | 33 ++++++ test/resources/pvc.yaml | 25 +++++ test/resources/task-with-workspace.yaml | 25 +++++ 13 files changed, 353 insertions(+), 58 deletions(-) create mode 100644 pkg/workspaces/testdata/pvc-typo.yaml create mode 100644 pkg/workspaces/testdata/pvc.yaml create mode 100644 test/resources/pipeline-with-workspace.yaml create mode 100644 test/resources/pvc.yaml create mode 100644 test/resources/task-with-workspace.yaml diff --git a/pkg/cmd/pipeline/start.go b/pkg/cmd/pipeline/start.go index 8ebb77716..6aca7a2d8 100644 --- a/pkg/cmd/pipeline/start.go +++ b/pkg/cmd/pipeline/start.go @@ -274,7 +274,7 @@ func (opt *startOptions) startPipeline(pipelineStart *v1beta1.Pipeline) error { } pr.Spec.Params = param - workspaces, err := workspaces.Merge(pr.Spec.Workspaces, opt.Workspaces) + workspaces, err := workspaces.Merge(pr.Spec.Workspaces, opt.Workspaces, opt.cliparams) if err != nil { return err } @@ -736,8 +736,8 @@ func NameArg(args []string, p cli.Params, file string) (*v1beta1.Pipeline, error return pipelineFile, nil } -func parsePipeline(taskLocation string, p cli.Params) (*v1beta1.Pipeline, error) { - b, err := file.LoadFileContent(p, taskLocation, file.IsYamlFile(), fmt.Errorf("invalid file format for %s: .yaml or .yml file extension and format required", taskLocation)) +func parsePipeline(pipelineLocation string, p cli.Params) (*v1beta1.Pipeline, error) { + b, err := file.LoadFileContent(p, pipelineLocation, file.IsYamlFile(), fmt.Errorf("invalid file format for %s: .yaml or .yml file extension and format required", pipelineLocation)) if err != nil { return nil, err } diff --git a/pkg/cmd/task/start.go b/pkg/cmd/task/start.go index d4f46b671..945fa1f3a 100644 --- a/pkg/cmd/task/start.go +++ b/pkg/cmd/task/start.go @@ -335,7 +335,7 @@ func startTask(opt startOptions, args []string) error { } tr.ObjectMeta.Labels = labels - workspaces, err := workspaces.Merge(tr.Spec.Workspaces, opt.Workspaces) + workspaces, err := workspaces.Merge(tr.Spec.Workspaces, opt.Workspaces, opt.cliparams) if err != nil { return err } diff --git a/pkg/file/file.go b/pkg/file/file.go index 559f872ed..db49d5ceb 100644 --- a/pkg/file/file.go +++ b/pkg/file/file.go @@ -41,12 +41,16 @@ func LoadFileContent(p cli.Params, target string, validate TypeValidator, errorM } var content []byte - cs, err := p.Clients() - if err != nil { - return nil, fmt.Errorf("failed to create tekton client") + var cs *cli.Clients + var err error + if p != nil { + cs, err = p.Clients() + if err != nil { + return nil, fmt.Errorf("failed to create tekton client") + } } - if strings.HasPrefix(target, "http") { + if strings.HasPrefix(target, "http") && cs != nil { content, err = getRemoteContent(cs, target) } else { content, err = ioutil.ReadFile(target) diff --git a/pkg/workspaces/testdata/pvc-typo.yaml b/pkg/workspaces/testdata/pvc-typo.yaml new file mode 100644 index 000000000..39d69fcf0 --- /dev/null +++ b/pkg/workspaces/testdata/pvc-typo.yaml @@ -0,0 +1,25 @@ +# Copyright 2020 The Tekton Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: vctname +spec: + storageClassNam: "storageclassname" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 50Mi \ No newline at end of file diff --git a/pkg/workspaces/testdata/pvc.yaml b/pkg/workspaces/testdata/pvc.yaml new file mode 100644 index 000000000..502282bae --- /dev/null +++ b/pkg/workspaces/testdata/pvc.yaml @@ -0,0 +1,25 @@ +# Copyright 2020 The Tekton Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: vctname +spec: + storageClassName: "storageclassname" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 50Mi \ No newline at end of file diff --git a/pkg/workspaces/workspaces.go b/pkg/workspaces/workspaces.go index 6572fcf4d..926ed7df2 100644 --- a/pkg/workspaces/workspaces.go +++ b/pkg/workspaces/workspaces.go @@ -16,20 +16,25 @@ package workspaces import ( "errors" + "fmt" "strings" + "github.com/tektoncd/cli/pkg/cli" + "github.com/tektoncd/cli/pkg/file" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/yaml" ) var ( - nameParam = "name" - claimNameParam = "claimName" - subPathParam = "subPath" - emptyDirParam = "emptyDir" - configParam = "config" - secretParam = "secret" - configItemParam = "item" + nameParam = "name" + claimNameParam = "claimName" + subPathParam = "subPath" + emptyDirParam = "emptyDir" + configParam = "config" + secretParam = "secret" + configItemParam = "item" + volumeClaimTemplateFile = "volumeClaimTemplateFile" ) const invalidWorkspace = "invalid input format for workspace : " @@ -37,9 +42,9 @@ const invalidWorkspace = "invalid input format for workspace : " var errNotFoundParam = errors.New("param not found") // Merge merges workspacebinding already in pipelineruns with given options -func Merge(ws []v1beta1.WorkspaceBinding, optWS []string) ([]v1beta1.WorkspaceBinding, +func Merge(ws []v1beta1.WorkspaceBinding, optWS []string, p cli.Params) ([]v1beta1.WorkspaceBinding, error) { - workspaces, err := parseWorkspace(optWS) + workspaces, err := parseWorkspace(optWS, p) if err != nil { return nil, err } @@ -62,7 +67,7 @@ func Merge(ws []v1beta1.WorkspaceBinding, optWS []string) ([]v1beta1.WorkspaceBi return ws, nil } -func parseWorkspace(w []string) (map[string]v1beta1.WorkspaceBinding, error) { +func parseWorkspace(w []string, p cli.Params) (map[string]v1beta1.WorkspaceBinding, error) { ws := map[string]v1beta1.WorkspaceBinding{} for _, v := range w { @@ -83,37 +88,49 @@ func parseWorkspace(w []string) (map[string]v1beta1.WorkspaceBinding, error) { return nil, err } - err = setWorkspaceConfig(r, &wB) - if err == nil { - ws[name] = wB - nWB++ - } else if err != errNotFoundParam { - return nil, err - } + // check for volumeClaimTemplate + vctFile, err := getPar(r, volumeClaimTemplateFile) + if err != nil { + err = setWorkspaceConfig(r, &wB) + if err == nil { + ws[name] = wB + nWB++ + } else if err != errNotFoundParam { + return nil, err + } - err = setWorkspaceSecret(r, &wB) - if err == nil { - ws[name] = wB - nWB++ - } else if err != errNotFoundParam { - return nil, err - } + err = setWorkspaceSecret(r, &wB) + if err == nil { + ws[name] = wB + nWB++ + } else if err != errNotFoundParam { + return nil, err + } - err = setWorkspaceEmptyDir(r, &wB) - if err == nil { - ws[name] = wB - nWB++ - } + err = setWorkspaceEmptyDir(r, &wB) + if err == nil { + ws[name] = wB + nWB++ + } - err = setWorkspacePVC(r, &wB) - if err == nil { + err = setWorkspacePVC(r, &wB) + if err == nil { + ws[name] = wB + nWB++ + } + + if nWB != 1 { + return nil, errors.New(invalidWorkspace + v) + } + } else { + err = setWorkspaceVCTemplate(r, &wB, vctFile, p) + if err != nil { + return nil, err + } ws[name] = wB - nWB++ - } - if nWB != 1 { - return nil, errors.New(invalidWorkspace + v) } } + return ws, nil } @@ -219,6 +236,34 @@ func setWorkspaceEmptyDir(r []string, wB *v1beta1.WorkspaceBinding) error { return nil } +func setWorkspaceVCTemplate(r []string, wB *v1beta1.WorkspaceBinding, vctFile string, p cli.Params) error { + pvc, err := parseVolumeClaimTemplate(vctFile, p) + if err != nil { + return err + } + + wB.VolumeClaimTemplate = pvc + return nil +} + +func parseVolumeClaimTemplate(filePath string, p cli.Params) (*corev1.PersistentVolumeClaim, error) { + b, err := file.LoadFileContent(p, filePath, file.IsYamlFile(), fmt.Errorf("invalid file format for %s: .yaml or .yml file extension and format required", filePath)) + if err != nil { + return nil, err + } + m := map[string]interface{}{} + err = yaml.UnmarshalStrict(b, &m) + if err != nil { + return nil, err + } + + pvc := corev1.PersistentVolumeClaim{} + if err := yaml.UnmarshalStrict(b, &pvc); err != nil { + return nil, err + } + return &pvc, nil +} + func getPar(r []string, par string) (string, error) { var p string for i := range r { diff --git a/pkg/workspaces/workspaces_test.go b/pkg/workspaces/workspaces_test.go index f95a244db..f0a85016a 100644 --- a/pkg/workspaces/workspaces_test.go +++ b/pkg/workspaces/workspaces_test.go @@ -17,10 +17,13 @@ package workspaces import ( "testing" + "github.com/tektoncd/cli/pkg/cli" "github.com/tektoncd/cli/pkg/test" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestMerge(t *testing.T) { @@ -41,49 +44,49 @@ func TestMerge(t *testing.T) { } optWS := []string{} - outWS, err := Merge(ws, optWS) + outWS, err := Merge(ws, optWS, nil) if err != nil { - t.Errorf("Not expected error:" + err.Error()) + t.Errorf("Not expected error: " + err.Error()) } test.AssertOutput(t, ws, outWS) optWS = []string{"test"} - _, err = Merge(ws, optWS) + _, err = Merge(ws, optWS, nil) if err == nil { t.Errorf("Expected error") } test.AssertOutput(t, "Name not found for workspace", err.Error()) optWS = []string{"name"} - _, err = Merge(ws, optWS) + _, err = Merge(ws, optWS, nil) if err == nil { t.Errorf("Expected error") } test.AssertOutput(t, "Name not found for workspace", err.Error()) optWS = []string{"name=test,configsecret=wrong"} - _, err = Merge(ws, optWS) + _, err = Merge(ws, optWS, nil) if err == nil { t.Errorf("Expected error") } test.AssertOutput(t, invalidWorkspace+optWS[0], err.Error()) optWS = []string{"name=emptydir-data-hp,emptyDir=s3"} - _, err = Merge(ws, optWS) + _, err = Merge(ws, optWS, nil) if err == nil { t.Errorf("Expected error") } test.AssertOutput(t, invalidWorkspace+optWS[0], err.Error()) optWS = []string{"name=recipe-store,config=sensitive-recipe-storage,item=brownies"} - _, err = Merge(ws, optWS) + _, err = Merge(ws, optWS, nil) if err == nil { t.Errorf("Expected error") } test.AssertOutput(t, "invalid key value", err.Error()) optWS = []string{"name=recipe-store,secret=secret-name,item=brownies"} - _, err = Merge(ws, optWS) + _, err = Merge(ws, optWS, nil) if err == nil { t.Errorf("Expected error") } @@ -91,7 +94,7 @@ func TestMerge(t *testing.T) { optWS = []string{"name=recipe-store,config=sensitive-recipe-storage," + "secret=secret-name,item=brownies=recipe.txt"} - _, err = Merge(ws, optWS) + _, err = Merge(ws, optWS, nil) if err == nil { t.Errorf("Expected error") } @@ -102,13 +105,29 @@ func TestMerge(t *testing.T) { "name=emptydir-data-hp,emptyDir=HugePages", "name=emptydir-data-mem,emptyDir=Memory", "name=emptydir-data,emptyDir=", - "name=shared-data-path,claimName=pvc3,subPath=dir"} - outWS, err = Merge(ws, optWS) + "name=shared-data-path,claimName=pvc3,subPath=dir", + } + outWS, err = Merge(ws, optWS, nil) if err != nil { - t.Errorf("Not expected error:" + err.Error()) + t.Errorf("Not expected error: " + err.Error()) } test.AssertOutput(t, 7, len(outWS)) + var p cli.Params + optWS = []string{"name=volumeclaimtemplatews,volumeClaimTemplateFile=./testdata/pvc.yaml"} + outWS, err = Merge(ws, optWS, p) + if err != nil { + t.Errorf("Not expected error: " + err.Error()) + } + + optWS = []string{"name=volumeclaimtemplatews,volumeClaimTemplateFile=./testdata/pvc-typo.yaml"} + _, err = Merge(ws, optWS, p) + if err == nil { + t.Errorf("Expected error") + } + test.AssertOutput(t, "error unmarshaling JSON: while decoding JSON: json: unknown field \"storageClassNam\"", err.Error()) + + storageClassName := "storageclassname" expectedWS := []v1beta1.WorkspaceBinding{ {Name: "foo", ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: v1.LocalObjectReference{Name: "bar"}}}, {Name: "emptydir-data", EmptyDir: &corev1.EmptyDirVolumeSource{}}, @@ -137,6 +156,29 @@ func TestMerge(t *testing.T) { SubPath: "dir", PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: "pvc3"}, }, + { + Name: "volumeclaimtemplatews", + VolumeClaimTemplate: &corev1.PersistentVolumeClaim{ + TypeMeta: metav1.TypeMeta{ + Kind: "PersistentVolumeClaim", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "vctname", + }, + Spec: corev1.PersistentVolumeClaimSpec{ + StorageClassName: &storageClassName, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: resource.MustParse("50Mi"), + }, + }, + AccessModes: []corev1.PersistentVolumeAccessMode{ + corev1.ReadWriteOnce, + }, + }, + }, + }, } for i := range outWS { @@ -155,6 +197,8 @@ func TestMerge(t *testing.T) { test.AssertOutput(t, expectedWS[5], outWS[i]) case "shared-data-path": test.AssertOutput(t, expectedWS[6], outWS[i]) + case "volumeclaimtemplatews": + test.AssertOutput(t, expectedWS[7], outWS[i]) } } } diff --git a/test/e2e/cli.go b/test/e2e/cli.go index 228fa35aa..2d0b44d42 100644 --- a/test/e2e/cli.go +++ b/test/e2e/cli.go @@ -18,6 +18,7 @@ import ( "bytes" "os" "os/exec" + "strings" "testing" "time" @@ -211,3 +212,12 @@ func (e TknRunner) RunInteractiveTestsDummy(t *testing.T, ops *Prompt) *expect.C return c } + +// TODO: Re-write this to just get the version of Tekton components through tkn version +// as described in https://github.com/tektoncd/cli/issues/1067 +func (e TknRunner) CheckVersion(component string, version string) bool { + cmd := append([]string{e.path}, "version") + result := icmd.RunCmd(icmd.Cmd{Command: cmd, Timeout: timeout}) + + return strings.Contains(result.Stdout(), component+" version: "+version) +} diff --git a/test/e2e/pipeline/pipeline_test.go b/test/e2e/pipeline/pipeline_test.go index bd7442a0d..00798bbd4 100644 --- a/test/e2e/pipeline/pipeline_test.go +++ b/test/e2e/pipeline/pipeline_test.go @@ -173,8 +173,7 @@ func TestPipelinesE2E(t *testing.T) { t.Run("Start PipelineRun using pipeline start command with SA as 'pipeline' ", func(t *testing.T) { res := tkn.Run("pipeline", "start", tePipelineName, "-r=source-repo="+tePipelineGitResourceName, - "--showlog", - "true") + "--showlog") time.Sleep(1 * time.Second) @@ -269,6 +268,37 @@ Waiting for logs to be available... return nil }}) }) + + t.Logf("Creating Pipeline volume-from-template in namespace: %s", namespace) + e2e.Assert(t, kubectl.Create(e2e.ResourcePath("pipeline-with-workspace.yaml")), icmd.Success) + + t.Run("Start PipelineRun with --workspace and volumeClaimTemplate", func(t *testing.T) { + if tkn.CheckVersion("Pipeline", "v0.10.2") { + t.Skip("Skip test as pipeline v0.10.2 doesn't support volumeClaimTemplates") + } + + res := tkn.Run("pipeline", "start", "pipeline-with-workspace", + "--workspace=name=ws,volumeClaimTemplateFile="+e2e.ResourcePath("pvc.yaml"), + "--showlog") + + time.Sleep(1 * time.Second) + + pipelineRunGeneratedName := e2e.GetPipelineRunListWithName(c, "pipeline-with-workspace").Items[0].Name + vars["Element"] = pipelineRunGeneratedName + expected := e2e.ProcessString(`(PipelineRun started: {{.Element}} +Waiting for logs to be available... +.*)`, vars) + res.Assert(t, icmd.Expected{ + ExitCode: 0, + Err: icmd.None, + }) + assert.Assert(t, is.Regexp(expected, res.Stdout())) + + timeout := 5 * time.Minute + if err := e2e.WaitForPipelineRunState(c, pipelineRunGeneratedName, timeout, e2e.PipelineRunSucceed(pipelineRunGeneratedName), "PipelineRunSucceeded"); err != nil { + t.Errorf("Error waiting for PipelineRun to Succeed: %s", err) + } + }) } func TestPipelinesNegativeE2E(t *testing.T) { diff --git a/test/e2e/task/start_test.go b/test/e2e/task/start_test.go index ab8b4b127..d6daca625 100644 --- a/test/e2e/task/start_test.go +++ b/test/e2e/task/start_test.go @@ -43,7 +43,7 @@ func TestTaskStartE2E(t *testing.T) { t.Fatalf("Error creating tknRunner %+v", err) } - t.Logf("Creating task in namespace: %s ", namespace) + t.Logf("Creating Task read-task in namespace: %s ", namespace) e2e.Assert(t, kubectl.Create(e2e.ResourcePath("read-file.yaml")), icmd.Success) t.Logf("Creating git pipeline resource in namespace: %s", namespace) @@ -122,4 +122,33 @@ Waiting for logs to be available... return nil }}) }) + + t.Logf("Creating Task task-with-workspace in namespace: %s ", namespace) + e2e.Assert(t, kubectl.Create(e2e.ResourcePath("task-with-workspace.yaml")), icmd.Success) + + t.Run("Start TaskRun with --workspace and volumeClaimTemplate", func(t *testing.T) { + if tkn.CheckVersion("Pipeline", "v0.10.2") { + t.Skip("Skip test as pipeline v0.10.2 doesn't support volumeClaimTemplates") + } + + res := tkn.Run("task", "start", "task-with-workspace", + "--showlog", + "--workspace=name=read-allowed,volumeClaimTemplateFile="+e2e.ResourcePath("pvc.yaml")) + + vars := make(map[string]interface{}) + taskRunGeneratedName := e2e.GetTaskRunListWithName(c, "task-with-workspace").Items[0].Name + vars["Taskrun"] = taskRunGeneratedName + expected := e2e.ProcessString(`(TaskRun started: {{.Taskrun}} +Waiting for logs to be available... +.*)`, vars) + res.Assert(t, icmd.Expected{ + ExitCode: 0, + Err: icmd.None, + }) + assert.Assert(t, is.Regexp(expected, res.Stdout())) + + if err := e2e.WaitForTaskRunState(c, taskRunGeneratedName, e2e.TaskRunSucceed(taskRunGeneratedName), "TaskRunSucceeded"); err != nil { + t.Errorf("Error waiting for TaskRun to Succeed: %s", err) + } + }) } diff --git a/test/resources/pipeline-with-workspace.yaml b/test/resources/pipeline-with-workspace.yaml new file mode 100644 index 000000000..c135f61dd --- /dev/null +++ b/test/resources/pipeline-with-workspace.yaml @@ -0,0 +1,33 @@ +# Copyright 2020 The Tekton Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: tekton.dev/v1alpha1 +kind: Pipeline +metadata: + name: pipeline-with-workspace +spec: + tasks: + - name: reader + taskSpec: + steps: + - name: list-files + image: ubuntu + script: ls $(workspaces.myws.path) + workspaces: + - name: myws + workspaces: + - name: myws + workspace: ws + workspaces: + - name: ws \ No newline at end of file diff --git a/test/resources/pvc.yaml b/test/resources/pvc.yaml new file mode 100644 index 000000000..ed9a974f9 --- /dev/null +++ b/test/resources/pvc.yaml @@ -0,0 +1,25 @@ +# Copyright 2020 The Tekton Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Used to create PersistentVolumeClaim from VolumeClaimTemplate + +metadata: + name: pvc +spec: + accessModes: + - ReadWriteOnce + volumeMode: Filesystem + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/test/resources/task-with-workspace.yaml b/test/resources/task-with-workspace.yaml new file mode 100644 index 000000000..1beffcb95 --- /dev/null +++ b/test/resources/task-with-workspace.yaml @@ -0,0 +1,25 @@ +# Copyright 2020 The Tekton Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: tekton.dev/v1alpha1 +kind: Task +metadata: + name: task-with-workspace +spec: + steps: + - name: list-files + image: ubuntu + script: ls $(workspaces.write-allowed.path) + workspaces: + - name: read-allowed \ No newline at end of file