dea472f443
Replace internal/traefik/install.go binary+systemd installer with a
podman-container reconciler (R-024). The reconciler is idempotent:
inspect → start-if-stopped → pull+run-if-absent.
Container run flags (research-validated):
--restart=unless-stopped (not always; research Topic 6)
--network host (binds 127.0.0.1:8080/8443 on host/LXC loopback)
-v /etc/traefik/traefik.yml:ro (overrides baked default; C-58)
-v /etc/traefik/dynamic:ro (orca writes atomically via SSH-push)
-v /etc/orca/step-ca-root.crt:ro (future mTLS; v0.14 uses tls:{})
No :Z SELinux flag (research Topic 7)
C-50: ensurePodmanLocal/Remote installs podman if absent.
C-57: removeLegacySystemdUnitLocal/Remote stops+disables+removes
the v0.13 orca-traefik.service + /usr/local/bin/traefik before
starting the podman container (upgrade path).
upgrade.go cutover rewritten to use the reconciler.
TLS model fix (research Topic 4): drop certResolver: orca from
dynamic config (traefik v3.3 only supports acme/tailscale resolvers,
not CA-file-based). Emit tls: {} instead. Real mTLS via dynamic
tls.certificates + clientAuth.caFiles deferred to v0.15 (grill
G-003, confidence 0.55 < 0.60).
Callsites updated:
init.go: installTraefikLocal → ensureTraefikContainerLocal
linux/bootstrap.go: traefik.InstallRemote → EnsureTraefikContainerRemote
proxmox/bootstrap.go: same
traefik_install.go: wrapper updated
Tests: internal/traefik/install_test.go (new) — ImageRef, podmanRunArgs,
container-running/stopped/absent paths, legacy systemd removal (C-57).
---ci---
project: orca
phase: 2
milestone: v0.14
status: execute
---/ci---
167 lines
4.8 KiB
Go
167 lines
4.8 KiB
Go
package traefik
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestImageRef(t *testing.T) {
|
|
tests := []struct {
|
|
version string
|
|
want string
|
|
}{
|
|
{"v0.13.1", "git.cloudinit.dev/coreci/orca-traefik:v0.13.1"},
|
|
{"0.13.1", "git.cloudinit.dev/coreci/orca-traefik:v0.13.1"},
|
|
{"", "git.cloudinit.dev/coreci/orca-traefik:latest"},
|
|
{"dev", "git.cloudinit.dev/coreci/orca-traefik:latest"},
|
|
{"0.1.0-dev", "git.cloudinit.dev/coreci/orca-traefik:latest"},
|
|
{"v1.2.3-dev", "git.cloudinit.dev/coreci/orca-traefik:latest"},
|
|
}
|
|
for _, tt := range tests {
|
|
got := ImageRef(tt.version)
|
|
if got != tt.want {
|
|
t.Errorf("ImageRef(%q) = %q, want %q", tt.version, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPodmanRunArgs(t *testing.T) {
|
|
args := podmanRunArgs("git.cloudinit.dev/coreci/orca-traefik:v0.13.1")
|
|
joined := strings.Join(args, " ")
|
|
checks := []string{
|
|
"run -d",
|
|
"--name orca-traefik",
|
|
"--restart=unless-stopped",
|
|
"--network host",
|
|
"/etc/traefik/traefik.yml:/etc/traefik/traefik.yml:ro",
|
|
"/etc/traefik/dynamic:/etc/traefik/dynamic:ro",
|
|
"/etc/orca/step-ca-root.crt:/etc/orca/step-ca-root.crt:ro",
|
|
"git.cloudinit.dev/coreci/orca-traefik:v0.13.1",
|
|
}
|
|
for _, c := range checks {
|
|
if !strings.Contains(joined, c) {
|
|
t.Errorf("podmanRunArgs missing %q\nfull: %s", c, joined)
|
|
}
|
|
}
|
|
// Ensure no :Z flag (research Topic 7)
|
|
if strings.Contains(joined, ":Z") {
|
|
t.Errorf("podmanRunArgs should NOT contain :Z SELinux flag\nfull: %s", joined)
|
|
}
|
|
// Ensure --restart=always is NOT used (research Topic 6)
|
|
if strings.Contains(joined, "--restart=always") {
|
|
t.Errorf("podmanRunArgs should use --restart=unless-stopped, not --restart=always\nfull: %s", joined)
|
|
}
|
|
}
|
|
|
|
func TestEnsureTraefikContainerRemote_ContainerRunning(t *testing.T) {
|
|
var cmds []string
|
|
execFn := func(cmd string) ([]byte, error) {
|
|
cmds = append(cmds, cmd)
|
|
if strings.Contains(cmd, "podman inspect") {
|
|
return []byte("true\n"), nil
|
|
}
|
|
return []byte(""), nil
|
|
}
|
|
err := EnsureTraefikContainerRemote(context.Background(), "v0.13.1", execFn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
// Should have checked inspect and found it running — no pull/run.
|
|
if len(cmds) < 1 {
|
|
t.Fatal("expected at least 1 command (inspect)")
|
|
}
|
|
for _, c := range cmds {
|
|
if strings.Contains(c, "podman pull") {
|
|
t.Errorf("should not pull when container is running: %s", c)
|
|
}
|
|
if strings.Contains(c, "podman run") {
|
|
t.Errorf("should not run when container is running: %s", c)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEnsureTraefikContainerRemote_ContainerStopped(t *testing.T) {
|
|
var cmds []string
|
|
execFn := func(cmd string) ([]byte, error) {
|
|
cmds = append(cmds, cmd)
|
|
if strings.Contains(cmd, "podman inspect") {
|
|
return []byte("false\n"), nil // stopped
|
|
}
|
|
return []byte(""), nil
|
|
}
|
|
err := EnsureTraefikContainerRemote(context.Background(), "v0.13.1", execFn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
// Should have started the container.
|
|
foundStart := false
|
|
for _, c := range cmds {
|
|
if strings.Contains(c, "podman start orca-traefik") {
|
|
foundStart = true
|
|
}
|
|
}
|
|
if !foundStart {
|
|
t.Errorf("expected 'podman start orca-traefik' when container is stopped\ncommands: %v", cmds)
|
|
}
|
|
}
|
|
|
|
func TestEnsureTraefikContainerRemote_ContainerAbsent(t *testing.T) {
|
|
var cmds []string
|
|
execFn := func(cmd string) ([]byte, error) {
|
|
cmds = append(cmds, cmd)
|
|
if strings.Contains(cmd, "podman inspect") {
|
|
return nil, &execError{"inspect failed: no such container"}
|
|
}
|
|
return []byte(""), nil
|
|
}
|
|
err := EnsureTraefikContainerRemote(context.Background(), "v0.13.1", execFn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
// Should have pulled and run.
|
|
foundPull := false
|
|
foundRun := false
|
|
for _, c := range cmds {
|
|
if strings.Contains(c, "podman pull") {
|
|
foundPull = true
|
|
}
|
|
if strings.Contains(c, "podman run -d") {
|
|
foundRun = true
|
|
}
|
|
}
|
|
if !foundPull {
|
|
t.Errorf("expected 'podman pull' when container is absent\ncommands: %v", cmds)
|
|
}
|
|
if !foundRun {
|
|
t.Errorf("expected 'podman run -d' when container is absent\ncommands: %v", cmds)
|
|
}
|
|
}
|
|
|
|
func TestEnsureTraefikContainerRemote_LegacySystemdRemoval(t *testing.T) {
|
|
var cmds []string
|
|
execFn := func(cmd string) ([]byte, error) {
|
|
cmds = append(cmds, cmd)
|
|
if strings.Contains(cmd, "podman inspect") {
|
|
return []byte("true\n"), nil // container running
|
|
}
|
|
return []byte(""), nil
|
|
}
|
|
_ = EnsureTraefikContainerRemote(context.Background(), "v0.13.1", execFn)
|
|
// Should include legacy systemd unit removal command (C-57).
|
|
foundLegacyRemoval := false
|
|
for _, c := range cmds {
|
|
if strings.Contains(c, "orca-traefik.service") && strings.Contains(c, "stop") {
|
|
foundLegacyRemoval = true
|
|
}
|
|
}
|
|
if !foundLegacyRemoval {
|
|
t.Errorf("expected legacy systemd unit removal command (C-57)\ncommands: %v", cmds)
|
|
}
|
|
}
|
|
|
|
// execError is a simple error type for testing.
|
|
type execError struct{ msg string }
|
|
|
|
func (e *execError) Error() string { return e.msg }
|