Skip to content

Commit 8a7b0cf

Browse files
lbernicktekton-robot
authored andcommitted
Add V1 Task Golang structs
This commit adds structs and validation for v1 Task, including results, params, and workspaces. Because we do not yet serve a v1 version of the Task CRD, this change should have no impact. This is copied from v1beta1 with the following exceptions: - omitted PipelineResources - removed deprecated Step and StepTemplate fields - did not copy over Task interface (TaskObject) as ClusterTask is not (yet?) moving to V1
1 parent a0c7d31 commit 8a7b0cf

20 files changed

+6972
-62
lines changed

pkg/apis/pipeline/v1/container_types.go

Lines changed: 526 additions & 0 deletions
Large diffs are not rendered by default.

pkg/apis/pipeline/v1/merge.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
Copyright 2022 The Tekton Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1
18+
19+
import (
20+
"encoding/json"
21+
22+
corev1 "k8s.io/api/core/v1"
23+
"k8s.io/apimachinery/pkg/util/strategicpatch"
24+
)
25+
26+
// mergeData is used to store the intermediate data needed to merge an object
27+
// with a template. It's provided to avoid repeatedly re-serializing the template.
28+
// +k8s:openapi-gen=false
29+
type mergeData struct {
30+
emptyJSON []byte
31+
templateJSON []byte
32+
patchSchema strategicpatch.PatchMetaFromStruct
33+
}
34+
35+
// MergeStepsWithStepTemplate takes a possibly nil container template and a
36+
// list of steps, merging each of the steps with the container template, if
37+
// it's not nil, and returning the resulting list.
38+
func MergeStepsWithStepTemplate(template *StepTemplate, steps []Step) ([]Step, error) {
39+
if template == nil {
40+
return steps, nil
41+
}
42+
43+
md, err := getMergeData(template.ToK8sContainer(), &corev1.Container{})
44+
if err != nil {
45+
return nil, err
46+
}
47+
48+
for i, s := range steps {
49+
merged := corev1.Container{}
50+
err := mergeObjWithTemplateBytes(md, s.ToK8sContainer(), &merged)
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
// If the container's args is nil, reset it to empty instead
56+
if merged.Args == nil && s.Args != nil {
57+
merged.Args = []string{}
58+
}
59+
60+
// Pass through original step Script, for later conversion.
61+
newStep := Step{Script: s.Script, OnError: s.OnError, Timeout: s.Timeout}
62+
newStep.SetContainerFields(merged)
63+
steps[i] = newStep
64+
}
65+
return steps, nil
66+
}
67+
68+
// getMergeData serializes the template and empty object to get the intermediate results necessary for
69+
// merging an object of the same type with this template.
70+
// This function is provided to avoid repeatedly serializing an identical template.
71+
func getMergeData(template, empty interface{}) (*mergeData, error) {
72+
// We need JSON bytes to generate a patch to merge the object
73+
// onto the template, so marshal the template.
74+
templateJSON, err := json.Marshal(template)
75+
if err != nil {
76+
return nil, err
77+
}
78+
// We need to do a three-way merge to actually merge the template and
79+
// object, so we need an empty object as the "original"
80+
emptyJSON, err := json.Marshal(empty)
81+
if err != nil {
82+
return nil, err
83+
}
84+
// Get the patch meta, which is needed for generating and applying the merge patch.
85+
patchSchema, err := strategicpatch.NewPatchMetaFromStruct(template)
86+
if err != nil {
87+
return nil, err
88+
}
89+
return &mergeData{templateJSON: templateJSON, emptyJSON: emptyJSON, patchSchema: patchSchema}, nil
90+
}
91+
92+
// mergeObjWithTemplateBytes merges obj with md's template JSON and updates out to reflect the merged result.
93+
// out is a pointer to the zero value of obj's type.
94+
// This function is provided to avoid repeatedly serializing an identical template.
95+
func mergeObjWithTemplateBytes(md *mergeData, obj, out interface{}) error {
96+
// Marshal the object to JSON
97+
objAsJSON, err := json.Marshal(obj)
98+
if err != nil {
99+
return err
100+
}
101+
// Create a merge patch, with the empty JSON as the original, the object JSON as the modified, and the template
102+
// JSON as the current - this lets us do a deep merge of the template and object, with awareness of
103+
// the "patchMerge" tags.
104+
patch, err := strategicpatch.CreateThreeWayMergePatch(md.emptyJSON, objAsJSON, md.templateJSON, md.patchSchema, true)
105+
if err != nil {
106+
return err
107+
}
108+
109+
// Actually apply the merge patch to the template JSON.
110+
mergedAsJSON, err := strategicpatch.StrategicMergePatchUsingLookupPatchMeta(md.templateJSON, patch, md.patchSchema)
111+
if err != nil {
112+
return err
113+
}
114+
// Unmarshal the merged JSON to a pointer, and return it.
115+
return json.Unmarshal(mergedAsJSON, out)
116+
}

