Skip to content

Commit

Permalink
Add repo digest to workloadmeta.container from docker runtime collect…
Browse files Browse the repository at this point in the history
…ion (#23045)

Add repo digest to workloadmeta.container from docker runtime collection
  • Loading branch information
zhuminyi authored Feb 23, 2024
1 parent 3fa1713 commit 018fc30
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/containerd/containerd/namespaces"

"github.com/DataDog/datadog-agent/comp/core/workloadmeta"
"github.com/DataDog/datadog-agent/comp/core/workloadmeta/collectors/util"
cutil "github.com/DataDog/datadog-agent/pkg/util/containerd"
"github.com/DataDog/datadog-agent/pkg/util/containers"
"github.com/DataDog/datadog-agent/pkg/util/log"
Expand All @@ -28,11 +29,6 @@ import (
// kataRuntimePrefix is the prefix used by Kata Containers runtime
const kataRuntimePrefix = "io.containerd.kata"

const (
// SHA256 is the prefix used by containerd for the repo digest
SHA256 = "@sha256:"
)

// buildWorkloadMetaContainer generates a workloadmeta.Container from a containerd.Container
func buildWorkloadMetaContainer(namespace string, container containerd.Container, containerdClient cutil.ContainerdItf, store workloadmeta.Component) (workloadmeta.Container, error) {
if container == nil {
Expand Down Expand Up @@ -66,7 +62,7 @@ func buildWorkloadMetaContainer(namespace string, container containerd.Container
log.Debugf("cannot split image name %q: %s", info.Image, err)
}

image.RepoDigest = extractRepoDigestFromImage(imageID, image.Registry, store) // "sha256:digest"
image.RepoDigest = util.ExtractRepoDigestFromImage(imageID, image.Registry, store) // "sha256:digest"

status, err := containerdClient.Status(namespace, container)
if err != nil {
Expand Down Expand Up @@ -161,56 +157,3 @@ func extractRuntimeFlavor(runtime string) workloadmeta.ContainerRuntimeFlavor {
}
return workloadmeta.ContainerRuntimeFlavorDefault
}

// extractRepoDigestFromImage extracts the repoDigest from workloadmeta store if it exists and unique
// the format of repoDigest is "registry/repo@sha256:digest", the format of return value is "sha256:digest"
func extractRepoDigestFromImage(imageID string, imageRegistry string, store workloadmeta.Component) string {
existingImgMetadata, err := store.GetImage(imageID)
if err == nil {
// If there is only one repoDigest, return it
if len(existingImgMetadata.RepoDigests) == 1 {
_, _, digest := parseRepoDigest(existingImgMetadata.RepoDigests[0])
return digest
}
// If there are multiple repoDigests, we should find the one that matches the current container's registry
for _, repoDigest := range existingImgMetadata.RepoDigests {
registry, _, digest := parseRepoDigest(repoDigest)
if registry == imageRegistry {
return digest
}
}
log.Debugf("cannot get matched registry in repodigests for image %s", imageID)
} else {
// TODO: we should handle the case when the image metadata is not found in the store
// For example, there could be a rare race condition when collection of image metadata is not finished yet
// In this case, we can either call containerd client directly or get it from knownImages in the local cache
// In fact, knownImages is already updated in the same goroutine, so it should be available
// If it is not available, containerd client can be used to get the image metadata
log.Debugf("cannot get image metadata for image %s: %s", imageID, err)
}
return ""
}

// parseRepoDigest extracts registry, repository, digest from repoDigest (in the format of "registry/repo@sha256:digest")
func parseRepoDigest(repoDigest string) (string, string, string) {
var registry, repository, digest string
imageName := repoDigest

digestStart := strings.Index(repoDigest, SHA256)
if digestStart != -1 {
digest = repoDigest[digestStart+len("@"):]
imageName = repoDigest[:digestStart]
}

registryEnd := strings.Index(imageName, "/")
// e.g <registry>/<repository>
if registryEnd != -1 {
registry = imageName[:registryEnd]
repository = imageName[registryEnd+len("/"):]
// e.g <registry>
} else {
registry = imageName
}

return registry, repository, digest
}
Original file line number Diff line number Diff line change
Expand Up @@ -200,60 +200,3 @@ func TestExtractRuntimeFlavor(t *testing.T) {
})
}
}

func TestParseRepoDigest(t *testing.T) {
for _, tc := range []struct {
repoDigest string
registry string
repository string
digest string
}{
{
repoDigest: "727006795293.dkr.ecr.us-east-1.amazonaws.com/spidly@sha256:fce79f86f7a3b9c660112da8484a8f5858a7da9e703892ba04c6f045da714300",
registry: "727006795293.dkr.ecr.us-east-1.amazonaws.com",
repository: "spidly",
digest: "sha256:fce79f86f7a3b9c660112da8484a8f5858a7da9e703892ba04c6f045da714300",
},
{
repoDigest: "docker.io/library/docker@sha256:b813c414ee36b8a2c44b45295698df6bdc3bdee4a435481dbb892e1b44e09d3b",
registry: "docker.io",
repository: "library/docker",
digest: "sha256:b813c414ee36b8a2c44b45295698df6bdc3bdee4a435481dbb892e1b44e09d3b",
},
{
repoDigest: "eu.gcr.io/datadog-staging/logs-event-store-api@sha256:747bd4fc36f3f263b5dcb9df907b98489a4cb46d636c223dc29f1fb7f9405070",
registry: "eu.gcr.io",
repository: "datadog-staging/logs-event-store-api",
digest: "sha256:747bd4fc36f3f263b5dcb9df907b98489a4cb46d636c223dc29f1fb7f9405070",
},
{
repoDigest: "registry.ddbuild.io/apm-integrations-testing/handmade/postgres",
registry: "registry.ddbuild.io",
repository: "apm-integrations-testing/handmade/postgres",
digest: "",
},
{
repoDigest: "registry.ddbuild.io/apm-integrations-testing/handmade/postgres@sha256:747bd4fc36f3f263b5dcb9df907b98489a4cb46d636c223dc29f1fb7f9405070",
registry: "registry.ddbuild.io",
repository: "apm-integrations-testing/handmade/postgres",
digest: "sha256:747bd4fc36f3f263b5dcb9df907b98489a4cb46d636c223dc29f1fb7f9405070",
},
{
repoDigest: "@sha256:747bd4fc36f3f263b5dcb9df907b98489a4cb46d636c223dc29f1fb7f9405070",
registry: "",
repository: "",
digest: "sha256:747bd4fc36f3f263b5dcb9df907b98489a4cb46d636c223dc29f1fb7f9405070",
},
{
repoDigest: "docker.io@sha256:747bd4fc36f3f263b5dcb9df907b98489a4cb46d636c223dc29f1fb7f9405070",
registry: "docker.io",
repository: "",
digest: "sha256:747bd4fc36f3f263b5dcb9df907b98489a4cb46d636c223dc29f1fb7f9405070",
},
} {
registry, repository, digest := parseRepoDigest(tc.repoDigest)
assert.Equal(t, tc.registry, registry)
assert.Equal(t, tc.repository, repository)
assert.Equal(t, tc.digest, digest)
}
}
9 changes: 5 additions & 4 deletions comp/core/workloadmeta/collectors/internal/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,12 @@ func (c *collector) Start(ctx context.Context, store workloadmeta.Component) err
return err
}

