Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions planetscale/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type Client struct {
DeployRequests DeployRequestsService
Keyspaces KeyspacesService
LookupVindex LookupVindexService
MaintenanceSchedules MaintenanceSchedulesService
Materialize MaterializeService
MoveTables MoveTablesService
Organizations OrganizationsService
Expand Down Expand Up @@ -334,6 +335,7 @@ func NewClient(opts ...ClientOption) (*Client, error) {
c.DeployRequests = &deployRequestsService{client: c}
c.Keyspaces = &keyspacesService{client: c}
c.LookupVindex = &lookupVindexService{client: c}
c.MaintenanceSchedules = &maintenanceSchedulesService{client: c}
c.Materialize = &materializeService{client: c}
c.MoveTables = &moveTablesService{client: c}
c.Organizations = &organizationsService{client: c}
Expand Down
55 changes: 55 additions & 0 deletions planetscale/databases.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,29 @@ type UpdateDatabaseSettingsRequest struct {
DefaultBranch *string `json:"default_branch,omitempty"`
}

// GetDatabaseThrottlerRequest gets the database-level Vitess migration throttler.
type GetDatabaseThrottlerRequest struct {
Organization string `json:"-"`
Database string `json:"-"`
}

// UpdateDatabaseThrottlerRequest updates the database-level Vitess migration throttler.
type UpdateDatabaseThrottlerRequest struct {
Organization string `json:"-"`
Database string `json:"-"`
Ratio *int `json:"ratio,omitempty"`
Configurations []*UpdateThrottlerConfiguration `json:"configurations,omitempty"`
}

// DatabaseThrottler is the database-level throttler configuration for Vitess
// deploy request migrations. Distinct from per-deploy-request throttler and
// from tablet/vtctld throttler.
type DatabaseThrottler struct {
Keyspaces []string `json:"keyspaces"`
Configurable *ThrottlerConfigurable `json:"configurable"`
Configurations []*ThrottlerConfiguration `json:"configurations"`
}

// DatabaseService is an interface for communicating with the PlanetScale
// Databases API endpoint.
type DatabasesService interface {
Expand All @@ -82,6 +105,8 @@ type DatabasesService interface {
List(context.Context, *ListDatabasesRequest, ...ListOption) ([]*Database, error)
Delete(context.Context, *DeleteDatabaseRequest) (*DatabaseDeletionRequest, error)
UpdateSettings(context.Context, *UpdateDatabaseSettingsRequest) (*Database, error)
GetThrottler(context.Context, *GetDatabaseThrottlerRequest) (*DatabaseThrottler, error)
UpdateThrottler(context.Context, *UpdateDatabaseThrottlerRequest) (*DatabaseThrottler, error)
}

// DatabaseDeletionRequest encapsulates the request for deleting a database from
Expand Down Expand Up @@ -231,6 +256,36 @@ func (ds *databasesService) UpdateSettings(ctx context.Context, updateReq *Updat
return db, nil
}

func (ds *databasesService) GetThrottler(ctx context.Context, getReq *GetDatabaseThrottlerRequest) (*DatabaseThrottler, error) {
pathStr := path.Join(databasesAPIPath(getReq.Organization), getReq.Database, "throttler")
req, err := ds.client.newRequest(http.MethodGet, pathStr, nil)
if err != nil {
return nil, fmt.Errorf("error creating request for get database throttler: %w", err)
}

throttler := &DatabaseThrottler{}
if err := ds.client.do(ctx, req, throttler); err != nil {
return nil, err
}

return throttler, nil
}

func (ds *databasesService) UpdateThrottler(ctx context.Context, updateReq *UpdateDatabaseThrottlerRequest) (*DatabaseThrottler, error) {
pathStr := path.Join(databasesAPIPath(updateReq.Organization), updateReq.Database, "throttler")
req, err := ds.client.newRequest(http.MethodPatch, pathStr, updateReq)
if err != nil {
return nil, fmt.Errorf("error creating request for update database throttler: %w", err)
}

throttler := &DatabaseThrottler{}
if err := ds.client.do(ctx, req, throttler); err != nil {
return nil, err
}

return throttler, nil
}

func databasesAPIPath(org string) string {
return path.Join("v1/organizations", org, "databases")
}
61 changes: 61 additions & 0 deletions planetscale/databases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -580,3 +580,64 @@ func TestDatabases_UpdateSettings(t *testing.T) {
c.Assert(db.RequireApprovalForDeploy, qt.IsTrue)
c.Assert(db.InsightsRawQueries, qt.IsFalse)
}

func TestDatabases_GetThrottler(t *testing.T) {
c := qt.New(t)

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.Assert(r.Method, qt.Equals, http.MethodGet)
c.Assert(r.URL.Path, qt.Equals, "/v1/organizations/my-org/databases/planetscale-go-test-db/throttler")
w.WriteHeader(200)
_, err := w.Write([]byte(`{"keyspaces":["main"],"configurable":{"id":"db-1","name":"planetscale-go-test-db"},"configurations":[{"keyspace_name":"main","ratio":50}]}`))
c.Assert(err, qt.IsNil)
}))

client, err := NewClient(WithBaseURL(ts.URL))
c.Assert(err, qt.IsNil)

throttler, err := client.Databases.GetThrottler(context.Background(), &GetDatabaseThrottlerRequest{
Organization: testOrg,
Database: testDatabase,
})
c.Assert(err, qt.IsNil)
c.Assert(throttler.Keyspaces, qt.DeepEquals, []string{"main"})
c.Assert(throttler.Configurations, qt.HasLen, 1)
c.Assert(throttler.Configurations[0].Ratio, qt.Equals, float64(50))
}

