Repository: digitalocean/doctl
Pull request #1302 (2022-11-03 15:08:27)

Add more support for project_id in doctl
Specifically, I'm adding support for project ids in two places:

* `doctl apps create` can now take in a `--project-id` parameter. This parameter will be used to assign the created app and resources to the project you specified!
* `doctl apps list` can now take a `--with-projects` flag. Adding this flag will make the `project_id` field populate on each app.

This also bumps the godo version to 1.89


Patch:
diff --git a/args.go b/args.go
index ae8f31dd..601c67d1 100644
--- a/args.go
+++ b/args.go
@@ -36,6 +36,8 @@ const (
 	ArgActionType = "action-type"
 	// ArgApp is the app ID.
 	ArgApp = "app"
+	// ArgAppWithProjects will determine whether project ids should be fetched along with listed apps.
+	ArgAppWithProjects = "with-projects"
 	// ArgAppSpec is a path to an app spec.
 	ArgAppSpec = "spec"
 	// ArgAppLogType the type of log.
diff --git a/commands/apps.go b/commands/apps.go
index 5288247f..bfa373fc 100644
--- a/commands/apps.go
+++ b/commands/apps.go
@@ -62,6 +62,7 @@ func Apps() *Command {
 	AddBoolFlag(create, doctl.ArgCommandWait, "", false,
 		"Boolean that specifies whether to wait for an app to complete before returning control to the terminal")
 	AddBoolFlag(create, doctl.ArgCommandUpsert, "", false, "Boolean that specifies whether the app should be updated if it already exists")
+	AddStringFlag(create, doctl.ArgProjectID, "", "", "The ID of the project to assign the created app and resources to. If not provided, the default project will be used.")
 
 	CmdBuilder(
 		cmd,
@@ -76,7 +77,7 @@ Only basic information is included with the text output format. For complete app
 		displayerType(&displayers.Apps{}),
 	)
 
-	CmdBuilder(
+	list := CmdBuilder(
 		cmd,
 		RunAppsList,
 		"list",
@@ -88,6 +89,7 @@ Only basic information is included with the text output format. For complete app
 		aliasOpt("ls"),
 		displayerType(&displayers.Apps{}),
 	)
+	AddBoolFlag(list, doctl.ArgAppWithProjects, "", false, "Boolean that specifies whether project ids should be fetched along with listed apps")
 
 	update := CmdBuilder(
 		cmd,
@@ -248,12 +250,17 @@ func RunAppsCreate(c *CmdConfig) error {
 		return err
 	}
 
-	app, err := c.Apps().Create(&godo.AppCreateRequest{Spec: appSpec})
+	projectID, err := c.Doit.GetString(c.NS, doctl.ArgProjectID)
+	if err != nil {
+		return err
+	}
+
+	app, err := c.Apps().Create(&godo.AppCreateRequest{Spec: appSpec, ProjectID: projectID})
 	if err != nil {
 		if gerr, ok := err.(*godo.ErrorResponse); ok && gerr.Response.StatusCode == 409 && upsert {
 			notice("App already exists, updating")
 
-			apps, err := c.Apps().List()
+			apps, err := c.Apps().List(false)
 			if err != nil {
 				return err
 			}
@@ -315,7 +322,12 @@ func RunAppsGet(c *CmdConfig) error {
 
 // RunAppsList lists all apps.
 func RunAppsList(c *CmdConfig) error {
-	apps, err := c.Apps().List()
+	withProjects, err := c.Doit.GetBool(c.NS, doctl.ArgAppWithProjects)
+	if err != nil {
+		return err
+	}
+
+	apps, err := c.Apps().List(withProjects)
 	if err != nil {
 		return err
 	}
diff --git a/commands/apps_dev.go b/commands/apps_dev.go
index d3b51f57..e772f910 100644
--- a/commands/apps_dev.go
+++ b/commands/apps_dev.go
@@ -678,7 +678,7 @@ func appsDevBuildSpecRequired(ws *workspace.AppDev, appsService do.AppsService)
 func appsDevSelectApp(appsService do.AppsService) (*godo.App, error) {
 	// TODO: consider updating the list component to accept an itemsFunc and displays its own loading screen
 	template.Print(`listing apps on your account...`, nil)
-	apps, err := appsService.List()
+	apps, err := appsService.List(false)
 	if err != nil {
 		return nil, fmt.Errorf("listing apps: %w", err)
 	}
diff --git a/do/apps.go b/do/apps.go
index 0e7cfa15..63c01c38 100644
--- a/do/apps.go
+++ b/do/apps.go
@@ -23,7 +23,7 @@ import (
 type AppsService interface {
 	Create(req *godo.AppCreateRequest) (*godo.App, error)
 	Get(appID string) (*godo.App, error)
-	List() ([]*godo.App, error)
+	List(withProjects bool) ([]*godo.App, error)
 	Update(appID string, req *godo.AppUpdateRequest) (*godo.App, error)
 	Delete(appID string) error
 	Propose(req *godo.AppProposeRequest) (*godo.AppProposeResponse, error)
@@ -77,8 +77,9 @@ func (s *appsService) Get(appID string) (*godo.App, error) {
 	return app, nil
 }
 
-func (s *appsService) List() ([]*godo.App, error) {
+func (s *appsService) List(withProjects bool) ([]*godo.App, error) {
 	f := func(opt *godo.ListOptions) ([]interface{}, *godo.Response, error) {
+		opt.WithProjects = withProjects
 		list, resp, err := s.client.Apps.List(s.ctx, opt)
 		if err != nil {
 			return nil, nil, err
diff --git a/do/mocks/AppsService.go b/do/mocks/AppsService.go
index b94f8adf..89ca6ebd 100644
--- a/do/mocks/AppsService.go
+++ b/do/mocks/AppsService.go
@@ -154,18 +154,18 @@ func (mr *MockAppsServiceMockRecorder) GetTier(slug interface{}) *gomock.Call {
 }
 
 // List mocks base method.
-func (m *MockAppsService) List() ([]*godo.App, error) {
+func (m *MockAppsService) List(withProjects bool) ([]*godo.App, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "List")
+	ret := m.ctrl.Call(m, "List", withProjects)
 	ret0, _ := ret[0].([]*godo.App)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // List indicates an expected call of List.
-func (mr *MockAppsServiceMockRecorder) List() *gomock.Call {
+func (mr *MockAppsServiceMockRecorder) List(withProjects interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockAppsService)(nil).List))
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockAppsService)(nil).List), withProjects)
 }
 
 // ListAlerts mocks base method.
diff --git a/go.mod b/go.mod
index c4dea4a3..253bef47 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ require (
 	github.com/blang/semver v3.5.1+incompatible
 	github.com/containerd/continuity v0.3.0 // indirect
 	github.com/creack/pty v1.1.11
-	github.com/digitalocean/godo v1.87.0
+	github.com/digitalocean/godo v1.89.0
 	github.com/docker/cli v20.10.17+incompatible
 	github.com/docker/docker v20.10.17+incompatible
 	github.com/docker/docker-credential-helpers v0.6.4 // indirect
diff --git a/go.sum b/go.sum
index 51192a4d..3f0974ea 100644
--- a/go.sum
+++ b/go.sum
@@ -280,8 +280,8 @@ github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8l
 github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
-github.com/digitalocean/godo v1.87.0 h1:U6jyE7Ga+6NkAa8pnpgrKk0lEU1e3Fc/kWipC9tARds=
-github.com/digitalocean/godo v1.87.0/go.mod h1:NRpFznZFvhHjBoqZAaOD3khVzsJ3EibzKqFL4R60dmA=
+github.com/digitalocean/godo v1.89.0 h1:UL3Ii4qfk86m4qEKg2iSwop0puvgOCKvwzXvwArU05E=
+github.com/digitalocean/godo v1.89.0/go.mod h1:NRpFznZFvhHjBoqZAaOD3khVzsJ3EibzKqFL4R60dmA=
 github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
 github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M=
diff --git a/vendor/github.com/digitalocean/godo/CHANGELOG.md b/vendor/github.com/digitalocean/godo/CHANGELOG.md
index a94da0e1..7a0feb0e 100644
--- a/vendor/github.com/digitalocean/godo/CHANGELOG.md
+++ b/vendor/github.com/digitalocean/godo/CHANGELOG.md
@@ -1,5 +1,16 @@
 # Change Log
 
+## [v1.89.0] - 2022-11-02
+
+- #575 - @ghostlandr - apps: add option to get projects data from Apps List endpoint
+
+## [v1.88.0] - 2022-10-31
+
+- #573 - @kamaln7 - apps: add ListBuildpacks, UpgradeBuildpack
+- #572 - @ghostlandr - Apps: add project id as a parameter to CreateApp and to the App struct
+- #570 - @kraai - Fix copy-and-paste error in comment
+- #568 - @StephenVarela - LBAAS-2321 Add project_id to load balancers structs
+
 ## [v1.87.0] - 2022-10-12
 
 - #564 - @DWizGuy58 - Add public monitoring alert policies for dbaas
diff --git a/vendor/github.com/digitalocean/godo/apps.gen.go b/vendor/github.com/digitalocean/godo/apps.gen.go
index dbc2c4a9..7761d9af 100644
--- a/vendor/github.com/digitalocean/godo/apps.gen.go
+++ b/vendor/github.com/digitalocean/godo/apps.gen.go
@@ -94,6 +94,8 @@ type App struct {
 	Domains                 []*AppDomain    `json:"domains,omitempty"`
 	PinnedDeployment        *Deployment     `json:"pinned_deployment,omitempty"`
 	BuildConfig             *AppBuildConfig `json:"build_config,omitempty"`
+	// The id of the project for the app. This will be empty if there is a lookup failure.
+	ProjectID string `json:"project_id,omitempty"`
 }
 
 // AppAlertSpec Configuration of an alert for the app or a individual component.
@@ -563,6 +565,8 @@ type AppCORSPolicy struct {
 // AppCreateRequest struct for AppCreateRequest
 type AppCreateRequest struct {
 	Spec *AppSpec `json:"spec"`
+	// Optional. The UUID of the project the app should be assigned.
+	ProjectID string `json:"project_id,omitempty"`
 }
 
 // DeployTemplate struct for DeployTemplate
@@ -1007,18 +1011,6 @@ type AppTier struct {
 	BuildSeconds         string `json:"build_seconds,omitempty"`
 }
 
-// UpgradeBuildpackRequest struct for UpgradeBuildpackRequest
-type UpgradeBuildpackRequest struct {
-	// The ID of the app to upgrade buildpack versions for.
-	AppID string `json:"app_id,omitempty"`
-	// The ID of the buildpack to upgrade.
-	BuildpackID string `json:"buildpack_id,omitempty"`
-	// The Major Version to upgrade the buildpack to. If omitted, the latest available major version will be used.
-	MajorVersion int32 `json:"major_version,omitempty"`
-	// Whether or not to trigger a deployment for the app after upgrading the buildpack.
-	TriggerDeployment bool `json:"trigger_deployment,omitempty"`
-}
-
 // UpgradeBuildpackResponse struct for UpgradeBuildpackResponse
 type UpgradeBuildpackResponse struct {
 	// The components that were affected by the upgrade.
diff --git a/vendor/github.com/digitalocean/godo/apps.go b/vendor/github.com/digitalocean/godo/apps.go
index a9195d4e..f19ea1e5 100644
--- a/vendor/github.com/digitalocean/godo/apps.go
+++ b/vendor/github.com/digitalocean/godo/apps.go
@@ -51,6 +51,9 @@ type AppsService interface {
 	UpdateAlertDestinations(ctx context.Context, appID, alertID string, update *AlertDestinationUpdateRequest) (*AppAlert, *Response, error)
 
 	Detect(ctx context.Context, detect *DetectRequest) (*DetectResponse, *Response, error)
+
+	ListBuildpacks(ctx context.Context) ([]*Buildpack, *Response, error)
+	UpgradeBuildpack(ctx context.Context, appID string, opts UpgradeBuildpackOptions) (*UpgradeBuildpackResponse, *Response, error)
 }
 
 // AppLogs represent app logs.
@@ -75,6 +78,16 @@ type AlertDestinationUpdateRequest struct {
 	SlackWebhooks []*AppAlertSlackWebhook `json:"slack_webhooks"`
 }
 
+// UpgradeBuildpackOptions struct for UpgradeBuildpackOptions
+type UpgradeBuildpackOptions struct {
+	// The ID of the buildpack to upgrade.
+	BuildpackID string `json:"buildpack_id,omitempty"`
+	// The Major Version to upgrade the buildpack to. If omitted, the latest available major version will be used.
+	MajorVersion int32 `json:"major_version,omitempty"`
+	// Whether or not to trigger a deployment for the app after upgrading the buildpack.
+	TriggerDeployment bool `json:"trigger_deployment,omitempty"`
+}
+
 type appRoot struct {
 	App *App `json:"app"`
 }
@@ -123,6 +136,10 @@ type appAlertRoot struct {
 	Alert *AppAlert `json:"alert"`
 }
 
+type buildpacksRoot struct {
+	Buildpacks []*Buildpack `json:"buildpacks,omitempty"`
+}
+
 // AppsServiceOp handles communication with Apps methods of the DigitalOcean API.
 type AppsServiceOp struct {
 	client *Client
@@ -444,6 +461,36 @@ func (s *AppsServiceOp) Detect(ctx context.Context, detect *DetectRequest) (*Det
 	return res, resp, nil
 }
 
+// ListBuildpacks lists the available buildpacks on App Platform.
+func (s *AppsServiceOp) ListBuildpacks(ctx context.Context) ([]*Buildpack, *Response, error) {
+	path := fmt.Sprintf("%s/buildpacks", appsBasePath)
+	req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+	root := new(buildpacksRoot)
+	resp, err := s.client.Do(ctx, req, root)
+	if err != nil {
+		return nil, resp, err
+	}
+	return root.Buildpacks, resp, nil
+}
+
+// UpgradeBuildpack upgrades a buildpack for an app.
+func (s *AppsServiceOp) UpgradeBuildpack(ctx context.Context, appID string, opts UpgradeBuildpackOptions) (*UpgradeBuildpackResponse, *Response, error) {
+	path := fmt.Sprintf("%s/%s/upgrade_buildpack", appsBasePath, appID)
+	req, err := s.client.NewRequest(ctx, http.MethodPost, path, opts)
+	if err != nil {
+		return nil, nil, err
+	}
+	root := new(UpgradeBuildpackResponse)
+	resp, err := s.client.Do(ctx, req, root)
+	if err != nil {
+		return nil, resp, err
+	}
+	return root, resp, nil
+}
+
 // AppComponentType is an app component type.
 type AppComponentType string
 
diff --git a/vendor/github.com/digitalocean/godo/apps_accessors.go b/vendor/github.com/digitalocean/godo/apps_accessors.go
index b968459c..6af35b2c 100644
--- a/vendor/github.com/digitalocean/godo/apps_accessors.go
+++ b/vendor/github.com/digitalocean/godo/apps_accessors.go
@@ -118,6 +118,14 @@ func (a *App) GetPinnedDeployment() *Deployment {
 	return a.PinnedDeployment
 }
 
+// GetProjectID returns the ProjectID field.
+func (a *App) GetProjectID() string {
+	if a == nil {
+		return ""
+	}
+	return a.ProjectID
+}
+
 // GetRegion returns the Region field.
 func (a *App) GetRegion() *AppRegion {
 	if a == nil {
@@ -398,6 +406,14 @@ func (a *AppCORSPolicy) GetMaxAge() string {
 	return a.MaxAge
 }
 
+// GetProjectID returns the ProjectID field.
+func (a *AppCreateRequest) GetProjectID() string {
+	if a == nil {
+		return ""
+	}
+	return a.ProjectID
+}
+
 // GetSpec returns the Spec field.
 func (a *AppCreateRequest) GetSpec() *AppSpec {
 	if a == nil {
@@ -2870,38 +2886,6 @@ func (i *ImageSourceSpecDeployOnPush) GetEnabled() bool {
 	return i.Enabled
 }
 
-// GetAppID returns the AppID field.
-func (u *UpgradeBuildpackRequest) GetAppID() string {
-	if u == nil {
-		return ""
-	}
-	return u.AppID
-}
-
-// GetBuildpackID returns the BuildpackID field.
-func (u *UpgradeBuildpackRequest) GetBuildpackID() string {
-	if u == nil {
-		return ""
-	}
-	return u.BuildpackID
-}
-
-// GetMajorVersion returns the MajorVersion field.
-func (u *UpgradeBuildpackRequest) GetMajorVersion() int32 {
-	if u == nil {
-		return 0
-	}
-	return u.MajorVersion
-}
-
-// GetTriggerDeployment returns the TriggerDeployment field.
-func (u *UpgradeBuildpackRequest) GetTriggerDeployment() bool {
-	if u == nil {
-		return false
-	}
-	return u.TriggerDeployment
-}
-
 // GetAffectedComponents returns the AffectedComponents field.
 func (u *UpgradeBuildpackResponse) GetAffectedComponents() []string {
 	if u == nil {
diff --git a/vendor/github.com/digitalocean/godo/droplet_actions.go b/vendor/github.com/digitalocean/godo/droplet_actions.go
index c1045cdf..2e09d0c5 100644
--- a/vendor/github.com/digitalocean/godo/droplet_actions.go
+++ b/vendor/github.com/digitalocean/godo/droplet_actions.go
@@ -293,7 +293,7 @@ func (s *DropletActionsServiceOp) Get(ctx context.Context, dropletID, actionID i
 	return s.get(ctx, path)
 }
 
-// GetByURI gets an action for a particular Droplet by id.
+// GetByURI gets an action for a particular Droplet by URI.
 func (s *DropletActionsServiceOp) GetByURI(ctx context.Context, rawurl string) (*Action, *Response, error) {
 	u, err := url.Parse(rawurl)
 	if err != nil {
diff --git a/vendor/github.com/digitalocean/godo/godo.go b/vendor/github.com/digitalocean/godo/godo.go
index b92c72f6..f1b151d5 100644
--- a/vendor/github.com/digitalocean/godo/godo.go
+++ b/vendor/github.com/digitalocean/godo/godo.go
@@ -21,7 +21,7 @@ import (
 )
 
 const (
-	libraryVersion = "1.87.0"
+	libraryVersion = "1.89.0"
 	defaultBaseURL = "https://api.digitalocean.com/"
 	userAgent      = "godo/" + libraryVersion
 	mediaType      = "application/json"
@@ -103,6 +103,9 @@ type ListOptions struct {
 
 	// For paginated result sets, the number of results to include per page.
 	PerPage int `url:"per_page,omitempty"`
+
+	// Whether App responses should include project_id fields. The field will be empty if false or if omitted. (ListApps)
+	WithProjects bool `url:"with_projects,omitempty"`
 }
 
 // TokenListOptions specifies the optional parameters to various List methods that support token pagination.
diff --git a/vendor/github.com/digitalocean/godo/load_balancers.go b/vendor/github.com/digitalocean/godo/load_balancers.go
index da19a519..6739495c 100644
--- a/vendor/github.com/digitalocean/godo/load_balancers.go
+++ b/vendor/github.com/digitalocean/godo/load_balancers.go
@@ -51,6 +51,7 @@ type LoadBalancer struct {
 	VPCUUID                      string           `json:"vpc_uuid,omitempty"`
 	DisableLetsEncryptDNSRecords *bool            `json:"disable_lets_encrypt_dns_records,omitempty"`
 	ValidateOnly                 bool             `json:"validate_only,omitempty"`
+	ProjectID                    string           `json:"project_id,omitempty"`
 }
 
 // String creates a human-readable description of a LoadBalancer.
@@ -81,6 +82,7 @@ func (l LoadBalancer) AsRequest() *LoadBalancerRequest {
 		VPCUUID:                      l.VPCUUID,
 		DisableLetsEncryptDNSRecords: l.DisableLetsEncryptDNSRecords,
 		ValidateOnly:                 l.ValidateOnly,
+		ProjectID:                    l.ProjectID,
 	}
 
 	if l.DisableLetsEncryptDNSRecords != nil {
@@ -165,6 +167,7 @@ type LoadBalancerRequest struct {
 	VPCUUID                      string           `json:"vpc_uuid,omitempty"`
 	DisableLetsEncryptDNSRecords *bool            `json:"disable_lets_encrypt_dns_records,omitempty"`
 	ValidateOnly                 bool             `json:"validate_only,omitempty"`
+	ProjectID                    string           `json:"project_id,omitempty"`
 }
 
 // String creates a human-readable description of a LoadBalancerRequest.
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 96940e8f..4b93e914 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -85,7 +85,7 @@ github.com/creack/pty
 # github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
 ## explicit
 github.com/davecgh/go-spew/spew
-# github.com/digitalocean/godo v1.87.0
+# github.com/digitalocean/godo v1.89.0
 ## explicit; go 1.18
 github.com/digitalocean/godo
 github.com/digitalocean/godo/metrics


Test patch:
diff --git a/commands/apps_test.go b/commands/apps_test.go
index fb6913b8..825325bf 100644
--- a/commands/apps_test.go
+++ b/commands/apps_test.go
@@ -174,7 +174,7 @@ func TestRunAppsList(t *testing.T) {
 			UpdatedAt: time.Now(),
 		}}
 
-		tm.apps.EXPECT().List().Times(1).Return(apps, nil)
+		tm.apps.EXPECT().List(false).Times(1).Return(apps, nil)
 
 		err := RunAppsList(config)
 		require.NoError(t, err)