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 }