validateDockerImage checks if a Docker image exists and is accessible. When Docker is not installed or the daemon is not running, validation is silently skipped (returns nil) so that compile-time validation does not depend on Docker availability. If requireDocker is true, returns an error instead of
(image string, verbose bool, requireDocker bool)
| 105 | // Docker is available and the image cannot be found. The caller treats these |
| 106 | // as warnings. |
| 107 | func validateDockerImage(image string, verbose bool, requireDocker bool) error { |
| 108 | dockerValidationLog.Printf("Validating Docker image: %s", image) |
| 109 | |
| 110 | // Reject names starting with '-' to prevent argument injection |
| 111 | if strings.HasPrefix(image, "-") { |
| 112 | return fmt.Errorf("container image name '%s' is invalid: names must not start with '-'", image) |
| 113 | } |
| 114 | |
| 115 | // Check if docker CLI is available on PATH. |
| 116 | // If Docker is not installed, skip validation silently — compile is a source |
| 117 | // transformation step and should not require Docker at authoring time. |
| 118 | // When requireDocker is true, return an error instead of skipping. |
| 119 | _, err := exec.LookPath("docker") |
| 120 | if err != nil { |
| 121 | if requireDocker { |
| 122 | return fmt.Errorf("docker not installed - could not validate container image '%s'. Install Docker or omit the --validate-images flag to skip container image validation", image) |
| 123 | } |
| 124 | dockerValidationLog.Print("Docker not installed, skipping container image validation") |
| 125 | return nil |
| 126 | } |
| 127 | |
| 128 | // Check if Docker daemon is actually running (cached check with short timeout). |
| 129 | // If the daemon is not running (common on CI runners like ubuntu-slim, or when |
| 130 | // Docker Desktop is stopped), skip validation silently instead of emitting a |
| 131 | // warning. Image accessibility is a runtime concern, not a compile-time one. |
| 132 | // When requireDocker is true, return an error instead of skipping. |
| 133 | if !isDockerDaemonRunning() { |
| 134 | if requireDocker { |
| 135 | return fmt.Errorf("docker daemon not running - could not validate container image '%s'. Start the Docker daemon or omit the --validate-images flag to skip container image validation", image) |
| 136 | } |
| 137 | dockerValidationLog.Print("Docker daemon not running, skipping container image validation") |
| 138 | return nil |
| 139 | } |
| 140 | |
| 141 | // Try to inspect the image (will succeed if image exists locally) |
| 142 | cmd := exec.Command("docker", "image", "inspect", image) |
| 143 | _, err = cmd.CombinedOutput() |
| 144 | |
| 145 | if err == nil { |
| 146 | // Image exists locally |
| 147 | dockerValidationLog.Printf("Docker image found locally: %s", image) |
| 148 | return nil |
| 149 | } |
| 150 | |
| 151 | dockerValidationLog.Printf("Docker image not found locally, attempting to pull: %s", image) |
| 152 | |
| 153 | // Image doesn't exist locally, try to pull it with retry logic |
| 154 | maxAttempts := 3 |
| 155 | waitTime := 5 // seconds |
| 156 | |
| 157 | var lastOutput string |
| 158 | |
| 159 | for attempt := 1; attempt <= maxAttempts; attempt++ { |
| 160 | dockerValidationLog.Printf("Attempt %d of %d: Pulling image %s", attempt, maxAttempts, image) |
| 161 | |
| 162 | pullCmd := exec.Command("docker", "pull", image) |
| 163 | pullOutput, pullErr := pullCmd.CombinedOutput() |
| 164 | outputStr := strings.TrimSpace(string(pullOutput)) |