validateContainerImages validates that container images specified in MCP configs exist and are accessible
(workflowData *WorkflowData)
| 180 | |
| 181 | // validateContainerImages validates that container images specified in MCP configs exist and are accessible |
| 182 | func (c *Compiler) validateContainerImages(workflowData *WorkflowData) error { |
| 183 | if workflowData.Tools == nil { |
| 184 | runtimeValidationLog.Print("No tools configured, skipping container validation") |
| 185 | return nil |
| 186 | } |
| 187 | |
| 188 | runtimeValidationLog.Printf("Validating container images for %d tools", len(workflowData.Tools)) |
| 189 | |
| 190 | // Snapshot daemon availability before iterating so we can detect a |
| 191 | // mid-loop transition (available → unavailable) and emit exactly one |
| 192 | // warning for it, correctly counted in the compiler's warning total. |
| 193 | daemonWasAvailable := isDockerDaemonRunning() |
| 194 | |
| 195 | var errors []string |
| 196 | for toolName, toolConfig := range workflowData.Tools { |
| 197 | if config, ok := toolConfig.(map[string]any); ok { |
| 198 | // Get the MCP configuration to extract container info |
| 199 | mcpConfig, err := getMCPConfig(config, toolName) |
| 200 | if err != nil { |
| 201 | // If we can't parse the MCP config, skip validation (will be caught elsewhere) |
| 202 | continue |
| 203 | } |
| 204 | |
| 205 | // Check if this tool originally had a container field (before transformation) |
| 206 | if containerName, hasContainer := config["container"]; hasContainer && mcpConfig.Type == "stdio" { |
| 207 | // Build the full container image name with version |
| 208 | containerStr, ok := containerName.(string) |
| 209 | if !ok { |
| 210 | continue |
| 211 | } |
| 212 | |
| 213 | containerImage := containerStr |
| 214 | if version, hasVersion := config["version"]; hasVersion { |
| 215 | if versionStr, ok := version.(string); ok && versionStr != "" { |
| 216 | containerImage = containerImage + ":" + versionStr |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | // Validate the container image exists using docker |
| 221 | if err := validateDockerImage(containerImage, c.verbose, c.requireDocker); err != nil { |
| 222 | errors = append(errors, fmt.Sprintf("tool '%s': %v", toolName, err)) |
| 223 | } |
| 224 | } |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | // If the daemon appeared available at the start of the loop but became |
| 229 | // unreachable during a pull (requireDocker=false path only), emit a single |
| 230 | // visible warning here where we can also increment the warning count. |
| 231 | // For requireDocker=true, the per-image errors are already returned below |
| 232 | // and surfaced as a warning by the caller — no extra warning is needed. |
| 233 | if daemonWasAvailable && !isDockerDaemonRunning() && !c.requireDocker { |
| 234 | fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Docker daemon is not running — skipping container image validation")) |
| 235 | c.IncrementWarningCount() |
| 236 | } |
| 237 | |
| 238 | if len(errors) > 0 { |
| 239 | return NewValidationError( |