Skip to content

Commit 8cc1fd5

Browse files
authored
Merge pull request kubernetes-sigs#9410 from odvarkadaniel/move-kubeadm-webhooks
🌱 Move Kubeadm API v1beta1 webhooks to separate package
2 parents beaa3ea + 359f92e commit 8cc1fd5

36 files changed

+933
-917
lines changed

.golangci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ issues:
212212
# should be removed as the referenced deprecated item is removed from the project.
213213
- linters:
214214
- staticcheck
215-
text: "SA1019: (bootstrapv1.ClusterStatus|scope.Config.Spec.UseExperimentalRetryJoin|DockerMachine.Spec.Bootstrapped|machineStatus.Bootstrapped) is deprecated"
215+
text: "SA1019: (bootstrapv1.ClusterStatus|KubeadmConfigSpec.UseExperimentalRetryJoin|scope.Config.Spec.UseExperimentalRetryJoin|DockerMachine.Spec.Bootstrapped|machineStatus.Bootstrapped) is deprecated"
216216
# Specific exclude rules for deprecated packages that are still part of the codebase. These
217217
# should be removed as the referenced deprecated packages are removed from the project.
218218
- linters:
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
rules:
2+
- selectorRegexp: sigs[.]k8s[.]io/controller-runtime
3+
allowedPrefixes:
4+
- "sigs.k8s.io/controller-runtime/pkg/conversion"
5+
forbiddenPrefixes: []

bootstrap/kubeadm/api/v1alpha4/groupversion_info.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,28 @@ limitations under the License.
2020
package v1alpha4
2121

2222
import (
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
"k8s.io/apimachinery/pkg/runtime"
2325
"k8s.io/apimachinery/pkg/runtime/schema"
24-
"sigs.k8s.io/controller-runtime/pkg/scheme"
2526
)
2627

2728
var (
2829
// GroupVersion is group version used to register these objects.
2930
GroupVersion = schema.GroupVersion{Group: "bootstrap.cluster.x-k8s.io", Version: "v1alpha4"}
3031

3132
// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
32-
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
33+
schemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
3334

3435
// AddToScheme adds the types in this group-version to the given scheme.
35-
AddToScheme = SchemeBuilder.AddToScheme
36+
AddToScheme = schemeBuilder.AddToScheme
3637

37-
localSchemeBuilder = SchemeBuilder.SchemeBuilder
38+
objectTypes = []runtime.Object{}
39+
40+
localSchemeBuilder = schemeBuilder
3841
)
42+
43+
func addKnownTypes(scheme *runtime.Scheme) error {
44+
scheme.AddKnownTypes(GroupVersion, objectTypes...)
45+
metav1.AddToGroupVersion(scheme, GroupVersion)
46+
return nil
47+
}

bootstrap/kubeadm/api/v1alpha4/kubeadmconfig_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ type KubeadmConfigList struct {
163163
}
164164

165165
func init() {
166-
SchemeBuilder.Register(&KubeadmConfig{}, &KubeadmConfigList{})
166+
objectTypes = append(objectTypes, &KubeadmConfig{}, &KubeadmConfigList{})
167167
}
168168

169169
// Encoding specifies the cloud-init file encoding.

bootstrap/kubeadm/api/v1alpha4/kubeadmconfigtemplate_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,5 @@ type KubeadmConfigTemplateList struct {
5858
}
5959

6060
func init() {
61-
SchemeBuilder.Register(&KubeadmConfigTemplate{}, &KubeadmConfigTemplateList{})
61+
objectTypes = append(objectTypes, &KubeadmConfigTemplate{}, &KubeadmConfigTemplateList{})
6262
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
rules:
2+
- selectorRegexp: sigs[.]k8s[.]io/controller-runtime
3+
allowedPrefixes: []
4+
forbiddenPrefixes:
5+
- "sigs.k8s.io/controller-runtime"

bootstrap/kubeadm/api/v1beta1/kubeadmconfig_types.go

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
20+
"fmt"
21+
2022
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
"k8s.io/apimachinery/pkg/util/validation/field"
2124

2225
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
26+
"sigs.k8s.io/cluster-api/feature"
2327
)
2428

2529
// Format specifies the output format of the bootstrap data
@@ -34,6 +38,16 @@ const (
3438
Ignition Format = "ignition"
3539
)
3640

