Skip to content

Commit eaaec1e

Browse files
authored
Copy toolregistry, signalhandler, logpersister, diff and pipedapi to under the sdk (#5874)
Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]>
1 parent b9bd46e commit eaaec1e

File tree

18 files changed

+2608
-0
lines changed

18 files changed

+2608
-0
lines changed

pkg/plugin/sdk/diff/diff.go

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

pkg/plugin/sdk/diff/diff_test.go

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
// Copyright 2024 The PipeCD Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package diff
16+
17+
import (
18+
"os"
19+
"reflect"
20+
"strings"
21+
"testing"
22+
23+
"github.com/stretchr/testify/assert"
24+
"github.com/stretchr/testify/require"
25+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
26+
"sigs.k8s.io/yaml"
27+
)
28+
29+
func TestDiff(t *testing.T) {
30+
testcases := []struct {
31+
name string
32+
yamlFile string
33+
resourceKey string
34+
options []Option
35+
diffNum int
36+
diffString string
37+
}{
38+
{
39+
name: "no diff",
40+
yamlFile: "testdata/no_diff.yaml",
41+
options: []Option{
42+
WithEquateEmpty(),
43+
WithIgnoreAddingMapKeys(),
44+
WithCompareNumberAndNumericString(),
45+
WithCompareBooleanAndBooleanString(),
46+
},
47+
diffNum: 0,
48+
},
49+
{
50+
name: "no diff by ignoring all adding map keys",
51+
yamlFile: "testdata/ignore_adding_map_keys.yaml",
52+
options: []Option{
53+
WithIgnoreAddingMapKeys(),
54+
},
55+
diffNum: 0,
56+
},
57+
{
58+
name: "diff by ignoring specified field with correct key",
59+
yamlFile: "testdata/has_diff.yaml",
60+
resourceKey: "deployment-key",
61+
options: []Option{
62+
WithIgnoreConfig(
63+
map[string][]string{
64+
"deployment-key": {
65+
"spec.replicas",
66+
"spec.template.spec.containers.0.args.1",
67+
"spec.template.spec.strategy.rollingUpdate.maxSurge",
68+
"spec.template.spec.containers.3.livenessProbe.initialDelaySeconds",
69+
},
70+
},
71+
),
72+
},
73+
diffNum: 6,
74+
diffString: ` spec:
75+
template:
76+
metadata:
77+
labels:
78+
#spec.template.metadata.labels.app
79+
- app: simple
80+
+ app: simple2
81+
82+
#spec.template.metadata.labels.component
83+
- component: foo
84+
85+
spec:
86+
containers:
87+
-
88+
#spec.template.spec.containers.1.image
89+
- image: gcr.io/pipecd/helloworld:v2.0.0
90+
+ image: gcr.io/pipecd/helloworld:v2.1.0
91+
92+
-
93+
#spec.template.spec.containers.2.image
94+
- image:
95+
96+
#spec.template.spec.containers.3
97+
+ - image: new-image
98+
+ livenessProbe:
99+
+ exec:
100+
+ command:
101+
+ - cat
102+
+ - /tmp/healthy
103+
+ name: foo
104+
105+
#spec.template.spec.strategy
106+
+ strategy:
107+
+ rollingUpdate:
108+
+ maxUnavailable: 25%
109+
+ type: RollingUpdate
110+
111+
`,
112+
},
113+
{
114+
name: "diff by ignoring specified field with wrong resource key",
115+
yamlFile: "testdata/has_diff.yaml",
116+
resourceKey: "deployment-key",
117+
options: []Option{
118+
WithIgnoreConfig(
119+
map[string][]string{
120+
"crd-key": {
121+
"spec.replicas",
122+
"spec.template.spec.containers.0.args.1",
123+
"spec.template.spec.strategy.rollingUpdate.maxSurge",
124+
"spec.template.spec.containers.3.livenessProbe.initialDelaySeconds",
125+
},
126+
},
127+
),
128+
},
129+
diffNum: 8,
130+
diffString: ` spec:
131+
#spec.replicas
132+
- replicas: 2
133+
+ replicas: 3
134+
135+
template:
136+
metadata:
137+
labels:
138+
#spec.template.metadata.labels.app
139+
- app: simple
140+
+ app: simple2
141+
142+
#spec.template.metadata.labels.component
143+
- component: foo
144+
145+
spec:
146+
containers:
147+
- args:
148+
#spec.template.spec.containers.0.args.1
149+
- - hello
150+
151+
-
152+
#spec.template.spec.containers.1.image
153+
- image: gcr.io/pipecd/helloworld:v2.0.0
154+
+ image: gcr.io/pipecd/helloworld:v2.1.0
155+
156+
-
157+
#spec.template.spec.containers.2.image
158+
- image:
159+
160+
#spec.template.spec.containers.3
161+
+ - image: new-image
162+
+ livenessProbe:
163+
+ exec:
164+
+ command:
165+
+ - cat
166+
+ - /tmp/healthy
167+
+ initialDelaySeconds: 5
168+
+ name: foo
169+
170+
#spec.template.spec.strategy
171+
+ strategy:
172+
+ rollingUpdate:
173+
+ maxSurge: 25%
174+
+ maxUnavailable: 25%
175+
+ type: RollingUpdate
176+
177+
`,
178+
},
179+
{
180+
name: "has diff",
181+
yamlFile: "testdata/has_diff.yaml",
182+
diffNum: 8,
183+
diffString: ` spec:
184+
#spec.replicas
185+
- replicas: 2
186+
+ replicas: 3
187+
188+
template:
189+
metadata:
190+
labels:
191+
#spec.template.metadata.labels.app
192+
- app: simple
193+
+ app: simple2
194+
195+
#spec.template.metadata.labels.component
196+
- component: foo
197+
198+
spec:
199+
containers:
200+
- args:
201+
#spec.template.spec.containers.0.args.1
202+
- - hello
203+
204+
-
205+
#spec.template.spec.containers.1.image
206+
- image: gcr.io/pipecd/helloworld:v2.0.0
207+
+ image: gcr.io/pipecd/helloworld:v2.1.0
208+
209+
-
210+
#spec.template.spec.containers.2.image
211+
- image:
212+
213+
#spec.template.spec.containers.3
214+
+ - image: new-image
215+
+ livenessProbe:
216+
+ exec:
217+
+ command:
218+
+ - cat
219+
+ - /tmp/healthy
220+
+ initialDelaySeconds: 5
221+
+ name: foo
222+
223+
#spec.template.spec.strategy
224+
+ strategy:
225+
+ rollingUpdate:
226+
+ maxSurge: 25%
227+
+ maxUnavailable: 25%
228+
+ type: RollingUpdate
229+
230+
`,
231+
},
232+
}
233+
for _, tc := range testcases {
234+
t.Run(tc.name, func(t *testing.T) {
235+
objs, err := loadUnstructureds(tc.yamlFile)
236+
require.NoError(t, err)
237+
require.Equal(t, 2, len(objs))
238+
239+
result, err := DiffUnstructureds(objs[0], objs[1], tc.resourceKey, tc.options...)
240+
require.NoError(t, err)
241+
assert.Equal(t, tc.diffNum, result.NumNodes())
242+
243+
renderer := NewRenderer(WithLeftPadding(1))
244+
ds := renderer.Render(result.Nodes())
245+
246+
assert.Equal(t, tc.diffString, ds)
247+
})
248+
}
249+
}
250+
251+
func loadUnstructureds(path string) ([]unstructured.Unstructured, error) {
252+
data, err := os.ReadFile(path)
253+
if err != nil {
254+
return nil, err
255+
}
256+
257+
const separator = "\n---"
258+
parts := strings.Split(string(data), separator)
259+
out := make([]unstructured.Unstructured, 0, len(parts))
260+
261+
for _, part := range parts {
262+
// Ignore all the cases where no content between separator.
263+
part = strings.TrimSpace(part)
264+
if len(part) == 0 {
265+
continue
266+
}
267+
var obj unstructured.Unstructured
268+
if err := yaml.Unmarshal([]byte(part), &obj); err != nil {
269+
return nil, err
270+
}
271+
out = append(out, obj)
272+
}
273+
return out, nil
274+
}
275+
276+
func TestIsEmptyInterface(t *testing.T) {
277+
testcases := []struct {
278+
name string
279+
v interface{}
280+
expected bool
281+
}{
282+
{
283+
name: "nil",
284+
v: nil,
285+
expected: true,
286+
},
287+
{
288+
name: "nil map",
289+
v: map[string]int(nil),
290+
expected: true,
291+
},
292+
{
293+
name: "empty map",
294+
v: map[string]int{},
295+
expected: true,
296+
},
297+
{
298+
name: "nil slice",
299+
v: []int(nil),
300+
expected: true,
301+
},
302+
{
303+
name: "empty slice",
304+
v: []int{},
305+
expected: true,
306+
},
307+
{
308+
name: "number",
309+
v: 1,
310+
expected: false,
311+
},
312+
}
313+
for _, tc := range testcases {
314+
t.Run(tc.name, func(t *testing.T) {
315+
s := []interface{}{tc.v}
316+
v := reflect.ValueOf(s)
317+
318+
got := isEmptyInterface(v.Index(0))
319+
assert.Equal(t, tc.expected, got)
320+
})
321+
}
322+
}

0 commit comments

Comments
 (0)