supabase-cli/internal/db/push/push.go

131 lines
4.1 KiB
Go

package push
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/go-errors/errors"
"github.com/jackc/pgconn"
"github.com/jackc/pgx/v4"
"github.com/spf13/afero"
"github.com/supabase/cli/internal/migration/up"
"github.com/supabase/cli/internal/utils"
"github.com/supabase/cli/internal/utils/flags"
"github.com/supabase/cli/pkg/migration"
"github.com/supabase/cli/pkg/vault"
)
func Run(ctx context.Context, dryRun, ignoreVersionMismatch bool, includeRoles, includeSeed bool, config pgconn.Config, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
if dryRun {
fmt.Fprintln(os.Stderr, "DRY RUN: migrations will *not* be pushed to the database.")
}
conn, err := utils.ConnectByConfig(ctx, config, options...)
if err != nil {
return err
}
defer conn.Close(context.Background())
pending, err := up.GetPendingMigrations(ctx, ignoreVersionMismatch, conn, fsys)
if err != nil {
return err
}
var seeds []migration.SeedFile
if includeSeed {
// TODO: flag should override config but we don't resolve glob paths when seed is disabled.
if !utils.Config.Db.Seed.Enabled {
fmt.Fprintln(os.Stderr, "Skipping seed because it is disabled in config.toml for project:", flags.ProjectRef)
} else if seeds, err = migration.GetPendingSeeds(ctx, utils.Config.Db.Seed.SqlPaths, conn, afero.NewIOFS(fsys)); err != nil {
return err
}
}
var globals []string
if includeRoles {
if exists, err := afero.Exists(fsys, utils.CustomRolesPath); err != nil {
return errors.Errorf("failed to find custom roles: %w", err)
} else if exists {
globals = append(globals, utils.CustomRolesPath)
}
}
if len(pending) == 0 && len(seeds) == 0 && len(globals) == 0 {
fmt.Println("Remote database is up to date.")
return nil
}
// Push pending migrations
if dryRun {
if len(globals) > 0 {
fmt.Fprintln(os.Stderr, "Would create custom roles "+utils.Bold(globals[0])+"...")
}
if len(pending) > 0 {
fmt.Fprintln(os.Stderr, "Would push these migrations:")
fmt.Fprint(os.Stderr, confirmPushAll(pending))
}
if len(seeds) > 0 {
fmt.Fprintln(os.Stderr, "Would seed these files:")
fmt.Fprint(os.Stderr, confirmSeedAll(seeds))
}
} else {
if len(globals) > 0 {
msg := "Do you want to create custom roles in the database cluster?"
if shouldPush, err := utils.NewConsole().PromptYesNo(ctx, msg, true); err != nil {
return err
} else if !shouldPush {
return errors.New(context.Canceled)
}
if err := migration.SeedGlobals(ctx, globals, conn, afero.NewIOFS(fsys)); err != nil {
return err
}
}
if len(pending) > 0 {
msg := fmt.Sprintf("Do you want to push these migrations to the remote database?\n%s\n", confirmPushAll(pending))
if shouldPush, err := utils.NewConsole().PromptYesNo(ctx, msg, true); err != nil {
return err
} else if !shouldPush {
return errors.New(context.Canceled)
}
if err := vault.UpsertVaultSecrets(ctx, utils.Config.Db.Vault, conn); err != nil {
return err
}
if err := migration.ApplyMigrations(ctx, pending, conn, afero.NewIOFS(fsys)); err != nil {
return err
}
} else {
fmt.Fprintln(os.Stderr, "Schema migrations are up to date.")
}
if len(seeds) > 0 {
msg := fmt.Sprintf("Do you want to seed the remote database with these files?\n%s\n", confirmSeedAll(seeds))
if shouldPush, err := utils.NewConsole().PromptYesNo(ctx, msg, true); err != nil {
return err
} else if !shouldPush {
return errors.New(context.Canceled)
}
if err := migration.SeedData(ctx, seeds, conn, afero.NewIOFS(fsys)); err != nil {
return err
}
} else if includeSeed {
fmt.Fprintln(os.Stderr, "Seed files are up to date.")
}
}
fmt.Println("Finished " + utils.Aqua("supabase db push") + ".")
return nil
}
func confirmPushAll(pending []string) (msg string) {
for _, path := range pending {
filename := filepath.Base(path)
msg += fmt.Sprintf(" • %s\n", utils.Bold(filename))
}
return msg
}
func confirmSeedAll(pending []migration.SeedFile) (msg string) {
for _, seed := range pending {
notice := seed.Path
if seed.Dirty {
notice += " (hash update)"
}
msg += fmt.Sprintf(" • %s\n", utils.Bold(notice))
}
return msg
}