41+
var (
42+
cannotUseWithIgnition = fmt.Sprintf("not supported when spec.format is set to: %q", Ignition)
43+
conflictingFileSourceMsg = "only one of content or contentFrom may be specified for a single file"
44+
conflictingUserSourceMsg = "only one of passwd or passwdFrom may be specified for a single user"
45+
kubeadmBootstrapFormatIgnitionFeatureDisabledMsg = "can be set only if the KubeadmBootstrapFormatIgnition feature gate is enabled"
46+
missingSecretNameMsg = "secret file source must specify non-empty secret name"
47+
missingSecretKeyMsg = "secret file source must specify non-empty secret key"
48+
pathConflictMsg = "path property must be unique among all files"
49+
)
50+
3751
// KubeadmConfigSpec defines the desired state of KubeadmConfig.
3852
// Either ClusterConfiguration and InitConfiguration should be defined or the JoinConfiguration should be defined.
3953
type KubeadmConfigSpec struct {
@@ -107,6 +121,242 @@ type KubeadmConfigSpec struct {
107121
Ignition *IgnitionSpec `json:"ignition,omitempty"`
108122
}
109123

124+
// Default defaults a KubeadmConfigSpec.
125+
func (c *KubeadmConfigSpec) Default() {
126+
if c.Format == "" {
127+
c.Format = CloudConfig
128+
}
129+
if c.InitConfiguration != nil && c.InitConfiguration.NodeRegistration.ImagePullPolicy == "" {
130+
c.InitConfiguration.NodeRegistration.ImagePullPolicy = "IfNotPresent"
131+
}
132+
if c.JoinConfiguration != nil && c.JoinConfiguration.NodeRegistration.ImagePullPolicy == "" {
133+
c.JoinConfiguration.NodeRegistration.ImagePullPolicy = "IfNotPresent"
134+
}
135+
}
136+
137+
// Validate ensures the KubeadmConfigSpec is valid.
138+
func (c *KubeadmConfigSpec) Validate(pathPrefix *field.Path) field.ErrorList {
139+
var allErrs field.ErrorList
140+
141+
allErrs = append(allErrs, c.validateFiles(pathPrefix)...)
142+
allErrs = append(allErrs, c.validateUsers(pathPrefix)...)
143+
allErrs = append(allErrs, c.validateIgnition(pathPrefix)...)
144+
145+
return allErrs
146+
}
147+
148+
func (c *KubeadmConfigSpec) validateFiles(pathPrefix *field.Path) field.ErrorList {
149+
var allErrs field.ErrorList
150+
151+
knownPaths := map[string]struct{}{}
152+
153+
for i := range c.Files {
154+
file := c.Files[i]
155+
if file.Content != "" && file.ContentFrom != nil {
156+
allErrs = append(
157+
allErrs,
158+
field.Invalid(
159+
pathPrefix.Child("files").Index(i),
160+
file,
161+
conflictingFileSourceMsg,
162+
),
163+
)
164+
}
165+
// n.b.: if we ever add types besides Secret as a ContentFrom
166+
// Source, we must add webhook validation here for one of the
167+
// sources being non-nil.
168+
if file.ContentFrom != nil {
169+
if file.ContentFrom.Secret.Name == "" {
170+
allErrs = append(
171+
allErrs,
172+
field.Required(
173+
pathPrefix.Child("files").Index(i).Child("contentFrom", "secret", "name"),
174+
missingSecretNameMsg,
175+
),
176+
)
177+
}
178+
if file.ContentFrom.Secret.Key == "" {
179+
allErrs = append(
180+
allErrs,
181+
field.Required(
182+
pathPrefix.Child("files").Index(i).Child("contentFrom", "secret", "key"),
183+
missingSecretKeyMsg,
184+
),
185+
)
186+
}
187+
}
188+
_, conflict := knownPaths[file.Path]
189+
if conflict {
190+
allErrs = append(
191+
allErrs,
192+
field.Invalid(
193+
pathPrefix.Child("files").Index(i).Child("path"),
194+
file,
195+
pathConflictMsg,
196+
),
197+
)
198+
}
199+
knownPaths[file.Path] = struct{}{}
200+
}
201+
202+
return allErrs
203+
}
204+
205+
func (c *KubeadmConfigSpec) validateUsers(pathPrefix *field.Path) field.ErrorList {
206+
var allErrs field.ErrorList
207+
208+
for i := range c.Users {
209+
user := c.Users[i]
210+
if user.Passwd != nil && user.PasswdFrom != nil {
211+
allErrs = append(
212+
allErrs,
213+
field.Invalid(
214+
pathPrefix.Child("users").Index(i),
215+
user,
216+
conflictingUserSourceMsg,
217+
),
218+
)
219+
}
220+
// n.b.: if we ever add types besides Secret as a PasswdFrom
221+
// Source, we must add webhook validation here for one of the
222+
// sources being non-nil.
223+
if user.PasswdFrom != nil {
224+
if user.PasswdFrom.Secret.Name == "" {
225+
allErrs = append(
226+
allErrs,
227+
field.Required(
228+
pathPrefix.Child("users").Index(i).Child("passwdFrom", "secret", "name"),
229+
missingSecretNameMsg,
230+
),
231+
)
232+
}
233+
if user.PasswdFrom.Secret.Key == "" {
234+
allErrs = append(
235+
allErrs,
236+
field.Required(
237+
pathPrefix.Child("users").Index(i).Child("passwdFrom", "secret", "key"),
238+
missingSecretKeyMsg,
239+
),
240+
)
241+
}
242+
}
243+
}
244+
245+
return allErrs
246+
}
247+
248+
func (c *KubeadmConfigSpec) validateIgnition(pathPrefix *field.Path) field.ErrorList {
249+
var allErrs field.ErrorList
250+
251+
if !feature.Gates.Enabled(feature.KubeadmBootstrapFormatIgnition) {
252+
if c.Format == Ignition {
253+
allErrs = append(allErrs, field.Forbidden(
254+
pathPrefix.Child("format"), kubeadmBootstrapFormatIgnitionFeatureDisabledMsg))
255+
}
256+
257+
if c.Ignition != nil {
258+
allErrs = append(allErrs, field.Forbidden(
259+
pathPrefix.Child("ignition"), kubeadmBootstrapFormatIgnitionFeatureDisabledMsg))
260+
}
261+
262+
return allErrs
263+
}
264+
265+
if c.Format != Ignition {
266+
if c.Ignition != nil {
267+
allErrs = append(
268+
allErrs,
269+
field.Invalid(
270+
pathPrefix.Child("format"),
271+
c.Format,
272+
fmt.Sprintf("must be set to %q if spec.ignition is set", Ignition),
273+
),
274+
)
275+
}
276+
277+
return allErrs
278+
}
279+
280+
for i, user := range c.Users {
281+
if user.Inactive != nil && *user.Inactive {
282+
allErrs = append(
283+
allErrs,
284+
field.Forbidden(
285+
pathPrefix.Child("users").Index(i).Child("inactive"),
286+
cannotUseWithIgnition,
287+
),
288+
)
289+
}
290+
}
291+
292+
if c.UseExperimentalRetryJoin {
293+
allErrs = append(
294+
allErrs,
295+
field.Forbidden(
296+
pathPrefix.Child("useExperimentalRetryJoin"),
297+
cannotUseWithIgnition,
298+
),
299+
)
300+
}
301+
302+
for i, file := range c.Files {
303+
if file.Encoding == Gzip || file.Encoding == GzipBase64 {
304+
allErrs = append(
305+
allErrs,
306+
field.Forbidden(
307+
pathPrefix.Child("files").Index(i).Child("encoding"),
308+
cannotUseWithIgnition,
309+
),
310+
)
311+
}
312+
}
313+
314+
if c.DiskSetup == nil {
315+
return allErrs
316+
}
317+
318+
for i, partition := range c.DiskSetup.Partitions {
319+
if partition.TableType != nil && *partition.TableType != "gpt" {
320+
allErrs = append(
321+
allErrs,
322+
field.Invalid(
323+
pathPrefix.Child("diskSetup", "partitions").Index(i).Child("tableType"),
324+
*partition.TableType,
325+
fmt.Sprintf(
326+
"only partition type %q is supported when spec.format is set to %q",
327+
"gpt",
328+
Ignition,
329+
),
330+
),
331+
)
332+
}
333+
}
334+
335+
for i, fs := range c.DiskSetup.Filesystems {
336+
if fs.ReplaceFS != nil {
337+
allErrs = append(
338+
allErrs,
339+
field.Forbidden(
340+
pathPrefix.Child("diskSetup", "filesystems").Index(i).Child("replaceFS"),
341+
cannotUseWithIgnition,
342+
),
343+
)
344+
}
345+
346+
if fs.Partition != nil {
347+
allErrs = append(
348+
allErrs,
349+
field.Forbidden(
350+
pathPrefix.Child("diskSetup", "filesystems").Index(i).Child("partition"),
351+
cannotUseWithIgnition,
352+
),
353+
)
354+
}
355+
}
356+
357+
return allErrs
358+
}
359+
110360
// IgnitionSpec contains Ignition specific configuration.
111361
type IgnitionSpec struct {
112362
// ContainerLinuxConfig contains CLC specific configuration.

0 commit comments

Comments
 (0)