@@ -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,63 @@ 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" ].([]map [string ]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+ if c [ "name" ].( string ) == obj .Spec .Container {
167+ templateContainer = c
147168 break
148169 }
149170 }
150- } else if len (templatePodSpec . Containers ) > 0 {
151- templateContainer = & templatePodSpec . Containers [0 ]
171+ } else if len (templatePodSpecContainers ) > 0 {
172+ templateContainer = templatePodSpecContainers [0 ]
152173 }
153174 if templateContainer == nil {
154175 // Welp, either nothing matched the name or somehow there are no containers.
155176 return cu.Result {}, errors .New ("no template container found" )
156177 }
157178
158179 // 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"
161186 if obj .Spec .Image != "" {
162- migrationContainer . Image = obj .Spec .Image
187+ migrationContainer [ "image" ] = obj .Spec .Image
163188 }
164189 if obj .Spec .Command != nil {
165- migrationContainer . Command = * obj .Spec .Command
190+ migrationContainer [ "command" ] = * obj .Spec .Command
166191 }
167192 if obj .Spec .Args != nil {
168- migrationContainer . Args = * obj .Spec .Args
193+ migrationContainer [ "args" ] = * obj .Spec .Args
169194 }
170195 // TODO resources?
171196
172197 // 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
176201
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
180209
181210 // 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-" ) {
185214 initContainers = append (initContainers , c )
186215 }
187216 }
188- migrationPodSpec . InitContainers = initContainers
217+ migrationPodSpec [ "initContainers" ] = initContainers
189218
190219 // add labels to the job's pod template
191220 jobTemplateLabels := map [string ]string {"migrations" : obj .Name }
@@ -205,21 +234,22 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
205234 }
206235 }
207236
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 ,
222251 },
252+ "spec" : migrationPodSpec ,
223253 },
224254 }
225255 err = controllerutil .SetControllerReference (obj , migrationJob , ctx .Scheme )
@@ -233,13 +263,13 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
233263 if err != nil {
234264 return cu.Result {}, errors .Wrap (err , "error getting latest migrator for status" )
235265 }
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 )
238268 return cu.Result {}, nil
239269 }
240270
241271 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 )
243273 if err != nil {
244274 if kerrors .IsNotFound (err ) {
245275 // Try to start the migrations.
@@ -250,11 +280,11 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
250280 ctx .Conditions .SetfUnknown (comp .GetReadyCondition (), "CreateError" , "Error on create, possible conflict: %v" , err )
251281 return cu.Result {Requeue : true }, nil
252282 }
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 )
255285 return cu.Result {}, nil
256286 } 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 )
258288 }
259289 }
260290
@@ -263,15 +293,15 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
263293 if len (existingJob .Spec .Template .Spec .Containers ) > 0 {
264294 existingImage = existingJob .Spec .Template .Spec .Containers [0 ].Image
265295 }
266- if existingImage == "" || existingImage != migrationContainer . Image {
296+ if existingImage == "" || existingImage != migrationJobImage {
267297 // Old, stale migration. Remove it and try again.
268298 policy := metav1 .DeletePropagationForeground
269299 err = ctx .Client .Delete (ctx , existingJob , & client.DeleteOptions {PropagationPolicy : & policy })
270300 if err != nil {
271301 return cu.Result {}, errors .Wrapf (err , "error deleting stale migration job %s/%s" , existingJob .Namespace , existingJob .Name )
272302 }
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 )
275305 return cu.Result {RequeueAfter : 1 * time .Second , SkipRemaining : true }, nil
276306 }
277307
@@ -284,7 +314,7 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
284314 }
285315 ctx .Events .Eventf (obj , "Normal" , "MigrationsSucceeded" , "Migration job %s/%s using image %s succeeded" , existingJob .Namespace , existingJob .Name , existingImage )
286316 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
288318 return cu.Result {}, nil
289319 }
290320
@@ -301,27 +331,20 @@ func (comp *migrationsComponent) Reconcile(ctx *cu.Context) (cu.Result, error) {
301331 return cu.Result {}, nil
302332}
303333
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 ) {
305335 namespace := obj .GetNamespace ()
306- owners := []client. Object {}
336+ owners := []* unstructured. Unstructured {}
307337 for {
308338 owners = append (owners , obj )
309339 ref := metav1 .GetControllerOfNoCopy (obj )
310340 if ref == nil {
311341 break
312342 }
313343 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 )
325348 if err != nil {
326349 // Gracefully handle objects we don't have access to
327350 if kerrors .IsForbidden (err ) {
@@ -337,34 +360,44 @@ func (_ *migrationsComponent) findOwners(ctx *cu.Context, obj client.Object) ([]
337360 return owners , nil
338361}
339362
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 )
351382 if err != nil {
352383 return nil
353384 }
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 {})
355388 } else {
356389 // TODO handle other WorkloadRef types
357390 return nil
358391 }
359392 }
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 {})
362395 default :
363396 return nil
364397 }
365398}
366399
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 ) {
368401 owners , err := comp .findOwners (ctx , obj )
369402 if err != nil {
370403 return nil , err
0 commit comments