github.com/Redstoneguy129/cli@v0.0.0-20230211220159-15dca4e91917/internal/db/remote/commit/commit.go (about) 1 package commit 2 3 import ( 4 "context" 5 _ "embed" 6 "errors" 7 "fmt" 8 "net/url" 9 "path/filepath" 10 11 "github.com/Redstoneguy129/cli/internal/db/diff" 12 "github.com/Redstoneguy129/cli/internal/db/dump" 13 "github.com/Redstoneguy129/cli/internal/migration/list" 14 "github.com/Redstoneguy129/cli/internal/migration/repair" 15 "github.com/Redstoneguy129/cli/internal/utils" 16 "github.com/jackc/pgx/v4" 17 "github.com/spf13/afero" 18 ) 19 20 func Run(ctx context.Context, schema []string, username, password, database string, fsys afero.Fs) error { 21 // Sanity checks. 22 { 23 if err := utils.AssertDockerIsRunning(ctx); err != nil { 24 return err 25 } 26 if err := utils.LoadConfigFS(fsys); err != nil { 27 return err 28 } 29 } 30 31 if err := utils.RunProgram(ctx, func(p utils.Program, ctx context.Context) error { 32 return run(p, ctx, schema, username, password, database, fsys) 33 }); err != nil { 34 return err 35 } 36 37 fmt.Println("Finished " + utils.Aqua("supabase db remote commit") + ".") 38 return nil 39 } 40 41 func run(p utils.Program, ctx context.Context, schema []string, username, password, database string, fsys afero.Fs) error { 42 projectRef, err := utils.LoadProjectRef(fsys) 43 if err != nil { 44 return err 45 } 46 host := utils.GetSupabaseDbHost(projectRef) 47 48 // 1. Assert `supabase/migrations` and `schema_migrations` are in sync. 49 p.Send(utils.StatusMsg("Connecting to remote database...")) 50 conn, err := utils.ConnectRemotePostgres(ctx, username, password, database, host) 51 if err != nil { 52 return err 53 } 54 defer conn.Close(context.Background()) 55 if err := AssertRemoteInSync(ctx, conn, fsys); err != nil { 56 return err 57 } 58 59 // 2. Fetch remote schema changes 60 if len(schema) == 0 { 61 schema, err = diff.LoadUserSchemas(ctx, conn) 62 if err != nil { 63 return err 64 } 65 } 66 timestamp := utils.GetCurrentTimestamp() 67 if err := fetchRemote(p, ctx, schema, timestamp, username, password, database, host, fsys); err != nil { 68 return err 69 } 70 71 // 3. Insert a row to `schema_migrations` 72 _, err = conn.Exec(ctx, repair.INSERT_MIGRATION_VERSION, timestamp) 73 return err 74 } 75 76 func fetchRemote(p utils.Program, ctx context.Context, schema []string, timestamp, username, password, database, host string, fsys afero.Fs) error { 77 path := filepath.Join(utils.MigrationsDir, timestamp+"_remote_commit.sql") 78 // Special case if this is the first migration 79 if migrations, err := list.LoadLocalMigrations(fsys); err != nil { 80 return err 81 } else if len(migrations) == 0 { 82 p.Send(utils.StatusMsg("Committing initial migration on remote database...")) 83 return dump.Run(ctx, path, username, password, database, host, false, fsys) 84 } 85 86 w := utils.StatusWriter{Program: p} 87 // Diff remote db (source) & shadow db (target) and write it as a new migration. 88 target := fmt.Sprintf("postgresql://%s@%s:6543/postgres", url.UserPassword(database, password), host) 89 output, err := diff.DiffDatabase(ctx, schema, target, w, fsys) 90 if err != nil { 91 return err 92 } 93 if len(output) == 0 { 94 return errors.New("no changes found") 95 } 96 return afero.WriteFile(fsys, path, []byte(output), 0644) 97 } 98 99 func AssertRemoteInSync(ctx context.Context, conn *pgx.Conn, fsys afero.Fs) error { 100 remoteMigrations, err := list.LoadRemoteMigrations(ctx, conn) 101 if err != nil { 102 return err 103 } 104 localMigrations, err := list.LoadLocalMigrations(fsys) 105 if err != nil { 106 return err 107 } 108 109 conflictErr := errors.New("The remote database's migration history is not in sync with the contents of " + utils.Bold(utils.MigrationsDir) + `. Resolve this by: 110 - Updating the project from version control to get the latest ` + utils.Bold(utils.MigrationsDir) + `, 111 - Pushing unapplied migrations with ` + utils.Aqua("supabase db push") + `, 112 - Or failing that, manually editing supabase_migrations.schema_migrations table with ` + utils.Aqua("supabase migration repair") + ".") 113 if len(remoteMigrations) != len(localMigrations) { 114 return conflictErr 115 } 116 117 for i, remoteTimestamp := range remoteMigrations { 118 // LoadLocalMigrations guarantees we always have a match 119 localTimestamp := utils.MigrateFilePattern.FindStringSubmatch(localMigrations[i])[1] 120 if localTimestamp != remoteTimestamp { 121 return conflictErr 122 } 123 } 124 125 return nil 126 }