From e22b2e59fc3261982778b4297f1d114a4d4d6a7c Mon Sep 17 00:00:00 2001 From: avallete Date: Sat, 5 Oct 2024 00:01:17 +0200 Subject: [PATCH 1/5] feat: check project status at link time --- api/beta.yaml | 57 ++++++++++++++++++++++ internal/link/link.go | 22 +++++++++ pkg/api/client.gen.go | 109 ++++++++++++++++++++++++++++++++++++++++++ pkg/api/types.gen.go | 35 ++++++++++++-- 4 files changed, 220 insertions(+), 3 deletions(-) diff --git a/api/beta.yaml b/api/beta.yaml index 79f3dc8f0..66d88dd9e 100644 --- a/api/beta.yaml +++ b/api/beta.yaml @@ -1726,6 +1726,32 @@ paths: - Auth security: - bearer: [] + /v1/projects/{ref}/status: + get: + operationId: v1-get-project-status + summary: Gets a specific project status that belongs to the authenticated user + parameters: + - name: ref + required: true + in: path + description: Project ref + schema: + minLength: 20 + maxLength: 20 + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/StatusResponse' + '500': + description: Failed to retrieve project status + tags: + - Projects + security: + - bearer: [] /v1/projects/{ref}/database/query: post: operationId: v1-run-a-query @@ -3103,6 +3129,37 @@ components: - id - ref - name + StatusResponse: + type: object + properties: + id: + type: number + ref: + type: string + name: + type: string + status: + enum: + - ACTIVE_HEALTHY + - ACTIVE_UNHEALTHY + - COMING_UP + - GOING_DOWN + - INACTIVE + - INIT_FAILED + - REMOVED + - RESTARTING + - UNKNOWN + - UPGRADING + - PAUSING + - RESTORING + - RESTORE_FAILED + - PAUSE_FAILED + type: string + required: + - id + - ref + - name + - status SecretResponse: type: object properties: diff --git a/internal/link/link.go b/internal/link/link.go index 3a2831070..9ff9447ea 100644 --- a/internal/link/link.go +++ b/internal/link/link.go @@ -29,6 +29,11 @@ func Run(ctx context.Context, projectRef string, fsys afero.Fs, options ...func( "api": utils.Config.Api, "db": utils.Config.Db, }) + + if err := checkRemoteProjectStatus(ctx, projectRef); err != nil { + return err + } + // 1. Check service config keys, err := tenant.GetApiKeys(ctx, projectRef) if err != nil { @@ -233,3 +238,20 @@ func updatePoolerConfig(config api.SupavisorConfigResponse) { utils.Config.Db.Pooler.MaxClientConn = uint(*config.MaxClientConn) } } + +func checkRemoteProjectStatus(ctx context.Context, projectRef string) error { + resp, err := utils.GetSupabase().V1GetProjectStatusWithResponse(ctx, projectRef) + if err != nil { + return errors.Errorf("failed to retrieve remote project status: %w", err) + } + if resp.JSON200 == nil { + return errors.New("Unexpected error retrieving remote project status: " + string(resp.Body)) + } + if resp.JSON200.Status == api.StatusResponseStatusINACTIVE { + return errors.New(fmt.Sprintf("Project is paused. An admin must unpause it from the Supabase dashboard at %s/project/%s", utils.Aqua(utils.GetSupabaseDashboardURL()), projectRef)) + } + if resp.JSON200.Status != api.StatusResponseStatusACTIVEHEALTHY { + fmt.Fprintf(os.Stderr, "%s: Project status is %s instead of Active Healthy. Some operations might fail.\n", utils.Yellow("Warning"), resp.JSON200.Status) + } + return nil +} diff --git a/pkg/api/client.gen.go b/pkg/api/client.gen.go index 594246049..a26848b62 100644 --- a/pkg/api/client.gen.go +++ b/pkg/api/client.gen.go @@ -337,6 +337,9 @@ type ClientInterface interface { V1UpdateSslEnforcementConfig(ctx context.Context, ref string, body V1UpdateSslEnforcementConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1GetProjectStatus request + V1GetProjectStatus(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1ListAllBuckets request V1ListAllBuckets(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -1469,6 +1472,18 @@ func (c *Client) V1UpdateSslEnforcementConfig(ctx context.Context, ref string, b return c.Client.Do(req) } +func (c *Client) V1GetProjectStatus(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1GetProjectStatusRequest(c.Server, ref) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) V1ListAllBuckets(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewV1ListAllBucketsRequest(c.Server, ref) if err != nil { @@ -4587,6 +4602,40 @@ func NewV1UpdateSslEnforcementConfigRequestWithBody(server string, ref string, c return req, nil } +// NewV1GetProjectStatusRequest generates requests for V1GetProjectStatus +func NewV1GetProjectStatusRequest(server string, ref string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "ref", runtime.ParamLocationPath, ref) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/projects/%s/status", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewV1ListAllBucketsRequest generates requests for V1ListAllBuckets func NewV1ListAllBucketsRequest(server string, ref string) (*http.Request, error) { var err error @@ -5328,6 +5377,9 @@ type ClientWithResponsesInterface interface { V1UpdateSslEnforcementConfigWithResponse(ctx context.Context, ref string, body V1UpdateSslEnforcementConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*V1UpdateSslEnforcementConfigResponse, error) + // V1GetProjectStatusWithResponse request + V1GetProjectStatusWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1GetProjectStatusResponse, error) + // V1ListAllBucketsWithResponse request V1ListAllBucketsWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1ListAllBucketsResponse, error) @@ -6808,6 +6860,28 @@ func (r V1UpdateSslEnforcementConfigResponse) StatusCode() int { return 0 } +type V1GetProjectStatusResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *StatusResponse +} + +// Status returns HTTPResponse.Status +func (r V1GetProjectStatusResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r V1GetProjectStatusResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type V1ListAllBucketsResponse struct { Body []byte HTTPResponse *http.Response @@ -7843,6 +7917,15 @@ func (c *ClientWithResponses) V1UpdateSslEnforcementConfigWithResponse(ctx conte return ParseV1UpdateSslEnforcementConfigResponse(rsp) } +// V1GetProjectStatusWithResponse request returning *V1GetProjectStatusResponse +func (c *ClientWithResponses) V1GetProjectStatusWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1GetProjectStatusResponse, error) { + rsp, err := c.V1GetProjectStatus(ctx, ref, reqEditors...) + if err != nil { + return nil, err + } + return ParseV1GetProjectStatusResponse(rsp) +} + // V1ListAllBucketsWithResponse request returning *V1ListAllBucketsResponse func (c *ClientWithResponses) V1ListAllBucketsWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1ListAllBucketsResponse, error) { rsp, err := c.V1ListAllBuckets(ctx, ref, reqEditors...) @@ -9562,6 +9645,32 @@ func ParseV1UpdateSslEnforcementConfigResponse(rsp *http.Response) (*V1UpdateSsl return response, nil } +// ParseV1GetProjectStatusResponse parses an HTTP response from a V1GetProjectStatusWithResponse call +func ParseV1GetProjectStatusResponse(rsp *http.Response) (*V1GetProjectStatusResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &V1GetProjectStatusResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest StatusResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseV1ListAllBucketsResponse parses an HTTP response from a V1ListAllBucketsWithResponse call func ParseV1ListAllBucketsResponse(rsp *http.Response) (*V1ListAllBucketsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/pkg/api/types.gen.go b/pkg/api/types.gen.go index c59dae1a5..662e8cf73 100644 --- a/pkg/api/types.gen.go +++ b/pkg/api/types.gen.go @@ -193,6 +193,24 @@ const ( SnippetResponseVisibilityUser SnippetResponseVisibility = "user" ) +// Defines values for StatusResponseStatus. +const ( + StatusResponseStatusACTIVEHEALTHY StatusResponseStatus = "ACTIVE_HEALTHY" + StatusResponseStatusACTIVEUNHEALTHY StatusResponseStatus = "ACTIVE_UNHEALTHY" + StatusResponseStatusCOMINGUP StatusResponseStatus = "COMING_UP" + StatusResponseStatusGOINGDOWN StatusResponseStatus = "GOING_DOWN" + StatusResponseStatusINACTIVE StatusResponseStatus = "INACTIVE" + StatusResponseStatusINITFAILED StatusResponseStatus = "INIT_FAILED" + StatusResponseStatusPAUSEFAILED StatusResponseStatus = "PAUSE_FAILED" + StatusResponseStatusPAUSING StatusResponseStatus = "PAUSING" + StatusResponseStatusREMOVED StatusResponseStatus = "REMOVED" + StatusResponseStatusRESTARTING StatusResponseStatus = "RESTARTING" + StatusResponseStatusRESTOREFAILED StatusResponseStatus = "RESTORE_FAILED" + StatusResponseStatusRESTORING StatusResponseStatus = "RESTORING" + StatusResponseStatusUNKNOWN StatusResponseStatus = "UNKNOWN" + StatusResponseStatusUPGRADING StatusResponseStatus = "UPGRADING" +) + // Defines values for SupavisorConfigResponseDatabaseType. const ( PRIMARY SupavisorConfigResponseDatabaseType = "PRIMARY" @@ -328,9 +346,9 @@ const ( // Defines values for V1ServiceHealthResponseStatus. const ( - V1ServiceHealthResponseStatusACTIVEHEALTHY V1ServiceHealthResponseStatus = "ACTIVE_HEALTHY" - V1ServiceHealthResponseStatusCOMINGUP V1ServiceHealthResponseStatus = "COMING_UP" - V1ServiceHealthResponseStatusUNHEALTHY V1ServiceHealthResponseStatus = "UNHEALTHY" + ACTIVEHEALTHY V1ServiceHealthResponseStatus = "ACTIVE_HEALTHY" + COMINGUP V1ServiceHealthResponseStatus = "COMING_UP" + UNHEALTHY V1ServiceHealthResponseStatus = "UNHEALTHY" ) // Defines values for VanitySubdomainConfigResponseStatus. @@ -1063,6 +1081,17 @@ type SslValidation struct { ValidationRecords []ValidationRecord `json:"validation_records"` } +// StatusResponse defines model for StatusResponse. +type StatusResponse struct { + Id float32 `json:"id"` + Name string `json:"name"` + Ref string `json:"ref"` + Status StatusResponseStatus `json:"status"` +} + +// StatusResponseStatus defines model for StatusResponse.Status. +type StatusResponseStatus string + // SubdomainAvailabilityResponse defines model for SubdomainAvailabilityResponse. type SubdomainAvailabilityResponse struct { Available bool `json:"available"` From e6b661f7c525e8db61fcecbb3dbe382505ee4fc3 Mon Sep 17 00:00:00 2001 From: avallete Date: Sat, 5 Oct 2024 00:15:14 +0200 Subject: [PATCH 2/5] fix: link color --- internal/link/link.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/link/link.go b/internal/link/link.go index 9ff9447ea..c8ff42083 100644 --- a/internal/link/link.go +++ b/internal/link/link.go @@ -248,7 +248,7 @@ func checkRemoteProjectStatus(ctx context.Context, projectRef string) error { return errors.New("Unexpected error retrieving remote project status: " + string(resp.Body)) } if resp.JSON200.Status == api.StatusResponseStatusINACTIVE { - return errors.New(fmt.Sprintf("Project is paused. An admin must unpause it from the Supabase dashboard at %s/project/%s", utils.Aqua(utils.GetSupabaseDashboardURL()), projectRef)) + return errors.New(fmt.Sprintf("Project is paused. An admin must unpause it from the Supabase dashboard at %s", utils.Aqua(fmt.Sprintf("%s/project/%s", utils.GetSupabaseDashboardURL(), projectRef)))) } if resp.JSON200.Status != api.StatusResponseStatusACTIVEHEALTHY { fmt.Fprintf(os.Stderr, "%s: Project status is %s instead of Active Healthy. Some operations might fail.\n", utils.Yellow("Warning"), resp.JSON200.Status) From 2ccb1924ca12b431e7d749060556ab0a5b269aab Mon Sep 17 00:00:00 2001 From: avallete Date: Sat, 5 Oct 2024 10:47:59 +0200 Subject: [PATCH 3/5] fix: testing --- internal/link/link_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/internal/link/link_test.go b/internal/link/link_test.go index f33961293..02e45be0f 100644 --- a/internal/link/link_test.go +++ b/internal/link/link_test.go @@ -48,6 +48,11 @@ func TestLinkCommand(t *testing.T) { helper.MockMigrationHistory(conn) // Flush pending mocks after test execution defer gock.OffAll() + // Mock project status + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + project + "/status"). + Reply(200). + JSON(api.StatusResponse{Status: api.StatusResponseStatusACTIVEHEALTHY}) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/api-keys"). Reply(200). @@ -119,6 +124,11 @@ func TestLinkCommand(t *testing.T) { fsys := afero.NewMemMapFs() // Flush pending mocks after test execution defer gock.OffAll() + // Mock project status + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + project + "/status"). + Reply(200). + JSON(api.StatusResponse{Status: api.StatusResponseStatusACTIVEHEALTHY}) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/api-keys"). Reply(200). @@ -159,6 +169,11 @@ func TestLinkCommand(t *testing.T) { fsys := afero.NewReadOnlyFs(afero.NewMemMapFs()) // Flush pending mocks after test execution defer gock.OffAll() + // Mock project status + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + project + "/status"). + Reply(200). + JSON(api.StatusResponse{Status: api.StatusResponseStatusACTIVEHEALTHY}) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/api-keys"). Reply(200). @@ -193,6 +208,22 @@ func TestLinkCommand(t *testing.T) { assert.NoError(t, err) assert.False(t, exists) }) + t.Run("throws error on project inactive", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewReadOnlyFs(afero.NewMemMapFs()) + // Flush pending mocks after test execution + defer gock.OffAll() + // Mock project status + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + project + "/status"). + Reply(200). + JSON(api.StatusResponse{Status: api.StatusResponseStatusINACTIVE}) + // Run test + err := Run(context.Background(), project, fsys) + // Check error + assert.ErrorContains(t, err, "Project is paused. An admin must unpause it from the Supabase dashboard") + assert.Empty(t, apitest.ListUnmatchedRequests()) + }) } func TestLinkPostgrest(t *testing.T) { From 813bc6462099c7999d5dd5a25e0eec402c9bed65 Mon Sep 17 00:00:00 2001 From: avallete Date: Sat, 5 Oct 2024 19:38:38 +0200 Subject: [PATCH 4/5] chore: use get project endpoint --- api/beta.yaml | 82 +++++--------- internal/link/link.go | 6 +- internal/link/link_test.go | 16 +-- pkg/api/client.gen.go | 218 ++++++++++++++++++------------------- pkg/api/types.gen.go | 35 +----- 5 files changed, 148 insertions(+), 209 deletions(-) diff --git a/api/beta.yaml b/api/beta.yaml index 66d88dd9e..90b8a420c 100644 --- a/api/beta.yaml +++ b/api/beta.yaml @@ -817,6 +817,31 @@ paths: security: - bearer: [] /v1/projects/{ref}: + get: + operationId: v1-get-project + summary: Gets a specific project status that belongs to the authenticated user + parameters: + - name: ref + required: true + in: path + description: Project ref + schema: + minLength: 20 + maxLength: 20 + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/V1ProjectResponse' + '500': + description: Failed to retrieve project status + tags: + - Projects + security: + - bearer: [] delete: operationId: v1-delete-a-project summary: Deletes the given project @@ -1726,32 +1751,6 @@ paths: - Auth security: - bearer: [] - /v1/projects/{ref}/status: - get: - operationId: v1-get-project-status - summary: Gets a specific project status that belongs to the authenticated user - parameters: - - name: ref - required: true - in: path - description: Project ref - schema: - minLength: 20 - maxLength: 20 - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/StatusResponse' - '500': - description: Failed to retrieve project status - tags: - - Projects - security: - - bearer: [] /v1/projects/{ref}/database/query: post: operationId: v1-run-a-query @@ -3129,37 +3128,6 @@ components: - id - ref - name - StatusResponse: - type: object - properties: - id: - type: number - ref: - type: string - name: - type: string - status: - enum: - - ACTIVE_HEALTHY - - ACTIVE_UNHEALTHY - - COMING_UP - - GOING_DOWN - - INACTIVE - - INIT_FAILED - - REMOVED - - RESTARTING - - UNKNOWN - - UPGRADING - - PAUSING - - RESTORING - - RESTORE_FAILED - - PAUSE_FAILED - type: string - required: - - id - - ref - - name - - status SecretResponse: type: object properties: diff --git a/internal/link/link.go b/internal/link/link.go index c8ff42083..5092fd7c8 100644 --- a/internal/link/link.go +++ b/internal/link/link.go @@ -240,17 +240,17 @@ func updatePoolerConfig(config api.SupavisorConfigResponse) { } func checkRemoteProjectStatus(ctx context.Context, projectRef string) error { - resp, err := utils.GetSupabase().V1GetProjectStatusWithResponse(ctx, projectRef) + resp, err := utils.GetSupabase().V1GetProjectWithResponse(ctx, projectRef) if err != nil { return errors.Errorf("failed to retrieve remote project status: %w", err) } if resp.JSON200 == nil { return errors.New("Unexpected error retrieving remote project status: " + string(resp.Body)) } - if resp.JSON200.Status == api.StatusResponseStatusINACTIVE { + if resp.JSON200.Status == api.V1ProjectResponseStatusINACTIVE { return errors.New(fmt.Sprintf("Project is paused. An admin must unpause it from the Supabase dashboard at %s", utils.Aqua(fmt.Sprintf("%s/project/%s", utils.GetSupabaseDashboardURL(), projectRef)))) } - if resp.JSON200.Status != api.StatusResponseStatusACTIVEHEALTHY { + if resp.JSON200.Status != api.V1ProjectResponseStatusACTIVEHEALTHY { fmt.Fprintf(os.Stderr, "%s: Project status is %s instead of Active Healthy. Some operations might fail.\n", utils.Yellow("Warning"), resp.JSON200.Status) } return nil diff --git a/internal/link/link_test.go b/internal/link/link_test.go index 02e45be0f..4b1b3dde6 100644 --- a/internal/link/link_test.go +++ b/internal/link/link_test.go @@ -50,9 +50,9 @@ func TestLinkCommand(t *testing.T) { defer gock.OffAll() // Mock project status gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + project + "/status"). + Get("/v1/projects/" + project). Reply(200). - JSON(api.StatusResponse{Status: api.StatusResponseStatusACTIVEHEALTHY}) + JSON(api.V1ProjectResponse{Status: api.V1ProjectResponseStatusACTIVEHEALTHY}) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/api-keys"). Reply(200). @@ -126,9 +126,9 @@ func TestLinkCommand(t *testing.T) { defer gock.OffAll() // Mock project status gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + project + "/status"). + Get("/v1/projects/" + project). Reply(200). - JSON(api.StatusResponse{Status: api.StatusResponseStatusACTIVEHEALTHY}) + JSON(api.V1ProjectResponse{Status: api.V1ProjectResponseStatusACTIVEHEALTHY}) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/api-keys"). Reply(200). @@ -171,9 +171,9 @@ func TestLinkCommand(t *testing.T) { defer gock.OffAll() // Mock project status gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + project + "/status"). + Get("/v1/projects/" + project). Reply(200). - JSON(api.StatusResponse{Status: api.StatusResponseStatusACTIVEHEALTHY}) + JSON(api.V1ProjectResponse{Status: api.V1ProjectResponseStatusACTIVEHEALTHY}) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/api-keys"). Reply(200). @@ -215,9 +215,9 @@ func TestLinkCommand(t *testing.T) { defer gock.OffAll() // Mock project status gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + project + "/status"). + Get("/v1/projects/" + project). Reply(200). - JSON(api.StatusResponse{Status: api.StatusResponseStatusINACTIVE}) + JSON(api.V1ProjectResponse{Status: api.V1ProjectResponseStatusINACTIVE}) // Run test err := Run(context.Background(), project, fsys) // Check error diff --git a/pkg/api/client.gen.go b/pkg/api/client.gen.go index a26848b62..151b764e7 100644 --- a/pkg/api/client.gen.go +++ b/pkg/api/client.gen.go @@ -136,6 +136,9 @@ type ClientInterface interface { // V1DeleteAProject request V1DeleteAProject(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1GetProject request + V1GetProject(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1GetProjectApiKeys request V1GetProjectApiKeys(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -337,9 +340,6 @@ type ClientInterface interface { V1UpdateSslEnforcementConfig(ctx context.Context, ref string, body V1UpdateSslEnforcementConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // V1GetProjectStatus request - V1GetProjectStatus(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) - // V1ListAllBuckets request V1ListAllBuckets(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -584,6 +584,18 @@ func (c *Client) V1DeleteAProject(ctx context.Context, ref string, reqEditors .. return c.Client.Do(req) } +func (c *Client) V1GetProject(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1GetProjectRequest(c.Server, ref) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) V1GetProjectApiKeys(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewV1GetProjectApiKeysRequest(c.Server, ref) if err != nil { @@ -1472,18 +1484,6 @@ func (c *Client) V1UpdateSslEnforcementConfig(ctx context.Context, ref string, b return c.Client.Do(req) } -func (c *Client) V1GetProjectStatus(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewV1GetProjectStatusRequest(c.Server, ref) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - func (c *Client) V1ListAllBuckets(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewV1ListAllBucketsRequest(c.Server, ref) if err != nil { @@ -2226,6 +2226,40 @@ func NewV1DeleteAProjectRequest(server string, ref string) (*http.Request, error return req, nil } +// NewV1GetProjectRequest generates requests for V1GetProject +func NewV1GetProjectRequest(server string, ref string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "ref", runtime.ParamLocationPath, ref) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/projects/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewV1GetProjectApiKeysRequest generates requests for V1GetProjectApiKeys func NewV1GetProjectApiKeysRequest(server string, ref string) (*http.Request, error) { var err error @@ -4602,40 +4636,6 @@ func NewV1UpdateSslEnforcementConfigRequestWithBody(server string, ref string, c return req, nil } -// NewV1GetProjectStatusRequest generates requests for V1GetProjectStatus -func NewV1GetProjectStatusRequest(server string, ref string) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "ref", runtime.ParamLocationPath, ref) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/v1/projects/%s/status", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - // NewV1ListAllBucketsRequest generates requests for V1ListAllBuckets func NewV1ListAllBucketsRequest(server string, ref string) (*http.Request, error) { var err error @@ -5176,6 +5176,9 @@ type ClientWithResponsesInterface interface { // V1DeleteAProjectWithResponse request V1DeleteAProjectWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1DeleteAProjectResponse, error) + // V1GetProjectWithResponse request + V1GetProjectWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1GetProjectResponse, error) + // V1GetProjectApiKeysWithResponse request V1GetProjectApiKeysWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1GetProjectApiKeysResponse, error) @@ -5377,9 +5380,6 @@ type ClientWithResponsesInterface interface { V1UpdateSslEnforcementConfigWithResponse(ctx context.Context, ref string, body V1UpdateSslEnforcementConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*V1UpdateSslEnforcementConfigResponse, error) - // V1GetProjectStatusWithResponse request - V1GetProjectStatusWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1GetProjectStatusResponse, error) - // V1ListAllBucketsWithResponse request V1ListAllBucketsWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1ListAllBucketsResponse, error) @@ -5705,6 +5705,28 @@ func (r V1DeleteAProjectResponse) StatusCode() int { return 0 } +type V1GetProjectResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *V1ProjectResponse +} + +// Status returns HTTPResponse.Status +func (r V1GetProjectResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r V1GetProjectResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type V1GetProjectApiKeysResponse struct { Body []byte HTTPResponse *http.Response @@ -6860,28 +6882,6 @@ func (r V1UpdateSslEnforcementConfigResponse) StatusCode() int { return 0 } -type V1GetProjectStatusResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *StatusResponse -} - -// Status returns HTTPResponse.Status -func (r V1GetProjectStatusResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r V1GetProjectStatusResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - type V1ListAllBucketsResponse struct { Body []byte HTTPResponse *http.Response @@ -7272,6 +7272,15 @@ func (c *ClientWithResponses) V1DeleteAProjectWithResponse(ctx context.Context, return ParseV1DeleteAProjectResponse(rsp) } +// V1GetProjectWithResponse request returning *V1GetProjectResponse +func (c *ClientWithResponses) V1GetProjectWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1GetProjectResponse, error) { + rsp, err := c.V1GetProject(ctx, ref, reqEditors...) + if err != nil { + return nil, err + } + return ParseV1GetProjectResponse(rsp) +} + // V1GetProjectApiKeysWithResponse request returning *V1GetProjectApiKeysResponse func (c *ClientWithResponses) V1GetProjectApiKeysWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1GetProjectApiKeysResponse, error) { rsp, err := c.V1GetProjectApiKeys(ctx, ref, reqEditors...) @@ -7917,15 +7926,6 @@ func (c *ClientWithResponses) V1UpdateSslEnforcementConfigWithResponse(ctx conte return ParseV1UpdateSslEnforcementConfigResponse(rsp) } -// V1GetProjectStatusWithResponse request returning *V1GetProjectStatusResponse -func (c *ClientWithResponses) V1GetProjectStatusWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1GetProjectStatusResponse, error) { - rsp, err := c.V1GetProjectStatus(ctx, ref, reqEditors...) - if err != nil { - return nil, err - } - return ParseV1GetProjectStatusResponse(rsp) -} - // V1ListAllBucketsWithResponse request returning *V1ListAllBucketsResponse func (c *ClientWithResponses) V1ListAllBucketsWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1ListAllBucketsResponse, error) { rsp, err := c.V1ListAllBuckets(ctx, ref, reqEditors...) @@ -8377,6 +8377,32 @@ func ParseV1DeleteAProjectResponse(rsp *http.Response) (*V1DeleteAProjectRespons return response, nil } +// ParseV1GetProjectResponse parses an HTTP response from a V1GetProjectWithResponse call +func ParseV1GetProjectResponse(rsp *http.Response) (*V1GetProjectResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &V1GetProjectResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest V1ProjectResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseV1GetProjectApiKeysResponse parses an HTTP response from a V1GetProjectApiKeysWithResponse call func ParseV1GetProjectApiKeysResponse(rsp *http.Response) (*V1GetProjectApiKeysResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -9645,32 +9671,6 @@ func ParseV1UpdateSslEnforcementConfigResponse(rsp *http.Response) (*V1UpdateSsl return response, nil } -// ParseV1GetProjectStatusResponse parses an HTTP response from a V1GetProjectStatusWithResponse call -func ParseV1GetProjectStatusResponse(rsp *http.Response) (*V1GetProjectStatusResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &V1GetProjectStatusResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest StatusResponse - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - } - - return response, nil -} - // ParseV1ListAllBucketsResponse parses an HTTP response from a V1ListAllBucketsWithResponse call func ParseV1ListAllBucketsResponse(rsp *http.Response) (*V1ListAllBucketsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/pkg/api/types.gen.go b/pkg/api/types.gen.go index 662e8cf73..c59dae1a5 100644 --- a/pkg/api/types.gen.go +++ b/pkg/api/types.gen.go @@ -193,24 +193,6 @@ const ( SnippetResponseVisibilityUser SnippetResponseVisibility = "user" ) -// Defines values for StatusResponseStatus. -const ( - StatusResponseStatusACTIVEHEALTHY StatusResponseStatus = "ACTIVE_HEALTHY" - StatusResponseStatusACTIVEUNHEALTHY StatusResponseStatus = "ACTIVE_UNHEALTHY" - StatusResponseStatusCOMINGUP StatusResponseStatus = "COMING_UP" - StatusResponseStatusGOINGDOWN StatusResponseStatus = "GOING_DOWN" - StatusResponseStatusINACTIVE StatusResponseStatus = "INACTIVE" - StatusResponseStatusINITFAILED StatusResponseStatus = "INIT_FAILED" - StatusResponseStatusPAUSEFAILED StatusResponseStatus = "PAUSE_FAILED" - StatusResponseStatusPAUSING StatusResponseStatus = "PAUSING" - StatusResponseStatusREMOVED StatusResponseStatus = "REMOVED" - StatusResponseStatusRESTARTING StatusResponseStatus = "RESTARTING" - StatusResponseStatusRESTOREFAILED StatusResponseStatus = "RESTORE_FAILED" - StatusResponseStatusRESTORING StatusResponseStatus = "RESTORING" - StatusResponseStatusUNKNOWN StatusResponseStatus = "UNKNOWN" - StatusResponseStatusUPGRADING StatusResponseStatus = "UPGRADING" -) - // Defines values for SupavisorConfigResponseDatabaseType. const ( PRIMARY SupavisorConfigResponseDatabaseType = "PRIMARY" @@ -346,9 +328,9 @@ const ( // Defines values for V1ServiceHealthResponseStatus. const ( - ACTIVEHEALTHY V1ServiceHealthResponseStatus = "ACTIVE_HEALTHY" - COMINGUP V1ServiceHealthResponseStatus = "COMING_UP" - UNHEALTHY V1ServiceHealthResponseStatus = "UNHEALTHY" + V1ServiceHealthResponseStatusACTIVEHEALTHY V1ServiceHealthResponseStatus = "ACTIVE_HEALTHY" + V1ServiceHealthResponseStatusCOMINGUP V1ServiceHealthResponseStatus = "COMING_UP" + V1ServiceHealthResponseStatusUNHEALTHY V1ServiceHealthResponseStatus = "UNHEALTHY" ) // Defines values for VanitySubdomainConfigResponseStatus. @@ -1081,17 +1063,6 @@ type SslValidation struct { ValidationRecords []ValidationRecord `json:"validation_records"` } -// StatusResponse defines model for StatusResponse. -type StatusResponse struct { - Id float32 `json:"id"` - Name string `json:"name"` - Ref string `json:"ref"` - Status StatusResponseStatus `json:"status"` -} - -// StatusResponseStatus defines model for StatusResponse.Status. -type StatusResponseStatus string - // SubdomainAvailabilityResponse defines model for SubdomainAvailabilityResponse. type SubdomainAvailabilityResponse struct { Available bool `json:"available"` From 8ff100c86d9472ad7c084e23236b0aad9bae5d61 Mon Sep 17 00:00:00 2001 From: avallete Date: Mon, 7 Oct 2024 10:05:21 +0200 Subject: [PATCH 5/5] chore: apply review suggestions --- api/beta.yaml | 4 ++-- internal/link/link.go | 14 ++++++++++---- internal/link/link_test.go | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/api/beta.yaml b/api/beta.yaml index 90b8a420c..cba870c1e 100644 --- a/api/beta.yaml +++ b/api/beta.yaml @@ -819,7 +819,7 @@ paths: /v1/projects/{ref}: get: operationId: v1-get-project - summary: Gets a specific project status that belongs to the authenticated user + summary: Gets a specific project that belongs to the authenticated user parameters: - name: ref required: true @@ -837,7 +837,7 @@ paths: schema: $ref: '#/components/schemas/V1ProjectResponse' '500': - description: Failed to retrieve project status + description: Failed to retrieve project tags: - Projects security: diff --git a/internal/link/link.go b/internal/link/link.go index 5092fd7c8..6edaf8764 100644 --- a/internal/link/link.go +++ b/internal/link/link.go @@ -247,11 +247,17 @@ func checkRemoteProjectStatus(ctx context.Context, projectRef string) error { if resp.JSON200 == nil { return errors.New("Unexpected error retrieving remote project status: " + string(resp.Body)) } - if resp.JSON200.Status == api.V1ProjectResponseStatusINACTIVE { - return errors.New(fmt.Sprintf("Project is paused. An admin must unpause it from the Supabase dashboard at %s", utils.Aqua(fmt.Sprintf("%s/project/%s", utils.GetSupabaseDashboardURL(), projectRef)))) - } - if resp.JSON200.Status != api.V1ProjectResponseStatusACTIVEHEALTHY { + + switch resp.JSON200.Status { + case api.V1ProjectResponseStatusINACTIVE: + utils.CmdSuggestion = fmt.Sprintf("An admin must unpause it from the Supabase dashboard at %s", utils.Aqua(fmt.Sprintf("%s/project/%s", utils.GetSupabaseDashboardURL(), projectRef))) + return errors.New("project is paused") + case api.V1ProjectResponseStatusACTIVEHEALTHY: + // Project is in the desired state, do nothing + return nil + default: fmt.Fprintf(os.Stderr, "%s: Project status is %s instead of Active Healthy. Some operations might fail.\n", utils.Yellow("Warning"), resp.JSON200.Status) } + return nil } diff --git a/internal/link/link_test.go b/internal/link/link_test.go index 4b1b3dde6..7e7fbe23a 100644 --- a/internal/link/link_test.go +++ b/internal/link/link_test.go @@ -221,7 +221,7 @@ func TestLinkCommand(t *testing.T) { // Run test err := Run(context.Background(), project, fsys) // Check error - assert.ErrorContains(t, err, "Project is paused. An admin must unpause it from the Supabase dashboard") + assert.ErrorContains(t, err, "project is paused") assert.Empty(t, apitest.ListUnmatchedRequests()) }) }