Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: check project status at link time #2730

Merged
merged 8 commits into from
Oct 8, 2024
Merged
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
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.