1
16
17
18 package config
19
20 import (
21 "bytes"
22 "fmt"
23 "net"
24 "reflect"
25 "strings"
26
27 "github.com/pkg/errors"
28
29 "k8s.io/apimachinery/pkg/runtime"
30 "k8s.io/apimachinery/pkg/runtime/schema"
31 netutil "k8s.io/apimachinery/pkg/util/net"
32 "k8s.io/apimachinery/pkg/util/version"
33 apimachineryversion "k8s.io/apimachinery/pkg/version"
34 componentversion "k8s.io/component-base/version"
35 "k8s.io/klog/v2"
36 netutils "k8s.io/utils/net"
37
38 kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
39 kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
40 kubeadmapiv1old "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
41 kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta4"
42 "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
43 "k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
44 "k8s.io/kubernetes/cmd/kubeadm/app/constants"
45 kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
46 )
47
48
49 type LoadOrDefaultConfigurationOptions struct {
50
51 AllowExperimental bool
52
53 SkipCRIDetect bool
54 }
55
56
57 func MarshalKubeadmConfigObject(obj runtime.Object, gv schema.GroupVersion) ([]byte, error) {
58 switch internalcfg := obj.(type) {
59 case *kubeadmapi.InitConfiguration:
60 return MarshalInitConfigurationToBytes(internalcfg, gv)
61 default:
62 return kubeadmutil.MarshalToYamlForCodecs(obj, gv, kubeadmscheme.Codecs)
63 }
64 }
65
66
67
68 func validateSupportedVersion(gv schema.GroupVersion, allowDeprecated, allowExperimental bool) error {
69
70
71
72
73
74
75
76
77
78 oldKnownAPIVersions := map[string]string{
79 "kubeadm.k8s.io/v1alpha1": "v1.11",
80 "kubeadm.k8s.io/v1alpha2": "v1.12",
81 "kubeadm.k8s.io/v1alpha3": "v1.14",
82 "kubeadm.k8s.io/v1beta1": "v1.15",
83 "kubeadm.k8s.io/v1beta2": "v1.22",
84 }
85
86
87 experimentalAPIVersions := map[string]string{
88
89
90 "kubeadm.k8s.io/v1beta4": "v1.28",
91 }
92
93
94 deprecatedAPIVersions := map[string]struct{}{}
95
96 gvString := gv.String()
97
98 if useKubeadmVersion := oldKnownAPIVersions[gvString]; useKubeadmVersion != "" {
99 return errors.Errorf("your configuration file uses an old API spec: %q. Please use kubeadm %s instead and run 'kubeadm config migrate --old-config old.yaml --new-config new.yaml', which will write the new, similar spec using a newer API version.", gv.String(), useKubeadmVersion)
100 }
101
102 if _, present := deprecatedAPIVersions[gvString]; present && !allowDeprecated {
103 klog.Warningf("your configuration file uses a deprecated API spec: %q. Please use 'kubeadm config migrate --old-config old.yaml --new-config new.yaml', which will write the new, similar spec using a newer API version.", gv.String())
104 }
105
106 if _, present := experimentalAPIVersions[gvString]; present && !allowExperimental {
107 return errors.Errorf("experimental API spec: %q is not allowed. You can use the --%s flag if the command supports it.", gv, options.AllowExperimentalAPI)
108 }
109
110 return nil
111 }
112
113
114
115
116 func NormalizeKubernetesVersion(cfg *kubeadmapi.ClusterConfiguration) error {
117 isCIVersion := kubeadmutil.KubernetesIsCIVersion(cfg.KubernetesVersion)
118
119
120 if isCIVersion && cfg.ImageRepository == kubeadmapiv1.DefaultImageRepository {
121 cfg.CIImageRepository = constants.DefaultCIImageRepository
122 }
123
124
125 ver, err := kubeadmutil.KubernetesReleaseVersion(cfg.KubernetesVersion)
126 if err != nil {
127 return err
128 }
129
130
131 if isCIVersion {
132 cfg.CIKubernetesVersion = fmt.Sprintf("%s%s", constants.CIKubernetesVersionPrefix, ver)
133 }
134
135 cfg.KubernetesVersion = ver
136
137
138 k8sVersion, err := version.ParseSemantic(cfg.KubernetesVersion)
139 if err != nil {
140 return errors.Wrapf(err, "couldn't parse Kubernetes version %q", cfg.KubernetesVersion)
141 }
142
143
144
145
146
147
148 mcpVersion := constants.MinimumControlPlaneVersion
149 versionInfo := componentversion.Get()
150 if isKubeadmPrereleaseVersion(&versionInfo, k8sVersion, mcpVersion) {
151 klog.V(1).Infof("WARNING: tolerating control plane version %s as a pre-release version", cfg.KubernetesVersion)
152
153 return nil
154 }
155
156 if k8sVersion.LessThan(mcpVersion) {
157 return errors.Errorf("this version of kubeadm only supports deploying clusters with the control plane version >= %s. Current version: %s",
158 mcpVersion, cfg.KubernetesVersion)
159 }
160 return nil
161 }
162
163
164 func LowercaseSANs(sans []string) {
165 for i, san := range sans {
166 lowercase := strings.ToLower(san)
167 if lowercase != san {
168 klog.V(1).Infof("lowercasing SAN %q to %q", san, lowercase)
169 sans[i] = lowercase
170 }
171 }
172 }
173
174
175
176 func VerifyAPIServerBindAddress(address string) error {
177 ip := netutils.ParseIPSloppy(address)
178 if ip == nil {
179 return errors.Errorf("cannot parse IP address: %s", address)
180 }
181
182
183
184
185
186 if ip.IsLoopback() {
187 return nil
188 }
189 if !ip.IsGlobalUnicast() {
190 return errors.Errorf("cannot use %q as the bind address for the API Server", address)
191 }
192 return nil
193 }
194
195
196
197 func ChooseAPIServerBindAddress(bindAddress net.IP) (net.IP, error) {
198 ip, err := netutil.ResolveBindAddress(bindAddress)
199 if err != nil {
200 if netutil.IsNoRoutesError(err) {
201 klog.Warningf("WARNING: could not obtain a bind address for the API Server: %v; using: %s", err, constants.DefaultAPIServerBindAddress)
202 defaultIP := netutils.ParseIPSloppy(constants.DefaultAPIServerBindAddress)
203 if defaultIP == nil {
204 return nil, errors.Errorf("cannot parse default IP address: %s", constants.DefaultAPIServerBindAddress)
205 }
206 return defaultIP, nil
207 }
208 return nil, err
209 }
210 if bindAddress != nil && !bindAddress.IsUnspecified() && !reflect.DeepEqual(ip, bindAddress) {
211 klog.Warningf("WARNING: overriding requested API server bind address: requested %q, actual %q", bindAddress, ip)
212 }
213 return ip, nil
214 }
215
216
217 func validateKnownGVKs(gvks []schema.GroupVersionKind) error {
218 var unknown []schema.GroupVersionKind
219
220 schemes := []*runtime.Scheme{
221 kubeadmscheme.Scheme,
222 componentconfigs.Scheme,
223 }
224
225 for _, gvk := range gvks {
226 var scheme *runtime.Scheme
227
228
229
230 if err := validateSupportedVersion(gvk.GroupVersion(), true, true); err != nil {
231 continue
232 }
233
234 for _, s := range schemes {
235 if _, err := s.New(gvk); err == nil {
236 scheme = s
237 break
238 }
239 }
240 if scheme == nil {
241 unknown = append(unknown, gvk)
242 }
243 }
244
245 if len(unknown) > 0 {
246 return errors.Errorf("unknown configuration APIs: %#v", unknown)
247 }
248
249 return nil
250 }
251
252
253
254 func MigrateOldConfig(oldConfig []byte, allowExperimental bool, mutators migrateMutators) ([]byte, error) {
255 newConfig := [][]byte{}
256
257 if mutators == nil {
258 mutators = defaultMigrateMutators()
259 }
260
261 gvkmap, err := kubeadmutil.SplitYAMLDocuments(oldConfig)
262 if err != nil {
263 return []byte{}, err
264 }
265
266 gvks := []schema.GroupVersionKind{}
267 for gvk := range gvkmap {
268 gvks = append(gvks, gvk)
269 }
270
271 if err := validateKnownGVKs(gvks); err != nil {
272 return []byte{}, err
273 }
274
275 gv := kubeadmapiv1old.SchemeGroupVersion
276 if allowExperimental {
277 gv = kubeadmapiv1.SchemeGroupVersion
278 }
279
280 if kubeadmutil.GroupVersionKindsHasInitConfiguration(gvks...) || kubeadmutil.GroupVersionKindsHasClusterConfiguration(gvks...) {
281 o, err := documentMapToInitConfiguration(gvkmap, true, allowExperimental, true, false)
282 if err != nil {
283 return []byte{}, err
284 }
285 if err := mutators.mutate([]any{o}); err != nil {
286 return []byte{}, err
287 }
288 b, err := MarshalKubeadmConfigObject(o, gv)
289 if err != nil {
290 return []byte{}, err
291 }
292 newConfig = append(newConfig, b)
293 }
294
295
296 if kubeadmutil.GroupVersionKindsHasJoinConfiguration(gvks...) {
297 o, err := documentMapToJoinConfiguration(gvkmap, true, allowExperimental, true, false)
298 if err != nil {
299 return []byte{}, err
300 }
301 if err := mutators.mutate([]any{o}); err != nil {
302 return []byte{}, err
303 }
304 b, err := MarshalKubeadmConfigObject(o, gv)
305 if err != nil {
306 return []byte{}, err
307 }
308 newConfig = append(newConfig, b)
309 }
310
311
312 if kubeadmutil.GroupVersionKindsHasResetConfiguration(gvks...) {
313 o, err := documentMapToResetConfiguration(gvkmap, true, allowExperimental, true, false)
314 if err != nil {
315 return []byte{}, err
316 }
317 if err := mutators.mutate([]any{o}); err != nil {
318 return []byte{}, err
319 }
320 b, err := MarshalKubeadmConfigObject(o, gv)
321 if err != nil {
322 return []byte{}, err
323 }
324 newConfig = append(newConfig, b)
325 }
326
327 return bytes.Join(newConfig, []byte(constants.YAMLDocumentSeparator)), nil
328 }
329
330
331
332 func ValidateConfig(config []byte, allowExperimental bool) error {
333 gvkmap, err := kubeadmutil.SplitYAMLDocuments(config)
334 if err != nil {
335 return err
336 }
337
338 gvks := []schema.GroupVersionKind{}
339 for gvk := range gvkmap {
340 gvks = append(gvks, gvk)
341 }
342
343 if err := validateKnownGVKs(gvks); err != nil {
344 return err
345 }
346
347
348 if kubeadmutil.GroupVersionKindsHasInitConfiguration(gvks...) || kubeadmutil.GroupVersionKindsHasClusterConfiguration(gvks...) {
349 if _, err := documentMapToInitConfiguration(gvkmap, true, allowExperimental, true, true); err != nil {
350 return err
351 }
352 }
353
354
355 if kubeadmutil.GroupVersionKindsHasJoinConfiguration(gvks...) {
356 if _, err := documentMapToJoinConfiguration(gvkmap, true, allowExperimental, true, true); err != nil {
357 return err
358 }
359 }
360
361
362 if kubeadmutil.GroupVersionKindsHasResetConfiguration(gvks...) {
363 if _, err := documentMapToResetConfiguration(gvkmap, true, allowExperimental, true, true); err != nil {
364 return err
365 }
366 }
367
368 return nil
369 }
370
371
372
373 func isKubeadmPrereleaseVersion(versionInfo *apimachineryversion.Info, k8sVersion, mcpVersion *version.Version) bool {
374 if len(versionInfo.Major) != 0 {
375 kubeadmVersion := version.MustParseSemantic(versionInfo.String())
376 if len(kubeadmVersion.PreRelease()) != 0 {
377
378
379 v := k8sVersion.WithMinor(k8sVersion.Minor() + 1)
380 if comp, _ := v.Compare(mcpVersion.String()); comp != -1 {
381 return true
382 }
383 }
384 }
385 return false
386 }
387
388
389
390 func prepareStaticVariables(config any) {
391 switch c := config.(type) {
392 case *kubeadmapi.InitConfiguration:
393 kubeadmapi.SetActiveTimeouts(c.Timeouts)
394 case *kubeadmapi.JoinConfiguration:
395 kubeadmapi.SetActiveTimeouts(c.Timeouts)
396 case *kubeadmapi.ResetConfiguration:
397 kubeadmapi.SetActiveTimeouts(c.Timeouts)
398 case *kubeadmapi.UpgradeConfiguration:
399 kubeadmapi.SetActiveTimeouts(c.Timeouts)
400 }
401 }
402
403
404
405 type migrateMutator struct {
406 in []any
407 mutateFunc func(in []any) error
408 }
409
410
411 type migrateMutators []migrateMutator
412
413
414
415 func (mutators migrateMutators) mutate(in []any) error {
416 var mutator *migrateMutator
417 for idx, m := range mutators {
418 if len(m.in) != len(in) {
419 continue
420 }
421 inputMatch := true
422 for idx := range m.in {
423 if reflect.TypeOf(m.in[idx]) != reflect.TypeOf(in[idx]) {
424 inputMatch = false
425 break
426 }
427 }
428 if inputMatch {
429 mutator = &mutators[idx]
430 break
431 }
432 }
433 if mutator == nil {
434 return errors.Errorf("could not find a mutator for input: %#v", in)
435 }
436 return mutator.mutateFunc(in)
437 }
438
439
440 func (mutators *migrateMutators) addEmpty(in []any) {
441 mutator := migrateMutator{
442 in: in,
443 mutateFunc: func(in []any) error { return nil },
444 }
445 *mutators = append(*mutators, mutator)
446 }
447
448
449
450 func defaultMigrateMutators() migrateMutators {
451 var (
452 mutators migrateMutators
453 mutator migrateMutator
454 )
455
456
457 mutator = migrateMutator{
458 in: []any{(*kubeadmapi.InitConfiguration)(nil)},
459 mutateFunc: func(in []any) error {
460 a := in[0].(*kubeadmapi.InitConfiguration)
461 a.Timeouts.ControlPlaneComponentHealthCheck.Duration = a.APIServer.TimeoutForControlPlane.Duration
462 a.APIServer.TimeoutForControlPlane = nil
463 return nil
464 },
465 }
466 mutators = append(mutators, mutator)
467
468
469 mutator = migrateMutator{
470 in: []any{(*kubeadmapi.JoinConfiguration)(nil)},
471 mutateFunc: func(in []any) error {
472 a := in[0].(*kubeadmapi.JoinConfiguration)
473 a.Timeouts.Discovery.Duration = a.Discovery.Timeout.Duration
474 a.Discovery.Timeout = nil
475 return nil
476 },
477 }
478 mutators = append(mutators, mutator)
479
480
481 mutators.addEmpty([]any{(*kubeadmapi.ResetConfiguration)(nil)})
482
483 return mutators
484 }
485
486
487 func defaultEmptyMigrateMutators() migrateMutators {
488 mutators := &migrateMutators{}
489
490 mutators.addEmpty([]any{(*kubeadmapi.InitConfiguration)(nil)})
491 mutators.addEmpty([]any{(*kubeadmapi.JoinConfiguration)(nil)})
492 mutators.addEmpty([]any{(*kubeadmapi.ResetConfiguration)(nil)})
493
494 return *mutators
495 }
496
497
498 func isKubeadmConfigPresent(docmap kubeadmapi.DocumentMap) bool {
499 for gvk := range docmap {
500 if gvk.Group == kubeadmapi.GroupName {
501 return true
502 }
503 }
504 return false
505 }
506
View as plain text