Skip to content

Commit 6f5275a

Browse files
added more tests
1 parent 4f21398 commit 6f5275a

3 files changed

Lines changed: 263 additions & 1 deletion

File tree

controllers/managedcloudprofile_controller_test.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
})

crd/cloudprofilesync.cobaltcore.dev_managedcloudprofiles.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ spec:
532532
maxAge:
533533
description: |-
534534
MaxAge defines the maximum age for images to keep. Images older than
535-
now MaxAge are eligible for deletion.
535+
now - MaxAge are eligible for deletion.
536536
type: string
537537
type: object
538538
imageName:
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
apiVersion: apiextensions.k8s.io/v1
2+
kind: CustomResourceDefinition
3+
metadata:
4+
name: shoots.core.gardener.cloud
5+
spec:
6+
group: core.gardener.cloud
7+
versions:
8+
- name: v1beta1
9+
served: true
10+
storage: true
11+
schema:
12+
openAPIV3Schema:
13+
description: Shoot is the schema for the shoots API.
14+
type: object
15+
properties:
16+
apiVersion:
17+
description: |-
18+
APIVersion defines the versioned schema of this representation of an object.
19+
Servers should convert recognized schemas to the latest internal value, and
20+
may reject unrecognized values.
21+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
22+
type: string
23+
kind:
24+
description: |-
25+
Kind is a string value representing the REST resource this object represents.
26+
Servers may infer this from the endpoint the client submits requests to.
27+
Cannot be updated.
28+
In CamelCase.
29+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
30+
type: string
31+
metadata:
32+
type: object
33+
spec:
34+
description: Spec defines the desired state of the Shoot.
35+
type: object
36+
properties:
37+
cloudProfile:
38+
description: Reference to the CloudProfile used by the Shoot.
39+
type: object
40+
properties:
41+
name:
42+
description: Name of the CloudProfile.
43+
type: string
44+
provider:
45+
description: Provider-specific configuration.
46+
type: object
47+
properties:
48+
workers:
49+
description: Worker pools for the Shoot.
50+
type: array
51+
items:
52+
type: object
53+
properties:
54+
machine:
55+
description: Machine configuration for a worker pool.
56+
type: object
57+
properties:
58+
image:
59+
description: Image configuration for worker nodes.
60+
type: object
61+
properties:
62+
name:
63+
description: Machine image name.
64+
type: string
65+
version:
66+
description: Machine image version.
67+
type: string
68+
status:
69+
description: Status contains the current status of the Shoot.
70+
type: object
71+
scope: Namespaced
72+
names:
73+
plural: shoots
74+
singular: shoot
75+
kind: Shoot

0 commit comments

Comments
 (0)