blob: cf6e74503155800693704cc256158f30acf3c207 [file] [log] [blame]
Serge Bazanskibe538db2020-11-12 00:22:42 +01001package kubecfg
2
3import (
4 "fmt"
5 "io/ioutil"
6 "path/filepath"
7 "testing"
8
9 pb_proto "github.com/golang/protobuf/proto"
10 openapi_v2 "github.com/googleapis/gnostic/openapiv2"
11 apiequality "k8s.io/apimachinery/pkg/api/equality"
12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
14 "k8s.io/apimachinery/pkg/runtime/schema"
15 "k8s.io/apimachinery/pkg/util/diff"
16 "k8s.io/apimachinery/pkg/util/strategicpatch"
17 "k8s.io/kube-openapi/pkg/util/proto"
18 "k8s.io/kubectl/pkg/util/openapi"
19
20 "code.hackerspace.pl/hscloud/cluster/tools/kartongips/utils"
21)
22
23func TestStringListContains(t *testing.T) {
24 t.Parallel()
25 foobar := []string{"foo", "bar"}
26 if stringListContains([]string{}, "") {
27 t.Error("Empty list was not empty")
28 }
29 if !stringListContains(foobar, "foo") {
30 t.Error("Failed to find foo")
31 }
32 if stringListContains(foobar, "baz") {
33 t.Error("Should not contain baz")
34 }
35}
36
37func TestIsValidKindSchema(t *testing.T) {
Serge Bazanskie00fe3a2020-11-12 00:45:13 +010038 t.Skip("Skip test broken by kartongips fork.")
Serge Bazanskibe538db2020-11-12 00:22:42 +010039 t.Parallel()
40 schemaResources := readSchemaOrDie(filepath.FromSlash("../../testdata/schema.pb"))
41
42 cmgvk := schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ConfigMap"}
43 if !isValidKindSchema(schemaResources.LookupResource(cmgvk)) {
44 t.Errorf("%s should have a valid schema", cmgvk)
45 }
46
47 if isValidKindSchema(nil) {
48 t.Error("nil should not be a valid schema")
49 }
50
51 // This is what a schema-less CRD appears as in k8s >= 1.15
52 mapSchema := &proto.Map{
53 BaseSchema: proto.BaseSchema{
54 Extensions: map[string]interface{}{
55 "x-kubernetes-group-version-kind": []interface{}{
56 map[interface{}]interface{}{"group": "bitnami.com", "kind": "SealedSecret", "version": "v1alpha1"},
57 },
58 },
59 },
60 SubType: &proto.Arbitrary{},
61 }
62 if isValidKindSchema(mapSchema) {
63 t.Error("Trivial type:object schema should be invalid")
64 }
65}
66
67func TestEligibleForGc(t *testing.T) {
68 t.Parallel()
69 const myTag = "my-gctag"
70 boolTrue := true
71 o := &unstructured.Unstructured{
72 Object: map[string]interface{}{
73 "apiVersion": "tests/v1alpha1",
74 "kind": "Dummy",
75 },
76 }
77
78 if eligibleForGc(o, myTag) {
79 t.Errorf("%v should not be eligible (no tag)", o)
80 }
81
82 // [gctag-migration]: Remove annotation in phase2
83 utils.SetMetaDataAnnotation(o, AnnotationGcTag, "unknowntag")
84 utils.SetMetaDataLabel(o, LabelGcTag, "unknowntag")
85 if eligibleForGc(o, myTag) {
86 t.Errorf("%v should not be eligible (wrong tag)", o)
87 }
88
89 // [gctag-migration]: Remove annotation in phase2
90 utils.SetMetaDataAnnotation(o, AnnotationGcTag, myTag)
91 utils.SetMetaDataLabel(o, LabelGcTag, myTag)
92 if !eligibleForGc(o, myTag) {
93 t.Errorf("%v should be eligible", o)
94 }
95
96 // [gctag-migration]: Remove testcase in phase2
97 utils.SetMetaDataAnnotation(o, AnnotationGcTag, myTag)
98 utils.DeleteMetaDataLabel(o, LabelGcTag) // no label. ie: pre-migration
99 if !eligibleForGc(o, myTag) {
100 t.Errorf("%v should be eligible (gctag-migration phase1)", o)
101 }
102
103 utils.SetMetaDataAnnotation(o, AnnotationGcStrategy, GcStrategyIgnore)
104 if eligibleForGc(o, myTag) {
105 t.Errorf("%v should not be eligible (strategy=ignore)", o)
106 }
107
108 utils.SetMetaDataAnnotation(o, AnnotationGcStrategy, GcStrategyAuto)
109 if !eligibleForGc(o, myTag) {
110 t.Errorf("%v should be eligible (strategy=auto)", o)
111 }
112
113 // Unstructured.SetOwnerReferences is broken in apimachinery release-1.6
114 // See kubernetes/kubernetes#46817
115 setOwnerRef := func(u *unstructured.Unstructured, ref metav1.OwnerReference) {
116 // This is not a complete nor robust reimplementation
117 c := map[string]interface{}{
118 "kind": ref.Kind,
119 "name": ref.Name,
120 }
121 if ref.Controller != nil {
122 c["controller"] = *ref.Controller
123 }
124 u.Object["metadata"].(map[string]interface{})["ownerReferences"] = []interface{}{c}
125 }
126 setOwnerRef(o, metav1.OwnerReference{Kind: "foo", Name: "bar"})
127 if !eligibleForGc(o, myTag) {
128 t.Errorf("%v should be eligible (non-controller ownerref)", o)
129 }
130
131 setOwnerRef(o, metav1.OwnerReference{Kind: "foo", Name: "bar", Controller: &boolTrue})
132 if eligibleForGc(o, myTag) {
133 t.Errorf("%v should not be eligible (controller ownerref)", o)
134 }
135}
136
137func exampleConfigMap() *unstructured.Unstructured {
138 result := &unstructured.Unstructured{
139 Object: map[string]interface{}{
140 "apiVersion": "v1",
141 "kind": "ConfigMap",
142 "metadata": map[string]interface{}{
143 "name": "myname",
144 "namespace": "mynamespace",
145 "annotations": map[string]interface{}{
146 "myannotation": "somevalue",
147 },
148 },
149 "data": map[string]interface{}{
150 "foo": "bar",
151 },
152 },
153 }
154
155 return result
156}
157
158func addOrigAnnotation(obj *unstructured.Unstructured) {
159 data, err := utils.CompactEncodeObject(obj)
160 if err != nil {
161 panic(fmt.Sprintf("Failed to serialise object: %v", err))
162 }
163 utils.SetMetaDataAnnotation(obj, AnnotationOrigObject, data)
164}
165
166func newPatchMetaFromStructOrDie(dataStruct interface{}) strategicpatch.PatchMetaFromStruct {
167 t, err := strategicpatch.NewPatchMetaFromStruct(dataStruct)
168 if err != nil {
169 panic(fmt.Sprintf("NewPatchMetaFromStruct(%t) failed: %v", dataStruct, err))
170 }
171 return t
172}
173
174func readSchemaOrDie(path string) openapi.Resources {
175 var doc openapi_v2.Document
176 b, err := ioutil.ReadFile(path)
177 if err != nil {
178 panic(fmt.Sprintf("Unable to read %s: %v", path, err))
179 }
180 if err := pb_proto.Unmarshal(b, &doc); err != nil {
181 panic(fmt.Sprintf("Unable to unmarshal %s: %v", path, err))
182 }
183 schemaResources, err := openapi.NewOpenAPIData(&doc)
184 if err != nil {
185 panic(fmt.Sprintf("Unable to parse openapi doc: %v", err))
186 }
187 return schemaResources
188}
189
190func TestPatchNoop(t *testing.T) {
Serge Bazanskie00fe3a2020-11-12 00:45:13 +0100191 t.Skip("Skip test broken by kartongips fork.")
Serge Bazanskibe538db2020-11-12 00:22:42 +0100192 t.Parallel()
193 schemaResources := readSchemaOrDie(filepath.FromSlash("../../testdata/schema.pb"))
194
195 existing := exampleConfigMap()
196 new := existing.DeepCopy()
197 addOrigAnnotation(existing)
198
199 result, err := patch(existing, new, schemaResources.LookupResource(existing.GroupVersionKind()))
200 if err != nil {
201 t.Errorf("patch() returned error: %v", err)
202 }
203
204 t.Logf("existing: %#v", existing)
205 t.Logf("result: %#v", result)
206 if !apiequality.Semantic.DeepEqual(existing, result) {
207 t.Error("Objects differed: ", diff.ObjectDiff(existing, result))
208 }
209}
210
211func TestPatchNoopNoAnnotation(t *testing.T) {
Serge Bazanskie00fe3a2020-11-12 00:45:13 +0100212 t.Skip("Skip test broken by kartongips fork.")
Serge Bazanskibe538db2020-11-12 00:22:42 +0100213 t.Parallel()
214 schemaResources := readSchemaOrDie(filepath.FromSlash("../../testdata/schema.pb"))
215
216 existing := exampleConfigMap()
217 new := existing.DeepCopy()
218 // Note: no addOrigAnnotation(existing)
219
220 result, err := patch(existing, new, schemaResources.LookupResource(existing.GroupVersionKind()))
221 if err != nil {
222 t.Errorf("patch() returned error: %v", err)
223 }
224
225 // result should == existing, except for annotation
226
227 if result.GetAnnotations()[AnnotationOrigObject] == "" {
228 t.Errorf("result lacks last-applied annotation")
229 }
230
231 utils.DeleteMetaDataAnnotation(result, AnnotationOrigObject)
232 if !apiequality.Semantic.DeepEqual(existing, result) {
233 t.Error("Objects differed: ", diff.ObjectDiff(existing, result))
234 }
235}
236
237func TestPatchNoConflict(t *testing.T) {
Serge Bazanskie00fe3a2020-11-12 00:45:13 +0100238 t.Skip("Skip test broken by kartongips fork.")
Serge Bazanskibe538db2020-11-12 00:22:42 +0100239 t.Parallel()
240 schemaResources := readSchemaOrDie(filepath.FromSlash("../../testdata/schema.pb"))
241
242 existing := exampleConfigMap()
243 utils.SetMetaDataAnnotation(existing, "someanno", "origvalue")
244 addOrigAnnotation(existing)
245 utils.SetMetaDataAnnotation(existing, "otheranno", "existingvalue")
246 new := exampleConfigMap()
247 utils.SetMetaDataAnnotation(new, "someanno", "newvalue")
248
249 result, err := patch(existing, new, schemaResources.LookupResource(existing.GroupVersionKind()))
250 if err != nil {
251 t.Errorf("patch() returned error: %v", err)
252 }
253
254 t.Logf("existing: %#v", existing)
255 t.Logf("result: %#v", result)
256 someanno := result.GetAnnotations()["someanno"]
257 if someanno != "newvalue" {
258 t.Errorf("someanno was %q", someanno)
259 }
260
261 otheranno := result.GetAnnotations()["otheranno"]
262 if otheranno != "existingvalue" {
263 t.Errorf("otheranno was %q", otheranno)
264 }
265}
266
267func TestPatchConflict(t *testing.T) {
Serge Bazanskie00fe3a2020-11-12 00:45:13 +0100268 t.Skip("Skip test broken by kartongips fork.")
Serge Bazanskibe538db2020-11-12 00:22:42 +0100269 t.Parallel()
270 schemaResources := readSchemaOrDie(filepath.FromSlash("../../testdata/schema.pb"))
271
272 existing := exampleConfigMap()
273 utils.SetMetaDataAnnotation(existing, "someanno", "origvalue")
274 addOrigAnnotation(existing)
275 utils.SetMetaDataAnnotation(existing, "someanno", "existingvalue")
276 new := exampleConfigMap()
277 utils.SetMetaDataAnnotation(new, "someanno", "newvalue")
278
279 result, err := patch(existing, new, schemaResources.LookupResource(existing.GroupVersionKind()))
280 if err != nil {
281 t.Errorf("patch() returned error: %v", err)
282 }
283
284 // `new` should win conflicts
285
286 t.Logf("existing: %#v", existing)
287 t.Logf("result: %#v", result)
288 value := result.GetAnnotations()["someanno"]
289 if value != "newvalue" {
290 t.Errorf("annotation was %q", value)
291 }
292}