open-consul/test/integration/consul-container/libs/node/consul-container.go

150 lines
3.8 KiB
Go

package node
import (
"context"
"fmt"
"os"
"strconv"
"time"
"github.com/docker/docker/pkg/ioutils"
"github.com/hashicorp/consul/api"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
"github.com/hashicorp/consul/integration/consul-container/libs/utils"
)
const bootLogLine = "Consul agent running"
const disableRYUKEnv = "TESTCONTAINERS_RYUK_DISABLED"
// consulContainerNode implements the Node interface by running a Consul node
// in a container.
type consulContainerNode struct {
ctx context.Context
client *api.Client
container testcontainers.Container
ip string
port int
}
// NewConsulContainer starts a Consul node in a container with the given config.
func NewConsulContainer(ctx context.Context, config Config) (Node, error) {
license, err := readLicense()
if err != nil {
return nil, err
}
name := utils.RandName("consul-")
tmpDir, err := ioutils.TempDir("", name)
if err != nil {
return nil, err
}
err = os.Chmod(tmpDir, 0777)
if err != nil {
return nil, err
}
err = os.Mkdir(tmpDir+"/config", 0777)
if err != nil {
return nil, err
}
configFile := tmpDir + "/config/config.hcl"
err = os.WriteFile(configFile, []byte(config.HCL), 0644)
if err != nil {
return nil, err
}
skipReaper := isRYUKDisabled()
req := testcontainers.ContainerRequest{
Image: "consul:" + config.Version,
ExposedPorts: []string{"8500/tcp"},
WaitingFor: wait.ForLog(bootLogLine).WithStartupTimeout(10 * time.Second),
AutoRemove: false,
Name: name,
Mounts: testcontainers.ContainerMounts{testcontainers.ContainerMount{Source: testcontainers.DockerBindMountSource{HostPath: configFile}, Target: "/consul/config/config.hcl"}},
Cmd: config.Cmd,
SkipReaper: skipReaper,
Env: map[string]string{"CONSUL_LICENSE": license},
}
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
return nil, err
}
localIP, err := container.Host(ctx)
if err != nil {
return nil, err
}
mappedPort, err := container.MappedPort(ctx, "8500")
if err != nil {
return nil, err
}
ip, err := container.ContainerIP(ctx)
if err != nil {
return nil, err
}
uri := fmt.Sprintf("http://%s:%s", localIP, mappedPort.Port())
c := new(consulContainerNode)
c.container = container
c.ip = ip
c.port = mappedPort.Int()
apiConfig := api.DefaultConfig()
apiConfig.Address = uri
c.client, err = api.NewClient(apiConfig)
c.ctx = ctx
if err != nil {
return nil, err
}
return c, nil
}
// GetClient returns an API client that can be used to communicate with the Node.
func (c *consulContainerNode) GetClient() *api.Client {
return c.client
}
// GetAddr return the network address associated with the Node.
func (c *consulContainerNode) GetAddr() (string, int) {
return c.ip, c.port
}
// Terminate attempts to terminate the container. On failure, an error will be
// returned and the reaper process (RYUK) will handle cleanup.
func (c *consulContainerNode) Terminate() error {
return c.container.Terminate(c.ctx)
}
// isRYUKDisabled returns whether the reaper process (RYUK) has been disabled
// by an environment variable.
//
// https://github.com/testcontainers/moby-ryuk
func isRYUKDisabled() bool {
skipReaperStr := os.Getenv(disableRYUKEnv)
skipReaper, err := strconv.ParseBool(skipReaperStr)
if err != nil {
return false
}
return skipReaper
}
func readLicense() (string, error) {
license := os.Getenv("CONSUL_LICENSE")
if license == "" {
licensePath := os.Getenv("CONSUL_LICENSE_PATH")
if licensePath != "" {
licenseBytes, err := os.ReadFile(licensePath)
if err != nil {
return "", err
}
license = string(licenseBytes)
}
}
return license, nil
}