diff --git a/internal/link/link.go b/internal/link/link.go index 26d336f44..73635aea8 100644 --- a/internal/link/link.go +++ b/internal/link/link.go @@ -7,13 +7,11 @@ import ( "os" "strconv" "strings" - "sync" "github.com/go-errors/errors" "github.com/jackc/pgconn" "github.com/jackc/pgx/v4" "github.com/spf13/afero" - "github.com/spf13/viper" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" "github.com/supabase/cli/internal/utils/tenant" @@ -21,6 +19,7 @@ import ( "github.com/supabase/cli/pkg/cast" cliConfig "github.com/supabase/cli/pkg/config" "github.com/supabase/cli/pkg/migration" + "github.com/supabase/cli/pkg/queue" ) func Run(ctx context.Context, projectRef string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error { @@ -60,59 +59,20 @@ major_version = %d } func LinkServices(ctx context.Context, projectRef, serviceKey string, fsys afero.Fs) { - // Ignore non-fatal errors linking services - var wg sync.WaitGroup - wg.Add(8) - go func() { - defer wg.Done() - if err := linkDatabaseSettings(ctx, projectRef); err != nil && viper.GetBool("DEBUG") { - fmt.Fprintln(os.Stderr, err) - } - }() - go func() { - defer wg.Done() - if err := linkNetworkRestrictions(ctx, projectRef); err != nil && viper.GetBool("DEBUG") { - fmt.Fprintln(os.Stderr, err) - } - }() - go func() { - defer wg.Done() - if err := linkPostgrest(ctx, projectRef); err != nil && viper.GetBool("DEBUG") { - fmt.Fprintln(os.Stderr, err) - } - }() - go func() { - defer wg.Done() - if err := linkGotrue(ctx, projectRef); err != nil && viper.GetBool("DEBUG") { - fmt.Fprintln(os.Stderr, err) - } - }() - go func() { - defer wg.Done() - if err := linkStorage(ctx, projectRef); err != nil && viper.GetBool("DEBUG") { - fmt.Fprintln(os.Stderr, err) - } - }() - go func() { - defer wg.Done() - if err := linkPooler(ctx, projectRef, fsys); err != nil && viper.GetBool("DEBUG") { - fmt.Fprintln(os.Stderr, err) - } - }() + jq := queue.NewJobQueue(5) + logger := utils.GetDebugLogger() + fmt.Fprintln(logger, jq.Put(func() error { return linkDatabaseSettings(ctx, projectRef) })) + fmt.Fprintln(logger, jq.Put(func() error { return linkNetworkRestrictions(ctx, projectRef) })) + fmt.Fprintln(logger, jq.Put(func() error { return linkPostgrest(ctx, projectRef) })) + fmt.Fprintln(logger, jq.Put(func() error { return linkGotrue(ctx, projectRef) })) + fmt.Fprintln(logger, jq.Put(func() error { return linkStorage(ctx, projectRef) })) + fmt.Fprintln(logger, jq.Put(func() error { return linkPooler(ctx, projectRef, fsys) })) api := tenant.NewTenantAPI(ctx, projectRef, serviceKey) - go func() { - defer wg.Done() - if err := linkPostgrestVersion(ctx, api, fsys); err != nil && viper.GetBool("DEBUG") { - fmt.Fprintln(os.Stderr, err) - } - }() - go func() { - defer wg.Done() - if err := linkGotrueVersion(ctx, api, fsys); err != nil && viper.GetBool("DEBUG") { - fmt.Fprintln(os.Stderr, err) - } - }() - wg.Wait() + fmt.Fprintln(logger, jq.Put(func() error { return linkPostgrestVersion(ctx, api, fsys) })) + fmt.Fprintln(logger, jq.Put(func() error { return linkGotrueVersion(ctx, api, fsys) })) + fmt.Fprintln(logger, jq.Put(func() error { return linkStorageVersion(ctx, api, fsys) })) + // Ignore non-fatal errors linking services + fmt.Fprintln(logger, jq.Collect()) } func linkPostgrest(ctx context.Context, projectRef string) error { @@ -164,14 +124,23 @@ func linkStorage(ctx context.Context, projectRef string) error { return nil } +func linkStorageVersion(ctx context.Context, api tenant.TenantAPI, fsys afero.Fs) error { + version, err := api.GetStorageVersion(ctx) + if err != nil { + return err + } + fmt.Fprintln(os.Stderr, version) + return utils.WriteFile(utils.StorageVersionPath, []byte(version), fsys) +} + const GET_LATEST_STORAGE_MIGRATION = "SELECT name FROM storage.migrations ORDER BY id DESC LIMIT 1" -func linkStorageVersion(ctx context.Context, conn *pgx.Conn, fsys afero.Fs) error { +func linkStorageMigration(ctx context.Context, conn *pgx.Conn, fsys afero.Fs) error { var name string if err := conn.QueryRow(ctx, GET_LATEST_STORAGE_MIGRATION).Scan(&name); err != nil { return errors.Errorf("failed to fetch storage migration: %w", err) } - return utils.WriteFile(utils.StorageVersionPath, []byte(name), fsys) + return utils.WriteFile(utils.StorageMigrationPath, []byte(name), fsys) } func linkDatabaseSettings(ctx context.Context, projectRef string) error { @@ -203,7 +172,7 @@ func linkDatabase(ctx context.Context, config pgconn.Config, fsys afero.Fs, opti } defer conn.Close(context.Background()) updatePostgresConfig(conn) - if err := linkStorageVersion(ctx, conn, fsys); err != nil { + if err := linkStorageMigration(ctx, conn, fsys); err != nil { fmt.Fprintln(os.Stderr, err) } // If `schema_migrations` doesn't exist on the remote database, create it. diff --git a/internal/link/link_test.go b/internal/link/link_test.go index 8b473d9de..aff34d937 100644 --- a/internal/link/link_test.go +++ b/internal/link/link_test.go @@ -113,6 +113,11 @@ func TestLinkCommand(t *testing.T) { Get("/rest/v1/"). Reply(200). JSON(rest) + storage := "1.28.0" + gock.New("https://" + utils.GetSupabaseHost(project)). + Get("/storage/v1/version"). + Reply(200). + BodyString(storage) // Run test err := Run(context.Background(), project, fsys, conn.Intercept) // Check error @@ -128,6 +133,9 @@ func TestLinkCommand(t *testing.T) { authVersion, err := afero.ReadFile(fsys, utils.GotrueVersionPath) assert.NoError(t, err) assert.Equal(t, []byte(auth.Version), authVersion) + storageVersion, err := afero.ReadFile(fsys, utils.StorageVersionPath) + assert.NoError(t, err) + assert.Equal(t, []byte("v"+storage), storageVersion) postgresVersion, err := afero.ReadFile(fsys, utils.PostgresVersionPath) assert.NoError(t, err) assert.Equal(t, []byte(mockPostgres.Database.Version), postgresVersion) @@ -180,6 +188,9 @@ func TestLinkCommand(t *testing.T) { gock.New("https://" + utils.GetSupabaseHost(project)). Get("/rest/v1/"). ReplyError(errors.New("network error")) + gock.New("https://" + utils.GetSupabaseHost(project)). + Get("/storage/v1/version"). + ReplyError(errors.New("network error")) // Run test err := Run(context.Background(), project, fsys, func(cc *pgx.ConnConfig) { cc.LookupFunc = func(ctx context.Context, host string) (addrs []string, err error) { @@ -251,6 +262,9 @@ func TestLinkCommand(t *testing.T) { gock.New("https://" + utils.GetSupabaseHost(project)). Get("/rest/v1/"). ReplyError(errors.New("network error")) + gock.New("https://" + utils.GetSupabaseHost(project)). + Get("/storage/v1/version"). + ReplyError(errors.New("network error")) gock.New(utils.DefaultApiHost). Get("/v1/projects"). ReplyError(errors.New("network error")) @@ -430,7 +444,7 @@ func TestLinkDatabase(t *testing.T) { err := linkDatabase(context.Background(), dbConfig, fsys, conn.Intercept) // Check error assert.NoError(t, err) - version, err := afero.ReadFile(fsys, utils.StorageVersionPath) + version, err := afero.ReadFile(fsys, utils.StorageMigrationPath) assert.NoError(t, err) assert.Equal(t, "custom-metadata", string(version)) }) @@ -454,7 +468,7 @@ func TestLinkDatabase(t *testing.T) { // Check error assert.NoError(t, err) assert.Equal(t, uint(15), utils.Config.Db.MajorVersion) - version, err := afero.ReadFile(fsys, utils.StorageVersionPath) + version, err := afero.ReadFile(fsys, utils.StorageMigrationPath) assert.NoError(t, err) assert.Equal(t, "custom-metadata", string(version)) }) @@ -479,7 +493,7 @@ func TestLinkDatabase(t *testing.T) { err := linkDatabase(context.Background(), dbConfig, fsys, conn.Intercept) // Check error assert.ErrorContains(t, err, "ERROR: permission denied for relation supabase_migrations (SQLSTATE 42501)") - exists, err := afero.Exists(fsys, utils.StorageVersionPath) + exists, err := afero.Exists(fsys, utils.StorageMigrationPath) assert.NoError(t, err) assert.False(t, exists) }) diff --git a/internal/utils/misc.go b/internal/utils/misc.go index 84d65eaeb..f81b792f5 100644 --- a/internal/utils/misc.go +++ b/internal/utils/misc.go @@ -69,6 +69,7 @@ var ( GotrueVersionPath = filepath.Join(TempDir, "gotrue-version") RestVersionPath = filepath.Join(TempDir, "rest-version") StorageVersionPath = filepath.Join(TempDir, "storage-version") + StorageMigrationPath = filepath.Join(TempDir, "storage-migration") StudioVersionPath = filepath.Join(TempDir, "studio-version") PgmetaVersionPath = filepath.Join(TempDir, "pgmeta-version") PoolerVersionPath = filepath.Join(TempDir, "pooler-version") diff --git a/pkg/config/config.go b/pkg/config/config.go index 915327e41..f909d4e88 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -607,11 +607,14 @@ func (c *config) Load(path string, fsys fs.FS) error { } } if version, err := fs.ReadFile(fsys, builder.StorageVersionPath); err == nil && len(version) > 0 { - // For backwards compatibility, exclude all strings that look like semver - if v := strings.TrimSpace(string(version)); !semver.IsValid(v) { - c.Storage.TargetMigration = v + // Only replace image if local storage version is newer + if i := strings.IndexByte(Images.Storage, ':'); VersionCompare(string(version), Images.Storage[i+1:]) > 0 { + c.Storage.Image = replaceImageTag(Images.Storage, string(version)) } } + if version, err := fs.ReadFile(fsys, builder.StorageMigrationPath); err == nil && len(version) > 0 { + c.Storage.TargetMigration = string(version) + } if version, err := fs.ReadFile(fsys, builder.EdgeRuntimeVersionPath); err == nil && len(version) > 0 { c.EdgeRuntime.Image = replaceImageTag(Images.EdgeRuntime, string(version)) } diff --git a/pkg/config/utils.go b/pkg/config/utils.go index b8ddeeb4d..829ec67a1 100644 --- a/pkg/config/utils.go +++ b/pkg/config/utils.go @@ -21,6 +21,7 @@ type pathBuilder struct { GotrueVersionPath string RestVersionPath string StorageVersionPath string + StorageMigrationPath string StudioVersionPath string PgmetaVersionPath string PoolerVersionPath string @@ -55,6 +56,7 @@ func NewPathBuilder(configPath string) pathBuilder { GotrueVersionPath: filepath.Join(base, ".temp", "gotrue-version"), RestVersionPath: filepath.Join(base, ".temp", "rest-version"), StorageVersionPath: filepath.Join(base, ".temp", "storage-version"), + StorageMigrationPath: filepath.Join(base, ".temp", "storage-migration"), EdgeRuntimeVersionPath: filepath.Join(base, ".temp", "edge-runtime-version"), StudioVersionPath: filepath.Join(base, ".temp", "studio-version"), PgmetaVersionPath: filepath.Join(base, ".temp", "pgmeta-version"),