@@ -17,7 +17,9 @@ limitations under the License.
17
17
package v1beta1
18
18
19
19
import (
20
+ "context"
20
21
"fmt"
22
+ "os"
21
23
"strings"
22
24
"time"
23
25
@@ -36,14 +38,15 @@ const defaultNodeDeletionTimeout = 10 * time.Second
36
38
37
39
func (m * Machine ) SetupWebhookWithManager (mgr ctrl.Manager ) error {
38
40
return ctrl .NewWebhookManagedBy (mgr ).
39
- For (m ).
41
+ For (& Machine {}).
42
+ WithValidator (MachineValidator (mgr .GetScheme ())).
40
43
Complete ()
41
44
}
42
45
43
- // +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
46
+ // +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
44
47
// +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
48
46
- var _ webhook.Validator = & Machine {}
49
+ var _ webhook.CustomValidator = & machineValidator {}
47
50
var _ webhook.Defaulter = & Machine {}
48
51
49
52
// Default implements webhook.Defaulter so a webhook will be registered for the type.
@@ -58,7 +61,10 @@ func (m *Machine) Default() {
58
61
}
59
62
60
63
if m .Spec .InfrastructureRef .Namespace == "" {
61
- m .Spec .InfrastructureRef .Namespace = m .Namespace
64
+ // Don't autofill namespace for MachinePool Machines since the infraRef will be populated by the MachinePool controller.
65
+ if ! isMachinePoolMachine (m ) {
66
+ m .Spec .InfrastructureRef .Namespace = m .Namespace
67
+ }
62
68
}
63
69
64
70
if m .Spec .Version != nil && ! strings .HasPrefix (* m .Spec .Version , "v" ) {
@@ -71,36 +77,94 @@ func (m *Machine) Default() {
71
77
}
72
78
}
73
79
74
- // ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
75
- func (m * Machine ) ValidateCreate () (admission.Warnings , error ) {
80
+ // MachineValidator creates a new CustomValidator for Machines.
81
+ func MachineValidator (_ * runtime.Scheme ) webhook.CustomValidator {
82
+ return & machineValidator {}
83
+ }
84
+
85
+ // machineValidator implements a defaulting webhook for Machine.
86
+ type machineValidator struct {}
87
+
88
+ // // ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
89
+ // func (m *Machine) ValidateCreate() (admission.Warnings, error) {
90
+ // return nil, m.validate(nil)
91
+ // }
92
+
93
+ // // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
94
+ // func (m *Machine) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
95
+ // oldM, ok := old.(*Machine)
96
+ // if !ok {
97
+ // return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a Machine but got a %T", old))
98
+ // }
99
+ // return nil, m.validate(oldM)
100
+ // }
101
+
102
+ // // ValidateDelete implements webhook.Validator so a webhook will be registered for the type.
103
+ //
104
+ // func (m *Machine) ValidateDelete() (admission.Warnings, error) {
105
+ // return nil, nil
106
+ func (* machineValidator ) ValidateCreate (_ context.Context , obj runtime.Object ) (admission.Warnings , error ) {
107
+ m , ok := obj .(* Machine )
108
+ if ! ok {
109
+ return nil , apierrors .NewBadRequest (fmt .Sprintf ("expected a Machine but got a %T" , obj ))
110
+ }
111
+
76
112
return nil , m .validate (nil )
77
113
}
78
114
79
115
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
80
- func (m * Machine ) ValidateUpdate (old runtime.Object ) (admission.Warnings , error ) {
81
- oldM , ok := old .(* Machine )
116
+ func (* machineValidator ) ValidateUpdate (_ context.Context , oldObj runtime.Object , newObj runtime.Object ) (admission.Warnings , error ) {
117
+ newM , ok := newObj .(* Machine )
118
+ if ! ok {
119
+ return nil , apierrors .NewBadRequest (fmt .Sprintf ("expected a Machine but got a %T" , newObj ))
120
+ }
121
+
122
+ oldM , ok := oldObj .(* Machine )
82
123
if ! ok {
83
- return nil , apierrors .NewBadRequest (fmt .Sprintf ("expected a Machine but got a %T" , old ))
124
+ return nil , apierrors .NewBadRequest (fmt .Sprintf ("expected a Machine but got a %T" , oldObj ))
84
125
}
85
- return nil , m .validate (oldM )
126
+ return nil , newM .validate (oldM )
86
127
}
87
128
88
129
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type.
89
- func (m * Machine ) ValidateDelete () (admission.Warnings , error ) {
130
+ func (* machineValidator ) ValidateDelete (ctx context.Context , obj runtime.Object ) (admission.Warnings , error ) {
131
+ m , ok := obj .(* Machine )
132
+ if ! ok {
133
+ return nil , apierrors .NewBadRequest (fmt .Sprintf ("expected a Machine but got a %T" , obj ))
134
+ }
135
+
136
+ req , err := admission .RequestFromContext (ctx )
137
+ if err != nil {
138
+ return nil , apierrors .NewBadRequest (fmt .Sprintf ("expected a admission.Request inside context: %v" , err ))
139
+ }
140
+
141
+ // Fallback machines are placeholders for InfraMachinePools that do not support MachinePool Machines. These have
142
+ // no bootstrap or infrastructure data and cannot be deleted by users. They instead exist to provide a consistent
143
+ // user experience for MachinePool Machines.
144
+ if _ , isFallbackMachine := m .Labels [FallbackMachineLabel ]; isFallbackMachine {
145
+ // Only allow the request if it is coming from the CAPI controller service account.
146
+ if req .UserInfo .Username != "system:serviceaccount:" + os .Getenv ("POD_NAMESPACE" )+ ":" + os .Getenv ("POD_SERVICE_ACCOUNT" ) {
147
+ return nil , 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" )
148
+ }
149
+ }
150
+
90
151
return nil , nil
91
152
}
92
153
93
154
func (m * Machine ) validate (old * Machine ) error {
94
155
var allErrs field.ErrorList
95
156
specPath := field .NewPath ("spec" )
96
157
if m .Spec .Bootstrap .ConfigRef == nil && m .Spec .Bootstrap .DataSecretName == nil {
97
- allErrs = append (
98
- allErrs ,
99
- field .Required (
100
- specPath .Child ("bootstrap" , "data" ),
101
- "expected either spec.bootstrap.dataSecretName or spec.bootstrap.configRef to be populated" ,
102
- ),
103
- )
158
+ // MachinePool Machines don't have a bootstrap configRef, so don't require it. The bootstrap config is instead owned by the MachinePool.
159
+ if ! isMachinePoolMachine (m ) {
160
+ allErrs = append (
161
+ allErrs ,
162
+ field .Required (
163
+ specPath .Child ("bootstrap" , "data" ),
164
+ "expected either spec.bootstrap.dataSecretName or spec.bootstrap.configRef to be populated" ,
165
+ ),
166
+ )
167
+ }
104
168
}
105
169
106
170
if m .Spec .Bootstrap .ConfigRef != nil && m .Spec .Bootstrap .ConfigRef .Namespace != m .Namespace {
@@ -114,15 +178,18 @@ func (m *Machine) validate(old *Machine) error {
114
178
)
115
179
}
116
180
117
- if m .Spec .InfrastructureRef .Namespace != m .Namespace {
118
- allErrs = append (
119
- allErrs ,
120
- field .Invalid (
121
- specPath .Child ("infrastructureRef" , "namespace" ),
122
- m .Spec .InfrastructureRef .Namespace ,
123
- "must match metadata.namespace" ,
124
- ),
125
- )
181
+ // InfraRef can be empty for MachinePool Machines so skip the check in that case.
182
+ if ! isMachinePoolMachine (m ) {
183
+ if m .Spec .InfrastructureRef .Namespace != m .Namespace {
184
+ allErrs = append (
185
+ allErrs ,
186
+ field .Invalid (
187
+ specPath .Child ("infrastructureRef" , "namespace" ),
188
+ m .Spec .InfrastructureRef .Namespace ,
189
+ "must match metadata.namespace" ,
190
+ ),
191
+ )
192
+ }
126
193
}
127
194
128
195
if old != nil && old .Spec .ClusterName != m .Spec .ClusterName {
@@ -143,3 +210,17 @@ func (m *Machine) validate(old *Machine) error {
143
210
}
144
211
return apierrors .NewInvalid (GroupVersion .WithKind ("Machine" ).GroupKind (), m .Name , allErrs )
145
212
}
213
+
214
+ func isMachinePoolMachine (m * Machine ) bool {
215
+ if m .OwnerReferences == nil {
216
+ return false
217
+ }
218
+
219
+ for _ , owner := range m .OwnerReferences {
220
+ if owner .Kind == "MachinePool" {
221
+ return true
222
+ }
223
+ }
224
+
225
+ return false
226
+ }
0 commit comments