Skip to content

Commit e3d22ad

Browse files
authored
fix: skip linking database if password is missing (#4429)
* fix: mark database linking error as non-fatal * fix: skip pooler if db host is resolvable * chore: update unit tests * chore: unban all ip on failure to connect
1 parent 4089042 commit e3d22ad

File tree

9 files changed

+138
-145
lines changed

9 files changed

+138
-145
lines changed

internal/bans/get/get.go

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,15 @@ import (
44
"context"
55
"fmt"
66

7-
"github.com/go-errors/errors"
87
"github.com/spf13/afero"
9-
"github.com/supabase/cli/internal/utils"
8+
"github.com/supabase/cli/internal/utils/flags"
109
)
1110

1211
func Run(ctx context.Context, projectRef string, fsys afero.Fs) error {
13-
// 1. Sanity checks.
14-
// 2. get network bans
15-
{
16-
resp, err := utils.GetSupabase().V1ListAllNetworkBansWithResponse(ctx, projectRef)
17-
if err != nil {
18-
return errors.Errorf("failed to retrieve network bans: %w", err)
19-
}
20-
if resp.JSON201 == nil {
21-
return errors.New("Unexpected error retrieving network bans: " + string(resp.Body))
22-
}
23-
fmt.Printf("DB banned IPs: %+v\n", resp.JSON201.BannedIpv4Addresses)
24-
return nil
12+
ips, err := flags.ListNetworkBans(ctx, projectRef)
13+
if err != nil {
14+
return err
2515
}
16+
fmt.Printf("DB banned IPs: %+v\n", ips)
17+
return nil
2618
}

internal/bootstrap/bootstrap.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,10 @@ func Run(ctx context.Context, starter StarterTemplate, fsys afero.Fs, options ..
112112
return err
113113
}
114114
// 6. Push migrations
115-
config := flags.NewDbConfigWithPassword(ctx, flags.ProjectRef)
115+
config, err := flags.NewDbConfigWithPassword(ctx, flags.ProjectRef)
116+
if err != nil {
117+
fmt.Fprintln(os.Stderr, err)
118+
}
116119
if err := writeDotEnv(keys, config, fsys); err != nil {
117120
fmt.Fprintln(os.Stderr, "Failed to create .env file:", err)
118121
}

internal/branches/get/get.go

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func Run(ctx context.Context, branchId string, fsys afero.Fs) error {
2727
if err != nil {
2828
return err
2929
}
30-
pooler, err := getPoolerConfig(ctx, detail.Ref)
30+
pooler, err := utils.GetPoolerConfigPrimary(ctx, detail.Ref)
3131
if err != nil {
3232
return err
3333
}
@@ -81,22 +81,6 @@ func getBranchDetail(ctx context.Context, branchId string) (api.BranchDetailResp
8181
return *resp.JSON200, nil
8282
}
8383

84-
func getPoolerConfig(ctx context.Context, ref string) (api.SupavisorConfigResponse, error) {
85-
var result api.SupavisorConfigResponse
86-
resp, err := utils.GetSupabase().V1GetPoolerConfigWithResponse(ctx, ref)
87-
if err != nil {
88-
return result, errors.Errorf("failed to get pooler: %w", err)
89-
} else if resp.JSON200 == nil {
90-
return result, errors.Errorf("unexpected get pooler status %d: %s", resp.StatusCode(), string(resp.Body))
91-
}
92-
for _, config := range *resp.JSON200 {
93-
if config.DatabaseType == api.SupavisorConfigResponseDatabaseTypePRIMARY {
94-
return config, nil
95-
}
96-
}
97-
return result, errors.Errorf("primary database not found: %s", ref)
98-
}
99-
10084
func toStandardEnvs(detail api.BranchDetailResponse, pooler api.SupavisorConfigResponse, keys []api.ApiKeyResponse) map[string]string {
10185
direct := pgconn.Config{
10286
Host: detail.DbHost,

internal/link/link.go

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"github.com/supabase/cli/pkg/api"
1919
"github.com/supabase/cli/pkg/cast"
2020
cliConfig "github.com/supabase/cli/pkg/config"
21-
"github.com/supabase/cli/pkg/migration"
2221
"github.com/supabase/cli/pkg/queue"
2322
)
2423

@@ -36,8 +35,9 @@ func Run(ctx context.Context, projectRef string, skipPooler bool, fsys afero.Fs,
3635
LinkServices(ctx, projectRef, keys.ServiceRole, skipPooler, fsys)
3736

3837
// 2. Check database connection
39-
config := flags.NewDbConfigWithPassword(ctx, projectRef)
40-
if err := linkDatabase(ctx, config, fsys, options...); err != nil {
38+
if config, err := flags.NewDbConfigWithPassword(ctx, projectRef); err != nil {
39+
fmt.Fprintln(os.Stderr, utils.Yellow("WARN:"), err)
40+
} else if err := linkDatabase(ctx, config, fsys, options...); err != nil {
4141
return err
4242
}
4343

@@ -186,14 +186,7 @@ func linkDatabase(ctx context.Context, config pgconn.Config, fsys afero.Fs, opti
186186
}
187187
defer conn.Close(context.Background())
188188
updatePostgresConfig(conn)
189-
if err := linkStorageMigration(ctx, conn, fsys); err != nil {
190-
fmt.Fprintln(os.Stderr, err)
191-
}
192-
// If `schema_migrations` doesn't exist on the remote database, create it.
193-
if err := migration.CreateMigrationTable(ctx, conn); err != nil {
194-
return err
195-
}
196-
return migration.CreateSeedTable(ctx, conn)
189+
return linkStorageMigration(ctx, conn, fsys)
197190
}
198191

199192
func updatePostgresConfig(conn *pgx.Conn) {
@@ -207,18 +200,11 @@ func updatePostgresConfig(conn *pgx.Conn) {
207200
}
208201

209202
func linkPooler(ctx context.Context, projectRef string, fsys afero.Fs) error {
210-
resp, err := utils.GetSupabase().V1GetPoolerConfigWithResponse(ctx, projectRef)
203+
primary, err := utils.GetPoolerConfigPrimary(ctx, projectRef)
211204
if err != nil {
212-
return errors.Errorf("failed to get pooler config: %w", err)
213-
}
214-
if resp.JSON200 == nil {
215-
return errors.Errorf("%w: %s", tenant.ErrAuthToken, string(resp.Body))
216-
}
217-
for _, config := range *resp.JSON200 {
218-
if config.DatabaseType == api.SupavisorConfigResponseDatabaseTypePRIMARY {
219-
updatePoolerConfig(config)
220-
}
205+
return err
221206
}
207+
updatePoolerConfig(primary)
222208
return utils.WriteFile(utils.PoolerUrlPath, []byte(utils.Config.Db.Pooler.ConnectionString), fsys)
223209
}
224210

internal/link/link_test.go

Lines changed: 22 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@ import (
1515
"github.com/stretchr/testify/assert"
1616
"github.com/supabase/cli/internal/testing/apitest"
1717
"github.com/supabase/cli/internal/testing/fstest"
18-
"github.com/supabase/cli/internal/testing/helper"
1918
"github.com/supabase/cli/internal/utils"
2019
"github.com/supabase/cli/internal/utils/tenant"
2120
"github.com/supabase/cli/pkg/api"
22-
"github.com/supabase/cli/pkg/migration"
2321
"github.com/supabase/cli/pkg/pgtest"
2422
"github.com/zalando/go-keyring"
2523
)
@@ -44,15 +42,6 @@ func TestLinkCommand(t *testing.T) {
4442
t.Cleanup(fstest.MockStdin(t, "\n"))
4543
// Setup in-memory fs
4644
fsys := afero.NewMemMapFs()
47-
// Setup mock postgres
48-
conn := pgtest.NewConn()
49-
defer conn.Close(t)
50-
conn.Query(utils.SET_SESSION_ROLE).
51-
Reply("SET ROLE").
52-
Query(GET_LATEST_STORAGE_MIGRATION).
53-
Reply("SELECT 1", []any{"custom-metadata"})
54-
helper.MockMigrationHistory(conn)
55-
helper.MockSeedHistory(conn)
5645
// Flush pending mocks after test execution
5746
defer gock.OffAll()
5847
// Mock project status
@@ -119,7 +108,7 @@ func TestLinkCommand(t *testing.T) {
119108
Reply(200).
120109
BodyString(storage)
121110
// Run test
122-
err := Run(context.Background(), project, false, fsys, conn.Intercept)
111+
err := Run(context.Background(), project, false, fsys)
123112
// Check error
124113
assert.NoError(t, err)
125114
assert.Empty(t, apitest.ListUnmatchedRequests())
@@ -198,22 +187,13 @@ func TestLinkCommand(t *testing.T) {
198187
}
199188
})
200189
// Check error
201-
assert.ErrorContains(t, err, "hostname resolving error")
190+
assert.NoError(t, err)
202191
assert.Empty(t, apitest.ListUnmatchedRequests())
203192
})
204193

205194
t.Run("throws error on write failure", func(t *testing.T) {
206195
// Setup in-memory fs
207196
fsys := afero.NewReadOnlyFs(afero.NewMemMapFs())
208-
// Setup mock postgres
209-
conn := pgtest.NewConn()
210-
defer conn.Close(t)
211-
conn.Query(utils.SET_SESSION_ROLE).
212-
Reply("SET ROLE").
213-
Query(GET_LATEST_STORAGE_MIGRATION).
214-
Reply("SELECT 1", []any{"custom-metadata"})
215-
helper.MockMigrationHistory(conn)
216-
helper.MockSeedHistory(conn)
217197
// Flush pending mocks after test execution
218198
defer gock.OffAll()
219199
// Mock project status
@@ -269,7 +249,7 @@ func TestLinkCommand(t *testing.T) {
269249
Get("/v1/projects").
270250
ReplyError(errors.New("network error"))
271251
// Run test
272-
err := Run(context.Background(), project, false, fsys, conn.Intercept)
252+
err := Run(context.Background(), project, false, fsys)
273253
// Check error
274254
assert.ErrorContains(t, err, "operation not permitted")
275255
assert.Empty(t, apitest.ListUnmatchedRequests())
@@ -419,6 +399,23 @@ func TestLinkPostgrest(t *testing.T) {
419399
}
420400

421401
func TestLinkDatabase(t *testing.T) {
402+
t.Run("syncs storage migration", func(t *testing.T) {
403+
// Setup in-memory fs
404+
fsys := afero.NewMemMapFs()
405+
// Setup mock postgres
406+
conn := pgtest.NewConn()
407+
defer conn.Close(t)
408+
conn.Query(GET_LATEST_STORAGE_MIGRATION).
409+
Reply("SELECT 1", []any{"custom-metadata"})
410+
// Run test
411+
err := linkDatabase(context.Background(), dbConfig, fsys, conn.Intercept)
412+
// Check error
413+
assert.NoError(t, err)
414+
storage, err := afero.ReadFile(fsys, utils.StorageMigrationPath)
415+
assert.NoError(t, err)
416+
assert.Equal(t, []byte("custom-metadata"), storage)
417+
})
418+
422419
t.Run("throws error on connect failure", func(t *testing.T) {
423420
// Setup in-memory fs
424421
fsys := afero.NewMemMapFs()
@@ -438,8 +435,6 @@ func TestLinkDatabase(t *testing.T) {
438435
defer conn.Close(t)
439436
conn.Query(GET_LATEST_STORAGE_MIGRATION).
440437
Reply("SELECT 1", []any{"custom-metadata"})
441-
helper.MockMigrationHistory(conn)
442-
helper.MockSeedHistory(conn)
443438
// Run test
444439
err := linkDatabase(context.Background(), dbConfig, fsys, conn.Intercept)
445440
// Check error
@@ -461,8 +456,6 @@ func TestLinkDatabase(t *testing.T) {
461456
defer conn.Close(t)
462457
conn.Query(GET_LATEST_STORAGE_MIGRATION).
463458
Reply("SELECT 1", []any{"custom-metadata"})
464-
helper.MockMigrationHistory(conn)
465-
helper.MockSeedHistory(conn)
466459
// Run test
467460
err := linkDatabase(context.Background(), dbConfig, fsys, conn.Intercept)
468461
// Check error
@@ -481,18 +474,11 @@ func TestLinkDatabase(t *testing.T) {
481474
conn := pgtest.NewConn()
482475
defer conn.Close(t)
483476
conn.Query(GET_LATEST_STORAGE_MIGRATION).
484-
ReplyError(pgerrcode.InsufficientPrivilege, "permission denied for relation migrations").
485-
Query(migration.SET_LOCK_TIMEOUT).
486-
Query(migration.CREATE_VERSION_SCHEMA).
487-
Reply("CREATE SCHEMA").
488-
Query(migration.CREATE_VERSION_TABLE).
489-
ReplyError(pgerrcode.InsufficientPrivilege, "permission denied for relation supabase_migrations").
490-
Query(migration.ADD_STATEMENTS_COLUMN).
491-
Query(migration.ADD_NAME_COLUMN)
477+
ReplyError(pgerrcode.InsufficientPrivilege, "permission denied for relation migrations")
492478
// Run test
493479
err := linkDatabase(context.Background(), dbConfig, fsys, conn.Intercept)
494480
// Check error
495-
assert.ErrorContains(t, err, "ERROR: permission denied for relation supabase_migrations (SQLSTATE 42501)")
481+
assert.ErrorContains(t, err, "ERROR: permission denied for relation migrations (SQLSTATE 42501)")
496482
exists, err := afero.Exists(fsys, utils.StorageMigrationPath)
497483
assert.NoError(t, err)
498484
assert.False(t, exists)

internal/projects/create/create.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"github.com/spf13/afero"
1111
"github.com/spf13/viper"
1212
"github.com/supabase/cli/internal/utils"
13-
"github.com/supabase/cli/internal/utils/credentials"
1413
"github.com/supabase/cli/internal/utils/flags"
1514
"github.com/supabase/cli/pkg/api"
1615
)
@@ -30,9 +29,6 @@ func Run(ctx context.Context, params api.V1CreateProjectBody, fsys afero.Fs) err
3029

3130
flags.ProjectRef = resp.JSON201.Id
3231
viper.Set("DB_PASSWORD", params.DbPass)
33-
if err := credentials.StoreProvider.Set(flags.ProjectRef, params.DbPass); err != nil {
34-
fmt.Fprintln(os.Stderr, "Failed to save database password:", err)
35-
}
3632

3733
projectUrl := fmt.Sprintf("%s/project/%s", utils.GetSupabaseDashboardURL(), resp.JSON201.Id)
3834
fmt.Fprintf(os.Stderr, "Created a new project at %s\n", utils.Bold(projectUrl))

internal/utils/connect.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/jackc/pgx/v4"
1616
"github.com/spf13/viper"
1717
"github.com/supabase/cli/internal/debug"
18+
"github.com/supabase/cli/pkg/api"
1819
"github.com/supabase/cli/pkg/pgxv5"
1920
"golang.org/x/net/publicsuffix"
2021
)
@@ -43,6 +44,22 @@ func ToPostgresURL(config pgconn.Config) string {
4344
)
4445
}
4546

47+
func GetPoolerConfigPrimary(ctx context.Context, ref string) (api.SupavisorConfigResponse, error) {
48+
var result api.SupavisorConfigResponse
49+
resp, err := GetSupabase().V1GetPoolerConfigWithResponse(ctx, ref)
50+
if err != nil {
51+
return result, errors.Errorf("failed to get pooler: %w", err)
52+
} else if resp.JSON200 == nil {
53+
return result, errors.Errorf("unexpected get pooler status %d: %s", resp.StatusCode(), string(resp.Body))
54+
}
55+
for _, config := range *resp.JSON200 {
56+
if config.DatabaseType == api.SupavisorConfigResponseDatabaseTypePRIMARY {
57+
return config, nil
58+
}
59+
}
60+
return result, errors.Errorf("primary database not found: %s", ref)
61+
}
62+
4663
func GetPoolerConfig(projectRef string) *pgconn.Config {
4764
logger := GetDebugLogger()
4865
if len(Config.Db.Pooler.ConnectionString) == 0 {

0 commit comments

Comments
 (0)