sigs.k8s.io/cluster-api@v1.7.1/bootstrap/kubeadm/internal/ignition/clc/clc_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 clc_test tests clc package.
    18  package clc_test
    19  
    20  import (
    21  	"testing"
    22  
    23  	ignition "github.com/flatcar/ignition/config/v2_3"
    24  	"github.com/flatcar/ignition/config/v2_3/types"
    25  	"github.com/google/go-cmp/cmp"
    26  	"k8s.io/utils/ptr"
    27  
    28  	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
    29  	"sigs.k8s.io/cluster-api/bootstrap/kubeadm/internal/cloudinit"
    30  	"sigs.k8s.io/cluster-api/bootstrap/kubeadm/internal/ignition/clc"
    31  )
    32  
    33  const (
    34  	configWithWarning = `---
    35  storage:
    36    files:
    37    - path: /foo
    38      contents:
    39        inline: foo
    40  `
    41  
    42  	// Should generate an Ignition warning about the colon in the partition label.
    43  	configWithIgnitionWarning = `---
    44  storage:
    45    disks:
    46    - device: /dev/sda
    47      partitions:
    48      - label: foo:bar
    49  `
    50  )
    51  
    52  func TestRender(t *testing.T) {
    53  	t.Parallel()
    54  
    55  	preKubeadmCommands := []string{
    56  		"pre-command",
    57  		"another-pre-command",
    58  		// Test multi-line commands as well.
    59  		"cat <<EOF > /etc/modules-load.d/containerd.conf\noverlay\nbr_netfilter\nEOF\n",
    60  	}
    61  	postKubeadmCommands := []string{
    62  		"post-kubeadm-command",
    63  		"another-post-kubeamd-command",
    64  		// Test multi-line commands as well.
    65  		"cat <<EOF > /etc/modules-load.d/containerd.conf\noverlay\nbr_netfilter\nEOF\n",
    66  	}
    67  
    68  	tc := []struct {
    69  		desc         string
    70  		input        *cloudinit.BaseUserData
    71  		wantIgnition types.Config
    72  	}{
    73  		{
    74  			desc: "renders valid Ignition JSON",
    75  			input: &cloudinit.BaseUserData{
    76  				PreKubeadmCommands:  preKubeadmCommands,
    77  				PostKubeadmCommands: postKubeadmCommands,
    78  				KubeadmCommand:      "kubeadm join",
    79  				NTP: &bootstrapv1.NTP{
    80  					Enabled: ptr.To(true),
    81  					Servers: []string{
    82  						"foo.bar",
    83  						"baz",
    84  					},
    85  				},
    86  				Users: []bootstrapv1.User{
    87  					{
    88  						Name:         "foo",
    89  						Gecos:        ptr.To("Foo B. Bar"),
    90  						Groups:       ptr.To("foo, bar"),
    91  						HomeDir:      ptr.To("/home/foo"),
    92  						Shell:        ptr.To("/bin/false"),
    93  						Passwd:       ptr.To("$6$j212wezy$7H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/"),
    94  						PrimaryGroup: ptr.To("foo"),
    95  						Sudo:         ptr.To("ALL=(ALL) NOPASSWD:ALL"),
    96  						SSHAuthorizedKeys: []string{
    97  							"foo",
    98  							"bar",
    99  						},
   100  					},
   101  				},
   102  				DiskSetup: &bootstrapv1.DiskSetup{
   103  					Partitions: []bootstrapv1.Partition{
   104  						{
   105  							Device:    "/dev/disk/azure/scsi1/lun0",
   106  							Layout:    true,
   107  							Overwrite: ptr.To(true),
   108  							TableType: ptr.To("gpt"),
   109  						},
   110  					},
   111  					Filesystems: []bootstrapv1.Filesystem{
   112  						{
   113  							Device:     "/dev/disk/azure/scsi1/lun0",
   114  							Filesystem: "ext4",
   115  							Label:      "test_disk",
   116  							ExtraOpts:  []string{"-F", "-E", "lazy_itable_init=1,lazy_journal_init=1"},
   117  							Overwrite:  ptr.To(true),
   118  						},
   119  					},
   120  				},
   121  				Mounts: []bootstrapv1.MountPoints{
   122  					{
   123  						"test_disk", "/var/lib/testdir", "foo",
   124  					},
   125  				},
   126  				WriteFiles: []bootstrapv1.File{
   127  					{
   128  						Path:        "/etc/testfile.yaml",
   129  						Encoding:    bootstrapv1.Base64,
   130  						Content:     "Zm9vCg==",
   131  						Permissions: "0600",
   132  						Owner:       "nobody:nobody",
   133  					},
   134  				},
   135  			},
   136  			wantIgnition: types.Config{
   137  				Ignition: types.Ignition{
   138  					Version: "2.3.0",
   139  				},
   140  				Passwd: types.Passwd{
   141  					Users: []types.PasswdUser{
   142  						{
   143  							Gecos: "Foo B. Bar",
   144  							Groups: []types.Group{
   145  								"foo",
   146  								"bar",
   147  							},
   148  							HomeDir:      "/home/foo",
   149  							Name:         "foo",
   150  							PasswordHash: ptr.To("$6$j212wezy$7H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/"),
   151  							PrimaryGroup: "foo",
   152  							SSHAuthorizedKeys: []types.SSHAuthorizedKey{
   153  								"foo",
   154  								"bar",
   155  							},
   156  							Shell: "/bin/false",
   157  						},
   158  					},
   159  				},
   160  				Storage: types.Storage{
   161  					Disks: []types.Disk{
   162  						{
   163  							Device:     "/dev/disk/azure/scsi1/lun0",
   164  							Partitions: []types.Partition{{}},
   165  							WipeTable:  true,
   166  						},
   167  					},
   168  					Files: []types.File{
   169  						{
   170  							Node: types.Node{
   171  								Filesystem: "root",
   172  								Path:       "/etc/sudoers.d/foo",
   173  							},
   174  							FileEmbedded1: types.FileEmbedded1{
   175  								Contents: types.FileContents{
   176  									Source: "data:,foo%20ALL%3D(ALL)%20NOPASSWD%3AALL%0A",
   177  								},
   178  								Mode: ptr.To(384),
   179  							},
   180  						},
   181  						{
   182  							Node: types.Node{
   183  								Filesystem: "root",
   184  								Path:       "/etc/testfile.yaml",
   185  								User:       &types.NodeUser{Name: "nobody"},
   186  								Group:      &types.NodeGroup{Name: "nobody"},
   187  							},
   188  							FileEmbedded1: types.FileEmbedded1{
   189  								Contents: types.FileContents{
   190  									Source: "data:,foo%0A",
   191  								},
   192  								Mode: ptr.To(384),
   193  							},
   194  						},
   195  						{
   196  							Node: types.Node{
   197  								Filesystem: "root",
   198  								Path:       "/etc/kubeadm.sh",
   199  							},
   200  							FileEmbedded1: types.FileEmbedded1{
   201  								Contents: types.FileContents{
   202  									Source: "data:,%23!%2Fbin%2Fbash%0Aset%20-e%0A%0Apre-command%0Aanother-pre-command%0Acat%20%3C%3CEOF%20%3E%20%2Fetc%2Fmodules-load.d%2Fcontainerd.conf%0Aoverlay%0Abr_netfilter%0AEOF%0A%0A%0Akubeadm%20join%0Amkdir%20-p%20%2Frun%2Fcluster-api%20%26%26%20echo%20success%20%3E%20%2Frun%2Fcluster-api%2Fbootstrap-success.complete%0Amv%20%2Fetc%2Fkubeadm.yml%20%2Ftmp%2F%0A%0Apost-kubeadm-command%0Aanother-post-kubeamd-command%0Acat%20%3C%3CEOF%20%3E%20%2Fetc%2Fmodules-load.d%2Fcontainerd.conf%0Aoverlay%0Abr_netfilter%0AEOF%0A",
   203  								},
   204  								Mode: ptr.To(448),
   205  							},
   206  						},
   207  						{
   208  							Node: types.Node{
   209  								Filesystem: "root",
   210  								Path:       "/etc/kubeadm.yml",
   211  							},
   212  							FileEmbedded1: types.FileEmbedded1{
   213  								Contents: types.FileContents{
   214  									Source: "data:,---%0Afoo%0A",
   215  								},
   216  								Mode: ptr.To(384),
   217  							},
   218  						},
   219  						{
   220  							Node: types.Node{
   221  								Filesystem: "root",
   222  								Path:       "/etc/ntp.conf",
   223  							},
   224  							FileEmbedded1: types.FileEmbedded1{
   225  								Contents: types.FileContents{
   226  									Source: "data:,%23%20Common%20pool%0Aserver%20foo.bar%0Aserver%20baz%0A%0A%23%20Warning%3A%20Using%20default%20NTP%20settings%20will%20leave%20your%20NTP%0A%23%20server%20accessible%20to%20all%20hosts%20on%20the%20Internet.%0A%0A%23%20If%20you%20want%20to%20deny%20all%20machines%20(including%20your%20own)%0A%23%20from%20accessing%20the%20NTP%20server%2C%20uncomment%3A%0A%23restrict%20default%20ignore%0A%0A%23%20Default%20configuration%3A%0A%23%20-%20Allow%20only%20time%20queries%2C%20at%20a%20limited%20rate%2C%20sending%20KoD%20when%20in%20excess.%0A%23%20-%20Allow%20all%20local%20queries%20(IPv4%2C%20IPv6)%0Arestrict%20default%20nomodify%20nopeer%20noquery%20notrap%20limited%20kod%0Arestrict%20127.0.0.1%0Arestrict%20%5B%3A%3A1%5D%0A",
   227  								},
   228  								Mode: ptr.To(420),
   229  							},
   230  						},
   231  					},
   232  					Filesystems: []types.Filesystem{
   233  						{
   234  							Mount: &types.Mount{
   235  								Device: "/dev/disk/azure/scsi1/lun0",
   236  								Format: "ext4",
   237  								Label:  ptr.To("test_disk"),
   238  								Options: []types.MountOption{
   239  									"-F",
   240  									"-E",
   241  									"lazy_itable_init=1,lazy_journal_init=1",
   242  								},
   243  								WipeFilesystem: true,
   244  							},
   245  							Name: "test_disk",
   246  						},
   247  					},
   248  				},
   249  				Systemd: types.Systemd{
   250  					Units: []types.Unit{
   251  						{
   252  							Contents: "[Unit]\nDescription=kubeadm\n# Run only once. After successful run, this file is moved to /tmp/.\nConditionPathExists=/etc/kubeadm.yml\nAfter=network.target\n[Service]\n# To not restart the unit when it exits, as it is expected.\nType=oneshot\nExecStart=/etc/kubeadm.sh\n[Install]\nWantedBy=multi-user.target\n",
   253  							Enabled:  ptr.To(true),
   254  							Name:     "kubeadm.service",
   255  						},
   256  						{
   257  							Enabled: ptr.To(true),
   258  							Name:    "ntpd.service",
   259  						},
   260  						{
   261  							Contents: "[Unit]\nDescription = Mount test_disk\n\n[Mount]\nWhat=/dev/disk/azure/scsi1/lun0\nWhere=/var/lib/testdir\nOptions=foo\n\n[Install]\nWantedBy=multi-user.target\n",
   262  							Enabled:  ptr.To(true),
   263  							Name:     "var-lib-testdir.mount",
   264  						},
   265  					},
   266  				},
   267  			},
   268  		},
   269  		{
   270  			desc: "multiple users with password auth",
   271  			input: &cloudinit.BaseUserData{
   272  				PreKubeadmCommands:  preKubeadmCommands,
   273  				PostKubeadmCommands: postKubeadmCommands,
   274  				KubeadmCommand:      "kubeadm join",
   275  				Users: []bootstrapv1.User{
   276  					{
   277  						Name:         "foo",
   278  						LockPassword: ptr.To(false),
   279  					},
   280  					{
   281  						Name:         "bar",
   282  						LockPassword: ptr.To(false),
   283  					},
   284  				},
   285  			},
   286  			wantIgnition: types.Config{
   287  				Ignition: types.Ignition{
   288  					Version: "2.3.0",
   289  				},
   290  				Passwd: types.Passwd{
   291  					Users: []types.PasswdUser{
   292  						{
   293  							Name: "foo",
   294  						},
   295  						{
   296  							Name: "bar",
   297  						},
   298  					},
   299  				},
   300  				Storage: types.Storage{
   301  					Files: []types.File{
   302  						{
   303  							Node: types.Node{
   304  								Filesystem: "root",
   305  								Path:       "/etc/ssh/sshd_config",
   306  							},
   307  							FileEmbedded1: types.FileEmbedded1{
   308  								Contents: types.FileContents{
   309  									Source: "data:,%23%20Use%20most%20defaults%20for%20sshd%20configuration.%0ASubsystem%20sftp%20internal-sftp%0AClientAliveInterval%20180%0AUseDNS%20no%0AUsePAM%20yes%0APrintLastLog%20no%20%23%20handled%20by%20PAM%0APrintMotd%20no%20%23%20handled%20by%20PAM%0A%0AMatch%20User%20foo%2Cbar%0A%20%20PasswordAuthentication%20yes%0A",
   310  								},
   311  								Mode: ptr.To(384),
   312  							},
   313  						},
   314  						{
   315  							Node: types.Node{
   316  								Filesystem: "root",
   317  								Path:       "/etc/kubeadm.sh",
   318  							},
   319  							FileEmbedded1: types.FileEmbedded1{
   320  								Contents: types.FileContents{
   321  									Source: "data:,%23!%2Fbin%2Fbash%0Aset%20-e%0A%0Apre-command%0Aanother-pre-command%0Acat%20%3C%3CEOF%20%3E%20%2Fetc%2Fmodules-load.d%2Fcontainerd.conf%0Aoverlay%0Abr_netfilter%0AEOF%0A%0A%0Akubeadm%20join%0Amkdir%20-p%20%2Frun%2Fcluster-api%20%26%26%20echo%20success%20%3E%20%2Frun%2Fcluster-api%2Fbootstrap-success.complete%0Amv%20%2Fetc%2Fkubeadm.yml%20%2Ftmp%2F%0A%0Apost-kubeadm-command%0Aanother-post-kubeamd-command%0Acat%20%3C%3CEOF%20%3E%20%2Fetc%2Fmodules-load.d%2Fcontainerd.conf%0Aoverlay%0Abr_netfilter%0AEOF%0A",
   322  								},
   323  								Mode: ptr.To(448),
   324  							},
   325  						},
   326  						{
   327  							Node: types.Node{
   328  								Filesystem: "root",
   329  								Path:       "/etc/kubeadm.yml",
   330  							},
   331  							FileEmbedded1: types.FileEmbedded1{
   332  								Contents: types.FileContents{
   333  									Source: "data:,---%0Afoo%0A",
   334  								},
   335  								Mode: ptr.To(384),
   336  							},
   337  						},
   338  					},
   339  				},
   340  				Systemd: types.Systemd{
   341  					Units: []types.Unit{
   342  						{
   343  							Contents: "[Unit]\nDescription=kubeadm\n# Run only once. After successful run, this file is moved to /tmp/.\nConditionPathExists=/etc/kubeadm.yml\nAfter=network.target\n[Service]\n# To not restart the unit when it exits, as it is expected.\nType=oneshot\nExecStart=/etc/kubeadm.sh\n[Install]\nWantedBy=multi-user.target\n",
   344  							Enabled:  ptr.To(true),
   345  							Name:     "kubeadm.service",
   346  						},
   347  					},
   348  				},
   349  			},
   350  		},
   351  		{
   352  			desc: "base64 encoded content",
   353  			input: &cloudinit.BaseUserData{
   354  				PreKubeadmCommands:  preKubeadmCommands,
   355  				PostKubeadmCommands: postKubeadmCommands,
   356  				KubeadmCommand:      "kubeadm join",
   357  				WriteFiles: []bootstrapv1.File{
   358  					{
   359  						Path:        "/etc/base64encodedcontent.yaml",
   360  						Encoding:    bootstrapv1.Base64,
   361  						Content:     "Zm9vCg==",
   362  						Permissions: "0600",
   363  					},
   364  					{
   365  						Path:        "/etc/plaincontent.yaml",
   366  						Content:     "foo",
   367  						Permissions: "0600",
   368  					},
   369  				},
   370  			},
   371  			wantIgnition: types.Config{
   372  				Ignition: types.Ignition{
   373  					Version: "2.3.0",
   374  				},
   375  				Storage: types.Storage{
   376  					Files: []types.File{
   377  						{
   378  							Node: types.Node{
   379  								Filesystem: "root",
   380  								Path:       "/etc/base64encodedcontent.yaml",
   381  							},
   382  							FileEmbedded1: types.FileEmbedded1{
   383  								Contents: types.FileContents{Source: "data:,foo%0A"},
   384  								Mode:     ptr.To(384),
   385  							},
   386  						},
   387  						{
   388  							Node: types.Node{
   389  								Filesystem: "root",
   390  								Path:       "/etc/plaincontent.yaml",
   391  							},
   392  							FileEmbedded1: types.FileEmbedded1{
   393  								Contents: types.FileContents{Source: "data:,foo%0A"},
   394  								Mode:     ptr.To(384),
   395  							},
   396  						},
   397  						{
   398  							Node: types.Node{
   399  								Filesystem: "root",
   400  								Path:       "/etc/kubeadm.sh",
   401  							},
   402  							FileEmbedded1: types.FileEmbedded1{
   403  								Contents: types.FileContents{
   404  									Source: "data:,%23!%2Fbin%2Fbash%0Aset%20-e%0A%0Apre-command%0Aanother-pre-command%0Acat%20%3C%3CEOF%20%3E%20%2Fetc%2Fmodules-load.d%2Fcontainerd.conf%0Aoverlay%0Abr_netfilter%0AEOF%0A%0A%0Akubeadm%20join%0Amkdir%20-p%20%2Frun%2Fcluster-api%20%26%26%20echo%20success%20%3E%20%2Frun%2Fcluster-api%2Fbootstrap-success.complete%0Amv%20%2Fetc%2Fkubeadm.yml%20%2Ftmp%2F%0A%0Apost-kubeadm-command%0Aanother-post-kubeamd-command%0Acat%20%3C%3CEOF%20%3E%20%2Fetc%2Fmodules-load.d%2Fcontainerd.conf%0Aoverlay%0Abr_netfilter%0AEOF%0A",
   405  								},
   406  								Mode: ptr.To(448),
   407  							},
   408  						},
   409  						{
   410  							Node: types.Node{
   411  								Filesystem: "root",
   412  								Path:       "/etc/kubeadm.yml",
   413  							},
   414  							FileEmbedded1: types.FileEmbedded1{
   415  								Contents: types.FileContents{
   416  									Source: "data:,---%0Afoo%0A",
   417  								},
   418  								Mode: ptr.To(384),
   419  							},
   420  						},
   421  					},
   422  				},
   423  				Systemd: types.Systemd{
   424  					Units: []types.Unit{
   425  						{
   426  							Contents: "[Unit]\nDescription=kubeadm\n# Run only once. After successful run, this file is moved to /tmp/.\nConditionPathExists=/etc/kubeadm.yml\nAfter=network.target\n[Service]\n# To not restart the unit when it exits, as it is expected.\nType=oneshot\nExecStart=/etc/kubeadm.sh\n[Install]\nWantedBy=multi-user.target\n",
   427  							Enabled:  ptr.To(true),
   428  							Name:     "kubeadm.service",
   429  						},
   430  					},
   431  				},
   432  			},
   433  		},
   434  		{
   435  			desc: "all file ownership combinations",
   436  			input: &cloudinit.BaseUserData{
   437  				PreKubeadmCommands:  preKubeadmCommands,
   438  				PostKubeadmCommands: postKubeadmCommands,
   439  				KubeadmCommand:      "kubeadm join",
   440  				WriteFiles: []bootstrapv1.File{
   441  					{
   442  						Path:        "/etc/username-group-name-owner.yaml",
   443  						Owner:       "nobody:nobody",
   444  						Permissions: "0600",
   445  					},
   446  					{
   447  						Path:        "/etc/user-only-owner.yaml",
   448  						Owner:       "nobody",
   449  						Permissions: "0600",
   450  					},
   451  					{
   452  						Path:        "/etc/user-only-with-colon-owner.yaml",
   453  						Owner:       "nobody:",
   454  						Permissions: "0600",
   455  					},
   456  					{
   457  						Path:        "/etc/group-only-owner.yaml",
   458  						Owner:       ":nobody",
   459  						Permissions: "0600",
   460  					},
   461  				},
   462  			},
   463  			wantIgnition: types.Config{
   464  				Ignition: types.Ignition{
   465  					Version: "2.3.0",
   466  				},
   467  				Storage: types.Storage{
   468  					Files: []types.File{
   469  						{
   470  							Node: types.Node{
   471  								Filesystem: "root",
   472  								Path:       "/etc/username-group-name-owner.yaml",
   473  								User: &types.NodeUser{
   474  									Name: "nobody",
   475  								},
   476  								Group: &types.NodeGroup{
   477  									Name: "nobody",
   478  								},
   479  							},
   480  							FileEmbedded1: types.FileEmbedded1{
   481  								Contents: types.FileContents{Source: "data:,"},
   482  								Mode:     ptr.To(384),
   483  							},
   484  						},
   485  						{
   486  							Node: types.Node{
   487  								Filesystem: "root",
   488  								Path:       "/etc/user-only-owner.yaml",
   489  								User: &types.NodeUser{
   490  									Name: "nobody",
   491  								},
   492  							},
   493  							FileEmbedded1: types.FileEmbedded1{
   494  								Contents: types.FileContents{Source: "data:,"},
   495  								Mode:     ptr.To(384),
   496  							},
   497  						},
   498  						{
   499  							Node: types.Node{
   500  								Filesystem: "root",
   501  								Path:       "/etc/user-only-with-colon-owner.yaml",
   502  								User: &types.NodeUser{
   503  									Name: "nobody",
   504  								},
   505  							},
   506  							FileEmbedded1: types.FileEmbedded1{
   507  								Contents: types.FileContents{Source: "data:,"},
   508  								Mode:     ptr.To(384),
   509  							},
   510  						},
   511  						{
   512  							Node: types.Node{
   513  								Filesystem: "root",
   514  								Path:       "/etc/group-only-owner.yaml",
   515  								Group: &types.NodeGroup{
   516  									Name: "nobody",
   517  								},
   518  							},
   519  							FileEmbedded1: types.FileEmbedded1{
   520  								Contents: types.FileContents{Source: "data:,"},
   521  								Mode:     ptr.To(384),
   522  							},
   523  						},
   524  						{
   525  							Node: types.Node{
   526  								Filesystem: "root",
   527  								Path:       "/etc/kubeadm.sh",
   528  							},
   529  							FileEmbedded1: types.FileEmbedded1{
   530  								Contents: types.FileContents{
   531  									Source: "data:,%23!%2Fbin%2Fbash%0Aset%20-e%0A%0Apre-command%0Aanother-pre-command%0Acat%20%3C%3CEOF%20%3E%20%2Fetc%2Fmodules-load.d%2Fcontainerd.conf%0Aoverlay%0Abr_netfilter%0AEOF%0A%0A%0Akubeadm%20join%0Amkdir%20-p%20%2Frun%2Fcluster-api%20%26%26%20echo%20success%20%3E%20%2Frun%2Fcluster-api%2Fbootstrap-success.complete%0Amv%20%2Fetc%2Fkubeadm.yml%20%2Ftmp%2F%0A%0Apost-kubeadm-command%0Aanother-post-kubeamd-command%0Acat%20%3C%3CEOF%20%3E%20%2Fetc%2Fmodules-load.d%2Fcontainerd.conf%0Aoverlay%0Abr_netfilter%0AEOF%0A",
   532  								},
   533  								Mode: ptr.To(448),
   534  							},
   535  						},
   536  						{
   537  							Node: types.Node{
   538  								Filesystem: "root",
   539  								Path:       "/etc/kubeadm.yml",
   540  							},
   541  							FileEmbedded1: types.FileEmbedded1{
   542  								Contents: types.FileContents{
   543  									Source: "data:,---%0Afoo%0A",
   544  								},
   545  								Mode: ptr.To(384),
   546  							},
   547  						},
   548  					},
   549  				},
   550  				Systemd: types.Systemd{
   551  					Units: []types.Unit{
   552  						{
   553  							Contents: "[Unit]\nDescription=kubeadm\n# Run only once. After successful run, this file is moved to /tmp/.\nConditionPathExists=/etc/kubeadm.yml\nAfter=network.target\n[Service]\n# To not restart the unit when it exits, as it is expected.\nType=oneshot\nExecStart=/etc/kubeadm.sh\n[Install]\nWantedBy=multi-user.target\n",
   554  							Enabled:  ptr.To(true),
   555  							Name:     "kubeadm.service",
   556  						},
   557  					},
   558  				},
   559  			},
   560  		},
   561  	}
   562  
   563  	for _, tt := range tc {
   564  		tt := tt
   565  
   566  		t.Run(tt.desc, func(t *testing.T) {
   567  			t.Parallel()
   568  
   569  			ignitionBytes, _, err := clc.Render(tt.input, &bootstrapv1.ContainerLinuxConfig{}, "foo")
   570  			if err != nil {
   571  				t.Fatalf("rendering: %v", err)
   572  			}
   573  
   574  			ign, reports, err := ignition.Parse(ignitionBytes)
   575  			if err != nil {
   576  				t.Fatalf("Parsing generated Ignition: %v", err)
   577  			}
   578  
   579  			if reports.IsFatal() {
   580  				t.Fatalf("Generated Ignition has fatal reports: %s", reports)
   581  			}
   582  
   583  			if diff := cmp.Diff(tt.wantIgnition, ign); diff != "" {
   584  				t.Fatalf("Ignition mismatch (-want +got):\n%s", diff)
   585  			}
   586  		})
   587  	}
   588  
   589  	t.Run("validates input parameter", func(t *testing.T) {
   590  		t.Parallel()
   591  
   592  		if _, _, err := clc.Render(nil, &bootstrapv1.ContainerLinuxConfig{}, "foo"); err == nil {
   593  			t.Fatal("expected error when passing empty input data")
   594  		}
   595  	})
   596  
   597  	t.Run("accepts empty clc parameter", func(t *testing.T) {
   598  		t.Parallel()
   599  
   600  		if _, _, err := clc.Render(&cloudinit.BaseUserData{}, nil, "bar"); err != nil {
   601  			t.Fatalf("unexpected error while rendering: %v", err)
   602  		}
   603  	})
   604  
   605  	t.Run("treats warnings as errors in strict mode", func(t *testing.T) {
   606  		config := &bootstrapv1.ContainerLinuxConfig{
   607  			Strict:           true,
   608  			AdditionalConfig: configWithWarning,
   609  		}
   610  
   611  		if _, _, err := clc.Render(&cloudinit.BaseUserData{}, config, "foo"); err == nil {
   612  			t.Fatalf("expected error")
   613  		}
   614  	})
   615  
   616  	t.Run("returns warnings", func(t *testing.T) {
   617  		config := &bootstrapv1.ContainerLinuxConfig{
   618  			AdditionalConfig: configWithWarning,
   619  		}
   620  
   621  		data, warnings, err := clc.Render(&cloudinit.BaseUserData{}, config, "foo")
   622  		if err != nil {
   623  			t.Fatalf("unexpected error: %v", err)
   624  		}
   625  
   626  		if warnings == "" {
   627  			t.Errorf("expected warnings to be not empty")
   628  		}
   629  
   630  		if len(data) == 0 {
   631  			t.Errorf("expected data to be returned on config with warnings")
   632  		}
   633  	})
   634  
   635  	t.Run("returns Ignition warnings", func(t *testing.T) {
   636  		config := &bootstrapv1.ContainerLinuxConfig{
   637  			AdditionalConfig: configWithIgnitionWarning,
   638  		}
   639  
   640  		data, warnings, err := clc.Render(&cloudinit.BaseUserData{}, config, "foo")
   641  		if err != nil {
   642  			t.Fatalf("unexpected error: %v", err)
   643  		}
   644  
   645  		if warnings == "" {
   646  			t.Errorf("expected warnings to be not empty")
   647  		}
   648  
   649  		if len(data) == 0 {
   650  			t.Errorf("expected data to be returned on config with warnings")
   651  		}
   652  	})
   653  }