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 &registryv1.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 &registryv1.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 &registryv1.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  }