diff --git a/testcontainer/environment/containers_configs.go b/testcontainer/environment/containers_configs.go new file mode 100644 index 0000000..bcfe548 --- /dev/null +++ b/testcontainer/environment/containers_configs.go @@ -0,0 +1,93 @@ +package environment + +import ( + "fmt" + + "github.com/imperiuse/golib/testcontainer" + "github.com/testcontainers/testcontainers-go/wait" +) + +const ( + NetworkName = "test_id_provisioning_network" + + AppName = "app" + + KafkaImage = "confluentinc/cp-kafka:7.2.0" + KafkaBrokerPort = "9092" + KafkaClientPort = "9101" + KafkaContainerName = "broker" + + ZookeeperImage = "confluentinc/cp-zookeeper:7.2.0" + ZooKeeperPort = "2181" + ZooKeeperContainerName = "zookeeper" + ZooTickTime = "2000" + + PostgresImage = "postgres:14" + PostgresPort = "5432" + PostgresContainerName = "db" + PostgresUsername = "admin" + PostgresPassword = "password" + PostgresDB = "id-provisioning" +) + +var ( + zooCfg = testcontainer.ZookeeperConfig{ + BaseContainerConfig: testcontainer.BaseContainerConfig{ + Image: ZookeeperImage, + Port: ZooKeeperPort, + Name: ZooKeeperContainerName, + ExposedPorts: []string{fmt.Sprintf("0.0.0.0:%[1]s:%[1]s", ZooKeeperPort)}, + Envs: map[string]string{ + "ZOOKEEPER_SERVER_ID": "1", + "ZOOKEEPER_SERVERS": "zoo1:2888:3888", + "ZOOKEEPER_CLIENT_PORT": ZooKeeperPort, + "ZOOKEEPER_TICK_TIME": ZooTickTime, + }, + WaitingForStrategy: wait.ForLog("binding to port 0.0.0.0/0.0.0.0:2181"), + }, + } + + kafkaCfg = testcontainer.KafkaConfig{ + BaseContainerConfig: testcontainer.BaseContainerConfig{ + Image: KafkaImage, + Name: KafkaContainerName, + Port: KafkaClientPort, + ExposedPorts: []string{ + fmt.Sprintf("0.0.0.0:%[1]s:%[1]s", KafkaClientPort), + fmt.Sprintf("0.0.0.0:%[1]s:%[1]s", KafkaBrokerPort), + }, + Envs: map[string]string{ + "KAFKA_BROKER_ID": "1", + "KAFKA_ZOOKEEPER_CONNECT": ZooKeeperContainerName + ":" + ZooKeeperPort, + "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP": "PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT", + "KAFKA_ADVERTISED_HOST_NAME": KafkaContainerName, + "KAFKA_ADVERTISED_LISTENERS": "PLAINTEXT://" + KafkaContainerName + ":29092,PLAINTEXT_HOST://localhost:" + + KafkaBrokerPort, + "KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR": "1", + "KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS": "0", + "KAFKA_CONFLUENT_LICENSE_TOPIC_REPLICATION_FACTOR": "1", + "KAFKA_CONFLUENT_BALANCER_TOPIC_REPLICATION_FACTOR": "1", + "KAFKA_TRANSACTION_STATE_LOG_MIN_ISR": "1", + "KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR": "1", + "KAFKA_AUTO_CREATE_TOPICS.ENABLE": "true", + }, + WaitingForStrategy: wait.ForLog("[KafkaServer id=1] started"), + }, + ClientPort: KafkaClientPort, + BrokerPort: KafkaBrokerPort, + } + + postgresCfg = testcontainer.PostgresConfig{BaseContainerConfig: testcontainer.BaseContainerConfig{ + Name: PostgresContainerName, + Image: PostgresImage, + Port: PostgresPort, + ExposedPorts: []string{fmt.Sprintf("0.0.0.0:%[1]s:%[1]s", PostgresPort)}, + Envs: map[string]string{ + "POSTGRES_USER": PostgresUsername, + "POSTGRES_PASSWORD": PostgresPassword, + "POSTGRES_DB": PostgresDB, + }, + WaitingForStrategy: wait.ForLog("database system is ready to accept connections"), + }, + } +) diff --git a/testcontainer/environment/environment.go b/testcontainer/environment/environment.go new file mode 100644 index 0000000..4627423 --- /dev/null +++ b/testcontainer/environment/environment.go @@ -0,0 +1,82 @@ +package environment + +import ( + "context" + "testing" + "time" + + "github.com/imperiuse/golib/testcontainer" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +type ContainersEnvironment struct { + // First - native go-testcontainers way for creating docker containers. + dockerNetwork *testcontainers.DockerNetwork + kafkaContainer *testcontainer.KafkaCluster + postgresContainer testcontainers.Container + + // Second - docker-compose way + go-testcontainers for create docker container env. + compose testcontainers.DockerCompose +} + +// StartPureDockerEnvironment - create and start docker containers env with first way. +func (c *ContainersEnvironment) StartPureDockerEnvironment(t *testing.T, ctx context.Context) { + t.Log("> From SetupEnvironment") + + t.Log("Create docker network") + dn, err := testcontainer.NewDockerNetwork(ctx, NetworkName) + require.Nil(t, err, "error must be nil for NewDockerNetwork") + require.NotNil(t, dn, "docker network must be not nil") + c.dockerNetwork = dn.(*testcontainers.DockerNetwork) + + t.Log("Create service deps") + c.kafkaContainer, err = testcontainer.NewKafkaCluster(ctx, kafkaCfg, zooCfg, c.dockerNetwork) + require.Nil(t, err, "error must be nil, when create NewKafkaCluster") + require.NotNil(t, c.kafkaContainer, "kafka cluster must be not nil") + + c.postgresContainer, err = testcontainer.NewPostgresContainer(ctx, postgresCfg, c.dockerNetwork) + require.Nil(t, err, "error must be nil, when create NewPostgresContainer") + require.NotNil(t, c.postgresContainer, "postgres container must be not nil") + + t.Log("Start deps services containers") + + require.Nil(t, c.kafkaContainer.Start(ctx), "kafka cluster must start without errors") + + require.Nil(t, c.postgresContainer.Start(ctx), "postgres must start without errors") + + const magicTime = time.Second * 3 + time.Sleep(magicTime) // time sleep development // todo think how to remove this +} + +// FinishedPureDockerEnvironment - finished containers (env) which we created by first way. +func (c *ContainersEnvironment) FinishedPureDockerEnvironment(t *testing.T, ctx context.Context) { + require.Nil(t, testcontainer.TerminateIfNotNil(ctx, c.kafkaContainer), "must not get an error while terminate kafka cluster") + require.Nil(t, testcontainer.TerminateIfNotNil(ctx, c.postgresContainer), "must not get an error while terminate postgres cluster") + require.Nil(t, c.dockerNetwork.Remove(ctx), "must not get an error while remove docker network") +} + +// StartDockerComposeEnvironment - create and start docker containers env with second way. +func (c *ContainersEnvironment) StartDockerComposeEnvironment( + t *testing.T, + composeFilePaths []string, + identifier string, +) { + c.compose = testcontainers.NewLocalDockerCompose(composeFilePaths, identifier). + WaitForService(PostgresContainerName, wait.ForLog("database system is ready to accept connections")). + WaitForService(ZooKeeperContainerName, wait.ForLog("binding to port 0.0.0.0/0.0.0.0:"+ZooKeeperPort)). + WaitForService(KafkaContainerName, wait.ForLog("[KafkaServer id=1] started")) + + if len(composeFilePaths) > 1 { // this is little tricky hack here. :) + // if we have one docker-compose file for app container, that add wait strategy. + c.compose = c.compose.WaitForService(AppName, wait.ForLog("App starting successfully! Ready for hard work!")) + } + + require.Nil(t, c.compose.WithCommand([]string{"up", "--force-recreate", "-d"}).Invoke().Error) +} + +// FinishedDockerComposeEnvironment - finished containers (env) which we created by second way. +func (c *ContainersEnvironment) FinishedDockerComposeEnvironment(t *testing.T) { + require.Nil(t, c.compose.Down().Error, "docker compose must down without errors") +} diff --git a/testcontainer/kafka.go b/testcontainer/kafka.go index 51410a3..f626f06 100644 --- a/testcontainer/kafka.go +++ b/testcontainer/kafka.go @@ -3,16 +3,15 @@ package testcontainer import ( "context" "fmt" - "time" - - "github.com/testcontainers/testcontainers-go" ) type ( // KafkaCluster - kafka cluster struct (together kafka container and zookeeper). KafkaCluster struct { - KafkaContainer testcontainers.Container - ZookeeperContainer testcontainers.Container + KafkaContainer Container + ZookeeperContainer Container + + KafkaURI string kafkaCfg KafkaConfig zooCfg ZookeeperConfig @@ -20,9 +19,9 @@ type ( // KafkaConfig - kafka container config with zookeeper. KafkaConfig struct { - BaseContainerConfig ClientPort string BrokerPort string + BaseContainerConfig } ) @@ -31,14 +30,14 @@ func NewKafkaCluster( ctx context.Context, kafkaCfg KafkaConfig, zooCfg ZookeeperConfig, - dockerNetwork *testcontainers.DockerNetwork, + dockerNetwork *DockerNetwork, ) (*KafkaCluster, error) { zookeeperContainer, err := NewZookeeperContainer(ctx, zooCfg, dockerNetwork) if err != nil { return nil, err } - kafkaContainer, err := NewKafkaContainer(ctx, kafkaCfg, zooCfg, dockerNetwork) + kafkaContainer, err := NewKafkaContainer(ctx, kafkaCfg, dockerNetwork) if err != nil { return nil, err } @@ -46,6 +45,7 @@ func NewKafkaCluster( return &KafkaCluster{ ZookeeperContainer: zookeeperContainer, KafkaContainer: kafkaContainer, + KafkaURI: fmt.Sprintf("%s:%s", kafkaCfg.Name, kafkaCfg.Port), kafkaCfg: kafkaCfg, zooCfg: zooCfg, }, nil @@ -54,54 +54,19 @@ func NewKafkaCluster( // NewKafkaContainer - create new kafka container, but do not start it yet. func NewKafkaContainer( ctx context.Context, - kafkaCfg KafkaConfig, - zooCfg ZookeeperConfig, - dockerNetwork *testcontainers.DockerNetwork, -) (testcontainers.Container, error) { - if len(kafkaCfg.ExposedPorts) == 0 { - kafkaCfg.ExposedPorts = []string{kafkaCfg.ClientPort} - } - - if len(kafkaCfg.Envs) == 0 { - kafkaCfg.Envs = map[string]string{ - "KAFKA_BROKER_ID": "1", - "KAFKA_ZOOKEEPER_CONNECT": "zookeeper:" + zooCfg.Port, - "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP": "PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT", - "KAFKA_ADVERTISED_LISTENERS": "PLAINTEXT://" + kafkaCfg.Name + ":29092,PLAINTEXT_HOST://localhost:" + - kafkaCfg.BrokerPort, - "KAFKA_METRIC_REPORTERS": "io.confluent.metrics.reporter.ConfluentMetricsReporter", - "KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR": "1", - "KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS": "0", - "KAFKA_CONFLUENT_LICENSE_TOPIC_REPLICATION_FACTOR": "1", - "KAFKA_CONFLUENT_BALANCER_TOPIC_REPLICATION_FACTOR": "1", - "KAFKA_TRANSACTION_STATE_LOG_MIN_ISR": "1", - "KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR": "1", - "KAFKA_JMX_PORT": "9101", - "KAFKA_JMX_HOSTNAME": "localhost", - "KAFKA_CONFLUENT_SCHEMA_REGISTRY_URL": "http://schema-registry:8089", - "CONFLUENT_METRICS_REPORTER_BOOTSTRAP_SERVERS": kafkaCfg.Name + ":29092", - "CONFLUENT_METRICS_REPORTER_TOPIC_REPLICAS": "1", - "CONFLUENT_METRICS_ENABLE": "true", - "CONFLUENT_SUPPORT_CUSTOMER_ID": "anonymous", - "KAFKA_AUTO_CREATE_TOPICS.ENABLE": "true", - } - } - + cfg KafkaConfig, + dockerNetwork *DockerNetwork, +) (Container, error) { // creates the kafka container, but do not start it yet - return testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ - ContainerRequest: GetBaseContainerRequest(kafkaCfg.BaseContainerConfig, dockerNetwork), - }) + return NewGenericContainer(ctx, cfg.BaseContainerConfig, dockerNetwork) } // Start - start ZookeeperContainer and KafkaContainer. func (c *KafkaCluster) Start(ctx context.Context) error { if err := c.ZookeeperContainer.Start(ctx); err != nil { - return fmt.Errorf("could not start container. err: %w", err) + return fmt.Errorf("could not start Zookeeper container. err: %w", err) } - const waitZoo = 5 * time.Second - time.Sleep(waitZoo) // time sleep development - return c.KafkaContainer.Start(ctx) } diff --git a/testcontainer/network.go b/testcontainer/network.go index 6822640..9edc57b 100644 --- a/testcontainer/network.go +++ b/testcontainer/network.go @@ -2,19 +2,17 @@ package testcontainer import ( "context" - - "github.com/testcontainers/testcontainers-go" ) // NewDockerNetwork - create new docker network. -func NewDockerNetwork(ctx context.Context, name string) (testcontainers.Network, error) { +func NewDockerNetwork(ctx context.Context, name string) (Network, error) { if err := PullDockerImage(ctx, ReaperImage); err != nil { return nil, err } - return testcontainers.GenericNetwork( - ctx, testcontainers.GenericNetworkRequest{ - NetworkRequest: testcontainers.NetworkRequest{ + return GenericNetwork( + ctx, GenericNetworkRequest{ + NetworkRequest: NetworkRequest{ Name: name, ReaperImage: ReaperImage, SkipReaper: IsSkipReaperImage, diff --git a/testcontainer/nginx.go b/testcontainer/nginx.go index 16e7d14..d25f571 100644 --- a/testcontainer/nginx.go +++ b/testcontainer/nginx.go @@ -2,11 +2,8 @@ package testcontainer import ( "context" - "fmt" - "github.com/docker/go-connections/nat" "github.com/testcontainers/testcontainers-go" - "github.com/testcontainers/testcontainers-go/wait" ) // NginxConfig - nginx container config. @@ -14,48 +11,11 @@ type NginxConfig struct { BaseContainerConfig } -type nginxContainer struct { - testcontainers.Container - URI string -} - // NewNginxContainer - create nginx container, but do not start it yet. func NewNginxContainer( ctx context.Context, cfg NginxConfig, dockerNetwork *testcontainers.DockerNetwork, - runContainer bool, -) (*nginxContainer, error) { - cfg.ExposedPorts = []string{cfg.Port + "/tcp"} - - cr := GetBaseContainerRequest(cfg.BaseContainerConfig, dockerNetwork) - - cr.WaitingFor = wait.ForHTTP("/") - - container, err := testcontainers.GenericContainer(ctx, - testcontainers.GenericContainerRequest{ - ContainerRequest: cr, - Started: runContainer, - }, - ) - if err != nil { - return nil, err - } - - ip, err := container.Host(ctx) - if err != nil { - return nil, err - } - - mappedPort, err := container.MappedPort(ctx, nat.Port(cfg.Port)) - if err != nil { - return nil, err - } - - uri := fmt.Sprintf("http://%s:%s", ip, mappedPort.Port()) - - return &nginxContainer{ - Container: container, - URI: uri, - }, nil +) (Container, error) { + return NewGenericContainer(ctx, cfg.BaseContainerConfig, dockerNetwork) } diff --git a/testcontainer/postgres.go b/testcontainer/postgres.go index 1470a8f..f60f82f 100644 --- a/testcontainer/postgres.go +++ b/testcontainer/postgres.go @@ -2,11 +2,6 @@ package testcontainer import ( "context" - "fmt" - - "github.com/docker/go-connections/nat" - "github.com/testcontainers/testcontainers-go" - "github.com/testcontainers/testcontainers-go/wait" ) // PostgresConfig - postgres container config. @@ -14,53 +9,11 @@ type PostgresConfig struct { BaseContainerConfig } -type postgresContainer struct { - testcontainers.Container - URI string -} - -// NewPostgresContainer - create nginx container, but do not start it yet. +// NewPostgresContainer - create postgres container, but do not start it yet. func NewPostgresContainer( ctx context.Context, cfg PostgresConfig, - dockerNetwork *testcontainers.DockerNetwork, - runContainer bool, -) (*postgresContainer, error) { - cfg.ExposedPorts = []string{cfg.Port + "/tcp"} - if len(cfg.Envs) == 0 { - cfg.Envs = map[string]string{ - "POSTGRES_USER": "postgres", - "POSTGRES_PASSWORD": "postgres", - "POSTGRES_DB": "postgres", - } - } - - cr := GetBaseContainerRequest(cfg.BaseContainerConfig, dockerNetwork) - - cr.WaitingFor = wait.ForHTTP(cfg.Port + "/tcp") - - container, err := testcontainers.GenericContainer(ctx, - testcontainers.GenericContainerRequest{ - ContainerRequest: cr, - Started: runContainer, - }, - ) - if err != nil { - return nil, err - } - - ip, err := container.Host(ctx) - if err != nil { - return nil, err - } - - mappedPort, err := container.MappedPort(ctx, nat.Port(cfg.Port)) - if err != nil { - return nil, err - } - - return &postgresContainer{ - Container: container, - URI: fmt.Sprintf("postgres://postgres:postgres@%s:%s/postgres", ip, mappedPort.Port()), - }, nil + dockerNetwork *DockerNetwork, +) (Container, error) { + return NewGenericContainer(ctx, cfg.BaseContainerConfig, dockerNetwork) } diff --git a/testcontainer/redis.go b/testcontainer/redis.go index d4837e3..0693795 100644 --- a/testcontainer/redis.go +++ b/testcontainer/redis.go @@ -2,12 +2,8 @@ package testcontainer import ( "context" - "fmt" - - "github.com/docker/go-connections/nat" "github.com/testcontainers/testcontainers-go" - "github.com/testcontainers/testcontainers-go/wait" ) // RedisConfig - redis container config. @@ -15,46 +11,11 @@ type RedisConfig struct { BaseContainerConfig } -type redisContainer struct { - testcontainers.Container - URI string -} - // NewRedisContainer - create redis container, but do not start it yet. func NewRedisContainer( ctx context.Context, cfg NginxConfig, dockerNetwork *testcontainers.DockerNetwork, - runContainer bool, -) (testcontainers.Container, error) { - cfg.ExposedPorts = []string{cfg.Port + "/tcp"} - - cr := GetBaseContainerRequest(cfg.BaseContainerConfig, dockerNetwork) - - cr.WaitingFor = wait.ForLog("* Ready to accept connections") - - container, err := testcontainers.GenericContainer(ctx, - testcontainers.GenericContainerRequest{ - ContainerRequest: cr, - Started: runContainer, - }, - ) - - if err != nil { - return nil, err - } - - mappedPort, err := container.MappedPort(ctx, nat.Port(cfg.Port)) - if err != nil { - return nil, err - } - - hostIP, err := container.Host(ctx) - if err != nil { - return nil, err - } - - uri := fmt.Sprintf("redis://%s:%s", hostIP, mappedPort.Port()) - - return &redisContainer{Container: container, URI: uri}, nil +) (Container, error) { + return NewGenericContainer(ctx, cfg.BaseContainerConfig, dockerNetwork) } diff --git a/testcontainer/testcontainer.go b/testcontainer/testcontainer.go index 19b5334..7a6359b 100644 --- a/testcontainer/testcontainer.go +++ b/testcontainer/testcontainer.go @@ -7,92 +7,167 @@ import ( "fmt" "io" "os" + "sync" "github.com/docker/docker/api/types" "github.com/docker/docker/client" "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" ) -// ReaperImage - is very important to re-define because by default testcontainers lib use reaper image based on docker.io -> -// https://github.com/testcontainers/testcontainers-go/blob/6ba6e7a0e4b0046507c28e24946d595a65a96dbf/reaper.go#L24 +// Type Aliasing for reduce code line size. Remove external deps from other places too. +type ( + Container = testcontainers.Container + ContainerRequest = testcontainers.ContainerRequest + ContainerFile = testcontainers.ContainerFile + + Network = testcontainers.Network + DockerNetwork = testcontainers.DockerNetwork + NetworkRequest = testcontainers.NetworkRequest + + GenericContainerRequest = testcontainers.GenericContainerRequest + GenericNetworkRequest = testcontainers.GenericNetworkRequest +) + +// Func aliasing - for reduce code line size. Remove external deps from other places too. var ( - IsSkipReaperImage = true - ReaperImage = "docker.io/testcontainers/ryuk:0.3.3" + GenericContainer = testcontainers.GenericContainer + GenericNetwork = testcontainers.GenericNetwork + + Mounts = testcontainers.Mounts + BindMount = testcontainers.BindMount ) +// BaseContainerConfig - base container request config. +type BaseContainerConfig struct { + Name string // Hostname, ContainerName, Network alias + Image string + Port string + Files []ContainerFile + Binds []string + Envs map[string]string + Cmd []string + ExposedPorts []string + AutoRemove bool + WaitingForStrategy wait.Strategy +} + +// IsSkipReaperImage - is skip usage Reaper Image. +const IsSkipReaperImage = false // not skip usage reaper image on CI + +// ReaperImage - is very important to re-define because by default testcontainers lib use reaper image based on docker.io -> +// https://github.com/testcontainers/testcontainers-go/blob/6ba6e7a0e4b0046507c28e24946d595a65a96dbf/reaper.go#L24 +const ReaperImage = "" // use standard Reaper Image. + +// NoUseAuth - not used any docker auth. const NoUseAuth = "" +// RegistryTokenEnv - env with docker registry token from dp. +const RegistryTokenEnv = "" // not use any docker registry token for docker demon. + var ( - // AuthRegistryCredStr - auth encoded string for docker registry. - AuthRegistryCredStr = func() string { ////nolint: gochecknoglobals // this is for tests purposes. - mustMarshalFunc := func(s interface{}) []byte { - b, err := json.Marshal(s) - if err != nil { - panic(err) - } - - return b - } + once sync.Once + authRegistryCredStr string +) - if registryToken := os.Getenv("REGISTRY_TOKEN"); registryToken != "" { - return base64.URLEncoding.EncodeToString( - mustMarshalFunc( - &types.AuthConfig{ - RegistryToken: registryToken, - }), - ) - } +type ( + Terminated interface { + Terminate(ctx context.Context) error + } +) + +func TerminateIfNotNil(ctx context.Context, container Terminated) error { + if container == nil { + return nil + } + + return container.Terminate(ctx) +} +// GetAuthRegistryCredStr - return auth encoded string for docker registry. +func GetAuthRegistryCredStr() string { + registryToken := os.Getenv(RegistryTokenEnv) + if registryToken == "" { return NoUseAuth - }() -) + } -// PullDockerImage - pull docker image via docker pull cmd. -func PullDockerImage(ctx context.Context, imageName string) error { - if AuthRegistryCredStr != "" { - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - return fmt.Errorf("could not create dcoker client with custom not pull image: %s; err: %w", - imageName, err) - } + once.Do(func() { + authRegistryCredStr = getAuthRegistryCredStr(registryToken) + }) - out, err := cli.ImagePull(ctx, imageName, types.ImagePullOptions{RegistryAuth: AuthRegistryCredStr}) + return authRegistryCredStr +} + +// getAuthRegistryCredStr - convert registry token to auth registry cred str in base64 format. +func getAuthRegistryCredStr(registryToken string) string { + mustMarshalFunc := func(s interface{}) []byte { + b, err := json.Marshal(s) if err != nil { - return fmt.Errorf("could not pull image: %s; err: %w", imageName, err) + panic(err) } - defer func() { _ = out.Close() }() - _, _ = io.Copy(os.Stdout, out) + return b + } + + return base64.URLEncoding.EncodeToString( + mustMarshalFunc( + &types.AuthConfig{ + RegistryToken: registryToken, + })) +} + +// PullDockerImage - pull docker image via docker client. +func PullDockerImage(ctx context.Context, imageName string) error { + if GetAuthRegistryCredStr() == "" { + fmt.Println("WARN! `AuthRegistryCredStr` is empty. NOT FORCE PULL ANY IMAGES.") + + return nil } - fmt.Println("WARN! `AuthRegistryCredStr` is empty. NOT PULL ANY IMAGES.") + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return fmt.Errorf("could not create docker client with custom not pull image: %s; err: %w", + imageName, err) + } + + out, err := cli.ImagePull(ctx, imageName, types.ImagePullOptions{RegistryAuth: GetAuthRegistryCredStr()}) + if err != nil { + return fmt.Errorf("could not pull image: %s; err: %w", imageName, err) + } + + defer func() { _ = out.Close() }() + _, _ = io.Copy(os.Stdout, out) return nil } -// BaseContainerConfig - base container request config. -type BaseContainerConfig struct { - Name string // Hostname, ContainerName, Network alias - Image string - Port string - ExposedPorts []string - Envs map[string]string +// NewGenericContainer - create new generic container with BaseContainerConfig and DockerNetwork. +func NewGenericContainer(ctx context.Context, cfg BaseContainerConfig, dockerNetwork *DockerNetwork) (Container, error) { + return GenericContainer(ctx, GenericContainerRequest{ + ContainerRequest: GetBaseContainerRequest(cfg, dockerNetwork), + }) } // GetBaseContainerRequest - return base ContainerRequest. func GetBaseContainerRequest( cfg BaseContainerConfig, - dockerNetwork *testcontainers.DockerNetwork, -) testcontainers.ContainerRequest { - return testcontainers.ContainerRequest{ + dockerNetwork *DockerNetwork, +) ContainerRequest { + return ContainerRequest{ + Name: cfg.Name, ReaperImage: ReaperImage, SkipReaper: IsSkipReaperImage, - RegistryCred: AuthRegistryCredStr, + RegistryCred: GetAuthRegistryCredStr(), Hostname: cfg.Name, Image: cfg.Image, - ExposedPorts: []string{cfg.Port}, + ExposedPorts: cfg.ExposedPorts, Env: cfg.Envs, + Files: cfg.Files, + Binds: cfg.Binds, + Cmd: cfg.Cmd, Networks: []string{dockerNetwork.Name}, NetworkAliases: map[string][]string{dockerNetwork.Name: {cfg.Name}}, + AutoRemove: cfg.AutoRemove, + WaitingFor: cfg.WaitingForStrategy, } } diff --git a/testcontainer/zookeeper.go b/testcontainer/zookeeper.go index c240945..4a7811b 100644 --- a/testcontainer/zookeeper.go +++ b/testcontainer/zookeeper.go @@ -2,8 +2,6 @@ package testcontainer import ( "context" - - "github.com/testcontainers/testcontainers-go" ) // ZookeeperConfig - zookeeper container config. @@ -15,15 +13,8 @@ type ZookeeperConfig struct { func NewZookeeperContainer( ctx context.Context, cfg ZookeeperConfig, - dockerNetwork *testcontainers.DockerNetwork, -) (testcontainers.Container, error) { - cfg.ExposedPorts = []string{cfg.Port} - cfg.Envs = map[string]string{"ZOOKEEPER_CLIENT_PORT": cfg.Port, "ZOOKEEPER_TICK_TIME": "2000"} - + dockerNetwork *DockerNetwork, +) (Container, error) { // creates the zookeeper container, but do not start it yet - return testcontainers.GenericContainer(ctx, - testcontainers.GenericContainerRequest{ - ContainerRequest: GetBaseContainerRequest(cfg.BaseContainerConfig, dockerNetwork), - }, - ) + return NewGenericContainer(ctx, cfg.BaseContainerConfig, dockerNetwork) }