Skip to content

Commit c135e85

Browse files
committed
Add support to disable CAPZ components through a manager flag
1 parent 3a9eb5b commit c135e85

File tree

4 files changed

+190
-18
lines changed

4 files changed

+190
-18
lines changed

api/v1beta1/types.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,3 +1245,23 @@ const (
12451245
// AKSAssignedIdentityUserAssigned ...
12461246
AKSAssignedIdentityUserAssigned AKSAssignedIdentity = "UserAssigned"
12471247
)
1248+
1249+
// DisableComponent defines a component to be disabled in CAPZ such as a controller or webhook.
1250+
// +kubebuilder:validation:Enum=DisableASOSecretController;DisableAzureJSONMachineController
1251+
type DisableComponent string
1252+
1253+
// NOTE: when adding a new DisableComponent, please also add it to the ValidDisableableComponents map.
1254+
const (
1255+
// DisableASOController disables the ASOSecretController from being deployed.
1256+
DisableASOSecretController DisableComponent = "DisableASOSecretController"
1257+
1258+
// DisableAzureJSONMachineController disables the AzureJSONMachineController from being deployed.
1259+
DisableAzureJSONMachineController DisableComponent = "DisableAzureJSONMachineController"
1260+
)
1261+
1262+
// ValidDisableableComponents is a map of valid disableable components used to quickly validate whether a component is
1263+
// valid or not.
1264+
var ValidDisableableComponents = map[DisableComponent]struct{}{
1265+
DisableASOSecretController: {},
1266+
DisableAzureJSONMachineController: {},
1267+
}

main.go

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import (
6767
"sigs.k8s.io/cluster-api-provider-azure/feature"
6868
"sigs.k8s.io/cluster-api-provider-azure/pkg/coalescing"
6969
"sigs.k8s.io/cluster-api-provider-azure/pkg/ot"
70+
"sigs.k8s.io/cluster-api-provider-azure/util/components"
7071
"sigs.k8s.io/cluster-api-provider-azure/util/reconciler"
7172
"sigs.k8s.io/cluster-api-provider-azure/version"
7273
)
@@ -120,6 +121,7 @@ var (
120121
managerOptions = flags.ManagerOptions{}
121122
timeouts reconciler.Timeouts
122123
enableTracing bool
124+
disableControllersOrWebhooks []string
123125
)
124126

125127
// InitFlags initializes all command-line flags.
@@ -266,6 +268,12 @@ func InitFlags(fs *pflag.FlagSet) {
266268
"(Deprecated) Provide fully qualified GVK string to override default kubeadm config watch source, in the form of Kind.version.group (default: KubeadmConfig.v1beta1.bootstrap.cluster.x-k8s.io)",
267269
)
268270

271+
fs.StringSliceVar(&disableControllersOrWebhooks,
272+
"disable-controllers-or-webhooks",
273+
[]string{},
274+
"Comma-separated list of controllers or webhooks to disable. The list can contain the following values: DisableASOSecretController,DisableAzureJSONMachineController",
275+
)
276+
269277
flags.AddManagerOptions(fs, &managerOptions)
270278

271279
feature.MutableGates.AddFlag(fs)
@@ -308,6 +316,16 @@ func main() {
308316
}
309317
}
310318

319+
// Validate valid disable components were passed in the flag
320+
if len(disableControllersOrWebhooks) > 0 {
321+
for _, component := range disableControllersOrWebhooks {
322+
if ok := components.IsValidDisableComponent(component); !ok {
323+
setupLog.Error(fmt.Errorf("invalid disable-controllers-or-webhooks value %s", component), "Invalid argument")
324+
os.Exit(1)
325+
}
326+
}
327+
}
328+
311329
restConfig := ctrl.GetConfigOrDie()
312330
restConfig.UserAgent = "cluster-api-provider-azure-manager"
313331
mgr, err := ctrl.NewManager(restConfig, ctrl.Options{
@@ -420,26 +438,30 @@ func registerControllers(ctx context.Context, mgr manager.Manager) {
420438
os.Exit(1)
421439
}
422440

423-
if err := (&controllers.AzureJSONMachineReconciler{
424-
Client: mgr.GetClient(),
425-
Recorder: mgr.GetEventRecorderFor("azurejsonmachine-reconciler"),
426-
Timeouts: timeouts,
427-
WatchFilterValue: watchFilterValue,
428-
CredentialCache: credCache,
429-
}).SetupWithManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: azureMachineConcurrency, SkipNameValidation: ptr.To(true)}); err != nil {
430-
setupLog.Error(err, "unable to create controller", "controller", "AzureJSONMachine")
431-
os.Exit(1)
441+
if !components.IsComponentDisabled(disableControllersOrWebhooks, infrav1.DisableAzureJSONMachineController) {
442+
if err := (&controllers.AzureJSONMachineReconciler{
443+
Client: mgr.GetClient(),
444+
Recorder: mgr.GetEventRecorderFor("azurejsonmachine-reconciler"),
445+
Timeouts: timeouts,
446+
WatchFilterValue: watchFilterValue,
447+
CredentialCache: credCache,
448+
}).SetupWithManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: azureMachineConcurrency, SkipNameValidation: ptr.To(true)}); err != nil {
449+
setupLog.Error(err, "unable to create controller", "controller", "AzureJSONMachine")
450+
os.Exit(1)
451+
}
432452
}
433453

