Skip to content

Commit

Permalink
Task auto-conversion from v1alpha1 to v1alpha2 🎋
Browse files Browse the repository at this point in the history
This adds auto-conversion methods and tests for Task types in v1alpha1
and v1alpha2. In order to reduce the number of duplicated fields, we
are using v1alpha2.TaskSpec embedded in v1alpha1.TaskSpec so that both
types are compatible.

Signed-off-by: Vincent Demeester <[email protected]>
  • Loading branch information
vdemeester committed Feb 5, 2020
1 parent d782021 commit f71d174
Show file tree
Hide file tree
Showing 26 changed files with 1,355 additions and 563 deletions.
51 changes: 51 additions & 0 deletions pkg/apis/pipeline/v1alpha1/conversion_error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
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.
*/

package v1alpha1

import (
"fmt"

"knative.dev/pkg/apis"
)

const (
// ConditionTypeConvertible is a Warning condition that is set on
// resources when they cannot be converted to warn of a forthcoming
// breakage.
ConditionTypeConvertible apis.ConditionType = "Convertible"
)

// CannotConvertError is returned when a field cannot be converted.
type CannotConvertError struct {
Message string
Field string
}

var _ error = (*CannotConvertError)(nil)

// Error implements error
func (cce *CannotConvertError) Error() string {
return cce.Message
}

// ConvertErrorf creates a CannotConvertError from the field name and format string.
func ConvertErrorf(field, msg string, args ...interface{}) error {
return &CannotConvertError{
Message: fmt.Sprintf(msg, args...),
Field: field,
}
}
49 changes: 49 additions & 0 deletions pkg/apis/pipeline/v1alpha1/pipeline_conversion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
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.
*/

package v1alpha1

import (
"context"
"fmt"

"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha2"
"knative.dev/pkg/apis"
)

var _ apis.Convertible = (*Pipeline)(nil)

// ConvertUp implements api.Convertible
func (source *Pipeline) ConvertUp(ctx context.Context, obj apis.Convertible) error {
switch sink := obj.(type) {
case *v1alpha2.Pipeline:
sink.ObjectMeta = source.ObjectMeta
return nil // source.Spec.ConvertUp(ctx, &sink.Spec)
default:
return fmt.Errorf("unknown version, got: %T", sink)
}
}

// ConvertDown implements api.Convertible
func (sink *Pipeline) ConvertDown(ctx context.Context, obj apis.Convertible) error {
switch source := obj.(type) {
case *v1alpha2.Pipeline:
sink.ObjectMeta = source.ObjectMeta
return nil // sink.Spec.ConvertDown(ctx, &source.Spec)
default:
return fmt.Errorf("unknown version, got: %T", sink)
}
}
21 changes: 13 additions & 8 deletions pkg/apis/pipeline/v1alpha1/pipeline_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"testing"

"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha2"
tb "github.com/tektoncd/pipeline/test/builder"
corev1 "k8s.io/api/core/v1"
)
Expand Down Expand Up @@ -84,10 +85,12 @@ func TestPipeline_Validate(t *testing.T) {
name: "pipeline spec with taskref and taskspec",
p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
tb.PipelineTask("foo", "foo-task", tb.PipelineTaskSpec(&v1alpha1.TaskSpec{
Steps: []v1alpha1.Step{{Container: corev1.Container{
Name: "foo",
Image: "bar",
}}},
TaskSpec: v1alpha2.TaskSpec{
Steps: []v1alpha1.Step{{Container: corev1.Container{
Name: "foo",
Image: "bar",
}}},
},
},
)))),
failureExpected: true,
Expand All @@ -101,10 +104,12 @@ func TestPipeline_Validate(t *testing.T) {
name: "pipeline spec valid taskspec",
p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
tb.PipelineTask("", "", tb.PipelineTaskSpec(&v1alpha1.TaskSpec{
Steps: []v1alpha1.Step{{Container: corev1.Container{
Name: "foo",
Image: "bar",
}}},
TaskSpec: v1alpha2.TaskSpec{
Steps: []v1alpha1.Step{{Container: corev1.Container{
Name: "foo",
Image: "bar",
}}},
},
},
)))),
failureExpected: false,
Expand Down
29 changes: 15 additions & 14 deletions pkg/apis/pipeline/v1alpha1/resource_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

"github.com/google/go-cmp/cmp"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha2"
corev1 "k8s.io/api/core/v1"
)

Expand Down Expand Up @@ -69,19 +70,19 @@ func TestApplyTaskModifier(t *testing.T) {
ts: v1alpha1.TaskSpec{},
}, {
name: "identical volume already added",
ts: v1alpha1.TaskSpec{
ts: v1alpha1.TaskSpec{TaskSpec: v1alpha2.TaskSpec{
// Trying to add the same Volume that has already been added shouldn't be an error
// and it should not be added twice
Volumes: []corev1.Volume{volume},
},
}},
}}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
if err := v1alpha1.ApplyTaskModifier(&tc.ts, &TestTaskModifier{}); err != nil {
t.Fatalf("Did not expect error modifying TaskSpec but got %v", err)
}

