From 3a60e6561066cbea79050f44038b29a33a7ecda3 Mon Sep 17 00:00:00 2001 From: penghaoh Date: Wed, 7 Oct 2020 14:50:44 -0700 Subject: [PATCH] Add prompt for image location --- internal/pkg/cli/job_init.go | 32 ++++++++-- internal/pkg/cli/job_init_test.go | 43 +++++++++++++ internal/pkg/cli/svc_init.go | 69 ++++++++++++++------- internal/pkg/cli/svc_init_test.go | 46 +++++++++++++- internal/pkg/term/selector/selector.go | 32 +++++----- internal/pkg/term/selector/selector_test.go | 7 ++- 6 files changed, 187 insertions(+), 42 deletions(-) diff --git a/internal/pkg/cli/job_init.go b/internal/pkg/cli/job_init.go index acc041aa7d0..0b68474057e 100644 --- a/internal/pkg/cli/job_init.go +++ b/internal/pkg/cli/job_init.go @@ -142,9 +142,15 @@ func (o *initJobOpts) Ask() error { if err := o.askJobName(); err != nil { return err } - if err := o.askDockerfile(); err != nil { + useImage, err := o.askDockerfile() + if err != nil { return err } + if useImage { + if err := o.askImage(); err != nil { + return err + } + } if err := o.askSchedule(); err != nil { return err } @@ -260,10 +266,23 @@ func (o *initJobOpts) askJobName() error { return nil } -func (o *initJobOpts) askDockerfile() error { - if o.dockerfilePath != "" || o.image != "" { +func (o *initJobOpts) askImage() error { + if o.image != "" { return nil } + image, err := o.prompt.Get(wkldInitImagePrompt, wkldInitImagePromptHelp, nil, + prompt.WithFinalMessage("Image:")) + if err != nil { + return fmt.Errorf("get image location: %w", err) + } + o.image = image + return nil +} + +func (o *initJobOpts) askDockerfile() (useImage bool, err error) { + if o.dockerfilePath != "" || o.image != "" { + return false, nil + } df, err := o.sel.Dockerfile( fmt.Sprintf(fmtWkldInitDockerfilePrompt, color.HighlightUserInput(o.name)), fmt.Sprintf(fmtWkldInitDockerfilePathPrompt, color.HighlightUserInput(o.name)), @@ -274,10 +293,13 @@ func (o *initJobOpts) askDockerfile() error { }, ) if err != nil { - return fmt.Errorf("select Dockerfile: %w", err) + return false, fmt.Errorf("select Dockerfile: %w", err) + } + if df == selector.DockerfilePromptUseImage { + return true, nil } o.dockerfilePath = df - return nil + return false, nil } func (o *initJobOpts) askSchedule() error { diff --git a/internal/pkg/cli/job_init_test.go b/internal/pkg/cli/job_init_test.go index 436fe5ccffd..839f8988c2c 100644 --- a/internal/pkg/cli/job_init_test.go +++ b/internal/pkg/cli/job_init_test.go @@ -207,6 +207,49 @@ func TestJobInitOpts_Ask(t *testing.T) { wantedErr: nil, wantedSchedule: wantedCronSchedule, }, + "returns an error if fail to get image location": { + inJobType: wantedJobType, + inJobName: wantedJobName, + inDockerfilePath: "", + + mockPrompt: func(m *mocks.Mockprompter) { + m.EXPECT().Get(wkldInitImagePrompt, wkldInitImagePromptHelp, nil, gomock.Any()). + Return("", mockError) + }, + mockSel: func(m *mocks.MockinitJobSelector) { + m.EXPECT().Dockerfile( + gomock.Eq(fmt.Sprintf(fmtWkldInitDockerfilePrompt, wantedJobName)), + gomock.Eq(fmt.Sprintf(fmtWkldInitDockerfilePathPrompt, wantedJobName)), + gomock.Eq(wkldInitDockerfileHelpPrompt), + gomock.Eq(wkldInitDockerfilePathHelpPrompt), + gomock.Any(), + ).Return("Use an existing image instead", nil) + }, + mockFileSystem: func(mockFS afero.Fs) {}, + wantedErr: fmt.Errorf("get image location: mock error"), + }, + "using existing image": { + inJobType: wantedJobType, + inJobName: wantedJobName, + inJobSchedule: wantedCronSchedule, + inDockerfilePath: "", + + mockPrompt: func(m *mocks.Mockprompter) { + m.EXPECT().Get(wkldInitImagePrompt, wkldInitImagePromptHelp, nil, gomock.Any()). + Return("mockImage", nil) + }, + mockSel: func(m *mocks.MockinitJobSelector) { + m.EXPECT().Dockerfile( + gomock.Eq(fmt.Sprintf(fmtWkldInitDockerfilePrompt, wantedJobName)), + gomock.Eq(fmt.Sprintf(fmtWkldInitDockerfilePathPrompt, wantedJobName)), + gomock.Eq(wkldInitDockerfileHelpPrompt), + gomock.Eq(wkldInitDockerfilePathHelpPrompt), + gomock.Any(), + ).Return("Use an existing image instead", nil) + }, + mockFileSystem: func(mockFS afero.Fs) {}, + wantedSchedule: wantedCronSchedule, + }, "prompt for existing dockerfile": { inJobType: wantedJobType, inJobName: wantedJobName, diff --git a/internal/pkg/cli/svc_init.go b/internal/pkg/cli/svc_init.go index d159165da41..772547556a4 100644 --- a/internal/pkg/cli/svc_init.go +++ b/internal/pkg/cli/svc_init.go @@ -51,6 +51,10 @@ const ( fmtAddSvcToAppStart = "Creating ECR repositories for service %s." fmtAddSvcToAppFailed = "Failed to create ECR repositories for service %s.\n" fmtAddSvcToAppComplete = "Created ECR repositories for service %s.\n" + + wkldInitImagePrompt = `What's the location of the image to use?` + wkldInitImagePromptHelp = `The location of an existing Docker image. Docker Hub registry are available by default. +Other repositories are specified with either repository-url/image:tag or repository-url/image@digest` ) const ( @@ -161,9 +165,15 @@ func (o *initSvcOpts) Ask() error { if err := o.askSvcName(); err != nil { return err } - if err := o.askDockerfile(); err != nil { + useImage, err := o.askDockerfile() + if err != nil { return err } + if useImage { + if err := o.askImage(); err != nil { + return err + } + } if err := o.askSvcPort(); err != nil { return err } @@ -327,10 +337,23 @@ func (o *initSvcOpts) askSvcName() error { return nil } +func (o *initSvcOpts) askImage() error { + if o.image != "" { + return nil + } + image, err := o.prompt.Get(wkldInitImagePrompt, wkldInitImagePromptHelp, nil, + prompt.WithFinalMessage("Image:")) + if err != nil { + return fmt.Errorf("get image location: %w", err) + } + o.image = image + return nil +} + // askDockerfile prompts for the Dockerfile by looking at sub-directories with a Dockerfile. -func (o *initSvcOpts) askDockerfile() error { +func (o *initSvcOpts) askDockerfile() (useImage bool, err error) { if o.dockerfilePath != "" || o.image != "" { - return nil + return false, nil } df, err := o.sel.Dockerfile( fmt.Sprintf(fmtWkldInitDockerfilePrompt, color.HighlightUserInput(o.name)), @@ -342,10 +365,13 @@ func (o *initSvcOpts) askDockerfile() error { }, ) if err != nil { - return err + return false, fmt.Errorf("select Dockerfile: %w", err) + } + if df == selector.DockerfilePromptUseImage { + return true, nil } o.dockerfilePath = df - return nil + return false, nil } func (o *initSvcOpts) askSvcPort() error { @@ -354,22 +380,23 @@ func (o *initSvcOpts) askSvcPort() error { return nil } - o.setupParser(o) - ports, err := o.df.GetExposedPorts() - // Ignore any errors in dockerfile parsing--we'll use the default instead. - if err != nil { - log.Debugln(err.Error()) - } - var defaultPort string - switch len(ports) { - case 0: - // There were no ports detected, keep the default port prompt. - defaultPort = defaultSvcPortString - case 1: - o.port = ports[0] - return nil - default: - defaultPort = strconv.Itoa(int(ports[0])) + defaultPort := defaultSvcPortString + if o.dockerfilePath != "" { + o.setupParser(o) + ports, err := o.df.GetExposedPorts() + // Ignore any errors in dockerfile parsing--we'll use the default instead. + if err != nil { + log.Debugln(err.Error()) + } + switch len(ports) { + case 0: + // There were no ports detected, keep the default port prompt. + case 1: + o.port = ports[0] + return nil + default: + defaultPort = strconv.Itoa(int(ports[0])) + } } port, err := o.prompt.Get( diff --git a/internal/pkg/cli/svc_init_test.go b/internal/pkg/cli/svc_init_test.go index 03753adf175..71a2a322b9a 100644 --- a/internal/pkg/cli/svc_init_test.go +++ b/internal/pkg/cli/svc_init_test.go @@ -189,6 +189,50 @@ func TestSvcInitOpts_Ask(t *testing.T) { mockDockerfile: func(m *mocks.MockdockerfileParser) {}, wantedErr: nil, }, + "returns an error if fail to get image location": { + inSvcType: wantedSvcType, + inSvcName: wantedSvcName, + inSvcPort: wantedSvcPort, + inDockerfilePath: "", + + mockPrompt: func(m *mocks.Mockprompter) { + m.EXPECT().Get(wkldInitImagePrompt, wkldInitImagePromptHelp, nil, gomock.Any()). + Return("", mockError) + }, + mockSel: func(m *mocks.MockdockerfileSelector) { + m.EXPECT().Dockerfile( + gomock.Eq(fmt.Sprintf(fmtWkldInitDockerfilePrompt, wantedSvcName)), + gomock.Eq(fmt.Sprintf(fmtWkldInitDockerfilePathPrompt, wantedSvcName)), + gomock.Eq(wkldInitDockerfileHelpPrompt), + gomock.Eq(wkldInitDockerfilePathHelpPrompt), + gomock.Any(), + ).Return("Use an existing image instead", nil) + }, + mockDockerfile: func(m *mocks.MockdockerfileParser) {}, + wantedErr: fmt.Errorf("get image location: mock error"), + }, + "using existing image": { + inSvcType: wantedSvcType, + inSvcName: wantedSvcName, + inDockerfilePath: "", + + mockPrompt: func(m *mocks.Mockprompter) { + m.EXPECT().Get(wkldInitImagePrompt, wkldInitImagePromptHelp, nil, gomock.Any()). + Return("mockImage", nil) + m.EXPECT().Get(gomock.Eq(fmt.Sprintf(svcInitSvcPortPrompt, "port")), gomock.Any(), gomock.Any(), gomock.Any()). + Return(defaultSvcPortString, nil) + }, + mockSel: func(m *mocks.MockdockerfileSelector) { + m.EXPECT().Dockerfile( + gomock.Eq(fmt.Sprintf(fmtWkldInitDockerfilePrompt, wantedSvcName)), + gomock.Eq(fmt.Sprintf(fmtWkldInitDockerfilePathPrompt, wantedSvcName)), + gomock.Eq(wkldInitDockerfileHelpPrompt), + gomock.Eq(wkldInitDockerfilePathHelpPrompt), + gomock.Any(), + ).Return("Use an existing image instead", nil) + }, + mockDockerfile: func(m *mocks.MockdockerfileParser) {}, + }, "select Dockerfile": { inSvcType: wantedSvcType, inSvcName: wantedSvcName, @@ -221,7 +265,7 @@ func TestSvcInitOpts_Ask(t *testing.T) { }, mockPrompt: func(m *mocks.Mockprompter) {}, mockDockerfile: func(m *mocks.MockdockerfileParser) {}, - wantedErr: fmt.Errorf("some error"), + wantedErr: fmt.Errorf("select Dockerfile: some error"), }, "asks for port if not specified": { inSvcType: wantedSvcType, diff --git a/internal/pkg/term/selector/selector.go b/internal/pkg/term/selector/selector.go index 0fe530f5140..9e9995a0d61 100644 --- a/internal/pkg/term/selector/selector.go +++ b/internal/pkg/term/selector/selector.go @@ -31,21 +31,10 @@ const ( yearly = "Yearly" ) -var scheduleTypes = []string{ - rate, - fixedSchedule, -} - -var presetSchedules = []string{ - custom, - hourly, - daily, - weekly, - monthly, - yearly, -} +const ( + // DockerfilePromptUseImage is the option for using existing image instead of Dockerfile. + DockerfilePromptUseImage = "Use an existing image instead" -var ( ratePrompt = "How long would you like to wait between executions?" rateHelp = `You can specify the time as a duration string. (For example, 2m, 1h30m, 24h)` @@ -62,6 +51,20 @@ For example: 0 17 ? * MON-FRI (5 pm on weekdays) (Y)es will continue execution. (N)o will allow you to input a different schedule.` ) +var scheduleTypes = []string{ + rate, + fixedSchedule, +} + +var presetSchedules = []string{ + custom, + hourly, + daily, + weekly, + monthly, + yearly, +} + // Prompter wraps the methods to ask for inputs from the terminal. type Prompter interface { Get(message, help string, validator prompt.ValidatorFunc, promptOpts ...prompt.Option) (string, error) @@ -436,6 +439,7 @@ func (s *WorkspaceSelect) Dockerfile(selPrompt, notFoundPrompt, selHelp, notFoun // If Dockerfiles are found in the current directory or subdirectory one level down, ask the user to select one. var sel string if err == nil { + dockerfiles = append(dockerfiles, DockerfilePromptUseImage) sel, err = s.prompt.SelectOne( selPrompt, selHelp, diff --git a/internal/pkg/term/selector/selector_test.go b/internal/pkg/term/selector/selector_test.go index 9702ce3c366..4850848bdb6 100644 --- a/internal/pkg/term/selector/selector_test.go +++ b/internal/pkg/term/selector/selector_test.go @@ -884,7 +884,12 @@ func TestWorkspaceSelect_Dockerfile(t *testing.T) { mockPrompt: func(m *mocks.MockPrompter) { m.EXPECT().SelectOne( gomock.Any(), gomock.Any(), - gomock.Eq(dockerfiles), + gomock.Eq([]string{ + "./Dockerfile", + "backend/Dockerfile", + "frontend/Dockerfile", + "Use an existing image instead", + }), gomock.Any(), ).Return("frontend/Dockerfile", nil) },