434-
if err := (&controllers.ASOSecretReconciler{
435-
Client: mgr.GetClient(),
436-
Recorder: mgr.GetEventRecorderFor("asosecret-reconciler"),
437-
Timeouts: timeouts,
438-
WatchFilterValue: watchFilterValue,
439-
CredentialCache: credCache,
440-
}).SetupWithManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: azureClusterConcurrency}); err != nil {
441-
setupLog.Error(err, "unable to create controller", "controller", "ASOSecret")
442-
os.Exit(1)
454+
if !components.IsComponentDisabled(disableControllersOrWebhooks, infrav1.DisableASOSecretController) {
455+
if err := (&controllers.ASOSecretReconciler{
456+
Client: mgr.GetClient(),
457+
Recorder: mgr.GetEventRecorderFor("asosecret-reconciler"),
458+
Timeouts: timeouts,
459+
WatchFilterValue: watchFilterValue,
460+
CredentialCache: credCache,
461+
}).SetupWithManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: azureClusterConcurrency}); err != nil {
462+
setupLog.Error(err, "unable to create controller", "controller", "ASOSecret")
463+
os.Exit(1)
464+
}
443465
}
444466

445467
// just use CAPI MachinePool feature flag rather than create a new one
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package components
18+
19+
import (
20+
"slices"
21+
22+
infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
23+
)
24+
25+
// IsValidDisableComponent validates if the provided value is a valid disable component by checking if the value exists
26+
// in the infrav1.ValidDisableableComponents map.
27+
func IsValidDisableComponent(value string) bool {
28+
_, ok := infrav1.ValidDisableableComponents[infrav1.DisableComponent(value)]
29+
return ok
30+
}
31+
32+
// IsComponentDisabled checks if the provided component is in the list of disabled components.
33+
func IsComponentDisabled(disabledComponents []string, component infrav1.DisableComponent) bool {
34+
return slices.Contains(disabledComponents, string(component))
35+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package components
18+
19+
import (
20+
"testing"
21+
22+
. "github.com/onsi/gomega"
23+
24+
infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
25+
)
26+
27+
func TestIsValidDisableComponent(t *testing.T) {
28+
g := NewWithT(t)
29+
30+
testCases := []struct {
31+
name string
32+
value string
33+
expected bool
34+
}{
35+
{
36+
name: "Valid component",
37+
value: string(infrav1.DisableASOSecretController),
38+
expected: true,
39+
},
40+
{
41+
name: "Invalid component",
42+
value: "InvalidComponent",
43+
expected: false,
44+
},
45+
{
46+
name: "Empty string",
47+
value: "",
48+
expected: false,
49+
},
50+
}
51+
52+
for _, tc := range testCases {
53+
t.Run(tc.name, func(t *testing.T) {
54+
result := IsValidDisableComponent(tc.value)
55+
g.Expect(result).To(Equal(tc.expected))
56+
})
57+
}
58+
}
59+
60+
func TestIsComponentDisabled(t *testing.T) {
61+
g := NewGomegaWithT(t)
62+
63+
testCases := []struct {
64+
name string
65+
disabledComponents []string
66+
component infrav1.DisableComponent
67+
expectedResult bool
68+
}{
69+
{
70+
name: "When DisableASOSecretController is in the list, expect true",
71+
disabledComponents: []string{"DisableASOSecretController", "component2"},
72+
component: infrav1.DisableASOSecretController,
73+
expectedResult: true,
74+
},
75+
{
76+
name: "When DisableASOSecretController is not in the list, expect false",
77+
disabledComponents: []string{"component", "component2"},
78+
component: infrav1.DisableASOSecretController,
79+
expectedResult: false,
80+
},
81+
{
82+
name: "When the list is empty, expect false",
83+
disabledComponents: []string{},
84+
component: infrav1.DisableComponent("component"),
85+
expectedResult: false,
86+
},
87+
}
88+
89+
for _, tc := range testCases {
90+
t.Run(tc.name, func(t *testing.T) {
91+
result := IsComponentDisabled(tc.disabledComponents, tc.component)
92+
g.Expect(result).To(Equal(tc.expectedResult))
93+
})
94+
}
95+
}

0 commit comments

Comments
 (0)