Skip to content

Commit 44ad712

Browse files
authored
Merge pull request #5758 from k8s-infra-cherrypick-robot/cherry-pick-5552-to-release-1.20
[release-1.20] Add support to disable CAPZ components through a manager flag
2 parents 1c132d6 + 62de68f commit 44ad712

File tree

5 files changed

+200
-18
lines changed

5 files changed

+200
-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+
// DisableASOSecretController 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+
}

docs/book/src/self-managed/externally-managed-azure-infrastructure.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,13 @@ If the `AzureCluster` resource includes a "cluster.x-k8s.io/managed-by" annotati
77
This is useful for scenarios where a different persona is managing the cluster infrastructure out-of-band while still wanting to use CAPI for automated machine management.
88

99
You should only use this feature if your cluster infrastructure lifecycle management has constraints that the reference implementation does not support. See [user stories](https://github.com/kubernetes-sigs/cluster-api/blob/10d89ceca938e4d3d94a1d1c2b60515bcdf39829/docs/proposals/20210203-externally-managed-cluster-infrastructure.md#user-stories) for more details.
10+
11+
## Disabling Specific Component Reconciliation
12+
Some controllers/webhooks may not be necessary to run in an externally managed cluster infrastructure scenario. These
13+
controllers/webhooks can be disabled through a flag on the manager called `disable-controllers-or-webhooks`. This flag
14+
accepts a comma separated list of values.
15+
16+
Currently, these are the only accepted values:
17+
1. `DisableASOSecretController` - disables the ASOSecretController from being deployed
18+
2. `DisableAzureJSONMachineController` - disables the AzureJSONMachineController from being deployed
19+

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)