err = c.generateEventsFromContainerList(ctx, filter)
err = c.generateEventsFromImageList(ctx)
if err != nil {
return err
}

err = c.generateEventsFromImageList(ctx)
err = c.generateEventsFromContainerList(ctx, filter)
if err != nil {
return err
}
Expand Down Expand Up @@ -300,7 +300,7 @@ func (c *collector) buildCollectorEvent(ctx context.Context, ev *docker.Containe
Name: strings.TrimPrefix(container.Name, "/"),
Labels: container.Config.Labels,
},
Image: extractImage(ctx, container, c.dockerUtil.ResolveImageNameFromContainer),
Image: extractImage(ctx, container, c.dockerUtil.ResolveImageNameFromContainer, c.store),
EnvVars: extractEnvVars(container.Config.Env),
Ports: extractPorts(container),
Runtime: workloadmeta.ContainerRuntimeDocker,
Expand Down Expand Up @@ -345,7 +345,7 @@ func (c *collector) buildCollectorEvent(ctx context.Context, ev *docker.Containe
return event, nil
}

func extractImage(ctx context.Context, container types.ContainerJSON, resolve resolveHook) workloadmeta.ContainerImage {
func extractImage(ctx context.Context, container types.ContainerJSON, resolve resolveHook, store workloadmeta.Component) workloadmeta.ContainerImage {
imageSpec := container.Config.Image
image := workloadmeta.ContainerImage{
RawName: imageSpec,
Expand Down Expand Up @@ -396,6 +396,7 @@ func extractImage(ctx context.Context, container types.ContainerJSON, resolve re
image.ShortName = shortName
image.Tag = tag
image.ID = container.Image
image.RepoDigest = util.ExtractRepoDigestFromImage(image.ID, image.Registry, store) // "sha256:digest"
return image
}

Expand Down
70 changes: 70 additions & 0 deletions comp/core/workloadmeta/collectors/util/image_metadata_util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2023-present Datadog, Inc.

// Package util contains utility functions for image metadata collection
package util

import (
"strings"

"github.com/DataDog/datadog-agent/comp/core/workloadmeta"
"github.com/DataDog/datadog-agent/pkg/util/log"
)

const (
// SHA256 is the prefix used by containerd for the repo digest
SHA256 = "@sha256:"
)

// ExtractRepoDigestFromImage extracts the repoDigest from workloadmeta store if it exists and unique
// the format of repoDigest is "registry/repo@sha256:digest", the format of return value is "sha256:digest"
func ExtractRepoDigestFromImage(imageID string, imageRegistry string, store workloadmeta.Component) string {
existingImgMetadata, err := store.GetImage(imageID)
if err == nil {
// If there is only one repoDigest, return it
if len(existingImgMetadata.RepoDigests) == 1 {
_, _, digest := parseRepoDigest(existingImgMetadata.RepoDigests[0])
return digest
}
// If there are multiple repoDigests, we should find the one that matches the current container's registry
for _, repoDigest := range existingImgMetadata.RepoDigests {
registry, _, digest := parseRepoDigest(repoDigest)
if registry == imageRegistry {
return digest
}
}
log.Debugf("cannot get matched registry in repodigests for image %s", imageID)
} else {
// TODO: we should handle the case when the image metadata is not found in the store
// For example, there could be a rare race condition when collection of image metadata is not finished yet
// In this case, we can either call containerd or docker directly or get it from knownImages in the local cache
log.Debugf("cannot get image metadata for image %s: %s", imageID, err)
}
return ""
}

// parseRepoDigest extracts registry, repository, digest from repoDigest (in the format of "registry/repo@sha256:digest")
func parseRepoDigest(repoDigest string) (string, string, string) {
var registry, repository, digest string
imageName := repoDigest

digestStart := strings.Index(repoDigest, SHA256)
if digestStart != -1 {
digest = repoDigest[digestStart+len("@"):]
imageName = repoDigest[:digestStart]
}

registryEnd := strings.Index(imageName, "/")
// e.g <registry>/<repository>
if registryEnd != -1 {
registry = imageName[:registryEnd]
repository = imageName[registryEnd+len("/"):]
// e.g <registry>
} else {
registry = imageName
}

return registry, repository, digest
}
70 changes: 70 additions & 0 deletions comp/core/workloadmeta/collectors/util/image_metadata_util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2023-present Datadog, Inc.

// Package util contains utility functions for image metadata collection
package util

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestParseRepoDigest(t *testing.T) {
for _, tc := range []struct {
repoDigest string
registry string
repository string
digest string
}{
{
repoDigest: "727006795293.dkr.ecr.us-east-1.amazonaws.com/spidly@sha256:fce79f86f7a3b9c660112da8484a8f5858a7da9e703892ba04c6f045da714300",
registry: "727006795293.dkr.ecr.us-east-1.amazonaws.com",
repository: "spidly",
digest: "sha256:fce79f86f7a3b9c660112da8484a8f5858a7da9e703892ba04c6f045da714300",
},
{
repoDigest: "docker.io/library/docker@sha256:b813c414ee36b8a2c44b45295698df6bdc3bdee4a435481dbb892e1b44e09d3b",
registry: "docker.io",
repository: "library/docker",
digest: "sha256:b813c414ee36b8a2c44b45295698df6bdc3bdee4a435481dbb892e1b44e09d3b",
},
{
repoDigest: "eu.gcr.io/datadog-staging/logs-event-store-api@sha256:747bd4fc36f3f263b5dcb9df907b98489a4cb46d636c223dc29f1fb7f9405070",
registry: "eu.gcr.io",
repository: "datadog-staging/logs-event-store-api",
digest: "sha256:747bd4fc36f3f263b5dcb9df907b98489a4cb46d636c223dc29f1fb7f9405070",
},
{
repoDigest: "registry.ddbuild.io/apm-integrations-testing/handmade/postgres",
registry: "registry.ddbuild.io",
repository: "apm-integrations-testing/handmade/postgres",
digest: "",
},
{
repoDigest: "registry.ddbuild.io/apm-integrations-testing/handmade/postgres@sha256:747bd4fc36f3f263b5dcb9df907b98489a4cb46d636c223dc29f1fb7f9405070",
registry: "registry.ddbuild.io",
repository: "apm-integrations-testing/handmade/postgres",
digest: "sha256:747bd4fc36f3f263b5dcb9df907b98489a4cb46d636c223dc29f1fb7f9405070",
},
{
repoDigest: "@sha256:747bd4fc36f3f263b5dcb9df907b98489a4cb46d636c223dc29f1fb7f9405070",
registry: "",
repository: "",
digest: "sha256:747bd4fc36f3f263b5dcb9df907b98489a4cb46d636c223dc29f1fb7f9405070",
},
{
repoDigest: "docker.io@sha256:747bd4fc36f3f263b5dcb9df907b98489a4cb46d636c223dc29f1fb7f9405070",
registry: "docker.io",
repository: "",
digest: "sha256:747bd4fc36f3f263b5dcb9df907b98489a4cb46d636c223dc29f1fb7f9405070",
},
} {
registry, repository, digest := parseRepoDigest(tc.repoDigest)
assert.Equal(t, tc.registry, registry)
assert.Equal(t, tc.repository, repository)
assert.Equal(t, tc.digest, digest)
}
}

0 comments on commit 018fc30

Please sign in to comment.