pkg/apis/pipeline/v1/merge_test.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
Copyright 2022 The Tekton Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1_test
18+
19+
import (
20+
"testing"
21+
22+
"github.com/google/go-cmp/cmp"
23+
v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
24+
"github.com/tektoncd/pipeline/test/diff"
25+
corev1 "k8s.io/api/core/v1"
26+
"k8s.io/apimachinery/pkg/api/resource"
27+
)
28+
29+
func TestMergeStepsWithStepTemplate(t *testing.T) {
30+
resourceQuantityCmp := cmp.Comparer(func(x, y resource.Quantity) bool {
31+
return x.Cmp(y) == 0
32+
})
33+
34+
for _, tc := range []struct {
35+
name string
36+
template *v1.StepTemplate
37+
steps []v1.Step
38+
expected []v1.Step
39+
}{{
40+
name: "nil-template",
41+
template: nil,
42+
steps: []v1.Step{{
43+
Image: "some-image",
44+
OnError: "foo",
45+
}},
46+
expected: []v1.Step{{
47+
Image: "some-image",
48+
OnError: "foo",
49+
}},
50+
}, {
51+
name: "not-overlapping",
52+
template: &v1.StepTemplate{
53+
Command: []string{"/somecmd"},
54+
},
55+
steps: []v1.Step{{
56+
Image: "some-image",
57+
OnError: "foo",
58+
}},
59+
expected: []v1.Step{{
60+
Command: []string{"/somecmd"}, Image: "some-image",
61+
OnError: "foo",
62+
}},
63+
}, {
64+
name: "overwriting-one-field",
65+
template: &v1.StepTemplate{
66+
Image: "some-image",
67+
Command: []string{"/somecmd"},
68+
},
69+
steps: []v1.Step{{
70+
Image: "some-other-image",
71+
}},
72+
expected: []v1.Step{{
73+
Command: []string{"/somecmd"},
74+
Image: "some-other-image",
75+
}},
76+
}, {
77+
name: "merge-and-overwrite-slice",
78+
template: &v1.StepTemplate{
79+
Env: []corev1.EnvVar{{
80+
Name: "KEEP_THIS",
81+
Value: "A_VALUE",
82+
}, {
83+
Name: "SOME_KEY",
84+
Value: "ORIGINAL_VALUE",
85+
}},
86+
},
87+
steps: []v1.Step{{
88+
Env: []corev1.EnvVar{{
89+
Name: "NEW_KEY",
90+
Value: "A_VALUE",
91+
}, {
92+
Name: "SOME_KEY",
93+
Value: "NEW_VALUE",
94+
}},
95+
}},
96+
expected: []v1.Step{{
97+
Env: []corev1.EnvVar{{
98+
Name: "NEW_KEY",
99+
Value: "A_VALUE",
100+
}, {
101+
Name: "KEEP_THIS",
102+
Value: "A_VALUE",
103+
}, {
104+
Name: "SOME_KEY",
105+
Value: "NEW_VALUE",
106+
}},
107+
}},
108+
}} {
109+
t.Run(tc.name, func(t *testing.T) {
110+
result, err := v1.MergeStepsWithStepTemplate(tc.template, tc.steps)
111+
if err != nil {
112+
t.Errorf("expected no error. Got error %v", err)
113+
}
114+
115+
if d := cmp.Diff(tc.expected, result, resourceQuantityCmp); d != "" {
116+
t.Errorf("merged steps don't match, diff: %s", diff.PrintWantGot(d))
117+
}
118+
})
119+
}
120+
}

0 commit comments

Comments
 (0)