| package component | 
 |  | 
 | import ( | 
 | 	"database/sql" | 
 | 	"flag" | 
 | 	"net/url" | 
 | 	"os" | 
 | 	"sync" | 
 |  | 
 | 	"github.com/cockroachdb/cockroach-go/v2/testserver" | 
 | 	"github.com/golang-migrate/migrate/v4" | 
 | 	_ "github.com/golang-migrate/migrate/v4/database/cockroachdb" | 
 | 	"github.com/golang-migrate/migrate/v4/source" | 
 | 	_ "github.com/lib/pq" | 
 | 	"k8s.io/klog/v2" | 
 |  | 
 | 	"source.monogon.dev/metropolis/cli/pkg/datafile" | 
 | ) | 
 |  | 
 | // CockroachConfig is the common configuration of a components' connection to | 
 | // CockroachDB. It's supposed to be instantiated within a Configuration struct | 
 | // of a component. | 
 | // | 
 | // It can be configured by flags (via RegisterFlags) or manually (eg. in tests). | 
 | type CockroachConfig struct { | 
 | 	// Migrations is the go-migrate source of migrations for this database. Usually | 
 | 	// this can be taken from a go-embedded set of migration files. | 
 | 	Migrations source.Driver | 
 |  | 
 | 	// EndpointHost is the host part of the endpoint address of the database server. | 
 | 	EndpointHost string | 
 | 	// TLSKeyPath is the filesystem path of the x509 key used to authenticate to the | 
 | 	// database server. | 
 | 	TLSKeyPath string | 
 | 	// TLSKeyPath is the filesystem path of the x509 certificate used to | 
 | 	// authenticate to the database server. | 
 | 	TLSCertificatePath string | 
 | 	// TLSCACertificatePath is the filesystem path of the x509 CA certificate used | 
 | 	// to verify the database server's certificate. | 
 | 	TLSCACertificatePath string | 
 | 	// UserName is the username to be used on the database server. | 
 | 	UserName string | 
 | 	// UserName is the database name to be used on the database server. | 
 | 	DatabaseName string | 
 |  | 
 | 	// InMemory indicates that an in-memory CockroachDB instance should be used. | 
 | 	// Data will be lost after the component shuts down. | 
 | 	InMemory bool | 
 |  | 
 | 	// mu guards inMemoryInstance. | 
 | 	mu sync.Mutex | 
 | 	// inMemoryInstance is populated with a CockroachDB test server handle when | 
 | 	// InMemory is set and Connect()/MigrateUp() is called. | 
 | 	inMemoryInstance testserver.TestServer | 
 | } | 
 |  | 
 | // RegisterFlags registers the connection configuration to be provided by flags. | 
 | // This must be called exactly once before then calling flags.Parse(). | 
 | func (c *CockroachConfig) RegisterFlags(prefix string) { | 
 | 	flag.StringVar(&c.EndpointHost, prefix+"_endpoint_host", "", "Host of CockroachDB endpoint for "+prefix) | 
 | 	flag.StringVar(&c.TLSKeyPath, prefix+"_tls_key_path", "", "Path to CockroachDB TLS client key for "+prefix) | 
 | 	flag.StringVar(&c.TLSCertificatePath, prefix+"_tls_certificate_path", "", "Path to CockroachDB TLS client certificate for "+prefix) | 
 | 	flag.StringVar(&c.TLSCACertificatePath, prefix+"_tls_ca_certificate_path", "", "Path to CockroachDB CA certificate for "+prefix) | 
 | 	flag.StringVar(&c.UserName, prefix+"_user_name", prefix, "CockroachDB user name for "+prefix) | 
 | 	flag.StringVar(&c.DatabaseName, prefix+"_database_name", prefix, "CockroachDB database name for "+prefix) | 
 | 	flag.BoolVar(&c.InMemory, prefix+"_eat_my_data", false, "Use in-memory CockroachDB for "+prefix+". Warning: Data will be lost at process shutdown!") | 
 | } | 
 |  | 
 | // startInMemory starts an in-memory cockroachdb server as a subprocess, and | 
 | // returns a DSN that connects to the newly created database. | 
 | func (c *CockroachConfig) startInMemory(scheme string) string { | 
 | 	c.mu.Lock() | 
 | 	defer c.mu.Unlock() | 
 |  | 
 | 	klog.Warningf("STARTING IN-MEMORY COCKROACHDB FOR TESTS") | 
 | 	klog.Warningf("ALL DATA WILL BE LOST AFTER SERVER SHUTDOWN!") | 
 |  | 
 | 	if c.inMemoryInstance == nil { | 
 | 		opts := []testserver.TestServerOpt{ | 
 | 			testserver.SecureOpt(), | 
 | 		} | 
 | 		if path, err := datafile.ResolveRunfile("external/cockroach/cockroach"); err == nil { | 
 | 			opts = append(opts, testserver.CockroachBinaryPathOpt(path)) | 
 | 		} else { | 
 | 			if os.Getenv("TEST_TMPDIR") != "" { | 
 | 				klog.Exitf("In test which requires in-memory cockroachdb, but @cockroach//:cockroach missing as a dependency. Failing.") | 
 | 			} | 
 | 			klog.Warningf("CockroachDB in-memory database requested, but not available as a build dependency. Trying to download it...") | 
 | 		} | 
 |  | 
 | 		inst, err := testserver.NewTestServer(opts...) | 
 | 		if err != nil { | 
 | 			klog.Exitf("Failed to create crdb test server: %v", err) | 
 | 		} | 
 | 		c.inMemoryInstance = inst | 
 | 	} | 
 |  | 
 | 	u := *c.inMemoryInstance.PGURL() | 
 | 	u.Scheme = scheme | 
 | 	return u.String() | 
 | } | 
 |  | 
 | // buildDSN returns a DSN to the configured database connection with a given DSN | 
 | // scheme. The scheme will usually be 'postgres' or 'cockroach', depending on | 
 | // whether it's used for lib/pq or for golang-migrate. | 
 | func (c *CockroachConfig) buildDSN(scheme string) string { | 
 | 	if c.InMemory { | 
 | 		return c.startInMemory(scheme) | 
 | 	} | 
 |  | 
 | 	query := make(url.Values) | 
 | 	query.Set("sslmode", "verify-full") | 
 | 	query.Set("sslcert", c.TLSCertificatePath) | 
 | 	query.Set("sslkey", c.TLSKeyPath) | 
 | 	query.Set("sslrootcert", c.TLSCACertificatePath) | 
 | 	u := url.URL{ | 
 | 		Scheme:   scheme, | 
 | 		User:     url.User(c.UserName), | 
 | 		Host:     c.EndpointHost, | 
 | 		Path:     c.DatabaseName, | 
 | 		RawQuery: query.Encode(), | 
 | 	} | 
 | 	return u.String() | 
 | } | 
 |  | 
 | // Connect returns a working *sql.DB handle to the database described by this | 
 | // CockroachConfig. | 
 | func (d *CockroachConfig) Connect() (*sql.DB, error) { | 
 | 	dsn := d.buildDSN("postgres") | 
 | 	klog.Infof("Connecting to %s...", dsn) | 
 | 	return sql.Open("postgres", d.buildDSN("postgres")) | 
 | } | 
 |  | 
 | // MigrateUp performs all possible migrations upwards for the database described | 
 | // by this CockroachConfig. | 
 | func (d *CockroachConfig) MigrateUp() error { | 
 | 	dsn := d.buildDSN("cockroachdb") | 
 | 	klog.Infof("Running migrations on %s...", dsn) | 
 | 	m, err := migrate.NewWithSourceInstance("iofs", d.Migrations, dsn) | 
 | 	if err != nil { | 
 | 		return err | 
 | 	} | 
 | 	return m.Up() | 
 | } |