Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MySQL module #943

Merged
merged 14 commits into from
Mar 23, 2023
Merged
12 changes: 6 additions & 6 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,6 @@ updates:
interval: monthly
open-pull-requests-limit: 3
rebase-strategy: disabled
- package-ecosystem: gomod
directory: /examples/mysql
schedule:
interval: monthly
open-pull-requests-limit: 3
rebase-strategy: disabled
- package-ecosystem: gomod
directory: /examples/nginx
schedule:
Expand Down Expand Up @@ -108,6 +102,12 @@ updates:
interval: monthly
open-pull-requests-limit: 3
rebase-strategy: disabled
- package-ecosystem: gomod
eddumelendez marked this conversation as resolved.
Show resolved Hide resolved
directory: /modules/mysql
schedule:
interval: monthly
open-pull-requests-limit: 3
rebase-strategy: disabled
- package-ecosystem: gomod
directory: /modules/pulsar
schedule:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name: Mysql example pipeline
name: MySQL module pipeline

on: [push, pull_request]

concurrency:
group: "${{ github.workflow }}-${{ github.head_ref || github.sha }}"
group: ${{ github.workflow }}-${{ github.head_ref || github.sha }}
cancel-in-progress: true

jobs:
Expand All @@ -24,15 +24,15 @@ jobs:
uses: actions/checkout@v3

- name: modVerify
working-directory: ./examples/mysql
working-directory: ./modules/mysql
run: go mod verify

- name: modTidy
working-directory: ./examples/mysql
working-directory: ./modules/mysql
run: make tools-tidy

- name: gotestsum
working-directory: ./examples/mysql
working-directory: ./modules/mysql
run: make test-unit

- name: Run checker
Expand Down
9 changes: 0 additions & 9 deletions docs/examples/mysql.md

This file was deleted.

61 changes: 61 additions & 0 deletions docs/modules/mysql.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# MySQL

## Adding this module to your project dependencies

Please run the following command to add the Postgres module to your Go dependencies:

```
go get github.com/testcontainers/testcontainers-go/modules/mysql
```

## Usage example

<!--codeinclude-->
[Creating a MySQL container](../../modules/mysql/mysql.go)
<!--/codeinclude-->

## Module Reference

The MySQL module exposes one entrypoint function to create the container, and this function receives three parameters:

```golang
func StartContainer(ctx context.Context, image string, opts ...Option) (*MySQLContainer, error) {
```

- `context.Context`, the Go context.
- `image`, the image to use for the container.
- `Option`, a variad argument for passing options.

## Container Options

When starting the MySQL container, you can pass options in a variadic way to configure it.

!!!tip

