github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/commands/live/init/cmdliveinit_test.go (about)

     1  // Copyright 2020 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package init
    16  
    17  import (
    18  	"os"
    19  	"path/filepath"
    20  	"regexp"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/GoogleContainerTools/kpt/internal/pkg"
    25  	"github.com/GoogleContainerTools/kpt/internal/printer/fake"
    26  	"github.com/GoogleContainerTools/kpt/internal/testutil"
    27  	kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1"
    28  	rgfilev1alpha1 "github.com/GoogleContainerTools/kpt/pkg/api/resourcegroup/v1alpha1"
    29  	"github.com/stretchr/testify/assert"
    30  	"k8s.io/cli-runtime/pkg/genericclioptions"
    31  	cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
    32  	"sigs.k8s.io/kustomize/kyaml/filesys"
    33  )
    34  
    35  var (
    36  	inventoryName      = "inventory-obj-name"
    37  	inventoryNamespace = "test-namespace"
    38  	inventoryID        = "XXXXXXX-OOOOOOOOOO-XXXX"
    39  )
    40  
    41  var kptFile = `
    42  apiVersion: kpt.dev/v1
    43  kind: Kptfile
    44  metadata:
    45    name: test1
    46  upstreamLock:
    47    type: git
    48    git:
    49      repo: git@github.com:seans3/blueprint-helloworld
    50      directory: /
    51      ref: master
    52  `
    53  
    54  const testInventoryID = "SSSSSSSSSS-RRRRR"
    55  
    56  var kptFileWithInventory = `
    57  apiVersion: kpt.dev/v1
    58  kind: Kptfile
    59  metadata:
    60    name: test1
    61  upstreamLock:
    62    type: git
    63    git:
    64      repo: git@github.com:seans3/blueprint-helloworld
    65      directory: /
    66      ref: master
    67  inventory:
    68      name: foo
    69      namespace: test-namespace
    70      inventoryID: ` + testInventoryID + "\n"
    71  
    72  var testTime = time.Unix(5555555, 66666666)
    73  
    74  var resourceGroupInventory = `
    75  apiVersion: kpt.dev/v1alpha1
    76  kind: ResourceGroup
    77  metadata:
    78    name: foo
    79    namespace: test-namespace
    80  `
    81  
    82  func TestCmd_generateID(t *testing.T) {
    83  	testCases := map[string]struct {
    84  		namespace string
    85  		name      string
    86  		t         time.Time
    87  		expected  string
    88  		isError   bool
    89  	}{
    90  		"Empty inventory namespace is an error": {
    91  			name:      inventoryName,
    92  			namespace: "",
    93  			t:         testTime,
    94  			isError:   true,
    95  		},
    96  		"Empty inventory name is an error": {
    97  			name:      "",
    98  			namespace: inventoryNamespace,
    99  			t:         testTime,
   100  			isError:   true,
   101  		},
   102  		"Namespace/name hash is valid": {
   103  			name:      inventoryName,
   104  			namespace: inventoryNamespace,
   105  			t:         testTime,
   106  			expected:  "fa6dc0d39b0465b90f101c2ad50d50e9b4022f23-5555555066666666",
   107  			isError:   false,
   108  		},
   109  	}
   110  
   111  	for tn, tc := range testCases {
   112  		t.Run(tn, func(t *testing.T) {
   113  			actual, err := generateID(tc.namespace, tc.name, tc.t)
   114  			// Check if there should be an error
   115  			if tc.isError {
   116  				if err == nil {
   117  					t.Fatalf("expected error but received none")
   118  				}
   119  				return
   120  			}
   121  			assert.NoError(t, err)
   122  			if tc.expected != actual {
   123  				t.Errorf("expecting generated id (%s), got (%s)", tc.expected, actual)
   124  			}
   125  		})
   126  	}
   127  }
   128  
   129  func TestCmd_Run(t *testing.T) {
   130  	testCases := map[string]struct {
   131  		kptfile           string
   132  		resourcegroup     string
   133  		rgfilename        string
   134  		name              string
   135  		namespace         string
   136  		inventoryID       string
   137  		force             bool
   138  		expectedErrorMsg  string
   139  		expectAutoGenID   bool
   140  		expectedInventory kptfilev1.Inventory
   141  	}{
   142  		"Fields are defaulted if not provided": {
   143  			kptfile:         kptFile,
   144  			name:            "",
   145  			rgfilename:      "resourcegroup.yaml",
   146  			namespace:       "testns",
   147  			inventoryID:     "",
   148  			expectAutoGenID: true,
   149  			expectedInventory: kptfilev1.Inventory{
   150  				Namespace: "testns",
   151  				Name:      "inventory-*",
   152  			},
   153  		},
   154  		"Provided values are used": {
   155  			kptfile:     kptFile,
   156  			rgfilename:  "custom-rg.yaml",
   157  			name:        "my-pkg",
   158  			namespace:   "my-ns",
   159  			inventoryID: "my-inv-id",
   160  			expectedInventory: kptfilev1.Inventory{
   161  				Namespace:   "my-ns",
   162  				Name:        "my-pkg",
   163  				InventoryID: "my-inv-id",
   164  			},
   165  		},
   166  		"Provided values are used with custom resourcegroup filename": {
   167  			kptfile:     kptFile,
   168  			rgfilename:  "custom-rg.yaml",
   169  			name:        "my-pkg",
   170  			namespace:   "my-ns",
   171  			inventoryID: "my-inv-id",
   172  			expectedInventory: kptfilev1.Inventory{
   173  				Namespace:   "my-ns",
   174  				Name:        "my-pkg",
   175  				InventoryID: "my-inv-id",
   176  			},
   177  		},
   178  		"Kptfile with inventory already set is error": {
   179  			kptfile:          kptFileWithInventory,
   180  			name:             inventoryName,
   181  			rgfilename:       "custom-rg.yaml",
   182  			namespace:        inventoryNamespace,
   183  			inventoryID:      inventoryID,
   184  			force:            false,
   185  			expectedErrorMsg: "inventory information already set",
   186  		},
   187  		"ResourceGroup with inventory already set is error": {
   188  			kptfile:          kptFile,
   189  			resourcegroup:    resourceGroupInventory,
   190  			rgfilename:       "resourcegroup.yaml",
   191  			name:             inventoryName,
   192  			namespace:        inventoryNamespace,
   193  			inventoryID:      inventoryID,
   194  			force:            false,
   195  			expectedErrorMsg: "inventory information already set for package",
   196  		},
   197  		"ResourceGroup with inventory and Kptfile with inventory already set is error": {
   198  			kptfile:          kptFileWithInventory,
   199  			resourcegroup:    resourceGroupInventory,
   200  			rgfilename:       "resourcegroup.yaml",
   201  			name:             inventoryName,
   202  			namespace:        inventoryNamespace,
   203  			inventoryID:      inventoryID,
   204  			force:            false,
   205  			expectedErrorMsg: "inventory information already set",
   206  		},
   207  		"The force flag allows changing inventory information even if already set in Kptfile": {
   208  			kptfile:     kptFileWithInventory,
   209  			name:        inventoryName,
   210  			rgfilename:  "resourcegroup.yaml",
   211  			namespace:   inventoryNamespace,
   212  			inventoryID: inventoryID,
   213  			force:       true,
   214  			expectedInventory: kptfilev1.Inventory{
   215  				Namespace:   inventoryNamespace,
   216  				Name:        inventoryName,
   217  				InventoryID: inventoryID,
   218  			},
   219  		},
   220  		"The force flag allows changing inventory information even if already set in ResourceGroup": {
   221  			kptfile:       kptFile,
   222  			resourcegroup: resourceGroupInventory,
   223  			rgfilename:    "resourcegroup.yaml",
   224  			name:          inventoryName,
   225  			namespace:     inventoryNamespace,
   226  			inventoryID:   inventoryID,
   227  			force:         true,
   228  			expectedInventory: kptfilev1.Inventory{
   229  				Namespace:   inventoryNamespace,
   230  				Name:        inventoryName,
   231  				InventoryID: inventoryID,
   232  			},
   233  		},
   234  	}
   235  
   236  	for tn, tc := range testCases {
   237  		t.Run(tn, func(t *testing.T) {
   238  			// Set up fake test factory
   239  			tf := cmdtesting.NewTestFactory().WithNamespace(tc.namespace)
   240  			defer tf.Cleanup()
   241  			ioStreams, _, _, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled
   242  
   243  			w, clean := testutil.SetupWorkspace(t)
   244  			defer clean()
   245  			err := os.WriteFile(filepath.Join(w.WorkspaceDirectory, kptfilev1.KptFileName),
   246  				[]byte(tc.kptfile), 0600)
   247  			if !assert.NoError(t, err) {
   248  				t.FailNow()
   249  			}
   250  
   251  			// Create ResourceGroup file if testing the STDIN feature.
   252  			if tc.resourcegroup != "" && tc.rgfilename != "" {
   253  				err := os.WriteFile(filepath.Join(w.WorkspaceDirectory, tc.rgfilename),
   254  					[]byte(tc.resourcegroup), 0600)
   255  				if !assert.NoError(t, err) {
   256  					t.FailNow()
   257  				}
   258  			}
   259  
   260  			revert := testutil.Chdir(t, w.WorkspaceDirectory)
   261  			defer revert()
   262  
   263  			runner := NewRunner(fake.CtxWithDefaultPrinter(), tf, ioStreams)
   264  			runner.RGFileName = tc.rgfilename
   265  			args := []string{
   266  				"--name", tc.name,
   267  				"--inventory-id", tc.inventoryID,
   268  			}
   269  			if tc.force {
   270  				args = append(args, "--force")
   271  			}
   272  			runner.Command.SetArgs(args)
   273  
   274  			err = runner.Command.Execute()
   275  
   276  			// Check if there should be an error
   277  			if tc.expectedErrorMsg != "" {
   278  				if !assert.Error(t, err) {
   279  					t.FailNow()
   280  				}
   281  				assert.Contains(t, err.Error(), tc.expectedErrorMsg)
   282  				return
   283  			}
   284  
   285  			// Otherwise, validate the kptfile values and/or resourcegroup values.
   286  			var actualInv kptfilev1.Inventory
   287  			assert.NoError(t, err)
   288  			kf, err := pkg.ReadKptfile(filesys.FileSystemOrOnDisk{}, w.WorkspaceDirectory)
   289  			assert.NoError(t, err)
   290  
   291  			switch tc.rgfilename {
   292  			case "":
   293  				if !assert.NotNil(t, kf.Inventory) {
   294  					t.FailNow()
   295  				}
   296  				actualInv = *kf.Inventory
   297  			default:
   298  				// Check resourcegroup file if testing the STDIN feature.
   299  				rg, err := pkg.ReadRGFile(w.WorkspaceDirectory, tc.rgfilename)
   300  				assert.NoError(t, err)
   301  				if !assert.NotNil(t, rg) {
   302  					t.FailNow()
   303  				}
   304  
   305  				// Convert resourcegroup inventory back to Kptfile structure so we can share assertion
   306  				// logic for Kptfile inventory and ResourceGroup inventory structure.
   307  				actualInv = kptfilev1.Inventory{
   308  					Name:        rg.Name,
   309  					Namespace:   rg.Namespace,
   310  					InventoryID: rg.Labels[rgfilev1alpha1.RGInventoryIDLabel],
   311  				}
   312  			}
   313  
   314  			expectedInv := tc.expectedInventory
   315  			assertInventoryName(t, expectedInv.Name, actualInv.Name)
   316  			assert.Equal(t, expectedInv.Namespace, actualInv.Namespace)
   317  			if tc.expectAutoGenID {
   318  				assertGenInvID(t, actualInv.Name, actualInv.Namespace, actualInv.InventoryID)
   319  			} else {
   320  				assert.Equal(t, expectedInv.InventoryID, actualInv.InventoryID)
   321  			}
   322  		})
   323  	}
   324  }
   325  
   326  func assertInventoryName(t *testing.T, expected, actual string) bool {
   327  	re := regexp.MustCompile(`^inventory-[0-9]+$`)
   328  	if expected == "inventory-*" {
   329  		if re.MatchString(actual) {
   330  			return true
   331  		}
   332  		t.Errorf("expected value on the format 'inventory-[0-9]+', but found %q", actual)
   333  	}
   334  	return assert.Equal(t, expected, actual)
   335  }
   336  
   337  func assertGenInvID(t *testing.T, name, namespace, actual string) bool {
   338  	re := regexp.MustCompile(`^([a-z0-9]+)-[0-9]+$`)
   339  	match := re.FindStringSubmatch(actual)
   340  	if len(match) != 2 {
   341  		t.Errorf("unexpected format for autogenerated inventoryID")
   342  		return false
   343  	}
   344  	prefix, err := generateHash(namespace, name)
   345  	if err != nil {
   346  		panic(err)
   347  	}
   348  	if got, want := match[1], prefix; got != want {
   349  		t.Errorf("expected prefix %q, but found %q", want, got)
   350  		return false
   351  	}
   352  	return true
   353  }