@@ -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,67 @@ 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" ].([]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
+ container := c .(map [string ]interface {})
167
+ if container ["name" ].(string ) == obj .Spec .Container {
168
+ templateContainer = container
147
169
break
148
170
}
149
171
}
150
- } else if len (templatePodSpec . Containers ) > 0 {
151
- templateContainer = & templatePodSpec . Containers [0 ]
172
+ } else if len (templatePodSpecContainers ) > 0 {
173
+ templateContainer = templatePodSpecContainers [0 ].( map [ string ] interface {})
152
174
}
153
175
if templateContainer == nil {
154
176
// Welp, either nothing matched the name or somehow there are no containers.
155
177
return cu.Result {}, errors .New ("no template container found" )
156
178
}
157
179
158
180
// Build a migration job object.
159
- migrationContainer := templateContainer .DeepCopy ()
160
- migrationContainer .Name = "migrations"
181
+ migrationContainer := make (map [string ]interface {})
182
+ err = deepCopyJSON (templateContainer , migrationContainer )
183
+ if err != nil {
184
+ return cu.Result {}, errors .Wrap (err , "error copying template container" )
185
+ }
186
+ migrationContainer ["name" ] = "migrations"
161
187
if obj .Spec .Image != "" {
162
- migrationContainer . Image = obj .Spec .Image
188
+ migrationContainer [ "image" ] = obj .Spec .Image
163
189
}
164
190
if obj .Spec .Command != nil {
165
- migrationContainer . Command = * obj .Spec .Command
191
+ migrationContainer [ "command" ] = * obj .Spec .Command
166
192
}
167
193
if obj .Spec .Args != nil {
168
- migrationContainer . Args = * obj .Spec .Args
194
+ migrationContainer [ "args" ] = * obj .Spec .Args
169
195
}
170
196
// TODO resources?
171
197
172
198
// Remove the probes since they will rarely work.
173
- migrationContainer . ReadinessProbe = nil
174
- migrationContainer . LivenessProbe = nil
175
- migrationContainer . StartupProbe = nil
199
+ migrationContainer [ "readinessProbe" ] = nil
200
+ migrationContainer [ "livenessProbe" ] = nil
201
+ migrationContainer [ "startupProbe" ] = nil
176
202
177
- migrationPodSpec := templatePodSpec .DeepCopy ()
178
- migrationPodSpec .Containers = []corev1.Container {* migrationContainer }
179
- migrationPodSpec .RestartPolicy = corev1 .RestartPolicyNever
203
+ migrationPodSpec := make (map [string ]interface {})
204
+ err = deepCopyJSON (templatePodSpec , migrationPodSpec )
205
+ if err != nil {
206
+ return cu.Result {}, errors .Wrap (err , "error copying template pod spec" )
207
+ }
208
+ migrationPodSpec ["containers" ] = []map [string ]interface {}{migrationContainer }
209
+ migrationPodSpec ["restartPolicy" ] = corev1 .RestartPolicyNever
180
210
181
211
// 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-" ) {
185
- initContainers = append (initContainers , c )
212
+ initContainers := []map [string ]interface {}{}
213
+ if migrationPodSpec ["initContainers" ] != nil {
214
+ for _ , c := range migrationPodSpec ["initContainers" ].([]interface {}) {
215
+ container := c .(map [string ]interface {})
216
+ if ! strings .HasPrefix (container ["name" ].(string ), "migrate-wait-" ) {
217
+ initContainers = append (initContainers , container )
218
+ }
186
219
}
187
220
}
188
- migrationPodSpec . InitContainers = initContainers
221
+ migrationPodSpec [ "initContainers" ] = initContainers
189
222
190
223
// add labels to the job's pod template
191
224
jobTemplateLabels := map [string ]string {"migrations" : obj .Name }
@@ -205,21 +238,22 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
205
238
}
206
239
}
207
240
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 ,
241
+ migrationJobName := obj . Name + "-migrations"
242
+ migrationJobNamespace := obj . Namespace
243
+ migrationJobImage := migrationContainer [ "image" ].( string )
244
+ migrationJob := & unstructured. Unstructured {}
245
+ migrationJob . SetAPIVersion ( "batch/v1" )
246
+ migrationJob . SetKind ( "Job" )
247
+ migrationJob . SetName ( migrationJobName )
248
+ migrationJob . SetNamespace ( migrationJobNamespace )
249
+ migrationJob . SetLabels ( obj . Labels )
250
+ migrationJob . UnstructuredContent ()[ "spec" ] = map [ string ] interface {} {
251
+ "template" : map [ string ] interface {}{
252
+ "metadata" : map [ string ] interface {}{
253
+ "labels" : jobTemplateLabels ,
254
+ "annotations" : jobTemplateAnnotations ,
222
255
},
256
+ "spec" : migrationPodSpec ,
223
257
},
224
258
}
225
259
err = controllerutil .SetControllerReference (obj , migrationJob , ctx .Scheme )
@@ -233,13 +267,13 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
233
267
if err != nil {
234
268
return cu.Result {}, errors .Wrap (err , "error getting latest migrator for status" )
235
269
}
236
- if uncachedObj .Status .LastSuccessfulMigration == migrationContainer . Image {
237
- ctx .Conditions .SetfTrue (comp .GetReadyCondition (), "MigrationsUpToDate" , "Migration %s already run" , migrationContainer . Image )
270
+ if uncachedObj .Status .LastSuccessfulMigration == migrationJobImage {
271
+ ctx .Conditions .SetfTrue (comp .GetReadyCondition (), "MigrationsUpToDate" , "Migration %s already run" , migrationJobImage )
238
272
return cu.Result {}, nil
239
273
}
240
274
241
275
existingJob := & batchv1.Job {}
242
- err = ctx .Client .Get (ctx , types.NamespacedName {Name : migrationJob . Name , Namespace : migrationJob . Namespace }, existingJob )
276
+ err = ctx .Client .Get (ctx , types.NamespacedName {Name : migrationJobName , Namespace : migrationJobNamespace }, existingJob )
243
277
if err != nil {
244
278
if kerrors .IsNotFound (err ) {
245
279
// Try to start the migrations.
@@ -250,11 +284,11 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
250
284
ctx .Conditions .SetfUnknown (comp .GetReadyCondition (), "CreateError" , "Error on create, possible conflict: %v" , err )
251
285
return cu.Result {Requeue : true }, nil
252
286
}
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 )
287
+ ctx .Events .Eventf (obj , "Normal" , "MigrationsStarted" , "Started migration job %s/%s using image %s" , migrationJobNamespace , migrationJobName , migrationJobImage )
288
+ ctx .Conditions .SetfFalse (comp .GetReadyCondition (), "MigrationsRunning" , "Started migration job %s/%s using image %s" , migrationJobNamespace , migrationJobName , migrationJobImage )
255
289
return cu.Result {}, nil
256
290
} else {
257
- return cu.Result {}, errors .Wrapf (err , "error getting existing migration job %s/%s" , migrationJob . Namespace , migrationJob . Name )
291
+ return cu.Result {}, errors .Wrapf (err , "error getting existing migration job %s/%s" , migrationJobNamespace , migrationJobName )
258
292
}
259
293
}
260
294
@@ -263,15 +297,15 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
263
297
if len (existingJob .Spec .Template .Spec .Containers ) > 0 {
264
298
existingImage = existingJob .Spec .Template .Spec .Containers [0 ].Image
265
299
}
266
- if existingImage == "" || existingImage != migrationContainer . Image {
300
+ if existingImage == "" || existingImage != migrationJobImage {
267
301
// Old, stale migration. Remove it and try again.
268
302
policy := metav1 .DeletePropagationForeground
269
303
err = ctx .Client .Delete (ctx , existingJob , & client.DeleteOptions {PropagationPolicy : & policy })
270
304
if err != nil {
271
305
return cu.Result {}, errors .Wrapf (err , "error deleting stale migration job %s/%s" , existingJob .Namespace , existingJob .Name )
272
306
}
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 )
307
+ ctx .Events .Eventf (obj , "Normal" , "StaleJob" , "Deleted stale migration job %s/%s (%s)" , migrationJobNamespace , migrationJobName , existingImage )
308
+ ctx .Conditions .SetfFalse (comp .GetReadyCondition (), "StaleJob" , "Deleted stale migration job %s/%s (%s)" , migrationJobNamespace , migrationJobName , existingImage )
275
309
return cu.Result {RequeueAfter : 1 * time .Second , SkipRemaining : true }, nil
276
310
}
277
311
@@ -284,7 +318,7 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
284
318
}
285
319
ctx .Events .Eventf (obj , "Normal" , "MigrationsSucceeded" , "Migration job %s/%s using image %s succeeded" , existingJob .Namespace , existingJob .Name , existingImage )
286
320
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
321
+ obj .Status .LastSuccessfulMigration = migrationJobImage
288
322
return cu.Result {}, nil
289
323
}
290
324
@@ -301,27 +335,20 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
301
335
return cu.Result {}, nil
302
336
}
303
337
304
- func (_ * migrationsComponent ) findOwners (ctx * cu.Context , obj client. Object ) ([]client. Object , error ) {
338
+ func (_ * migrationsComponent ) findOwners (ctx * cu.Context , obj * unstructured. Unstructured ) ([]* unstructured. Unstructured , error ) {
305
339
namespace := obj .GetNamespace ()
306
- owners := []client. Object {}
340
+ owners := []* unstructured. Unstructured {}
307
341
for {
308
342
owners = append (owners , obj )
309
343
ref := metav1 .GetControllerOfNoCopy (obj )
310
344
if ref == nil {
311
345
break
312
346
}
313
347
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 )
348
+ obj = & unstructured.Unstructured {}
349
+ obj .SetGroupVersionKind (gvk )
350
+ obj .SetName (ref .Name ) // Is this needed?
351
+ err := ctx .Client .Get (ctx , types.NamespacedName {Name : ref .Name , Namespace : namespace }, obj )
325
352
if err != nil {
326
353
// Gracefully handle objects we don't have access to
327
354
if kerrors .IsForbidden (err ) {
@@ -337,34 +364,44 @@ func (_ *migrationsComponent) findOwners(ctx *cu.Context, obj client.Object) ([]
337
364
return owners , nil
338
365
}
339
366
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 )
367
+ func (_ * migrationsComponent ) findSpecFor (ctx * cu.Context , obj * unstructured.Unstructured ) map [string ]interface {} {
368
+ gvk := obj .GetObjectKind ().GroupVersionKind ()
369
+ switch fmt .Sprintf ("%s/%s" , gvk .Group , gvk .Kind ) {
370
+ case "/Pod" :
371
+ return obj .UnstructuredContent ()["spec" ].(map [string ]interface {})
372
+ case "apps/Deployment" :
373
+ spec := obj .UnstructuredContent ()["spec" ].(map [string ]interface {})
374
+ template := spec ["template" ].(map [string ]interface {})
375
+ return template ["spec" ].(map [string ]interface {})
376
+ case "argoproj.io/Rollout" :
377
+ spec := obj .UnstructuredContent ()["spec" ].(map [string ]interface {})
378
+ if spec ["workloadRef" ] != nil {
379
+ workloadRef := spec ["workloadRef" ].(map [string ]interface {})
380
+ workloadKind := workloadRef ["kind" ].(string )
381
+ if workloadKind == "Deployment" {
382
+ deployment := & unstructured.Unstructured {}
383
+ deployment .SetAPIVersion (workloadRef ["apiVersion" ].(string ))
384
+ deployment .SetKind (workloadKind )
385
+ err := ctx .Client .Get (ctx , types.NamespacedName {Name : workloadRef ["name" ].(string ), Namespace : obj .GetNamespace ()}, deployment )
351
386
if err != nil {
352
387
return nil
353
388
}
354
- return & deployment .Spec .Template .Spec
389
+ deploymentSpec := deployment .UnstructuredContent ()["spec" ].(map [string ]interface {})
390
+ deploymentTemplate := deploymentSpec ["template" ].(map [string ]interface {})
391
+ return deploymentTemplate ["spec" ].(map [string ]interface {})
355
392
} else {
356
393
// TODO handle other WorkloadRef types
357
394
return nil
358
395
}
359
396
}
360
- return & v . Spec . Template . Spec
361
- // TODO other types. lots of them.
397
+ template := spec [ "template" ].( map [ string ] interface {})
398
+ return template [ "spec" ].( map [ string ] interface {})
362
399
default :
363
400
return nil
364
401
}
365
402
}
366
403
367
- func (comp * migrationsComponent ) findOwnerSpec (ctx * cu.Context , obj client. Object ) (* corev1. PodSpec , error ) {
404
+ func (comp * migrationsComponent ) findOwnerSpec (ctx * cu.Context , obj * unstructured. Unstructured ) (map [ string ] interface {} , error ) {
368
405
owners , err := comp .findOwners (ctx , obj )
369
406
if err != nil {
370
407
return nil , err
0 commit comments