Skip to content

Commit 4f21398

Browse files
added additional tests for new functionality
1 parent aa7247e commit 4f21398

3 files changed

Lines changed: 510 additions & 41 deletions

File tree

controllers/managedcloudprofile_controller.go

Lines changed: 103 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ const (
3131
CloudProfileAppliedConditionType string = "CloudProfileApplied"
3232
)
3333

34+
// OCIFactory provides a hook for constructing OCI sources. It defaults to
35+
// cloudprofilesync.NewOCI but can be overridden in tests to simulate errors
36+
var OCIFactory = func(params cloudprofilesync.OCIParams, insecure bool) (cloudprofilesync.Source, error) {
37+
return cloudprofilesync.NewOCI(params, insecure)
38+
}
39+
3440
type Reconciler struct {
3541
client.Client
3642
}
@@ -53,6 +59,10 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
5359
cloudProfile.Spec = CloudProfileSpecToGardener(&mcp.Spec.CloudProfile)
5460
errs := make([]error, 0)
5561
for _, updates := range mcp.Spec.MachineImageUpdates {
62+
// only call updater when an explicit source is provided
63+
if updates.Source.OCI == nil {
64+
continue
65+
}
5666
errs = append(errs, r.updateMachineImages(ctx, log, updates, &cloudProfile.Spec))
5767
}
5868
gardenerv1beta1.SetObjectDefaults_CloudProfile(&cloudProfile)
@@ -98,29 +108,48 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
98108
if err != nil {
99109
return ctrl.Result{}, err
100110
}
101-
oci, err := cloudprofilesync.NewOCI(cloudprofilesync.OCIParams{
111+
src, err := OCIFactory(cloudprofilesync.OCIParams{
102112
Registry: updates.Source.OCI.Registry,
103113
Repository: updates.Source.OCI.Repository,
104114
Username: updates.Source.OCI.Username,
105115
Password: string(password),
106116
Parallel: 1,
107117
}, updates.Source.OCI.Insecure)
108118
if err != nil {
119+
if patchErr := r.patchStatusAndCondition(ctx, &mcp, v1alpha1.FailedReconcileStatus, metav1.Condition{
120+
Type: CloudProfileAppliedConditionType,
121+
Status: metav1.ConditionFalse,
122+
ObservedGeneration: mcp.Generation,
123+
Reason: "GarbageCollectionFailed",
124+
Message: fmt.Sprintf("failed to initialize OCI source for garbage collection: %s", err),
125+
}); patchErr != nil {
126+
return ctrl.Result{}, fmt.Errorf("failed to patch ManagedCloudProfile status: %w (original error: %w)", patchErr, err)
127+
}
109128
return ctrl.Result{}, fmt.Errorf("failed to initialize OCI source for garbage collection: %w", err)
110129
}
111-
source = oci
130+
source = src
112131
default:
113132
continue
114133
}
115134

116135
versions, err := source.GetVersions(ctx)
117136
if err != nil {
137+
if patchErr := r.patchStatusAndCondition(ctx, &mcp, v1alpha1.FailedReconcileStatus, metav1.Condition{
138+
Type: CloudProfileAppliedConditionType,
139+
Status: metav1.ConditionFalse,
140+
ObservedGeneration: mcp.Generation,
141+
Reason: "GarbageCollectionFailed",
142+
Message: fmt.Sprintf("failed to list source versions for garbage collection: %s", err),
143+
}); patchErr != nil {
144+
return ctrl.Result{}, fmt.Errorf("failed to patch ManagedCloudProfile status: %w (original error: %w)", patchErr, err)
145+
}
118146
return ctrl.Result{}, fmt.Errorf("failed to list source versions for garbage collection: %w", err)
119147
}
120148

121149
referencedVersions := r.getReferencedVersions(ctx, mcp.Name, updates.ImageName)
122150

123151
cutoff := time.Now().Add(-updates.GarbageCollection.MaxAge.Duration)
152+
versionsToDelete := make([]string, 0)
124153
for _, v := range versions {
125154
if v.CreatedAt.IsZero() {
126155
continue
@@ -129,36 +158,56 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
129158
continue
130159
}
131160
if v.CreatedAt.Before(cutoff) {
132-
if err := r.deleteVersion(ctx, mcp.Name, updates.ImageName, v.Version); err != nil {
133-
if apierrors.IsInvalid(err) {
134-
log.V(1).Info("garbage collection validation failed, skipping", "image", updates.ImageName, "version", v.Version)
135-
continue
136-
}
137-
return ctrl.Result{}, fmt.Errorf("failed to delete image version: %w", err)
161+
versionsToDelete = append(versionsToDelete, v.Version)
162+
}
163+
}
164+
165+
if len(versionsToDelete) > 0 {
166+
if err := r.deleteVersion(ctx, mcp.Name, updates.ImageName, versionsToDelete); err != nil {
167+
if apierrors.IsInvalid(err) {
168+
log.V(1).Info("garbage collection validation failed, skipping", "image", updates.ImageName)
169+
continue
138170
}
139-
log.Info("deleted image version from CloudProfile", "image", updates.ImageName, "version", v.Version)
171+
if patchErr := r.patchStatusAndCondition(ctx, &mcp, v1alpha1.FailedReconcileStatus, metav1.Condition{
172+
Type: CloudProfileAppliedConditionType,
173+
Status: metav1.ConditionFalse,
174+
ObservedGeneration: mcp.Generation,
175+
Reason: "GarbageCollectionFailed",
176+
Message: fmt.Sprintf("failed to list source versions for garbage collection: %s", err),
177+
}); patchErr != nil {
178+
return ctrl.Result{}, fmt.Errorf("failed to patch ManagedCloudProfile status: %w (original error: %w)", patchErr, err)
179+
}
180+
return ctrl.Result{}, fmt.Errorf("failed to delete image versions: %w", err)
181+
}
182+
for _, v := range versionsToDelete {
183+
log.Info("deleted image version from CloudProfile", "image", updates.ImageName, "version", v)
140184
}
141185
}
142186
}
143187

144188
return ctrl.Result{RequeueAfter: 5 * time.Minute}, nil
145189
}
146190

