github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/commands/live/migrate/migratecmd_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 migrate 16 17 import ( 18 "os" 19 "path/filepath" 20 "strings" 21 "testing" 22 23 "github.com/GoogleContainerTools/kpt/internal/pkg" 24 "github.com/GoogleContainerTools/kpt/internal/printer/fake" 25 rgfilev1alpha1 "github.com/GoogleContainerTools/kpt/pkg/api/resourcegroup/v1alpha1" 26 "github.com/stretchr/testify/assert" 27 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 28 "k8s.io/cli-runtime/pkg/genericclioptions" 29 cmdtesting "k8s.io/kubectl/pkg/cmd/testing" 30 "k8s.io/kubectl/pkg/cmd/util" 31 "sigs.k8s.io/cli-utils/pkg/common" 32 "sigs.k8s.io/cli-utils/pkg/inventory" 33 "sigs.k8s.io/cli-utils/pkg/manifestreader" 34 "sigs.k8s.io/cli-utils/pkg/object" 35 "sigs.k8s.io/kustomize/kyaml/filesys" 36 ) 37 38 var testNamespace = "test-inventory-namespace" 39 var inventoryObjName = "test-inventory-obj" 40 var testInventoryLabel = "test-inventory-label" 41 42 var rgInvObj = &unstructured.Unstructured{ 43 Object: map[string]interface{}{ 44 "apiVersion": "kpt.dev/v1alpha1", 45 "kind": "ResourceGroup", 46 "metadata": map[string]interface{}{ 47 "name": inventoryObjName, 48 "namespace": testNamespace, 49 "labels": map[string]interface{}{ 50 common.InventoryLabel: testInventoryLabel, 51 }, 52 }, 53 "spec": map[string]interface{}{ 54 "resources": []interface{}{}, 55 }, 56 }, 57 } 58 59 var cmInvStr = ` 60 kind: ConfigMap 61 apiVersion: v1 62 metadata: 63 name: test-inventory-obj 64 namespace: test-inventory-namespace 65 labels: 66 cli-utils.sigs.k8s.io/inventory-id: test-inventory-label 67 ` 68 69 var cmInvObj = &unstructured.Unstructured{ 70 Object: map[string]interface{}{ 71 "apiVersion": "v1", 72 "kind": "ConfigMap", 73 "metadata": map[string]interface{}{ 74 "name": inventoryObjName, 75 "namespace": testNamespace, 76 "labels": map[string]interface{}{ 77 common.InventoryLabel: testInventoryLabel, 78 }, 79 }, 80 }, 81 } 82 83 var pod1 = &unstructured.Unstructured{ 84 Object: map[string]interface{}{ 85 "apiVersion": "v1", 86 "kind": "Pod", 87 "metadata": map[string]interface{}{ 88 "name": "pod-1", 89 "namespace": testNamespace, 90 }, 91 }, 92 } 93 94 var pod2 = &unstructured.Unstructured{ 95 Object: map[string]interface{}{ 96 "apiVersion": "v1", 97 "kind": "Pod", 98 "metadata": map[string]interface{}{ 99 "name": "pod-2", 100 "namespace": testNamespace, 101 }, 102 }, 103 } 104 105 func TestKptMigrate_migrateKptfileToRG(t *testing.T) { 106 testCases := map[string]struct { 107 kptfile string 108 rgFilename string 109 resourcegroup string 110 dryRun bool 111 isError bool 112 }{ 113 "Missing Kptfile is an error": { 114 kptfile: "", 115 rgFilename: "resourcegroup.yaml", 116 dryRun: false, 117 isError: true, 118 }, 119 "Kptfile with existing inventory will create ResourceGroup": { 120 kptfile: kptFileWithInventory, 121 rgFilename: "resourcegroup.yaml", 122 dryRun: false, 123 isError: false, 124 }, 125 "ResopurceGroup file already exists will error": { 126 kptfile: kptFileWithInventory, 127 rgFilename: "resourcegroup.yaml", 128 resourcegroup: resourceGroupInventory, 129 dryRun: false, 130 isError: true, 131 }, 132 "Dry-run will not fill in inventory fields": { 133 kptfile: kptFile, 134 rgFilename: "resourcegroup.yaml", 135 dryRun: true, 136 isError: false, 137 }, 138 "Custom ResourceGroup file will be generated": { 139 kptfile: kptFileWithInventory, 140 rgFilename: "custom-rg.yaml", 141 dryRun: false, 142 isError: false, 143 }, 144 } 145 146 for tn, tc := range testCases { 147 t.Run(tn, func(t *testing.T) { 148 // Set up fake test factory 149 tf := cmdtesting.NewTestFactory().WithNamespace(inventoryNamespace) 150 defer tf.Cleanup() 151 ioStreams, _, _, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled 152 153 // Set up temp directory with Ktpfile 154 dir := t.TempDir() 155 p := filepath.Join(dir, "Kptfile") 156 err := os.WriteFile(p, []byte(tc.kptfile), 0600) 157 assert.NoError(t, err) 158 159 if tc.resourcegroup != "" { 160 p := filepath.Join(dir, tc.rgFilename) 161 err = os.WriteFile(p, []byte(tc.resourcegroup), 0600) 162 assert.NoError(t, err) 163 } 164 165 ctx := fake.CtxWithDefaultPrinter() 166 // Create MigrateRunner and call "updateKptfile" 167 cmLoader := manifestreader.NewManifestLoader(tf) 168 migrateRunner := NewRunner(ctx, tf, cmLoader, ioStreams) 169 migrateRunner.dryRun = tc.dryRun 170 migrateRunner.rgFile = tc.rgFilename 171 migrateRunner.cmInvClientFunc = func(factory util.Factory) (inventory.Client, error) { 172 return inventory.NewFakeClient([]object.ObjMetadata{}), nil 173 } 174 err = migrateRunner.migrateKptfileToRG([]string{dir}) 175 // Check if there should be an error 176 if tc.isError { 177 if err == nil { 178 t.Fatalf("expected error but received none") 179 } 180 return 181 } 182 assert.NoError(t, err) 183 kf, err := pkg.ReadKptfile(filesys.FileSystemOrOnDisk{}, dir) 184 if !assert.NoError(t, err) { 185 t.FailNow() 186 } 187 188 rg, err := pkg.ReadRGFile(dir, migrateRunner.rgFile) 189 if !tc.dryRun && !assert.NoError(t, err) { 190 t.FailNow() 191 } 192 193 // Ensure the Kptfile does not contain inventory information. 194 if !assert.Nil(t, kf.Inventory) { 195 t.Errorf("inventory information should not be set in Kptfile") 196 } 197 198 if !tc.dryRun { 199 if rg == nil { 200 t.Fatalf("unable to read ResourceGroup file") 201 } 202 assert.Equal(t, inventoryNamespace, rg.ObjectMeta.Namespace) 203 if len(rg.ObjectMeta.Name) == 0 { 204 t.Errorf("inventory name not set in Kptfile") 205 } 206 if rg.ObjectMeta.Labels[rgfilev1alpha1.RGInventoryIDLabel] != testInventoryID { 207 t.Errorf("inventory id not set correctly in ResourceGroup: %s", rg.ObjectMeta.Labels[rgfilev1alpha1.RGInventoryIDLabel]) 208 } 209 } else if rg != nil { 210 t.Errorf("inventory shouldn't be set during dryrun") 211 } 212 }) 213 } 214 } 215 216 func TestKptMigrate_retrieveConfigMapInv(t *testing.T) { 217 testCases := map[string]struct { 218 configMap string 219 expected *unstructured.Unstructured 220 isError bool 221 }{ 222 "Missing ConfigMap is an error": { 223 configMap: "", 224 expected: nil, 225 isError: true, 226 }, 227 "ConfigMap inventory object is correctly retrieved": { 228 configMap: cmInvStr, 229 expected: cmInvObj, 230 isError: false, 231 }, 232 } 233 234 for tn, tc := range testCases { 235 t.Run(tn, func(t *testing.T) { 236 // Set up fake test factory 237 tf := cmdtesting.NewTestFactory().WithNamespace(inventoryNamespace) 238 defer tf.Cleanup() 239 ioStreams, _, _, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled 240 241 ctx := fake.CtxWithDefaultPrinter() 242 // Create MigrateRunner and call "retrieveConfigMapInv" 243 cmLoader := manifestreader.NewManifestLoader(tf) 244 migrateRunner := NewRunner(ctx, tf, cmLoader, ioStreams) 245 migrateRunner.cmInvClientFunc = func(factory util.Factory) (inventory.Client, error) { 246 return inventory.NewFakeClient([]object.ObjMetadata{}), nil 247 } 248 actual, err := migrateRunner.retrieveConfigMapInv(strings.NewReader(tc.configMap), []string{"-"}) 249 // Check if there should be an error 250 if tc.isError { 251 if err == nil { 252 t.Fatalf("expected error but received none") 253 } 254 return 255 } 256 assert.NoError(t, err) 257 if tc.expected.GetName() != actual.Name() { 258 t.Errorf("expected ConfigMap (%#v), got (%#v)", tc.expected, actual) 259 } 260 if tc.expected.GetNamespace() != actual.Namespace() { 261 t.Errorf("expected ConfigMap (%#v), got (%#v)", tc.expected, actual) 262 } 263 }) 264 } 265 } 266 267 func TestKptMigrate_findResourceGroupInv(t *testing.T) { 268 testCases := map[string]struct { 269 objs []*unstructured.Unstructured 270 expected *unstructured.Unstructured 271 isError bool 272 }{ 273 "Empty objs returns an error": { 274 objs: []*unstructured.Unstructured{}, 275 expected: nil, 276 isError: true, 277 }, 278 "Objs without inventory obj returns an error": { 279 objs: []*unstructured.Unstructured{pod1}, 280 expected: nil, 281 isError: true, 282 }, 283 "Objs without ConfigMap inventory obj returns an error": { 284 objs: []*unstructured.Unstructured{cmInvObj, pod1}, 285 expected: nil, 286 isError: true, 287 }, 288 "Objs without ResourceGroup inventory obj returns ResourceGroup": { 289 objs: []*unstructured.Unstructured{rgInvObj, pod1}, 290 expected: rgInvObj, 291 isError: false, 292 }, 293 } 294 295 for tn, tc := range testCases { 296 t.Run(tn, func(t *testing.T) { 297 actual, err := findResourceGroupInv(tc.objs) 298 if tc.isError { 299 if err == nil { 300 t.Fatalf("expected error but received none") 301 } 302 return 303 } 304 assert.NoError(t, err) 305 if tc.expected != actual { 306 t.Errorf("expected ResourceGroup (%#v), got (%#v)", tc.expected, actual) 307 } 308 }) 309 } 310 } 311 312 func TestKptMigrate_migrateObjs(t *testing.T) { 313 testCases := map[string]struct { 314 invObj string 315 objs []object.ObjMetadata 316 isError bool 317 rgFilename string 318 }{ 319 "No objects to migrate is valid": { 320 invObj: "", 321 objs: []object.ObjMetadata{}, 322 isError: false, 323 }, 324 "One migrate object is valid": { 325 invObj: resourceGroupInventory, 326 objs: []object.ObjMetadata{object.UnstructuredToObjMetadata(pod1)}, 327 rgFilename: rgfilev1alpha1.RGFileName, 328 isError: false, 329 }, 330 "Multiple migrate objects are valid": { 331 invObj: resourceGroupInventory, 332 objs: []object.ObjMetadata{ 333 object.UnstructuredToObjMetadata(pod1), 334 object.UnstructuredToObjMetadata(pod2), 335 }, 336 rgFilename: rgfilev1alpha1.RGFileName, 337 isError: false, 338 }, 339 "Kptfile does not have inventory is valid": { 340 invObj: resourceGroupInventory, 341 objs: []object.ObjMetadata{}, 342 rgFilename: rgfilev1alpha1.RGFileName, 343 isError: false, 344 }, 345 "One migrate object is valid with inventory in Kptfile": { 346 invObj: resourceGroupInventory, 347 objs: []object.ObjMetadata{object.UnstructuredToObjMetadata(pod1)}, 348 rgFilename: rgfilev1alpha1.RGFileName, 349 isError: false, 350 }, 351 "Migrate to ResourceGroup object with custom filename": { 352 invObj: resourceGroupInventory, 353 objs: []object.ObjMetadata{object.UnstructuredToObjMetadata(pod1)}, 354 rgFilename: "test-rg.yaml", 355 isError: false, 356 }, 357 } 358 359 for tn, tc := range testCases { 360 t.Run(tn, func(t *testing.T) { 361 // Set up fake test factory 362 tf := cmdtesting.NewTestFactory().WithNamespace(inventoryNamespace) 363 defer tf.Cleanup() 364 ioStreams, _, _, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled 365 366 ctx := fake.CtxWithDefaultPrinter() 367 // Create MigrateRunner and call "retrieveConfigMapInv" 368 rgInvClient := inventory.NewFakeClient(tc.objs) 369 cmLoader := manifestreader.NewManifestLoader(tf) 370 migrateRunner := NewRunner(ctx, tf, cmLoader, ioStreams) 371 migrateRunner.rgFile = tc.rgFilename 372 373 err := migrateRunner.migrateObjs(rgInvClient, tc.objs, strings.NewReader(tc.invObj), []string{"-"}) 374 // Check if there should be an error 375 if tc.isError { 376 if err == nil { 377 t.Fatalf("expected error but received none") 378 } 379 return 380 } 381 assert.NoError(t, err) 382 // Retrieve the objects stored by the inventory client and validate. 383 migratedObjs, err := rgInvClient.GetClusterObjs(nil) 384 assert.NoError(t, err) 385 if len(tc.objs) != len(migratedObjs) { 386 t.Errorf("expected num migrated objs (%d), got (%d)", len(tc.objs), len(migratedObjs)) 387 } 388 for _, migratedObj := range migratedObjs { 389 found := false 390 var expectedObj object.ObjMetadata 391 for _, expectedObj = range tc.objs { 392 if expectedObj == migratedObj { 393 found = true 394 } 395 } 396 if !found { 397 t.Fatalf("expected migrated object (%#v), but not found", expectedObj) 398 return 399 } 400 } 401 }) 402 } 403 } 404 405 var kptFileWithInventory = ` 406 apiVersion: kpt.dev/v1 407 kind: Kptfile 408 metadata: 409 name: test1 410 upstreamLock: 411 type: git 412 git: 413 repo: git@github.com:seans3/blueprint-helloworld 414 directory: / 415 ref: master 416 inventory: 417 name: foo 418 namespace: test-namespace 419 inventoryID: ` + testInventoryID + "\n" 420 421 const testInventoryID = "SSSSSSSSSS-RRRRR" 422 423 var kptFile = ` 424 apiVersion: kpt.dev/v1 425 kind: Kptfile 426 metadata: 427 name: test1 428 upstreamLock: 429 type: git 430 git: 431 repo: git@github.com:seans3/blueprint-helloworld 432 directory: / 433 ref: master 434 ` 435 436 var inventoryNamespace = "test-namespace" 437 438 var resourceGroupInventory = ` 439 apiVersion: kpt.dev/v1alpha1 440 kind: ResourceGroup 441 metadata: 442 name: foo 443 namespace: test-namespace 444 labels: 445 cli-utils.sigs.k8s.io/inventory-id: SSSSSSSSSS-RRRRR 446 `