Skip to content

Commit c70120e

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

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
@@ -66,6 +66,7 @@ import (
6666
"sigs.k8s.io/cluster-api-provider-azure/feature"
6767
"sigs.k8s.io/cluster-api-provider-azure/pkg/coalescing"
6868
"sigs.k8s.io/cluster-api-provider-azure/pkg/ot"
69+
"sigs.k8s.io/cluster-api-provider-azure/util/components"
6970
"sigs.k8s.io/cluster-api-provider-azure/util/reconciler"
7071
"sigs.k8s.io/cluster-api-provider-azure/version"
7172
)
@@ -118,6 +119,7 @@ var (
118119
managerOptions = flags.ManagerOptions{}
119120
timeouts reconciler.Timeouts
120121
enableTracing bool
122+
disableControllersOrWebhooks []string
121123
)
122124

123125
// InitFlags initializes all command-line flags.
@@ -264,6 +266,12 @@ func InitFlags(fs *pflag.FlagSet) {
264266
"(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)",
265267
)
266268

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

269277
feature.MutableGates.AddFlag(fs)
@@ -306,6 +314,16 @@ func main() {
306314
}
307315
}
308316

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

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

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

443465
// 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)