diff --git a/.gitignore b/.gitignore index 0da1903..197c610 100644 --- a/.gitignore +++ b/.gitignore @@ -270,3 +270,4 @@ $RECYCLE.BIN/ # End of https://www.gitignore.io/api/vim,linux,macos,emacs,windows,terraform,intellij+all,visualstudiocode bin/ +vendor diff --git a/client.go b/client.go index d06e190..16020f6 100644 --- a/client.go +++ b/client.go @@ -14,6 +14,7 @@ import ( "github.com/RedisLabs/rediscloud-go-api/service/account" "github.com/RedisLabs/rediscloud-go-api/service/cloud_accounts" "github.com/RedisLabs/rediscloud-go-api/service/databases" + "github.com/RedisLabs/rediscloud-go-api/service/regions" "github.com/RedisLabs/rediscloud-go-api/service/subscriptions" ) @@ -22,6 +23,7 @@ type Client struct { CloudAccount *cloud_accounts.API Database *databases.API Subscription *subscriptions.API + Regions *regions.API } func NewClient(configs ...Option) (*Client, error) { @@ -53,12 +55,14 @@ func NewClient(configs ...Option) (*Client, error) { c := cloud_accounts.NewAPI(client, t, config.logger) d := databases.NewAPI(client, t, config.logger) s := subscriptions.NewAPI(client, t, config.logger) + r := regions.NewAPI(client, t, config.logger) return &Client{ Account: a, CloudAccount: c, Database: d, Subscription: s, + Regions: r, }, nil } @@ -207,7 +211,9 @@ func prettyPrint(data []byte) string { // redactPasswords: Redacts password values from a JSON message. func redactPasswords(data string) string { m1 := regexp.MustCompile(`\"password\"\s*:\s*\"(?:[^"\\]|\\.)*\"`) - return m1.ReplaceAllString(data, "\"password\": \"REDACTED\"") + output := m1.ReplaceAllString(data, "\"password\": \"REDACTED\"") + m2 := regexp.MustCompile(`\"global_password\"\s*:\s*\"(?:[^"\\]|\\.)*\"`) + return m2.ReplaceAllString(output, "\"global_password\": \"REDACTED\"") } func escapePath(path string) string { diff --git a/client_test.go b/client_test.go index 49a30d6..901f0b6 100644 --- a/client_test.go +++ b/client_test.go @@ -252,8 +252,8 @@ func TestCredentialTripper_RedactPasswordFromNestedBody(t *testing.T) { ProtoMajor: 1, ProtoMinor: 1, Header: map[string][]string{}, - Body: ioutil.NopCloser(bytes.NewBufferString(`{"security": {"password":"pass"}}`)), - ContentLength: 33, + Body: ioutil.NopCloser(bytes.NewBufferString(`{"security": {"password":"pass", "global_password":"globalpass"}}`)), + ContentLength: 65, Host: "example.org", } expected := &http.Response{ @@ -262,7 +262,7 @@ func TestCredentialTripper_RedactPasswordFromNestedBody(t *testing.T) { ProtoMajor: 1, ProtoMinor: 1, Header: map[string][]string{}, - Body: ioutil.NopCloser(bytes.NewBufferString(`{"security": {"password":"REDACTED"}}`)), + Body: ioutil.NopCloser(bytes.NewBufferString(`{"security": {"password":"REDACTED", "global_password":"REDACTED"}}`)), } mockTripper.On("RoundTrip", request).Return(expected, nil) @@ -285,13 +285,14 @@ func TestCredentialTripper_RedactPasswordFromNestedBody(t *testing.T) { POST /foo/bar HTTP/1.1 Host: example.org User-Agent: test-user-agent -Content-Length: 33 +Content-Length: 65 Accept: application/json Accept-Encoding: gzip { "security": { - "password": "REDACTED" + "password": "REDACTED", + "global_password": "REDACTED" } }`, mockLogger.log[0]) } diff --git a/internal/http_client.go b/internal/http_client.go index 6840e04..6d4407d 100644 --- a/internal/http_client.go +++ b/internal/http_client.go @@ -44,6 +44,10 @@ func (c *HttpClient) Delete(ctx context.Context, name, path string, responseBody return c.connection(ctx, http.MethodDelete, name, path, nil, nil, responseBody) } +func (c *HttpClient) DeleteWithQuery(ctx context.Context, name, path string, requestBody interface{}, responseBody interface{}) error { + return c.connection(ctx, http.MethodDelete, name, path, nil, requestBody, responseBody) +} + func (c *HttpClient) connection(ctx context.Context, method, name, path string, query url.Values, requestBody interface{}, responseBody interface{}) error { parsed := new(url.URL) *parsed = *c.baseUrl diff --git a/service/databases/model.go b/service/databases/model.go index 5770b21..eb4989e 100644 --- a/service/databases/model.go +++ b/service/databases/model.go @@ -67,6 +67,7 @@ func (o CreateModule) String() string { return internal.ToString(o) } +// TODO: do we need a separate ActiveActiveDatabase type as well? type Database struct { ID *int `json:"databaseId,omitempty"` Name *string `json:"name,omitempty"` diff --git a/service/databases/model_active_active.go b/service/databases/model_active_active.go new file mode 100644 index 0000000..e8a0493 --- /dev/null +++ b/service/databases/model_active_active.go @@ -0,0 +1,149 @@ +package databases + +import ( + "time" + + "github.com/RedisLabs/rediscloud-go-api/internal" +) + +type ActiveActiveDatabase struct { + ID *int `json:"databaseId,omitempty"` + Name *string `json:"name,omitempty"` + Protocol *string `json:"protocol,omitempty"` + Status *string `json:"status,omitempty"` + MemoryStorage *string `json:"memoryStorage,omitempty"` + ActiveActiveRedis *bool `json:"activeActiveRedis,omitempty"` + ActivatedOn *time.Time `json:"activatedOn,omitempty"` + LastModified *time.Time `json:"lastModified,omitempty"` + SupportOSSClusterAPI *bool `json:"supportOSSClusterApi,omitempty"` + UseExternalEndpointForOSSClusterAPI *bool `json:"useExternalEndpointForOSSClusterApi,omitempty"` + Replication *bool `json:"replication,omitempty"` + DataEvictionPolicy *string `json:"dataEvictionPolicy,omitempty"` + CrdbDatabases []*CrdbDatabase `json:"crdbDatabases,omitempty"` +} + +func (o ActiveActiveDatabase) String() string { + return internal.ToString(o) +} + +type CrdbDatabase struct { + Provider *string `json:"provider,omitempty"` + Region *string `json:"region,omitempty"` + RedisVersionCompliance *string `json:"redisVersionCompliance,omitempty"` + PublicEndpoint *string `json:"publicEndpoint,omitempty"` + PrivateEndpoint *string `json:"privateEndpoint,omitempty"` + MemoryLimitInGB *float64 `json:"memoryLimitInGb,omitempty"` + MemoryUsedInMB *float64 `json:"memoryUsedInMb,omitempty"` + ReadOperationsPerSecond *int `json:"readOperationsPerSecond,omitempty"` + WriteOperationsPerSecond *int `json:"writeOperationsPerSecond,omitempty"` + DataPersistence *string `json:"dataPersistence,omitempty"` + Alerts []*Alert `json:"alerts,omitempty"` + Security *Security `json:"security,omitempty"` + Backup *Backup `json:"backup,omitempty"` +} + +func (o CrdbDatabase) String() string { + return internal.ToString(o) +} + +type Backup struct { + Enabled *bool `json:"enableRemoteBackup,omitempty"` + Interval *string `json:"interval,omitempty"` + Destination *string `json:"destination,omitempty"` +} + +func (o Backup) String() string { + return internal.ToString(o) +} + +type CreateActiveActiveDatabase struct { + DryRun *bool `json:"dryRun,omitempty"` + Name *string `json:"name,omitempty"` + Protocol *string `json:"protocol,omitempty"` + MemoryLimitInGB *float64 `json:"memoryLimitInGb,omitempty"` + SupportOSSClusterAPI *bool `json:"supportOSSClusterApi,omitempty"` + UseExternalEndpointForOSSClusterAPI *bool `json:"useExternalEndpointForOSSClusterApi,omitempty"` + DataEvictionPolicy *string `json:"dataEvictionPolicy,omitempty"` + GlobalDataPersistence *string `json:"dataPersistence,omitempty"` + GlobalSourceIP []*string `json:"sourceIp,omitempty"` + GlobalPassword *string `json:"password,omitempty"` + GlobalAlerts []*CreateAlert `json:"alerts,omitempty"` + LocalThroughputMeasurement []*LocalThroughput `json:"localThroughputMeasurement,omitempty"` +} + +func (o CreateActiveActiveDatabase) String() string { + return internal.ToString(o) +} + +type LocalThroughput struct { + Region *string `json:"region,omitempty"` + WriteOperationsPerSecond *int `json:"writeOperationsPerSecond,omitempty"` + ReadOperationsPerSecond *int `json:"readOperationsPerSecond,omitempty"` +} + +func (o LocalThroughput) String() string { + return internal.ToString(o) +} + +type UpdateActiveActiveDatabase struct { + DryRun *bool `json:"dryRun,omitempty"` + MemoryLimitInGB *float64 `json:"memoryLimitInGb,omitempty"` + SupportOSSClusterAPI *bool `json:"supportOSSClusterApi,omitempty"` + UseExternalEndpointForOSSClusterAPI *bool `json:"useExternalEndpointForOSSClusterApi,omitempty"` + ClientSSLCertificate *string `json:"clientSslCertificate,omitempty"` + EnableTls *bool `json:"enableTls,omitempty"` + GlobalDataPersistence *string `json:"globalDataPersistence,omitempty"` + GlobalPassword *string `json:"globalPassword,omitempty"` + GlobalSourceIP []*string `json:"globalSourceIp,omitempty"` + GlobalAlerts []*UpdateAlert `json:"globalAlerts,omitempty"` + Regions []*LocalRegionProperties `json:"regions,omitempty"` + DataEvictionPolicy *string `json:"dataEvictionPolicy,omitempty"` +} + +func (o UpdateActiveActiveDatabase) String() string { + return internal.ToString(o) +} + +type LocalRegionProperties struct { + Region *string `json:"region,omitempty"` + RemoteBackup *DatabaseBackupConfig `json:"remoteBackup,omitempty"` + LocalThroughputMeasurement *LocalThroughput `json:"localThroughputMeasurement,omitempty"` + DataPersistence *string `json:"dataPersistence,omitempty"` + Password *string `json:"password,omitempty"` + SourceIP []*string `json:"sourceIp,omitempty"` + Alerts []*UpdateAlert `json:"alerts,omitempty"` +} + +func (o LocalRegionProperties) String() string { + return internal.ToString(o) +} + +type DatabaseBackupConfig struct { + Active *bool `json:"active,omitempty"` + Interval *string `json:"interval,omitempty"` + TimeUTC *string `json:"timeUTC,omitempty"` + StorageType *string `json:"storageType,omitempty"` + StoragePath *string `json:"storagePath,omitempty"` +} + +func (o DatabaseBackupConfig) String() string { + return internal.ToString(o) +} + +type listActiveActiveDatabaseResponse struct { + AccountId *string `json:"accountId,omitempty"` + Subscription []*listActiveActiveDbSubscription `json:"subscription,omitempty"` +} + +func (o listActiveActiveDatabaseResponse) String() string { + return internal.ToString(o) +} + +type listActiveActiveDbSubscription struct { + ID *int `json:"subscriptionId,omitempty"` + Databases []*ActiveActiveDatabase `json:"databases,omitempty"` +} + +func (o listActiveActiveDbSubscription) String() string { + return internal.ToString(o) +} diff --git a/service/databases/service_active_active.go b/service/databases/service_active_active.go new file mode 100644 index 0000000..fd564cb --- /dev/null +++ b/service/databases/service_active_active.go @@ -0,0 +1,153 @@ +package databases + +import ( + "context" + "fmt" + "net/http" + "strconv" + + "github.com/RedisLabs/rediscloud-go-api/internal" + "github.com/RedisLabs/rediscloud-go-api/redis" +) + +// Create will create a new database for the subscription and return the identifier of the database. +func (a *API) ActiveActiveCreate(ctx context.Context, subscription int, db CreateActiveActiveDatabase) (int, error) { + var task taskResponse + err := a.client.Post(ctx, fmt.Sprintf("create database for subscription %d", subscription), fmt.Sprintf("/subscriptions/%d/databases", subscription), db, &task) + if err != nil { + return 0, err + } + + a.logger.Printf("Waiting for new database for subscription %d to finish being created", subscription) + + id, err := a.task.WaitForResourceId(ctx, *task.ID) + if err != nil { + return 0, err + } + + return id, nil +} + +// Update will update certain values of an existing database. +func (a *API) ActiveActiveUpdate(ctx context.Context, subscription int, database int, update UpdateActiveActiveDatabase) error { + var task taskResponse + err := a.client.Put(ctx, fmt.Sprintf("update database %d for subscription %d", database, subscription), fmt.Sprintf("/subscriptions/%d/databases/%d/regions", subscription, database), update, &task) + if err != nil { + return err + } + + a.logger.Printf("Waiting for database %d for subscription %d to finish being updated", database, subscription) + + err = a.task.Wait(ctx, *task.ID) + if err != nil { + return err + } + + return nil +} + +// List will return a ListDatabase that is capable of paging through all of the databases associated with a +// subscription. +func (a *API) ListActiveActive(ctx context.Context, subscription int) *ListActiveActiveDatabase { + return newListActiveActiveDatabase(ctx, a.client, subscription, 100) +} + +// Get will retrieve an existing database. +func (a *API) GetActiveActive(ctx context.Context, subscription int, database int) (*ActiveActiveDatabase, error) { + var db ActiveActiveDatabase + err := a.client.Get(ctx, fmt.Sprintf("get database %d for subscription %d", subscription, database), fmt.Sprintf("/subscriptions/%d/databases/%d", subscription, database), &db) + if err != nil { + return nil, wrap404Error(subscription, database, err) + } + + return &db, nil +} + +type ListActiveActiveDatabase struct { + client HttpClient + subscription int + ctx context.Context + pageSize int + + offset int + page []*ActiveActiveDatabase + err error + fin bool + value *ActiveActiveDatabase +} + +func newListActiveActiveDatabase(ctx context.Context, client HttpClient, subscription int, pageSize int) *ListActiveActiveDatabase { + return &ListActiveActiveDatabase{client: client, subscription: subscription, ctx: ctx, pageSize: pageSize} +} + +// Next attempts to retrieve the next page of databases and will return false if no more databases were found. +// Any error that occurs within this function can be retrieved from the `Err()` function. +func (d *ListActiveActiveDatabase) Next() bool { + if d.err != nil { + return false + } + + if d.fin { + return false + } + + if len(d.page) == 0 { + if err := d.nextPage(); err != nil { + d.setError(err) + return false + } + } + + d.updateValue() + + return true +} + +// Value returns the current page of databases. +func (d *ListActiveActiveDatabase) Value() *ActiveActiveDatabase { + return d.value +} + +// Err returns any error that occurred while trying to retrieve the next page of databases. +func (d *ListActiveActiveDatabase) Err() error { + return d.err +} + +func (d *ListActiveActiveDatabase) nextPage() error { + u := fmt.Sprintf("/subscriptions/%d/databases", d.subscription) + q := map[string][]string{ + "limit": {strconv.Itoa(d.pageSize)}, + "offset": {strconv.Itoa(d.offset)}, + } + + var list listActiveActiveDatabaseResponse + err := d.client.GetWithQuery(d.ctx, fmt.Sprintf("list databases for %d", d.subscription), u, q, &list) + if err != nil { + return err + } + + if len(list.Subscription) != 1 || redis.IntValue(list.Subscription[0].ID) != d.subscription { + return fmt.Errorf("server didn't respond with just a single subscription") + } + + d.page = list.Subscription[0].Databases + d.offset += d.pageSize + + return nil +} + +func (d *ListActiveActiveDatabase) updateValue() { + d.value = d.page[0] + d.page = d.page[1:] +} + +func (d *ListActiveActiveDatabase) setError(err error) { + if httpErr, ok := err.(*internal.HTTPError); ok && httpErr.StatusCode == http.StatusNotFound { + d.fin = true + } else { + d.err = err + } + + d.page = nil + d.value = nil +} diff --git a/service/regions/model.go b/service/regions/model.go new file mode 100644 index 0000000..221d1eb --- /dev/null +++ b/service/regions/model.go @@ -0,0 +1,63 @@ +package regions + +import ( + "github.com/RedisLabs/rediscloud-go-api/internal" +) + +type Regions struct { + SubscriptionId *int `json:"subscriptionId,omitempty"` + Regions []*Region `json:"regions,omitempty"` +} + +func (o Regions) String() string { + return internal.ToString(o) +} + +type Region struct { + RegionId *int `json:"regionId,omitempty"` + Region *string `json:"region,omitempty"` + RecreateRegion *bool `json:"-"` + DeploymentCIDR *string `json:"deploymentCIDR,omitempty"` + VpcId *string `json:"vpcId,omitempty"` + Databases []*Database `json:"databases,omitempty"` +} + +func (o Region) String() string { + return internal.ToString(o) +} + +type Database struct { + DatabaseId *int `json:"databaseId,omitempty"` + DatabaseName *string `json:"DatabaseName,omitempty"` + ReadOperationsPerSecond *int `json:"readOperationsPerSecond,omitempty"` + WriteOperationsPerSecond *int `json:"writeOperationsPerSecond,omitempty"` +} + +type CreateRegion struct { + Region *string `json:"region,omitempty"` + DeploymentCIDR *string `json:"deploymentCIDR,omitempty"` + DryRun *bool `json:"dryRun,omitempty"` + Databases []*CreateDatabase `json:"databases,omitempty"` +} + +type DeleteRegion struct { + Region *string `json:"region,omitempty"` +} +type DeleteRegions struct { + Regions []*DeleteRegion `json:"regions,omitempty"` +} + +type CreateLocalThroughput struct { + Region *string `json:"region,omitempty"` + WriteOperationsPerSecond *int `json:"writeOperationsPerSecond"` + ReadOperationsPerSecond *int `json:"readOperationsPerSecond"` +} + +type CreateDatabase struct { + Name *string `json:"name,omitempty"` + LocalThroughputMeasurement *CreateLocalThroughput `json:"localThroughputMeasurement,omitempty"` +} + +type taskResponse struct { + ID *string `json:"taskId,omitempty"` +} diff --git a/service/regions/regions.go b/service/regions/regions.go new file mode 100644 index 0000000..39a63f5 --- /dev/null +++ b/service/regions/regions.go @@ -0,0 +1,2 @@ +// Package regions contains the API calls for the Active-Active Subscription regions service. +package regions diff --git a/service/regions/service.go b/service/regions/service.go new file mode 100644 index 0000000..7dc89d4 --- /dev/null +++ b/service/regions/service.go @@ -0,0 +1,90 @@ +package regions + +import ( + "context" + "fmt" + "net/http" + + "github.com/RedisLabs/rediscloud-go-api/internal" + "github.com/RedisLabs/rediscloud-go-api/service/subscriptions" +) + +type Log interface { + Printf(format string, args ...interface{}) +} + +type HttpClient interface { + Get(ctx context.Context, name, path string, responseBody interface{}) error + Post(ctx context.Context, name, path string, requestBody interface{}, responseBody interface{}) error + Put(ctx context.Context, name, path string, requestBody interface{}, responseBody interface{}) error + DeleteWithQuery(ctx context.Context, name, path string, requestBody interface{}, responseBody interface{}) error +} + +type Task interface { + WaitForResourceId(ctx context.Context, id string) (int, error) + WaitForResource(ctx context.Context, id string, resource interface{}) error + Wait(ctx context.Context, id string) error +} + +type API struct { + client HttpClient + task Task + logger Log +} + +func NewAPI(client HttpClient, task Task, logger Log) *API { + return &API{client: client, task: task, logger: logger} +} + +// Create will create a new region +func (a *API) Create(ctx context.Context, subId int, region CreateRegion) (int, error) { + var task taskResponse + err := a.client.Post(ctx, "create subscription region", fmt.Sprintf("/subscriptions/%d/regions", subId), region, &task) + if err != nil { + return 0, wrap404Error(subId, err) + } + + a.logger.Printf("Waiting for task %s to finish creating the subscription region", task) + + id, err := a.task.WaitForResourceId(ctx, *task.ID) + if err != nil { + return 0, err + } + + return id, nil +} + +// List will list all of a given subscription's active-active regions. +func (a API) List(ctx context.Context, subId int) (*Regions, error) { + var response Regions + err := a.client.Get(ctx, "list regions", fmt.Sprintf("/subscriptions/%d/regions", subId), &response) + if err != nil { + return nil, wrap404Error(subId, err) + } + + return &response, nil +} + +func (a *API) DeleteWithQuery(ctx context.Context, id int, regions DeleteRegions) error { + var task taskResponse + err := a.client.DeleteWithQuery(ctx, fmt.Sprintf("delete region %d", id), fmt.Sprintf("/subscriptions/%d/regions/", id), regions, &task) + if err != nil { + return wrap404Error(id, err) + } + + a.logger.Printf("Waiting for region %d to finish being deleted", id) + + err = a.task.Wait(ctx, *task.ID) + if err != nil { + return err + } + + return nil +} + +func wrap404Error(id int, err error) error { + if v, ok := err.(*internal.HTTPError); ok && v.StatusCode == http.StatusNotFound { + return &subscriptions.NotFound{ID: id} + } + return err +} diff --git a/service/subscriptions/model.go b/service/subscriptions/model.go index db40c73..77a0308 100644 --- a/service/subscriptions/model.go +++ b/service/subscriptions/model.go @@ -8,6 +8,7 @@ import ( type CreateSubscription struct { Name *string `json:"name,omitempty"` + DeploymentType *string `json:"deploymentType,omitempty"` DryRun *bool `json:"dryRun,omitempty"` PaymentMethodID *int `json:"paymentMethodId,omitempty"` PaymentMethod *string `json:"paymentMethod,omitempty"` @@ -51,16 +52,17 @@ func (o CreateNetworking) String() string { } type CreateDatabase struct { - Name *string `json:"name,omitempty"` - Protocol *string `json:"protocol,omitempty"` - MemoryLimitInGB *float64 `json:"memoryLimitInGb,omitempty"` - SupportOSSClusterAPI *bool `json:"supportOSSClusterApi,omitempty"` - DataPersistence *string `json:"dataPersistence,omitempty"` - Replication *bool `json:"replication,omitempty"` - ThroughputMeasurement *CreateThroughput `json:"throughputMeasurement,omitempty"` - Modules []*CreateModules `json:"modules,omitempty"` - Quantity *int `json:"quantity,omitempty"` - AverageItemSizeInBytes *int `json:"averageItemSizeInBytes,omitempty"` + Name *string `json:"name,omitempty"` + Protocol *string `json:"protocol,omitempty"` + MemoryLimitInGB *float64 `json:"memoryLimitInGb,omitempty"` + SupportOSSClusterAPI *bool `json:"supportOSSClusterApi,omitempty"` + DataPersistence *string `json:"dataPersistence,omitempty"` + Replication *bool `json:"replication,omitempty"` + ThroughputMeasurement *CreateThroughput `json:"throughputMeasurement,omitempty"` + LocalThroughputMeasurement []*CreateLocalThroughput `json:"localThroughputMeasurement,omitempty"` + Modules []*CreateModules `json:"modules,omitempty"` + Quantity *int `json:"quantity,omitempty"` + AverageItemSizeInBytes *int `json:"averageItemSizeInBytes,omitempty"` } func (o CreateDatabase) String() string { @@ -76,6 +78,16 @@ func (o CreateThroughput) String() string { return internal.ToString(o) } +type CreateLocalThroughput struct { + Region *string `json:"region,omitempty"` + WriteOperationsPerSecond *int `json:"writeOperationsPerSecond"` + ReadOperationsPerSecond *int `json:"readOperationsPerSecond"` +} + +func (o CreateLocalThroughput) String() string { + return internal.ToString(o) +} + type CreateModules struct { Name *string `json:"name,omitempty"` } @@ -97,6 +109,7 @@ type Subscription struct { ID *int `json:"id,omitempty"` Name *string `json:"name,omitempty"` Status *string `json:"status,omitempty"` + DeploymentType *string `json:"deploymentType,omitempty"` PaymentMethod *string `json:"paymentMethodType,omitempty"` PaymentMethodID *int `json:"paymentMethodId,omitempty"` MemoryStorage *string `json:"memoryStorage,omitempty"` @@ -174,6 +187,21 @@ func (o CreateVPCPeering) String() string { return internal.ToString(o) } +type CreateActiveActiveVPCPeering struct { + SourceRegion *string `json:"sourceRegion,omitempty"` + DestinationRegion *string `json:"destinationRegion,omitempty"` + AWSAccountID *string `json:"awsAccountId,omitempty"` + VPCId *string `json:"vpcId,omitempty"` + VPCCidr *string `json:"vpcCidr,omitempty"` + Provider *string `json:"provider,omitempty"` + VPCProjectUID *string `json:"vpcProjectUid,omitempty"` + VPCNetworkName *string `json:"vpcNetworkName,omitempty"` +} + +func (o CreateActiveActiveVPCPeering) String() string { + return internal.ToString(o) +} + type listVpcPeering struct { Peerings []*VPCPeering `json:"peerings"` } @@ -197,6 +225,39 @@ func (o VPCPeering) String() string { return internal.ToString(o) } +type listActiveActiveVpcPeering struct { + SubscriptionId *int `json:"subscriptionId,omitempty"` + Regions []*ActiveActiveVpcRegion `json:"regions,omitempty"` +} + +type ActiveActiveVpcRegion struct { + ID *int `json:"id,omitempty"` + SourceRegion *string `json:"region,omitempty"` + VPCPeerings []*ActiveActiveVPCPeering `json:"vpcPeerings,omitempty"` +} + +type ActiveActiveVPCPeering struct { + ID *int `json:"id,omitempty"` + Status *string `json:"status,omitempty"` + RegionId *int `json:"regionId,omitempty"` + RegionName *string `json:"regionName,omitempty"` + AWSAccountID *string `json:"awsAccountId,omitempty"` + AWSPeeringID *string `json:"awsPeeringUid,omitempty"` + VPCId *string `json:"vpcUid,omitempty"` + VPCCidr *string `json:"vpcCidr,omitempty"` + GCPProjectUID *string `json:"vpcProjectUid,omitempty"` + NetworkName *string `json:"vpcNetworkName,omitempty"` + RedisProjectUID *string `json:"redisProjectUid,omitempty"` + RedisNetworkName *string `json:"redisNetworkName,omitempty"` + CloudPeeringID *string `json:"cloudPeeringId,omitempty"` + SourceRegion *string `json:"sourceRegion,omitempty"` + DestinationRegion *string `json:"destinationRegion,omitempty"` +} + +func (o ActiveActiveVPCPeering) String() string { + return internal.ToString(o) +} + type listSubscriptionResponse struct { Subscriptions []*Subscription `json:"subscriptions"` } @@ -210,11 +271,11 @@ func (o taskResponse) String() string { } type NotFound struct { - id int + ID int } func (f *NotFound) Error() string { - return fmt.Sprintf("subscription %d not found", f.id) + return fmt.Sprintf("subscription %d not found", f.ID) } const ( @@ -237,4 +298,7 @@ const ( VPCPeeringStatusPendingAcceptance = "pending-acceptance" // Failed value of the `Status` field in `VPCPeering` VPCPeeringStatusFailed = "failed" + + SubscriptionDeploymentTypeSingleRegion = "single-region" + SubscriptionDeploymentTypeActiveActive = "active-active" ) diff --git a/service/subscriptions/service.go b/service/subscriptions/service.go index 223b410..2809f27 100644 --- a/service/subscriptions/service.go +++ b/service/subscriptions/service.go @@ -170,6 +170,24 @@ func (a *API) ListVPCPeering(ctx context.Context, id int) ([]*VPCPeering, error) return peering.Peerings, nil } +func (a *API) ListActiveActiveVPCPeering(ctx context.Context, id int) ([]*ActiveActiveVpcRegion, error) { + var task taskResponse + err := a.client.Get(ctx, fmt.Sprintf("get peerings for subscription %d", id), fmt.Sprintf("/subscriptions/%d/regions/peerings/", id), &task) + if err != nil { + return nil, wrap404Error(id, err) + } + + a.logger.Printf("Waiting for subscription %d peering details to be retrieved", id) + + var peering listActiveActiveVpcPeering + err = a.task.WaitForResource(ctx, *task.ID, &peering) + if err != nil { + return nil, err + } + + return peering.Regions, nil +} + // CreateVPCPeering creates a new VPC peering from the subscription VPC and returns the identifier of the VPC peering. func (a *API) CreateVPCPeering(ctx context.Context, id int, create CreateVPCPeering) (int, error) { var task taskResponse @@ -188,6 +206,23 @@ func (a *API) CreateVPCPeering(ctx context.Context, id int, create CreateVPCPeer return id, nil } +func (a *API) CreateActiveActiveVPCPeering(ctx context.Context, id int, create CreateActiveActiveVPCPeering) (int, error) { + var task taskResponse + err := a.client.Post(ctx, fmt.Sprintf("create peering for subscription %d", id), fmt.Sprintf("/subscriptions/%d/regions/peerings/", id), create, &task) + if err != nil { + return 0, wrap404Error(id, err) + } + + a.logger.Printf("Waiting for subscription %d peering details to be retrieved", id) + + id, err = a.task.WaitForResourceId(ctx, *task.ID) + if err != nil { + return 0, err + } + + return id, nil +} + // DeleteVPCPeering destroys an existing VPC peering connection. func (a *API) DeleteVPCPeering(ctx context.Context, subscription int, peering int) error { var task taskResponse @@ -206,9 +241,26 @@ func (a *API) DeleteVPCPeering(ctx context.Context, subscription int, peering in return nil } +func (a *API) DeleteActiveActiveVPCPeering(ctx context.Context, subscription int, peering int) error { + var task taskResponse + err := a.client.Delete(ctx, fmt.Sprintf("deleting peering %d for subscription %d", peering, subscription), fmt.Sprintf("/subscriptions/%d/regions/peerings/%d", subscription, peering), &task) + if err != nil { + return err + } + + a.logger.Printf("Waiting for peering %d for subscription %d to be deleted", peering, subscription) + + err = a.task.Wait(ctx, *task.ID) + if err != nil { + return err + } + + return nil +} + func wrap404Error(id int, err error) error { if v, ok := err.(*internal.HTTPError); ok && v.StatusCode == http.StatusNotFound { - return &NotFound{id: id} + return &NotFound{ID: id} } return err }