github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/kustomize/pre_kustomize_test.go (about) 1 package kustomize 2 3 import ( 4 "context" 5 "os" 6 "path/filepath" 7 "testing" 8 9 "github.com/go-kit/kit/log" 10 "github.com/replicatedhq/ship/pkg/api" 11 "github.com/replicatedhq/ship/pkg/state" 12 "github.com/replicatedhq/ship/pkg/util" 13 "github.com/spf13/afero" 14 "github.com/spf13/viper" 15 "github.com/stretchr/testify/require" 16 "sigs.k8s.io/kustomize/pkg/patch" 17 "sigs.k8s.io/kustomize/pkg/types" 18 ) 19 20 type testFile struct { 21 path string 22 contents string 23 } 24 25 func addTestFiles(fs afero.Afero, testFiles []testFile) error { 26 for _, testFile := range testFiles { 27 if err := fs.MkdirAll(filepath.Dir(testFile.path), 0755); err != nil { 28 return err 29 } 30 if err := fs.WriteFile(testFile.path, []byte(testFile.contents), 0644); err != nil { 31 return err 32 } 33 } 34 return nil 35 } 36 37 func readTestFiles(step api.Kustomize, fs afero.Afero) ([]testFile, error) { 38 files := []testFile{} 39 if err := fs.Walk(step.Base, func(targetPath string, info os.FileInfo, err error) error { 40 if filepath.Ext(targetPath) == ".yaml" { 41 contents, err := fs.ReadFile(targetPath) 42 if err != nil { 43 return err 44 } 45 46 files = append(files, testFile{ 47 path: targetPath, 48 contents: string(contents), 49 }) 50 } 51 return nil 52 }); err != nil { 53 return files, err 54 } 55 56 return files, nil 57 } 58 59 func TestKustomizer_replaceOriginal(t *testing.T) { 60 61 tests := []struct { 62 name string 63 step api.Kustomize 64 built []util.PostKustomizeFile 65 original []testFile 66 expect []testFile 67 }{ 68 { 69 name: "replace single file", 70 step: api.Kustomize{ 71 Base: "", 72 }, 73 built: []util.PostKustomizeFile{ 74 { 75 Minimal: util.MinimalK8sYaml{ 76 Kind: "Fruit", 77 Metadata: util.MinimalK8sMetadata{ 78 Name: "strawberry", 79 }, 80 }, 81 Full: map[string]interface{}{ 82 "kind": "Fruit", 83 "metadata": map[string]interface{}{ 84 "name": "strawberry", 85 }, 86 "spec": map[string]interface{}{ 87 "modified": "modified", 88 }, 89 }, 90 }, 91 }, 92 original: []testFile{ 93 { 94 path: "strawberry.yaml", 95 contents: `kind: Fruit 96 metadata: 97 name: strawberry 98 spec: 99 original: original 100 `, 101 }, 102 }, 103 expect: []testFile{ 104 { 105 path: "strawberry.yaml", 106 contents: `kind: Fruit 107 metadata: 108 name: strawberry 109 spec: 110 modified: modified 111 `, 112 }, 113 }, 114 }, 115 { 116 name: "skip CRDs", 117 step: api.Kustomize{ 118 Base: "", 119 }, 120 built: []util.PostKustomizeFile{ 121 { 122 Minimal: util.MinimalK8sYaml{ 123 Kind: "CustomResourceDefinition", 124 Metadata: util.MinimalK8sMetadata{ 125 Name: "strawberry", 126 }, 127 }, 128 Full: map[string]interface{}{ 129 "kind": "CustomResourceDefinition", 130 "metadata": map[string]interface{}{ 131 "name": "strawberry", 132 }, 133 "spec": map[string]interface{}{ 134 "modified": "modified", 135 }, 136 }, 137 }, 138 }, 139 original: []testFile{ 140 { 141 path: "strawberry.yaml", 142 contents: `kind: CustomResourceDefinition 143 metadata: 144 name: strawberry 145 spec: 146 original: original 147 `, 148 }, 149 }, 150 expect: []testFile{ 151 { 152 path: "strawberry.yaml", 153 contents: `kind: CustomResourceDefinition 154 metadata: 155 name: strawberry 156 spec: 157 original: original 158 `, 159 }, 160 }, 161 }, 162 { 163 name: "replace nested file", 164 step: api.Kustomize{ 165 Base: "", 166 }, 167 built: []util.PostKustomizeFile{ 168 { 169 Minimal: util.MinimalK8sYaml{ 170 Kind: "Fruit", 171 Metadata: util.MinimalK8sMetadata{ 172 Name: "banana", 173 }, 174 }, 175 Full: map[string]interface{}{ 176 "kind": "Fruit", 177 "metadata": map[string]interface{}{ 178 "name": "banana", 179 }, 180 "spec": map[string]interface{}{ 181 "modified": "modified", 182 }, 183 }, 184 }, 185 }, 186 original: []testFile{ 187 { 188 path: "somedir/banana.yaml", 189 contents: `kind: Fruit 190 metadata: 191 name: banana 192 spec: 193 original: original 194 `, 195 }, 196 }, 197 expect: []testFile{ 198 { 199 path: "somedir/banana.yaml", 200 contents: `kind: Fruit 201 metadata: 202 name: banana 203 spec: 204 modified: modified 205 `, 206 }, 207 }, 208 }, 209 { 210 name: "replace multiple files", 211 step: api.Kustomize{ 212 Base: "", 213 }, 214 built: []util.PostKustomizeFile{ 215 { 216 Minimal: util.MinimalK8sYaml{ 217 Kind: "Fruit", 218 Metadata: util.MinimalK8sMetadata{ 219 Name: "dragonfruit", 220 }, 221 }, 222 Full: map[string]interface{}{ 223 "kind": "Fruit", 224 "metadata": map[string]interface{}{ 225 "name": "dragonfruit", 226 }, 227 "spec": map[string]interface{}{ 228 "modified": "modified dragonfruit", 229 }, 230 }, 231 }, 232 { 233 Minimal: util.MinimalK8sYaml{ 234 Kind: "Fruit", 235 Metadata: util.MinimalK8sMetadata{ 236 Name: "pomegranate", 237 }, 238 }, 239 Full: map[string]interface{}{ 240 "kind": "Fruit", 241 "metadata": map[string]interface{}{ 242 "name": "pomegranate", 243 }, 244 "spec": map[string]interface{}{ 245 "modified": "modified pomegranate", 246 }, 247 }, 248 }, 249 }, 250 original: []testFile{ 251 { 252 path: "somedir/dragonfruit.yaml", 253 contents: `kind: Fruit 254 metadata: 255 name: dragonfruit 256 spec: 257 original: original dragonfruit 258 `, 259 }, 260 { 261 path: "pomegranate.yaml", 262 contents: `kind: Fruit 263 metadata: 264 name: pomegranate 265 spec: 266 original: original pomegranate 267 `, 268 }, 269 }, 270 expect: []testFile{ 271 { 272 path: "somedir/dragonfruit.yaml", 273 contents: `kind: Fruit 274 metadata: 275 name: dragonfruit 276 spec: 277 modified: modified dragonfruit 278 `, 279 }, 280 { 281 path: "pomegranate.yaml", 282 contents: `kind: Fruit 283 metadata: 284 name: pomegranate 285 spec: 286 modified: modified pomegranate 287 `, 288 }, 289 }, 290 }, 291 } 292 293 for _, tt := range tests { 294 t.Run(tt.name, func(t *testing.T) { 295 req := require.New(t) 296 mockFs := afero.Afero{Fs: afero.NewMemMapFs()} 297 err := addTestFiles(mockFs, tt.original) 298 req.NoError(err) 299 300 l := &Kustomizer{ 301 Logger: log.NewNopLogger(), 302 FS: mockFs, 303 } 304 305 err = l.replaceOriginal(tt.step.Base, tt.built) 306 req.NoError(err) 307 308 actual, err := readTestFiles(tt.step, mockFs) 309 req.NoError(err) 310 311 req.ElementsMatch(tt.expect, actual) 312 }) 313 } 314 } 315 316 func TestKustomizer_resolveExistingKustomize(t *testing.T) { 317 tests := []struct { 318 name string 319 original []testFile 320 InKust *state.Kustomize 321 overlayDir string 322 WantKust *state.Kustomize 323 wantErr bool 324 }{ 325 { 326 name: "no files in overlay dir, no current state", 327 original: []testFile{ 328 { 329 path: "test/overlays/ship/placeholder", 330 contents: "abc", 331 }, 332 }, 333 overlayDir: "test/overlays/ship", 334 InKust: nil, 335 WantKust: nil, 336 }, 337 { 338 name: "no files in overlay dir, some current state", 339 original: []testFile{ 340 { 341 path: "test/overlays/placeholder", 342 contents: "abc", 343 }, 344 }, 345 overlayDir: "test/overlays", 346 InKust: &state.Kustomize{ 347 Overlays: map[string]state.Overlay{ 348 "ship": state.Overlay{ 349 Patches: map[string]string{"abc": "xyz"}, 350 Resources: map[string]string{"abc": "xyz"}, 351 ExcludedBases: []string{"excludedBase"}, 352 }, 353 }, 354 }, 355 WantKust: &state.Kustomize{ 356 Overlays: map[string]state.Overlay{ 357 "ship": state.Overlay{ 358 Patches: map[string]string{"abc": "xyz"}, 359 Resources: map[string]string{"abc": "xyz"}, 360 ExcludedBases: []string{"excludedBase"}, 361 }, 362 }, 363 }, 364 }, 365 { 366 name: "garbled kustomization in current dir, some current state", 367 original: []testFile{ 368 { 369 path: "test/overlays/kustomization.yaml", 370 contents: "abc", 371 }, 372 }, 373 overlayDir: "test/overlays", 374 InKust: &state.Kustomize{ 375 Overlays: map[string]state.Overlay{ 376 "ship": state.Overlay{ 377 Patches: map[string]string{"abc": "xyz"}, 378 Resources: map[string]string{"abc": "xyz"}, 379 ExcludedBases: []string{"excludedBase"}, 380 }, 381 }, 382 }, 383 wantErr: true, 384 WantKust: &state.Kustomize{ 385 Overlays: map[string]state.Overlay{ 386 "ship": state.Overlay{ 387 Patches: map[string]string{"abc": "xyz"}, 388 Resources: map[string]string{"abc": "xyz"}, 389 ExcludedBases: []string{"excludedBase"}, 390 }, 391 }, 392 }, 393 }, 394 { 395 name: "overlays and resources files in overlay dir, some current state", 396 original: []testFile{ 397 { 398 path: "test/overlays/kustomization.yaml", 399 contents: `kind: "" 400 apiversion: "" 401 bases: 402 - ../abc 403 resources: 404 - myresource.yaml 405 patchesStrategicMerge: 406 - mypatch.yaml 407 `, 408 }, 409 { 410 path: "test/overlays/myresource.yaml", 411 contents: `this is my resource`, 412 }, 413 { 414 path: "test/overlays/mypatch.yaml", 415 contents: `this is my patch`, 416 }, 417 }, 418 overlayDir: "test/overlays", 419 InKust: &state.Kustomize{ 420 Overlays: map[string]state.Overlay{ 421 "ship": state.Overlay{ 422 Patches: map[string]string{"abc": "xyz"}, 423 Resources: map[string]string{"abc": "xyz"}, 424 ExcludedBases: []string{"excludedBase"}, 425 }, 426 }, 427 }, 428 WantKust: &state.Kustomize{ 429 Overlays: map[string]state.Overlay{ 430 "ship": state.Overlay{ 431 Patches: map[string]string{"/mypatch.yaml": "this is my patch"}, 432 Resources: map[string]string{"/myresource.yaml": "this is my resource"}, 433 ExcludedBases: []string{"excludedBase"}, 434 RawKustomize: types.Kustomization{ 435 PatchesStrategicMerge: []patch.StrategicMerge{"mypatch.yaml"}, 436 Resources: []string{"myresource.yaml"}, 437 Bases: []string{"../abc"}, 438 }, 439 }, 440 }, 441 }, 442 }, 443 { 444 name: "resources files in overlay dir, no current state", 445 original: []testFile{ 446 { 447 path: "test/overlays/kustomization.yaml", 448 contents: `kind: "" 449 apiversion: "" 450 bases: 451 - ../abc 452 resources: 453 - myresource.yaml 454 - myotherresource.yaml 455 `, 456 }, 457 { 458 path: "test/overlays/myresource.yaml", 459 contents: `this is my resource`, 460 }, 461 { 462 path: "test/overlays/myotherresource.yaml", 463 contents: `this is my other resource`, 464 }, 465 }, 466 overlayDir: "test/overlays", 467 InKust: nil, 468 WantKust: &state.Kustomize{ 469 Overlays: map[string]state.Overlay{ 470 "ship": state.Overlay{ 471 Patches: map[string]string{}, 472 Resources: map[string]string{ 473 "/myresource.yaml": "this is my resource", 474 "/myotherresource.yaml": "this is my other resource", 475 }, 476 ExcludedBases: []string{}, 477 RawKustomize: types.Kustomization{ 478 Resources: []string{ 479 "myresource.yaml", 480 "myotherresource.yaml", 481 }, 482 Bases: []string{"../abc"}, 483 }, 484 }, 485 }, 486 }, 487 }, 488 } 489 for _, tt := range tests { 490 t.Run(tt.name, func(t *testing.T) { 491 req := require.New(t) 492 fs := afero.Afero{Fs: afero.NewMemMapFs()} 493 494 err := addTestFiles(fs, tt.original) 495 req.NoError(err) 496 497 manager, err := state.NewDisposableManager(log.NewNopLogger(), fs, viper.New()) 498 req.NoError(err) 499 500 err = manager.SaveKustomize(tt.InKust) 501 req.NoError(err) 502 503 l := &Kustomizer{ 504 Logger: log.NewNopLogger(), 505 FS: fs, 506 State: manager, 507 } 508 509 err = l.resolveExistingKustomize(context.Background(), tt.overlayDir) 510 if tt.wantErr { 511 req.Error(err) 512 } else { 513 req.NoError(err) 514 } 515 516 outState, err := manager.CachedState() 517 req.NoError(err) 518 519 outKust := outState.CurrentKustomize() 520 req.Equal(tt.WantKust, outKust) 521 }) 522 } 523 }