From 5509b980d21529954aee92bef4960c030e3fc5db Mon Sep 17 00:00:00 2001 From: "planetscale-cli[bot]" <272331943+planetscale-cli[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 00:10:49 +0000 Subject: [PATCH] Sync API client from planetscale/cli@214d340 --- planetscale/deploy_requests.go | 193 ++++++++++++++++++++++++++++ planetscale/deploy_requests_test.go | 161 +++++++++++++++++++++++ 2 files changed, 354 insertions(+) diff --git a/planetscale/deploy_requests.go b/planetscale/deploy_requests.go index 8a626ef..d506979 100644 --- a/planetscale/deploy_requests.go +++ b/planetscale/deploy_requests.go @@ -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) } @@ -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"` @@ -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 @@ -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") } diff --git a/planetscale/deploy_requests_test.go b/planetscale/deploy_requests_test.go index 16f6980..cbe807d 100644 --- a/planetscale/deploy_requests_test.go +++ b/planetscale/deploy_requests_test.go @@ -623,3 +623,164 @@ func TestDeployRequests_DeployOperations(t *testing.T) { c.Assert(err, qt.IsNil) c.Assert(do, qt.DeepEquals, want) } + +func TestDeployRequests_GetDeployQueue(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/test-organization/databases/test-database/deploy-queue") + w.WriteHeader(200) + _, err := w.Write([]byte(`{"type":"list","data":[{"id":"dep-1","deploy_request_number":7,"state":"queued","auto_cutover":true}]}`)) + c.Assert(err, qt.IsNil) + })) + + client, err := NewClient(WithBaseURL(ts.URL)) + c.Assert(err, qt.IsNil) + + deployments, err := client.DeployRequests.GetDeployQueue(context.Background(), &GetDeployQueueRequest{ + Organization: "test-organization", + Database: "test-database", + }) + c.Assert(err, qt.IsNil) + c.Assert(deployments, qt.HasLen, 1) + c.Assert(deployments[0].ID, qt.Equals, "dep-1") + c.Assert(deployments[0].DeployRequestNumber, qt.Equals, uint64(7)) + c.Assert(deployments[0].AutoCutover, qt.IsTrue) +} + +func TestDeployRequests_GetDeployment(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/test-organization/databases/test-database/deploy-requests/42/deployment") + w.WriteHeader(200) + _, err := w.Write([]byte(`{"id":"dep-1","deploy_request_number":42,"state":"in_progress","auto_delete_branch":true}`)) + c.Assert(err, qt.IsNil) + })) + + client, err := NewClient(WithBaseURL(ts.URL)) + c.Assert(err, qt.IsNil) + + deployment, err := client.DeployRequests.GetDeployment(context.Background(), &GetDeploymentRequest{ + Organization: "test-organization", + Database: "test-database", + Number: 42, + }) + c.Assert(err, qt.IsNil) + c.Assert(deployment.ID, qt.Equals, "dep-1") + c.Assert(deployment.AutoDeleteBranch, qt.IsTrue) +} + +func TestDeployRequests_ListReviews(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/test-organization/databases/test-database/deploy-requests/42/reviews") + w.WriteHeader(200) + _, err := w.Write([]byte(`{"type":"list","data":[{"id":"rev-1","state":"approved","body":"lgtm","actor":{"display_name":"gomez"}}]}`)) + c.Assert(err, qt.IsNil) + })) + + client, err := NewClient(WithBaseURL(ts.URL)) + c.Assert(err, qt.IsNil) + + reviews, err := client.DeployRequests.ListReviews(context.Background(), &ListDeployRequestReviewsRequest{ + Organization: "test-organization", + Database: "test-database", + Number: 42, + }) + c.Assert(err, qt.IsNil) + c.Assert(reviews, qt.HasLen, 1) + c.Assert(reviews[0].ID, qt.Equals, "rev-1") + c.Assert(reviews[0].Actor.Name, qt.Equals, "gomez") +} + +func TestDeployRequests_CheckStorage(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/test-organization/databases/test-database/deploy-requests/42/storage-check") + w.WriteHeader(200) + _, err := w.Write([]byte(`{"enough_storage":true,"upgradeable":false,"storage_bytes_needed":0,"storage_report":{}}`)) + c.Assert(err, qt.IsNil) + })) + + client, err := NewClient(WithBaseURL(ts.URL)) + c.Assert(err, qt.IsNil) + + check, err := client.DeployRequests.CheckStorage(context.Background(), &CheckDeployRequestStorageRequest{ + Organization: "test-organization", + Database: "test-database", + Number: 42, + }) + c.Assert(err, qt.IsNil) + c.Assert(check.EnoughStorage, qt.IsTrue) + c.Assert(check.StorageBytesNeeded, qt.Equals, int64(0)) +} + +func TestDeployRequests_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/test-organization/databases/test-database/deploy-requests/42/throttler") + w.WriteHeader(200) + _, err := w.Write([]byte(`{"keyspaces":["main"],"configurable":{"id":"dr-1","name":"42"},"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.DeployRequests.GetThrottler(context.Background(), &GetDeployRequestThrottlerRequest{ + Organization: "test-organization", + Database: "test-database", + Number: 42, + }) + 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 TestDeployRequests_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/test-organization/databases/test-database/deploy-requests/42/throttler") + + var body UpdateDeployRequestThrottlerRequest + 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.DeployRequests.UpdateThrottler(context.Background(), &UpdateDeployRequestThrottlerRequest{ + Organization: "test-organization", + Database: "test-database", + Number: 42, + 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)) +}