blob: bff98f0dafa7d14247dbf97e44a60e3497982440 [file] [log] [blame]
Serge Bazanskia5baa872022-09-15 18:49:35 +02001package component
2
3import (
4 "database/sql"
5 "flag"
6 "net/url"
7 "os"
8 "sync"
9
10 "github.com/cockroachdb/cockroach-go/v2/testserver"
11 "github.com/golang-migrate/migrate/v4"
12 _ "github.com/golang-migrate/migrate/v4/database/cockroachdb"
13 "github.com/golang-migrate/migrate/v4/source"
14 _ "github.com/lib/pq"
15 "k8s.io/klog/v2"
16
17 "source.monogon.dev/metropolis/cli/pkg/datafile"
18)
19
20// CockroachConfig is the common configuration of a components' connection to
21// CockroachDB. It's supposed to be instantiated within a Configuration struct
22// of a component.
23//
24// It can be configured by flags (via RegisterFlags) or manually (eg. in tests).
25type CockroachConfig struct {
26 // Migrations is the go-migrate source of migrations for this database. Usually
27 // this can be taken from a go-embedded set of migration files.
28 Migrations source.Driver
29
30 // EndpointHost is the host part of the endpoint address of the database server.
31 EndpointHost string
32 // TLSKeyPath is the filesystem path of the x509 key used to authenticate to the
33 // database server.
34 TLSKeyPath string
35 // TLSKeyPath is the filesystem path of the x509 certificate used to
36 // authenticate to the database server.
37 TLSCertificatePath string
38 // TLSCACertificatePath is the filesystem path of the x509 CA certificate used
39 // to verify the database server's certificate.
40 TLSCACertificatePath string
41 // UserName is the username to be used on the database server.
42 UserName string
43 // UserName is the database name to be used on the database server.
44 DatabaseName string
45
46 // InMemory indicates that an in-memory CockroachDB instance should be used.
47 // Data will be lost after the component shuts down.
48 InMemory bool
49
50 // mu guards inMemoryInstance.
51 mu sync.Mutex
52 // inMemoryInstance is populated with a CockroachDB test server handle when
53 // InMemory is set and Connect()/MigrateUp() is called.
54 inMemoryInstance testserver.TestServer
55}
56
57// RegisterFlags registers the connection configuration to be provided by flags.
58// This must be called exactly once before then calling flags.Parse().
59func (c *CockroachConfig) RegisterFlags(prefix string) {
60 flag.StringVar(&c.EndpointHost, prefix+"_endpoint_host", "", "Host of CockroachDB endpoint for "+prefix)
61 flag.StringVar(&c.TLSKeyPath, prefix+"_tls_key_path", "", "Path to CockroachDB TLS client key for "+prefix)
62 flag.StringVar(&c.TLSCertificatePath, prefix+"_tls_certificate_path", "", "Path to CockroachDB TLS client certificate for "+prefix)
63 flag.StringVar(&c.TLSCACertificatePath, prefix+"_tls_ca_certificate_path", "", "Path to CockroachDB CA certificate for "+prefix)
64 flag.StringVar(&c.UserName, prefix+"_user_name", prefix, "CockroachDB user name for "+prefix)
65 flag.StringVar(&c.DatabaseName, prefix+"_database_name", prefix, "CockroachDB database name for "+prefix)
66 flag.BoolVar(&c.InMemory, prefix+"_eat_my_data", false, "Use in-memory CockroachDB for "+prefix+". Warning: Data will be lost at process shutdown!")
67}
68
69// startInMemory starts an in-memory cockroachdb server as a subprocess, and
70// returns a DSN that connects to the newly created database.
71func (c *CockroachConfig) startInMemory(scheme string) string {
72 c.mu.Lock()
73 defer c.mu.Unlock()
74
75 klog.Warningf("STARTING IN-MEMORY COCKROACHDB FOR TESTS")
76 klog.Warningf("ALL DATA WILL BE LOST AFTER SERVER SHUTDOWN!")
77
78 if c.inMemoryInstance == nil {
79 opts := []testserver.TestServerOpt{
80 testserver.SecureOpt(),
81 }
82 if path, err := datafile.ResolveRunfile("external/cockroach/cockroach"); err == nil {
83 opts = append(opts, testserver.CockroachBinaryPathOpt(path))
84 } else {
85 if os.Getenv("TEST_TMPDIR") != "" {
86 klog.Exitf("In test which requires in-memory cockroachdb, but @cockroach//:cockroach missing as a dependency. Failing.")
87 }
88 klog.Warningf("CockroachDB in-memory database requested, but not available as a build dependency. Trying to download it...")
89 }
90
91 inst, err := testserver.NewTestServer(opts...)
92 if err != nil {
93 klog.Exitf("Failed to create crdb test server: %v", err)
94 }
95 c.inMemoryInstance = inst
96 }
97
98 u := *c.inMemoryInstance.PGURL()
99 u.Scheme = scheme
100 return u.String()
101}
102
103// buildDSN returns a DSN to the configured database connection with a given DSN
104// scheme. The scheme will usually be 'postgres' or 'cockroach', depending on
105// whether it's used for lib/pq or for golang-migrate.
106func (c *CockroachConfig) buildDSN(scheme string) string {
107 if c.InMemory {
108 return c.startInMemory(scheme)
109 }
110
111 query := make(url.Values)
112 query.Set("sslmode", "verify-full")
113 query.Set("sslcert", c.TLSCertificatePath)
114 query.Set("sslkey", c.TLSKeyPath)
115 query.Set("sslrootcert", c.TLSCACertificatePath)
116 u := url.URL{
117 Scheme: scheme,
118 User: url.User(c.UserName),
119 Host: c.EndpointHost,
120 Path: c.DatabaseName,
121 RawQuery: query.Encode(),
122 }
123 return u.String()
124}
125
126// Connect returns a working *sql.DB handle to the database described by this
127// CockroachConfig.
128func (d *CockroachConfig) Connect() (*sql.DB, error) {
129 dsn := d.buildDSN("postgres")
130 klog.Infof("Connecting to %s...", dsn)
131 return sql.Open("postgres", d.buildDSN("postgres"))
132}
133
134// MigrateUp performs all possible migrations upwards for the database described
135// by this CockroachConfig.
136func (d *CockroachConfig) MigrateUp() error {
137 dsn := d.buildDSN("cockroachdb")
138 klog.Infof("Running migrations on %s...", dsn)
139 m, err := migrate.NewWithSourceInstance("iofs", d.Migrations, dsn)
140 if err != nil {
141 return err
142 }
143 return m.Up()
144}