collectDockerImages collects all Docker images used in MCP configurations. When workflowData.ActionCache contains container pins, the returned slice uses the pinned references (image:tag@sha256:…) instead of the bare tags, ensuring deterministic and supply-chain-safe image pulls.
(tools map[string]any, workflowData *WorkflowData, actionMode ActionMode)
| 17 | // the pinned references (image:tag@sha256:…) instead of the bare tags, ensuring |
| 18 | // deterministic and supply-chain-safe image pulls. |
| 19 | func collectDockerImages(tools map[string]any, workflowData *WorkflowData, actionMode ActionMode) []string { |
| 20 | var images []string |
| 21 | imageSet := make(map[string]struct{}) // Use a set to avoid duplicates |
| 22 | |
| 23 | // Check for GitHub tool (uses Docker image) |
| 24 | if rawGithubTool, hasGitHub := tools["github"]; hasGitHub { |
| 25 | // Only proceed when the value is an actual config map; a boolean false |
| 26 | // means the tool is explicitly disabled. |
| 27 | if githubTool, ok := rawGithubTool.(map[string]any); ok { |
| 28 | githubType := getGitHubType(githubTool) |
| 29 | // Only add if using local (Docker) mode |
| 30 | if githubType == GitHubMCPModeLocal { |
| 31 | githubDockerImageVersion := getGitHubDockerImageVersion(githubTool) |
| 32 | image := "ghcr.io/github/github-mcp-server:" + githubDockerImageVersion |
| 33 | if !setutil.Contains(imageSet, image) { |
| 34 | images = append(images, image) |
| 35 | imageSet[image] = struct { |
| 36 | }{} |
| 37 | } |
| 38 | } |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | // Check for Playwright tool (uses Docker image - no version tag, only one image) |
| 43 | // Only in MCP mode; CLI mode installs @playwright/cli via npm instead. |
| 44 | if _, hasPlaywright := tools["playwright"]; hasPlaywright { |
| 45 | if !isPlaywrightCLIMode(tools) { |
| 46 | image := "mcr.microsoft.com/playwright/mcp" |
| 47 | if !setutil.Contains(imageSet, image) { |
| 48 | images = append(images, image) |
| 49 | imageSet[image] = struct { |
| 50 | }{} |
| 51 | } |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | // Check for safe-outputs MCP server. |
| 56 | // Safe outputs run in the published gh-aw node container and must be part of |
| 57 | // the default predownload set and lock-file manifest whenever enabled. |
| 58 | if workflowData != nil && HasSafeOutputsEnabled(workflowData.SafeOutputs) { |
| 59 | image := constants.DefaultGhAwNodeImage |
| 60 | if !setutil.Contains(imageSet, image) { |
| 61 | images = append(images, image) |
| 62 | imageSet[image] = struct { |
| 63 | }{} |
| 64 | dockerLog.Printf("Added safe-outputs MCP server container: %s", image) |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | // Check for agentic-workflows tool |
| 69 | // In dev mode, the image is built locally in the workflow, so don't add to pull list |
| 70 | // In release/script mode, use alpine:latest which needs to be pulled |
| 71 | if _, hasAgenticWorkflows := tools["agentic-workflows"]; hasAgenticWorkflows { |
| 72 | if !actionMode.IsDev() { |
| 73 | // Release/script mode: Use alpine:latest (needs to be pulled) |
| 74 | image := constants.DefaultAlpineImage |
| 75 | if !setutil.Contains(imageSet, image) { |
| 76 | images = append(images, image) |