From 955106abb03eb8aaa62709d2ec045ba74cd58622 Mon Sep 17 00:00:00 2001 From: Priti Desai Date: Wed, 18 Dec 2019 09:30:30 -0800 Subject: [PATCH] Allow Resources to be Optional in Pipeline Pipeline inputs and outputs are considered required, there is no way today to mark them optional. This change introduced a new field called optional as part of the PipelineTaskInputResource and PipelineTaskOutputResource by default a resource is required. To mark any resource optional, set optional to true: apiVersion: tekton.dev/v1alpha1 kind: Pipeline metadata: name: pipeline-build-image spec: inputs: resources: - name: workspace type: git optional: true tasks: - name: check-workspace Closes #1710 --- .../pipelineruns/demo-optional-resources.yaml | 122 ++++++++++++++++++ pkg/apis/pipeline/v1alpha1/pipeline_types.go | 4 + .../resources/pipelinerunresolution.go | 44 +++++-- 3 files changed, 158 insertions(+), 12 deletions(-) create mode 100644 examples/pipelineruns/demo-optional-resources.yaml diff --git a/examples/pipelineruns/demo-optional-resources.yaml b/examples/pipelineruns/demo-optional-resources.yaml new file mode 100644 index 00000000000..e0ff29288c7 --- /dev/null +++ b/examples/pipelineruns/demo-optional-resources.yaml @@ -0,0 +1,122 @@ +apiVersion: tekton.dev/v1alpha1 +kind: Condition +metadata: + name: check-git-pipeline-resource +spec: + resources: + - name: git-repo + type: git + optional: true + check: + image: alpine + command: ["/bin/sh"] + args: ['-c', 'test ! -f $(resources.git-repo.path)'] +--- + +apiVersion: tekton.dev/v1alpha1 +kind: Condition +metadata: + name: check-image-pipeline-resource +spec: + resources: + - name: built-image + type: image + optional: true + check: + image: alpine + command: ["/bin/sh"] + args: ['-c', 'test ! -z $(resources.built-image.url)'] +--- + +apiVersion: tekton.dev/v1alpha1 +kind: Task +metadata: + name: build-an-image +spec: + inputs: + resources: + - name: git-repo + type: git + optional: true + params: + - name: DOCKERFILE + description: The path to the dockerfile to build from GitHub Repo + default: "Dockerfile" + outputs: + resources: + - name: built-image + type: image + optional: true + steps: + - name: build-an-image + image: "gcr.io/kaniko-project/executor:latest" + command: + - /kaniko/executor + args: + - --dockerfile=$(inputs.params.DOCKERFILE) + - --destination=$(outputs.resources.built-image.url) +--- + +apiVersion: tekton.dev/v1alpha1 +kind: Pipeline +metadata: + name: demo-pipeline-to-build-an-image +spec: + resources: + - name: source-repo + type: git + optional: true + - name: web-image + type: image + optional: true + tasks: + - name: build-an-image + taskRef: + name: build-an-image + conditions: + - conditionRef: "check-git-pipeline-resource" + resources: + - name: git-repo + resource: source-repo + - conditionRef: "check-image-pipeline-resource" + resources: + - name: built-image + resource: web-image + resources: + inputs: + - name: git-repo + resource: source-repo + outputs: + - name: built-image + resource: web-image + +--- + +apiVersion: tekton.dev/v1alpha1 +kind: PipelineRun +metadata: + name: demo-pipeline-run-1 +spec: + pipelineRef: + name: demo-pipeline-to-build-an-image + serviceAccountName: 'default' +--- + +apiVersion: tekton.dev/v1alpha1 +kind: PipelineRun +metadata: + name: demo-pipeline-run-2 +spec: + pipelineRef: + name: demo-pipeline-to-build-an-image + serviceAccountName: 'default' + resources: + - name: source-repo + resourceSpec: + type: git + params: + - name: revision + value: master + - name: url + value: https://github.com/tektoncd/pipeline +--- diff --git a/pkg/apis/pipeline/v1alpha1/pipeline_types.go b/pkg/apis/pipeline/v1alpha1/pipeline_types.go index 0c37dc103ac..7b23e8826d9 100644 --- a/pkg/apis/pipeline/v1alpha1/pipeline_types.go +++ b/pkg/apis/pipeline/v1alpha1/pipeline_types.go @@ -174,6 +174,10 @@ type PipelineDeclaredResource struct { Name string `json:"name"` // Type is the type of the PipelineResource. Type PipelineResourceType `json:"type"` + // Optional declares the resource as optional. + // optional: true - the resource is considered optional + // optional: false - the resource is considered required (default/equivalent of not specifying it) + Optional bool `json:"optional,omitempty"` } // PipelineConditionResource allows a Pipeline to declare how its DeclaredPipelineResources diff --git a/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go b/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go index b5401c2d3bc..15adcd04ee1 100644 --- a/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go +++ b/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go @@ -187,15 +187,29 @@ func GetResourcesFromBindings(pr *v1alpha1.PipelineRun, getResource resources.Ge // ValidateResourceBindings validate that the PipelineResources declared in Pipeline p are bound in PipelineRun. func ValidateResourceBindings(p *v1alpha1.PipelineSpec, pr *v1alpha1.PipelineRun) error { required := make([]string, 0, len(p.Resources)) + optional := make([]string, 0, len(p.Resources)) for _, resource := range p.Resources { - required = append(required, resource.Name) + if resource.Optional { + // create a list of optional resources + optional = append(optional, resource.Name) + } else { + // create a list of required resources + required = append(required, resource.Name) + } } provided := make([]string, 0, len(pr.Spec.Resources)) for _, resource := range pr.Spec.Resources { provided = append(provided, resource.Name) } - if err := list.IsSame(required, provided); err != nil { - return fmt.Errorf("pipelineRun bound resources didn't match Pipeline: %w", err) + // verify that the list of required resources does exist in the provided resources + missing := list.DiffLeft(required, provided) + if len(missing) > 0 { + return fmt.Errorf("Pipeline's declared required resources are missing from the PipelineRun: %s", missing) + } + // verify that the list of provided resources does not have any extra resources (outside of required and optional resources combined) + extra := list.DiffLeft(provided, append(required, optional...)) + if len(extra) > 0 { + return fmt.Errorf("PipelineRun's declared resources didn't match usage in Pipeline: %s", extra) } return nil } @@ -427,10 +441,12 @@ func resolveConditionChecks(pt *v1alpha1.PipelineTask, taskRunStatus map[string] conditionResources := map[string]*v1alpha1.PipelineResource{} for _, declared := range ptc.Resources { r, ok := providedResources[declared.Resource] - if !ok { - return nil, fmt.Errorf("resources %s missing for condition %s in pipeline task %s", declared.Resource, cName, pt.Name) + for _, resource := range c.Spec.Resources { + if declared.Name == resource.Name && !resource.Optional && !ok { + return nil, fmt.Errorf("resources %s missing for condition %s in pipeline task %s", declared.Resource, cName, pt.Name) + } + conditionResources[declared.Name] = r } - conditionResources[declared.Name] = r } rcc := ResolvedConditionCheck{ @@ -459,17 +475,21 @@ func ResolvePipelineTaskResources(pt v1alpha1.PipelineTask, ts *v1alpha1.TaskSpe if pt.Resources != nil { for _, taskInput := range pt.Resources.Inputs { resource, ok := providedResources[taskInput.Resource] - if !ok { - return nil, fmt.Errorf("pipelineTask tried to use input resource %s not present in declared resources", taskInput.Resource) + for _, r := range ts.Inputs.Resources { + if r.Name == taskInput.Name && !r.Optional && !ok { + return nil, fmt.Errorf("pipelineTask tried to use input resource %s not present in declared resources", taskInput.Resource) + } + rtr.Inputs[taskInput.Name] = resource } - rtr.Inputs[taskInput.Name] = resource } for _, taskOutput := range pt.Resources.Outputs { resource, ok := providedResources[taskOutput.Resource] - if !ok { - return nil, fmt.Errorf("pipelineTask tried to use output resource %s not present in declared resources", taskOutput.Resource) + for _, r := range ts.Outputs.Resources { + if r.Name == taskOutput.Name && !r.Optional && !ok { + return nil, fmt.Errorf("pipelineTask tried to use output resource %s not present in declared resources", taskOutput.Resource) + } + rtr.Outputs[taskOutput.Name] = resource } - rtr.Outputs[taskOutput.Name] = resource } } return &rtr, nil