Skip to content

Commit

Permalink
Add APIs for Conditionals
Browse files Browse the repository at this point in the history
This commit adds the basic APIs for conditionals in Tekton:
1. Condition CRD defines a condition how a condition is evaluated i.e.
   the container spec and any input parameters.
2. The `Conditions` field in `PipelineTask` references `Condition`
   resources that have to pass before the task is executed.
3. The `ConditionChecks` field in `PipelineRun.Status.TaskRuns` surfaces
   the status of conditions that were evaluated for that particular task.
  • Loading branch information
dibyom committed Jul 16, 2019
1 parent 5bb9937 commit 5838a06
Show file tree
Hide file tree
Showing 20 changed files with 1,077 additions and 1 deletion.
2 changes: 1 addition & 1 deletion config/200-clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ rules:
resources: ["mutatingwebhookconfigurations"]
verbs: ["get", "list", "create", "update", "delete", "patch", "watch"]
- apiGroups: ["tekton.dev"]
resources: ["tasks", "clustertasks", "taskruns", "pipelines", "pipelineruns", "pipelineresources"]
resources: ["tasks", "clustertasks", "taskruns", "pipelines", "pipelineruns", "pipelineresources", "conditions"]
verbs: ["get", "list", "create", "update", "delete", "patch", "watch"]
- apiGroups: ["tekton.dev"]
resources: ["taskruns/finalizers", "pipelineruns/finalizers"]
Expand Down
31 changes: 31 additions & 0 deletions config/500-condition.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copyright 2018 The Knative 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
#
# https://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: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: conditions.tekton.dev
spec:
group: tekton.dev
names:
kind: Condition
plural: conditions
categories:
- all
- tekton-pipelines
scope: Namespaced
# Opt into the status subresource so metadata.generation
# starts to increment
subresources:
status: {}
version: v1alpha1
82 changes: 82 additions & 0 deletions pkg/apis/pipeline/v1alpha1/condition_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
*
* Copyright 2019 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.
* /
*/

package v1alpha1

