@@ -393,6 +393,99 @@ var _ = Describe("The ManagedCloudProfile reconciler", func() {
393393 Expect (k8sClient .Delete (ctx , & cloudProfile )).To (Succeed ())
394394 })
395395
396+ It ("preserves machine image versions referenced by Shoot workers" , func (ctx SpecContext ) {
397+ var cloudProfile gardenerv1beta1.CloudProfile
398+ cloudProfile .Name = "test-gc-shoot-preserve"
399+ cloudProfile .Spec .Regions = []gardenerv1beta1.Region {{Name : "foo" }}
400+ cloudProfile .Spec .MachineTypes = []gardenerv1beta1.MachineType {{Name : "baz" }}
401+ cloudProfile .Spec .MachineImages = []gardenerv1beta1.MachineImage {
402+ {
403+ Name : "shoot-preserve-image" ,
404+ Versions : []gardenerv1beta1.MachineImageVersion {
405+ {ExpirableVersion : gardenerv1beta1.ExpirableVersion {Version : "1.0.0" }, Architectures : []string {"amd64" }},
406+ {ExpirableVersion : gardenerv1beta1.ExpirableVersion {Version : "1.0.1+abc" }, Architectures : []string {"amd64" }},
407+ },
408+ },
409+ }
410+ Expect (k8sClient .Create (ctx , & cloudProfile )).To (Succeed ())
411+
412+ // Create a Shoot that references version "1.0.0"
413+ var shoot gardenerv1beta1.Shoot
414+ shoot .Name = "test-shoot"
415+ shoot .Namespace = metav1 .NamespaceDefault
416+ shoot .Spec .CloudProfile = & gardenerv1beta1.CloudProfileReference {Name : cloudProfile .Name }
417+ shoot .Spec .Provider .Workers = []gardenerv1beta1.Worker {
418+ {
419+ Name : "worker1" ,
420+ Machine : gardenerv1beta1.Machine {
421+ Image : & gardenerv1beta1.ShootMachineImage {
422+ Name : "shoot-preserve-image" ,
423+ Version : ptr .To ("1.0.0" ),
424+ },
425+ },
426+ },
427+ }
428+ Expect (k8sClient .Create (ctx , & shoot )).To (Succeed ())
429+
430+ var mcp v1alpha1.ManagedCloudProfile
431+ mcp .Name = "test-gc-shoot-preserve"
432+ mcp .Spec .CloudProfile = v1alpha1.CloudProfileSpec {
433+ Regions : []gardenerv1beta1.Region {{Name : "foo" }},
434+ MachineImages : []gardenerv1beta1.MachineImage {
435+ {
436+ Name : "shoot-preserve-image" ,
437+ Versions : []gardenerv1beta1.MachineImageVersion {
438+ {ExpirableVersion : gardenerv1beta1.ExpirableVersion {Version : "1.0.0" }, Architectures : []string {"amd64" }},
439+ {ExpirableVersion : gardenerv1beta1.ExpirableVersion {Version : "1.0.1+abc" }, Architectures : []string {"amd64" }},
440+ },
441+ },
442+ },
443+ MachineTypes : []gardenerv1beta1.MachineType {{Name : "baz" }},
444+ }
445+ mcp .Spec .MachineImageUpdates = []v1alpha1.MachineImageUpdate {
446+ {
447+ ImageName : "shoot-preserve-image" ,
448+ Source : v1alpha1.MachineImageUpdateSource {
449+ OCI : & v1alpha1.MachineImageUpdateSourceOCI {
450+ Registry : registryAddr ,
451+ Repository : "repo" ,
452+ Insecure : true ,
453+ },
454+ },
455+ GarbageCollection : & v1alpha1.GarbageCollectionConfig {
456+ Enabled : true ,
457+ MaxAge : metav1.Duration {Duration : 0 }, // Treat all versions as old
458+ },
459+ },
460+ }
461+ Expect (k8sClient .Create (ctx , & mcp )).To (Succeed ())
462+
463+ Eventually (func (g Gomega ) v1alpha1.ReconcileStatus {
464+ g .Expect (k8sClient .Get (ctx , client .ObjectKeyFromObject (& mcp ), & mcp )).To (Succeed ())
465+ return mcp .Status .Status
466+ }).Should (Equal (v1alpha1 .SucceededReconcileStatus ))
467+
468+ // Verify that "1.0.0" is preserved because it's referenced by the Shoot, but "1.0.1+abc" is deleted
469+ Eventually (func (g Gomega ) []string {
470+ g .Expect (k8sClient .Get (ctx , client .ObjectKeyFromObject (& cloudProfile ), & cloudProfile )).To (Succeed ())
471+ if len (cloudProfile .Spec .MachineImages ) == 0 {
472+ return []string {}
473+ }
474+ versions := []string {}
475+ for _ , v := range cloudProfile .Spec .MachineImages [0 ].Versions {
476+ versions = append (versions , v .Version )
477+ }
478+ return versions
479+ }).Should (And (
480+ ContainElement ("1.0.0" ), // Preserved due to Shoot reference
481+ Not (ContainElement ("1.0.1+abc" )), // Deleted as it's old and not referenced
482+ ))
483+
484+ Expect (k8sClient .Delete (ctx , & mcp )).To (Succeed ())
485+ Expect (k8sClient .Delete (ctx , & cloudProfile )).To (Succeed ())
486+ Expect (k8sClient .Delete (ctx , & shoot )).To (Succeed ())
487+ })
488+
396489 It ("handles missing credential for GC OCI source" , func (ctx SpecContext ) {
397490 var mcp v1alpha1.ManagedCloudProfile
398491 mcp .Name = "test-gc-cred-error"
@@ -609,4 +702,98 @@ var _ = Describe("The ManagedCloudProfile reconciler", func() {
609702 Expect (k8sClient .Delete (ctx , & cloudProfile )).To (Succeed ())
610703 })
611704
705+ It ("updates ProviderConfig when garbage collecting machine image versions" , func (ctx SpecContext ) {
706+ var cloudProfile gardenerv1beta1.CloudProfile
707+ cloudProfile .Name = "test-gc-provider-config"
708+ cloudProfile .Spec .Regions = []gardenerv1beta1.Region {{Name : "foo" }}
709+ cloudProfile .Spec .MachineTypes = []gardenerv1beta1.MachineType {{Name : "baz" }}
710+ cloudProfile .Spec .MachineImages = []gardenerv1beta1.MachineImage {
711+ {
712+ Name : "provider-config-image" ,
713+ Versions : []gardenerv1beta1.MachineImageVersion {
714+ {ExpirableVersion : gardenerv1beta1.ExpirableVersion {Version : "1.0.0" }, Architectures : []string {"amd64" }},
715+ {ExpirableVersion : gardenerv1beta1.ExpirableVersion {Version : "1.0.1+abc" }, Architectures : []string {"amd64" }},
716+ },
717+ },
718+ }
719+
720+ var cfg providercfg.CloudProfileConfig
721+ cfg .MachineImages = []providercfg.MachineImages {
722+ {
723+ Name : "provider-config-image" ,
724+ Versions : []providercfg.MachineImageVersion {
725+ {Image : "repo/provider-config-image:1.0.0" },
726+ {Image : "repo/provider-config-image:1.0.1+abc" },
727+ },
728+ },
729+ }
730+ raw , err := json .Marshal (cfg )
731+ Expect (err ).To (Succeed ())
732+ cloudProfile .Spec .ProviderConfig = & runtime.RawExtension {Raw : raw }
733+ Expect (k8sClient .Create (ctx , & cloudProfile )).To (Succeed ())
734+
735+ var mcp v1alpha1.ManagedCloudProfile
736+ mcp .Name = "test-gc-provider-config"
737+ mcp .Spec .CloudProfile = v1alpha1.CloudProfileSpec {
738+ Regions : []gardenerv1beta1.Region {{Name : "foo" }},
739+ MachineImages : []gardenerv1beta1.MachineImage {
740+ {
741+ Name : "provider-config-image" ,
742+ Versions : []gardenerv1beta1.MachineImageVersion {
743+ {ExpirableVersion : gardenerv1beta1.ExpirableVersion {Version : "1.0.0" }, Architectures : []string {"amd64" }},
744+ {ExpirableVersion : gardenerv1beta1.ExpirableVersion {Version : "1.0.1+abc" }, Architectures : []string {"amd64" }},
745+ },
746+ },
747+ },
748+ MachineTypes : []gardenerv1beta1.MachineType {{Name : "baz" }},
749+ }
750+ mcp .Spec .MachineImageUpdates = []v1alpha1.MachineImageUpdate {
751+ {
752+ ImageName : "provider-config-image" ,
753+ Source : v1alpha1.MachineImageUpdateSource {
754+ OCI : & v1alpha1.MachineImageUpdateSourceOCI {
755+ Registry : registryAddr ,
756+ Repository : "repo" ,
757+ Insecure : true ,
758+ },
759+ },
760+ GarbageCollection : & v1alpha1.GarbageCollectionConfig {
761+ Enabled : true ,
762+ MaxAge : metav1.Duration {Duration : 0 }, // Treat all versions as old
763+ },
764+ },
765+ }
766+ Expect (k8sClient .Create (ctx , & mcp )).To (Succeed ())
767+
768+ Eventually (func (g Gomega ) v1alpha1.ReconcileStatus {
769+ g .Expect (k8sClient .Get (ctx , client .ObjectKeyFromObject (& mcp ), & mcp )).To (Succeed ())
770+ return mcp .Status .Status
771+ }).Should (Equal (v1alpha1 .SucceededReconcileStatus ))
772+
773+ // Verify that ProviderConfig was updated to remove deleted versions
774+ Eventually (func (g Gomega ) []string {
775+ g .Expect (k8sClient .Get (ctx , client .ObjectKeyFromObject (& cloudProfile ), & cloudProfile )).To (Succeed ())
776+ if cloudProfile .Spec .ProviderConfig == nil {
777+ return []string {}
778+ }
779+ var updatedCfg providercfg.CloudProfileConfig
780+ if err := json .Unmarshal (cloudProfile .Spec .ProviderConfig .Raw , & updatedCfg ); err != nil {
781+ return []string {}
782+ }
783+ for _ , img := range updatedCfg .MachineImages {
784+ if img .Name == "provider-config-image" {
785+ images := make ([]string , len (img .Versions ))
786+ for i , v := range img .Versions {
787+ images [i ] = v .Image
788+ }
789+ return images
790+ }
791+ }
792+ return []string {}
793+ }).Should (BeEmpty ()) // All versions should be deleted from ProviderConfig
794+
795+ Expect (k8sClient .Delete (ctx , & mcp )).To (Succeed ())
796+ Expect (k8sClient .Delete (ctx , & cloudProfile )).To (Succeed ())
797+ })
798+
612799})
0 commit comments