sigs.k8s.io/cluster-api@v1.7.1/bootstrap/kubeadm/internal/ignition/ignition_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 ignition_test
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"net/url"
    23  	"strings"
    24  	"testing"
    25  
    26  	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
    27  	"sigs.k8s.io/cluster-api/bootstrap/kubeadm/internal/cloudinit"
    28  	"sigs.k8s.io/cluster-api/bootstrap/kubeadm/internal/ignition"
    29  )
    30  
    31  const testString = "foo bar baz"
    32  
    33  func Test_NewNode(t *testing.T) {
    34  	t.Parallel()
    35  
    36  	t.Run("returns error when", func(t *testing.T) {
    37  		t.Parallel()
    38  
    39  		cases := map[string]*ignition.NodeInput{
    40  			"nil input is given":      nil,
    41  			"nil node input is given": {},
    42  		}
    43  
    44  		for name, input := range cases {
    45  			input := input
    46  
    47  			t.Run(name, func(t *testing.T) {
    48  				t.Parallel()
    49  
    50  				ignitionData, _, err := ignition.NewNode(input)
    51  				if err == nil {
    52  					t.Fatalf("Expected error")
    53  				}
    54  
    55  				if ignitionData != nil {
    56  					t.Fatalf("Unexpected data returned %v", ignitionData)
    57  				}
    58  			})
    59  		}
    60  	})
    61  
    62  	t.Run("returns JSON data without error", func(t *testing.T) {
    63  		t.Parallel()
    64  
    65  		input := &ignition.NodeInput{
    66  			NodeInput: &cloudinit.NodeInput{},
    67  			Ignition:  &bootstrapv1.IgnitionSpec{},
    68  		}
    69  
    70  		ignitionData, _, err := ignition.NewNode(input)
    71  		if err != nil {
    72  			t.Fatalf("Unexpected error: %v", err)
    73  		}
    74  
    75  		if ignitionData == nil {
    76  			t.Fatalf("Returned data is nil")
    77  		}
    78  
    79  		decodedValue := map[string]interface{}{}
    80  
    81  		if err := json.Unmarshal(ignitionData, &decodedValue); err != nil {
    82  			t.Fatalf("Decoding received Ignition data as JSON: %v", err)
    83  		}
    84  	})
    85  
    86  	t.Run("returns Ignition with user-specified snippet", func(t *testing.T) {
    87  		t.Parallel()
    88  
    89  		input := &ignition.NodeInput{
    90  			NodeInput: &cloudinit.NodeInput{},
    91  			Ignition: &bootstrapv1.IgnitionSpec{
    92  				ContainerLinuxConfig: &bootstrapv1.ContainerLinuxConfig{
    93  					AdditionalConfig: fmt.Sprintf(`storage:
    94    files:
    95    - path: /etc/foo
    96      mode: 0644
    97      contents:
    98        inline: |
    99          %s
   100  `, testString),
   101  				},
   102  			},
   103  		}
   104  
   105  		ignitionData, _, err := ignition.NewNode(input)
   106  		if err != nil {
   107  			t.Fatalf("Unexpected error: %v", err)
   108  		}
   109  
   110  		// Ignition stores content URL-encoded.
   111  		u := url.URL{Path: testString}
   112  
   113  		if !strings.Contains(string(ignitionData), u.String()) {
   114  			t.Fatalf("Expected %q to be included in %q", testString, string(ignitionData))
   115  		}
   116  	})
   117  
   118  	t.Run("returns warnings if any", func(t *testing.T) {
   119  		t.Parallel()
   120  
   121  		input := &ignition.NodeInput{
   122  			NodeInput: &cloudinit.NodeInput{},
   123  			Ignition: &bootstrapv1.IgnitionSpec{
   124  				ContainerLinuxConfig: &bootstrapv1.ContainerLinuxConfig{
   125  					AdditionalConfig: fmt.Sprintf(`storage:
   126    files:
   127    - path: /etc/foo
   128      contents:
   129        inline: |
   130          %s
   131  `, testString),
   132  				},
   133  			},
   134  		}
   135  
   136  		ignitionData, warnings, err := ignition.NewNode(input)
   137  		if err != nil {
   138  			t.Fatalf("Unexpected error: %v", err)
   139  		}
   140  
   141  		if warnings == "" {
   142  			t.Fatalf("Expected warnings")
   143  		}
   144  
   145  		if len(ignitionData) == 0 {
   146  			t.Fatalf("Data should be returned with warnings but no errors")
   147  		}
   148  	})
   149  }
   150  
   151  func Test_NewJoinControlPlane(t *testing.T) {
   152  	t.Parallel()
   153  
   154  	t.Run("returns error when", func(t *testing.T) {
   155  		t.Parallel()
   156  
   157  		cases := map[string]*ignition.ControlPlaneJoinInput{
   158  			"nil input is given":      nil,
   159  			"nil node input is given": {},
   160  		}
   161  
   162  		for name, input := range cases {
   163  			input := input
   164  
   165  			t.Run(name, func(t *testing.T) {
   166  				t.Parallel()
   167  
   168  				ignitionData, _, err := ignition.NewJoinControlPlane(input)
   169  				if err == nil {
   170  					t.Fatalf("Expected error")
   171  				}
   172  
   173  				if ignitionData != nil {
   174  					t.Fatalf("Unexpected data returned %v", ignitionData)
   175  				}
   176  			})
   177  		}
   178  	})
   179  
   180  	t.Run("returns JSON data without error", func(t *testing.T) {
   181  		t.Parallel()
   182  
   183  		input := &ignition.ControlPlaneJoinInput{
   184  			ControlPlaneJoinInput: &cloudinit.ControlPlaneJoinInput{},
   185  			Ignition:              &bootstrapv1.IgnitionSpec{},
   186  		}
   187  
   188  		ignitionData, _, err := ignition.NewJoinControlPlane(input)
   189  		if err != nil {
   190  			t.Fatalf("Unexpected error: %v", err)
   191  		}
   192  
   193  		if ignitionData == nil {
   194  			t.Fatalf("Returned data is nil")
   195  		}
   196  
   197  		decodedValue := map[string]interface{}{}
   198  
   199  		if err := json.Unmarshal(ignitionData, &decodedValue); err != nil {
   200  			t.Fatalf("Decoding received Ignition data as JSON: %v", err)
   201  		}
   202  	})
   203  
   204  	t.Run("returns Ignition with user specified snippet", func(t *testing.T) {
   205  		t.Parallel()
   206  
   207  		input := &ignition.ControlPlaneJoinInput{
   208  			ControlPlaneJoinInput: &cloudinit.ControlPlaneJoinInput{},
   209  			Ignition: &bootstrapv1.IgnitionSpec{
   210  				ContainerLinuxConfig: &bootstrapv1.ContainerLinuxConfig{
   211  					AdditionalConfig: fmt.Sprintf(`storage:
   212    files:
   213    - path: /etc/foo
   214      mode: 0644
   215      contents:
   216        inline: |
   217          %s
   218  `, testString),
   219  				},
   220  			},
   221  		}
   222  
   223  		ignitionData, _, err := ignition.NewJoinControlPlane(input)
   224  		if err != nil {
   225  			t.Fatalf("Unexpected error: %v", err)
   226  		}
   227  
   228  		// Ignition stores content URL-encoded.
   229  		u := url.URL{Path: testString}
   230  
   231  		if !strings.Contains(string(ignitionData), u.String()) {
   232  			t.Fatalf("Expected %q to be included in %q", testString, string(ignitionData))
   233  		}
   234  	})
   235  
   236  	t.Run("returns warnings if any", func(t *testing.T) {
   237  		t.Parallel()
   238  
   239  		input := &ignition.ControlPlaneJoinInput{
   240  			ControlPlaneJoinInput: &cloudinit.ControlPlaneJoinInput{},
   241  			Ignition: &bootstrapv1.IgnitionSpec{
   242  				ContainerLinuxConfig: &bootstrapv1.ContainerLinuxConfig{
   243  					AdditionalConfig: fmt.Sprintf(`storage:
   244    files:
   245    - path: /etc/foo
   246      contents:
   247        inline: |
   248          %s
   249  `, testString),
   250  				},
   251  			},
   252  		}
   253  
   254  		ignitionData, warnings, err := ignition.NewJoinControlPlane(input)
   255  		if err != nil {
   256  			t.Fatalf("Unexpected error: %v", err)
   257  		}
   258  
   259  		if warnings == "" {
   260  			t.Fatalf("Expected to get some warnings")
   261  		}
   262  
   263  		if len(ignitionData) == 0 {
   264  			t.Fatalf("Data should be returned with warnings but no errors")
   265  		}
   266  	})
   267  }
   268  
   269  func Test_NewInitControlPlane(t *testing.T) {
   270  	t.Parallel()
   271  
   272  	t.Run("returns error when", func(t *testing.T) {
   273  		t.Parallel()
   274  
   275  		cases := map[string]*ignition.ControlPlaneInput{
   276  			"nil input is given":      nil,
   277  			"nil node input is given": {},
   278  		}
   279  
   280  		for name, input := range cases {
   281  			input := input
   282  
   283  			t.Run(name, func(t *testing.T) {
   284  				t.Parallel()
   285  
   286  				ignitionData, _, err := ignition.NewInitControlPlane(input)
   287  				if err == nil {
   288  					t.Fatalf("Expected error")
   289  				}
   290  
   291  				if ignitionData != nil {
   292  					t.Fatalf("Unexpected data returned %v", ignitionData)
   293  				}
   294  			})
   295  		}
   296  	})
   297  
   298  	t.Run("returns without error", func(t *testing.T) {
   299  		t.Parallel()
   300  
   301  		input := &ignition.ControlPlaneInput{
   302  			ControlPlaneInput: &cloudinit.ControlPlaneInput{},
   303  		}
   304  
   305  		ignitionData, _, err := ignition.NewInitControlPlane(input)
   306  		if err != nil {
   307  			t.Fatalf("Unexpected error: %v", err)
   308  		}
   309  
   310  		if ignitionData == nil {
   311  			t.Fatalf("Returned data is nil")
   312  		}
   313  
   314  		t.Run("valid_JSON_data", func(t *testing.T) {
   315  			decodedValue := map[string]interface{}{}
   316  
   317  			if err := json.Unmarshal(ignitionData, &decodedValue); err != nil {
   318  				t.Fatalf("Decoding received Ignition data as JSON: %v", err)
   319  			}
   320  		})
   321  	})
   322  
   323  	t.Run("returns Ignition with user-specified snippet", func(t *testing.T) {
   324  		t.Parallel()
   325  
   326  		input := &ignition.ControlPlaneInput{
   327  			ControlPlaneInput: &cloudinit.ControlPlaneInput{},
   328  			Ignition: &bootstrapv1.IgnitionSpec{
   329  				ContainerLinuxConfig: &bootstrapv1.ContainerLinuxConfig{
   330  					AdditionalConfig: fmt.Sprintf(`storage:
   331    files:
   332    - path: /etc/foo
   333      mode: 0644
   334      contents:
   335        inline: |
   336          %s
   337  `, testString),
   338  				},
   339  			},
   340  		}
   341  
   342  		ignitionData, _, err := ignition.NewInitControlPlane(input)
   343  		if err != nil {
   344  			t.Fatalf("Unexpected error: %v", err)
   345  		}
   346  
   347  		// Ignition stores content URL-encoded.
   348  		u := url.URL{Path: testString}
   349  
   350  		if !strings.Contains(string(ignitionData), u.String()) {
   351  			t.Fatalf("Expected %q to be included in %q", testString, string(ignitionData))
   352  		}
   353  	})
   354  
   355  	t.Run("returns warnings if any", func(t *testing.T) {
   356  		t.Parallel()
   357  
   358  		input := &ignition.ControlPlaneInput{
   359  			ControlPlaneInput: &cloudinit.ControlPlaneInput{},
   360  			Ignition: &bootstrapv1.IgnitionSpec{
   361  				ContainerLinuxConfig: &bootstrapv1.ContainerLinuxConfig{
   362  					AdditionalConfig: fmt.Sprintf(`storage:
   363    files:
   364    - path: /etc/foo
   365      contents:
   366        inline: |
   367          %s
   368  `, testString),
   369  				},
   370  			},
   371  		}
   372  
   373  		ignitionData, warnings, err := ignition.NewInitControlPlane(input)
   374  		if err != nil {
   375  			t.Fatalf("Unexpected error: %v", err)
   376  		}
   377  
   378  		if warnings == "" {
   379  			t.Fatalf("Expected warnings")
   380  		}
   381  
   382  		if len(ignitionData) == 0 {
   383  			t.Fatalf("Data should be returned with warnings but no errors")
   384  		}
   385  	})
   386  }