@@ -18,18 +18,19 @@ package components
18
18
19
19
import (
20
20
"context"
21
+ "encoding/json"
22
+ "fmt"
21
23
"strings"
22
24
"time"
23
25
24
26
cu "github.com/coderanger/controller-utils"
25
27
"github.com/pkg/errors"
26
- appsv1 "k8s.io/api/apps/v1"
27
28
batchv1 "k8s.io/api/batch/v1"
28
29
corev1 "k8s.io/api/core/v1"
29
30
kerrors "k8s.io/apimachinery/pkg/api/errors"
30
31
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
31
33
"k8s.io/apimachinery/pkg/labels"
32
- "k8s.io/apimachinery/pkg/runtime"
33
34
"k8s.io/apimachinery/pkg/runtime/schema"
34
35
"k8s.io/apimachinery/pkg/types"
35
36
ctrl "sigs.k8s.io/controller-runtime"
@@ -40,7 +41,6 @@ import (
40
41
"sigs.k8s.io/controller-runtime/pkg/source"
41
42
42
43
migrationsv1beta1 "github.com/coderanger/migrations-operator/api/v1beta1"
43
- argoprojstubv1alpha1 "github.com/coderanger/migrations-operator/stubs/argoproj/v1alpha1"
44
44
"github.com/coderanger/migrations-operator/utils"
45
45
"github.com/coderanger/migrations-operator/webhook"
46
46
)
@@ -83,6 +83,24 @@ func (comp *migrationsComponent) Setup(ctx *cu.Context, bldr *ctrl.Builder) erro
83
83
return nil
84
84
}
85
85
86
+ func deepCopyJSON (src map [string ]interface {}, dest map [string ]interface {}) error {
87
+ if src == nil {
88
+ return errors .New ("src is nil. You cannot read from a nil map" )
89
+ }
90
+ if dest == nil {
91
+ return errors .New ("dest is nil. You cannot insert to a nil map" )
92
+ }
93
+ jsonStr , err := json .Marshal (src )
94
+ if err != nil {
95
+ return err
96
+ }
97
+ err = json .Unmarshal (jsonStr , & dest )
98
+ if err != nil {
99
+ return err
100
+ }
101
+ return nil
102
+ }
103
+
86
104
func (comp * migrationsComponent ) Reconcile (ctx * cu.Context ) (cu.Result , error ) {
87
105
obj := ctx .Object .(* migrationsv1beta1.Migrator )
88
106
@@ -105,16 +123,18 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
105
123
}
106
124
107
125
// Find a template pod to start from.
108
- allPods := & corev1.PodList {}
126
+ allPods := & unstructured.UnstructuredList {}
127
+ allPods .SetAPIVersion ("v1" )
128
+ allPods .SetKind ("Pod" )
109
129
err = ctx .Client .List (ctx , allPods , & client.ListOptions {Namespace : obj .Namespace })
110
130
if err != nil {
111
131
return cu.Result {}, errors .Wrapf (err , "error listing pods in namespace %s" , obj .Namespace )
112
132
}
113
- pods := []* corev1. Pod {}
114
- var templatePod * corev1. Pod
133
+ pods := []* unstructured. Unstructured {}
134
+ var templatePod * unstructured. Unstructured
115
135
for i := range allPods .Items {
116
136
pod := & allPods .Items [i ]
117
- labelSet := labels .Set (pod .Labels )
137
+ labelSet := labels .Set (pod .GetLabels () )
118
138
if selector .Matches (labelSet ) {
119
139
pods = append (pods , pod )
120
140
if templatePod == nil && templateSelector .Matches (labelSet ) {
@@ -138,54 +158,63 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
138
158
}
139
159
140
160
// Find the template container.
141
- var templateContainer * corev1.Container
161
+ templatePodSpecContainers := templatePodSpec ["containers" ].([]map [string ]interface {})
162
+ var templateContainer map [string ]interface {}
142
163
if obj .Spec .Container != "" {
143
164
// Looking for a specific container name.
144
- for _ , c := range templatePodSpec . Containers {
145
- if c . Name == obj .Spec .Container {
146
- templateContainer = & c
165
+ for _ , c := range templatePodSpecContainers {
166
+ if c [ "name" ].( string ) == obj .Spec .Container {
167
+ templateContainer = c
147
168
break
148
169
}
149
170
}
150
- } else if len (templatePodSpec . Containers ) > 0 {
151
- templateContainer = & templatePodSpec . Containers [0 ]
171
+ } else if len (templatePodSpecContainers ) > 0 {
172
+ templateContainer = templatePodSpecContainers [0 ]
152
173
}
153
174
if templateContainer == nil {
154
175
// Welp, either nothing matched the name or somehow there are no containers.
155
176
return cu.Result {}, errors .New ("no template container found" )
156
177
}
157
178
158
179
// Build a migration job object.
159
- migrationContainer := templateContainer .DeepCopy ()
160
- migrationContainer .Name = "migrations"
180
+ migrationContainer := make (map [string ]interface {})
181
+ err = deepCopyJSON (templateContainer , migrationContainer )
182
+ if err != nil {
183
+ return cu.Result {}, errors .Wrap (err , "error copying template container" )
184
+ }
185
+ migrationContainer ["name" ] = "migrations"
161
186
if obj .Spec .Image != "" {
162
- migrationContainer . Image = obj .Spec .Image
187
+ migrationContainer [ "image" ] = obj .Spec .Image
163
188
}
164
189
if obj .Spec .Command != nil {
165
- migrationContainer . Command = * obj .Spec .Command
190
+ migrationContainer [ "command" ] = * obj .Spec .Command
166
191
}
167
192
if obj .Spec .Args != nil {
168
- migrationContainer . Args = * obj .Spec .Args
193
+ migrationContainer [ "args" ] = * obj .Spec .Args
169
194
}
170
195
// TODO resources?
171
196
172
197
// Remove the probes since they will rarely work.
173
- migrationContainer . ReadinessProbe = nil
174
- migrationContainer . LivenessProbe = nil
175
- migrationContainer . StartupProbe = nil
198
+ migrationContainer [ "readinessProbe" ] = nil
199
+ migrationContainer [ "livenessProbe" ] = nil
200
+ migrationContainer [ "startupProbe" ] = nil
176
201
177
- migrationPodSpec := templatePodSpec .DeepCopy ()
178
- migrationPodSpec .Containers = []corev1.Container {* migrationContainer }
179
- migrationPodSpec .RestartPolicy = corev1 .RestartPolicyNever
202
+ migrationPodSpec := make (map [string ]interface {})
203
+ err = deepCopyJSON (templatePodSpec , migrationPodSpec )
204
+ if err != nil {
205
+ return cu.Result {}, errors .Wrap (err , "error copying template pod spec" )
206
+ }
207
+ migrationPodSpec ["containers" ] = []map [string ]interface {}{migrationContainer }
208
+ migrationPodSpec ["restartPolicy" ] = corev1 .RestartPolicyNever
180
209
181
210
// Purge any migration wait initContainers since that would be a yodawg situation.
182
- initContainers := []corev1. Container {}
183
- for _ , c := range migrationPodSpec . InitContainers {
184
- if ! strings .HasPrefix (c . Name , "migrate-wait-" ) {
211
+ initContainers := []map [ string ] interface {} {}
212
+ for _ , c := range migrationPodSpec [ "initContainers" ].([] map [ string ] interface {}) {
213
+ if ! strings .HasPrefix (c [ "name" ].( string ) , "migrate-wait-" ) {
185
214
initContainers = append (initContainers , c )
186
215
}
187
216
}
188
- migrationPodSpec . InitContainers = initContainers
217
+ migrationPodSpec [ "initContainers" ] = initContainers
189
218
190
219
// add labels to the job's pod template
191
220
jobTemplateLabels := map [string ]string {"migrations" : obj .Name }
@@ -205,21 +234,22 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
205
234
}
206
235
}
207
236
208
- migrationJob := & batchv1. Job {
209
- ObjectMeta : metav1. ObjectMeta {
210
- Name : obj . Name + "-migrations" ,
211
- Namespace : obj . Namespace ,
212
- Labels : obj . Labels ,
213
- Annotations : map [ string ] string {},
214
- },
215
- Spec : batchv1. JobSpec {
216
- Template : corev1. PodTemplateSpec {
217
- ObjectMeta : metav1. ObjectMeta {
218
- Labels : jobTemplateLabels ,
219
- Annotations : jobTemplateAnnotations ,
220
- } ,
221
- Spec : * migrationPodSpec ,
237
+ migrationJobName := obj . Name + "-migrations"
238
+ migrationJobNamespace := obj . Namespace
239
+ migrationJobImage := migrationContainer [ "image" ].( string )
240
+ migrationJob := & unstructured. Unstructured {}
241
+ migrationJob . SetAPIVersion ( "batch/v1" )
242
+ migrationJob . SetKind ( "Job" )
243
+ migrationJob . SetName ( migrationJobName )
244
+ migrationJob . SetNamespace ( migrationJobNamespace )
245
+ migrationJob . SetLabels ( obj . Labels )
246
+ migrationJob . UnstructuredContent ()[ "spec" ] = map [ string ] interface {} {
247
+ "template" : map [ string ] interface {}{
248
+ "meta" : map [ string ] interface {}{
249
+ "labels" : jobTemplateLabels ,
250
+ "annotations" : jobTemplateAnnotations ,
222
251
},
252
+ "spec" : migrationPodSpec ,
223
253
},
224
254
}
225
255
err = controllerutil .SetControllerReference (obj , migrationJob , ctx .Scheme )
@@ -233,13 +263,13 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
233
263
if err != nil {
234
264
return cu.Result {}, errors .Wrap (err , "error getting latest migrator for status" )
235
265
}
236
- if uncachedObj .Status .LastSuccessfulMigration == migrationContainer . Image {
237
- ctx .Conditions .SetfTrue (comp .GetReadyCondition (), "MigrationsUpToDate" , "Migration %s already run" , migrationContainer . Image )
266
+ if uncachedObj .Status .LastSuccessfulMigration == migrationJobImage {
267
+ ctx .Conditions .SetfTrue (comp .GetReadyCondition (), "MigrationsUpToDate" , "Migration %s already run" , migrationJobImage )
238
268
return cu.Result {}, nil
239
269
}
240
270
241
271
existingJob := & batchv1.Job {}
242
- err = ctx .Client .Get (ctx , types.NamespacedName {Name : migrationJob . Name , Namespace : migrationJob . Namespace }, existingJob )
272
+ err = ctx .Client .Get (ctx , types.NamespacedName {Name : migrationJobName , Namespace : migrationJobNamespace }, existingJob )
243
273
if err != nil {
244
274
if kerrors .IsNotFound (err ) {
245
275
// Try to start the migrations.
@@ -250,11 +280,11 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
250
280
ctx .Conditions .SetfUnknown (comp .GetReadyCondition (), "CreateError" , "Error on create, possible conflict: %v" , err )
251
281
return cu.Result {Requeue : true }, nil
252
282
}
253
- ctx .Events .Eventf (obj , "Normal" , "MigrationsStarted" , "Started migration job %s/%s using image %s" , migrationJob . Namespace , migrationJob . Name , migrationContainer . Image )
254
- ctx .Conditions .SetfFalse (comp .GetReadyCondition (), "MigrationsRunning" , "Started migration job %s/%s using image %s" , migrationJob . Namespace , migrationJob . Name , migrationContainer . Image )
283
+ ctx .Events .Eventf (obj , "Normal" , "MigrationsStarted" , "Started migration job %s/%s using image %s" , migrationJobNamespace , migrationJobName , migrationJobImage )
284
+ ctx .Conditions .SetfFalse (comp .GetReadyCondition (), "MigrationsRunning" , "Started migration job %s/%s using image %s" , migrationJobNamespace , migrationJobName , migrationJobImage )
255
285
return cu.Result {}, nil
256
286
} else {
257
- return cu.Result {}, errors .Wrapf (err , "error getting existing migration job %s/%s" , migrationJob . Namespace , migrationJob . Name )
287
+ return cu.Result {}, errors .Wrapf (err , "error getting existing migration job %s/%s" , migrationJobNamespace , migrationJobName )
258
288
}
259
289
}
260
290
@@ -263,15 +293,15 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
263
293
if len (existingJob .Spec .Template .Spec .Containers ) > 0 {
264
294
existingImage = existingJob .Spec .Template .Spec .Containers [0 ].Image
265
295
}
266
- if existingImage == "" || existingImage != migrationContainer . Image {
296
+ if existingImage == "" || existingImage != migrationJobImage {
267
297
// Old, stale migration. Remove it and try again.
268
298
policy := metav1 .DeletePropagationForeground
269
299
err = ctx .Client .Delete (ctx , existingJob , & client.DeleteOptions {PropagationPolicy : & policy })
270
300
if err != nil {
271
301
return cu.Result {}, errors .Wrapf (err , "error deleting stale migration job %s/%s" , existingJob .Namespace , existingJob .Name )
272
302
}
273
- ctx .Events .Eventf (obj , "Normal" , "StaleJob" , "Deleted stale migration job %s/%s (%s)" , migrationJob . Namespace , migrationJob . Name , existingImage )
274
- ctx .Conditions .SetfFalse (comp .GetReadyCondition (), "StaleJob" , "Deleted stale migration job %s/%s (%s)" , migrationJob . Namespace , migrationJob . Name , existingImage )
303
+ ctx .Events .Eventf (obj , "Normal" , "StaleJob" , "Deleted stale migration job %s/%s (%s)" , migrationJobNamespace , migrationJobName , existingImage )
304
+ ctx .Conditions .SetfFalse (comp .GetReadyCondition (), "StaleJob" , "Deleted stale migration job %s/%s (%s)" , migrationJobNamespace , migrationJobName , existingImage )
275
305
return cu.Result {RequeueAfter : 1 * time .Second , SkipRemaining : true }, nil
276
306
}
277
307
@@ -284,7 +314,7 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
284
314
}
285
315
ctx .Events .Eventf (obj , "Normal" , "MigrationsSucceeded" , "Migration job %s/%s using image %s succeeded" , existingJob .Namespace , existingJob .Name , existingImage )
286
316
ctx .Conditions .SetfTrue (comp .GetReadyCondition (), "MigrationsSucceeded" , "Migration job %s/%s using image %s succeeded" , existingJob .Namespace , existingJob .Name , existingImage )
287
- obj .Status .LastSuccessfulMigration = migrationContainer . Image
317
+ obj .Status .LastSuccessfulMigration = migrationJobImage
288
318
return cu.Result {}, nil
289
319
}
290
320
@@ -301,27 +331,20 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
301
331
return cu.Result {}, nil
302
332
}
303
333
304
- func (_ * migrationsComponent ) findOwners (ctx * cu.Context , obj client. Object ) ([]client. Object , error ) {
334
+ func (_ * migrationsComponent ) findOwners (ctx * cu.Context , obj * unstructured. Unstructured ) ([]* unstructured. Unstructured , error ) {
305
335
namespace := obj .GetNamespace ()
306
- owners := []client. Object {}
336
+ owners := []* unstructured. Unstructured {}
307
337
for {
308
338
owners = append (owners , obj )
309
339
ref := metav1 .GetControllerOfNoCopy (obj )
310
340
if ref == nil {
311
341
break
312
342
}
313
343
gvk := schema .FromAPIVersionAndKind (ref .APIVersion , ref .Kind )
314
- ownerObj , err := ctx .Scheme .New (gvk )
315
- if err != nil {
316
- // Gracefully handle kinds that we haven't registered. Useful when a Rollout or Deployment is
317
- // owned by someone's in-house operator
318
- if runtime .IsNotRegisteredError (err ) {
319
- break
320
- }
321
- return nil , errors .Wrapf (err , "error finding object type for owner reference %v" , ref )
322
- }
323
- obj = ownerObj .(client.Object )
324
- err = ctx .Client .Get (ctx , types.NamespacedName {Name : ref .Name , Namespace : namespace }, obj )
344
+ obj := & unstructured.Unstructured {}
345
+ obj .SetGroupVersionKind (gvk )
346
+ obj .SetName (ref .Name ) // Is this needed?
347
+ err := ctx .Client .Get (ctx , types.NamespacedName {Name : ref .Name , Namespace : namespace }, obj )
325
348
if err != nil {
326
349
// Gracefully handle objects we don't have access to
327
350
if kerrors .IsForbidden (err ) {
@@ -337,34 +360,44 @@ func (_ *migrationsComponent) findOwners(ctx *cu.Context, obj client.Object) ([]
337
360
return owners , nil
338
361
}
339
362
340
- func (_ * migrationsComponent ) findSpecFor (ctx * cu.Context , obj client.Object ) * corev1.PodSpec {
341
- switch v := obj .(type ) {
342
- case * corev1.Pod :
343
- return & v .Spec
344
- case * appsv1.Deployment :
345
- return & v .Spec .Template .Spec
346
- case * argoprojstubv1alpha1.Rollout :
347
- if v .Spec .WorkloadRef != nil {
348
- if v .Spec .WorkloadRef .Kind == "Deployment" {
349
- deployment := appsv1.Deployment {}
350
- err := ctx .Client .Get (ctx , client.ObjectKey {Namespace : v .Namespace , Name : v .Spec .WorkloadRef .Name }, & deployment )
363
+ func (_ * migrationsComponent ) findSpecFor (ctx * cu.Context , obj * unstructured.Unstructured ) map [string ]interface {} {
364
+ gvk := obj .GetObjectKind ().GroupVersionKind ()
365
+ switch fmt .Sprintf ("%s/%s" , gvk .Group , gvk .Kind ) {
366
+ case "/Pod" :
367
+ return obj .UnstructuredContent ()["spec" ].(map [string ]interface {})
368
+ case "apps/Deployment" :
369
+ spec := obj .UnstructuredContent ()["spec" ].(map [string ]interface {})
370
+ template := spec ["template" ].(map [string ]interface {})
371
+ return template ["spec" ].(map [string ]interface {})
372
+ case "argoproj.io/Rollout" :
373
+ spec := obj .UnstructuredContent ()["spec" ].(map [string ]interface {})
374
+ workloadRef := spec ["workloadRef" ].(map [string ]interface {})
375
+ if workloadRef != nil {
376
+ workloadKind := workloadRef ["kind" ].(string )
377
+ if workloadKind == "Deployment" {
378
+ deployment := & unstructured.Unstructured {}
379
+ deployment .SetAPIVersion (workloadRef ["apiVersion" ].(string ))
380
+ deployment .SetKind (workloadKind )
381
+ err := ctx .Client .Get (ctx , types.NamespacedName {Name : workloadRef ["name" ].(string ), Namespace : obj .GetNamespace ()}, obj )
351
382
if err != nil {
352
383
return nil
353
384
}
354
- return & deployment .Spec .Template .Spec
385
+ deploymentSpec := deployment .UnstructuredContent ()["spec" ].(map [string ]interface {})
386
+ deploymentTemplate := deploymentSpec ["template" ].(map [string ]interface {})
387
+ return deploymentTemplate ["spec" ].(map [string ]interface {})
355
388
} else {
356
389
// TODO handle other WorkloadRef types
357
390
return nil
358
391
}
359
392
}
360
- return & v . Spec . Template . Spec
361
- // TODO other types. lots of them.
393
+ template := spec [ "template" ].( map [ string ] interface {})
394
+ return template [ "spec" ].( map [ string ] interface {})
362
395
default :
363
396
return nil
364
397
}
365
398
}
366
399
367
- func (comp * migrationsComponent ) findOwnerSpec (ctx * cu.Context , obj client. Object ) (* corev1. PodSpec , error ) {
400
+ func (comp * migrationsComponent ) findOwnerSpec (ctx * cu.Context , obj * unstructured. Unstructured ) (map [ string ] interface {} , error ) {
368
401
owners , err := comp .findOwners (ctx , obj )
369
402
if err != nil {
370
403
return nil , err
0 commit comments