Skip to content

Commit 49fa89f

Browse files
authored
fix: set ownership meta on addon resources to prevent deleting out-of-band addons (#41)
Signed-off-by: Artur Shad Nik <[email protected]>
1 parent 80854f8 commit 49fa89f

File tree

4 files changed

+113
-9
lines changed

4 files changed

+113
-9
lines changed

fleetconfig-controller/api/v1alpha1/constants.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package v1alpha1
22

3+
import "k8s.io/apimachinery/pkg/labels"
4+
35
const (
46
// FleetConfigFinalizer is the finalizer for FleetConfig cleanup.
57
FleetConfigFinalizer = "fleetconfig.open-cluster-management.io/cleanup"
@@ -58,6 +60,9 @@ const (
5860
const (
5961
// LabelManagedClusterType is the label key for the managed cluster type.
6062
LabelManagedClusterType = "fleetconfig.open-cluster-management.io/managedClusterType"
63+
64+
// LabelAddOnManagedBy is the label key for the lifecycle manager of an add-on resource.
65+
LabelAddOnManagedBy = "addon.open-cluster-management.io/managedBy"
6166
)
6267

6368
// Registration driver types
@@ -83,3 +88,12 @@ const (
8388

8489
// AllowedAddonURLSchemes are the URL schemes which can be used to provide manifests for configuring addons.
8590
var AllowedAddonURLSchemes = []string{"http", "https"}
91+
92+
var (
93+
// ManagedByLabels are labeles applies to resources to denote that fleetconfig-controller is managing the lifecycle.
94+
ManagedByLabels = map[string]string{
95+
LabelAddOnManagedBy: "fleetconfig-controller",
96+
}
97+
// ManagedBySelector is a label selector for filtering add-on resources managed fleetconfig-controller.
98+
ManagedBySelector = labels.SelectorFromSet(labels.Set(ManagedByLabels))
99+
)

fleetconfig-controller/api/v1alpha1/validation.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ func getManagedClusterAddOns(ctx context.Context) ([]addonv1alpha1.ManagedCluste
184184
if err != nil {
185185
return nil, fmt.Errorf("failed to create addon clientset: %w", err)
186186
}
187-
addonList, err := addonClientset.AddonV1alpha1().ManagedClusterAddOns(metav1.NamespaceAll).List(ctx, metav1.ListOptions{})
187+
addonList, err := addonClientset.AddonV1alpha1().ManagedClusterAddOns(metav1.NamespaceAll).List(ctx, metav1.ListOptions{LabelSelector: ManagedBySelector.String()})
188188
if err != nil {
189189
return nil, fmt.Errorf("failed to list ManagedClusterAddOns: %w", err)
190190
}

fleetconfig-controller/internal/controller/addon.go

Lines changed: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import (
88
"slices"
99
"strings"
1010

11+
"github.com/pkg/errors"
1112
corev1 "k8s.io/api/core/v1"
1213
kerrs "k8s.io/apimachinery/pkg/api/errors"
1314
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15+
1416
"k8s.io/apimachinery/pkg/types"
1517
addonapi "open-cluster-management.io/api/client/addon/clientset/versioned"
1618
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -19,7 +21,6 @@ import (
1921
"github.com/open-cluster-management-io/lab/fleetconfig-controller/api/v1alpha1"
2022
exec_utils "github.com/open-cluster-management-io/lab/fleetconfig-controller/internal/exec"
2123
"github.com/open-cluster-management-io/lab/fleetconfig-controller/internal/file"
22-
"github.com/pkg/errors"
2324
)
2425

2526
const (
@@ -34,7 +35,7 @@ func handleAddonConfig(ctx context.Context, kClient client.Client, addonC *addon
3435
logger.V(0).Info("handleAddOnConfig", "fleetconfig", fc.Name)
3536

3637
// get existing addons
37-
createdAddOns, err := addonC.AddonV1alpha1().AddOnTemplates().List(ctx, metav1.ListOptions{})
38+
createdAddOns, err := addonC.AddonV1alpha1().AddOnTemplates().List(ctx, metav1.ListOptions{LabelSelector: v1alpha1.ManagedBySelector.String()})
3839
if err != nil {
3940
return err
4041
}
@@ -80,15 +81,15 @@ func handleAddonConfig(ctx context.Context, kClient client.Client, addonC *addon
8081
return err
8182
}
8283

83-
err = handleAddonCreate(ctx, kClient, fc, addonsToCreate)
84+
err = handleAddonCreate(ctx, kClient, addonC, fc, addonsToCreate)
8485
if err != nil {
8586
return err
8687
}
8788

8889
return nil
8990
}
9091

91-
func handleAddonCreate(ctx context.Context, kClient client.Client, fc *v1alpha1.FleetConfig, addons []v1alpha1.AddOnConfig) error {
92+
func handleAddonCreate(ctx context.Context, kClient client.Client, addonC *addonapi.Clientset, fc *v1alpha1.FleetConfig, addons []v1alpha1.AddOnConfig) error {
9293
if len(addons) == 0 {
9394
return nil
9495
}
@@ -159,7 +160,61 @@ func handleAddonCreate(ctx context.Context, kClient client.Client, fc *v1alpha1.
159160
return fmt.Errorf("failed to create addon: %v, output: %s", err, string(out))
160161
}
161162
logger.V(0).Info("created addon", "AddOnTemplate", a.Name, "output", string(stdout))
163+
164+
// label created resources
165+
err = labelConfigurationResources(ctx, addonC, a)
166+
if err != nil {
167+
logger.V(0).Error(err, "failed to label addon resources", "addon", a.Name, "version", a.Version)
168+
}
169+
}
170+
return nil
171+
}
172+
173+
// labelConfigurationResources labels the AddOnTemplate and ClusterManagementAddOn resources created for an addon
174+
func labelConfigurationResources(ctx context.Context, addonC *addonapi.Clientset, addon v1alpha1.AddOnConfig) error {
175+
logger := log.FromContext(ctx)
176+
177+
// Label AddOnTemplate with a.Name-a.Version
178+
addonTemplateName := fmt.Sprintf("%s-%s", addon.Name, addon.Version)
179+
addonTemplate, err := addonC.AddonV1alpha1().AddOnTemplates().Get(ctx, addonTemplateName, metav1.GetOptions{})
180+
if err != nil {
181+
return fmt.Errorf("failed to get AddOnTemplate %s: %v", addonTemplateName, err)
182+
}
183+
184+
// Add managedBy label to AddOnTemplate
185+
if addonTemplate.Labels == nil {
186+
addonTemplate.Labels = make(map[string]string)
187+
}
188+
for k, v := range v1alpha1.ManagedByLabels {
189+
addonTemplate.Labels[k] = v
190+
}
191+
192+
_, err = addonC.AddonV1alpha1().AddOnTemplates().Update(ctx, addonTemplate, metav1.UpdateOptions{})
193+
if err != nil {
194+
return fmt.Errorf("failed to update AddOnTemplate %s with labels: %v", addonTemplateName, err)
195+
}
196+
logger.V(2).Info("labeled AddOnTemplate", "name", addonTemplateName, "label", v1alpha1.LabelAddOnManagedBy)
197+
198+
// Label ClusterManagementAddOn with a.Name
199+
clusterMgmtAddOn, err := addonC.AddonV1alpha1().ClusterManagementAddOns().Get(ctx, addon.Name, metav1.GetOptions{})
200+
if err != nil {
201+
return fmt.Errorf("failed to get ClusterManagementAddOn %s: %v", addon.Name, err)
202+
}
203+
204+
// Add managedBy label to ClusterManagementAddOn
205+
if clusterMgmtAddOn.Labels == nil {
206+
clusterMgmtAddOn.Labels = make(map[string]string)
207+
}
208+
for k, v := range v1alpha1.ManagedByLabels {
209+
clusterMgmtAddOn.Labels[k] = v
210+
}
211+
212+
_, err = addonC.AddonV1alpha1().ClusterManagementAddOns().Update(ctx, clusterMgmtAddOn, metav1.UpdateOptions{})
213+
if err != nil {
214+
return fmt.Errorf("failed to update ClusterManagementAddOn %s with labels: %v", addon.Name, err)
162215
}
216+
logger.V(2).Info("labeled ClusterManagementAddOn", "name", addon.Name, "label", v1alpha1.LabelAddOnManagedBy)
217+
163218
return nil
164219
}
165220

@@ -198,6 +253,8 @@ func handleAddonDelete(ctx context.Context, addonC *addonapi.Clientset, fc *v1al
198253
}
199254

200255
// check if there are any remaining addon templates for the same addon names as what was just deleted (different versions of the same addon)
256+
// dont use a label selector here - in case an addon with the same name was created out of band, and it is the last remaining version, we dont want
257+
// to delete its ClusterManagementAddOn
201258
allAddons, err := addonC.AddonV1alpha1().AddOnTemplates().List(ctx, metav1.ListOptions{})
202259
if err != nil && !kerrs.IsNotFound(err) {
203260
return fmt.Errorf("failed to clean up addons %v: %v", purgeList, err)
@@ -231,7 +288,7 @@ func handleAddonDelete(ctx context.Context, addonC *addonapi.Clientset, fc *v1al
231288
return nil
232289
}
233290

234-
func handleSpokeAddons(ctx context.Context, spoke v1alpha1.Spoke, fc *v1alpha1.FleetConfig) ([]string, error) {
291+
func handleSpokeAddons(ctx context.Context, addonC *addonapi.Clientset, spoke v1alpha1.Spoke, fc *v1alpha1.FleetConfig) ([]string, error) {
235292
var enabledAddons []string
236293

237294
addons := spoke.AddOns
@@ -294,7 +351,7 @@ func handleSpokeAddons(ctx context.Context, spoke v1alpha1.Spoke, fc *v1alpha1.F
294351
}
295352

296353
// Enable new addons and updated addons
297-
newEnabledAddons, err := handleAddonEnable(ctx, spoke.Name, addonsToEnable)
354+
newEnabledAddons, err := handleAddonEnable(ctx, addonC, spoke.Name, addonsToEnable)
298355
// even if an error is returned, any addon which was successfully enabled is tracked, so append before returning
299356
enabledAddons = append(enabledAddons, newEnabledAddons...)
300357
if err != nil {
@@ -304,7 +361,7 @@ func handleSpokeAddons(ctx context.Context, spoke v1alpha1.Spoke, fc *v1alpha1.F
304361
return enabledAddons, nil
305362
}
306363

307-
func handleAddonEnable(ctx context.Context, spokeName string, addons []v1alpha1.AddOn) ([]string, error) {
364+
func handleAddonEnable(ctx context.Context, addonC *addonapi.Clientset, spokeName string, addons []v1alpha1.AddOn) ([]string, error) {
308365
if len(addons) == 0 {
309366
return nil, nil
310367
}
@@ -343,6 +400,11 @@ func handleAddonEnable(ctx context.Context, spokeName string, addons []v1alpha1.
343400
enableErrs = append(enableErrs, fmt.Errorf("failed to enable addon: %v, output: %s", err, string(out)))
344401
continue
345402
}
403+
err = labelManagedClusterAddOn(ctx, addonC, spokeName, a.ConfigName)
404+
if err != nil {
405+
enableErrs = append(enableErrs, err)
406+
continue
407+
}
346408
enabledAddons = append(enabledAddons, a.ConfigName)
347409
logger.V(1).Info("enabled addon", "managedcluster", spokeName, "addon", a.ConfigName, "output", string(stdout))
348410
}
@@ -353,6 +415,30 @@ func handleAddonEnable(ctx context.Context, spokeName string, addons []v1alpha1.
353415
return enabledAddons, nil
354416
}
355417

418+
func labelManagedClusterAddOn(ctx context.Context, addonC *addonapi.Clientset, spokeName, addonName string) error {
419+
logger := log.FromContext(ctx)
420+
421+
mcao, err := addonC.AddonV1alpha1().ManagedClusterAddOns(spokeName).Get(ctx, addonName, metav1.GetOptions{})
422+
if err != nil {
423+
return fmt.Errorf("failed to get ManagedClusterAddOn %s for spoke %s: %v", addonName, spokeName, err)
424+
}
425+
426+
if mcao.Labels == nil {
427+
mcao.Labels = make(map[string]string)
428+
}
429+
for k, v := range v1alpha1.ManagedByLabels {
430+
mcao.Labels[k] = v
431+
}
432+
433+
_, err = addonC.AddonV1alpha1().ManagedClusterAddOns(spokeName).Update(ctx, mcao, metav1.UpdateOptions{})
434+
if err != nil {
435+
return fmt.Errorf("failed to update ManagedClusterAddOn %s for spoke %s with labels: %v", addonName, spokeName, err)
436+
}
437+
logger.V(2).Info("labeled ManagedClusterAddOn", "name", addonName, "spoke", spokeName, "label", v1alpha1.LabelAddOnManagedBy)
438+
439+
return nil
440+
}
441+
356442
func handleAddonDisable(ctx context.Context, spokeName string, addons []string) error {
357443
if len(addons) == 0 {
358444
return nil

fleetconfig-controller/internal/controller/spoke.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ func handleSpokes(ctx context.Context, kClient client.Client, fc *v1alpha1.Fleet
4949
if err != nil {
5050
return err
5151
}
52+
addonClient, err := common.AddOnClient(hubKubeconfig)
53+
if err != nil {
54+
return err
55+
}
5256

5357
// clean up deregistered spokes
5458
joinedSpokes := make([]v1alpha1.JoinedSpoke, 0)
@@ -165,7 +169,7 @@ func handleSpokes(ctx context.Context, kClient client.Client, fc *v1alpha1.Fleet
165169
}
166170
}
167171

168-
enabledAddons, err := handleSpokeAddons(ctx, spoke, fc)
172+
enabledAddons, err := handleSpokeAddons(ctx, addonClient, spoke, fc)
169173
allEnabledAddons[i] = enabledAddons
170174
if err != nil {
171175
msg := fmt.Sprintf("failed to enable addons for spoke cluster %s: %s", spoke.Name, err.Error())

0 commit comments

Comments
 (0)