diff --git a/commands/apps.go b/commands/apps.go index 6489f6bc6..32c7153af 100644 --- a/commands/apps.go +++ b/commands/apps.go @@ -33,6 +33,7 @@ import ( "github.com/digitalocean/doctl/internal/apps" "github.com/digitalocean/doctl/pkg/terminal" "github.com/digitalocean/godo" + "github.com/google/uuid" multierror "github.com/hashicorp/go-multierror" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" @@ -189,7 +190,7 @@ Only basic information is included with the text output format. For complete app logs := CmdBuilder( cmd, RunAppsGetLogs, - "logs ", + "logs ", "Retrieves logs", `Retrieves component logs for a deployment of an app. @@ -659,17 +660,24 @@ func RunAppsGetLogs(c *CmdConfig) error { if err != nil { return err } - if deploymentID == "" { - app, err := c.Apps().Get(appID) + + _, err = uuid.Parse(appID) + if err != nil || deploymentID == "" { + app, err := c.Apps().Find(appID) if err != nil { return err } - if app.ActiveDeployment != nil { - deploymentID = app.ActiveDeployment.ID - } else if app.InProgressDeployment != nil { - deploymentID = app.InProgressDeployment.ID - } else { - return fmt.Errorf("unable to retrieve logs; no deployment found for app %s", appID) + + appID = app.ID + + if deploymentID == "" { + if app.ActiveDeployment != nil { + deploymentID = app.ActiveDeployment.ID + } else if app.InProgressDeployment != nil { + deploymentID = app.InProgressDeployment.ID + } else { + return fmt.Errorf("unable to retrieve logs; no deployment found for app %s", appID) + } } } diff --git a/commands/apps_test.go b/commands/apps_test.go index c4e705a03..7cce02754 100644 --- a/commands/apps_test.go +++ b/commands/apps_test.go @@ -560,6 +560,94 @@ func TestRunAppsGetLogs(t *testing.T) { } } +func TestRunAppsGetLogsWithAppName(t *testing.T) { + appName := "test-app" + component := "service" + + testApp := &godo.App{ + ID: uuid.New().String(), + Spec: &testAppSpec, + ActiveDeployment: &godo.Deployment{ + ID: uuid.New().String(), + Spec: &testAppSpec, + }, + } + + types := map[string]godo.AppLogType{ + "build": godo.AppLogTypeBuild, + "deploy": godo.AppLogTypeDeploy, + "run": godo.AppLogTypeRun, + } + + for typeStr, logType := range types { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + tm.apps.EXPECT().Find(appName).Times(1).Return(testApp, nil) + tm.apps.EXPECT().GetLogs(testApp.ID, testApp.ActiveDeployment.ID, component, logType, true, 1).Times(1).Return(&godo.AppLogs{LiveURL: "https://proxy-apps-prod-ams3-001.ondigitalocean.app/?token=aa-bb-11-cc-33"}, nil) + tm.listen.EXPECT().Listen(gomock.Any()).Times(1).Return(nil) + + tc := config.Doit.(*doctl.TestConfig) + tc.ListenFn = func(url *url.URL, token string, schemaFunc listen.SchemaFunc, out io.Writer, in <-chan []byte) listen.ListenerService { + assert.Equal(t, "aa-bb-11-cc-33", token) + assert.Equal(t, "wss://proxy-apps-prod-ams3-001.ondigitalocean.app/?token=aa-bb-11-cc-33", url.String()) + return tm.listen + } + + config.Args = append(config.Args, appName, component) + config.Doit.Set(config.NS, doctl.ArgAppDeployment, "") + config.Doit.Set(config.NS, doctl.ArgAppLogType, typeStr) + config.Doit.Set(config.NS, doctl.ArgAppLogFollow, true) + config.Doit.Set(config.NS, doctl.ArgAppLogTail, 1) + + err := RunAppsGetLogs(config) + require.NoError(t, err) + }) + } +} + +func TestRunAppsGetLogsWithAppNameAndDeploymentID(t *testing.T) { + appName := "test-app" + component := "service" + + testApp := &godo.App{ + ID: uuid.New().String(), + Spec: &testAppSpec, + ActiveDeployment: &godo.Deployment{ + ID: uuid.New().String(), + Spec: &testAppSpec, + }, + } + + types := map[string]godo.AppLogType{ + "build": godo.AppLogTypeBuild, + "deploy": godo.AppLogTypeDeploy, + "run": godo.AppLogTypeRun, + } + + for typeStr, logType := range types { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + tm.apps.EXPECT().Find(appName).Times(1).Return(testApp, nil) + tm.apps.EXPECT().GetLogs(testApp.ID, testApp.ActiveDeployment.ID, component, logType, true, 1).Times(1).Return(&godo.AppLogs{LiveURL: "https://proxy-apps-prod-ams3-001.ondigitalocean.app/?token=aa-bb-11-cc-33"}, nil) + tm.listen.EXPECT().Listen(gomock.Any()).Times(1).Return(nil) + + tc := config.Doit.(*doctl.TestConfig) + tc.ListenFn = func(url *url.URL, token string, schemaFunc listen.SchemaFunc, out io.Writer, in <-chan []byte) listen.ListenerService { + assert.Equal(t, "aa-bb-11-cc-33", token) + assert.Equal(t, "wss://proxy-apps-prod-ams3-001.ondigitalocean.app/?token=aa-bb-11-cc-33", url.String()) + return tm.listen + } + + config.Args = append(config.Args, appName, component) + config.Doit.Set(config.NS, doctl.ArgAppDeployment, testApp.ActiveDeployment.ID) + config.Doit.Set(config.NS, doctl.ArgAppLogType, typeStr) + config.Doit.Set(config.NS, doctl.ArgAppLogFollow, true) + config.Doit.Set(config.NS, doctl.ArgAppLogTail, 1) + + err := RunAppsGetLogs(config) + require.NoError(t, err) + }) + } +} + func TestRunAppsConsole(t *testing.T) { appID := uuid.New().String() deploymentID := uuid.New().String() diff --git a/do/apps.go b/do/apps.go index d5bc0a4ad..f2b8516da 100644 --- a/do/apps.go +++ b/do/apps.go @@ -15,13 +15,16 @@ package do import ( "context" + "fmt" "github.com/digitalocean/godo" + "github.com/google/uuid" ) // AppsService is the interface that wraps godo AppsService. type AppsService interface { Create(req *godo.AppCreateRequest) (*godo.App, error) + Find(appRef string) (*godo.App, error) Get(appID string) (*godo.App, error) List(withProjects bool) ([]*godo.App, error) Update(appID string, req *godo.AppUpdateRequest) (*godo.App, error) @@ -74,11 +77,32 @@ func (s *appsService) Create(req *godo.AppCreateRequest) (*godo.App, error) { return app, nil } +func (s *appsService) Find(appRef string) (*godo.App, error) { + _, err := uuid.Parse(appRef) + if err == nil { + return s.Get(appRef) + } + + apps, err := s.List(false) + if err != nil { + return nil, err + } + + for _, app := range apps { + if app.Spec.Name == appRef { + return app, nil + } + } + + return nil, fmt.Errorf("Cannot find app %s", appRef) +} + func (s *appsService) Get(appID string) (*godo.App, error) { app, _, err := s.client.Apps.Get(s.ctx, appID) if err != nil { return nil, err } + return app, nil } diff --git a/do/mocks/AppsService.go b/do/mocks/AppsService.go index f4fdd9852..96494c5e3 100644 --- a/do/mocks/AppsService.go +++ b/do/mocks/AppsService.go @@ -84,6 +84,21 @@ func (mr *MockAppsServiceMockRecorder) Delete(appID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockAppsService)(nil).Delete), appID) } +// Find mocks base method. +func (m *MockAppsService) Find(appRef string) (*godo.App, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Find", appRef) + ret0, _ := ret[0].(*godo.App) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Find indicates an expected call of Find. +func (mr *MockAppsServiceMockRecorder) Find(appRef any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Find", reflect.TypeOf((*MockAppsService)(nil).Find), appRef) +} + // Get mocks base method. func (m *MockAppsService) Get(appID string) (*godo.App, error) { m.ctrl.T.Helper()