diff --git a/internal/pkg/aws/codepipeline/codepipeline.go b/internal/pkg/aws/codepipeline/codepipeline.go index 257072b0885..13c5ea193c1 100644 --- a/internal/pkg/aws/codepipeline/codepipeline.go +++ b/internal/pkg/aws/codepipeline/codepipeline.go @@ -38,6 +38,7 @@ type CodePipeline struct { // Pipeline represents an existing CodePipeline resource. type Pipeline struct { + // Name is the resource name of the pipeline in CodePipeline, e.g. myapp-mypipeline-RANDOMSTRING. Name string `json:"pipelineName"` Region string `json:"region"` AccountID string `json:"accountId"` diff --git a/internal/pkg/cli/flag.go b/internal/pkg/cli/flag.go index 1c61386d829..43b548c2646 100644 --- a/internal/pkg/cli/flag.go +++ b/internal/pkg/cli/flag.go @@ -237,6 +237,7 @@ Defaults to all logs. Only one of end-time / follow may be used.` pipelineResourcesFlagDescription = "Optional. Show the resources in your pipeline." localSvcFlagDescription = "Only show services in the workspace." localJobFlagDescription = "Only show jobs in the workspace." + localPipelineFlagDescription = "Only show pipelines in the workspace." deleteSecretFlagDescription = "Deletes AWS Secrets Manager secret associated with a pipeline source repository." svcPortFlagDescription = "The port on which your service listens." diff --git a/internal/pkg/cli/job_init.go b/internal/pkg/cli/job_init.go index 81640d2c17f..03c06443d21 100644 --- a/internal/pkg/cli/job_init.go +++ b/internal/pkg/cli/job_init.go @@ -125,7 +125,7 @@ func newInitJobOpts(vars initJobVars) (*initJobOpts, error) { func (o *initJobOpts) Validate() error { // If this app is pending creation, we'll skip validation. if !o.wsPendingCreation { - if err := validateInputApp(o.wsAppName, o.appName, o.store); err != nil { + if err := validateWorkspaceApp(o.wsAppName, o.appName, o.store); err != nil { return err } o.appName = o.wsAppName diff --git a/internal/pkg/cli/list/list.go b/internal/pkg/cli/list/list.go index 8dd909c6562..0866cc4edea 100644 --- a/internal/pkg/cli/list/list.go +++ b/internal/pkg/cli/list/list.go @@ -135,10 +135,9 @@ func filterByName(wklds []*config.Workload, wantedNames []string) []*config.Work } var filtered []*config.Workload for _, wkld := range wklds { - if _, ok := isWanted[wkld.Name]; !ok { - continue + if isWanted[wkld.Name] { + filtered = append(filtered, wkld) } - filtered = append(filtered, wkld) } return filtered } diff --git a/internal/pkg/cli/pipeline_init.go b/internal/pkg/cli/pipeline_init.go index deab2ffb735..87d09daf51b 100644 --- a/internal/pkg/cli/pipeline_init.go +++ b/internal/pkg/cli/pipeline_init.go @@ -170,7 +170,7 @@ func (o *initPipelineOpts) Validate() error { // Ask prompts for required fields that are not passed in and validates them. func (o *initPipelineOpts) Ask() error { // This command must be executed in the app's workspace because the pipeline manifest and buildspec will be created and stored. - if err := validateInputApp(o.wsAppName, o.appName, o.store); err != nil { + if err := validateWorkspaceApp(o.wsAppName, o.appName, o.store); err != nil { return err } o.appName = o.wsAppName diff --git a/internal/pkg/cli/pipeline_list.go b/internal/pkg/cli/pipeline_list.go index 4a0028d46a5..257bb686035 100644 --- a/internal/pkg/cli/pipeline_list.go +++ b/internal/pkg/cli/pipeline_list.go @@ -4,21 +4,26 @@ package cli import ( + "context" "encoding/json" "fmt" "io" "os" - "strings" + "sort" + "sync" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ssm" "github.com/aws/copilot-cli/internal/pkg/aws/identity" rg "github.com/aws/copilot-cli/internal/pkg/aws/resourcegroups" + "github.com/aws/copilot-cli/internal/pkg/deploy" + "github.com/aws/copilot-cli/internal/pkg/describe" + "github.com/aws/copilot-cli/internal/pkg/workspace" + "golang.org/x/sync/errgroup" - "github.com/aws/copilot-cli/internal/pkg/aws/codepipeline" "github.com/aws/copilot-cli/internal/pkg/aws/sessions" "github.com/aws/copilot-cli/internal/pkg/config" - "github.com/aws/copilot-cli/internal/pkg/deploy" "github.com/aws/copilot-cli/internal/pkg/term/prompt" "github.com/aws/copilot-cli/internal/pkg/term/selector" "github.com/spf13/cobra" @@ -27,43 +32,71 @@ import ( const ( pipelineListAppNamePrompt = "Which application are the pipelines in?" pipelineListAppNameHelper = "An application is a collection of related services." + + pipelineListTimeout = 10 * time.Second ) type listPipelineVars struct { - appName string - shouldOutputJSON bool + appName string + shouldOutputJSON bool + shouldShowLocalPipelines bool } type listPipelineOpts struct { listPipelineVars - codepipeline pipelineGetter - pipelineLister deployedPipelineLister prompt prompter sel configSelector store store w io.Writer + workspace wsPipelineGetter + pipelineLister deployedPipelineLister + + newDescriber newPipelineDescriberFunc + + wsAppName string } +type newPipelineDescriberFunc func(pipeline deploy.Pipeline) (describer, error) + func newListPipelinesOpts(vars listPipelineVars) (*listPipelineOpts, error) { + ws, err := workspace.New() + if err != nil { + return nil, err + } + defaultSession, err := sessions.ImmutableProvider(sessions.UserAgentExtras("pipeline ls")).Default() if err != nil { return nil, fmt.Errorf("default session: %w", err) } + + var wsAppName string + if vars.shouldShowLocalPipelines { + wsAppName = tryReadingAppName() + } + store := config.NewSSMStore(identity.New(defaultSession), ssm.New(defaultSession), aws.StringValue(defaultSession.Config.Region)) prompter := prompt.New() return &listPipelineOpts{ listPipelineVars: vars, - codepipeline: codepipeline.New(defaultSession), pipelineLister: deploy.NewPipelineStore(rg.New(defaultSession)), prompt: prompter, sel: selector.NewConfigSelect(prompter, store), store: store, w: os.Stdout, + workspace: ws, + newDescriber: func(pipeline deploy.Pipeline) (describer, error) { + return describe.NewPipelineDescriber(pipeline, false) + }, + wsAppName: wsAppName, }, nil } // Ask asks for and validates fields that are required but not passed in. func (o *listPipelineOpts) Ask() error { + if o.shouldShowLocalPipelines { + return validateWorkspaceApp(o.wsAppName, o.appName, o.store) + } + if o.appName != "" { if _, err := o.store.GetApplication(o.appName); err != nil { return fmt.Errorf("validate application: %w", err) @@ -75,60 +108,172 @@ func (o *listPipelineOpts) Ask() error { } o.appName = app } + return nil } // Execute writes the pipelines. func (o *listPipelineOpts) Execute() error { - var out string - pipelines, err := o.pipelineLister.ListDeployedPipelines(o.appName) + ctx, cancel := context.WithTimeout(context.Background(), pipelineListTimeout) + defer cancel() + + switch { + case o.shouldShowLocalPipelines && o.shouldOutputJSON: + return o.jsonOutputLocal(ctx) + case o.shouldShowLocalPipelines: + return o.humanOutputLocal() + case o.shouldOutputJSON: + return o.jsonOutputDeployed(ctx) + } + + return o.humanOutputDeployed() +} + +// jsonOutputLocal prints data about all pipelines in the current workspace. +// If a local pipeline has been deployed, data from codepipeline is included. +func (o *listPipelineOpts) jsonOutputLocal(ctx context.Context) error { + local, err := o.workspace.ListPipelines() if err != nil { - return fmt.Errorf("list deployed pipelines in application %s: %w", o.appName, err) + return err } - if o.shouldOutputJSON { - var pipelineInfo []*codepipeline.Pipeline - for _, pipeline := range pipelines { - info, err := o.codepipeline.GetPipeline(pipeline.ResourceName) - if err != nil { - return fmt.Errorf("get pipeline info for %s: %w", pipeline.Name, err) - } - pipelineInfo = append(pipelineInfo, info) - } - data, err := o.jsonOutput(pipelineInfo) - if err != nil { - return err + deployed, err := getDeployedPipelines(ctx, o.appName, o.pipelineLister, o.newDescriber) + if err != nil { + return err + } + + cp := make(map[string]*describe.Pipeline) + for _, pipeline := range deployed { + cp[pipeline.Name] = pipeline + } + + type info struct { + Name string `json:"name"` + ManifestPath string `json:"manifestPath"` + PipelineName string `json:"pipelineName,omitempty"` + } + + var out struct { + Pipelines []info `json:"pipelines"` + } + for _, pipeline := range local { + i := info{ + Name: pipeline.Name, + ManifestPath: pipeline.Path, } - out = data - } else { - var pipelineNames []string - for _, pipeline := range pipelines { - pipelineNames = append(pipelineNames, pipeline.Name) + + if v, ok := cp[pipeline.Name]; ok { + i.PipelineName = v.Pipeline.Name } - out = o.humanOutput(pipelineNames) + + out.Pipelines = append(out.Pipelines, i) + } + + b, err := json.Marshal(out) + if err != nil { + return fmt.Errorf("marshal pipelines: %w", err) } - fmt.Fprint(o.w, out) + fmt.Fprintf(o.w, "%s\n", b) return nil } -func (o *listPipelineOpts) jsonOutput(pipelines []*codepipeline.Pipeline) (string, error) { +// humanOutputLocal prints the name of all pipelines in the current workspace. +func (o *listPipelineOpts) humanOutputLocal() error { + local, err := o.workspace.ListPipelines() + if err != nil { + return err + } + + for _, pipeline := range local { + fmt.Fprintln(o.w, pipeline.Name) + } + + return nil +} + +// jsonOutputDeployed prints data about all pipelines in the given app that have been deployed. +func (o *listPipelineOpts) jsonOutputDeployed(ctx context.Context) error { + pipelines, err := getDeployedPipelines(ctx, o.appName, o.pipelineLister, o.newDescriber) + if err != nil { + return err + } + type serializedPipelines struct { - Pipelines []*codepipeline.Pipeline `json:"pipelines"` + Pipelines []*describe.Pipeline `json:"pipelines"` } b, err := json.Marshal(serializedPipelines{Pipelines: pipelines}) if err != nil { - return "", fmt.Errorf("marshal pipelines: %w", err) + return fmt.Errorf("marshal pipelines: %w", err) + } + + fmt.Fprintf(o.w, "%s\n", b) + return nil +} + +// humanOutputDeployed prints the name of all pipelines in the given app that have been deployed. +func (o *listPipelineOpts) humanOutputDeployed() error { + pipelines, err := o.pipelineLister.ListDeployedPipelines(o.appName) + if err != nil { + return fmt.Errorf("list deployed pipelines: %w", err) + } + + sort.Slice(pipelines, func(i, j int) bool { + return pipelines[i].Name < pipelines[j].Name + }) + + for _, p := range pipelines { + fmt.Fprintln(o.w, p.Name) } - return fmt.Sprintf("%s\n", b), nil + + return nil } -func (o *listPipelineOpts) humanOutput(pipelines []string) string { - b := &strings.Builder{} - for _, pipeline := range pipelines { - fmt.Fprintln(b, pipeline) +func getDeployedPipelines(ctx context.Context, app string, lister deployedPipelineLister, newDescriber newPipelineDescriberFunc) ([]*describe.Pipeline, error) { + pipelines, err := lister.ListDeployedPipelines(app) + if err != nil { + return nil, fmt.Errorf("list deployed pipelines: %w", err) + } + + var mux sync.Mutex + var res []*describe.Pipeline + + g, _ := errgroup.WithContext(ctx) + + for i := range pipelines { + pipeline := pipelines[i] + g.Go(func() error { + d, err := newDescriber(pipeline) + if err != nil { + return fmt.Errorf("create pipeline describer for %q: %w", pipeline.ResourceName, err) + } + + info, err := d.Describe() + if err != nil { + return fmt.Errorf("describe pipeline %q: %w", pipeline.ResourceName, err) + } + + p, ok := info.(*describe.Pipeline) + if !ok { + return fmt.Errorf("unexpected describer for %q: %T", pipeline.ResourceName, info) + } + + mux.Lock() + defer mux.Unlock() + res = append(res, p) + return nil + }) + } + + if err := g.Wait(); err != nil { + return nil, err } - return b.String() + + sort.Slice(res, func(i, j int) bool { + return res[i].Name < res[j].Name + }) + + return res, nil } // buildPipelineListCmd builds the command for showing a list of all deployed pipelines. @@ -154,5 +299,6 @@ func buildPipelineListCmd() *cobra.Command { cmd.Flags().StringVarP(&vars.appName, appFlag, appFlagShort, tryReadingAppName(), appFlagDescription) cmd.Flags().BoolVar(&vars.shouldOutputJSON, jsonFlag, false, jsonFlagDescription) + cmd.Flags().BoolVar(&vars.shouldShowLocalPipelines, localFlag, false, localPipelineFlagDescription) return cmd } diff --git a/internal/pkg/cli/pipeline_list_test.go b/internal/pkg/cli/pipeline_list_test.go index fc7b5edf022..3957e04fa16 100644 --- a/internal/pkg/cli/pipeline_list_test.go +++ b/internal/pkg/cli/pipeline_list_test.go @@ -12,64 +12,80 @@ import ( "github.com/aws/copilot-cli/internal/pkg/aws/codepipeline" "github.com/aws/copilot-cli/internal/pkg/cli/mocks" "github.com/aws/copilot-cli/internal/pkg/deploy" + "github.com/aws/copilot-cli/internal/pkg/describe" + "github.com/aws/copilot-cli/internal/pkg/workspace" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) type pipelineListMocks struct { prompt *mocks.Mockprompter - pipelineGetter *mocks.MockpipelineGetter - pipelineLister *mocks.MockdeployedPipelineLister sel *mocks.MockconfigSelector + store *mocks.Mockstore + workspace *mocks.MockwsPipelineGetter + pipelineLister *mocks.MockdeployedPipelineLister + describer *mocks.Mockdescriber } func TestPipelineList_Ask(t *testing.T) { testCases := map[string]struct { - inputApp string + inputApp string + inWsAppName string + shouldShowLocalPipelines bool - mockSelector func(m *mocks.MockconfigSelector) - mockStore func(m *mocks.Mockstore) + setupMocks func(mocks pipelineListMocks) wantedApp string wantedErr error }{ "success with no flags set": { - mockSelector: func(m *mocks.MockconfigSelector) { - m.EXPECT().Application(pipelineListAppNamePrompt, pipelineListAppNameHelper).Return("my-app", nil) + setupMocks: func(m pipelineListMocks) { + m.sel.EXPECT().Application(pipelineListAppNamePrompt, pipelineListAppNameHelper).Return("my-app", nil) }, - mockStore: func(m *mocks.Mockstore) {}, wantedApp: "my-app", wantedErr: nil, }, "success with app flag set": { - inputApp: "my-app", - mockSelector: func(m *mocks.MockconfigSelector) {}, - mockStore: func(m *mocks.Mockstore) { - m.EXPECT().GetApplication("my-app").Return(nil, nil) + inputApp: "my-app", + setupMocks: func(m pipelineListMocks) { + m.store.EXPECT().GetApplication("my-app").Return(nil, nil) }, - wantedApp: "my-app", wantedErr: nil, }, "error if fail to select app": { - mockSelector: func(m *mocks.MockconfigSelector) { - m.EXPECT().Application(pipelineListAppNamePrompt, pipelineListAppNameHelper).Return("", errors.New("some error")) + setupMocks: func(m pipelineListMocks) { + m.sel.EXPECT().Application(pipelineListAppNamePrompt, pipelineListAppNameHelper).Return("", errors.New("some error")) }, - mockStore: func(m *mocks.Mockstore) {}, - wantedApp: "my-app", wantedErr: fmt.Errorf("select application: some error"), }, "error if passed-in app doesn't exist": { - inputApp: "my-app", - mockSelector: func(m *mocks.MockconfigSelector) {}, - mockStore: func(m *mocks.Mockstore) { - m.EXPECT().GetApplication("my-app").Return(nil, errors.New("some error")) + inputApp: "my-app", + setupMocks: func(m pipelineListMocks) { + m.store.EXPECT().GetApplication("my-app").Return(nil, errors.New("some error")) }, - wantedApp: "", wantedErr: errors.New("validate application: some error"), }, + "using workspace successful": { + inWsAppName: "my-app", + setupMocks: func(m pipelineListMocks) { + m.store.EXPECT().GetApplication("my-app").Return(nil, nil) + }, + shouldShowLocalPipelines: true, + }, + "--local not in workspace": { + inWsAppName: "", + shouldShowLocalPipelines: true, + wantedErr: errNoAppInWorkspace, + }, + "--local workspace and app name mismatch": { + inWsAppName: "my-app", + inputApp: "not-my-app", + shouldShowLocalPipelines: true, + wantedErr: errors.New("cannot specify app not-my-app because the workspace is already registered with app my-app"), + }, } for name, tc := range testCases { @@ -77,26 +93,37 @@ func TestPipelineList_Ask(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockSelector := mocks.NewMockconfigSelector(ctrl) - mockStore := mocks.NewMockstore(ctrl) - tc.mockSelector(mockSelector) - tc.mockStore(mockStore) + mocks := pipelineListMocks{ + prompt: mocks.NewMockprompter(ctrl), + sel: mocks.NewMockconfigSelector(ctrl), + store: mocks.NewMockstore(ctrl), + workspace: mocks.NewMockwsPipelineGetter(ctrl), + pipelineLister: mocks.NewMockdeployedPipelineLister(ctrl), + } + if tc.setupMocks != nil { + tc.setupMocks(mocks) + } - listPipelines := &listPipelineOpts{ + opts := &listPipelineOpts{ listPipelineVars: listPipelineVars{ - appName: tc.inputApp, + appName: tc.inputApp, + shouldShowLocalPipelines: tc.shouldShowLocalPipelines, }, - sel: mockSelector, - store: mockStore, + prompt: mocks.prompt, + sel: mocks.sel, + store: mocks.store, + workspace: mocks.workspace, + pipelineLister: mocks.pipelineLister, + wsAppName: tc.inWsAppName, } - err := listPipelines.Ask() + err := opts.Ask() if tc.wantedErr != nil { require.EqualError(t, err, tc.wantedErr.Error()) } else { require.NoError(t, err) - require.Equal(t, tc.wantedApp, listPipelines.appName, "expected app names to match") + require.Equal(t, tc.wantedApp, opts.appName, "expected app names to match") } }) } @@ -124,31 +151,37 @@ func TestPipelineList_Execute(t *testing.T) { } mockError := errors.New("mock error") testCases := map[string]struct { - shouldOutputJSON bool - setupMocks func(m pipelineListMocks) - expectedContent string - expectedErr error + shouldOutputJSON bool + shouldShowLocalPipelines bool + setupMocks func(m pipelineListMocks) + expectedContent string + expectedErr error }{ "with JSON output": { shouldOutputJSON: true, setupMocks: func(m pipelineListMocks) { m.pipelineLister.EXPECT().ListDeployedPipelines(mockAppName).Return([]deploy.Pipeline{mockPipeline, mockLegacyPipeline}, nil) - m.pipelineGetter.EXPECT(). - GetPipeline(mockPipelineResourceName). - Return(&codepipeline.Pipeline{Name: mockPipelineResourceName}, nil) - m.pipelineGetter.EXPECT(). - GetPipeline(mockLegacyPipelineResourceName). - Return(&codepipeline.Pipeline{Name: mockLegacyPipelineResourceName}, nil) + m.describer.EXPECT().Describe().Return(&describe.Pipeline{ + Name: mockLegacyPipelineName, + Pipeline: codepipeline.Pipeline{ + Name: mockLegacyPipelineResourceName, + }, + }, nil) + m.describer.EXPECT().Describe().Return(&describe.Pipeline{ + Name: mockPipelineName, + Pipeline: codepipeline.Pipeline{ + Name: mockPipelineResourceName, + }, + }, nil) }, - expectedContent: "{\"pipelines\":[{\"pipelineName\":\"pipeline-coolapp-my-pipeline-repo-ABCDERANDOMRANDOM\",\"region\":\"\",\"accountId\":\"\",\"stages\":null,\"createdAt\":\"0001-01-01T00:00:00Z\",\"updatedAt\":\"0001-01-01T00:00:00Z\"},{\"pipelineName\":\"bad-goose\",\"region\":\"\",\"accountId\":\"\",\"stages\":null,\"createdAt\":\"0001-01-01T00:00:00Z\",\"updatedAt\":\"0001-01-01T00:00:00Z\"}]}\n", + expectedContent: `{"pipelines":[{"name":"bad-goose","pipelineName":"bad-goose","region":"","accountId":"","stages":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"},{"name":"my-pipeline-repo","pipelineName":"pipeline-coolapp-my-pipeline-repo-ABCDERANDOMRANDOM","region":"","accountId":"","stages":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"}]}` + "\n", }, "with human output": { - shouldOutputJSON: false, setupMocks: func(m pipelineListMocks) { m.pipelineLister.EXPECT().ListDeployedPipelines(mockAppName).Return([]deploy.Pipeline{mockPipeline, mockLegacyPipeline}, nil) }, - expectedContent: `my-pipeline-repo -bad-goose + expectedContent: `bad-goose +my-pipeline-repo `, }, "with failed call to list pipelines": { @@ -156,17 +189,41 @@ bad-goose setupMocks: func(m pipelineListMocks) { m.pipelineLister.EXPECT().ListDeployedPipelines(mockAppName).Return(nil, mockError) }, - expectedErr: fmt.Errorf("list deployed pipelines in application coolapp: mock error"), + expectedErr: fmt.Errorf("list deployed pipelines: mock error"), }, "with failed call to get pipeline info": { shouldOutputJSON: true, setupMocks: func(m pipelineListMocks) { m.pipelineLister.EXPECT().ListDeployedPipelines(mockAppName).Return([]deploy.Pipeline{mockPipeline}, nil) - m.pipelineGetter.EXPECT(). - GetPipeline(mockPipelineResourceName). - Return(nil, mockError) + m.describer.EXPECT().Describe().Return(nil, mockError) + }, + expectedErr: fmt.Errorf(`describe pipeline %q: mock error`, mockPipelineResourceName), + }, + "ls --local": { + shouldShowLocalPipelines: true, + setupMocks: func(m pipelineListMocks) { + m.workspace.EXPECT().ListPipelines().Return([]workspace.PipelineManifest{{Name: mockLegacyPipeline.Name}, {Name: mockPipeline.Name}}, nil) + }, + expectedContent: `bad-goose +my-pipeline-repo +`, + }, + "ls --local --json with one deployed, one local": { + shouldShowLocalPipelines: true, + shouldOutputJSON: true, + setupMocks: func(m pipelineListMocks) { + m.workspace.EXPECT().ListPipelines().Return([]workspace.PipelineManifest{ + {Name: mockLegacyPipeline.Name, Path: "/copilot/pipeline.yml"}, + {Name: mockPipeline.Name, Path: "/copilot/pipelines/my-pipeline-repo/manifest.yml"}}, nil) + m.pipelineLister.EXPECT().ListDeployedPipelines(mockAppName).Return([]deploy.Pipeline{mockPipeline}, nil) + m.describer.EXPECT().Describe().Return(&describe.Pipeline{ + Name: mockPipelineName, + Pipeline: codepipeline.Pipeline{ + Name: mockPipelineResourceName, + }, + }, nil) }, - expectedErr: fmt.Errorf("get pipeline info for my-pipeline-repo: mock error"), + expectedContent: `{"pipelines":[{"name":"bad-goose","manifestPath":"/copilot/pipeline.yml"},{"name":"my-pipeline-repo","manifestPath":"/copilot/pipelines/my-pipeline-repo/manifest.yml","pipelineName":"pipeline-coolapp-my-pipeline-repo-ABCDERANDOMRANDOM"}]}` + "\n", }, } @@ -176,30 +233,34 @@ bad-goose ctrl := gomock.NewController(t) defer ctrl.Finish() - mockPrompt := mocks.NewMockprompter(ctrl) - mockPipelineGetter := mocks.NewMockpipelineGetter(ctrl) - mockPipelineLister := mocks.NewMockdeployedPipelineLister(ctrl) - mockSel := mocks.NewMockconfigSelector(ctrl) - mocks := pipelineListMocks{ - prompt: mockPrompt, - pipelineGetter: mockPipelineGetter, - pipelineLister: mockPipelineLister, - sel: mockSel, + prompt: mocks.NewMockprompter(ctrl), + sel: mocks.NewMockconfigSelector(ctrl), + store: mocks.NewMockstore(ctrl), + workspace: mocks.NewMockwsPipelineGetter(ctrl), + pipelineLister: mocks.NewMockdeployedPipelineLister(ctrl), + describer: mocks.NewMockdescriber(ctrl), + } + if tc.setupMocks != nil { + tc.setupMocks(mocks) } - tc.setupMocks(mocks) b := &bytes.Buffer{} opts := &listPipelineOpts{ listPipelineVars: listPipelineVars{ - appName: mockAppName, - shouldOutputJSON: tc.shouldOutputJSON, + appName: mockAppName, + shouldOutputJSON: tc.shouldOutputJSON, + shouldShowLocalPipelines: tc.shouldShowLocalPipelines, }, - codepipeline: mockPipelineGetter, - pipelineLister: mockPipelineLister, - sel: mockSel, - prompt: mockPrompt, + prompt: mocks.prompt, + sel: mocks.sel, + store: mocks.store, w: b, + workspace: mocks.workspace, + pipelineLister: mocks.pipelineLister, + newDescriber: func(pipeline deploy.Pipeline) (describer, error) { + return mocks.describer, nil + }, } // WHEN diff --git a/internal/pkg/cli/svc_init.go b/internal/pkg/cli/svc_init.go index 00e7bb66aeb..1239d179eb4 100644 --- a/internal/pkg/cli/svc_init.go +++ b/internal/pkg/cli/svc_init.go @@ -188,7 +188,7 @@ func newInitSvcOpts(vars initSvcVars) (*initSvcOpts, error) { func (o *initSvcOpts) Validate() error { // If this app is pending creation, we'll skip validation. if !o.wsPendingCreation { - if err := validateInputApp(o.wsAppName, o.appName, o.store); err != nil { + if err := validateWorkspaceApp(o.wsAppName, o.appName, o.store); err != nil { return err } o.appName = o.wsAppName @@ -561,7 +561,7 @@ func (o *initSvcOpts) askSvcPublishers() (err error) { return nil } -func validateInputApp(wsApp, inputApp string, store store) error { +func validateWorkspaceApp(wsApp, inputApp string, store store) error { if wsApp == "" { // NOTE: This command is required to be executed under a workspace. We don't prompt for it. return errNoAppInWorkspace diff --git a/internal/pkg/describe/pipeline_show.go b/internal/pkg/describe/pipeline_show.go index 196c732638e..e00dff3b3b1 100644 --- a/internal/pkg/describe/pipeline_show.go +++ b/internal/pkg/describe/pipeline_show.go @@ -26,6 +26,7 @@ type pipelineGetter interface { // Pipeline contains serialized parameters for a pipeline. type Pipeline struct { + // Name is the user provided name for a pipeline Name string `json:"name"` codepipeline.Pipeline