diff --git a/cmd/start.go b/cmd/start.go index bd1cc1f8a..2a91dbee4 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -40,6 +40,7 @@ var ( excludedContainers []string ignoreHealthCheck bool preview bool + skipSeed bool startCmd = &cobra.Command{ GroupID: groupLocalDev, @@ -47,7 +48,7 @@ var ( Short: "Start containers for Supabase local development", RunE: func(cmd *cobra.Command, args []string) error { validateExcludedContainers(excludedContainers) - return start.Run(cmd.Context(), afero.NewOsFs(), excludedContainers, ignoreHealthCheck) + return start.Run(cmd.Context(), afero.NewOsFs(), excludedContainers, ignoreHealthCheck, skipSeed) }, } ) @@ -58,6 +59,7 @@ func init() { flags.StringSliceVarP(&excludedContainers, "exclude", "x", []string{}, "Names of containers to not start. ["+names+"]") flags.BoolVar(&ignoreHealthCheck, "ignore-health-check", false, "Ignore unhealthy services and exit 0") flags.BoolVar(&preview, "preview", false, "Connect to feature preview branch") + flags.BoolVar(&skipSeed, "no-seed", false, "Skip storage seeding on startup") cobra.CheckErr(flags.MarkHidden("preview")) rootCmd.AddCommand(startCmd) } diff --git a/internal/start/start.go b/internal/start/start.go index bcc2cde48..bd37816c4 100644 --- a/internal/start/start.go +++ b/internal/start/start.go @@ -43,7 +43,7 @@ import ( "github.com/supabase/cli/pkg/config" ) -func Run(ctx context.Context, fsys afero.Fs, excludedContainers []string, ignoreHealthCheck bool) error { +func Run(ctx context.Context, fsys afero.Fs, excludedContainers []string, ignoreHealthCheck bool, skipSeed bool) error { // Sanity checks. { if err := flags.LoadConfig(fsys); err != nil { @@ -68,7 +68,7 @@ func Run(ctx context.Context, fsys afero.Fs, excludedContainers []string, ignore Password: utils.Config.Db.Password, Database: "postgres", } - if err := run(ctx, fsys, excludedContainers, dbConfig); err != nil { + if err := run(ctx, fsys, excludedContainers, dbConfig, skipSeed); err != nil { if ignoreHealthCheck && start.IsUnhealthyError(err) { fmt.Fprintln(os.Stderr, err) } else { @@ -212,7 +212,7 @@ func pullImagesUsingCompose(ctx context.Context, project types.Project) error { return service.Pull(ctx, &project, api.PullOptions{IgnoreFailures: true}) } -func run(ctx context.Context, fsys afero.Fs, excludedContainers []string, dbConfig pgconn.Config, options ...func(*pgx.ConnConfig)) error { +func run(ctx context.Context, fsys afero.Fs, excludedContainers []string, dbConfig pgconn.Config, skipSeed bool, options ...func(*pgx.ConnConfig)) error { excluded := make(map[string]bool) for _, name := range excludedContainers { excluded[name] = true @@ -249,7 +249,6 @@ func run(ctx context.Context, fsys afero.Fs, excludedContainers []string, dbConf utils.Config.Storage.ImageTransformation.Enabled && !isContainerExcluded(utils.Config.Storage.ImgProxyImage, excluded) isS3ProtocolEnabled := utils.Config.Storage.S3Protocol != nil && utils.Config.Storage.S3Protocol.Enabled fmt.Fprintln(os.Stderr, "Starting containers...") - workdir, err := os.Getwd() if err != nil { return errors.Errorf("failed to get working directory: %w", err) @@ -1278,14 +1277,23 @@ EOF } fmt.Fprintln(os.Stderr, "Waiting for health checks...") + if utils.NoBackupVolume && slices.Contains(started, utils.StorageId) { if err := start.WaitForHealthyService(ctx, serviceTimeout, utils.StorageId); err != nil { return err } - // Disable prompts when seeding - if err := buckets.Run(ctx, "", false, fsys); err != nil { - return err + + // Follow db.seed.enabled by default, allow --no-seed as override + if skipSeed { + fmt.Fprintln(os.Stderr, "Skipping storage seeding (--no-seed enabled)") + } else if !utils.Config.Db.Seed.Enabled { + fmt.Fprintln(os.Stderr, "Skipping storage seeding (db.seed.enabled = false)") + } else { + if err := buckets.Run(ctx, "", false, fsys); err != nil { + return err + } } + } return start.WaitForHealthyService(ctx, serviceTimeout, started...) } diff --git a/internal/start/start_test.go b/internal/start/start_test.go index a90d7a719..b1412f02e 100644 --- a/internal/start/start_test.go +++ b/internal/start/start_test.go @@ -31,7 +31,7 @@ func TestStartCommand(t *testing.T) { fsys := afero.NewMemMapFs() require.NoError(t, afero.WriteFile(fsys, utils.ConfigPath, []byte("malformed"), 0644)) // Run test - err := Run(context.Background(), fsys, []string{}, false) + err := Run(context.Background(), fsys, []string{}, false, false) // Check error assert.ErrorContains(t, err, "toml: expected = after a key, but the document ends there") }) @@ -47,7 +47,7 @@ func TestStartCommand(t *testing.T) { Get("/v" + utils.Docker.ClientVersion() + "/containers"). ReplyError(errors.New("network error")) // Run test - err := Run(context.Background(), fsys, []string{}, false) + err := Run(context.Background(), fsys, []string{}, false, false) // Check error assert.ErrorContains(t, err, "network error") assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -84,7 +84,7 @@ func TestStartCommand(t *testing.T) { Reply(http.StatusOK). JSON(running) // Run test - err := Run(context.Background(), fsys, []string{}, false) + err := Run(context.Background(), fsys, []string{}, false, false) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -202,7 +202,7 @@ func TestDatabaseStart(t *testing.T) { Reply(http.StatusOK). JSON([]storage.BucketResponse{}) // Run test - err := run(context.Background(), fsys, []string{}, pgconn.Config{Host: utils.DbId}, conn.Intercept) + err := run(context.Background(), fsys, []string{}, pgconn.Config{Host: utils.DbId}, false, conn.Intercept) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -248,11 +248,102 @@ func TestDatabaseStart(t *testing.T) { // Run test exclude := ExcludableContainers() exclude = append(exclude, "invalid", exclude[0]) - err := run(context.Background(), fsys, exclude, pgconn.Config{Host: utils.DbId}) + err := run(context.Background(), fsys, exclude, pgconn.Config{Host: utils.DbId}, false) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) }) + + t.Run("skips storage seeding when db.seed.enabled is false", func(t *testing.T) { + utils.Config.Analytics.Enabled = false + utils.Config.Api.Enabled = false + utils.Config.Auth.Enabled = false + utils.Config.Realtime.Enabled = false + utils.Config.Studio.Enabled = false + utils.Config.EdgeRuntime.Enabled = false + utils.Config.Inbucket.Enabled = false + utils.Config.Db.Pooler.Enabled = false + + fsys := afero.NewMemMapFs() + + // Restore global state + origSeed := utils.Config.Db.Seed.Enabled + t.Cleanup(func() { + utils.Config.Db.Seed.Enabled = origSeed + }) + utils.Config.Db.Seed.Enabled = false + + require.NoError(t, apitest.MockDocker(utils.Docker)) + defer gock.OffAll() + + gock.New(utils.Docker.DaemonHost()). + Head("/_ping"). + Reply(http.StatusOK) + + gock.New(utils.Docker.DaemonHost()). + Post("/v" + utils.Docker.ClientVersion() + "/networks/create"). + Reply(http.StatusCreated). + JSON(network.CreateResponse{}) + + // 🔑 REQUIRED: cache all images + for _, img := range config.Images.Services() { + service := utils.GetRegistryImageUrl(img) + gock.New(utils.Docker.DaemonHost()). + Get("/v" + utils.Docker.ClientVersion() + "/images/" + service + "/json"). + Reply(http.StatusOK). + JSON(image.InspectResponse{}) + } + + // ALSO mock postgres image (DB is not part of Services()) + dbImage := utils.GetRegistryImageUrl(utils.Config.Db.Image) + gock.New(utils.Docker.DaemonHost()). + Get("/v" + utils.Docker.ClientVersion() + "/images/" + dbImage + "/json"). + Reply(http.StatusOK). + JSON(image.InspectResponse{}) + + utils.DbId = "test-postgres" + utils.Config.Db.Port = 54322 + utils.Config.Db.MajorVersion = 15 + + gock.New(utils.Docker.DaemonHost()). + Get("/v" + utils.Docker.ClientVersion() + "/volumes/" + utils.DbId). + Reply(http.StatusOK). + JSON(volume.Volume{}) + + apitest.MockDockerStart( + utils.Docker, + utils.GetRegistryImageUrl(utils.Config.Db.Image), + utils.DbId, + ) + + gock.New(utils.Docker.DaemonHost()). + Get("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/json"). + Reply(http.StatusOK). + JSON(container.InspectResponse{ + ContainerJSONBase: &container.ContainerJSONBase{ + State: &container.State{ + Running: true, + Health: &container.Health{Status: types.Healthy}, + }, + }, + }) + exclude := []string{ + utils.ShortContainerImageName(utils.Config.Api.KongImage), + utils.ShortContainerImageName(utils.Config.Storage.Image), + } + + err := run( + context.Background(), + fsys, + exclude, + pgconn.Config{Host: utils.DbId}, + false, + ) + + assert.NoError(t, err) + assert.Empty(t, apitest.ListUnmatchedRequests()) + }) + } func TestFormatMapForEnvConfig(t *testing.T) {