Skip to content

Commit

Permalink
Allow Resources to be Optional in Pipeline
Browse files Browse the repository at this point in the history
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 tektoncd#1710
  • Loading branch information
pritidesai committed Dec 19, 2019
1 parent 2add7d2 commit d7ec631
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 16 deletions.
130 changes: 130 additions & 0 deletions examples/pipelineruns/demo-optional-resources.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
apiVersion: tekton.dev/v1alpha1
kind: Condition
metadata:
name: check-git-pipeline-resource
spec:
params:
- name: "path"
resources:
- name: git-repo
type: git
optional: true
check:
image: alpine
command: ["/bin/sh"]
args: ['-c', 'test -f $(resources.git-repo.path)/$(params.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
params:
- name: "path"
default: "README.md"
tasks:
- name: build-an-image
taskRef:
name: build-an-image
conditions:
- conditionRef: "check-git-pipeline-resource"
params:
- name: "path"
value: "$(params.path)"
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
---
4 changes: 4 additions & 0 deletions pkg/apis/pipeline/v1alpha1/pipeline_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package resources

import (
"fmt"

"github.com/tektoncd/pipeline/pkg/apis/pipeline"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1"
corev1 "k8s.io/api/core/v1"
Expand Down
64 changes: 49 additions & 15 deletions pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -426,11 +440,15 @@ 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)
if r, ok := providedResources[declared.Resource]; ok {
conditionResources[declared.Name] = r
} else {
for _, resource := range c.Spec.Resources {
if declared.Name == resource.Name && !resource.Optional {
return nil, fmt.Errorf("resources %s missing for condition %s in pipeline task %s", declared.Resource, cName, pt.Name)
}
}
}
conditionResources[declared.Name] = r
}

rcc := ResolvedConditionCheck{
Expand Down Expand Up @@ -458,18 +476,34 @@ 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)
if resource, ok := providedResources[taskInput.Resource]; ok {
rtr.Inputs[taskInput.Name] = resource
} else {
if ts.Inputs != nil {
for _, r := range ts.Inputs.Resources {
if r.Name == taskInput.Name && !r.Optional {
return nil, fmt.Errorf("pipelineTask tried to use input resource %s not present in declared resources", taskInput.Resource)
}
}
} else {
return nil, fmt.Errorf("pipelineTask tried to use input resource %s not present in declared resources", taskInput.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)
if resource, ok := providedResources[taskOutput.Resource]; ok {
rtr.Outputs[taskOutput.Name] = resource
} else {
if ts.Outputs != nil {
for _, r := range ts.Outputs.Resources {
if r.Name == taskOutput.Name && !r.Optional {
return nil, fmt.Errorf("pipelineTask tried to use output resource %s not present in declared resources", taskOutput.Resource)
}
}
} else {
return nil, fmt.Errorf("pipelineTask tried to use output resource %s not present in declared resources", taskOutput.Resource)
}
}
rtr.Outputs[taskOutput.Name] = resource
}
}
return &rtr, nil
Expand Down

0 comments on commit d7ec631

Please sign in to comment.