1
16
17 package managedfields
18
19 import (
20 "testing"
21
22 "github.com/google/go-cmp/cmp"
23 "sigs.k8s.io/structured-merge-diff/v4/typed"
24
25 "k8s.io/apimachinery/pkg/api/equality"
26 "k8s.io/apimachinery/pkg/api/meta"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
29 "k8s.io/apimachinery/pkg/runtime"
30 runtimeschema "k8s.io/apimachinery/pkg/runtime/schema"
31 )
32
33 func TestExtractInto(t *testing.T) {
34 one := int32(1)
35 parser, err := typed.NewParser(schemaYAML)
36 if err != nil {
37 t.Fatalf("Failed to parse schema: %v", err)
38 }
39 cases := []struct {
40 name string
41 obj runtime.Object
42 objType typed.ParseableType
43 managedFields []metav1.ManagedFieldsEntry
44 fieldManager string
45 expectedOut interface{}
46 subresource string
47 }{
48 {
49 name: "unstructured, no matching manager",
50 obj: &unstructured.Unstructured{Object: map[string]interface{}{"spec": map[string]interface{}{"replicas": 1}}},
51 objType: parser.Type("io.k8s.api.apps.v1.Deployment"),
52 managedFields: []metav1.ManagedFieldsEntry{
53 applyFieldsEntry("mgr999", `{ "f:spec": { "f:replicas": {}}}`, ""),
54 },
55 fieldManager: "mgr1",
56 expectedOut: map[string]interface{}{},
57 },
58 {
59 name: "unstructured, one manager",
60 obj: &unstructured.Unstructured{Object: map[string]interface{}{"spec": map[string]interface{}{"replicas": 1}}},
61 objType: parser.Type("io.k8s.api.apps.v1.Deployment"),
62 managedFields: []metav1.ManagedFieldsEntry{
63 applyFieldsEntry("mgr1", `{ "f:spec": { "f:replicas": {}}}`, ""),
64 },
65 fieldManager: "mgr1",
66 expectedOut: map[string]interface{}{"spec": map[string]interface{}{"replicas": 1}},
67 },
68 {
69 name: "unstructured, multiple manager",
70 obj: &unstructured.Unstructured{Object: map[string]interface{}{"spec": map[string]interface{}{"paused": true}}},
71 objType: parser.Type("io.k8s.api.apps.v1.Deployment"),
72 managedFields: []metav1.ManagedFieldsEntry{
73 applyFieldsEntry("mgr1", `{ "f:spec": { "f:replicas": {}}}`, ""),
74 applyFieldsEntry("mgr2", `{ "f:spec": { "f:paused": {}}}`, ""),
75 },
76 fieldManager: "mgr2",
77 expectedOut: map[string]interface{}{"spec": map[string]interface{}{"paused": true}},
78 },
79 {
80 name: "structured, no matching manager",
81 obj: &fakeDeployment{Spec: fakeDeploymentSpec{Replicas: &one}},
82 objType: parser.Type("io.k8s.api.apps.v1.Deployment"),
83 managedFields: []metav1.ManagedFieldsEntry{
84 applyFieldsEntry("mgr999", `{ "f:spec": { "f:replicas": {}}}`, ""),
85 },
86 fieldManager: "mgr1",
87 expectedOut: map[string]interface{}{},
88 },
89 {
90 name: "structured, one manager",
91 obj: &fakeDeployment{Spec: fakeDeploymentSpec{Replicas: &one}},
92 objType: parser.Type("io.k8s.api.apps.v1.Deployment"),
93 managedFields: []metav1.ManagedFieldsEntry{
94 applyFieldsEntry("mgr1", `{ "f:spec": { "f:replicas": {}}}`, ""),
95 },
96 fieldManager: "mgr1",
97 expectedOut: map[string]interface{}{"spec": map[string]interface{}{"replicas": int64(1)}},
98 },
99 {
100 name: "structured, multiple manager",
101 obj: &fakeDeployment{Spec: fakeDeploymentSpec{Replicas: &one, Paused: true}},
102 objType: parser.Type("io.k8s.api.apps.v1.Deployment"),
103 managedFields: []metav1.ManagedFieldsEntry{
104 applyFieldsEntry("mgr1", `{ "f:spec": { "f:replicas": {}}}`, ""),
105 applyFieldsEntry("mgr2", `{ "f:spec": { "f:paused": {}}}`, ""),
106 },
107 fieldManager: "mgr2",
108 expectedOut: map[string]interface{}{"spec": map[string]interface{}{"paused": true}},
109 },
110 {
111 name: "subresource",
112 obj: &fakeDeployment{Status: fakeDeploymentStatus{Replicas: &one}},
113 objType: parser.Type("io.k8s.api.apps.v1.Deployment"),
114 managedFields: []metav1.ManagedFieldsEntry{
115 applyFieldsEntry("mgr1", `{ "f:status": { "f:replicas": {}}}`, "status"),
116 },
117 fieldManager: "mgr1",
118 expectedOut: map[string]interface{}{"status": map[string]interface{}{"replicas": int64(1)}},
119 subresource: "status",
120 },
121 }
122 for _, tc := range cases {
123 t.Run(tc.name, func(t *testing.T) {
124 out := map[string]interface{}{}
125 accessor, err := meta.Accessor(tc.obj)
126 if err != nil {
127 t.Fatalf("Error accessing object: %v", err)
128 }
129 accessor.SetManagedFields(tc.managedFields)
130 err = ExtractInto(tc.obj, tc.objType, tc.fieldManager, &out, tc.subresource)
131 if err != nil {
132 t.Fatalf("Unexpected extract error: %v", err)
133 }
134 if !equality.Semantic.DeepEqual(out, tc.expectedOut) {
135 t.Fatalf("Expected output did not match actual output: %s", cmp.Diff(out, tc.expectedOut))
136 }
137 })
138 }
139 }
140
141 func applyFieldsEntry(fieldManager string, fieldsJSON string, subresource string) metav1.ManagedFieldsEntry {
142 return metav1.ManagedFieldsEntry{
143 Manager: fieldManager,
144 Operation: metav1.ManagedFieldsOperationApply,
145 APIVersion: "v1",
146 FieldsType: "FieldsV1",
147 FieldsV1: &metav1.FieldsV1{Raw: []byte(fieldsJSON)},
148 Subresource: subresource,
149 }
150 }
151
152 type fakeDeployment struct {
153 metav1.ObjectMeta `json:"metadata,omitempty"`
154 Spec fakeDeploymentSpec `json:"spec"`
155 Status fakeDeploymentStatus `json:"status"`
156 }
157
158 type fakeDeploymentSpec struct {
159 Replicas *int32 `json:"replicas"`
160 Paused bool `json:"paused,omitempty"`
161 }
162
163 type fakeDeploymentStatus struct {
164 Replicas *int32 `json:"replicas"`
165 }
166
167 func (o *fakeDeployment) GetObjectMeta() metav1.ObjectMeta {
168 return o.ObjectMeta
169 }
170 func (o *fakeDeployment) GetObjectKind() runtimeschema.ObjectKind {
171 return runtimeschema.EmptyObjectKind
172 }
173 func (o *fakeDeployment) DeepCopyObject() runtime.Object {
174 return o
175 }
176
177
178 const schemaYAML = typed.YAMLObject(`types:
179 - name: io.k8s.api.apps.v1.Deployment
180 map:
181 fields:
182 - name: apiVersion
183 type:
184 scalar: string
185 - name: kind
186 type:
187 scalar: string
188 - name: metadata
189 type:
190 namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta
191 - name: spec
192 type:
193 namedType: io.k8s.api.apps.v1.DeploymentSpec
194 - name: status
195 type:
196 namedType: io.k8s.api.apps.v1.DeploymentStatus
197 - name: io.k8s.api.apps.v1.DeploymentSpec
198 map:
199 fields:
200 - name: paused
201 type:
202 scalar: boolean
203 - name: replicas
204 type:
205 scalar: numeric
206 - name: io.k8s.api.apps.v1.DeploymentStatus
207 map:
208 fields:
209 - name: replicas
210 type:
211 scalar: numeric
212 - name: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta
213 map:
214 fields:
215 - name: creationTimestamp
216 type:
217 namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time
218 - name: managedFields
219 type:
220 list:
221 elementType:
222 namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry
223 elementRelationship: atomic
224 - name: io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry
225 map:
226 fields:
227 - name: apiVersion
228 type:
229 scalar: string
230 - name: fieldsType
231 type:
232 scalar: string
233 - name: fieldsV1
234 type:
235 namedType: io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1
236 - name: manager
237 type:
238 scalar: string
239 - name: operation
240 type:
241 scalar: string
242 - name: time
243 type:
244 namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time
245 - name: subresource
246 type:
247 scalar: string
248 - name: io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1
249 map:
250 elementType:
251 scalar: untyped
252 list:
253 elementType:
254 namedType: __untyped_atomic_
255 elementRelationship: atomic
256 map:
257 elementType:
258 namedType: __untyped_atomic_
259 elementRelationship: atomic
260 - name: io.k8s.apimachinery.pkg.apis.meta.v1.Time
261 scalar: untyped
262 - name: __untyped_atomic_
263 scalar: untyped
264 list:
265 elementType:
266 namedType: __untyped_atomic_
267 elementRelationship: atomic
268 map:
269 elementType:
270 namedType: __untyped_atomic_
271 elementRelationship: atomic
272 `)
273
View as plain text