github.com/shipa-corp/ketch@v0.6.0/cmd/ketch/app_deploy_test.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "context" 6 "io/ioutil" 7 "os" 8 "path" 9 "testing" 10 11 "k8s.io/apimachinery/pkg/api/errors" 12 13 registryv1 "github.com/google/go-containerregistry/pkg/v1" 14 "github.com/stretchr/testify/require" 15 v1 "k8s.io/api/apps/v1" 16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 "k8s.io/apimachinery/pkg/runtime" 18 "k8s.io/client-go/kubernetes/fake" 19 "sigs.k8s.io/controller-runtime/pkg/client" 20 21 ketchv1 "github.com/shipa-corp/ketch/internal/api/v1beta1" 22 "github.com/shipa-corp/ketch/internal/build" 23 "github.com/shipa-corp/ketch/internal/deploy" 24 "github.com/shipa-corp/ketch/internal/pack" 25 ) 26 27 type getterCreatorMockFn func(m *mockClient, obj runtime.Object) error 28 type funcMap map[int]getterCreatorMockFn 29 30 type mockClient struct { 31 get funcMap 32 create funcMap 33 update funcMap 34 35 app *ketchv1.App 36 framework *ketchv1.Framework 37 38 getCounter int 39 createCounter int 40 updateCounter int 41 } 42 43 func newMockClient() *mockClient { 44 return &mockClient{ 45 get: make(funcMap), 46 update: make(funcMap), 47 create: make(funcMap), 48 app: &ketchv1.App{ 49 Spec: ketchv1.AppSpec{ 50 Description: "foo", 51 Framework: "initialframework", 52 Builder: "initialbuilder", 53 }, 54 }, 55 framework: &ketchv1.Framework{ 56 TypeMeta: metav1.TypeMeta{}, 57 ObjectMeta: metav1.ObjectMeta{}, 58 Spec: ketchv1.FrameworkSpec{}, 59 Status: ketchv1.FrameworkStatus{}, 60 }, 61 } 62 } 63 64 func (m *mockClient) Get(_ context.Context, _ client.ObjectKey, obj client.Object) error { 65 m.getCounter++ 66 67 if f, ok := m.get[m.getCounter]; ok { 68 return f(m, obj) 69 } 70 71 switch v := obj.(type) { 72 case *ketchv1.App: 73 *v = *m.app 74 return nil 75 case *ketchv1.Framework: 76 *v = *m.framework 77 return nil 78 } 79 panic("unhandled type") 80 } 81 82 func (m *mockClient) Create(_ context.Context, obj client.Object, _ ...client.CreateOption) error { 83 m.createCounter++ 84 85 if f, ok := m.create[m.createCounter]; ok { 86 return f(m, obj) 87 } 88 89 switch v := obj.(type) { 90 case *ketchv1.App: 91 m.app = v 92 return nil 93 case *ketchv1.Framework: 94 m.framework = v 95 return nil 96 } 97 panic("unhandled type") 98 } 99 100 func (m *mockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { 101 m.updateCounter++ 102 103 if f, ok := m.update[m.updateCounter]; ok { 104 return f(m, obj) 105 } 106 107 switch v := obj.(type) { 108 case *ketchv1.App: 109 m.app = v 110 return nil 111 case *ketchv1.Framework: 112 m.framework = v 113 return nil 114 } 115 panic("unhandled type") 116 } 117 118 type packMocker struct{} 119 120 func (packMocker) BuildAndPushImage(ctx context.Context, req pack.BuildRequest) error { 121 return nil 122 } 123 124 func getImageConfig(ctx context.Context, args deploy.ImageConfigRequest) (*registryv1.ConfigFile, error) { 125 return ®istryv1.ConfigFile{ 126 Config: registryv1.Config{ 127 Cmd: []string{"/bin/eatme"}, 128 }, 129 }, nil 130 } 131 132 var ( 133 ketchYaml string = ` 134 kubernetes: 135 processes: 136 web: 137 ports: 138 - name: apache-http # an optional name for the port 139 protocol: TCP 140 port: 80 # The port that is going to be exposed on the router. 141 target_port: 9999 # The port on which the application listens on. 142 worker: 143 ports: 144 - name: http 145 protocol: TCP 146 port: 80 147 worker-2: 148 ports: [] 149 ` 150 procfile string = ` 151 web: python app.py 152 worker: python app.py 153 ` 154 packBuildMetadata string = "{\"bom\":null,\"buildpacks\":[{\"id\":\"heroku/python\",\"version\":\"0.3.1\"},{\"id\":\"heroku/procfile\",\"version\":\"0.6.2\"}],\"launcher\":{\"version\":\"0.11.3\",\"source\":{\"git\":{\"repository\":\"github.com/buildpacks/lifecycle\",\"commit\":\"aa4bbac\"}}},\"processes\":[{\"type\":\"web\",\"command\":\"python app.py\",\"args\":null,\"direct\":false,\"buildpackID\":\"heroku/procfile\"},{\"type\":\"worker\",\"command\":\"python app.py\",\"args\":null,\"direct\":false,\"buildpackID\":\"heroku/procfile\"},{\"type\":\"worker1\",\"command\":\"python app.py\",\"args\":null,\"direct\":false,\"buildpackID\":\"heroku/procfile\"}]}" 155 ) 156 157 func TestNewCommand(t *testing.T) { 158 tt := []struct { 159 name string 160 params *deploy.Services 161 arguments []string 162 setup func(t *testing.T) 163 userDefault string 164 validate func(t *testing.T, m *mockClient) 165 wantError bool 166 }{ 167 { 168 name: "change builder from previous deploy", 169 arguments: []string{ 170 "myapp", 171 "src", 172 "--image", "shipa/go-sample:latest", 173 "--builder", "some other builder", 174 }, 175 setup: func(t *testing.T) { 176 dir := t.TempDir() 177 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 178 require.Nil(t, os.Chdir(dir)) 179 require.Nil(t, ioutil.WriteFile("src/Procfile", []byte(procfile), 0600)) 180 }, 181 validate: func(t *testing.T, mock *mockClient) { 182 require.Equal(t, "some other builder", mock.app.Spec.Builder) 183 }, 184 params: &deploy.Services{ 185 Client: func() *mockClient { 186 m := newMockClient() 187 m.app.Spec.Builder = "superduper builder" 188 return m 189 }(), 190 KubeClient: fake.NewSimpleClientset(), 191 Builder: build.GetSourceHandler(&packMocker{}), 192 GetImageConfig: getImageConfig, 193 Wait: nil, 194 Writer: &bytes.Buffer{}, 195 }, 196 }, 197 { 198 name: "use builder from previous deploy", 199 arguments: []string{ 200 "myapp", 201 "src", 202 "--framework", "initialframework", 203 "--image", "shipa/go-sample:latest", 204 }, 205 setup: func(t *testing.T) { 206 dir := t.TempDir() 207 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 208 require.Nil(t, os.Chdir(dir)) 209 require.Nil(t, ioutil.WriteFile("src/Procfile", []byte(procfile), 0600)) 210 }, 211 validate: func(t *testing.T, mock *mockClient) { 212 require.Equal(t, "superduper builder", mock.app.Spec.Builder) 213 }, 214 params: &deploy.Services{ 215 Client: func() *mockClient { 216 m := newMockClient() 217 m.app.Spec.Builder = "superduper builder" 218 return m 219 }(), 220 KubeClient: fake.NewSimpleClientset(), 221 Builder: build.GetSourceHandler(&packMocker{}), 222 GetImageConfig: getImageConfig, 223 Wait: nil, 224 Writer: &bytes.Buffer{}, 225 }, 226 }, 227 { 228 name: "use default builder for new app", 229 arguments: []string{ 230 "myapp", 231 "src", 232 "--framework", "myframework", 233 "--image", "shipa/go-sample:latest", 234 }, 235 setup: func(t *testing.T) { 236 dir := t.TempDir() 237 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 238 require.Nil(t, os.Chdir(dir)) 239 require.Nil(t, ioutil.WriteFile("src/Procfile", []byte(procfile), 0600)) 240 }, 241 validate: func(t *testing.T, mock *mockClient) { 242 require.Equal(t, deploy.DefaultBuilder, mock.app.Spec.Builder) 243 }, 244 params: &deploy.Services{ 245 Client: func() *mockClient { 246 m := newMockClient() 247 m.get[1] = func(_ *mockClient, _ runtime.Object) error { 248 return errors.NewNotFound(v1.Resource(""), "") 249 } 250 return m 251 }(), 252 KubeClient: fake.NewSimpleClientset(), 253 Builder: build.GetSourceHandler(&packMocker{}), 254 GetImageConfig: getImageConfig, 255 Wait: nil, 256 Writer: &bytes.Buffer{}, 257 }, 258 }, 259 { 260 name: "use user default builder for new app", 261 arguments: []string{ 262 "myapp", 263 "src", 264 "--framework", "myframework", 265 "--image", "shipa/go-sample:latest", 266 }, 267 setup: func(t *testing.T) { 268 dir := t.TempDir() 269 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 270 require.Nil(t, os.Chdir(dir)) 271 require.Nil(t, ioutil.WriteFile("src/Procfile", []byte(procfile), 0600)) 272 }, 273 userDefault: "newDefault", 274 validate: func(t *testing.T, mock *mockClient) { 275 require.Equal(t, "newDefault", mock.app.Spec.Builder) 276 }, 277 params: &deploy.Services{ 278 Client: func() *mockClient { 279 m := newMockClient() 280 m.get[1] = func(_ *mockClient, _ runtime.Object) error { 281 return errors.NewNotFound(v1.Resource(""), "") 282 } 283 return m 284 }(), 285 KubeClient: fake.NewSimpleClientset(), 286 Builder: build.GetSourceHandler(&packMocker{}), 287 GetImageConfig: getImageConfig, 288 Wait: nil, 289 Writer: &bytes.Buffer{}, 290 }, 291 }, 292 { 293 name: "don't update builder on previous deployment", 294 arguments: []string{ 295 "myapp", 296 "src", 297 "--image", "shipa/go-sample:latest", 298 }, 299 setup: func(t *testing.T) { 300 dir := t.TempDir() 301 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 302 require.Nil(t, os.Chdir(dir)) 303 require.Nil(t, ioutil.WriteFile("src/Procfile", []byte(procfile), 0600)) 304 }, 305 userDefault: "newDefault", 306 validate: func(t *testing.T, mock *mockClient) { 307 require.Equal(t, mock.app.Spec.Builder, "initialBuilder") 308 309 }, 310 params: &deploy.Services{ 311 Client: func() *mockClient { 312 m := newMockClient() 313 m.app.Spec.Builder = "initialBuilder" 314 m.app.Spec.Deployments = []ketchv1.AppDeploymentSpec{ 315 { 316 Image: "shipa/go-sample:latest", 317 Version: 1, 318 Processes: nil, 319 KetchYaml: nil, 320 Labels: nil, 321 RoutingSettings: ketchv1.RoutingSettings{}, 322 ExposedPorts: nil, 323 }, 324 } 325 return m 326 }(), 327 328 KubeClient: fake.NewSimpleClientset(), 329 Builder: build.GetSourceHandler(&packMocker{}), 330 GetImageConfig: getImageConfig, 331 Wait: nil, 332 Writer: &bytes.Buffer{}, 333 }, 334 }, 335 { 336 name: "use assigned builder for new app", 337 arguments: []string{ 338 "myapp", 339 "src", 340 "--framework", "myframework", 341 "--image", "shipa/go-sample:latest", 342 "--builder", "superduper", 343 "--build-packs", "pack1,pack2", 344 }, 345 setup: func(t *testing.T) { 346 dir := t.TempDir() 347 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 348 require.Nil(t, os.Chdir(dir)) 349 require.Nil(t, ioutil.WriteFile("src/Procfile", []byte(procfile), 0600)) 350 }, 351 validate: func(t *testing.T, mock *mockClient) { 352 require.Equal(t, "superduper", mock.app.Spec.Builder) 353 require.Equal(t, []string{"pack1", "pack2"}, mock.app.Spec.BuildPacks) 354 }, 355 params: &deploy.Services{ 356 Client: func() *mockClient { 357 m := newMockClient() 358 m.get[1] = func(_ *mockClient, _ runtime.Object) error { 359 return errors.NewNotFound(v1.Resource(""), "") 360 } 361 return m 362 }(), 363 KubeClient: fake.NewSimpleClientset(), 364 Builder: build.GetSourceHandler(&packMocker{}), 365 GetImageConfig: getImageConfig, 366 Wait: nil, 367 Writer: &bytes.Buffer{}, 368 }, 369 }, 370 // build from source, creates app 371 { 372 name: "happy path build from source", 373 arguments: []string{ 374 "myapp", 375 "src", 376 "--framework", "myframework", 377 "--image", "shipa/go-sample:latest", 378 "--env", "foo=bar,zip=zap", 379 "--builder", "newbuilder", 380 }, 381 setup: func(t *testing.T) { 382 dir := t.TempDir() 383 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 384 require.Nil(t, os.Chdir(dir)) 385 require.Nil(t, ioutil.WriteFile("src/ketch.yaml", []byte(ketchYaml), 0600)) 386 require.Nil(t, ioutil.WriteFile("src/Procfile", []byte(procfile), 0600)) 387 }, 388 validate: func(t *testing.T, mock *mockClient) { 389 require.Equal(t, "myframework", mock.app.Spec.Framework) 390 require.Equal(t, "newbuilder", mock.app.Spec.Builder) 391 require.Len(t, mock.app.Spec.Deployments, 1) 392 require.Len(t, mock.app.Spec.Deployments[0].KetchYaml.Kubernetes.Processes, 3) 393 require.Len(t, mock.app.Spec.Env, 2) 394 }, 395 params: &deploy.Services{ 396 Client: func() *mockClient { 397 m := newMockClient() 398 m.get[1] = func(_ *mockClient, _ runtime.Object) error { 399 return errors.NewNotFound(v1.Resource(""), "") 400 } 401 return m 402 }(), 403 KubeClient: fake.NewSimpleClientset(), 404 Builder: build.GetSourceHandler(&packMocker{}), 405 GetImageConfig: getImageConfig, 406 Wait: nil, 407 Writer: &bytes.Buffer{}, 408 }, 409 }, 410 // build from source, updates app 411 { 412 name: "with custom yaml path", 413 arguments: []string{ 414 "myapp", 415 "src", 416 "--image", "shipa/go-sample:latest", 417 "--env", "foo=bar,zip=zap", 418 "--ketch-yaml", "config/ketch.yaml", 419 "--registry-secret", "supersecret", 420 }, 421 setup: func(t *testing.T) { 422 dir := t.TempDir() 423 424 require.Nil(t, os.Mkdir(path.Join(dir, "config"), 0700)) 425 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 426 require.Nil(t, os.MkdirAll(path.Join(dir, "src/include1"), 0700)) 427 require.Nil(t, os.MkdirAll(path.Join(dir, "src/include2"), 0700)) 428 require.Nil(t, os.Chdir(dir)) 429 require.Nil(t, ioutil.WriteFile("config/ketch.yaml", []byte(ketchYaml), 0600)) 430 require.Nil(t, ioutil.WriteFile("src/Procfile", []byte(procfile), 0600)) 431 }, 432 validate: func(t *testing.T, mock *mockClient) { 433 require.Len(t, mock.app.Spec.Deployments, 1) 434 require.Len(t, mock.app.Spec.Deployments[0].KetchYaml.Kubernetes.Processes, 3) 435 require.Len(t, mock.app.Spec.Env, 2) 436 require.Equal(t, "supersecret", mock.app.Spec.DockerRegistry.SecretName) 437 }, 438 params: &deploy.Services{ 439 Client: func() *mockClient { 440 m := newMockClient() 441 442 return m 443 }(), 444 KubeClient: fake.NewSimpleClientset(), 445 Builder: build.GetSourceHandler(&packMocker{}), 446 GetImageConfig: getImageConfig, 447 Wait: nil, 448 Writer: &bytes.Buffer{}, 449 }, 450 }, 451 { 452 name: "happy path with canary deploy build from source", 453 arguments: []string{ 454 "myapp", 455 "src", 456 "--steps", "4", 457 "--step-interval", "1h", 458 "--image", "shipa/go-sample:latest", 459 }, 460 setup: func(t *testing.T) { 461 dir := t.TempDir() 462 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 463 require.Nil(t, os.Chdir(dir)) 464 require.Nil(t, ioutil.WriteFile("src/Procfile", []byte(procfile), 0600)) 465 }, 466 validate: func(t *testing.T, mock *mockClient) { 467 require.Equal(t, mock.app.Spec.Framework, "initialframework") 468 469 }, 470 params: &deploy.Services{ 471 Client: func() *mockClient { 472 m := newMockClient() 473 m.app.Spec.Deployments = []ketchv1.AppDeploymentSpec{ 474 { 475 Image: "shipa/go-sample:latest", 476 Version: 1, 477 Processes: nil, 478 KetchYaml: nil, 479 Labels: nil, 480 RoutingSettings: ketchv1.RoutingSettings{}, 481 ExposedPorts: nil, 482 }, 483 } 484 return m 485 }(), 486 487 KubeClient: fake.NewSimpleClientset(), 488 Builder: build.GetSourceHandler(&packMocker{}), 489 GetImageConfig: getImageConfig, 490 Wait: nil, 491 Writer: &bytes.Buffer{}, 492 }, 493 }, 494 { 495 name: "happy path build from image", 496 arguments: []string{ 497 "myapp", 498 "--framework", "myframework", 499 "--image", "shipa/go-sample:latest", 500 }, 501 setup: func(t *testing.T) { 502 dir := t.TempDir() 503 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 504 require.Nil(t, os.Chdir(dir)) 505 }, 506 params: &deploy.Services{ 507 Client: func() *mockClient { 508 m := newMockClient() 509 m.get[1] = func(_ *mockClient, _ runtime.Object) error { 510 return errors.NewNotFound(v1.Resource(""), "") 511 } 512 return m 513 }(), 514 515 KubeClient: fake.NewSimpleClientset(), 516 Builder: build.GetSourceHandler(&packMocker{}), 517 GetImageConfig: getImageConfig, 518 Wait: nil, 519 Writer: &bytes.Buffer{}, 520 }, 521 }, 522 { 523 name: "missing source path", 524 wantError: true, 525 arguments: []string{ 526 "myapp", 527 "src", 528 "--framework", "myframework", 529 "--image", "shipa/go-sample:latest", 530 }, 531 setup: func(t *testing.T) { 532 dir := t.TempDir() 533 require.Nil(t, os.Chdir(dir)) 534 }, 535 params: &deploy.Services{ 536 Client: func() *mockClient { 537 m := newMockClient() 538 m.get[1] = func(_ *mockClient, _ runtime.Object) error { 539 return errors.NewNotFound(v1.Resource(""), "") 540 } 541 return m 542 }(), 543 544 KubeClient: fake.NewSimpleClientset(), 545 Builder: build.GetSourceHandler(&packMocker{}), 546 GetImageConfig: getImageConfig, 547 Wait: nil, 548 Writer: &bytes.Buffer{}, 549 }, 550 }, 551 { 552 name: "procfile flag with source specified", 553 wantError: true, 554 arguments: []string{ 555 "myapp", 556 "src", 557 "--framework", "myframework", 558 "--image", "shipa/go-sample:latest", 559 "--procfile", "./Procfile", 560 }, 561 setup: func(t *testing.T) { 562 dir := t.TempDir() 563 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 564 require.Nil(t, os.Chdir(dir)) 565 }, 566 validate: func(t *testing.T, mock *mockClient) { 567 require.Equal(t, "myframework", mock.app.Spec.Framework) 568 }, 569 params: &deploy.Services{ 570 Client: func() *mockClient { 571 m := newMockClient() 572 m.get[1] = func(_ *mockClient, _ runtime.Object) error { 573 return errors.NewNotFound(v1.Resource(""), "") 574 } 575 return m 576 }(), 577 KubeClient: fake.NewSimpleClientset(), 578 Builder: build.GetSourceHandler(&packMocker{}), 579 GetImageConfig: getImageConfig, 580 Wait: nil, 581 Writer: &bytes.Buffer{}, 582 }, 583 }, 584 { 585 name: "with environment variables", 586 arguments: []string{ 587 "myapp", 588 "src", 589 "--framework", "myframework", 590 "--image", "shipa/go-sample:latest", 591 "--env", "foo=bar,bobb=dobbs", 592 }, 593 setup: func(t *testing.T) { 594 dir := t.TempDir() 595 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 596 require.Nil(t, os.Chdir(dir)) 597 require.Nil(t, ioutil.WriteFile("src/Procfile", []byte(procfile), 0600)) 598 }, 599 params: &deploy.Services{ 600 Client: func() *mockClient { 601 m := newMockClient() 602 m.get[1] = func(_ *mockClient, _ runtime.Object) error { 603 return errors.NewNotFound(v1.Resource(""), "") 604 } 605 return m 606 }(), 607 608 KubeClient: fake.NewSimpleClientset(), 609 Builder: build.GetSourceHandler(&packMocker{}), 610 GetImageConfig: getImageConfig, 611 Wait: nil, 612 Writer: &bytes.Buffer{}, 613 }, 614 }, 615 { 616 name: "with messed up environment variables", 617 wantError: true, 618 arguments: []string{ 619 "myapp", 620 "src", 621 "--framework", "myframework", 622 "--image", "shipa/go-sample:latest", 623 "--env", "foo=bar,bobbdobbs", 624 }, 625 setup: func(t *testing.T) { 626 dir := t.TempDir() 627 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 628 require.Nil(t, os.Chdir(dir)) 629 require.Nil(t, ioutil.WriteFile("src/Procfile", []byte(procfile), 0600)) 630 }, 631 params: &deploy.Services{ 632 Client: func() *mockClient { 633 m := newMockClient() 634 m.get[1] = func(_ *mockClient, _ runtime.Object) error { 635 return errors.NewNotFound(v1.Resource(""), "") 636 } 637 return m 638 }(), 639 KubeClient: fake.NewSimpleClientset(), 640 Builder: build.GetSourceHandler(&packMocker{}), 641 GetImageConfig: getImageConfig, 642 Wait: nil, 643 Writer: &bytes.Buffer{}, 644 }, 645 }, 646 { 647 name: "missing Procfile in src", 648 wantError: true, 649 arguments: []string{ 650 "myapp", 651 "src", 652 "--framework", "myframework", 653 "--image", "shipa/go-sample:latest", 654 }, 655 setup: func(t *testing.T) { 656 dir := t.TempDir() 657 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 658 require.Nil(t, os.Chdir(dir)) 659 }, 660 params: &deploy.Services{ 661 Client: func() *mockClient { 662 m := newMockClient() 663 m.get[1] = func(_ *mockClient, _ runtime.Object) error { 664 return errors.NewNotFound(v1.Resource(""), "") 665 } 666 return m 667 }(), 668 KubeClient: fake.NewSimpleClientset(), 669 Builder: build.GetSourceHandler(&packMocker{}), 670 GetImageConfig: getImageConfig, 671 Wait: nil, 672 Writer: &bytes.Buffer{}, 673 }, 674 }, 675 { 676 name: "illicit use of --unit-version without --units", 677 wantError: true, 678 arguments: []string{ 679 "myapp", 680 "src", 681 "--framework", "myframework", 682 "--image", "shipa/go-sample:latest", 683 "--unit-version", "7", 684 }, 685 setup: func(t *testing.T) { 686 dir := t.TempDir() 687 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 688 require.Nil(t, os.Chdir(dir)) 689 require.Nil(t, ioutil.WriteFile("src/Procfile", []byte(procfile), 0600)) 690 }, 691 params: &deploy.Services{ 692 Client: func() *mockClient { 693 m := newMockClient() 694 m.get[1] = func(_ *mockClient, _ runtime.Object) error { 695 return errors.NewNotFound(v1.Resource(""), "") 696 } 697 return m 698 }(), 699 KubeClient: fake.NewSimpleClientset(), 700 Builder: build.GetSourceHandler(&packMocker{}), 701 GetImageConfig: getImageConfig, 702 Wait: nil, 703 Writer: &bytes.Buffer{}, 704 }, 705 }, 706 { 707 name: "illicit use of --unit-process without --units", 708 wantError: true, 709 arguments: []string{ 710 "myapp", 711 "src", 712 "--framework", "myframework", 713 "--image", "shipa/go-sample:latest", 714 "--unit-process", "test", 715 }, 716 setup: func(t *testing.T) { 717 dir := t.TempDir() 718 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 719 require.Nil(t, os.Chdir(dir)) 720 require.Nil(t, ioutil.WriteFile("src/Procfile", []byte(procfile), 0600)) 721 }, 722 params: &deploy.Services{ 723 Client: func() *mockClient { 724 m := newMockClient() 725 m.get[1] = func(_ *mockClient, _ runtime.Object) error { 726 return errors.NewNotFound(v1.Resource(""), "") 727 } 728 return m 729 }(), 730 KubeClient: fake.NewSimpleClientset(), 731 Builder: build.GetSourceHandler(&packMocker{}), 732 GetImageConfig: getImageConfig, 733 Wait: nil, 734 Writer: &bytes.Buffer{}, 735 }, 736 }, 737 { 738 name: "happy path with --units build from source, update deployment spec", 739 arguments: []string{ 740 "myapp", 741 "src", 742 "--units", "4", 743 "--image", "shipa/go-sample:latest", 744 }, 745 setup: func(t *testing.T) { 746 dir := t.TempDir() 747 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 748 require.Nil(t, os.Chdir(dir)) 749 require.Nil(t, ioutil.WriteFile("src/Procfile", []byte(procfile), 0600)) 750 }, 751 validate: func(t *testing.T, mock *mockClient) { 752 // changes from the previous deployment defined below to the one created by procfile variable above 753 require.Equal(t, mock.app.Spec.Deployments[0].Processes[1].Name, "worker") 754 require.Equal(t, mock.app.Spec.Deployments[0].Processes[1].Cmd[0], "worker") 755 for _, process := range mock.app.Spec.Deployments[0].Processes { 756 require.Equal(t, *process.Units, 4) 757 } 758 require.Equal(t, mock.app.Spec.Framework, "initialframework") 759 760 }, 761 params: &deploy.Services{ 762 Client: func() *mockClient { 763 m := newMockClient() 764 m.app.Spec.Deployments = []ketchv1.AppDeploymentSpec{ 765 { 766 Image: "shipa/go-sample:latest", 767 Version: 1, 768 Processes: []ketchv1.ProcessSpec{ 769 { 770 Name: "web", 771 Cmd: []string{"/cnb/process/web"}, 772 }, 773 { 774 Name: "worker1", 775 Cmd: []string{"do", "work"}, 776 }, 777 { 778 Name: "worker2", 779 Cmd: []string{"do", "work"}, 780 }, 781 }, 782 KetchYaml: nil, 783 Labels: nil, 784 RoutingSettings: ketchv1.RoutingSettings{}, 785 ExposedPorts: nil, 786 }, 787 } 788 return m 789 }(), 790 791 KubeClient: fake.NewSimpleClientset(), 792 Builder: build.GetSourceHandler(&packMocker{}), 793 794 GetImageConfig: func(ctx context.Context, args deploy.ImageConfigRequest) (*registryv1.ConfigFile, error) { 795 return ®istryv1.ConfigFile{ 796 Config: registryv1.Config{ 797 Cmd: []string{"/bin/eatme"}, 798 Labels: map[string]string{"io.buildpacks.build.metadata": packBuildMetadata}, 799 }, 800 }, nil 801 }, 802 Wait: nil, 803 Writer: &bytes.Buffer{}, 804 }, 805 }, 806 { 807 name: "happy path with --units and --unit-process build from source, update deployment spec", 808 arguments: []string{ 809 "myapp", 810 "src", 811 "--units", "4", 812 "--unit-process", "worker", 813 "--image", "shipa/go-sample:latest", 814 }, 815 setup: func(t *testing.T) { 816 dir := t.TempDir() 817 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 818 require.Nil(t, os.Chdir(dir)) 819 require.Nil(t, ioutil.WriteFile("src/Procfile", []byte(procfile), 0600)) 820 }, 821 validate: func(t *testing.T, mock *mockClient) { 822 require.Nil(t, mock.app.Spec.Deployments[0].Processes[0].Units) 823 require.Equal(t, *mock.app.Spec.Deployments[0].Processes[1].Units, 4) 824 // changes from the previous deployment defined below to the one created by procfile variable above 825 require.Equal(t, mock.app.Spec.Deployments[0].Processes[1].Name, "worker") 826 require.Equal(t, mock.app.Spec.Deployments[0].Processes[1].Cmd[0], "worker") 827 require.Equal(t, mock.app.Spec.Framework, "initialframework") 828 829 }, 830 params: &deploy.Services{ 831 Client: func() *mockClient { 832 m := newMockClient() 833 m.app.Spec.Deployments = []ketchv1.AppDeploymentSpec{ 834 { 835 Image: "shipa/go-sample:latest", 836 Version: 1, 837 Processes: []ketchv1.ProcessSpec{ 838 { 839 Name: "web", 840 Cmd: []string{"/cnb/process/web"}, 841 }, 842 { 843 Name: "worker1", 844 Cmd: []string{"do", "work"}, 845 }, 846 }, 847 KetchYaml: nil, 848 Labels: nil, 849 RoutingSettings: ketchv1.RoutingSettings{}, 850 ExposedPorts: nil, 851 }, 852 } 853 return m 854 }(), 855 856 KubeClient: fake.NewSimpleClientset(), 857 Builder: build.GetSourceHandler(&packMocker{}), 858 GetImageConfig: func(ctx context.Context, args deploy.ImageConfigRequest) (*registryv1.ConfigFile, error) { 859 return ®istryv1.ConfigFile{ 860 Config: registryv1.Config{ 861 Cmd: []string{"/bin/eatme"}, 862 Labels: map[string]string{"io.buildpacks.build.metadata": packBuildMetadata}, 863 }, 864 }, nil 865 }, 866 Wait: nil, 867 Writer: &bytes.Buffer{}, 868 }, 869 }, 870 { 871 name: "happy path with --units, --unit-process, and --unit-version build from image", 872 arguments: []string{ 873 "myapp", 874 "--units", "4", 875 "--unit-process", "worker1", 876 "--unit-version", "1", 877 "--image", "shipa/go-sample:latest", 878 }, 879 setup: func(t *testing.T) { 880 dir := t.TempDir() 881 require.Nil(t, os.Mkdir(path.Join(dir, "src"), 0700)) 882 require.Nil(t, os.Chdir(dir)) 883 }, 884 validate: func(t *testing.T, mock *mockClient) { 885 require.Nil(t, mock.app.Spec.Deployments[0].Processes[0].Units) 886 require.Equal(t, *mock.app.Spec.Deployments[0].Processes[1].Units, 4) 887 require.Nil(t, mock.app.Spec.Deployments[1].Processes[0].Units) 888 require.Nil(t, mock.app.Spec.Deployments[1].Processes[1].Units) 889 require.Equal(t, mock.app.Spec.Framework, "initialframework") 890 891 }, 892 params: &deploy.Services{ 893 Client: func() *mockClient { 894 m := newMockClient() 895 // must be canary to have more than one deployment 896 m.app.Spec.Canary.Active = true 897 m.app.Spec.Deployments = []ketchv1.AppDeploymentSpec{ 898 { 899 Image: "shipa/go-sample:latest", 900 Version: 1, 901 Processes: []ketchv1.ProcessSpec{ 902 { 903 Name: "web", 904 Cmd: []string{"/cnb/process/web"}, 905 }, 906 { 907 Name: "worker1", 908 Cmd: []string{"do", "work"}, 909 }, 910 }, 911 KetchYaml: nil, 912 Labels: nil, 913 RoutingSettings: ketchv1.RoutingSettings{}, 914 ExposedPorts: nil, 915 }, 916 { 917 Image: "shipa/go-sample:latest", 918 Version: 2, 919 Processes: []ketchv1.ProcessSpec{ 920 { 921 Name: "web", 922 Cmd: []string{"/cnb/process/web"}, 923 }, 924 { 925 Name: "worker1", 926 Cmd: []string{"do", "work"}, 927 }, 928 }, 929 KetchYaml: nil, 930 Labels: nil, 931 RoutingSettings: ketchv1.RoutingSettings{}, 932 ExposedPorts: nil, 933 }, 934 } 935 return m 936 }(), 937 938 KubeClient: fake.NewSimpleClientset(), 939 Builder: build.GetSourceHandler(&packMocker{}), 940 GetImageConfig: getImageConfig, 941 Wait: nil, 942 Writer: &bytes.Buffer{}, 943 }, 944 }, 945 } 946 947 for _, tc := range tt { 948 t.Run(tc.name, func(t *testing.T) { 949 // restore working dir so we don't screw up other tests 950 wd, err := os.Getwd() 951 require.Nil(t, err) 952 defer func() { 953 _ = os.Chdir(wd) 954 }() 955 956 if tc.setup != nil { 957 tc.setup(t) 958 } 959 cmd := newAppDeployCmd(nil, tc.params, tc.userDefault) 960 cmd.SetArgs(tc.arguments) 961 err = cmd.Execute() 962 if tc.wantError { 963 t.Logf("got error %s", err) 964 require.NotNil(t, err) 965 return 966 } 967 968 require.Nil(t, err) 969 if tc.validate != nil { 970 mock, ok := tc.params.Client.(*mockClient) 971 require.True(t, ok) 972 tc.validate(t, mock) 973 } 974 }) 975 } 976 }