import (
"github.com/knative/pkg/apis"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// Check that Task may be validated and defaulted.
var _ apis.Validatable = (*Condition)(nil)

// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// Condition declares a step that is used to gate the execution of a Task in a Pipeline.
// A condition execution (ConditionCheck) evaluates to either true or false
// +k8s:openapi-gen=true
type Condition struct {
metav1.TypeMeta `json:",inline"`
// +optional
metav1.ObjectMeta `json:"metadata"`

// Spec holds the desired state of the Condition from the client
// +optional
Spec ConditionSpec `json:"spec"`
}

// ConditionSpec defines the desired state of the Condition
type ConditionSpec struct {
// Check declares container whose exit code determines where a condition is true or false
Check corev1.Container `json:"check,omitempty"`

// Params is an optional set of parameters which must be supplied by the user when a Condition
// is evaluated
// +optional
Params []ParamSpec `json:"params,omitempty"`
}


// ConditionCheck represents a single evaluation of a Condition step.
type ConditionCheck TaskRun

// ConditionCheckStatus is the observed state of a ConditionCheck
type ConditionCheckStatus TaskRunStatus

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// ConditionList contains a list of Conditions
type ConditionList struct {
metav1.TypeMeta `json:",inline"`
// +optional
metav1.ListMeta `json:"metadata,omitempty"`
Items []Condition `json:"items"`
}

func NewConditionCheck(tr *TaskRun) *ConditionCheck {
if tr == nil {
return nil
}

cc := ConditionCheck(*tr)
return &cc
}
41 changes: 41 additions & 0 deletions pkg/apis/pipeline/v1alpha1/condition_validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
Copyright 2019 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.
*/

package v1alpha1

import (
"context"
"github.com/knative/pkg/apis"
"k8s.io/apimachinery/pkg/api/equality"
)

func (c Condition) Validate(ctx context.Context) *apis.FieldError {
if err := validateObjectMetadata(c.GetObjectMeta()); err != nil {
return err.ViaField("metadata")
}
return c.Spec.Validate(ctx).ViaField("Spec")
}

func (cs *ConditionSpec) Validate(ctx context.Context) *apis.FieldError {
if equality.Semantic.DeepEqual(cs, ConditionSpec{}) {
return apis.ErrMissingField(apis.CurrentField)
}

if cs.Check.Image == "" {
return apis.ErrMissingField("Check.Image")
}
return nil
}
93 changes: 93 additions & 0 deletions pkg/apis/pipeline/v1alpha1/condition_validation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
*
* Copyright 2019 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.
* /
*/

package v1alpha1

import (
"context"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/knative/pkg/apis"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"testing"
)
func TestCondition_Validate(t *testing.T) {
c := Condition{
ObjectMeta: metav1.ObjectMeta{
Name: "taskname",
},
Spec: ConditionSpec{
Check: corev1.Container{
Name: "foo",
Image: "bar",
},
Params: []ParamSpec{
{
Name: "expected",
},
},
},
}
if err := c.Validate(context.Background()); err != nil {
t.Errorf("Condition.Validate() unexpected error = %v", err)
}
}

func TestCondition_Invalidate(t *testing.T) {
tcs := []struct{
name string
cond Condition
expectedError apis.FieldError
}{{
name: "invalid meta",
cond: Condition{
ObjectMeta: metav1.ObjectMeta{
Name : "invalid.,name",
},
},
expectedError: apis.FieldError{
Message: "Invalid resource name: special character . must not be present",
Paths: []string{"metadata.name"},
},
},{
name: "no image",
cond:Condition{
ObjectMeta: metav1.ObjectMeta{Name: "cond"},
Spec: ConditionSpec{
Check: corev1.Container{},
},
},
expectedError: apis.FieldError{
Message: "missing field(s)",
Paths: []string{"Spec.Check.Image"},
},
}}

for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
err := tc.cond.Validate(context.Background())
if err == nil {
t.Fatalf("Expected an Error, got nothing for %v", tc)
}
if d := cmp.Diff(tc.expectedError, *err, cmpopts.IgnoreUnexported(apis.FieldError{})); d != "" {
t.Errorf("Condition.Validate() errors diff -want, +got: %v", d)
}
})
}
}
15 changes: 15 additions & 0 deletions pkg/apis/pipeline/v1alpha1/pipeline_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ type PipelineTask struct {
// TaskRef is a reference to a task definition.
TaskRef TaskRef `json:"taskRef"`

// Conditions is a list of conditions that need to be true for the task to run
// +optional
Conditions []PipelineTaskCondition `json:"conditions,omitempty"`

// Retries represents how many times this task should be retried in case of task failure: ConditionSucceeded set to False
// +optional
Retries int `json:"retries,omitempty"`
Expand All @@ -107,6 +111,17 @@ type PipelineTaskParam struct {
Value string `json:"value"`
}

// PipelineTaskCondition allows a PipelineTask to declare a Condition to be evaluated before
// the Task is run.
type PipelineTaskCondition struct {
// ConditionRef is the name of the Condition to use for the conditionCheck
ConditionRef string `json:"conditionRef"`

// Params declare parameters passed to this Condition
// +optional
Params []Param `json:"params,omitempty"`
}

// PipelineDeclaredResource is used by a Pipeline to declare the types of the
// PipelineResources that it will required to run and names which can be used to
// refer to these PipelineResources in PipelineTaskResourceBindings.
Expand Down
11 changes: 11 additions & 0 deletions pkg/apis/pipeline/v1alpha1/pipelinerun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,17 @@ type PipelineRunTaskRunStatus struct {
// Status is the TaskRunStatus for the corresponding TaskRun
// +optional
Status *TaskRunStatus `json:"status,omitempty"`
// ConditionChecks maps a conditionCheckName to the Status for the corresponding ConditionCheck
// +optional
ConditionChecks map[string]*PipelineRunConditionCheckStatus `json:"conditionChecks,omitempty"`
}

type PipelineRunConditionCheckStatus struct {
// ConditionName is the name of the Condition
ConditionName string `json:"conditionName,omitempty"`
// Status is the ConditionCheckStatus for the corresponding ConditionCheck
// +optional
Status *ConditionCheckStatus `json:"status,omitempty"`
}

var pipelineRunCondSet = apis.NewBatchConditionSet()
Expand Down
Loading

0 comments on commit 5838a06

Please sign in to comment.