147-
func (r *Reconciler) deleteVersion(ctx context.Context, cloudProfileName, imageName, version string) error {
191+
func (r *Reconciler) deleteVersion(ctx context.Context, cloudProfileName, imageName string, versions []string) error {
148192
var cp gardenerv1beta1.CloudProfile
149193
if err := r.Get(ctx, types.NamespacedName{Name: cloudProfileName}, &cp); err != nil {
150194
return err
151195
}
196+
197+
versionsSet := make(map[string]bool)
198+
for _, v := range versions {
199+
versionsSet[v] = true
200+
}
201+
152202
for i := range cp.Spec.MachineImages {
153203
if cp.Spec.MachineImages[i].Name != imageName {
154204
continue
155205
}
156206
newVersions := make([]gardenerv1beta1.MachineImageVersion, 0, len(cp.Spec.MachineImages[i].Versions))
157207
for _, mv := range cp.Spec.MachineImages[i].Versions {
158-
if mv.Version == version {
159-
continue
208+
if !versionsSet[mv.Version] {
209+
newVersions = append(newVersions, mv)
160210
}
161-
newVersions = append(newVersions, mv)
162211
}
163212
cp.Spec.MachineImages[i].Versions = newVersions
164213
}
@@ -171,10 +220,16 @@ func (r *Reconciler) deleteVersion(ctx context.Context, cloudProfileName, imageN
171220
}
172221
filtered := make([]providercfg.MachineImageVersion, 0, len(cfg.MachineImages[i].Versions))
173222
for _, mv := range cfg.MachineImages[i].Versions {
174-
if strings.HasSuffix(mv.Image, ":"+version) {
175-
continue
223+
found := false
224+
for _, version := range versions {
225+
if strings.HasSuffix(mv.Image, ":"+version) {
226+
found = true
227+
break
228+
}
229+
}
230+
if !found {
231+
filtered = append(filtered, mv)
176232
}
177-
filtered = append(filtered, mv)
178233
}
179234
cfg.MachineImages[i].Versions = filtered
180235
}
@@ -191,22 +246,40 @@ func (r *Reconciler) deleteVersion(ctx context.Context, cloudProfileName, imageN
191246
func (r *Reconciler) getReferencedVersions(ctx context.Context, cloudProfileName, imageName string) map[string]bool {
192247
referenced := make(map[string]bool)
193248

194-
shootList := &gardenerv1beta1.ShootList{}
195-
if err := r.List(ctx, shootList, client.InNamespace(metav1.NamespaceAll)); err != nil {
196-
return referenced
197-
}
198-
199-
for _, shoot := range shootList.Items {
200-
if shoot.Spec.CloudProfile == nil || shoot.Spec.CloudProfile.Name != cloudProfileName {
201-
continue
249+
var cp gardenerv1beta1.CloudProfile
250+
if err := r.Get(ctx, types.NamespacedName{Name: cloudProfileName}, &cp); err == nil {
251+
if cp.Spec.ProviderConfig != nil {
252+
var cfg providercfg.CloudProfileConfig
253+
if err := json.Unmarshal(cp.Spec.ProviderConfig.Raw, &cfg); err == nil {
254+
for _, img := range cfg.MachineImages {
255+
if img.Name != imageName {
256+
continue
257+
}
258+
for _, v := range img.Versions {
259+
if idx := strings.LastIndex(v.Image, ":"); idx != -1 {
260+
version := v.Image[idx+1:]
261+
referenced[version] = true
262+
}
263+
}
264+
}
265+
}
202266
}
267+
}
203268

204-
for _, worker := range shoot.Spec.Provider.Workers {
205-
if worker.Machine.Image == nil || worker.Machine.Image.Name != imageName {
269+
shootList := &gardenerv1beta1.ShootList{}
270+
if err := r.List(ctx, shootList, client.InNamespace(metav1.NamespaceAll)); err == nil {
271+
for _, shoot := range shootList.Items {
272+
if shoot.Spec.CloudProfile == nil || shoot.Spec.CloudProfile.Name != cloudProfileName {
206273
continue
207274
}
208-
if worker.Machine.Image.Version != nil {
209-
referenced[*worker.Machine.Image.Version] = true
275+
276+
for _, worker := range shoot.Spec.Provider.Workers {
277+
if worker.Machine.Image == nil || worker.Machine.Image.Name != imageName {
278+
continue
279+
}
280+
if worker.Machine.Image.Version != nil {
281+
referenced[*worker.Machine.Image.Version] = true
282+
}
210283
}
211284
}
212285
}
@@ -222,7 +295,7 @@ func (r *Reconciler) updateMachineImages(ctx context.Context, log logr.Logger, u
222295
if err != nil {
223296
return err
224297
}
225-
oci, err := cloudprofilesync.NewOCI(cloudprofilesync.OCIParams{
298+
src, err := OCIFactory(cloudprofilesync.OCIParams{
226299
Registry: update.Source.OCI.Registry,
227300
Repository: update.Source.OCI.Repository,
228301
Username: update.Source.OCI.Username,
@@ -232,7 +305,7 @@ func (r *Reconciler) updateMachineImages(ctx context.Context, log logr.Logger, u
232305
if err != nil {
233306
return fmt.Errorf("failed to initialize oci source: %w", err)
234307
}
235-
source = oci
308+
source = src
236309
default:
237310
return errors.New("no machine images source configured")
238311
}

0 commit comments

Comments
 (0)