coverage~bigbes/sr-ht-dolt3523280ccmd/doltsrht-migrate/main.go

Coverage
0.0% 0/52 statements
Δ
+0.0
Blob
205fd57
1 // Command doltsrht-migrate runs the dolt.sr.ht Postgres migrations. It is a thin
2 // single-service wrapper around git.sr.ht/~bitfehler/brant, modeled on
3 // sourcehut-migrate: the brant subcommands (up, down, current, list, stamp,
4 // validate, ping) apply the files under the migrations directory, and an extra
5 // `init` subcommand loads the full schema.sql in one shot and stamps the
6 // database to head (used for a fresh install instead of replaying every
7 // migration).
8 //
9 // The connection string comes from [dolt.sr.ht]connection-string unless
10 // overridden with --dsn. Migrations are read from ./migrations in a dev checkout
11 // or from the installed assets path (/usr/share/sourcehut/migrations/dolt.sr.ht)
12 // otherwise. The lib/pq "postgres" driver this module already links is used in
13 // preference to brant's default pgx driver.
14 package main
15
16 import (
17 "context"
18 "errors"
19 "fmt"
20 "log"
21 "os"
22 "path/filepath"
23
24 "git.sr.ht/~bitfehler/brant"
25 "git.sr.ht/~bitfehler/brant/cli"
26 "github.com/alexflint/go-arg"
27 _ "github.com/lib/pq" // registers the "postgres" database/sql driver
28 "github.com/vaughan0/go-ini"
29
30 "sourcecraft.dev/bigbes/sr-ht-core/config"
31 )
32
33 // serviceName is the SourceHut service identifier and config section name.
34 const serviceName = "dolt.sr.ht"
35
36 // driverName is the database/sql driver this binary links (lib/pq). It overrides
37 // brant's postgres-dialect default of "pgx", which this module does not import.
38 const driverName = "postgres"
39
40 // InitArgs configures the `init` subcommand: load the full DDL and stamp to head.
41 type InitArgs struct {
42 Schema string `arg:"--schema" default:"schema.sql" placeholder:"FILE" help:"schema file to initialize the database with"`
43 }
44
45 // Args embeds brant's CLI arguments (the up/down/... subcommands and shared
46 // flags such as --dir and --dsn) and adds the init subcommand and the -a
47 // migrate-on-upgrade gate.
48 type Args struct {
49 cli.Args
50 Init *InitArgs `arg:"subcommand:init" help:"initialize the database from the schema file and stamp to head"`
51 Auto bool `arg:"-a" help:"honor [dolt.sr.ht]migrate-on-upgrade; exit early when it is disabled"`
52 }
53
54 0 func (Args) Epilogue() string {
55 0 return "Use `<cmd> --help` for help with individual commands"
56 0 }
57
58 0 func main() {
59 0 var a Args
60 0 p := arg.MustParse(&a)
61 0 if p.Subcommand() == nil {
62 0 p.WriteHelp(os.Stderr)
63 0 os.Exit(1)
64 0 }
65
66 0 conf := config.LoadConfig()
67 0
68 0 if a.Auto && !config.GetBool(conf, serviceName, "migrate-on-upgrade", false) {
69 0 log.Printf("doltsrht-migrate: [%s]migrate-on-upgrade disabled, exiting", serviceName)
70 0 return
71 0 }
72
73 0 if a.DataSourceName == "" {
74 0 dsn, ok := conf.Get(serviceName, "connection-string")
75 0 if !ok || dsn == "" {
76 0 log.Fatalf("doltsrht-migrate: no [%s]connection-string configured", serviceName)
77 0 }
78 0 a.DataSourceName = dsn
79 }
80
81 // Use lib/pq's "postgres" driver rather than brant's default "pgx".
82 0 drv := driverName
83 0 a.Driver = &drv
84 0
85 0 resolvePaths(conf, &a)
86 0 log.Printf("doltsrht-migrate: loading migrations from %s", a.Directory)
87 0
88 0 if a.Init != nil {
89 0 log.Printf("doltsrht-migrate: initializing schema from %s", a.Init.Schema)
90 0 if err := initDatabase(&a); err != nil {
91 0 log.Fatalf("doltsrht-migrate: init failed: %v", err)
92 0 }
93 0 return
94 }
95
96 0 cli.RunWithArgs(&a.Args)
97 }
98
99 // resolvePaths picks the migrations directory and schema file when the user did
100 // not override --dir: a ./migrations directory in the working tree (dev
101 // checkout) wins, otherwise the installed assets path is used, mirroring
102 // sourcehut-migrate.
103 0 func resolvePaths(conf ini.File, a *Args) {
104 0 if a.Directory != "./migrations" {
105 0 return // user overrode --dir; respect it verbatim
106 0 }
107
108 0 info, err := os.Stat("migrations")
109 0 if err != nil && !errors.Is(err, os.ErrNotExist) {
110 0 log.Fatalf("doltsrht-migrate: checking ./migrations: %v", err)
111 0 }
112 0 if err == nil && info.IsDir() {
113 0 log.Println("doltsrht-migrate: found ./migrations, using it")
114 0 return
115 0 }
116
117 0 assetsDir := config.GetString(conf, "sr.ht", "assets", "/usr/share/sourcehut")
118 0 a.Directory = filepath.Join(assetsDir, "migrations", serviceName)
119 0 if a.Init != nil && a.Init.Schema == "schema.sql" {
120 0 a.Init.Schema = filepath.Join(assetsDir, serviceName+".sql")
121 0 }
122 }
123
124 // initDatabase applies the schema file wholesale and stamps the version table to
125 // head, so a fresh install skips replaying the incremental migrations.
126 0 func initDatabase(a *Args) error {
127 0 p, err := cli.ProviderFromArgs(&a.Args)
128 0 if err != nil {
129 0 return fmt.Errorf("creating provider: %w", err)
130 0 }
131 0 defer p.Close()
132 0
133 0 statements, err := os.ReadFile(a.Init.Schema)
134 0 if err != nil {
135 0 return fmt.Errorf("reading schema %s: %w", a.Init.Schema, err)
136 0 }
137
138 0 db, err := p.DB()
139 0 if err != nil {
140 0 return fmt.Errorf("connecting to database: %w", err)
141 0 }
142 0 if _, err := db.Exec(string(statements)); err != nil {
143 0 return fmt.Errorf("executing %s: %w", a.Init.Schema, err)
144 0 }
145
146 0 if err := p.Stamp(context.Background(), brant.VERSION_HEAD, false); err != nil {
147 0 return fmt.Errorf("stamping database: %w", err)
148 0 }
149 0 return nil
150 }