diff --git a/args.go b/args.go index f11b60bda..50d0b68f0 100644 --- a/args.go +++ b/args.go @@ -406,4 +406,13 @@ const ( // ArgAlertPolicySlackURLs are the Slack URLs to send alerts to. ArgAlertPolicySlackURLs = "slack-urls" + + // ArgCommitHash are the Git commit hash. + ArgCommitHash = "sha" + // ArgProjectSource is either git, github or gitlab. + ArgProjectSource = "source" + // ArgDeployOnPush allow auto deploy on project update. + ArgDeployOnPush = "deploy-on-push" + // ArgProjectBrach is git project branch. + ArgProjectBranch = "branch" ) diff --git a/commands/apps.go b/commands/apps.go index f6f70035c..559532e95 100644 --- a/commands/apps.go +++ b/commands/apps.go @@ -22,6 +22,7 @@ import ( "net/http" "net/url" "os" + "strconv" "strings" "time" @@ -113,6 +114,15 @@ This permanently deletes the app and all its associated deployments.`, ) AddBoolFlag(deleteApp, doctl.ArgForce, doctl.ArgShortForce, false, "Delete the App without a confirmation prompt") + // output is global flag + detect := CmdBuilder(cmd, + RunAppsDetect, "detect", "Detect functions", "Detect functions project and convert it into apps project by adding the AppSpec.", Writer, aliasOpt("dt")) + AddStringFlag(detect, doctl.ArgProjectSource, "", "", `Project source.`) + AddStringFlag(detect, doctl.ArgCommitHash, "", "", `Git commit hash`) // not sure if need to support commit hash? + AddStringFlag(detect, doctl.ArgProjectName, "", "", `App name to be used`) + AddStringFlag(detect, doctl.ArgProjectBrach, "", "", `Project branch to be used`) + AddBoolFlag(detect, doctl.ArgDeployOnPush, "", *boolPtr(true), `Auto deploy on project update.`) + deploymentCreate := CmdBuilder( cmd, RunAppsCreateDeployment, @@ -393,6 +403,69 @@ func RunAppsDelete(c *CmdConfig) error { return nil } +// RunAppsDetect detects an function project and converts it into apps project. +func RunAppsDetect(c *CmdConfig) error { + source, err := c.Doit.GetString(c.NS, doctl.ArgProjectSource) + if err != nil { + return err + } + if len(source) == 0 { + return fmt.Errorf("source cannot be empty") + } + + sha, err := c.Doit.GetString(c.NS, doctl.ArgCommitHash) + if err != nil { + return err + } + + name, err := c.Doit.GetString(c.NS, doctl.ArgProjectName) + if err != nil { + return err + } + if len(name) == 0 { + return fmt.Errorf("name cannot be empty") + } + + branch, err := c.Doit.GetString(c.NS, doctl.ArgProjectBrach) + if err != nil { + return err + } + if len(branch) == 0 { + return fmt.Errorf("branch cannot be empty") + } + + // Need to check, How user value will be overrided + autoDeploy, err := c.Doit.GetBool(c.NS, doctl.ArgDeployOnPush) + if err != nil { + return err + } + if len(c.Args) > 0 { + fmt.Println(c.Args[0]) + x, err := strconv.ParseBool(c.Args[0]) + if err == nil { + autoDeploy = x + } else { + return fmt.Errorf("expected true/false for deploy-on-push, received : %s", c.Args[0]) + } + } + + spec, err := c.Apps().Detect(source, sha, name, branch, autoDeploy) + if err != nil { + return err + } + + switch Output { + case "json": + e := json.NewEncoder(c.Out) + e.SetIndent("", " ") + return e.Encode(spec) + case "text": + return nil + default: + return fmt.Errorf("unknown output type") + } +} + // RunAppsCreateDeployment creates a deployment for an app. func RunAppsCreateDeployment(c *CmdConfig) error { if len(c.Args) < 1 { diff --git a/commands/apps_test.go b/commands/apps_test.go index b66ca32e1..b772f7493 100644 --- a/commands/apps_test.go +++ b/commands/apps_test.go @@ -23,6 +23,7 @@ func TestAppsCommand(t *testing.T) { require.NotNil(t, cmd) assertCommandNames(t, cmd, "create", + "detect", "get", "list", "update", diff --git a/do/apps.go b/do/apps.go index 0e7cfa159..5bc36669f 100644 --- a/do/apps.go +++ b/do/apps.go @@ -15,6 +15,7 @@ package do import ( "context" + "strings" "github.com/digitalocean/godo" ) @@ -26,6 +27,7 @@ type AppsService interface { List() ([]*godo.App, error) Update(appID string, req *godo.AppUpdateRequest) (*godo.App, error) Delete(appID string) error + Detect(source string, sha string, name string, branch string, autoDeploy bool) (*godo.AppSpec, error) Propose(req *godo.AppProposeRequest) (*godo.AppProposeResponse, error) CreateDeployment(appID string, forceRebuild bool) (*godo.Deployment, error) @@ -119,6 +121,86 @@ func (s *appsService) Delete(appID string) error { return err } +func (s *appsService) Detect(source string, sha string, name string, branch string, autoDeploy bool) (*godo.AppSpec, error) { + var dr godo.DetectRequest + if strings.Contains(source, "github") { + dr.GitHub = &godo.GitHubSourceSpec{ + Repo: verifyGitSource(source, "github"), + Branch: branch, + DeployOnPush: autoDeploy, + } + } else if strings.Contains(source, "gitlab") { + dr.GitLab = &godo.GitLabSourceSpec{ + Repo: verifyGitSource(source, "gitlab"), + Branch: branch, + DeployOnPush: autoDeploy, + } + } else { + dr.Git = &godo.GitSourceSpec{ + RepoCloneURL: source, + Branch: branch, + } + } + dr.SourceDir = "/" + dr.CommitSHA = sha + + resp, _, err := s.client.Apps.Detect(context.Background(), &dr) + if err != nil { + return nil, err + } + + var appSpec godo.AppSpec + + appSpec.Name = name + var funcSpecArray []*godo.AppFunctionsSpec + for _, component := range resp.Components { + + if component.Strategy == "SERVERLESS" { + for _, serverlessPackage := range component.ServerlessPackages { + var functionSpec godo.AppFunctionsSpec + functionSpec.Name = serverlessPackage.Name + if strings.Contains(source, "github") { + functionSpec.GitHub = &godo.GitHubSourceSpec{ + Repo: verifyGitSource(source, "github"), + Branch: branch, + DeployOnPush: autoDeploy, + } + } else if strings.Contains(source, "gitlab") { + functionSpec.GitLab = &godo.GitLabSourceSpec{ + Repo: verifyGitSource(source, "gitlab"), + Branch: branch, + DeployOnPush: autoDeploy, + } + } else { + functionSpec.Git = &godo.GitSourceSpec{ + RepoCloneURL: source, + Branch: branch, + } + } + functionSpec.SourceDir = "/" + functionSpec.Routes = []*godo.AppRouteSpec{ + { + Path: "/", + PreservePathPrefix: false, + }, + } + funcSpecArray = append(funcSpecArray, &functionSpec) + + } + } + appSpec.Functions = funcSpecArray + } + return &appSpec, nil +} + +func verifyGitSource(s string, splitter string) string { + x := strings.Split(s, splitter+".com/") + if strings.Contains(x[1], ".git") { + x = strings.Split(x[1], ".") + } + return x[0] +} + func (s *appsService) Propose(req *godo.AppProposeRequest) (*godo.AppProposeResponse, error) { res, _, err := s.client.Apps.Propose(s.ctx, req) if err != nil { diff --git a/do/mocks/AppsService.go b/do/mocks/AppsService.go index b94f8adf5..ae6f14999 100644 --- a/do/mocks/AppsService.go +++ b/do/mocks/AppsService.go @@ -78,6 +78,21 @@ func (mr *MockAppsServiceMockRecorder) Delete(appID interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockAppsService)(nil).Delete), appID) } +// Detect mocks base method. +func (m *MockAppsService) Detect(source, sha, name, branch string, autoDeploy bool) (*godo.AppSpec, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Detect", source, sha, name, branch, autoDeploy) + ret0, _ := ret[0].(*godo.AppSpec) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Detect indicates an expected call of Detect. +func (mr *MockAppsServiceMockRecorder) Detect(source, sha, name, branch, autoDeploy interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Detect", reflect.TypeOf((*MockAppsService)(nil).Detect), source, sha, name, branch, autoDeploy) +} + // Get mocks base method. func (m *MockAppsService) Get(appID string) (*godo.App, error) { m.ctrl.T.Helper() diff --git a/do/mocks/DatabasesService.go b/do/mocks/DatabasesService.go index a16e1bc16..64ecd6032 100644 --- a/do/mocks/DatabasesService.go +++ b/do/mocks/DatabasesService.go @@ -18,20 +18,6 @@ type MockDatabasesService struct { recorder *MockDatabasesServiceMockRecorder } -func (m *MockDatabasesService) ListOptions() (*do.DatabaseOptions, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListOptions") - ret0, _ := ret[0].(*do.DatabaseOptions) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListOptions indicates an expected call of ListOptions. -func (mr *MockDatabasesServiceMockRecorder) ListOptions() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListOptions", reflect.TypeOf((*MockDatabasesService)(nil).ListOptions)) -} - // MockDatabasesServiceMockRecorder is the mock recorder for MockDatabasesService. type MockDatabasesServiceMockRecorder struct { mock *MockDatabasesService @@ -389,6 +375,21 @@ func (mr *MockDatabasesServiceMockRecorder) ListDBs(arg0 interface{}) *gomock.Ca return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListDBs", reflect.TypeOf((*MockDatabasesService)(nil).ListDBs), arg0) } +// ListOptions mocks base method. +func (m *MockDatabasesService) ListOptions() (*do.DatabaseOptions, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListOptions") + ret0, _ := ret[0].(*do.DatabaseOptions) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListOptions indicates an expected call of ListOptions. +func (mr *MockDatabasesServiceMockRecorder) ListOptions() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListOptions", reflect.TypeOf((*MockDatabasesService)(nil).ListOptions)) +} + // ListPools mocks base method. func (m *MockDatabasesService) ListPools(arg0 string) (do.DatabasePools, error) { m.ctrl.T.Helper()