controller v0.267.0: tests off DooPlex's Docker, cut-off copies refused, two pages true
gates / gates (push) Successful in 26s
gates / gates (push) Successful in 26s
R-650: internal/dockerexec — every docker exec routed through it; under go test a real docker is refused (opt-in FELHOM_TEST_REAL_DOCKER=1; a stub under the temp dir is allowed). api/stacks/web tests run under a silent stub (TestMain). TestR650_NoBareDockerExec pins it repo-wide. R-640: a dump without its engine's completion marker is refused before the first mutation (unit + off-site restore) and again before any load. R-499: the Tier-2 page's system-disk sentence has four true branches. R-518: the backup button states the measured ~8 min stop. R-626: measured on 9202, not reproduced. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -2,8 +2,8 @@ package stacks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/dockerexec"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -45,7 +45,7 @@ func (m *Manager) guestNetExec(args ...string) (string, error) {
|
||||
if m.guestNetExecFn != nil {
|
||||
return m.guestNetExecFn(args...)
|
||||
}
|
||||
out, err := exec.Command("docker", append([]string{"exec", sambaContainer}, args...)...).Output()
|
||||
out, err := dockerexec.Command("docker", append([]string{"exec", sambaContainer}, args...)...).Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("docker exec %s: %w", strings.Join(args, " "), err)
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ package stacks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/dockerexec"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -221,7 +221,7 @@ func (m *Manager) wireController(traefikDir string) error {
|
||||
}
|
||||
|
||||
if !containerOnNetwork(controllerContainer, traefikNetwork) {
|
||||
out, err := exec.Command("docker", "network", "connect", traefikNetwork, controllerContainer).CombinedOutput()
|
||||
out, err := dockerexec.Command("docker", "network", "connect", traefikNetwork, controllerContainer).CombinedOutput()
|
||||
if err != nil && !strings.Contains(string(out), "already exists") {
|
||||
return fmt.Errorf("network connect %s: %s: %w", controllerContainer, strings.TrimSpace(string(out)), err)
|
||||
}
|
||||
@@ -234,7 +234,7 @@ func (m *Manager) wireController(traefikDir string) error {
|
||||
// We list the network names and match exactly — NOT `{{index .Networks "name"}}`, whose output for an
|
||||
// absent key is "<nil>" (a non-empty string), which would falsely read as "already attached".
|
||||
func containerOnNetwork(name, network string) bool {
|
||||
out, err := exec.Command("docker", "inspect", "--format",
|
||||
out, err := dockerexec.Command("docker", "inspect", "--format",
|
||||
"{{range $k, $_ := .NetworkSettings.Networks}}{{$k}}\n{{end}}", name).Output()
|
||||
if err != nil {
|
||||
return false
|
||||
@@ -250,14 +250,14 @@ func containerOnNetwork(name, network string) bool {
|
||||
// ensureTraefikNetwork creates the external traefik-public docker network if absent (idempotent;
|
||||
// tolerates a create/inspect race). Uses the docker CLI directly — it's a network op, not compose.
|
||||
func (m *Manager) ensureTraefikNetwork() error {
|
||||
if exec.Command("docker", "network", "inspect", traefikNetwork).Run() == nil {
|
||||
if dockerexec.Command("docker", "network", "inspect", traefikNetwork).Run() == nil {
|
||||
return nil
|
||||
}
|
||||
m.logger.Printf("[INFO] [infra] creating docker network %s", traefikNetwork)
|
||||
out, err := exec.Command("docker", "network", "create", traefikNetwork).CombinedOutput()
|
||||
out, err := dockerexec.Command("docker", "network", "create", traefikNetwork).CombinedOutput()
|
||||
if err != nil {
|
||||
// Tolerate a race where another actor created it between our inspect and create.
|
||||
if exec.Command("docker", "network", "inspect", traefikNetwork).Run() == nil {
|
||||
if dockerexec.Command("docker", "network", "inspect", traefikNetwork).Run() == nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("network create %s: %s: %w", traefikNetwork, strings.TrimSpace(string(out)), err)
|
||||
@@ -295,7 +295,7 @@ func writeInfraFiles(dir string, files map[string]infra.FileSpec) error {
|
||||
// containerRunning reports whether a container with the given name is currently running. It asks the
|
||||
// daemon directly (works before the stack dir exists), mirroring monitor.checkProtectedContainers.
|
||||
func containerRunning(name string) bool {
|
||||
out, err := exec.Command("docker", "inspect", "--format", "{{.State.Running}}", name).Output()
|
||||
out, err := dockerexec.Command("docker", "inspect", "--format", "{{.State.Running}}", name).Output()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package stacks
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/dockerexec"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
@@ -46,7 +46,7 @@ func (m *Manager) ReadInitialCredentials(stackName string) (*ExtractedCreds, err
|
||||
return &ExtractedCreds{Available: false}, nil
|
||||
}
|
||||
|
||||
out, err := exec.Command("docker", "exec", container, "cat", spec.File).Output()
|
||||
out, err := dockerexec.Command("docker", "exec", container, "cat", spec.File).Output()
|
||||
if err != nil {
|
||||
// File missing / container not exec-able yet — expected during early boot or after the
|
||||
// customer deletes the file. Not an error worth surfacing; hide the card.
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/dockerexec"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -32,7 +33,7 @@ const installedRecordTimeout = 30 * time.Second
|
||||
type execRunner func(ctx context.Context, dir string, env []string, name string, args ...string) (string, error)
|
||||
|
||||
func defaultExecRunner(ctx context.Context, dir string, env []string, name string, args ...string) (string, error) {
|
||||
cmd := exec.CommandContext(ctx, name, args...)
|
||||
cmd := dockerexec.CommandContext(ctx, name, args...)
|
||||
if dir != "" {
|
||||
cmd.Dir = dir
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/dockerexec"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -500,7 +501,7 @@ func toTitleCase(s string) string {
|
||||
}
|
||||
|
||||
func detectComposeCommand() string {
|
||||
if err := exec.Command("docker", "compose", "version").Run(); err == nil {
|
||||
if err := dockerexec.Command("docker", "compose", "version").Run(); err == nil {
|
||||
return "docker compose"
|
||||
}
|
||||
if _, err := exec.LookPath("docker-compose"); err == nil {
|
||||
@@ -1377,9 +1378,9 @@ func (m *Manager) composeExecCustomEnv(dir string, env []string, args ...string)
|
||||
|
||||
if m.composeCmd == "docker compose" {
|
||||
fullArgs := append([]string{"compose"}, args...)
|
||||
cmd = exec.Command("docker", fullArgs...)
|
||||
cmd = dockerexec.Command("docker", fullArgs...)
|
||||
} else {
|
||||
cmd = exec.Command("docker-compose", args...)
|
||||
cmd = dockerexec.Command("docker-compose", args...)
|
||||
}
|
||||
|
||||
cmd.Dir = dir
|
||||
@@ -1456,7 +1457,7 @@ func (m *Manager) execCommand(name string, args ...string) (string, error) {
|
||||
if m.execFn != nil {
|
||||
return m.execFn(name, args...)
|
||||
}
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd := dockerexec.Command(name, args...)
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
@@ -1545,7 +1546,7 @@ func (m *Manager) checkLocalImages(name, stackDir string) {
|
||||
|
||||
m.logger.Printf("[INFO] [stacks] Deploying stack %s — checking %d images...", name, len(images))
|
||||
for _, img := range images {
|
||||
cmd := exec.Command("docker", "image", "inspect", img)
|
||||
cmd := dockerexec.Command("docker", "image", "inspect", img)
|
||||
if err := cmd.Run(); err != nil {
|
||||
m.logger.Printf("[DEBUG] %s — not found locally, will pull", img)
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package stacks
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/dockerexec"
|
||||
)
|
||||
|
||||
// TestMain puts a silent docker stub on PATH for every test in this package: R-650, the fixtures
|
||||
// here build a real stacks.Manager, and on the build host (DooPlex) that reached production Docker.
|
||||
func TestMain(m *testing.M) { os.Exit(dockerexec.RunWithStub(m)) }
|
||||
@@ -2,10 +2,10 @@ package stacks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/dockerexec"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/util"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -98,7 +98,7 @@ func (m *Manager) SambaImagePresent() bool {
|
||||
if m.sambaImgFn != nil {
|
||||
return m.sambaImgFn()
|
||||
}
|
||||
return exec.Command("docker", "image", "inspect", infra.SambaImage).Run() == nil
|
||||
return dockerexec.Command("docker", "image", "inspect", infra.SambaImage).Run() == nil
|
||||
}
|
||||
|
||||
// shareAvailable reports whether a share's folder can be exported right now: its owning registered
|
||||
@@ -287,7 +287,7 @@ func (m *Manager) sambaSetPassword(password string) error {
|
||||
var lastErr error
|
||||
// The container may need a moment to accept exec right after `compose up -d`.
|
||||
for attempt := 1; attempt <= 10; attempt++ {
|
||||
cmd := exec.Command("docker", "exec", "-i", sambaContainer,
|
||||
cmd := dockerexec.Command("docker", "exec", "-i", sambaContainer,
|
||||
"smbpasswd", "-s", "-a", infra.SambaHouseholdUser)
|
||||
cmd.Stdin = strings.NewReader(password + "\n" + password + "\n")
|
||||
out, err := cmd.CombinedOutput()
|
||||
@@ -350,7 +350,7 @@ func (m *Manager) sambaLANAddr() (string, error) {
|
||||
if m.sambaAddrFn != nil {
|
||||
return m.sambaAddrFn()
|
||||
}
|
||||
out, err := exec.Command("docker", "exec", sambaContainer,
|
||||
out, err := dockerexec.Command("docker", "exec", sambaContainer,
|
||||
"ip", "-4", "-o", "addr", "show", infra.SambaHostInterface).Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("docker exec ip addr: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user