Skip to content

Commit

Permalink
feat: check project status at link time (#2730)
Browse files Browse the repository at this point in the history
* feat: check project status at link time

* fix: link color

* fix: testing

* chore: use get project endpoint

* chore: apply review suggestions
  • Loading branch information
avallete authored Oct 8, 2024
1 parent 52dfb4d commit ce26abd
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 0 deletions.
25 changes: 25 additions & 0 deletions api/beta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,31 @@ paths:
security:
- bearer: []
/v1/projects/{ref}:
get:
operationId: v1-get-project
summary: Gets a specific project 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
tags:
- Projects
security:
- bearer: []
delete:
operationId: v1-delete-a-project
summary: Deletes the given project
Expand Down
28 changes: 28 additions & 0 deletions internal/link/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -236,3 +241,26 @@ 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().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))
}

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
}
31 changes: 31 additions & 0 deletions internal/link/link_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ func TestLinkCommand(t *testing.T) {
helper.MockSeedHistory(conn)
// Flush pending mocks after test execution
defer gock.OffAll()
// Mock project status
gock.New(utils.DefaultApiHost).
Get("/v1/projects/" + project).
Reply(200).
JSON(api.V1ProjectResponse{Status: api.V1ProjectResponseStatusACTIVEHEALTHY})
gock.New(utils.DefaultApiHost).
Get("/v1/projects/" + project + "/api-keys").
Reply(200).
Expand Down Expand Up @@ -120,6 +125,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).
Reply(200).
JSON(api.V1ProjectResponse{Status: api.V1ProjectResponseStatusACTIVEHEALTHY})
gock.New(utils.DefaultApiHost).
Get("/v1/projects/" + project + "/api-keys").
Reply(200).
Expand Down Expand Up @@ -160,6 +170,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).
Reply(200).
JSON(api.V1ProjectResponse{Status: api.V1ProjectResponseStatusACTIVEHEALTHY})
gock.New(utils.DefaultApiHost).
Get("/v1/projects/" + project + "/api-keys").
Reply(200).
Expand Down Expand Up @@ -194,6 +209,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).
Reply(200).
JSON(api.V1ProjectResponse{Status: api.V1ProjectResponseStatusINACTIVE})
// Run test
err := Run(context.Background(), project, fsys)
// Check error
assert.ErrorContains(t, err, "project is paused")
assert.Empty(t, apitest.ListUnmatchedRequests())
})
}

func TestLinkPostgrest(t *testing.T) {
Expand Down
109 changes: 109 additions & 0 deletions pkg/api/client.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit ce26abd

Please sign in to comment.