github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/testserver/datastore/mysql.go (about) 1 //go:build docker 2 // +build docker 3 4 package datastore 5 6 import ( 7 "context" 8 "database/sql" 9 "fmt" 10 "testing" 11 12 "github.com/google/uuid" 13 "github.com/ory/dockertest/v3" 14 "github.com/stretchr/testify/require" 15 16 "github.com/authzed/spicedb/internal/datastore/mysql/migrations" 17 "github.com/authzed/spicedb/internal/datastore/mysql/version" 18 "github.com/authzed/spicedb/pkg/datastore" 19 "github.com/authzed/spicedb/pkg/migrate" 20 "github.com/authzed/spicedb/pkg/secrets" 21 ) 22 23 const ( 24 mysqlPort = 3306 25 defaultCreds = "root:secret" 26 testDBPrefix = "spicedb_test_" 27 ) 28 29 type mysqlTester struct { 30 db *sql.DB 31 hostname string 32 creds string 33 port string 34 options MySQLTesterOptions 35 } 36 37 // MySQLTesterOptions allows tweaking the behaviour of the builder for the MySQL datastore 38 type MySQLTesterOptions struct { 39 Prefix string 40 MigrateForNewDatastore bool 41 UseV8 bool 42 } 43 44 // RunMySQLForTesting returns a RunningEngineForTest for the mysql driver 45 // backed by a MySQL instance with RunningEngineForTest options - no prefix is added, and datastore migration is run. 46 func RunMySQLForTesting(t testing.TB, bridgeNetworkName string) RunningEngineForTest { 47 return RunMySQLForTestingWithOptions(t, MySQLTesterOptions{Prefix: "", MigrateForNewDatastore: true}, bridgeNetworkName) 48 } 49 50 // RunMySQLForTestingWithOptions returns a RunningEngineForTest for the mysql driver 51 // backed by a MySQL instance, while allowing options to be forwarded 52 func RunMySQLForTestingWithOptions(t testing.TB, options MySQLTesterOptions, bridgeNetworkName string) RunningEngineForTest { 53 pool, err := dockertest.NewPool("") 54 require.NoError(t, err) 55 56 containerImageTag := version.MinimumSupportedMySQLVersion 57 58 name := fmt.Sprintf("mysql-%s", uuid.New().String()) 59 resource, err := pool.RunWithOptions(&dockertest.RunOptions{ 60 Name: name, 61 Repository: "mirror.gcr.io/library/mysql", 62 Tag: containerImageTag, 63 Env: []string{"MYSQL_ROOT_PASSWORD=secret"}, 64 // increase max connections (default 151) to accommodate tests using the same docker container 65 Cmd: []string{"--max-connections=500"}, 66 NetworkID: bridgeNetworkName, 67 }) 68 require.NoError(t, err) 69 70 builder := &mysqlTester{ 71 creds: defaultCreds, 72 options: options, 73 } 74 t.Cleanup(func() { 75 require.NoError(t, pool.Purge(resource)) 76 }) 77 78 port := resource.GetPort(fmt.Sprintf("%d/tcp", mysqlPort)) 79 if bridgeNetworkName != "" { 80 builder.hostname = name 81 builder.port = fmt.Sprintf("%d", mysqlPort) 82 } else { 83 builder.port = port 84 } 85 86 dsn := fmt.Sprintf("%s@(localhost:%s)/mysql?parseTime=true", builder.creds, port) 87 require.NoError(t, pool.Retry(func() error { 88 var err error 89 builder.db, err = sql.Open("mysql", dsn) 90 if err != nil { 91 return err 92 } 93 ctx, cancelPing := context.WithTimeout(context.Background(), dockerBootTimeout) 94 defer cancelPing() 95 err = builder.db.PingContext(ctx) 96 if err != nil { 97 return err 98 } 99 return nil 100 })) 101 102 return builder 103 } 104 105 func (mb *mysqlTester) NewDatabase(t testing.TB) string { 106 uniquePortion, err := secrets.TokenHex(4) 107 require.NoError(t, err, "Could not generate unique portion of db name: %s", err) 108 dbName := testDBPrefix + uniquePortion 109 tx, err := mb.db.Begin() 110 require.NoError(t, err, "Could being transaction: %s", err) 111 _, err = tx.Exec(fmt.Sprintf("CREATE DATABASE %s;", dbName)) 112 require.NoError(t, err, "failed to create database %s: %s", dbName, err) 113 err = tx.Commit() 114 require.NoError(t, err, "failed to commit: %s", err) 115 return fmt.Sprintf("%s@(%s:%s)/%s?parseTime=true", mb.creds, mb.hostname, mb.port, dbName) 116 } 117 118 func (mb *mysqlTester) runMigrate(t testing.TB, dsn string) { 119 driver, err := migrations.NewMySQLDriverFromDSN(dsn, mb.options.Prefix, datastore.NoCredentialsProvider) 120 require.NoError(t, err, "failed to create migration driver: %s", err) 121 err = migrations.Manager.Run(context.Background(), driver, migrate.Head, migrate.LiveRun) 122 require.NoError(t, err, "failed to run migration: %s", err) 123 } 124 125 func (mb *mysqlTester) NewDatastore(t testing.TB, initFunc InitFunc) datastore.Datastore { 126 dsn := mb.NewDatabase(t) 127 if mb.options.MigrateForNewDatastore { 128 mb.runMigrate(t, dsn) 129 } 130 return initFunc("mysql", dsn) 131 }