Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions pkg/toolsets/core/get_cluster_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,20 @@ type getClusterImagesParams struct {
Clusters []string `json:"clusters,omitempty" jsonschema:"list of clusters to get images from. Empty to return images for all clusters"`
}

// getClusterImages retrieves all container images used across specified clusters.
type podReference struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
}

type imageUsage struct {
Image string `json:"image"`
Pods []podReference `json:"pods"`
}

// getClusterImages retrieves all container images used across specified clusters,
// along with the pods (name and namespace) using each image.
// If no clusters are provided, it fetches images from all available clusters.
// Returns a JSON map of cluster names to lists of container images.
// Returns a JSON map of cluster names to lists of image usage entries.
func (t *Tools) getClusterImages(ctx context.Context, toolReq *mcp.CallToolRequest, params getClusterImagesParams) (*mcp.CallToolResult, any, error) {
zap.L().Debug("getClusterImages called")

Expand All @@ -42,10 +53,12 @@ func (t *Tools) getClusterImages(ctx context.Context, toolReq *mcp.CallToolReque
clusters = params.Clusters
}

imagesInClusters := map[string][]string{}
imagesInClusters := map[string][]imageUsage{}

for _, cluster := range clusters {
images := []string{}
imageIndex := map[string]int{} // image name → index in slice
var usages []imageUsage

unstructuredPods, err := t.client.GetResources(ctx, client.ListParams{
Cluster: cluster,
Kind: "pod",
Expand All @@ -61,15 +74,28 @@ func (t *Tools) getClusterImages(ctx context.Context, toolReq *mcp.CallToolReque
zap.L().Error("failed convert unstructured object to Pod", zap.String("tool", "getClusterImages"), zap.Error(err))
return nil, nil, fmt.Errorf("failed to convert unstructured object to Pod: %w", err)
}
for _, container := range pod.Spec.InitContainers {
images = append(images, container.Image)
ref := podReference{Name: pod.Name, Namespace: pod.Namespace}
var containerImages []string
for _, c := range pod.Spec.InitContainers {
containerImages = append(containerImages, c.Image)
}
for _, c := range pod.Spec.Containers {
containerImages = append(containerImages, c.Image)
}
for _, container := range pod.Spec.Containers {
images = append(images, container.Image)
for _, image := range containerImages {
if idx, ok := imageIndex[image]; ok {
usages[idx].Pods = append(usages[idx].Pods, ref)
} else {
imageIndex[image] = len(usages)
usages = append(usages, imageUsage{Image: image, Pods: []podReference{ref}})
}
}
}

imagesInClusters[cluster] = images
if usages == nil {
usages = []imageUsage{}
}
imagesInClusters[cluster] = usages
}

response, err := json.Marshal(imagesInClusters)
Expand Down
12 changes: 10 additions & 2 deletions pkg/toolsets/core/get_cluster_images_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ func TestGetClusterImages(t *testing.T) {
{Group: "", Version: "v1", Resource: "pods"}: "PodList",
}, fakePodWithImage),
expectedResult: `{
"local": ["busybox:latest", "nginx:1.21", "redis:alpine"]
"local": [
{"image": "busybox:latest", "pods": [{"name": "test-pod", "namespace": "default"}]},
{"image": "nginx:1.21", "pods": [{"name": "test-pod", "namespace": "default"}]},
{"image": "redis:alpine", "pods": [{"name": "test-pod", "namespace": "default"}]}
]
}`,
},
"get images from cluster with no pods": {
Expand All @@ -91,7 +95,11 @@ func TestGetClusterImages(t *testing.T) {
}, fakePodWithImage),
rancherURL: fakeUrl,
expectedResult: `{
"local": ["busybox:latest", "nginx:1.21", "redis:alpine"]
"local": [
{"image": "busybox:latest", "pods": [{"name": "test-pod", "namespace": "default"}]},
{"image": "nginx:1.21", "pods": [{"name": "test-pod", "namespace": "default"}]},
{"image": "redis:alpine", "pods": [{"name": "test-pod", "namespace": "default"}]}
]
}`,
},
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/toolsets/core/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func (t *Tools) AddTools(mcpServer *mcp.Server) {
Meta: map[string]any{
toolsSetAnn: toolsSet,
},
Description: `Returns a list of all container images for the specified clusters. If clusters is empty, returns images for all clusters.`},
Description: `Returns all container images running across the specified clusters, along with the pods (name and namespace) using each image. Use in priority this tool to audit clusters for container registry or image usage, or to find which pods are running a specific container image. If clusters is empty, returns data for all clusters.`},
t.getClusterImages,
)

Expand Down
Loading