@@ -17,6 +17,7 @@ limitations under the License.
17
17
package v1beta1
18
18
19
19
import (
20
+ "context"
20
21
"fmt"
21
22
"strings"
22
23
"time"
@@ -27,6 +28,7 @@ import (
27
28
"k8s.io/apimachinery/pkg/util/validation/field"
28
29
ctrl "sigs.k8s.io/controller-runtime"
29
30
"sigs.k8s.io/controller-runtime/pkg/webhook"
31
+ "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
30
32
31
33
"sigs.k8s.io/cluster-api/util/version"
32
34
)
@@ -35,14 +37,15 @@ const defaultNodeDeletionTimeout = 10 * time.Second
35
37
36
38
func (m * Machine ) SetupWebhookWithManager (mgr ctrl.Manager ) error {
37
39
return ctrl .NewWebhookManagedBy (mgr ).
38
- For (m ).
40
+ For (& Machine {}).
41
+ WithValidator (MachineValidator (mgr .GetScheme ())).
39
42
Complete ()
40
43
}
41
44
42
- // +kubebuilder:webhook:verbs=create;update,path=/validate-cluster-x-k8s-io-v1beta1-machine,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=cluster.x-k8s.io,resources=machines,versions=v1beta1,name=validation.machine.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1
43
- // +kubebuilder:webhook:verbs=create;update,path=/mutate-cluster-x-k8s-io-v1beta1-machine,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=cluster.x-k8s.io,resources=machines,versions=v1beta1,name=default.machine.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1
45
+ // +kubebuilder:webhook:verbs=create;update;delete ,path=/validate-cluster-x-k8s-io-v1beta1-machine,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=cluster.x-k8s.io,resources=machines,versions=v1beta1,name=validation.machine.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1
46
+ // +kubebuilder:webhook:verbs=create;update;delete ,path=/mutate-cluster-x-k8s-io-v1beta1-machine,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=cluster.x-k8s.io,resources=machines,versions=v1beta1,name=default.machine.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1
44
47
45
- var _ webhook.Validator = & Machine {}
48
+ var _ webhook.CustomValidator = & machineValidator {}
46
49
var _ webhook.Defaulter = & Machine {}
47
50
48
51
// Default implements webhook.Defaulter so a webhook will be registered for the type.
@@ -57,7 +60,10 @@ func (m *Machine) Default() {
57
60
}
58
61
59
62
if m .Spec .InfrastructureRef .Namespace == "" {
60
- m .Spec .InfrastructureRef .Namespace = m .Namespace
63
+ // Don't autofill namespace for MachinePool Machines since the infraRef will be populated by the MachinePool controller.
64
+ if ! isMachinePoolMachine (m ) {
65
+ m .Spec .InfrastructureRef .Namespace = m .Namespace
66
+ }
61
67
}
62
68
63
69
if m .Spec .Version != nil && ! strings .HasPrefix (* m .Spec .Version , "v" ) {
@@ -70,36 +76,96 @@ func (m *Machine) Default() {
70
76
}
71
77
}
72
78
79
+ // MachineValidator creates a new CustomValidator for Machines.
80
+ func MachineValidator (scheme * runtime.Scheme ) webhook.CustomValidator {
81
+ // Note: The error return parameter is always nil and will be dropped with the next CR release.
82
+ decoder , _ := admission .NewDecoder (scheme )
83
+ return & machineValidator {
84
+ decoder : decoder ,
85
+ }
86
+ }
87
+
88
+ // machineValidator implements a defaulting webhook for Machine.
89
+ type machineValidator struct {
90
+ decoder * admission.Decoder
91
+ }
92
+
73
93
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
74
- func (m * Machine ) ValidateCreate () error {
94
+ func (* machineValidator ) ValidateCreate (_ context.Context , obj runtime.Object ) error {
95
+ m , ok := obj .(* Machine )
96
+ if ! ok {
97
+ return apierrors .NewBadRequest (fmt .Sprintf ("expected a Machine but got a %T" , obj ))
98
+ }
99
+
75
100
return m .validate (nil )
76
101
}
77
102
78
103
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
79
- func (m * Machine ) ValidateUpdate (old runtime.Object ) error {
80
- oldM , ok := old .(* Machine )
104
+ func (* machineValidator ) ValidateUpdate (_ context.Context , oldObj runtime.Object , newObj runtime.Object ) error {
105
+ newM , ok := newObj .(* Machine )
106
+ if ! ok {
107
+ return apierrors .NewBadRequest (fmt .Sprintf ("expected a Machine but got a %T" , newObj ))
108
+ }
109
+
110
+ oldM , ok := oldObj .(* Machine )
81
111
if ! ok {
82
- return apierrors .NewBadRequest (fmt .Sprintf ("expected a Machine but got a %T" , old ))
112
+ return apierrors .NewBadRequest (fmt .Sprintf ("expected a Machine but got a %T" , oldObj ))
83
113
}
84
- return m .validate (oldM )
114
+ return newM .validate (oldM )
85
115
}
86
116
87
117
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type.
88
- func (m * Machine ) ValidateDelete () error {
118
+ func (* machineValidator ) ValidateDelete (ctx context.Context , obj runtime.Object ) error {
119
+ m , ok := obj .(* Machine )
120
+ if ! ok {
121
+ return apierrors .NewBadRequest (fmt .Sprintf ("expected a Machine but got a %T" , obj ))
122
+ }
123
+
124
+ req , err := admission .RequestFromContext (ctx )
125
+ if err != nil {
126
+ return apierrors .NewBadRequest (fmt .Sprintf ("expected a admission.Request inside context: %v" , err ))
127
+ }
128
+
129
+ // Fallback machines are placeholders for InfraMachinePools that do not support MachinePool Machines. These have
130
+ // no bootstrap or infrastructure data and cannot be deleted by users. They instead exist to provide a consistent
131
+ // user experience for MachinePool Machines.
132
+ if _ , isFallbackMachine := m .Labels [FallbackMachineLabel ]; isFallbackMachine {
133
+ if ! isControllerRequest (req ) {
134
+ return apierrors .NewBadRequest ("this Machine is a placeholder for InfraMachinePools that do not support MachinePool Machines and cannot be deleted by users, scale down the MachinePool instead to delete" )
135
+ }
136
+ }
137
+
89
138
return nil
90
139
}
91
140
141
+ func isControllerRequest (req admission.Request ) bool {
142
+ if req .UserInfo .Username == CAPIServiceAccountUsername {
143
+ return true
144
+ }
145
+
146
+ for _ , group := range req .UserInfo .Groups {
147
+ if group == ServiceAccountGroupName || group == CAPIServiceAccountGroupName {
148
+ return true
149
+ }
150
+ }
151
+
152
+ return false
153
+ }
154
+
92
155
func (m * Machine ) validate (old * Machine ) error {
93
156
var allErrs field.ErrorList
94
157
specPath := field .NewPath ("spec" )
95
158
if m .Spec .Bootstrap .ConfigRef == nil && m .Spec .Bootstrap .DataSecretName == nil {
96
- allErrs = append (
97
- allErrs ,
98
- field .Required (
99
- specPath .Child ("bootstrap" , "data" ),
100
- "expected either spec.bootstrap.dataSecretName or spec.bootstrap.configRef to be populated" ,
101
- ),
102
- )
159
+ // MachinePool Machines don't have a bootstrap configRef, so don't require it. The bootstrap config is instead owned by the MachinePool.
160
+ if ! isMachinePoolMachine (m ) {
161
+ allErrs = append (
162
+ allErrs ,
163
+ field .Required (
164
+ specPath .Child ("bootstrap" , "data" ),
165
+ "expected either spec.bootstrap.dataSecretName or spec.bootstrap.configRef to be populated" ,
166
+ ),
167
+ )
168
+ }
103
169
}
104
170
105
171
if m .Spec .Bootstrap .ConfigRef != nil && m .Spec .Bootstrap .ConfigRef .Namespace != m .Namespace {
@@ -113,15 +179,18 @@ func (m *Machine) validate(old *Machine) error {
113
179
)
114
180
}
115
181
116
- if m .Spec .InfrastructureRef .Namespace != m .Namespace {
117
- allErrs = append (
118
- allErrs ,
119
- field .Invalid (
120
- specPath .Child ("infrastructureRef" , "namespace" ),
121
- m .Spec .InfrastructureRef .Namespace ,
122
- "must match metadata.namespace" ,
123
- ),
124
- )
182
+ // InfraRef can be empty for MachinePool Machines so skip the check in that case.
183
+ if ! isMachinePoolMachine (m ) {
184
+ if m .Spec .InfrastructureRef .Namespace != m .Namespace {
185
+ allErrs = append (
186
+ allErrs ,
187
+ field .Invalid (
188
+ specPath .Child ("infrastructureRef" , "namespace" ),
189
+ m .Spec .InfrastructureRef .Namespace ,
190
+ "must match metadata.namespace" ,
191
+ ),
192
+ )
193
+ }
125
194
}
126
195
127
196
if old != nil && old .Spec .ClusterName != m .Spec .ClusterName {
@@ -142,3 +211,17 @@ func (m *Machine) validate(old *Machine) error {
142
211
}
143
212
return apierrors .NewInvalid (GroupVersion .WithKind ("Machine" ).GroupKind (), m .Name , allErrs )
144
213
}
214
+
215
+ func isMachinePoolMachine (m * Machine ) bool {
216
+ if m .OwnerReferences == nil {
217
+ return false
218
+ }
219
+
220
+ for _ , owner := range m .OwnerReferences {
221
+ if owner .Kind == "MachinePool" {
222
+ return true
223
+ }
224
+ }
225
+
226
+ return false
227
+ }
0 commit comments