github.com/openfga/openfga@v1.5.4-rc1/pkg/testfixtures/storage/mysql.go (about) 1 package storage 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "log" 8 "net" 9 "testing" 10 "time" 11 12 "github.com/cenkalti/backoff/v4" 13 "github.com/docker/docker/api/types/container" 14 "github.com/go-sql-driver/mysql" 15 "github.com/pressly/goose/v3" 16 "github.com/stretchr/testify/require" 17 "github.com/testcontainers/testcontainers-go" 18 testcontainersmysql "github.com/testcontainers/testcontainers-go/modules/mysql" 19 20 "github.com/openfga/openfga/assets" 21 ) 22 23 const ( 24 mySQLImage = "mysql:8" 25 ) 26 27 type mySQLTestContainer struct { 28 addr string 29 version int64 30 username string 31 password string 32 } 33 34 // NewMySQLTestContainer returns an implementation of the DatastoreTestContainer interface 35 // for MySQL. 36 func NewMySQLTestContainer() *mySQLTestContainer { 37 return &mySQLTestContainer{} 38 } 39 40 func (m *mySQLTestContainer) GetDatabaseSchemaVersion() int64 { 41 return m.version 42 } 43 44 // RunMySQLTestContainer runs a MySQL container, connects to it, and returns a 45 // bootstrapped implementation of the DatastoreTestContainer interface wired up for the 46 // MySQL datastore engine. 47 func (m *mySQLTestContainer) RunMySQLTestContainer(t testing.TB) DatastoreTestContainer { 48 ctx := context.Background() 49 50 mysqlContainer, err := testcontainersmysql.RunContainer(ctx, 51 testcontainers.WithImage(mySQLImage), 52 testcontainers.WithHostConfigModifier(func(hostConfig *container.HostConfig) { 53 hostConfig.Tmpfs = map[string]string{"/var/lib/mysql": ""} 54 }), 55 testcontainersmysql.WithDatabase("defaultdb"), 56 testcontainersmysql.WithUsername("root"), 57 testcontainersmysql.WithPassword("secret"), 58 ) 59 require.NoError(t, err) 60 t.Cleanup(func() { require.NoError(t, mysqlContainer.Terminate(ctx)) }) 61 62 mysqlHost, err := mysqlContainer.Host(ctx) 63 require.NoError(t, err) 64 mysqlPort, err := mysqlContainer.MappedPort(ctx, "3306/tcp") 65 require.NoError(t, err) 66 67 mySQLTestContainer := &mySQLTestContainer{ 68 addr: net.JoinHostPort(mysqlHost, mysqlPort.Port()), 69 username: "root", 70 password: "secret", 71 } 72 73 uri := fmt.Sprintf("%s:%s@tcp(%s)/defaultdb?parseTime=true", mySQLTestContainer.username, mySQLTestContainer.password, mySQLTestContainer.addr) 74 75 err = mysql.SetLogger(log.New(io.Discard, "", 0)) 76 require.NoError(t, err) 77 78 goose.SetLogger(goose.NopLogger()) 79 80 db, err := goose.OpenDBWithDriver("mysql", uri) 81 require.NoError(t, err) 82 defer db.Close() 83 84 backoffPolicy := backoff.NewExponentialBackOff() 85 backoffPolicy.MaxElapsedTime = 2 * time.Minute 86 err = backoff.Retry( 87 func() error { 88 return db.Ping() 89 }, 90 backoffPolicy, 91 ) 92 require.NoError(t, err, "failed to connect to mysql container") 93 94 goose.SetBaseFS(assets.EmbedMigrations) 95 96 err = goose.Up(db, assets.MySQLMigrationDir) 97 require.NoError(t, err) 98 version, err := goose.GetDBVersion(db) 99 require.NoError(t, err) 100 mySQLTestContainer.version = version 101 102 return mySQLTestContainer 103 } 104 105 // GetConnectionURI returns the mysql connection uri for the running mysql test container. 106 func (m *mySQLTestContainer) GetConnectionURI(includeCredentials bool) string { 107 creds := "" 108 if includeCredentials { 109 creds = fmt.Sprintf("%s:%s@", m.username, m.password) 110 } 111 112 return fmt.Sprintf( 113 "%stcp(%s)/%s?parseTime=true", 114 creds, 115 m.addr, 116 "defaultdb", 117 ) 118 } 119 120 func (m *mySQLTestContainer) GetUsername() string { 121 return m.username 122 } 123 124 func (m *mySQLTestContainer) GetPassword() string { 125 return m.password 126 }