From def7b718883d3b7bec6f24cfe4b5cff6d0d47a5f Mon Sep 17 00:00:00 2001 From: Cesar Garza Date: Wed, 25 Jan 2023 12:34:47 -0600 Subject: [PATCH] databases: Support upgrading the database version (#918) --- .../resource_digitalocean_database_cluster.go | 9 +- ...urce_digitalocean_database_cluster_test.go | 47 +++ docs/resources/database_cluster.md | 3 +- go.mod | 2 +- go.sum | 4 +- .../github.com/digitalocean/godo/CHANGELOG.md | 15 +- .../github.com/digitalocean/godo/databases.go | 104 ++++-- vendor/github.com/digitalocean/godo/godo.go | 6 +- vendor/github.com/digitalocean/godo/tokens.go | 228 ++++++++++++ vendor/github.com/digitalocean/godo/uptime.go | 342 ++++++++++++++++++ vendor/modules.txt | 2 +- 11 files changed, 733 insertions(+), 29 deletions(-) create mode 100644 vendor/github.com/digitalocean/godo/tokens.go create mode 100644 vendor/github.com/digitalocean/godo/uptime.go diff --git a/digitalocean/resource_digitalocean_database_cluster.go b/digitalocean/resource_digitalocean_database_cluster.go index 26f627995..ab1f02c1b 100644 --- a/digitalocean/resource_digitalocean_database_cluster.go +++ b/digitalocean/resource_digitalocean_database_cluster.go @@ -54,7 +54,6 @@ func resourceDigitalOceanDatabaseCluster() *schema.Resource { // CustomizeDiffFunc is used to provide users with a better hint in the error message. // Required: true, Optional: true, - ForceNew: true, // When Redis clusters are forced to upgrade, this prevents attempting // to recreate clusters specifying the previous version in their config. DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { @@ -393,6 +392,14 @@ func resourceDigitalOceanDatabaseClusterUpdate(ctx context.Context, d *schema.Re } } + if d.HasChange("version") { + upgradeVersionReq := &godo.UpgradeVersionRequest{Version: d.Get("version").(string)} + _, err := client.Databases.UpgradeMajorVersion(context.Background(), d.Id(), upgradeVersionReq) + if err != nil { + return diag.Errorf("Error upgrading version for database cluster: %s", err) + } + } + if d.HasChange("tags") { err := setTags(client, d, godo.DatabaseResourceType) if err != nil { diff --git a/digitalocean/resource_digitalocean_database_cluster_test.go b/digitalocean/resource_digitalocean_database_cluster_test.go index d2f342549..6b60f3fa7 100644 --- a/digitalocean/resource_digitalocean_database_cluster_test.go +++ b/digitalocean/resource_digitalocean_database_cluster_test.go @@ -446,6 +446,43 @@ func TestAccDigitalOceanDatabaseCluster_MongoDBPassword(t *testing.T) { }) } +func TestAccDigitalOceanDatabaseCluster_Upgrade(t *testing.T) { + var database godo.Database + databaseName := randomTestName() + previousPGVersion := "13" + latestPGVersion := "14" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckDigitalOceanDatabaseClusterDestroy, + Steps: []resource.TestStep{ + { + // TODO: Hardcoding the versions here is not ideal. + // We will need to determine a better way to fetch the last and latest versions dynamically. + Config: fmt.Sprintf(testAccCheckDigitalOceanDatabaseClusterConfigCustomVersion, databaseName, "pg", previousPGVersion), + Check: resource.ComposeTestCheckFunc( + testAccCheckDigitalOceanDatabaseClusterExists( + "digitalocean_database_cluster.foobar", &database), + resource.TestCheckResourceAttr( + "digitalocean_database_cluster.foobar", "name", databaseName), + resource.TestCheckResourceAttr( + "digitalocean_database_cluster.foobar", "engine", "pg"), + resource.TestCheckResourceAttr( + "digitalocean_database_cluster.foobar", "version", previousPGVersion), + ), + }, + { + Config: fmt.Sprintf(testAccCheckDigitalOceanDatabaseClusterConfigCustomVersion, databaseName, "pg", latestPGVersion), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "digitalocean_database_cluster.foobar", "version", latestPGVersion), + ), + }, + }, + }) +} + func testAccCheckDigitalOceanDatabaseClusterDestroy(s *terraform.State) error { client := testAccProvider.Meta().(*CombinedConfig).godoClient() @@ -712,3 +749,13 @@ resource "digitalocean_database_cluster" "foobar" { region = "nyc3" node_count = 1 }` + +const testAccCheckDigitalOceanDatabaseClusterConfigCustomVersion = ` +resource "digitalocean_database_cluster" "foobar" { + name = "%s" + engine = "%s" + version = "%s" + size = "db-s-1vcpu-1gb" + region = "nyc3" + node_count = 1 +}` diff --git a/docs/resources/database_cluster.md b/docs/resources/database_cluster.md index 618bffffb..1ccc74604 100644 --- a/docs/resources/database_cluster.md +++ b/docs/resources/database_cluster.md @@ -65,7 +65,8 @@ The following arguments are supported: * `size` - (Required) Database Droplet size associated with the cluster (ex. `db-s-1vcpu-1gb`). See here for a [list of valid size slugs](https://docs.digitalocean.com/reference/api/api-reference/#tag/Databases). * `region` - (Required) DigitalOcean region where the cluster will reside. * `node_count` - (Required) Number of nodes that will be included in the cluster. -* `version` - (Required) Engine version used by the cluster (ex. `11` for PostgreSQL 11). +* `version` - (Required) Engine version used by the cluster (ex. `14` for PostgreSQL 14). + When this value is changed, a call to the [Upgrade major Version for a Database](https://docs.digitalocean.com/reference/api/api-reference/#operation/databases_update_major_version) API operation is made with the new version. * `tags` - (Optional) A list of tag names to be applied to the database cluster. * `private_network_uuid` - (Optional) The ID of the VPC where the database cluster will be located. * `eviction_policy` - (Optional) A string specifying the eviction policy for a Redis cluster. Valid values are: `noeviction`, `allkeys_lru`, `allkeys_random`, `volatile_lru`, `volatile_random`, or `volatile_ttl`. diff --git a/go.mod b/go.mod index 35b5caa1e..49897e8f1 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/digitalocean/terraform-provider-digitalocean require ( github.com/aws/aws-sdk-go v1.42.18 - github.com/digitalocean/godo v1.92.0 + github.com/digitalocean/godo v1.95.0 github.com/hashicorp/awspolicyequivalence v1.5.0 github.com/hashicorp/go-uuid v1.0.2 github.com/hashicorp/go-version v1.3.0 diff --git a/go.sum b/go.sum index 22fa7fc78..b419b77c9 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/digitalocean/godo v1.92.0 h1:eK9DgdLcjozZbywjh/9k2NF++ttbcidASOoCGRu48yQ= -github.com/digitalocean/godo v1.92.0/go.mod h1:NRpFznZFvhHjBoqZAaOD3khVzsJ3EibzKqFL4R60dmA= +github.com/digitalocean/godo v1.95.0 h1:S48/byPKui7RHZc1wYEPfRvkcEvToADNb5I3guu95xg= +github.com/digitalocean/godo v1.95.0/go.mod h1:NRpFznZFvhHjBoqZAaOD3khVzsJ3EibzKqFL4R60dmA= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= diff --git a/vendor/github.com/digitalocean/godo/CHANGELOG.md b/vendor/github.com/digitalocean/godo/CHANGELOG.md index 04da396cd..812828be7 100644 --- a/vendor/github.com/digitalocean/godo/CHANGELOG.md +++ b/vendor/github.com/digitalocean/godo/CHANGELOG.md @@ -1,6 +1,19 @@ # Change Log -## [v1.92.0] - 2022-12-08 +## [v1.95.0] - 2023-01-23 + +- #595 - @dweinshenker - Add UpgradeMajorVersion to godo + +## [v1.94.0] - 2022-01-23 + +- #596 - @DMW2151 - DBAAS-3906: Include updatePool for DB Clusters +- #593 - @danaelhe - Add Uptime Checks and Alerts Support + +## [v1.93.0] - 2022-12-15 + +- #591 - @andrewsomething - tokens: Add initial support for new API. + +## [v1.92.0] - 2022-12-14 - #589 - @wez470 - load-balancers: Minor doc fixup - #585 - @StephenVarela - Add firewall support for load balancers diff --git a/vendor/github.com/digitalocean/godo/databases.go b/vendor/github.com/digitalocean/godo/databases.go index 69bd6c9f5..c70e96561 100644 --- a/vendor/github.com/digitalocean/godo/databases.go +++ b/vendor/github.com/digitalocean/godo/databases.go @@ -9,27 +9,28 @@ import ( ) const ( - databaseBasePath = "/v2/databases" - databaseSinglePath = databaseBasePath + "/%s" - databaseCAPath = databaseBasePath + "/%s/ca" - databaseConfigPath = databaseBasePath + "/%s/config" - databaseResizePath = databaseBasePath + "/%s/resize" - databaseMigratePath = databaseBasePath + "/%s/migrate" - databaseMaintenancePath = databaseBasePath + "/%s/maintenance" - databaseBackupsPath = databaseBasePath + "/%s/backups" - databaseUsersPath = databaseBasePath + "/%s/users" - databaseUserPath = databaseBasePath + "/%s/users/%s" - databaseResetUserAuthPath = databaseUserPath + "/reset_auth" - databaseDBPath = databaseBasePath + "/%s/dbs/%s" - databaseDBsPath = databaseBasePath + "/%s/dbs" - databasePoolPath = databaseBasePath + "/%s/pools/%s" - databasePoolsPath = databaseBasePath + "/%s/pools" - databaseReplicaPath = databaseBasePath + "/%s/replicas/%s" - databaseReplicasPath = databaseBasePath + "/%s/replicas" - databaseEvictionPolicyPath = databaseBasePath + "/%s/eviction_policy" - databaseSQLModePath = databaseBasePath + "/%s/sql_mode" - databaseFirewallRulesPath = databaseBasePath + "/%s/firewall" - databaseOptionsPath = databaseBasePath + "/options" + databaseBasePath = "/v2/databases" + databaseSinglePath = databaseBasePath + "/%s" + databaseCAPath = databaseBasePath + "/%s/ca" + databaseConfigPath = databaseBasePath + "/%s/config" + databaseResizePath = databaseBasePath + "/%s/resize" + databaseMigratePath = databaseBasePath + "/%s/migrate" + databaseMaintenancePath = databaseBasePath + "/%s/maintenance" + databaseBackupsPath = databaseBasePath + "/%s/backups" + databaseUsersPath = databaseBasePath + "/%s/users" + databaseUserPath = databaseBasePath + "/%s/users/%s" + databaseResetUserAuthPath = databaseUserPath + "/reset_auth" + databaseDBPath = databaseBasePath + "/%s/dbs/%s" + databaseDBsPath = databaseBasePath + "/%s/dbs" + databasePoolPath = databaseBasePath + "/%s/pools/%s" + databasePoolsPath = databaseBasePath + "/%s/pools" + databaseReplicaPath = databaseBasePath + "/%s/replicas/%s" + databaseReplicasPath = databaseBasePath + "/%s/replicas" + databaseEvictionPolicyPath = databaseBasePath + "/%s/eviction_policy" + databaseSQLModePath = databaseBasePath + "/%s/sql_mode" + databaseFirewallRulesPath = databaseBasePath + "/%s/firewall" + databaseOptionsPath = databaseBasePath + "/options" + databaseUpgradeMajorVersionPath = databaseBasePath + "/%s/upgrade" ) // SQL Mode constants allow for MySQL-specific SQL flavor configuration. @@ -124,6 +125,7 @@ type DatabasesService interface { CreatePool(context.Context, string, *DatabaseCreatePoolRequest) (*DatabasePool, *Response, error) GetPool(context.Context, string, string) (*DatabasePool, *Response, error) DeletePool(context.Context, string, string) (*Response, error) + UpdatePool(context.Context, string, string, *DatabaseUpdatePoolRequest) (*Response, error) GetReplica(context.Context, string, string) (*DatabaseReplica, *Response, error) ListReplicas(context.Context, string, *ListOptions) ([]DatabaseReplica, *Response, error) CreateReplica(context.Context, string, *DatabaseCreateReplicaRequest) (*DatabaseReplica, *Response, error) @@ -141,6 +143,7 @@ type DatabasesService interface { UpdateRedisConfig(context.Context, string, *RedisConfig) (*Response, error) UpdateMySQLConfig(context.Context, string, *MySQLConfig) (*Response, error) ListOptions(todo context.Context) (*DatabaseOptions, *Response, error) + UpgradeMajorVersion(context.Context, string, *UpgradeVersionRequest) (*Response, error) } // DatabasesServiceOp handles communication with the Databases related methods @@ -299,6 +302,14 @@ type DatabaseCreatePoolRequest struct { Mode string `json:"mode"` } +// DatabaseUpdatePoolRequest is used to update a database connection pool +type DatabaseUpdatePoolRequest struct { + User string `json:"user,omitempty"` + Size int `json:"size"` + Database string `json:"db"` + Mode string `json:"mode"` +} + // DatabaseCreateUserRequest is used to create a new database user type DatabaseCreateUserRequest struct { Name string `json:"name"` @@ -521,6 +532,10 @@ type evictionPolicyRoot struct { EvictionPolicy string `json:"eviction_policy"` } +type UpgradeVersionRequest struct { + Version string `json:"version"` +} + type sqlModeRoot struct { SQLMode string `json:"sql_mode"` } @@ -904,6 +919,37 @@ func (svc *DatabasesServiceOp) DeletePool(ctx context.Context, databaseID, name return resp, nil } +// UpdatePool will update an existing database connection pool +func (svc *DatabasesServiceOp) UpdatePool(ctx context.Context, databaseID, name string, updatePool *DatabaseUpdatePoolRequest) (*Response, error) { + path := fmt.Sprintf(databasePoolPath, databaseID, name) + + if updatePool == nil { + return nil, NewArgError("updatePool", "cannot be nil") + } + + if updatePool.Mode == "" { + return nil, NewArgError("mode", "cannot be empty") + } + + if updatePool.Database == "" { + return nil, NewArgError("database", "cannot be empty") + } + + if updatePool.Size < 1 { + return nil, NewArgError("size", "cannot be less than 1") + } + + req, err := svc.client.NewRequest(ctx, http.MethodPut, path, updatePool) + if err != nil { + return nil, err + } + resp, err := svc.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + return resp, nil +} + // GetReplica returns a single database replica func (svc *DatabasesServiceOp) GetReplica(ctx context.Context, databaseID, name string) (*DatabaseReplica, *Response, error) { path := fmt.Sprintf(databaseReplicaPath, databaseID, name) @@ -1179,3 +1225,19 @@ func (svc *DatabasesServiceOp) ListOptions(ctx context.Context) (*DatabaseOption return root.Options, resp, nil } + +// UpgradeMajorVersion upgrades the major version of a cluster. +func (svc *DatabasesServiceOp) UpgradeMajorVersion(ctx context.Context, databaseID string, upgradeReq *UpgradeVersionRequest) (*Response, error) { + path := fmt.Sprintf(databaseUpgradeMajorVersionPath, databaseID) + req, err := svc.client.NewRequest(ctx, http.MethodPut, path, upgradeReq) + if err != nil { + return nil, err + } + + resp, err := svc.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} diff --git a/vendor/github.com/digitalocean/godo/godo.go b/vendor/github.com/digitalocean/godo/godo.go index 354a1d516..b226c220a 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.92.0" + libraryVersion = "1.95.0" defaultBaseURL = "https://api.digitalocean.com/" userAgent = "godo/" + libraryVersion mediaType = "application/json" @@ -81,6 +81,8 @@ type Client struct { Storage StorageService StorageActions StorageActionsService Tags TagsService + Tokens TokensService + UptimeChecks UptimeChecksService VPCs VPCsService // Optional function called after every successful request made to the DO APIs @@ -250,6 +252,8 @@ func NewClient(httpClient *http.Client) *Client { c.Storage = &StorageServiceOp{client: c} c.StorageActions = &StorageActionsServiceOp{client: c} c.Tags = &TagsServiceOp{client: c} + c.Tokens = &TokensServiceOp{client: c} + c.UptimeChecks = &UptimeChecksServiceOp{client: c} c.VPCs = &VPCsServiceOp{client: c} c.headers = make(map[string]string) diff --git a/vendor/github.com/digitalocean/godo/tokens.go b/vendor/github.com/digitalocean/godo/tokens.go new file mode 100644 index 000000000..13aa418df --- /dev/null +++ b/vendor/github.com/digitalocean/godo/tokens.go @@ -0,0 +1,228 @@ +package godo + +import ( + "context" + "fmt" + "net/http" + "time" +) + +const ( + accessTokensBasePath = "v2/tokens" + tokenScopesBasePath = accessTokensBasePath + "/scopes" +) + +// TokensService is an interface for managing DigitalOcean API access tokens. +// It is not currently generally available. Follow the release notes for +// updates: https://docs.digitalocean.com/release-notes/api/ +type TokensService interface { + List(context.Context, *ListOptions) ([]Token, *Response, error) + Get(context.Context, int) (*Token, *Response, error) + Create(context.Context, *TokenCreateRequest) (*Token, *Response, error) + Update(context.Context, int, *TokenUpdateRequest) (*Token, *Response, error) + Revoke(context.Context, int) (*Response, error) + ListScopes(context.Context, *ListOptions) ([]TokenScope, *Response, error) + ListScopesByNamespace(context.Context, string, *ListOptions) ([]TokenScope, *Response, error) +} + +// TokensServiceOp handles communication with the tokens related methods of the +// DigitalOcean API. +type TokensServiceOp struct { + client *Client +} + +var _ TokensService = &TokensServiceOp{} + +// Token represents a DigitalOcean API token. +type Token struct { + ID int `json:"id"` + Name string `json:"name"` + Scopes []string `json:"scopes"` + ExpirySeconds *int `json:"expiry_seconds"` + CreatedAt time.Time `json:"created_at"` + LastUsedAt string `json:"last_used_at"` + + // AccessToken contains the actual Oauth token string. It is only included + // in the create response. + AccessToken string `json:"access_token,omitempty"` +} + +// tokenRoot represents a response from the DigitalOcean API +type tokenRoot struct { + Token *Token `json:"token"` +} + +type tokensRoot struct { + Tokens []Token `json:"tokens"` + Links *Links `json:"links"` + Meta *Meta `json:"meta"` +} + +// TokenCreateRequest represents a request to create a token. +type TokenCreateRequest struct { + Name string `json:"name"` + Scopes []string `json:"scopes"` + ExpirySeconds *int `json:"expiry_seconds,omitempty"` +} + +// TokenUpdateRequest represents a request to update a token. +type TokenUpdateRequest struct { + Name string `json:"name,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +// TokenScope is a representation of a scope for the public API. +type TokenScope struct { + Name string `json:"name"` +} + +type tokenScopesRoot struct { + TokenScopes []TokenScope `json:"scopes"` + Links *Links `json:"links"` + Meta *Meta `json:"meta"` +} + +type tokenScopeNamespaceParam struct { + Namespace string `url:"namespace,omitempty"` +} + +// List all DigitalOcean API access tokens. +func (c TokensServiceOp) List(ctx context.Context, opt *ListOptions) ([]Token, *Response, error) { + path, err := addOptions(accessTokensBasePath, opt) + if err != nil { + return nil, nil, err + } + + req, err := c.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(tokensRoot) + resp, err := c.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if l := root.Links; l != nil { + resp.Links = l + } + if m := root.Meta; m != nil { + resp.Meta = m + } + + return root.Tokens, resp, err +} + +// Get a specific DigitalOcean API access token. +func (c TokensServiceOp) Get(ctx context.Context, tokenID int) (*Token, *Response, error) { + path := fmt.Sprintf("%s/%d", accessTokensBasePath, tokenID) + req, err := c.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(tokenRoot) + resp, err := c.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.Token, resp, err +} + +// Create a new DigitalOcean API access token. +func (c TokensServiceOp) Create(ctx context.Context, createRequest *TokenCreateRequest) (*Token, *Response, error) { + req, err := c.client.NewRequest(ctx, http.MethodPost, accessTokensBasePath, createRequest) + if err != nil { + return nil, nil, err + } + + root := new(tokenRoot) + resp, err := c.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.Token, resp, err +} + +// Update the name or scopes of a specific DigitalOcean API access token. +func (c TokensServiceOp) Update(ctx context.Context, tokenID int, updateRequest *TokenUpdateRequest) (*Token, *Response, error) { + path := fmt.Sprintf("%s/%d", accessTokensBasePath, tokenID) + req, err := c.client.NewRequest(ctx, http.MethodPatch, path, updateRequest) + if err != nil { + return nil, nil, err + } + + root := new(tokenRoot) + resp, err := c.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.Token, resp, err +} + +// Revoke a specific DigitalOcean API access token. +func (c TokensServiceOp) Revoke(ctx context.Context, tokenID int) (*Response, error) { + path := fmt.Sprintf("%s/%d", accessTokensBasePath, tokenID) + req, err := c.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + + resp, err := c.client.Do(ctx, req, nil) + + return resp, err +} + +// ListScopes lists all available scopes that can be granted to a token. +func (c TokensServiceOp) ListScopes(ctx context.Context, opt *ListOptions) ([]TokenScope, *Response, error) { + path, err := addOptions(tokenScopesBasePath, opt) + if err != nil { + return nil, nil, err + } + + return listTokenScopes(ctx, c, path) +} + +// ListScopesByNamespace lists available scopes in a namespace that can be granted +// to a token (e.g. the namespace for the `droplet:read“ scope is `droplet`). +func (c TokensServiceOp) ListScopesByNamespace(ctx context.Context, namespace string, opt *ListOptions) ([]TokenScope, *Response, error) { + path, err := addOptions(tokenScopesBasePath, opt) + if err != nil { + return nil, nil, err + } + + namespaceOpt := tokenScopeNamespaceParam{ + Namespace: namespace, + } + + path, err = addOptions(path, namespaceOpt) + if err != nil { + return nil, nil, err + } + + return listTokenScopes(ctx, c, path) +} + +func listTokenScopes(ctx context.Context, c TokensServiceOp, path string) ([]TokenScope, *Response, error) { + req, err := c.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(tokenScopesRoot) + resp, err := c.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if l := root.Links; l != nil { + resp.Links = l + } + if m := root.Meta; m != nil { + resp.Meta = m + } + + return root.TokenScopes, resp, err +} diff --git a/vendor/github.com/digitalocean/godo/uptime.go b/vendor/github.com/digitalocean/godo/uptime.go new file mode 100644 index 000000000..915d6c713 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/uptime.go @@ -0,0 +1,342 @@ +package godo + +import ( + "context" + "fmt" + "net/http" + "path" +) + +const uptimeChecksBasePath = "/v2/uptime/checks" + +// UptimeChecksService is an interface for creating and managing Uptime checks with the DigitalOcean API. +// See: https://docs.digitalocean.com/reference/api/api-reference/#tag/Uptime +type UptimeChecksService interface { + List(context.Context, *ListOptions) ([]UptimeCheck, *Response, error) + Get(context.Context, string) (*UptimeCheck, *Response, error) + GetState(context.Context, string) (*UptimeCheckState, *Response, error) + Create(context.Context, *CreateUptimeCheckRequest) (*UptimeCheck, *Response, error) + Update(context.Context, string, *UpdateUptimeCheckRequest) (*UptimeCheck, *Response, error) + Delete(context.Context, string) (*Response, error) + GetAlert(context.Context, string, string) (*UptimeAlert, *Response, error) + ListAlerts(context.Context, string, *ListOptions) ([]UptimeAlert, *Response, error) + CreateAlert(context.Context, string, *CreateUptimeAlertRequest) (*UptimeAlert, *Response, error) + UpdateAlert(context.Context, string, string, *UpdateUptimeAlertRequest) (*UptimeAlert, *Response, error) + DeleteAlert(context.Context, string, string) (*Response, error) +} + +// UptimeChecksServiceOp handles communication with Uptime Check methods of the DigitalOcean API. +type UptimeChecksServiceOp struct { + client *Client +} + +// UptimeCheck represents a DigitalOcean UptimeCheck configuration. +type UptimeCheck struct { + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Target string `json:"target"` + Regions []string `json:"regions"` + Enabled bool `json:"enabled"` +} + +// UptimeAlert represents a DigitalOcean Uptime Alert configuration. +type UptimeAlert struct { + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Threshold int `json:"threshold"` + Comparison string `json:"comparison"` + Notifications *Notifications `json:"notifications"` + Period string `json:"period"` +} + +// Notifications represents a DigitalOcean Notifications configuration. +type Notifications struct { + Email []string `json:"email"` + Slack []SlackDetails `json:"slack"` +} + +// UptimeCheckState represents a DigitalOcean Uptime Check's state configuration. +type UptimeCheckState struct { + Regions map[string]UptimeRegion `json:"regions"` + PreviousOutage UptimePreviousOutage `json:"previous_outage"` +} + +type UptimeRegion struct { + Status string `json:"status"` + StatusChangedAt string `json:"status_changed_at"` + ThirtyDayUptimePercentage float32 `json:"thirty_day_uptime_percentage"` +} + +// UptimePreviousOutage represents a DigitalOcean Uptime Check's previous outage configuration. +type UptimePreviousOutage struct { + Region string `json:"region"` + StartedAt string `json:"started_at"` + EndedAt string `json:"ended_at"` + DurationSeconds int `json:"duration_seconds"` +} + +// CreateUptimeCheckRequest represents the request to create a new uptime check. +type CreateUptimeCheckRequest struct { + Name string `json:"name"` + Type string `json:"type"` + Target string `json:"target"` + Regions []string `json:"regions"` + Enabled bool `json:"enabled"` +} + +// UpdateUptimeCheckRequest represents the request to update uptime check information. +type UpdateUptimeCheckRequest struct { + Name string `json:"name"` + Type string `json:"type"` + Target string `json:"target"` + Regions []string `json:"regions"` + Enabled bool `json:"enabled"` +} + +// CreateUptimeUptimeAlertRequest represents the request to create a new Uptime Alert. +type CreateUptimeAlertRequest struct { + Name string `json:"name"` + Type string `json:"type"` + Threshold int `json:"threshold"` + Comparison string `json:"comparison"` + Notifications *Notifications `json:"notifications"` + Period string `json:"period"` +} + +// UpdateUptimeAlertRequest represents the request to create a new alert. +type UpdateUptimeAlertRequest struct { + Name string `json:"name"` + Type string `json:"type"` + Threshold int `json:"threshold"` + Comparison string `json:"comparison"` + Notifications *Notifications `json:"notifications"` + Period string `json:"period"` +} + +type uptimeChecksRoot struct { + UptimeChecks []UptimeCheck `json:"checks"` + Links *Links `json:"links"` + Meta *Meta `json:"meta"` +} + +type uptimeCheckStateRoot struct { + UptimeCheckState UptimeCheckState `json:"state"` +} + +type uptimeAlertsRoot struct { + UptimeAlerts []UptimeAlert `json:"alerts"` + Links *Links `json:"links"` + Meta *Meta `json:"meta"` +} + +type uptimeCheckRoot struct { + UptimeCheck *UptimeCheck `json:"check"` +} + +type uptimeAlertRoot struct { + UptimeAlert *UptimeAlert `json:"alert"` +} + +var _ UptimeChecksService = &UptimeChecksServiceOp{} + +// List Checks. +func (p *UptimeChecksServiceOp) List(ctx context.Context, opts *ListOptions) ([]UptimeCheck, *Response, error) { + path, err := addOptions(uptimeChecksBasePath, opts) + if err != nil { + return nil, nil, err + } + + req, err := p.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(uptimeChecksRoot) + resp, err := p.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if l := root.Links; l != nil { + resp.Links = l + } + if m := root.Meta; m != nil { + resp.Meta = m + } + + return root.UptimeChecks, resp, err +} + +// GetState of uptime check. +func (p *UptimeChecksServiceOp) GetState(ctx context.Context, uptimeCheckID string) (*UptimeCheckState, *Response, error) { + path := path.Join(uptimeChecksBasePath, uptimeCheckID, "/state") + + req, err := p.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(uptimeCheckStateRoot) + resp, err := p.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return &root.UptimeCheckState, resp, err +} + +// Get retrieves a single uptime check by its ID. +func (p *UptimeChecksServiceOp) Get(ctx context.Context, uptimeCheckID string) (*UptimeCheck, *Response, error) { + path := path.Join(uptimeChecksBasePath, uptimeCheckID) + + req, err := p.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(uptimeCheckRoot) + resp, err := p.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.UptimeCheck, resp, err +} + +// Create a new uptime check. +func (p *UptimeChecksServiceOp) Create(ctx context.Context, cr *CreateUptimeCheckRequest) (*UptimeCheck, *Response, error) { + req, err := p.client.NewRequest(ctx, http.MethodPost, uptimeChecksBasePath, cr) + if err != nil { + return nil, nil, err + } + + root := new(uptimeCheckRoot) + resp, err := p.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.UptimeCheck, resp, err +} + +// Update an uptime check. +func (p *UptimeChecksServiceOp) Update(ctx context.Context, uptimeCheckID string, ur *UpdateUptimeCheckRequest) (*UptimeCheck, *Response, error) { + path := path.Join(uptimeChecksBasePath, uptimeCheckID) + req, err := p.client.NewRequest(ctx, http.MethodPut, path, ur) + if err != nil { + return nil, nil, err + } + + root := new(uptimeCheckRoot) + resp, err := p.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.UptimeCheck, resp, err +} + +// Delete an existing uptime check. +func (p *UptimeChecksServiceOp) Delete(ctx context.Context, uptimeCheckID string) (*Response, error) { + path := path.Join(uptimeChecksBasePath, uptimeCheckID) + req, err := p.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + + return p.client.Do(ctx, req, nil) +} + +// alerts + +// ListAlerts lists alerts for a check. +func (p *UptimeChecksServiceOp) ListAlerts(ctx context.Context, uptimeCheckID string, opts *ListOptions) ([]UptimeAlert, *Response, error) { + fullPath := path.Join(uptimeChecksBasePath, uptimeCheckID, "/alerts") + path, err := addOptions(fullPath, opts) + if err != nil { + return nil, nil, err + } + + req, err := p.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(uptimeAlertsRoot) + resp, err := p.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if l := root.Links; l != nil { + resp.Links = l + } + if m := root.Meta; m != nil { + resp.Meta = m + } + + return root.UptimeAlerts, resp, err +} + +// CreateAlert creates a new check alert. +func (p *UptimeChecksServiceOp) CreateAlert(ctx context.Context, uptimeCheckID string, cr *CreateUptimeAlertRequest) (*UptimeAlert, *Response, error) { + fullPath := path.Join(uptimeChecksBasePath, uptimeCheckID, "/alerts") + req, err := p.client.NewRequest(ctx, http.MethodPost, fullPath, cr) + if err != nil { + return nil, nil, err + } + + root := new(uptimeAlertRoot) + resp, err := p.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.UptimeAlert, resp, err +} + +// GetAlert retrieves a single uptime check alert by its ID. +func (p *UptimeChecksServiceOp) GetAlert(ctx context.Context, uptimeCheckID string, alertID string) (*UptimeAlert, *Response, error) { + path := fmt.Sprintf("v2/uptime/checks/%s/alerts/%s", uptimeCheckID, alertID) + + req, err := p.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(uptimeAlertRoot) + resp, err := p.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.UptimeAlert, resp, err +} + +// UpdateAlert updates an check's alert. +func (p *UptimeChecksServiceOp) UpdateAlert(ctx context.Context, uptimeCheckID string, alertID string, ur *UpdateUptimeAlertRequest) (*UptimeAlert, *Response, error) { + path := path.Join(uptimeChecksBasePath, uptimeCheckID, "/alerts/", alertID) + req, err := p.client.NewRequest(ctx, http.MethodPut, path, ur) + if err != nil { + return nil, nil, err + } + + root := new(uptimeAlertRoot) + resp, err := p.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.UptimeAlert, resp, err +} + +// DeleteAlert deletes an existing check's alert. +func (p *UptimeChecksServiceOp) DeleteAlert(ctx context.Context, uptimeCheckID string, alertID string) (*Response, error) { + path := path.Join(uptimeChecksBasePath, uptimeCheckID, "/alerts/", alertID) + req, err := p.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + + return p.client.Do(ctx, req, nil) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index d7598357b..dd926c97d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -64,7 +64,7 @@ github.com/aws/aws-sdk-go/service/sts/stsiface # github.com/davecgh/go-spew v1.1.1 ## explicit github.com/davecgh/go-spew/spew -# github.com/digitalocean/godo v1.92.0 +# github.com/digitalocean/godo v1.95.0 ## explicit; go 1.18 github.com/digitalocean/godo github.com/digitalocean/godo/metrics