|
12 | 12 | // |
13 | 13 | // # Validation Pattern: Warning vs Error |
14 | 14 | // |
15 | | -// Docker image validation uses a flexible approach: |
16 | | -// - If Docker is not available, a warning is emitted but validation is skipped |
| 15 | +// Docker image validation returns errors for all failure cases. The caller |
| 16 | +// (validateContainerImages) collects these and surfaces them as compiler warnings: |
| 17 | +// - If Docker is not installed, returns an error |
| 18 | +// - If the Docker daemon is not running, returns an error (with fast timeout check) |
17 | 19 | // - If an image cannot be pulled due to authentication (private repo), validation passes |
18 | | -// - If an image truly doesn't exist, validation fails with an error |
| 20 | +// - If an image truly doesn't exist, returns an error |
19 | 21 | // - Verbose mode provides detailed validation feedback |
20 | 22 | // |
21 | 23 | // # When to Add Validation Here |
|
32 | 34 | package workflow |
33 | 35 |
|
34 | 36 | import ( |
| 37 | + "context" |
35 | 38 | "fmt" |
36 | | - "os" |
37 | 39 | "os/exec" |
38 | 40 | "strings" |
| 41 | + "sync" |
39 | 42 | "time" |
40 | 43 |
|
41 | | - "github.com/github/gh-aw/pkg/console" |
42 | 44 | "github.com/github/gh-aw/pkg/constants" |
43 | 45 | "github.com/github/gh-aw/pkg/logger" |
44 | 46 | ) |
45 | 47 |
|
46 | 48 | var dockerValidationLog = logger.New("workflow:docker_validation") |
47 | 49 |
|
48 | | -// validateDockerImage checks if a Docker image exists and is accessible |
49 | | -// Returns nil if docker is not available (with a warning printed) |
| 50 | +// dockerDaemonCheckTimeout is how long to wait for `docker info` to respond. |
| 51 | +// If the daemon isn't running, this prevents long hangs on every docker command. |
| 52 | +const dockerDaemonCheckTimeout = 3 * time.Second |
| 53 | + |
| 54 | +// Cached result of Docker daemon availability check. |
| 55 | +// Checked once per process to avoid repeated slow checks when daemon is down. |
| 56 | +var ( |
| 57 | + dockerDaemonOnce sync.Once |
| 58 | + dockerDaemonAvailable bool |
| 59 | +) |
| 60 | + |
| 61 | +// isDockerDaemonRunning checks if the Docker daemon is responsive. |
| 62 | +// Uses a short timeout to avoid hanging when Docker is installed but the daemon is stopped. |
| 63 | +// Results are cached for the process lifetime. |
| 64 | +func isDockerDaemonRunning() bool { |
| 65 | + dockerDaemonOnce.Do(func() { |
| 66 | + dockerValidationLog.Print("Checking if Docker daemon is running") |
| 67 | + ctx, cancel := context.WithTimeout(context.Background(), dockerDaemonCheckTimeout) |
| 68 | + defer cancel() |
| 69 | + |
| 70 | + cmd := exec.CommandContext(ctx, "docker", "info") |
| 71 | + cmd.Stdout = nil |
| 72 | + cmd.Stderr = nil |
| 73 | + err := cmd.Run() |
| 74 | + |
| 75 | + dockerDaemonAvailable = err == nil |
| 76 | + if !dockerDaemonAvailable { |
| 77 | + dockerValidationLog.Printf("Docker daemon not running or not responsive: %v", err) |
| 78 | + } else { |
| 79 | + dockerValidationLog.Print("Docker daemon is running") |
| 80 | + } |
| 81 | + }) |
| 82 | + return dockerDaemonAvailable |
| 83 | +} |
| 84 | + |
| 85 | +// validateDockerImage checks if a Docker image exists and is accessible. |
| 86 | +// Returns an error if Docker is not installed, the daemon is not running, |
| 87 | +// or the image cannot be found. The caller treats these as warnings. |
50 | 88 | func validateDockerImage(image string, verbose bool) error { |
51 | 89 | dockerValidationLog.Printf("Validating Docker image: %s", image) |
52 | 90 |
|
53 | | - // Check if docker is available |
| 91 | + // Check if docker CLI is available on PATH |
54 | 92 | _, err := exec.LookPath("docker") |
55 | 93 | if err != nil { |
56 | | - dockerValidationLog.Print("Docker not available, skipping image validation") |
57 | | - // Docker not available - print warning and skip validation |
58 | | - if verbose { |
59 | | - fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Docker not available - skipping validation for container image '%s'", image))) |
60 | | - } |
61 | | - return nil |
| 94 | + dockerValidationLog.Print("Docker not installed, cannot validate image") |
| 95 | + return fmt.Errorf("Docker not installed - could not validate container image '%s'. Install Docker or remove container-based tools", image) |
| 96 | + } |
| 97 | + |
| 98 | + // Check if Docker daemon is actually running (cached check with short timeout). |
| 99 | + // This prevents multi-minute hangs when Docker Desktop is installed but not running, |
| 100 | + // which is common on macOS development machines. |
| 101 | + if !isDockerDaemonRunning() { |
| 102 | + dockerValidationLog.Print("Docker daemon not running, cannot validate image") |
| 103 | + return fmt.Errorf("Docker daemon not running - could not validate container image '%s'. Start Docker Desktop or remove container-based tools", image) |
62 | 104 | } |
63 | 105 |
|
64 | 106 | // Try to inspect the image (will succeed if image exists locally) |
|
0 commit comments