hako/e2e/e2eutil/container.go
Felipe M. 7f9cd3e477
Some checks failed
CI / goreleaser-lint (pull_request) Successful in 9s
CI / format (pull_request) Successful in 1m11s
CI / build (pull_request) Successful in 6m40s
CI / test (pull_request) Successful in 7m41s
CI / e2e (pull_request) Has been cancelled
CI / lint (pull_request) Has been cancelled
Merge origin/master into chore/upgrade-deps
Master migrated CI to Forgejo Actions and, independently, fixed the same
three e2e failures this branch did. Resolution takes master's side for CI
and e2e infrastructure and keeps this branch's dependency upgrades.

Superseded by master, dropped from this branch:
- .woodpecker pipelines, deleted in favour of .github/workflows. The
  branch gating and Go version bumps made here are moot: the workflows
  already target master and read go-version-file from go.mod.
- The host-gateway route added to reach the test web server. Master's
  shared Docker network with a container alias is the better fix; it does
  not depend on Docker Desktop and works under the Docker-in-Docker
  service the new e2e job uses. e2eutil/hostgateway.go is removed.
- Local formatting of e2e/e2eutil/helper.go, to keep master's file intact.

Kept from this branch:
- All dependency upgrades, including e2e on testcontainers 0.44 rather
  than master's 0.40. Master's container.go still called MappedPort with
  a nat.Port; 0.44 takes a string, so its call is adapted and the
  go-connections import dropped.
- golangci-lint v2.13.2 on the /v2 module path. Master left the v1.64.5
  pin and the pre-v2 path against a `version: "2"` config, so `make
  ci-lint` would still fail.

Both sides reached the same conclusions on the selector, CreateLink
navigation and IsVisible strict-mode fixes; master's wording of each is
kept. Containerfile stays on master's Alpine 3.23.
2026-09-22 17:37:53 +02:00

108 lines
3 KiB
Go

package e2eutil
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
// StartHakoContainer starts a Hako container for E2E testing.
// It builds the webapp, compiles the binary, creates a container, and returns
// the container instance and base URL for accessing the server.
func StartHakoContainer(ctx context.Context, t *testing.T) (testcontainers.Container, string, error) {
t.Helper()
// Get the project root (one level up from e2e)
projectRoot, err := filepath.Abs(filepath.Join("..", ".."))
if err != nil {
return nil, "", fmt.Errorf("failed to get project root: %w", err)
}
// Build webapp
t.Log("Building webapp...")
cmd := exec.CommandContext(ctx, "make", "build-webapp")
cmd.Dir = projectRoot
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return nil, "", fmt.Errorf("failed to build webapp: %w", err)
}
// Build Hako binary for the container (Linux)
t.Log("Building Hako binary...")
binaryPath := filepath.Join(projectRoot, "e2e", "hako")
cmd = exec.CommandContext(ctx, "go", "build", "-o", binaryPath, "./cmd/hako")
cmd.Dir = projectRoot
cmd.Env = append(os.Environ(), "CGO_ENABLED=0", "GOOS=linux", "GOARCH=amd64")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return nil, "", fmt.Errorf("failed to build binary: %w", err)
}
// Ensure binary is removed on cleanup
t.Cleanup(func() {
os.Remove(binaryPath)
})
// Ensure shared network exists so Hako can reach the test web server by alias.
nw, err := getSharedNetwork(ctx)
if err != nil {
return nil, "", err
}
// Create container request attached to the shared network.
req := testcontainers.ContainerRequest{
FromDockerfile: testcontainers.FromDockerfile{
Context: filepath.Join(projectRoot, "e2e"),
Dockerfile: "Dockerfile.e2e",
PrintBuildLog: true,
},
ExposedPorts: []string{"8080/tcp"},
WaitingFor: wait.ForLog("Starting HTTP server").
WithStartupTimeout(30 * time.Second),
Networks: []string{nw.Name},
}
// Start container
t.Log("Starting Hako container...")
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
return nil, "", fmt.Errorf("failed to start container: %w", err)
}
// Register cleanup
t.Cleanup(func() {
if err := container.Terminate(ctx); err != nil {
t.Logf("Failed to terminate container: %v", err)
}
})
// Get the mapped port
mappedPort, err := container.MappedPort(ctx, "8080/tcp")
if err != nil {
return nil, "", fmt.Errorf("failed to get mapped port: %w", err)
}
// Get the host
host, err := container.Host(ctx)
if err != nil {
return nil, "", fmt.Errorf("failed to get container host: %w", err)
}
// Construct base URL
baseURL := fmt.Sprintf("http://%s:%s", host, mappedPort.Port())
t.Logf("Hako container started at %s", baseURL)
return container, baseURL, nil
}