// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package cluster import ( "context" "fmt" "io" "strconv" "strings" "time" "github.com/hashicorp/consul/test/integration/consul-container/libs/utils" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) type ConsulDataplaneContainer struct { ctx context.Context container testcontainers.Container ip string appPort []int serviceName string externalAdminPort int internalAdminPort int } func (g ConsulDataplaneContainer) GetAddr() (string, int) { return g.ip, g.appPort[0] } func (g ConsulDataplaneContainer) GetServiceName() string { return g.serviceName } // GetAdminAddr returns the external admin port func (g ConsulDataplaneContainer) GetAdminAddr() (string, int) { return "localhost", g.externalAdminPort } func (c ConsulDataplaneContainer) Terminate() error { return TerminateContainer(c.ctx, c.container, true) } func (g ConsulDataplaneContainer) Exec(ctx context.Context, cmd []string) (string, error) { exitCode, reader, err := g.container.Exec(ctx, cmd) if err != nil { return "", fmt.Errorf("exec with error %s", err) } if exitCode != 0 { return "", fmt.Errorf("exec with exit code %d", exitCode) } buf, err := io.ReadAll(reader) if err != nil { return "", fmt.Errorf("error reading from exec output: %w", err) } return string(buf), nil } func (g ConsulDataplaneContainer) GetStatus() (string, error) { state, err := g.container.State(g.ctx) return state.Status, err } func NewConsulDataplane(ctx context.Context, proxyID string, serverAddresses string, grpcPort int, serviceBindPorts []int, node Agent, tproxy bool, bootstrapToken string, containerArgs ...string) (*ConsulDataplaneContainer, error) { namePrefix := fmt.Sprintf("%s-consul-dataplane-%s", node.GetDatacenter(), proxyID) containerName := utils.RandName(namePrefix) internalAdminPort, err := node.ClaimAdminPort() if err != nil { return nil, err } pod := node.GetPod() if pod == nil { return nil, fmt.Errorf("node Pod is required") } var ( appPortStrs []string adminPortStr = strconv.Itoa(internalAdminPort) ) for _, port := range serviceBindPorts { appPortStrs = append(appPortStrs, strconv.Itoa(port)) } // expose the app ports and the envoy adminPortStr on the agent container exposedPorts := make([]string, len(appPortStrs)) copy(exposedPorts, appPortStrs) exposedPorts = append(exposedPorts, adminPortStr) req := testcontainers.ContainerRequest{ Image: "consul-dataplane:local", WaitingFor: wait.ForLog("").WithStartupTimeout(60 * time.Second), AutoRemove: false, Name: containerName, Env: map[string]string{}, } var command []string if tproxy { req.Entrypoint = []string{"sh", "/bin/tproxy-startup.sh"} req.Env["REDIRECT_TRAFFIC_ARGS"] = strings.Join( []string{ // TODO once we run this on a different pod from Consul agents, we can eliminate most of this. "-exclude-inbound-port", fmt.Sprint(internalAdminPort), "-exclude-inbound-port", "8300", "-exclude-inbound-port", "8301", "-exclude-inbound-port", "8302", "-exclude-inbound-port", "8500", "-exclude-inbound-port", "8502", "-exclude-inbound-port", "8600", "-proxy-inbound-port", "20000", "-consul-dns-ip", "127.0.0.1", "-consul-dns-port", "8600", }, " ", ) req.CapAdd = append(req.CapAdd, "NET_ADMIN") command = append(command, "consul-dataplane") } command = append(command, "-addresses", serverAddresses, fmt.Sprintf("-grpc-port=%d", grpcPort), fmt.Sprintf("-proxy-id=%s", proxyID), "-proxy-namespace=default", "-proxy-partition=default", "-log-level=info", "-log-json=false", "-envoy-concurrency=2", "-tls-disabled", fmt.Sprintf("-envoy-admin-bind-port=%d", internalAdminPort), ) if bootstrapToken != "" { command = append(command, "-credential-type=static", fmt.Sprintf("-static-token=%s", bootstrapToken)) } req.Cmd = append(command, containerArgs...) info, err := LaunchContainerOnNode(ctx, node, req, exposedPorts) if err != nil { return nil, err } out := &ConsulDataplaneContainer{ ctx: ctx, container: info.Container, ip: info.IP, serviceName: containerName, externalAdminPort: info.MappedPorts[adminPortStr].Int(), internalAdminPort: internalAdminPort, } for _, port := range appPortStrs { out.appPort = append(out.appPort, info.MappedPorts[port].Int()) } fmt.Printf("NewConsulDataplane: proxyID %s, mapped App Port %d, service bind port %v\n", proxyID, out.appPort, serviceBindPorts) fmt.Printf("NewConsulDataplane: proxyID %s, , mapped admin port %d, admin port %d\n", proxyID, out.externalAdminPort, internalAdminPort) return out, nil }