~bigbes/sr-ht-dolt

ref: 7a77409cf6f612b291637017fc571292d645f13a sr-ht-dolt/cmd/doltsrht-migrate/main.go -rw-r--r-- 4.8 KiB
7a77409c — Eugene Blikh mcpsrv: add the generic browse tools 5 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// Command doltsrht-migrate runs the dolt.sr.ht Postgres migrations. It is a thin
// single-service wrapper around git.sr.ht/~bitfehler/brant, modeled on
// sourcehut-migrate: the brant subcommands (up, down, current, list, stamp,
// validate, ping) apply the files under the migrations directory, and an extra
// `init` subcommand loads the full schema.sql in one shot and stamps the
// database to head (used for a fresh install instead of replaying every
// migration).
//
// The connection string comes from [dolt.sr.ht]connection-string unless
// overridden with --dsn. Migrations are read from ./migrations in a dev checkout
// or from the installed assets path (/usr/share/sourcehut/migrations/dolt.sr.ht)
// otherwise. The lib/pq "postgres" driver this module already links is used in
// preference to brant's default pgx driver.
package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"os"
	"path/filepath"

	"git.sr.ht/~bitfehler/brant"
	"git.sr.ht/~bitfehler/brant/cli"
	"github.com/alexflint/go-arg"
	_ "github.com/lib/pq" // registers the "postgres" database/sql driver
	"github.com/vaughan0/go-ini"

	"sourcecraft.dev/bigbes/sr-ht-core/config"
)

// serviceName is the SourceHut service identifier and config section name.
const serviceName = "dolt.sr.ht"

// driverName is the database/sql driver this binary links (lib/pq). It overrides
// brant's postgres-dialect default of "pgx", which this module does not import.
const driverName = "postgres"

// InitArgs configures the `init` subcommand: load the full DDL and stamp to head.
type InitArgs struct {
	Schema string `arg:"--schema" default:"schema.sql" placeholder:"FILE" help:"schema file to initialize the database with"`
}

// Args embeds brant's CLI arguments (the up/down/... subcommands and shared
// flags such as --dir and --dsn) and adds the init subcommand and the -a
// migrate-on-upgrade gate.
type Args struct {
	cli.Args
	Init *InitArgs `arg:"subcommand:init" help:"initialize the database from the schema file and stamp to head"`
	Auto bool      `arg:"-a" help:"honor [dolt.sr.ht]migrate-on-upgrade; exit early when it is disabled"`
}

func (Args) Epilogue() string {
	return "Use `<cmd> --help` for help with individual commands"
}

func main() {
	var a Args
	p := arg.MustParse(&a)
	if p.Subcommand() == nil {
		p.WriteHelp(os.Stderr)
		os.Exit(1)
	}

	conf := config.LoadConfig()

	if a.Auto && !config.GetBool(conf, serviceName, "migrate-on-upgrade", false) {
		log.Printf("doltsrht-migrate: [%s]migrate-on-upgrade disabled, exiting", serviceName)
		return
	}

	if a.DataSourceName == "" {
		dsn, ok := conf.Get(serviceName, "connection-string")
		if !ok || dsn == "" {
			log.Fatalf("doltsrht-migrate: no [%s]connection-string configured", serviceName)
		}
		a.DataSourceName = dsn
	}

	// Use lib/pq's "postgres" driver rather than brant's default "pgx".
	drv := driverName
	a.Driver = &drv

	resolvePaths(conf, &a)
	log.Printf("doltsrht-migrate: loading migrations from %s", a.Directory)

	if a.Init != nil {
		log.Printf("doltsrht-migrate: initializing schema from %s", a.Init.Schema)
		if err := initDatabase(&a); err != nil {
			log.Fatalf("doltsrht-migrate: init failed: %v", err)
		}
		return
	}

	cli.RunWithArgs(&a.Args)
}

// resolvePaths picks the migrations directory and schema file when the user did
// not override --dir: a ./migrations directory in the working tree (dev
// checkout) wins, otherwise the installed assets path is used, mirroring
// sourcehut-migrate.
func resolvePaths(conf ini.File, a *Args) {
	if a.Directory != "./migrations" {
		return // user overrode --dir; respect it verbatim
	}

	info, err := os.Stat("migrations")
	if err != nil && !errors.Is(err, os.ErrNotExist) {
		log.Fatalf("doltsrht-migrate: checking ./migrations: %v", err)
	}
	if err == nil && info.IsDir() {
		log.Println("doltsrht-migrate: found ./migrations, using it")
		return
	}

	assetsDir := config.GetString(conf, "sr.ht", "assets", "/usr/share/sourcehut")
	a.Directory = filepath.Join(assetsDir, "migrations", serviceName)
	if a.Init != nil && a.Init.Schema == "schema.sql" {
		a.Init.Schema = filepath.Join(assetsDir, serviceName+".sql")
	}
}

// initDatabase applies the schema file wholesale and stamps the version table to
// head, so a fresh install skips replaying the incremental migrations.
func initDatabase(a *Args) error {
	p, err := cli.ProviderFromArgs(&a.Args)
	if err != nil {
		return fmt.Errorf("creating provider: %w", err)
	}
	defer p.Close()

	statements, err := os.ReadFile(a.Init.Schema)
	if err != nil {
		return fmt.Errorf("reading schema %s: %w", a.Init.Schema, err)
	}

	db, err := p.DB()
	if err != nil {
		return fmt.Errorf("connecting to database: %w", err)
	}
	if _, err := db.Exec(string(statements)); err != nil {
		return fmt.Errorf("executing %s: %w", a.Init.Schema, err)
	}

	if err := p.Stamp(context.Background(), brant.VERSION_HEAD, false); err != nil {
		return fmt.Errorf("stamping database: %w", err)
	}
	return nil
}