@@ -18,18 +18,19 @@ package components
1818
1919import (
2020 "context"
21+ "encoding/json"
22+ "fmt"
2123 "strings"
2224 "time"
2325
2426 cu "github.com/coderanger/controller-utils"
2527 "github.com/pkg/errors"
26- appsv1 "k8s.io/api/apps/v1"
2728 batchv1 "k8s.io/api/batch/v1"
2829 corev1 "k8s.io/api/core/v1"
2930 kerrors "k8s.io/apimachinery/pkg/api/errors"
3031 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3133 "k8s.io/apimachinery/pkg/labels"
32- "k8s.io/apimachinery/pkg/runtime"
3334 "k8s.io/apimachinery/pkg/runtime/schema"
3435 "k8s.io/apimachinery/pkg/types"
3536 ctrl "sigs.k8s.io/controller-runtime"
@@ -40,7 +41,6 @@ import (
4041 "sigs.k8s.io/controller-runtime/pkg/source"
4142
4243 migrationsv1beta1 "github.com/coderanger/migrations-operator/api/v1beta1"
43- argoprojstubv1alpha1 "github.com/coderanger/migrations-operator/stubs/argoproj/v1alpha1"
4444 "github.com/coderanger/migrations-operator/utils"
4545 "github.com/coderanger/migrations-operator/webhook"
4646)
@@ -83,6 +83,24 @@ func (comp *migrationsComponent) Setup(ctx *cu.Context, bldr *ctrl.Builder) erro
8383 return nil
8484}
8585
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+
86104func (comp * migrationsComponent ) Reconcile (ctx * cu.Context ) (cu.Result , error ) {
87105 obj := ctx .Object .(* migrationsv1beta1.Migrator )
88106
@@ -105,16 +123,18 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
105123 }
106124
107125 // Find a template pod to start from.
108- allPods := & corev1.PodList {}
126+ allPods := & unstructured.UnstructuredList {}
127+ allPods .SetAPIVersion ("v1" )
128+ allPods .SetKind ("Pod" )
109129 err = ctx .Client .List (ctx , allPods , & client.ListOptions {Namespace : obj .Namespace })
110130 if err != nil {
111131 return cu.Result {}, errors .Wrapf (err , "error listing pods in namespace %s" , obj .Namespace )
112132 }
113- pods := []* corev1. Pod {}
114- var templatePod * corev1. Pod
133+ pods := []* unstructured. Unstructured {}
134+ var templatePod * unstructured. Unstructured
115135 for i := range allPods .Items {
116136 pod := & allPods .Items [i ]
117- labelSet := labels .Set (pod .Labels )
137+ labelSet := labels .Set (pod .GetLabels () )
118138 if selector .Matches (labelSet ) {
119139 pods = append (pods , pod )
120140 if templatePod == nil && templateSelector .Matches (labelSet ) {
@@ -138,54 +158,67 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
138158 }
139159
140160 // Find the template container.
141- var templateContainer * corev1.Container
161+ templatePodSpecContainers := templatePodSpec ["containers" ].([]interface {})
162+ var templateContainer map [string ]interface {}
142163 if obj .Spec .Container != "" {
143164 // 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
147169 break
148170 }
149171 }
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 {})
152174 }
153175 if templateContainer == nil {
154176 // Welp, either nothing matched the name or somehow there are no containers.
155177 return cu.Result {}, errors .New ("no template container found" )
156178 }
157179
158180 // 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"
161187 if obj .Spec .Image != "" {
162- migrationContainer . Image = obj .Spec .Image
188+ migrationContainer [ "image" ] = obj .Spec .Image
163189 }
164190 if obj .Spec .Command != nil {
165- migrationContainer . Command = * obj .Spec .Command
191+ migrationContainer [ "command" ] = * obj .Spec .Command
166192 }
167193 if obj .Spec .Args != nil {
168- migrationContainer . Args = * obj .Spec .Args
194+ migrationContainer [ "args" ] = * obj .Spec .Args
169195 }
170196 // TODO resources?
171197
172198 // 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
176202
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
180210
181211 // 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+ }
186219 }
187220 }
188- migrationPodSpec . InitContainers = initContainers
221+ migrationPodSpec [ "initContainers" ] = initContainers
189222
190223 // add labels to the job's pod template
191224 jobTemplateLabels := map [string ]string {"migrations" : obj .Name }
@@ -205,21 +238,22 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
205238 }
206239 }
207240
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 ,
222255 },
256+ "spec" : migrationPodSpec ,
223257 },
224258 }
225259 err = controllerutil .SetControllerReference (obj , migrationJob , ctx .Scheme )
@@ -233,13 +267,13 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
233267 if err != nil {
234268 return cu.Result {}, errors .Wrap (err , "error getting latest migrator for status" )
235269 }
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 )
238272 return cu.Result {}, nil
239273 }
240274
241275 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 )
243277 if err != nil {
244278 if kerrors .IsNotFound (err ) {
245279 // Try to start the migrations.
@@ -250,11 +284,11 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
250284 ctx .Conditions .SetfUnknown (comp .GetReadyCondition (), "CreateError" , "Error on create, possible conflict: %v" , err )
251285 return cu.Result {Requeue : true }, nil
252286 }
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 )
255289 return cu.Result {}, nil
256290 } 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 )
258292 }
259293 }
260294
@@ -263,15 +297,15 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
263297 if len (existingJob .Spec .Template .Spec .Containers ) > 0 {
264298 existingImage = existingJob .Spec .Template .Spec .Containers [0 ].Image
265299 }
266- if existingImage == "" || existingImage != migrationContainer . Image {
300+ if existingImage == "" || existingImage != migrationJobImage {
267301 // Old, stale migration. Remove it and try again.
268302 policy := metav1 .DeletePropagationForeground
269303 err = ctx .Client .Delete (ctx , existingJob , & client.DeleteOptions {PropagationPolicy : & policy })
270304 if err != nil {
271305 return cu.Result {}, errors .Wrapf (err , "error deleting stale migration job %s/%s" , existingJob .Namespace , existingJob .Name )
272306 }
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 )
275309 return cu.Result {RequeueAfter : 1 * time .Second , SkipRemaining : true }, nil
276310 }
277311
@@ -284,7 +318,7 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
284318 }
285319 ctx .Events .Eventf (obj , "Normal" , "MigrationsSucceeded" , "Migration job %s/%s using image %s succeeded" , existingJob .Namespace , existingJob .Name , existingImage )
286320 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
288322 return cu.Result {}, nil
289323 }
290324
@@ -301,27 +335,20 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
301335 return cu.Result {}, nil
302336}
303337
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 ) {
305339 namespace := obj .GetNamespace ()
306- owners := []client. Object {}
340+ owners := []* unstructured. Unstructured {}
307341 for {
308342 owners = append (owners , obj )
309343 ref := metav1 .GetControllerOfNoCopy (obj )
310344 if ref == nil {
311345 break
312346 }
313347 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 )
325352 if err != nil {
326353 // Gracefully handle objects we don't have access to
327354 if kerrors .IsForbidden (err ) {
@@ -337,34 +364,44 @@ func (_ *migrationsComponent) findOwners(ctx *cu.Context, obj client.Object) ([]
337364 return owners , nil
338365}
339366
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 )
351386 if err != nil {
352387 return nil
353388 }
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 {})
355392 } else {
356393 // TODO handle other WorkloadRef types
357394 return nil
358395 }
359396 }
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 {})
362399 default :
363400 return nil
364401 }
365402}
366403
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 ) {
368405 owners , err := comp .findOwners (ctx , obj )
369406 if err != nil {
370407 return nil , err
0 commit comments