sigs.k8s.io/cluster-api@v1.7.1/bootstrap/kubeadm/internal/webhooks/kubeadmconfig_test.go (about)

     1  /*
     2  Copyright 2021 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 webhooks
    18  
    19  import (
    20  	"testing"
    21  
    22  	. "github.com/onsi/gomega"
    23  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  	utilfeature "k8s.io/component-base/featuregate/testing"
    25  	"k8s.io/utils/ptr"
    26  	ctrl "sigs.k8s.io/controller-runtime"
    27  
    28  	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
    29  	"sigs.k8s.io/cluster-api/feature"
    30  	"sigs.k8s.io/cluster-api/internal/webhooks/util"
    31  )
    32  
    33  var ctx = ctrl.SetupSignalHandler()
    34  
    35  func TestKubeadmConfigDefault(t *testing.T) {
    36  	defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.ClusterTopology, true)()
    37  
    38  	g := NewWithT(t)
    39  
    40  	kubeadmConfig := &bootstrapv1.KubeadmConfig{
    41  		ObjectMeta: metav1.ObjectMeta{
    42  			Namespace: "foo",
    43  		},
    44  		Spec: bootstrapv1.KubeadmConfigSpec{},
    45  	}
    46  	updateDefaultingKubeadmConfig := kubeadmConfig.DeepCopy()
    47  	updateDefaultingKubeadmConfig.Spec.Verbosity = ptr.To[int32](4)
    48  	webhook := &KubeadmConfig{}
    49  	t.Run("for KubeadmConfig", util.CustomDefaultValidateTest(ctx, updateDefaultingKubeadmConfig, webhook))
    50  
    51  	g.Expect(webhook.Default(ctx, kubeadmConfig)).To(Succeed())
    52  
    53  	g.Expect(kubeadmConfig.Spec.Format).To(Equal(bootstrapv1.CloudConfig))
    54  
    55  	ignitionKubeadmConfig := &bootstrapv1.KubeadmConfig{
    56  		ObjectMeta: metav1.ObjectMeta{
    57  			Namespace: "foo",
    58  		},
    59  		Spec: bootstrapv1.KubeadmConfigSpec{
    60  			Format: bootstrapv1.Ignition,
    61  		},
    62  	}
    63  	g.Expect(webhook.Default(ctx, ignitionKubeadmConfig)).To(Succeed())
    64  	g.Expect(ignitionKubeadmConfig.Spec.Format).To(Equal(bootstrapv1.Ignition))
    65  }
    66  
    67  func TestKubeadmConfigValidate(t *testing.T) {
    68  	cases := map[string]struct {
    69  		in                    *bootstrapv1.KubeadmConfig
    70  		enableIgnitionFeature bool
    71  		expectErr             bool
    72  	}{
    73  		"valid content": {
    74  			in: &bootstrapv1.KubeadmConfig{
    75  				ObjectMeta: metav1.ObjectMeta{
    76  					Name:      "baz",
    77  					Namespace: metav1.NamespaceDefault,
    78  				},
    79  				Spec: bootstrapv1.KubeadmConfigSpec{
    80  					Files: []bootstrapv1.File{
    81  						{
    82  							Content: "foo",
    83  						},
    84  					},
    85  				},
    86  			},
    87  		},
    88  		"valid contentFrom": {
    89  			in: &bootstrapv1.KubeadmConfig{
    90  				ObjectMeta: metav1.ObjectMeta{
    91  					Name:      "baz",
    92  					Namespace: metav1.NamespaceDefault,
    93  				},
    94  				Spec: bootstrapv1.KubeadmConfigSpec{
    95  					Files: []bootstrapv1.File{
    96  						{
    97  							ContentFrom: &bootstrapv1.FileSource{
    98  								Secret: bootstrapv1.SecretFileSource{
    99  									Name: "foo",
   100  									Key:  "bar",
   101  								},
   102  							},
   103  						},
   104  					},
   105  				},
   106  			},
   107  		},
   108  		"invalid content and contentFrom": {
   109  			in: &bootstrapv1.KubeadmConfig{
   110  				ObjectMeta: metav1.ObjectMeta{
   111  					Name:      "baz",
   112  					Namespace: metav1.NamespaceDefault,
   113  				},
   114  				Spec: bootstrapv1.KubeadmConfigSpec{
   115  					Files: []bootstrapv1.File{
   116  						{
   117  							ContentFrom: &bootstrapv1.FileSource{},
   118  							Content:     "foo",
   119  						},
   120  					},
   121  				},
   122  			},
   123  			expectErr: true,
   124  		},
   125  		"invalid contentFrom without name": {
   126  			in: &bootstrapv1.KubeadmConfig{
   127  				ObjectMeta: metav1.ObjectMeta{
   128  					Name:      "baz",
   129  					Namespace: metav1.NamespaceDefault,
   130  				},
   131  				Spec: bootstrapv1.KubeadmConfigSpec{
   132  					Files: []bootstrapv1.File{
   133  						{
   134  							ContentFrom: &bootstrapv1.FileSource{
   135  								Secret: bootstrapv1.SecretFileSource{
   136  									Key: "bar",
   137  								},
   138  							},
   139  							Content: "foo",
   140  						},
   141  					},
   142  				},
   143  			},
   144  			expectErr: true,
   145  		},
   146  		"invalid contentFrom without key": {
   147  			in: &bootstrapv1.KubeadmConfig{
   148  				ObjectMeta: metav1.ObjectMeta{
   149  					Name:      "baz",
   150  					Namespace: metav1.NamespaceDefault,
   151  				},
   152  				Spec: bootstrapv1.KubeadmConfigSpec{
   153  					Files: []bootstrapv1.File{
   154  						{
   155  							ContentFrom: &bootstrapv1.FileSource{
   156  								Secret: bootstrapv1.SecretFileSource{
   157  									Name: "foo",
   158  								},
   159  							},
   160  							Content: "foo",
   161  						},
   162  					},
   163  				},
   164  			},
   165  			expectErr: true,
   166  		},
   167  		"invalid with duplicate file path": {
   168  			in: &bootstrapv1.KubeadmConfig{
   169  				ObjectMeta: metav1.ObjectMeta{
   170  					Name:      "baz",
   171  					Namespace: metav1.NamespaceDefault,
   172  				},
   173  				Spec: bootstrapv1.KubeadmConfigSpec{
   174  					Files: []bootstrapv1.File{
   175  						{
   176  							Content: "foo",
   177  						},
   178  						{
   179  							Content: "bar",
   180  						},
   181  					},
   182  				},
   183  			},
   184  			expectErr: true,
   185  		},
   186  		"valid passwd": {
   187  			in: &bootstrapv1.KubeadmConfig{
   188  				ObjectMeta: metav1.ObjectMeta{
   189  					Name:      "baz",
   190  					Namespace: metav1.NamespaceDefault,
   191  				},
   192  				Spec: bootstrapv1.KubeadmConfigSpec{
   193  					Users: []bootstrapv1.User{
   194  						{
   195  							Passwd: ptr.To("foo"),
   196  						},
   197  					},
   198  				},
   199  			},
   200  		},
   201  		"valid passwdFrom": {
   202  			in: &bootstrapv1.KubeadmConfig{
   203  				ObjectMeta: metav1.ObjectMeta{
   204  					Name:      "baz",
   205  					Namespace: metav1.NamespaceDefault,
   206  				},
   207  				Spec: bootstrapv1.KubeadmConfigSpec{
   208  					Users: []bootstrapv1.User{
   209  						{
   210  							PasswdFrom: &bootstrapv1.PasswdSource{
   211  								Secret: bootstrapv1.SecretPasswdSource{
   212  									Name: "foo",
   213  									Key:  "bar",
   214  								},
   215  							},
   216  						},
   217  					},
   218  				},
   219  			},
   220  		},
   221  		"invalid passwd and passwdFrom": {
   222  			in: &bootstrapv1.KubeadmConfig{
   223  				ObjectMeta: metav1.ObjectMeta{
   224  					Name:      "baz",
   225  					Namespace: metav1.NamespaceDefault,
   226  				},
   227  				Spec: bootstrapv1.KubeadmConfigSpec{
   228  					Users: []bootstrapv1.User{
   229  						{
   230  							PasswdFrom: &bootstrapv1.PasswdSource{},
   231  							Passwd:     ptr.To("foo"),
   232  						},
   233  					},
   234  				},
   235  			},
   236  			expectErr: true,
   237  		},
   238  		"invalid passwdFrom without name": {
   239  			in: &bootstrapv1.KubeadmConfig{
   240  				ObjectMeta: metav1.ObjectMeta{
   241  					Name:      "baz",
   242  					Namespace: metav1.NamespaceDefault,
   243  				},
   244  				Spec: bootstrapv1.KubeadmConfigSpec{
   245  					Users: []bootstrapv1.User{
   246  						{
   247  							PasswdFrom: &bootstrapv1.PasswdSource{
   248  								Secret: bootstrapv1.SecretPasswdSource{
   249  									Key: "bar",
   250  								},
   251  							},
   252  							Passwd: ptr.To("foo"),
   253  						},
   254  					},
   255  				},
   256  			},
   257  			expectErr: true,
   258  		},
   259  		"invalid passwdFrom without key": {
   260  			in: &bootstrapv1.KubeadmConfig{
   261  				ObjectMeta: metav1.ObjectMeta{
   262  					Name:      "baz",
   263  					Namespace: metav1.NamespaceDefault,
   264  				},
   265  				Spec: bootstrapv1.KubeadmConfigSpec{
   266  					Users: []bootstrapv1.User{
   267  						{
   268  							PasswdFrom: &bootstrapv1.PasswdSource{
   269  								Secret: bootstrapv1.SecretPasswdSource{
   270  									Name: "foo",
   271  								},
   272  							},
   273  							Passwd: ptr.To("foo"),
   274  						},
   275  					},
   276  				},
   277  			},
   278  			expectErr: true,
   279  		},
   280  		"Ignition field is set, format is not Ignition": {
   281  			enableIgnitionFeature: true,
   282  			in: &bootstrapv1.KubeadmConfig{
   283  				ObjectMeta: metav1.ObjectMeta{
   284  					Name:      "baz",
   285  					Namespace: "default",
   286  				},
   287  				Spec: bootstrapv1.KubeadmConfigSpec{
   288  					Ignition: &bootstrapv1.IgnitionSpec{},
   289  				},
   290  			},
   291  			expectErr: true,
   292  		},
   293  		"Ignition field is not set, format is Ignition": {
   294  			enableIgnitionFeature: true,
   295  			in: &bootstrapv1.KubeadmConfig{
   296  				ObjectMeta: metav1.ObjectMeta{
   297  					Name:      "baz",
   298  					Namespace: "default",
   299  				},
   300  				Spec: bootstrapv1.KubeadmConfigSpec{
   301  					Format: bootstrapv1.Ignition,
   302  				},
   303  			},
   304  		},
   305  		"format is Ignition, user is inactive": {
   306  			enableIgnitionFeature: true,
   307  			in: &bootstrapv1.KubeadmConfig{
   308  				ObjectMeta: metav1.ObjectMeta{
   309  					Name:      "baz",
   310  					Namespace: "default",
   311  				},
   312  				Spec: bootstrapv1.KubeadmConfigSpec{
   313  					Format: bootstrapv1.Ignition,
   314  					Users: []bootstrapv1.User{
   315  						{
   316  							Inactive: ptr.To(true),
   317  						},
   318  					},
   319  				},
   320  			},
   321  			expectErr: true,
   322  		},
   323  		"format is Ignition, non-GPT partition configured": {
   324  			enableIgnitionFeature: true,
   325  			in: &bootstrapv1.KubeadmConfig{
   326  				ObjectMeta: metav1.ObjectMeta{
   327  					Name:      "baz",
   328  					Namespace: "default",
   329  				},
   330  				Spec: bootstrapv1.KubeadmConfigSpec{
   331  					Format: bootstrapv1.Ignition,
   332  					DiskSetup: &bootstrapv1.DiskSetup{
   333  						Partitions: []bootstrapv1.Partition{
   334  							{
   335  								TableType: ptr.To("MS-DOS"),
   336  							},
   337  						},
   338  					},
   339  				},
   340  			},
   341  			expectErr: true,
   342  		},
   343  		"format is Ignition, experimental retry join is set": {
   344  			enableIgnitionFeature: true,
   345  			in: &bootstrapv1.KubeadmConfig{
   346  				ObjectMeta: metav1.ObjectMeta{
   347  					Name:      "baz",
   348  					Namespace: "default",
   349  				},
   350  				Spec: bootstrapv1.KubeadmConfigSpec{
   351  					Format:                   bootstrapv1.Ignition,
   352  					UseExperimentalRetryJoin: true,
   353  				},
   354  			},
   355  			expectErr: true,
   356  		},
   357  		"feature gate disabled, format is Ignition": {
   358  			in: &bootstrapv1.KubeadmConfig{
   359  				ObjectMeta: metav1.ObjectMeta{
   360  					Name:      "baz",
   361  					Namespace: "default",
   362  				},
   363  				Spec: bootstrapv1.KubeadmConfigSpec{
   364  					Format: bootstrapv1.Ignition,
   365  				},
   366  			},
   367  			expectErr: true,
   368  		},
   369  		"feature gate disabled, Ignition field is set": {
   370  			in: &bootstrapv1.KubeadmConfig{
   371  				ObjectMeta: metav1.ObjectMeta{
   372  					Name:      "baz",
   373  					Namespace: "default",
   374  				},
   375  				Spec: bootstrapv1.KubeadmConfigSpec{
   376  					Format: bootstrapv1.Ignition,
   377  					Ignition: &bootstrapv1.IgnitionSpec{
   378  						ContainerLinuxConfig: &bootstrapv1.ContainerLinuxConfig{},
   379  					},
   380  				},
   381  			},
   382  			expectErr: true,
   383  		},
   384  		"replaceFS specified with Ignition": {
   385  			enableIgnitionFeature: true,
   386  			in: &bootstrapv1.KubeadmConfig{
   387  				ObjectMeta: metav1.ObjectMeta{
   388  					Name:      "baz",
   389  					Namespace: "default",
   390  				},
   391  				Spec: bootstrapv1.KubeadmConfigSpec{
   392  					Format: bootstrapv1.Ignition,
   393  					DiskSetup: &bootstrapv1.DiskSetup{
   394  						Filesystems: []bootstrapv1.Filesystem{
   395  							{
   396  								ReplaceFS: ptr.To("ntfs"),
   397  							},
   398  						},
   399  					},
   400  				},
   401  			},
   402  			expectErr: true,
   403  		},
   404  		"filesystem partition specified with Ignition": {
   405  			enableIgnitionFeature: true,
   406  			in: &bootstrapv1.KubeadmConfig{
   407  				ObjectMeta: metav1.ObjectMeta{
   408  					Name:      "baz",
   409  					Namespace: "default",
   410  				},
   411  				Spec: bootstrapv1.KubeadmConfigSpec{
   412  					Format: bootstrapv1.Ignition,
   413  					DiskSetup: &bootstrapv1.DiskSetup{
   414  						Filesystems: []bootstrapv1.Filesystem{
   415  							{
   416  								Partition: ptr.To("1"),
   417  							},
   418  						},
   419  					},
   420  				},
   421  			},
   422  			expectErr: true,
   423  		},
   424  		"file encoding gzip specified with Ignition": {
   425  			enableIgnitionFeature: true,
   426  			in: &bootstrapv1.KubeadmConfig{
   427  				ObjectMeta: metav1.ObjectMeta{
   428  					Name:      "baz",
   429  					Namespace: "default",
   430  				},
   431  				Spec: bootstrapv1.KubeadmConfigSpec{
   432  					Format: bootstrapv1.Ignition,
   433  					Files: []bootstrapv1.File{
   434  						{
   435  							Encoding: bootstrapv1.Gzip,
   436  						},
   437  					},
   438  				},
   439  			},
   440  			expectErr: true,
   441  		},
   442  		"file encoding gzip+base64 specified with Ignition": {
   443  			enableIgnitionFeature: true,
   444  			in: &bootstrapv1.KubeadmConfig{
   445  				ObjectMeta: metav1.ObjectMeta{
   446  					Name:      "baz",
   447  					Namespace: "default",
   448  				},
   449  				Spec: bootstrapv1.KubeadmConfigSpec{
   450  					Format: bootstrapv1.Ignition,
   451  					Files: []bootstrapv1.File{
   452  						{
   453  							Encoding: bootstrapv1.GzipBase64,
   454  						},
   455  					},
   456  				},
   457  			},
   458  			expectErr: true,
   459  		},
   460  	}
   461  
   462  	for name, tt := range cases {
   463  		t.Run(name, func(t *testing.T) {
   464  			if tt.enableIgnitionFeature {
   465  				// NOTE: KubeadmBootstrapFormatIgnition feature flag is disabled by default.
   466  				// Enabling the feature flag temporarily for this test.
   467  				defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.KubeadmBootstrapFormatIgnition, true)()
   468  			}
   469  			g := NewWithT(t)
   470  
   471  			webhook := &KubeadmConfig{}
   472  
   473  			if tt.expectErr {
   474  				warnings, err := webhook.ValidateCreate(ctx, tt.in)
   475  				g.Expect(err).To(HaveOccurred())
   476  				g.Expect(warnings).To(BeEmpty())
   477  				warnings, err = webhook.ValidateUpdate(ctx, nil, tt.in)
   478  				g.Expect(err).To(HaveOccurred())
   479  				g.Expect(warnings).To(BeEmpty())
   480  			} else {
   481  				warnings, err := webhook.ValidateCreate(ctx, tt.in)
   482  				g.Expect(err).ToNot(HaveOccurred())
   483  				g.Expect(warnings).To(BeEmpty())
   484  				warnings, err = webhook.ValidateUpdate(ctx, nil, tt.in)
   485  				g.Expect(err).ToNot(HaveOccurred())
   486  				g.Expect(warnings).To(BeEmpty())
   487  			}
   488  		})
   489  	}
   490  }