diff --git a/pkg/sync/common/types.go b/pkg/sync/common/types.go index b02ad8c20..c8c1e607b 100644 --- a/pkg/sync/common/types.go +++ b/pkg/sync/common/types.go @@ -116,7 +116,6 @@ func NewHookType(t string) (HookType, bool) { t == string(HookTypePostSync) || t == string(HookTypeSyncFail) || t == string(HookTypeSkip) - } type HookDeletePolicy string @@ -137,6 +136,8 @@ func NewHookDeletePolicy(p string) (HookDeletePolicy, bool) { type ResourceSyncResult struct { // holds associated resource key ResourceKey kube.ResourceKey + // holds the images associated with the resource + Images []string // holds resource version Version string // holds the execution order diff --git a/pkg/sync/sync_context.go b/pkg/sync/sync_context.go index 35981ebaa..1652d35f3 100644 --- a/pkg/sync/sync_context.go +++ b/pkg/sync/sync_context.go @@ -1248,7 +1248,7 @@ func (sc *syncContext) runTasks(tasks syncTasks, dryRun bool) runState { // finally create resources var tasksGroup syncTasks for _, task := range createTasks { - //Only wait if the type of the next task is different than the previous type + // Only wait if the type of the next task is different than the previous type if len(tasksGroup) > 0 && tasksGroup[0].targetObj.GetKind() != task.kind() { state = sc.processCreateTasks(state, tasksGroup, dryRun) tasksGroup = syncTasks{task} @@ -1308,6 +1308,7 @@ func (sc *syncContext) setResourceResult(task *syncTask, syncStatus common.Resul res := common.ResourceSyncResult{ ResourceKey: kube.GetResourceKey(task.obj()), + Images: kube.GetResourceImages(task.obj()), Version: task.version(), Status: task.syncStatus, Message: task.message, diff --git a/pkg/utils/kube/kube.go b/pkg/utils/kube/kube.go index f88ed172b..8ff1b808d 100644 --- a/pkg/utils/kube/kube.go +++ b/pkg/utils/kube/kube.go @@ -404,6 +404,34 @@ func GetDeploymentReplicas(u *unstructured.Unstructured) *int64 { return &val } +func GetResourceImages(u *unstructured.Unstructured) []string { + var images []string + + // Extract containers for resources like pods, without template + containers, found, err := unstructured.NestedSlice(u.Object, "spec", "containers") + if !found || err != nil { + // Extract containers for other resources that have template + containers, found, err = unstructured.NestedSlice(u.Object, "spec", "template", "spec", "containers") + if !found || err != nil { + return nil + } + } + + for _, container := range containers { + containerMap, ok := container.(map[string]interface{}) + if !ok { + return nil + } + image, found, err := unstructured.NestedString(containerMap, "image") + if !found || err != nil { + continue + } + images = append(images, image) + } + + return images +} + // RetryUntilSucceed keep retrying given action with specified interval until action succeed or specified context is done. func RetryUntilSucceed(ctx context.Context, interval time.Duration, desc string, log logr.Logger, action func() error) { pollErr := wait.PollUntilContextCancel(ctx, interval, true, func(ctx context.Context) (bool /*done*/, error) { diff --git a/pkg/utils/kube/kube_test.go b/pkg/utils/kube/kube_test.go index e2ecca3a3..8778644d4 100644 --- a/pkg/utils/kube/kube_test.go +++ b/pkg/utils/kube/kube_test.go @@ -56,7 +56,6 @@ func TestUnsetLabels(t *testing.T) { require.NoError(t, err) assert.Empty(t, dep.ObjectMeta.Labels) } - } func TestCleanKubectlOutput(t *testing.T) { @@ -167,6 +166,67 @@ spec: assert.Nil(t, GetDeploymentReplicas(&noDeployment)) } +func TestGetResourceImagesForResourcesWithTemplate(t *testing.T) { + manifest := []byte(` +apiVersion: extensions/v1beta2 +kind: Deployment +metadata: + name: nginx-deployment + labels: + foo: bar +spec: + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.7.9 + ports: + - containerPort: 80 + - name: agent + image: agent:1.0.0 +`) + + expected := []string{"nginx:1.7.9", "agent:1.0.0"} + + deployment := unstructured.Unstructured{} + err := yaml.Unmarshal([]byte(manifest), &deployment) + require.NoError(t, err) + + images := GetResourceImages(&deployment) + assert.Equal(t, expected, images) +} + +func TestGetResourceImagesForPod(t *testing.T) { + manifest := []byte(` +apiVersion: v1 +kind: Pod +metadata: + name: example-pod + labels: + app: my-app +spec: + containers: + - name: nginx-container + image: nginx:1.21 + ports: + - containerPort: 80 + - name: sidecar-container + image: busybox:1.35 + command: ["sh", "-c", "echo Hello from the sidecar; sleep 3600"] +`) + expected := []string{"nginx:1.21", "busybox:1.35"} + + pod := unstructured.Unstructured{} + err := yaml.Unmarshal([]byte(manifest), &pod) + require.NoError(t, err) + + images := GetResourceImages(&pod) + assert.Equal(t, expected, images) +} + func TestSplitYAML_SingleObject(t *testing.T) { objs, err := SplitYAML([]byte(depWithLabel)) require.NoError(t, err)