github.com/SamarSidharth/kpt@v0.0.0-20231122062228-c7d747ae3ace/pkg/live/load_test.go (about)

     1  // Copyright 2021 The kpt Authors
     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 live
    16  
    17  import (
    18  	"bytes"
    19  	"os"
    20  	"path/filepath"
    21  	"sort"
    22  	"testing"
    23  
    24  	"github.com/GoogleContainerTools/kpt/internal/testutil"
    25  	"github.com/GoogleContainerTools/kpt/internal/testutil/pkgbuilder"
    26  	kptfile "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1"
    27  	rgfilev1alpha1 "github.com/GoogleContainerTools/kpt/pkg/api/resourcegroup/v1alpha1"
    28  	"github.com/stretchr/testify/assert"
    29  	"k8s.io/apimachinery/pkg/runtime/schema"
    30  	cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
    31  	"k8s.io/kubectl/pkg/util/slice"
    32  	"sigs.k8s.io/cli-utils/pkg/object"
    33  	"sigs.k8s.io/kustomize/kyaml/kio"
    34  )
    35  
    36  func TestLoad_LocalDisk(t *testing.T) {
    37  	testCases := map[string]struct {
    38  		pkg            *pkgbuilder.RootPkg
    39  		namespace      string
    40  		expectedObjs   object.ObjMetadataSet
    41  		expectedInv    kptfile.Inventory
    42  		expectedErrMsg string
    43  		rgFile         string
    44  	}{
    45  		"no Kptfile in root package": {
    46  			pkg: pkgbuilder.NewRootPkg().
    47  				WithFile("deployment.yaml", deploymentA),
    48  			namespace: "foo",
    49  			expectedObjs: []object.ObjMetadata{
    50  				{
    51  					GroupKind: schema.GroupKind{
    52  						Group: "apps",
    53  						Kind:  "Deployment",
    54  					},
    55  					Name:      "test-deployment",
    56  					Namespace: "foo",
    57  				},
    58  			},
    59  			expectedErrMsg: "no ResourceGroup object was provided within the stream or package",
    60  		},
    61  		"missing namespace for namespace scoped resources are defaulted": {
    62  			pkg: pkgbuilder.NewRootPkg().
    63  				WithFile("cm.yaml", configMap),
    64  			namespace: "foo",
    65  			expectedObjs: []object.ObjMetadata{
    66  				{
    67  					GroupKind: schema.GroupKind{
    68  						Kind: "ConfigMap",
    69  					},
    70  					Name:      "cm",
    71  					Namespace: "foo",
    72  				},
    73  			},
    74  			expectedErrMsg: "no ResourceGroup object was provided within the stream or package",
    75  		},
    76  		"function config is excluded": {
    77  			pkg: pkgbuilder.NewRootPkg().
    78  				WithKptfile(
    79  					pkgbuilder.NewKptfile().
    80  						WithPipeline(
    81  							pkgbuilder.NewFunction("gcr.io/kpt-dev/func:latest").
    82  								WithConfigPath("cm.yaml"),
    83  						),
    84  				).WithRGFile(pkgbuilder.NewRGFile().WithInventory(pkgbuilder.Inventory{
    85  				Name:      "foo",
    86  				Namespace: "bar",
    87  				ID:        "foo-bar"},
    88  			)).
    89  				WithFile("cm.yaml", configMap).
    90  				WithSubPackages(
    91  					pkgbuilder.NewSubPkg("subpkg").
    92  						WithKptfile(
    93  							pkgbuilder.NewKptfile().
    94  								WithPipeline(
    95  									pkgbuilder.NewFunction("gcr.io/kpt-dev/func").
    96  										WithConfigPath("deployment.yaml"),
    97  								),
    98  						).
    99  						WithFile("deployment.yaml", deploymentA),
   100  				),
   101  			namespace: "foo",
   102  			expectedInv: kptfile.Inventory{
   103  				Name:        "foo",
   104  				Namespace:   "bar",
   105  				InventoryID: "foo-bar",
   106  			},
   107  			expectedObjs: []object.ObjMetadata{
   108  				{
   109  					GroupKind: schema.GroupKind{
   110  						Kind: "ConfigMap",
   111  					},
   112  					Name:      "cm",
   113  					Namespace: "foo",
   114  				},
   115  				{
   116  					GroupKind: schema.GroupKind{
   117  						Group: "apps",
   118  						Kind:  "Deployment",
   119  					},
   120  					Name:      "test-deployment",
   121  					Namespace: "foo",
   122  				},
   123  			},
   124  		},
   125  		"inventory info is taken from the root Kptfile": {
   126  			pkg: pkgbuilder.NewRootPkg().
   127  				WithKptfile(
   128  					pkgbuilder.NewKptfile().
   129  						WithInventory(pkgbuilder.Inventory{
   130  							Name:      "foo",
   131  							Namespace: "bar",
   132  							ID:        "foo-bar",
   133  						}),
   134  				).
   135  				WithFile("cm.yaml", configMap).
   136  				WithSubPackages(
   137  					pkgbuilder.NewSubPkg("subpkg").
   138  						WithKptfile().
   139  						WithFile("deployment.yaml", deploymentA),
   140  				),
   141  			namespace: "foo",
   142  			expectedObjs: []object.ObjMetadata{
   143  				{
   144  					GroupKind: schema.GroupKind{
   145  						Kind: "ConfigMap",
   146  					},
   147  					Name:      "cm",
   148  					Namespace: "foo",
   149  				},
   150  				{
   151  					GroupKind: schema.GroupKind{
   152  						Group: "apps",
   153  						Kind:  "Deployment",
   154  					},
   155  					Name:      "test-deployment",
   156  					Namespace: "foo",
   157  				},
   158  			},
   159  			expectedInv: kptfile.Inventory{
   160  				Name:        "foo",
   161  				Namespace:   "bar",
   162  				InventoryID: "foo-bar",
   163  			},
   164  		},
   165  		"Inventory information in subpackages are ignored": {
   166  			pkg: pkgbuilder.NewRootPkg().
   167  				WithKptfile(
   168  					pkgbuilder.NewKptfile().
   169  						WithInventory(pkgbuilder.Inventory{
   170  							Name:      "foo",
   171  							Namespace: "bar",
   172  							ID:        "foo-bar",
   173  						}),
   174  				).
   175  				WithSubPackages(
   176  					pkgbuilder.NewSubPkg("subpkg").
   177  						WithKptfile(
   178  							pkgbuilder.NewKptfile().
   179  								WithInventory(pkgbuilder.Inventory{
   180  									Name:      "subpkg",
   181  									Namespace: "subpkg",
   182  									ID:        "subpkg",
   183  								}),
   184  						),
   185  				),
   186  			namespace:    "foo",
   187  			expectedObjs: []object.ObjMetadata{},
   188  			expectedInv: kptfile.Inventory{
   189  				Name:        "foo",
   190  				Namespace:   "bar",
   191  				InventoryID: "foo-bar",
   192  			},
   193  		},
   194  		"Inventory information taken from resourcegroup": {
   195  			pkg: pkgbuilder.NewRootPkg().
   196  				WithKptfile(
   197  					pkgbuilder.NewKptfile(),
   198  				).WithRGFile(pkgbuilder.NewRGFile().WithInventory(pkgbuilder.Inventory{
   199  				Name:      "foo",
   200  				Namespace: "bar",
   201  				ID:        "foo-bar"},
   202  			)),
   203  			namespace:    "foo",
   204  			expectedObjs: []object.ObjMetadata{},
   205  			expectedInv: kptfile.Inventory{
   206  				Name:        "foo",
   207  				Namespace:   "bar",
   208  				InventoryID: "foo-bar",
   209  			},
   210  			rgFile: "resourcegroup.yaml",
   211  		},
   212  	}
   213  
   214  	for tn, tc := range testCases {
   215  		t.Run(tn, func(t *testing.T) {
   216  			tf := cmdtesting.NewTestFactory().WithNamespace(tc.namespace)
   217  			defer tf.Cleanup()
   218  
   219  			dir := tc.pkg.ExpandPkg(t, nil)
   220  			defer func() {
   221  				_ = os.RemoveAll(dir)
   222  			}()
   223  
   224  			var buf bytes.Buffer
   225  			objs, inv, err := Load(tf, dir, &buf)
   226  
   227  			if tc.expectedErrMsg != "" {
   228  				if !assert.Error(t, err) {
   229  					t.FailNow()
   230  				}
   231  				assert.Contains(t, err.Error(), tc.expectedErrMsg)
   232  				return
   233  			}
   234  			assert.NoError(t, err)
   235  
   236  			objMetas := object.UnstructuredSetToObjMetadataSet(objs)
   237  			sort.Slice(objMetas, func(i, j int) bool {
   238  				return objMetas[i].String() < objMetas[j].String()
   239  			})
   240  			assert.Equal(t, tc.expectedObjs, objMetas)
   241  
   242  			assert.Equal(t, tc.expectedInv, inv)
   243  		})
   244  	}
   245  }
   246  
   247  func TestLoad_StdIn(t *testing.T) {
   248  	testCases := map[string]struct {
   249  		pkg            *pkgbuilder.RootPkg
   250  		namespace      string
   251  		expectedObjs   object.ObjMetadataSet
   252  		expectedInv    kptfile.Inventory
   253  		expectedErrMsg string
   254  		rgFile         string
   255  	}{
   256  		"no inventory among the resources": {
   257  			pkg: pkgbuilder.NewRootPkg().
   258  				WithKptfile(
   259  					pkgbuilder.NewKptfile(),
   260  				).
   261  				WithFile("deployment.yaml", deploymentA),
   262  			expectedErrMsg: "no ResourceGroup object was provided within the stream or package",
   263  		},
   264  		"missing namespace for namespace scoped resources are defaulted": {
   265  			pkg: pkgbuilder.NewRootPkg().
   266  				WithKptfile(
   267  					pkgbuilder.NewKptfile().
   268  						WithInventory(pkgbuilder.Inventory{
   269  							Name:      "foo",
   270  							Namespace: "bar",
   271  							ID:        "foo-bar",
   272  						}),
   273  				).
   274  				WithFile("cm.yaml", configMap),
   275  			namespace: "foo",
   276  			expectedObjs: []object.ObjMetadata{
   277  				{
   278  					GroupKind: schema.GroupKind{
   279  						Kind: "ConfigMap",
   280  					},
   281  					Name:      "cm",
   282  					Namespace: "foo",
   283  				},
   284  			},
   285  			expectedInv: kptfile.Inventory{
   286  				Name:        "foo",
   287  				Namespace:   "bar",
   288  				InventoryID: "foo-bar",
   289  			},
   290  		},
   291  		"inventory info is taken from the Kptfile": {
   292  			pkg: pkgbuilder.NewRootPkg().
   293  				WithKptfile(
   294  					pkgbuilder.NewKptfile().
   295  						WithInventory(pkgbuilder.Inventory{
   296  							Name:      "foo",
   297  							Namespace: "bar",
   298  							ID:        "foo-bar",
   299  						}),
   300  				).
   301  				WithFile("cm.yaml", configMap).
   302  				WithSubPackages(
   303  					pkgbuilder.NewSubPkg("subpkg").
   304  						WithKptfile().
   305  						WithFile("deployment.yaml", deploymentA),
   306  				),
   307  			namespace: "foo",
   308  			expectedObjs: []object.ObjMetadata{
   309  				{
   310  					GroupKind: schema.GroupKind{
   311  						Kind: "ConfigMap",
   312  					},
   313  					Name:      "cm",
   314  					Namespace: "foo",
   315  				},
   316  				{
   317  					GroupKind: schema.GroupKind{
   318  						Group: "apps",
   319  						Kind:  "Deployment",
   320  					},
   321  					Name:      "test-deployment",
   322  					Namespace: "foo",
   323  				},
   324  			},
   325  			expectedInv: kptfile.Inventory{
   326  				Name:        "foo",
   327  				Namespace:   "bar",
   328  				InventoryID: "foo-bar",
   329  			},
   330  		},
   331  		"Multiple Kptfiles with inventory is an error": {
   332  			pkg: pkgbuilder.NewRootPkg().
   333  				WithKptfile(
   334  					pkgbuilder.NewKptfile().
   335  						WithInventory(pkgbuilder.Inventory{
   336  							Name:      "foo",
   337  							Namespace: "bar",
   338  							ID:        "foo-bar",
   339  						}),
   340  				).
   341  				WithSubPackages(
   342  					pkgbuilder.NewSubPkg("subpkg").
   343  						WithKptfile(
   344  							pkgbuilder.NewKptfile().
   345  								WithInventory(pkgbuilder.Inventory{
   346  									Name:      "subpkg",
   347  									Namespace: "subpkg",
   348  									ID:        "subpkg",
   349  								}),
   350  						),
   351  				),
   352  			namespace:      "foo",
   353  			expectedErrMsg: "multiple Kptfile inventories found in package",
   354  		},
   355  		"Inventory using STDIN resourcegroup file": {
   356  			pkg: pkgbuilder.NewRootPkg().
   357  				WithKptfile(
   358  					pkgbuilder.NewKptfile(),
   359  				).
   360  				WithFile("cm.yaml", configMap).
   361  				WithRGFile(pkgbuilder.NewRGFile().WithInventory(pkgbuilder.Inventory{
   362  					Name:      "foo",
   363  					Namespace: "bar",
   364  					ID:        "foo-bar",
   365  				},
   366  				)),
   367  			namespace: "foo",
   368  			expectedInv: kptfile.Inventory{
   369  				Name:        "foo",
   370  				Namespace:   "bar",
   371  				InventoryID: "foo-bar",
   372  			},
   373  			expectedObjs: []object.ObjMetadata{
   374  				{
   375  					GroupKind: schema.GroupKind{
   376  						Kind: "ConfigMap",
   377  					},
   378  					Name:      "cm",
   379  					Namespace: "foo",
   380  				},
   381  			},
   382  		},
   383  		"Multiple inventories using STDIN resourcegroup and Kptfile is error": {
   384  			pkg: pkgbuilder.NewRootPkg().
   385  				WithKptfile(
   386  					pkgbuilder.NewKptfile().
   387  						WithInventory(pkgbuilder.Inventory{
   388  							Name:      "foo",
   389  							Namespace: "bar",
   390  							ID:        "foo-bar",
   391  						}),
   392  				).
   393  				WithFile("cm.yaml", configMap).
   394  				WithRGFile(pkgbuilder.NewRGFile().WithInventory(pkgbuilder.Inventory{
   395  					Name:      "foo",
   396  					Namespace: "bar",
   397  					ID:        "foo-bar",
   398  				},
   399  				)),
   400  			expectedErrMsg: "inventory was found in both Kptfile and ResourceGroup object",
   401  		},
   402  		"Non-valid inventory using STDIN Kptfile is error": {
   403  			pkg: pkgbuilder.NewRootPkg().
   404  				WithKptfile(
   405  					pkgbuilder.NewKptfile().
   406  						WithInventory(pkgbuilder.Inventory{
   407  							Name: "foo",
   408  						}),
   409  				).
   410  				WithFile("cm.yaml", configMap),
   411  			expectedErrMsg: "the provided ResourceGroup is not valid",
   412  		},
   413  		"Non-valid inventory in resourcegroup is error": {
   414  			pkg: pkgbuilder.NewRootPkg().
   415  				WithKptfile(
   416  					pkgbuilder.NewKptfile(),
   417  				).
   418  				WithFile("cm.yaml", configMap).
   419  				WithRGFile(pkgbuilder.NewRGFile().WithInventory(pkgbuilder.Inventory{
   420  					Name: "foo",
   421  				},
   422  				)),
   423  			expectedErrMsg: "the provided ResourceGroup is not valid",
   424  			rgFile:         rgfilev1alpha1.RGFileName,
   425  		},
   426  	}
   427  
   428  	for tn, tc := range testCases {
   429  		t.Run(tn, func(t *testing.T) {
   430  			tf := cmdtesting.NewTestFactory().WithNamespace(tc.namespace)
   431  			defer tf.Cleanup()
   432  
   433  			dir := tc.pkg.ExpandPkg(t, nil)
   434  			defer func() {
   435  				_ = os.RemoveAll(dir)
   436  			}()
   437  
   438  			revert := testutil.Chdir(t, dir)
   439  			defer revert()
   440  
   441  			var buf bytes.Buffer
   442  			err := (&kio.Pipeline{
   443  				Inputs: []kio.Reader{
   444  					kio.LocalPackageReader{
   445  						PackagePath:           dir,
   446  						OmitReaderAnnotations: true,
   447  						MatchFilesGlob:        append([]string{kptfile.KptFileName}, kio.DefaultMatch...),
   448  						IncludeSubpackages:    true,
   449  						WrapBareSeqNode:       true,
   450  					},
   451  				},
   452  				Outputs: []kio.Writer{
   453  					kio.ByteWriter{
   454  						Writer: &buf,
   455  					},
   456  				},
   457  			}).Execute()
   458  			if !assert.NoError(t, err) {
   459  				t.FailNow()
   460  			}
   461  
   462  			os.Remove(filepath.Join(dir, kptfile.KptFileName))
   463  			os.Remove(filepath.Join(dir, tc.rgFile))
   464  
   465  			objs, inv, err := Load(tf, "-", &buf)
   466  
   467  			if tc.expectedErrMsg != "" {
   468  				if !assert.Error(t, err) {
   469  					t.FailNow()
   470  				}
   471  				assert.Contains(t, err.Error(), tc.expectedErrMsg)
   472  				return
   473  			}
   474  			assert.NoError(t, err)
   475  
   476  			objMetas := object.UnstructuredSetToObjMetadataSet(objs)
   477  			sort.Slice(objMetas, func(i, j int) bool {
   478  				return objMetas[i].String() < objMetas[j].String()
   479  			})
   480  			assert.Equal(t, tc.expectedObjs, objMetas)
   481  
   482  			assert.Equal(t, tc.expectedInv, inv)
   483  		})
   484  	}
   485  }
   486  
   487  func TestValidateInventory(t *testing.T) {
   488  	testCases := map[string]struct {
   489  		inventory           kptfile.Inventory
   490  		expectErr           bool
   491  		expectedErrorFields []string
   492  	}{
   493  		"complete inventory info validate": {
   494  			inventory: kptfile.Inventory{
   495  				Name:        "foo",
   496  				Namespace:   "default",
   497  				InventoryID: "foo-default",
   498  			},
   499  			expectErr: false,
   500  		},
   501  		"inventory info without name doesn't validate": {
   502  			inventory: kptfile.Inventory{
   503  				Namespace:   "default",
   504  				InventoryID: "foo-default",
   505  			},
   506  			expectErr:           true,
   507  			expectedErrorFields: []string{"name"},
   508  		},
   509  		"inventory namespace doesn't validate": {
   510  			inventory: kptfile.Inventory{
   511  				Name: "foo",
   512  			},
   513  			expectErr:           true,
   514  			expectedErrorFields: []string{"namespace", "inventoryID"},
   515  		},
   516  	}
   517  
   518  	for tn, tc := range testCases {
   519  		t.Run(tn, func(t *testing.T) {
   520  			err := validateInventory(tc.inventory)
   521  
   522  			if !tc.expectErr {
   523  				assert.NoError(t, err)
   524  				return
   525  			}
   526  
   527  			if !assert.Error(t, err) {
   528  				t.FailNow()
   529  			}
   530  
   531  			invInfoValError, ok := err.(*InventoryInfoValidationError)
   532  			if !ok {
   533  				t.Errorf("expected error of type *InventoryInfoValidationError")
   534  				t.FailNow()
   535  			}
   536  			assert.Equal(t, len(tc.expectedErrorFields), len(invInfoValError.Violations))
   537  			fields := invInfoValError.Violations.Fields()
   538  			for i := range tc.expectedErrorFields {
   539  				if !slice.ContainsString(fields, tc.expectedErrorFields[i], nil) {
   540  					t.Errorf("expected error for field %s, but didn't find it", tc.expectedErrorFields[i])
   541  				}
   542  			}
   543  		})
   544  	}
   545  }