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  }