Skip to content

Commit fca7fd0

Browse files
authored
fix: pin storage migration instead of version (#3475)
1 parent f56c35d commit fca7fd0

File tree

8 files changed

+56
-52
lines changed

8 files changed

+56
-52
lines changed

internal/db/start/start.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ func initStorageJob(host string) utils.DockerJob {
305305
Image: utils.Config.Storage.Image,
306306
Env: []string{
307307
"DB_INSTALL_ROLES=false",
308+
"DB_MIGRATIONS_FREEZE_AT=" + utils.Config.Storage.TargetMigration,
308309
"ANON_KEY=" + utils.Config.Auth.AnonKey.Value,
309310
"SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey.Value,
310311
"PGRST_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,

internal/link/link.go

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func Run(ctx context.Context, projectRef string, fsys afero.Fs, options ...func(
4545
// 2. Check database connection
4646
config := flags.GetDbConfigOptionalPassword(projectRef)
4747
if len(config.Password) > 0 {
48-
if err := linkDatabase(ctx, config, options...); err != nil {
48+
if err := linkDatabase(ctx, config, fsys, options...); err != nil {
4949
return err
5050
}
5151
// Save database password
@@ -76,7 +76,7 @@ func Run(ctx context.Context, projectRef string, fsys afero.Fs, options ...func(
7676
func LinkServices(ctx context.Context, projectRef, anonKey string, fsys afero.Fs) {
7777
// Ignore non-fatal errors linking services
7878
var wg sync.WaitGroup
79-
wg.Add(8)
79+
wg.Add(7)
8080
go func() {
8181
defer wg.Done()
8282
if err := linkDatabaseSettings(ctx, projectRef); err != nil && viper.GetBool("DEBUG") {
@@ -120,12 +120,6 @@ func LinkServices(ctx context.Context, projectRef, anonKey string, fsys afero.Fs
120120
fmt.Fprintln(os.Stderr, err)
121121
}
122122
}()
123-
go func() {
124-
defer wg.Done()
125-
if err := linkStorageVersion(ctx, api, fsys); err != nil && viper.GetBool("DEBUG") {
126-
fmt.Fprintln(os.Stderr, err)
127-
}
128-
}()
129123
wg.Wait()
130124
}
131125

@@ -178,12 +172,14 @@ func linkStorage(ctx context.Context, projectRef string) error {
178172
return nil
179173
}
180174

181-
func linkStorageVersion(ctx context.Context, api tenant.TenantAPI, fsys afero.Fs) error {
182-
version, err := api.GetStorageVersion(ctx)
183-
if err != nil {
184-
return err
175+
const GET_LATEST_STORAGE_MIGRATION = "SELECT name FROM storage.migrations ORDER BY id DESC LIMIT 1"
176+
177+
func linkStorageVersion(ctx context.Context, conn *pgx.Conn, fsys afero.Fs) error {
178+
var name string
179+
if err := conn.QueryRow(ctx, GET_LATEST_STORAGE_MIGRATION).Scan(&name); err != nil {
180+
return errors.Errorf("failed to fetch storage migration: %w", err)
185181
}
186-
return utils.WriteFile(utils.StorageVersionPath, []byte(version), fsys)
182+
return utils.WriteFile(utils.StorageVersionPath, []byte(name), fsys)
187183
}
188184

189185
func linkDatabaseSettings(ctx context.Context, projectRef string) error {
@@ -197,13 +193,16 @@ func linkDatabaseSettings(ctx context.Context, projectRef string) error {
197193
return nil
198194
}
199195

200-
func linkDatabase(ctx context.Context, config pgconn.Config, options ...func(*pgx.ConnConfig)) error {
196+
func linkDatabase(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
201197
conn, err := utils.ConnectByConfig(ctx, config, options...)
202198
if err != nil {
203199
return err
204200
}
205201
defer conn.Close(context.Background())
206202
updatePostgresConfig(conn)
203+
if err := linkStorageVersion(ctx, conn, fsys); err != nil {
204+
fmt.Fprintln(os.Stderr, err)
205+
}
207206
// If `schema_migrations` doesn't exist on the remote database, create it.
208207
if err := migration.CreateMigrationTable(ctx, conn); err != nil {
209208
return err

internal/link/link_test.go

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ func TestLinkCommand(t *testing.T) {
4646
// Setup mock postgres
4747
conn := pgtest.NewConn()
4848
defer conn.Close(t)
49+
conn.Query(GET_LATEST_STORAGE_MIGRATION).
50+
Reply("SELECT 1", []interface{}{"custom-metadata"})
4951
helper.MockMigrationHistory(conn)
5052
helper.MockSeedHistory(conn)
5153
// Flush pending mocks after test execution
@@ -98,10 +100,6 @@ func TestLinkCommand(t *testing.T) {
98100
Get("/rest/v1/").
99101
Reply(200).
100102
JSON(rest)
101-
gock.New("https://" + utils.GetSupabaseHost(project)).
102-
Get("/storage/v1/version").
103-
Reply(200).
104-
BodyString("0.40.4")
105103
// Run test
106104
err := Run(context.Background(), project, fsys, conn.Intercept)
107105
// Check error
@@ -163,9 +161,6 @@ func TestLinkCommand(t *testing.T) {
163161
gock.New("https://" + utils.GetSupabaseHost(project)).
164162
Get("/rest/v1/").
165163
ReplyError(errors.New("network error"))
166-
gock.New("https://" + utils.GetSupabaseHost(project)).
167-
Get("/storage/v1/version").
168-
ReplyError(errors.New("network error"))
169164
// Run test
170165
err := Run(context.Background(), project, fsys, func(cc *pgx.ConnConfig) {
171166
cc.LookupFunc = func(ctx context.Context, host string) (addrs []string, err error) {
@@ -217,9 +212,6 @@ func TestLinkCommand(t *testing.T) {
217212
gock.New("https://" + utils.GetSupabaseHost(project)).
218213
Get("/rest/v1/").
219214
ReplyError(errors.New("network error"))
220-
gock.New("https://" + utils.GetSupabaseHost(project)).
221-
Get("/storage/v1/version").
222-
ReplyError(errors.New("network error"))
223215
gock.New(utils.DefaultApiHost).
224216
Get("/v1/projects").
225217
ReplyError(errors.New("network error"))
@@ -237,6 +229,8 @@ func TestLinkCommand(t *testing.T) {
237229

238230
func TestStatusCheck(t *testing.T) {
239231
project := "test-project"
232+
token := apitest.RandomAccessToken(t)
233+
t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
240234

241235
t.Run("updates postgres version when healthy", func(t *testing.T) {
242236
// Setup in-memory fs
@@ -372,59 +366,81 @@ func TestLinkPostgrest(t *testing.T) {
372366

373367
func TestLinkDatabase(t *testing.T) {
374368
t.Run("throws error on connect failure", func(t *testing.T) {
369+
// Setup in-memory fs
370+
fsys := afero.NewMemMapFs()
375371
// Run test
376-
err := linkDatabase(context.Background(), pgconn.Config{})
372+
err := linkDatabase(context.Background(), pgconn.Config{}, fsys)
377373
// Check error
378374
assert.ErrorContains(t, err, "invalid port (outside range)")
379375
})
380376

381377
t.Run("ignores missing server version", func(t *testing.T) {
378+
// Setup in-memory fs
379+
fsys := afero.NewMemMapFs()
382380
// Setup mock postgres
383381
conn := pgtest.NewWithStatus(map[string]string{
384382
"standard_conforming_strings": "on",
385383
})
386384
defer conn.Close(t)
385+
conn.Query(GET_LATEST_STORAGE_MIGRATION).
386+
Reply("SELECT 1", []interface{}{"custom-metadata"})
387387
helper.MockMigrationHistory(conn)
388388
helper.MockSeedHistory(conn)
389389
// Run test
390-
err := linkDatabase(context.Background(), dbConfig, conn.Intercept)
390+
err := linkDatabase(context.Background(), dbConfig, fsys, conn.Intercept)
391391
// Check error
392392
assert.NoError(t, err)
393+
version, err := afero.ReadFile(fsys, utils.StorageVersionPath)
394+
assert.NoError(t, err)
395+
assert.Equal(t, "custom-metadata", string(version))
393396
})
394397

395398
t.Run("updates config to newer db version", func(t *testing.T) {
396399
utils.Config.Db.MajorVersion = 14
400+
// Setup in-memory fs
401+
fsys := afero.NewMemMapFs()
397402
// Setup mock postgres
398403
conn := pgtest.NewWithStatus(map[string]string{
399404
"standard_conforming_strings": "on",
400405
"server_version": "15.0",
401406
})
402407
defer conn.Close(t)
408+
conn.Query(GET_LATEST_STORAGE_MIGRATION).
409+
Reply("SELECT 1", []interface{}{"custom-metadata"})
403410
helper.MockMigrationHistory(conn)
404411
helper.MockSeedHistory(conn)
405412
// Run test
406-
err := linkDatabase(context.Background(), dbConfig, conn.Intercept)
413+
err := linkDatabase(context.Background(), dbConfig, fsys, conn.Intercept)
407414
// Check error
408415
assert.NoError(t, err)
409-
utils.Config.Db.MajorVersion = 15
410416
assert.Equal(t, uint(15), utils.Config.Db.MajorVersion)
417+
version, err := afero.ReadFile(fsys, utils.StorageVersionPath)
418+
assert.NoError(t, err)
419+
assert.Equal(t, "custom-metadata", string(version))
411420
})
412421

413422
t.Run("throws error on query failure", func(t *testing.T) {
414423
utils.Config.Db.MajorVersion = 14
424+
// Setup in-memory fs
425+
fsys := afero.NewMemMapFs()
415426
// Setup mock postgres
416427
conn := pgtest.NewConn()
417428
defer conn.Close(t)
418-
conn.Query(migration.SET_LOCK_TIMEOUT).
429+
conn.Query(GET_LATEST_STORAGE_MIGRATION).
430+
ReplyError(pgerrcode.InsufficientPrivilege, "permission denied for relation migrations").
431+
Query(migration.SET_LOCK_TIMEOUT).
419432
Query(migration.CREATE_VERSION_SCHEMA).
420433
Reply("CREATE SCHEMA").
421434
Query(migration.CREATE_VERSION_TABLE).
422435
ReplyError(pgerrcode.InsufficientPrivilege, "permission denied for relation supabase_migrations").
423436
Query(migration.ADD_STATEMENTS_COLUMN).
424437
Query(migration.ADD_NAME_COLUMN)
425438
// Run test
426-
err := linkDatabase(context.Background(), dbConfig, conn.Intercept)
439+
err := linkDatabase(context.Background(), dbConfig, fsys, conn.Intercept)
427440
// Check error
428441
assert.ErrorContains(t, err, "ERROR: permission denied for relation supabase_migrations (SQLSTATE 42501)")
442+
exists, err := afero.Exists(fsys, utils.StorageVersionPath)
443+
assert.NoError(t, err)
444+
assert.False(t, exists)
429445
})
430446
}

internal/start/start.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import (
3131
"github.com/supabase/cli/internal/utils"
3232
"github.com/supabase/cli/internal/utils/flags"
3333
"github.com/supabase/cli/pkg/config"
34-
"golang.org/x/mod/semver"
3534
)
3635

3736
func Run(ctx context.Context, fsys afero.Fs, excludedContainers []string, ignoreHealthCheck bool) error {
@@ -90,18 +89,10 @@ type kongConfig struct {
9089
ApiPort uint16
9190
}
9291

93-
// TODO: deprecate after removing storage headers from kong
94-
func StorageVersionBelow(target string) bool {
95-
parts := strings.Split(utils.Config.Storage.Image, ":v")
96-
return semver.Compare(parts[len(parts)-1], target) < 0
97-
}
98-
9992
var (
10093
//go:embed templates/kong.yml
10194
kongConfigEmbed string
102-
kongConfigTemplate = template.Must(template.New("kongConfig").Funcs(template.FuncMap{
103-
"StorageVersionBelow": StorageVersionBelow,
104-
}).Parse(kongConfigEmbed))
95+
kongConfigTemplate = template.Must(template.New("kongConfig").Parse(kongConfigEmbed))
10596

10697
//go:embed templates/custom_nginx.template
10798
nginxConfigEmbed string
@@ -838,6 +829,7 @@ EOF
838829
container.Config{
839830
Image: utils.Config.Storage.Image,
840831
Env: []string{
832+
"DB_MIGRATIONS_FREEZE_AT=" + utils.Config.Storage.TargetMigration,
841833
"ANON_KEY=" + utils.Config.Auth.AnonKey.Value,
842834
"SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey.Value,
843835
"AUTH_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
@@ -856,7 +848,6 @@ EOF
856848
"S3_PROTOCOL_ACCESS_KEY_ID=" + utils.Config.Storage.S3Credentials.AccessKeyId,
857849
"S3_PROTOCOL_ACCESS_KEY_SECRET=" + utils.Config.Storage.S3Credentials.SecretAccessKey,
858850
"S3_PROTOCOL_PREFIX=/storage/v1",
859-
fmt.Sprintf("S3_ALLOW_FORWARDED_HEADER=%v", StorageVersionBelow("1.10.1")),
860851
"UPLOAD_FILE_SIZE_LIMIT=52428800000",
861852
"UPLOAD_FILE_SIZE_LIMIT_STANDARD=5242880000",
862853
},

internal/start/templates/kong.yml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,6 @@ services:
119119
- /storage/v1/
120120
plugins:
121121
- name: cors
122-
{{if StorageVersionBelow "1.10.1" }}
123-
- name: request-transformer
124-
config:
125-
add:
126-
headers:
127-
- "Forwarded: host={{ .ApiHost }}:{{ .ApiPort }};proto=http"
128-
{{end}}
129122
- name: pg-meta
130123
_comment: "pg-meta: /pg/* -> http://pg-meta:8080/*"
131124
url: http://{{ .PgmetaId }}:8080/

pkg/config/config.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -615,13 +615,16 @@ func (c *config) Load(path string, fsys fs.FS) error {
615615
if version, err := fs.ReadFile(fsys, builder.RestVersionPath); err == nil && len(version) > 0 {
616616
c.Api.Image = replaceImageTag(Images.Postgrest, string(version))
617617
}
618-
if version, err := fs.ReadFile(fsys, builder.StorageVersionPath); err == nil && len(version) > 0 {
619-
c.Storage.Image = replaceImageTag(Images.Storage, string(version))
620-
}
621618
if version, err := fs.ReadFile(fsys, builder.GotrueVersionPath); err == nil && len(version) > 0 {
622619
c.Auth.Image = replaceImageTag(Images.Gotrue, string(version))
623620
}
624621
}
622+
if version, err := fs.ReadFile(fsys, builder.StorageVersionPath); err == nil && len(version) > 0 {
623+
// For backwards compatibility, exclude all strings that look like semver
624+
if v := strings.TrimSpace(string(version)); !semver.IsValid(v) {
625+
c.Storage.TargetMigration = v
626+
}
627+
}
625628
if version, err := fs.ReadFile(fsys, builder.EdgeRuntimeVersionPath); err == nil && len(version) > 0 {
626629
c.EdgeRuntime.Image = replaceImageTag(Images.EdgeRuntime, string(version))
627630
}

pkg/config/storage.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type (
1010
storage struct {
1111
Enabled bool `toml:"enabled"`
1212
Image string `toml:"-"`
13+
TargetMigration string `toml:"-"`
1314
ImgProxyImage string `toml:"-"`
1415
FileSizeLimit sizeInBytes `toml:"file_size_limit"`
1516
ImageTransformation *imageTransformation `toml:"image_transformation"`

pkg/config/templates/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ FROM timberio/vector:0.28.1-alpine AS vector
1212
FROM supabase/supavisor:2.5.1 AS supavisor
1313
FROM supabase/gotrue:v2.171.0 AS gotrue
1414
FROM supabase/realtime:v2.34.47 AS realtime
15-
FROM supabase/storage-api:v1.22.5 AS storage
15+
FROM supabase/storage-api:v1.22.6 AS storage
1616
FROM supabase/logflare:1.12.0 AS logflare
1717
# Append to JobImages when adding new dependencies below
1818
FROM supabase/pgadmin-schema-diff:cli-0.0.5 AS differ

0 commit comments

Comments
 (0)