func TestDatabases_UpdateThrottler(t *testing.T) {
c := qt.New(t)

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.Assert(r.Method, qt.Equals, http.MethodPatch)
c.Assert(r.URL.Path, qt.Equals, "/v1/organizations/my-org/databases/planetscale-go-test-db/throttler")

var body UpdateDatabaseThrottlerRequest
c.Assert(json.NewDecoder(r.Body).Decode(&body), qt.IsNil)
c.Assert(body.Ratio, qt.IsNotNil)
c.Assert(*body.Ratio, qt.Equals, 25)
c.Assert(body.Configurations, qt.DeepEquals, []*UpdateThrottlerConfiguration{
{KeyspaceName: "main", Ratio: 10},
})

w.WriteHeader(200)
_, err := w.Write([]byte(`{"keyspaces":["main"],"configurations":[{"keyspace_name":"main","ratio":10}]}`))
c.Assert(err, qt.IsNil)
}))

client, err := NewClient(WithBaseURL(ts.URL))
c.Assert(err, qt.IsNil)

ratio := 25
throttler, err := client.Databases.UpdateThrottler(context.Background(), &UpdateDatabaseThrottlerRequest{
Organization: testOrg,
Database: testDatabase,
Ratio: &ratio,
Configurations: []*UpdateThrottlerConfiguration{
{KeyspaceName: "main", Ratio: 10},
},
})
c.Assert(err, qt.IsNil)
c.Assert(throttler.Configurations, qt.HasLen, 1)
c.Assert(throttler.Configurations[0].Ratio, qt.Equals, float64(10))
}
193 changes: 193 additions & 0 deletions planetscale/deploy_requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ type DeployRequestsService interface {
Get(context.Context, *GetDeployRequestRequest) (*DeployRequest, error)
List(context.Context, *ListDeployRequestsRequest) ([]*DeployRequest, error)
GetDeployOperations(context.Context, *GetDeployOperationsRequest) ([]*DeployOperation, error)
GetDeployQueue(context.Context, *GetDeployQueueRequest) ([]*Deployment, error)
GetDeployment(context.Context, *GetDeploymentRequest) (*Deployment, error)
ListReviews(context.Context, *ListDeployRequestReviewsRequest) ([]*DeployRequestReview, error)
CheckStorage(context.Context, *CheckDeployRequestStorageRequest) (*DeployRequestStorageCheck, error)
GetThrottler(context.Context, *GetDeployRequestThrottlerRequest) (*DeployRequestThrottler, error)
UpdateThrottler(context.Context, *UpdateDeployRequestThrottlerRequest) (*DeployRequestThrottler, error)
SkipRevertDeploy(context.Context, *SkipRevertDeployRequestRequest) (*DeployRequest, error)
RevertDeploy(context.Context, *RevertDeployRequestRequest) (*DeployRequest, error)
}
Expand Down Expand Up @@ -135,6 +141,17 @@ type Deployment struct {
InstantDDLEligible bool `json:"instant_ddl_eligible"`
InstantDDL bool `json:"instant_ddl"`

AutoCutover bool `json:"auto_cutover"`
AutoDeleteBranch bool `json:"auto_delete_branch"`
CutoverExpiring bool `json:"cutover_expiring"`
TableLocked bool `json:"table_locked"`
QueuePaused bool `json:"queue_paused"`
ParallelLaneBlocked bool `json:"parallel_lane_blocked"`
DeployCheckErrors string `json:"deploy_check_errors"`
LockedTableName string `json:"locked_table_name"`
QueuePauseReason string `json:"queue_pause_reason"`
Strategy string `json:"strategy"`

Actor *Actor `json:"actor"`
CutoverActor *Actor `json:"cutover_actor"`
CancelledActor *Actor `json:"cancelled_actor"`
Expand All @@ -144,7 +161,90 @@ type Deployment struct {
StartedAt *time.Time `json:"started_at"`
QueuedAt *time.Time `json:"queued_at"`
FinishedAt *time.Time `json:"finished_at"`
SubmittedAt *time.Time `json:"submitted_at"`
CutoverAt *time.Time `json:"cutover_at"`
ReadyToCutoverAt *time.Time `json:"ready_to_cutover_at"`
ForceCutoverRequestedAt *time.Time `json:"force_cutover_requested_at"`
SchemaLastUpdatedAt *time.Time `json:"schema_last_updated_at"`
}

// GetDeployQueueRequest gets the deploy queue for a database.
type GetDeployQueueRequest struct {
Organization string `json:"-"`
Database string `json:"-"`
}

// GetDeploymentRequest gets the deployment for a deploy request.
type GetDeploymentRequest struct {
Organization string `json:"-"`
Database string `json:"-"`
Number uint64 `json:"-"`
}

// ListDeployRequestReviewsRequest lists reviews for a deploy request.
type ListDeployRequestReviewsRequest struct {
Organization string `json:"-"`
Database string `json:"-"`
Number uint64 `json:"-"`
}

// CheckDeployRequestStorageRequest checks storage readiness for a deploy request.
type CheckDeployRequestStorageRequest struct {
Organization string `json:"-"`
Database string `json:"-"`
Number uint64 `json:"-"`
}

// DeployRequestStorageCheck is the storage check response for a deploy request.
type DeployRequestStorageCheck struct {
EnoughStorage bool `json:"enough_storage"`
Upgradeable bool `json:"upgradeable"`
StorageBytesNeeded int64 `json:"storage_bytes_needed"`
StorageReport map[string]any `json:"storage_report"`
}

// GetDeployRequestThrottlerRequest gets throttler config for a deploy request.
type GetDeployRequestThrottlerRequest struct {
Organization string `json:"-"`
Database string `json:"-"`
Number uint64 `json:"-"`
}

// UpdateDeployRequestThrottlerRequest updates throttler config for a deploy request.
type UpdateDeployRequestThrottlerRequest struct {
Organization string `json:"-"`
Database string `json:"-"`
Number uint64 `json:"-"`
Ratio *int `json:"ratio,omitempty"`
Configurations []*UpdateThrottlerConfiguration `json:"configurations,omitempty"`
}

// UpdateThrottlerConfiguration is a per-keyspace throttler ratio for updates.
type UpdateThrottlerConfiguration struct {
KeyspaceName string `json:"keyspace_name"`
Ratio int `json:"ratio"`
}

// DeployRequestThrottler is the throttler configuration for a deploy request.
type DeployRequestThrottler struct {
Keyspaces []string `json:"keyspaces"`
Configurable *ThrottlerConfigurable `json:"configurable"`
Configurations []*ThrottlerConfiguration `json:"configurations"`
}

// ThrottlerConfigurable identifies the resource owning throttler config.
type ThrottlerConfigurable struct {
ID string `json:"id"`
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `json:"deleted_at"`
}

// ThrottlerConfiguration is a per-keyspace throttler ratio.
type ThrottlerConfiguration struct {
KeyspaceName string `json:"keyspace_name"`
Ratio float64 `json:"ratio"`
}

// DeployRequest encapsulates the request to deploy a database branch's schema
Expand Down Expand Up @@ -552,6 +652,99 @@ func (d *deployRequestsService) GetDeployOperations(ctx context.Context, getReq
return resp.Ops, nil
}

type deployQueueResponse struct {
Deployments []*Deployment `json:"data"`
}

func (d *deployRequestsService) GetDeployQueue(ctx context.Context, getReq *GetDeployQueueRequest) ([]*Deployment, error) {
pathStr := path.Join(databasesAPIPath(getReq.Organization), getReq.Database, "deploy-queue")
req, err := d.client.newRequest(http.MethodGet, pathStr, nil)
if err != nil {
return nil, fmt.Errorf("error creating http request: %w", err)
}

resp := &deployQueueResponse{}
if err := d.client.do(ctx, req, &resp); err != nil {
return nil, err
}

return resp.Deployments, nil
}

func (d *deployRequestsService) GetDeployment(ctx context.Context, getReq *GetDeploymentRequest) (*Deployment, error) {
req, err := d.client.newRequest(http.MethodGet, deployRequestActionAPIPath(getReq.Organization, getReq.Database, getReq.Number, "deployment"), nil)
if err != nil {
return nil, fmt.Errorf("error creating http request: %w", err)
}

deployment := &Deployment{}
if err := d.client.do(ctx, req, deployment); err != nil {
return nil, err
}

return deployment, nil
}

type deployRequestReviewsResponse struct {
Reviews []*DeployRequestReview `json:"data"`
}

func (d *deployRequestsService) ListReviews(ctx context.Context, listReq *ListDeployRequestReviewsRequest) ([]*DeployRequestReview, error) {
req, err := d.client.newRequest(http.MethodGet, deployRequestActionAPIPath(listReq.Organization, listReq.Database, listReq.Number, "reviews"), nil)
if err != nil {
return nil, fmt.Errorf("error creating http request: %w", err)
}

resp := &deployRequestReviewsResponse{}
if err := d.client.do(ctx, req, &resp); err != nil {
return nil, err
}

return resp.Reviews, nil
}

func (d *deployRequestsService) CheckStorage(ctx context.Context, checkReq *CheckDeployRequestStorageRequest) (*DeployRequestStorageCheck, error) {
req, err := d.client.newRequest(http.MethodGet, deployRequestActionAPIPath(checkReq.Organization, checkReq.Database, checkReq.Number, "storage-check"), nil)
if err != nil {
return nil, fmt.Errorf("error creating http request: %w", err)
}

check := &DeployRequestStorageCheck{}
if err := d.client.do(ctx, req, check); err != nil {
return nil, err
}

return check, nil
}

func (d *deployRequestsService) GetThrottler(ctx context.Context, getReq *GetDeployRequestThrottlerRequest) (*DeployRequestThrottler, error) {
req, err := d.client.newRequest(http.MethodGet, deployRequestActionAPIPath(getReq.Organization, getReq.Database, getReq.Number, "throttler"), nil)
if err != nil {
return nil, fmt.Errorf("error creating http request: %w", err)
}

throttler := &DeployRequestThrottler{}
if err := d.client.do(ctx, req, throttler); err != nil {
return nil, err
}

return throttler, nil
}

func (d *deployRequestsService) UpdateThrottler(ctx context.Context, updateReq *UpdateDeployRequestThrottlerRequest) (*DeployRequestThrottler, error) {
req, err := d.client.newRequest(http.MethodPatch, deployRequestActionAPIPath(updateReq.Organization, updateReq.Database, updateReq.Number, "throttler"), updateReq)
if err != nil {
return nil, fmt.Errorf("error creating http request: %w", err)
}

throttler := &DeployRequestThrottler{}
if err := d.client.do(ctx, req, throttler); err != nil {
return nil, err
}

return throttler, nil
}

func deployRequestsAPIPath(org, db string) string {
return path.Join(databasesAPIPath(org), db, "deploy-requests")
}
Expand Down
Loading
Loading