You can find all the available configuration and environment variables for the MySQL Docker image on [Docker Hub](https://hub.docker.com/_/mysql).

### Set username, password and database name

If you need to set a different database, and its credentials, you can use `WithUsername`, `WithPassword`, `WithDatabase`
options. By default, the username, the password and the database name is `test`.

<!--codeinclude-->
[Custom Database initialization](../../modules/mysql/mysql_test.go) inside_block:customInitialization
<!--/codeinclude-->

### Init Scripts

If you would like to perform DDL or DML operations in the MySQL container, add one or more `*.sql`, `*.sql.gz`, or `*.sh`
scripts to the container request. Those files will be copied under `/docker-entrypoint-initdb.d`.

<!--codeinclude-->
[Include init scripts](../../modules/mysql/mysql_test.go) inside_block:withScripts
<!--/codeinclude-->

### Custom configuration

If you need to set a custom configuration, you can use `WithConfigFile` option.

<!--codeinclude-->
[Custom MySQL config file](../../modules/mysql/mysql_test.go) inside_block:withConfigFile
<!--/codeinclude-->
38 changes: 0 additions & 38 deletions examples/mysql/mysql.go

This file was deleted.

55 changes: 0 additions & 55 deletions examples/mysql/mysql_test.go

This file was deleted.

2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ nav:
- modules/index.md
- modules/couchbase.md
- modules/localstack.md
- modules/mysql.md
- modules/pulsar.md
- Examples:
- examples/index.md
Expand All @@ -61,7 +62,6 @@ nav:
- examples/datastore.md
- examples/firestore.md
- examples/mongodb.md
- examples/mysql.md
- examples/nginx.md
- examples/postgres.md
- examples/pubsub.md
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion examples/mysql/go.mod → modules/mysql/go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/testcontainers/testcontainers-go/examples/mysql
module github.com/testcontainers/testcontainers-go/modules/mysql

go 1.19

Expand Down
File renamed without changes.
150 changes: 150 additions & 0 deletions modules/mysql/mysql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package mysql

import (
"context"
"fmt"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
"path/filepath"
"strings"
)

const rootUser = "root"
const defaultUser = "test"
const defaultPassword = "test"
const defaultDatabaseName = "test"
const defaultImage = "mysql:8"

// MySQLContainer represents the MySQL container type used in the module
type MySQLContainer struct {
testcontainers.Container
username string
password string
database string
}

type MySQLContainerOption func(req *testcontainers.ContainerRequest)

// StartContainer creates an instance of the MySQL container type
func StartContainer(ctx context.Context, opts ...MySQLContainerOption) (*MySQLContainer, error) {
req := testcontainers.ContainerRequest{
Image: defaultImage,
ExposedPorts: []string{"3306/tcp", "33060/tcp"},
Env: map[string]string{
"MYSQL_USER": defaultUser,
"MYSQL_PASSWORD": defaultPassword,
"MYSQL_DATABASE": defaultDatabaseName,
},
WaitingFor: wait.ForLog("port: 3306 MySQL Community Server"),
}

opts = append(opts, func(req *testcontainers.ContainerRequest) {
username := req.Env["MYSQL_USER"]
password := req.Env["MYSQL_PASSWORD"]
if strings.EqualFold(rootUser, username) {
delete(req.Env, "MYSQL_USER")
}
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved
if len(password) != 0 && password != "" {
req.Env["MYSQL_ROOT_PASSWORD"] = password
} else if strings.EqualFold(rootUser, username) {
req.Env["MYSQL_ALLOW_EMPTY_PASSWORD"] = "yes"
delete(req.Env, "MYSQL_PASSWORD")
}
})
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved

for _, opt := range opts {
opt(&req)
}

username, ok := req.Env["MYSQL_USER"]
if !ok {
username = rootUser
}
password := req.Env["MYSQL_PASSWORD"]

if len(password) == 0 && password == "" && !strings.EqualFold(rootUser, username) {
return nil, fmt.Errorf("empty password can be used only with the root user")
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved
}

container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
return nil, err
}

database := req.Env["MYSQL_DATABASE"]

return &MySQLContainer{container, username, password, database}, nil
}

func (c *MySQLContainer) ConnectionString(ctx context.Context) (string, error) {
eddumelendez marked this conversation as resolved.
Show resolved Hide resolved
containerPort, err := c.MappedPort(ctx, "3306/tcp")
if err != nil {
return "", err
}

host, err := c.Host(ctx)
if err != nil {
return "", err
}

connectionString := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", c.username, c.password, host, containerPort.Port(), c.database)
return connectionString, nil
}

// WithImage sets the image to be used for the mysql container
func WithImage(image string) func(req *testcontainers.ContainerRequest) {
return func(req *testcontainers.ContainerRequest) {
if image == "" {
image = "mysql:8"
}

req.Image = image
}
}

func WithUsername(username string) func(req *testcontainers.ContainerRequest) {
return func(req *testcontainers.ContainerRequest) {
req.Env["MYSQL_USER"] = username
}
}

func WithPassword(password string) func(req *testcontainers.ContainerRequest) {
return func(req *testcontainers.ContainerRequest) {
req.Env["MYSQL_PASSWORD"] = password
}
}

func WithDatabase(database string) func(req *testcontainers.ContainerRequest) {
return func(req *testcontainers.ContainerRequest) {
req.Env["MYSQL_DATABASE"] = database
}
}

func WithConfigFile(configFile string) func(req *testcontainers.ContainerRequest) {
return func(req *testcontainers.ContainerRequest) {
cf := testcontainers.ContainerFile{
HostFilePath: configFile,
ContainerFilePath: "/etc/mysql/conf.d/my.cnf",
FileMode: 0755,
}
req.Files = append(req.Files, cf)
}
}

func WithScripts(scripts ...string) func(req *testcontainers.ContainerRequest) {
return func(req *testcontainers.ContainerRequest) {
var initScripts []testcontainers.ContainerFile
for _, script := range scripts {
cf := testcontainers.ContainerFile{
HostFilePath: script,
ContainerFilePath: "/docker-entrypoint-initdb.d/" + filepath.Base(script),
FileMode: 0755,
}
initScripts = append(initScripts, cf)
}
req.Files = append(req.Files, initScripts...)
}
}
Loading