expectedTaskSpec := v1alpha1.TaskSpec{
expectedTaskSpec := v1alpha1.TaskSpec{TaskSpec: v1alpha2.TaskSpec{
Steps: []v1alpha1.Step{{
Container: prependStep,
}, {
Expand All @@ -90,7 +91,7 @@ func TestApplyTaskModifier(t *testing.T) {
Volumes: []corev1.Volume{
volume,
},
}
}}

if d := cmp.Diff(expectedTaskSpec, tc.ts); d != "" {
t.Errorf("TaskSpec was not modified as expected (-want, +got): %s", d)
Expand All @@ -105,34 +106,34 @@ func TestApplyTaskModifier_AlreadyAdded(t *testing.T) {
ts v1alpha1.TaskSpec
}{{
name: "prepend already added",
ts: v1alpha1.TaskSpec{
ts: v1alpha1.TaskSpec{TaskSpec: v1alpha2.TaskSpec{
Steps: []v1alpha1.Step{{Container: prependStep}},
},
}},
}, {
name: "append already added",
ts: v1alpha1.TaskSpec{
ts: v1alpha1.TaskSpec{TaskSpec: v1alpha2.TaskSpec{
Steps: []v1alpha1.Step{{Container: appendStep}},
},
}},
}, {
name: "both steps already added",
ts: v1alpha1.TaskSpec{
ts: v1alpha1.TaskSpec{TaskSpec: v1alpha2.TaskSpec{
Steps: []v1alpha1.Step{{Container: prependStep}, {Container: appendStep}},
},
}},
}, {
name: "both steps already added reverse order",
ts: v1alpha1.TaskSpec{
ts: v1alpha1.TaskSpec{TaskSpec: v1alpha2.TaskSpec{
Steps: []v1alpha1.Step{{Container: appendStep}, {Container: prependStep}},
},
}},
}, {
name: "volume with same name but diff content already added",
ts: v1alpha1.TaskSpec{
ts: v1alpha1.TaskSpec{TaskSpec: v1alpha2.TaskSpec{
Volumes: []corev1.Volume{{
Name: "magic-volume",
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
}},
},
}},
}}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
Expand Down
123 changes: 123 additions & 0 deletions pkg/apis/pipeline/v1alpha1/task_conversion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
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.
*/

package v1alpha1

import (
"context"
"fmt"

"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha2"
"knative.dev/pkg/apis"
)

var _ apis.Convertible = (*Task)(nil)

// ConvertUp implements api.Convertible
func (source *Task) ConvertUp(ctx context.Context, obj apis.Convertible) error {
switch sink := obj.(type) {
case *v1alpha2.Task:
sink.ObjectMeta = source.ObjectMeta
return source.Spec.ConvertUp(ctx, &sink.Spec)
default:
return fmt.Errorf("unknown version, got: %T", sink)
}
}

func (source *TaskSpec) ConvertUp(ctx context.Context, sink *v1alpha2.TaskSpec) error {
sink.Steps = source.Steps
sink.Volumes = source.Volumes
sink.StepTemplate = source.StepTemplate
sink.Sidecars = source.Sidecars
sink.Workspaces = source.Workspaces
sink.Results = source.Results
sink.Resources = source.Resources
sink.Params = source.Params
if source.Inputs != nil {
if len(source.Inputs.Params) > 0 && len(source.Params) > 0 {
// This shouldn't happen as it shouldn't pass validation
return apis.ErrMultipleOneOf("inputs.params", "params")
}
if len(source.Inputs.Params) > 0 {
sink.Params = make([]v1alpha2.ParamSpec, len(source.Inputs.Params))
for i, param := range source.Inputs.Params {
sink.Params[i] = *param.DeepCopy()
}
}
if len(source.Inputs.Resources) > 0 {
if sink.Resources == nil {
sink.Resources = &v1alpha2.TaskResources{}
}
if len(source.Inputs.Resources) > 0 && source.Resources != nil && len(source.Resources.Inputs) > 0 {
// This shouldn't happen as it shouldn't pass validation but just in case
return apis.ErrMultipleOneOf("inputs.resources", "resources.inputs")
}
sink.Resources.Inputs = make([]v1alpha2.TaskResource, len(source.Inputs.Resources))
for i, resource := range source.Inputs.Resources {
sink.Resources.Inputs[i] = v1alpha2.TaskResource{ResourceDeclaration: v1alpha2.ResourceDeclaration{
Name: resource.Name,
Type: resource.Type,
Description: resource.Description,
TargetPath: resource.TargetPath,
Optional: resource.Optional,
}}
}
}
}
if source.Outputs != nil && len(source.Outputs.Resources) > 0 {
if sink.Resources == nil {
sink.Resources = &v1alpha2.TaskResources{}
}
if len(source.Outputs.Resources) > 0 && source.Resources != nil && len(source.Resources.Outputs) > 0 {
// This shouldn't happen as it shouldn't pass validation but just in case
return apis.ErrMultipleOneOf("outputs.resources", "resources.outputs")
}
sink.Resources.Outputs = make([]v1alpha2.TaskResource, len(source.Outputs.Resources))
for i, resource := range source.Outputs.Resources {
sink.Resources.Outputs[i] = v1alpha2.TaskResource{ResourceDeclaration: v1alpha2.ResourceDeclaration{
Name: resource.Name,
Type: resource.Type,
Description: resource.Description,
TargetPath: resource.TargetPath,
Optional: resource.Optional,
}}
}
}
return nil
}

// ConvertDown implements api.Convertible
func (sink *Task) ConvertDown(ctx context.Context, obj apis.Convertible) error {
switch source := obj.(type) {
case *v1alpha2.Task:
sink.ObjectMeta = source.ObjectMeta
return sink.Spec.ConvertDown(ctx, &source.Spec)
default:
return fmt.Errorf("unknown version, got: %T", sink)
}
}

func (sink *TaskSpec) ConvertDown(ctx context.Context, source *v1alpha2.TaskSpec) error {
sink.Steps = source.Steps
sink.Volumes = source.Volumes
sink.StepTemplate = source.StepTemplate
sink.Sidecars = source.Sidecars
sink.Workspaces = source.Workspaces
sink.Results = source.Results
sink.Params = source.Params
sink.Resources = source.Resources
return nil
}
Loading

0 comments on commit f71d174

Please sign in to comment.