converge/integrationtest/integration_test.go

252 lines
7.1 KiB
Go

package integrationtest
import (
"context"
"fmt"
"git.wamblee.org/converge/pkg/support/ioutils"
"git.wamblee.org/converge/pkg/support/sshsupport"
"git.wamblee.org/converge/pkg/testsupport"
"github.com/stretchr/testify/suite"
testcontainers "github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/network"
"github.com/testcontainers/testcontainers-go/wait"
"io"
"log"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
const (
agentEntryPointTemplate = `
curl -v http://converge:8000/downloads/agent > agent
chmod 755 agent
echo "{{.publicKey}}" > .authorized_keys
cat .authorized_keys
echo "HOSTNAME=$( hostname )"
./agent --id abc ws://converge:8000
tail -f /dev/null `
clientEntryPointTemplate = `
echo "HOSTNAME_OF_AGENT_ON_CLIENT=agent"
echo Ready
sleep 1000000
`
clientEntryPointTemplatex = `
curl -v http://converge:8000/downloads/wsproxy > wsproxy
chmod 755 wsproxy
echo "{{.privateKey}}" > id_rsa
chmod 400 id_rsa
echo "===================================================================================================="
echo "
hostname | sed 's/^/=============== HOSTNAME_OF_AGENT_ON_CLIENT=/g' " |
ssh -tt -oStrictHostKeyChecking=no -i id_rsa -oProxyCommand="./wsproxy ws://converge:8000/client/abc" localhost
echo "===================================================================================================="
echo Ready
sleep 1000000`
)
var (
version = getTag()
convergeImage = fmt.Sprintf("%s/converge:%s", getRegistry(), version)
agentImage = fmt.Sprintf("%s/converge-test:%s", getRegistry(), version)
clientImage = agentImage
)
func getTag() string {
tag, ok := os.LookupEnv("IMAGE_TAG")
if ok {
return tag
}
return "1.0.0"
}
func getRegistry() string {
registry, ok := os.LookupEnv("REGISTRY")
if ok {
return registry
}
moduleDir := testsupport.GetGoModDIr()
log.Printf("Go module directory %s", moduleDir)
envFile := filepath.Join(moduleDir, ".env")
file, err := os.Open(envFile)
if err != nil {
panic("Could not find .env file and REGISTRY env var is not set")
}
defer file.Close()
props, err := ioutils.ReadPropertyFile(file)
if err != nil {
panic(fmt.Sprintf("Error reading environment file '%v': %v", envFile, err))
}
registry, ok = props["REGISTRY"]
if !ok {
panic(fmt.Sprintf("Could not find REGISTRY setting in property file '%v'", envFile))
}
return registry
}
type IntegrationTestSuite struct {
suite.Suite
ctx context.Context
cancelFunc context.CancelFunc
}
func Test_IntegrationTestSuite(t *testing.T) {
suite.Run(t, &IntegrationTestSuite{})
}
func (s *IntegrationTestSuite) SetupSuite() {
}
func (s *IntegrationTestSuite) TearDownSuite() {
}
func (s *IntegrationTestSuite) SetupTest() {
ctx, cancelFunc := testsupport.CreateTestContext(context.Background(), 60*time.Second)
s.ctx = ctx
s.cancelFunc = cancelFunc
}
func (s *IntegrationTestSuite) TearDownTest() {
s.cancelFunc()
}
type ContainerLogger func(logtype string, msg string)
func logContainerOutput(container string, logtype string, msg string) {
log.Printf("%s: %s: %s", container, logtype, msg)
}
func containerLogger(container string) ContainerLogger {
return func(logtype string, msg string) {
logContainerOutput(container, logtype, msg)
}
}
func (c ContainerLogger) Accept(log testcontainers.Log) {
c(log.LogType, string(log.Content))
}
func createLogConsumerConfig(container string) *testcontainers.LogConsumerConfig {
return &testcontainers.LogConsumerConfig{
Opts: []testcontainers.LogProductionOption{testcontainers.WithLogProductionTimeout(10 * time.Second)},
Consumers: []testcontainers.LogConsumer{containerLogger(container)},
}
}
func (s *IntegrationTestSuite) runCmd(container testcontainers.Container, cmd []string) (int, string, error) {
status, reader, err := container.Exec(s.ctx, cmd)
if err != nil {
return 0, "", err
}
output, err := io.ReadAll(reader)
if err != nil {
return 0, "", err
}
return status, string(output), nil
}
func (s *IntegrationTestSuite) runCmdFailOnError(container testcontainers.Container, cmd []string) (int, string) {
status, output, err := s.runCmd(container, cmd)
s.Require().Nil(err)
return status, output
}
func (s *IntegrationTestSuite) defineContainer(net *testcontainers.DockerNetwork, hostname string,
image string,
logLine string,
ports []string) testcontainers.GenericContainerRequest {
log.Printf("PORTS %v", ports)
waitStrategies := []wait.Strategy{wait.ForLog(logLine)}
if len(ports) > 0 {
waitStrategies = append(waitStrategies, wait.ForExposedPort())
}
request := testcontainers.GenericContainerRequest{
ContainerRequest: testcontainers.ContainerRequest{
Image: image,
ExposedPorts: ports,
Hostname: hostname,
WaitingFor: wait.ForAll(waitStrategies...).WithStartupTimeoutDefault(30 * time.Second),
LogConsumerCfg: createLogConsumerConfig(hostname),
Networks: []string{net.Name},
//NetworkAliases: map[string][]string{
// net.Name: []string{hostname},
//},
},
Started: false,
}
return request
}
func (s *IntegrationTestSuite) Test_SingleAgentAndClient() {
net, err := network.New(s.ctx)
s.Require().Nil(err)
defer net.Remove(s.ctx)
// SSH key to use
privateKey, publicKey, err := sshsupport.GeneratePrivatePublicKey(2048)
s.Require().Nil(err)
// The server
log.Println("Starting converge server")
converge, err := testcontainers.GenericContainer(s.ctx,
s.defineContainer(net, "converge",
convergeImage,
"Rendez-vous server listening",
[]string{"8000/tcp"}))
s.Require().Nil(err)
err = converge.Start(s.ctx)
s.Require().Nil(err)
defer converge.Terminate(s.ctx)
port, err := converge.MappedPort(s.ctx, "8000/tcp")
s.Require().Nil(err)
log.Printf("External port %v", port)
// The agent
log.Println("Starting agent")
agentEntrypoint := testsupport.Template(agentEntryPointTemplate,
map[string]string{"publicKey": publicKey})
agentRequest := s.defineContainer(net, "agent", agentImage,
"starting ssh server, waiting for debug sessions", []string{})
agentRequest.Entrypoint = []string{
"sh", "-c", agentEntrypoint}
agent, err := testcontainers.GenericContainer(s.ctx, agentRequest)
s.Require().Nil(err)
err = agent.Start(s.ctx)
log.Printf("Error from starting agent %+v", err)
s.Require().Nil(err)
defer agent.Terminate(s.ctx)
// The client
log.Println("Starting client")
clientEntrypoint := testsupport.Template(clientEntryPointTemplate,
map[string]string{"privateKey": privateKey})
clientRequest := s.defineContainer(net, "client", clientImage,
"Ready", []string{})
clientRequest.Entrypoint = []string{
"sh", "-c", clientEntrypoint}
client, err := testcontainers.GenericContainer(s.ctx, clientRequest)
s.Require().Nil(err)
err = client.Start(s.ctx)
log.Printf("Error from starting client %+v", err)
s.Require().Nil(err)
defer client.Terminate(s.ctx)
input, err := client.Logs(s.ctx)
s.Require().Nil(err)
inputBytes, err := io.ReadAll(input)
s.Require().Nil(err)
s.True(strings.Contains(string(inputBytes), "HOSTNAME_OF_AGENT_ON_CLIENT=agent"))
}