Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[workloadmeta] Add ContainerImageMetadata Kind #14535

Merged
merged 1 commit into from
Dec 5, 2022
Merged
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
4 changes: 4 additions & 0 deletions pkg/tagger/collectors/workloadmeta_extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ func (c *WorkloadMetaCollector) processEvents(evBundle workloadmeta.EventBundle)
tagInfos = append(tagInfos, c.handleKubePod(ev)...)
case workloadmeta.KindECSTask:
tagInfos = append(tagInfos, c.handleECSTask(ev)...)
case workloadmeta.KindContainerImageMetadata:
// No tags for now
default:
log.Errorf("cannot handle event for entity %q with kind %q", entityID.ID, entityID.Kind)
}
Expand Down Expand Up @@ -594,6 +596,8 @@ func buildTaggerEntityID(entityID workloadmeta.EntityID) string {
return kubelet.PodUIDToTaggerEntityName(entityID.ID)
case workloadmeta.KindECSTask:
return fmt.Sprintf("ecs_task://%s", entityID.ID)
case workloadmeta.KindContainerImageMetadata:
return fmt.Sprintf("container_image_metadata://%s", entityID.ID)
default:
log.Errorf("can't recognize entity %q with kind %q; trying %s://%s as tagger entity",
entityID.ID, entityID.Kind, entityID.ID, entityID.Kind)
Expand Down
2 changes: 2 additions & 0 deletions pkg/workloadmeta/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ func (s *store) Dump(verbose bool) WorkloadDumpResponse {
info = e.String(verbose)
case *ECSTask:
info = e.String(verbose)
case *ContainerImageMetadata:
info = e.String(verbose)
default:
return "", fmt.Errorf("unsupported type %T", e)
}
Expand Down
13 changes: 13 additions & 0 deletions pkg/workloadmeta/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,19 @@ func (s *store) GetECSTask(id string) (*ECSTask, error) {
return entity.(*ECSTask), nil
}

// ListImages implements Store#ListImages
func (s *store) ListImages() []*ContainerImageMetadata {
entities := s.listEntitiesByKind(KindContainerImageMetadata)

images := make([]*ContainerImageMetadata, 0, len(entities))
for _, entity := range entities {
image := entity.(*ContainerImageMetadata)
images = append(images, image)
}

return images
}

// Notify implements Store#Notify
func (s *store) Notify(events []CollectorEvent) {
if len(events) > 0 {
Expand Down
41 changes: 41 additions & 0 deletions pkg/workloadmeta/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,47 @@ func TestListContainersWithFilter(t *testing.T) {
assert.DeepEqual(t, []*Container{runningContainer}, runningContainers)
}

func TestListImages(t *testing.T) {
image := &ContainerImageMetadata{
EntityID: EntityID{
Kind: KindContainerImageMetadata,
ID: "abc",
},
}

tests := []struct {
name string
preEvents []CollectorEvent
expectedImages []*ContainerImageMetadata
}{
{
name: "some images stored",
preEvents: []CollectorEvent{
{
Type: EventTypeSet,
Source: fooSource,
Entity: image,
},
},
expectedImages: []*ContainerImageMetadata{image},
},
{
name: "no containers stored",
preEvents: nil,
expectedImages: []*ContainerImageMetadata{},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
testStore := newTestStore()
testStore.handleEvents(test.preEvents)

assert.DeepEqual(t, test.expectedImages, testStore.ListImages())
})
}
}

func newTestStore() *store {
return &store{
store: make(map[Kind]map[string]*cachedEntity),
Expand Down
13 changes: 13 additions & 0 deletions pkg/workloadmeta/testing/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,19 @@ func (s *Store) GetECSTask(id string) (*workloadmeta.ECSTask, error) {
return entity.(*workloadmeta.ECSTask), nil
}

// ListImages implements Store#ListImages
func (s *Store) ListImages() []*workloadmeta.ContainerImageMetadata {
entities := s.listEntitiesByKind(workloadmeta.KindContainerImageMetadata)

images := make([]*workloadmeta.ContainerImageMetadata, 0, len(entities))
for _, entity := range entities {
image := entity.(*workloadmeta.ContainerImageMetadata)
images = append(images, image)
}

return images
}

// Set sets an entity in the store.
func (s *Store) Set(entity workloadmeta.Entity) {
s.mu.Lock()
Expand Down
108 changes: 105 additions & 3 deletions pkg/workloadmeta/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"time"

"github.com/mohae/deepcopy"
"github.com/opencontainers/image-spec/specs-go/v1"

"github.com/DataDog/datadog-agent/pkg/util/containers"
)
Expand Down Expand Up @@ -73,6 +74,10 @@ type Store interface {
// kind KindECSTask and the given ID.
GetECSTask(id string) (*ECSTask, error)

// ListImages returns metadata about all known images, equivalent to all
// entities with kind KindContainerImageMetadata.
ListImages() []*ContainerImageMetadata

// Notify notifies the store with a slice of events. It should only be
// used by workloadmeta collectors.
Notify(events []CollectorEvent)
Expand All @@ -86,9 +91,10 @@ type Kind string

// Defined Kinds
const (
KindContainer Kind = "container"
KindKubernetesPod Kind = "kubernetes_pod"
KindECSTask Kind = "ecs_task"
KindContainer Kind = "container"
KindKubernetesPod Kind = "kubernetes_pod"
KindECSTask Kind = "ecs_task"
KindContainerImageMetadata Kind = "container_image_metadata"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can name the kind container_image
because we are only in workloadmetadata, so "metadata" is a bit redundant.
WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ContainerImage struct already exists. It's the one that we use to attach basic image info to containers:

type ContainerImage struct {

)

// Source is the source name of an entity.
Expand Down Expand Up @@ -596,6 +602,102 @@ func (t ECSTask) String(verbose bool) string {

var _ Entity = &ECSTask{}

// ContainerImageMetadata is an Entity that represents container image metadata
type ContainerImageMetadata struct {
EntityID
EntityMeta
ShortName string
RepoTags []string
RepoDigests []string
MediaType string
SizeBytes int64
OS string
OSVersion string
Architecture string
Variant string
Layers []ContainerImageLayer
}

// ContainerImageLayer represents a layer of a container image
type ContainerImageLayer struct {
MediaType string
Digest string
SizeBytes int64
URLs []string
History v1.History
}

// GetID implements Entity#GetID.
func (i ContainerImageMetadata) GetID() EntityID {
return i.EntityID
}

// Merge implements Entity#Merge.
func (i *ContainerImageMetadata) Merge(e Entity) error {
otherImage, ok := e.(*ContainerImageMetadata)
if !ok {
return fmt.Errorf("cannot merge ContainerImageMetadata with different kind %T", e)
}

return merge(i, otherImage)
}

// DeepCopy implements Entity#DeepCopy.
func (i ContainerImageMetadata) DeepCopy() Entity {
cp := deepcopy.Copy(i).(ContainerImageMetadata)
return &cp
}

// String implements Entity#String.
func (i ContainerImageMetadata) String(verbose bool) string {
var sb strings.Builder

_, _ = fmt.Fprintln(&sb, "----------- Entity ID -----------")
_, _ = fmt.Fprint(&sb, i.EntityID.String(verbose))

_, _ = fmt.Fprintln(&sb, "----------- Entity Meta -----------")
_, _ = fmt.Fprint(&sb, i.EntityMeta.String(verbose))

_, _ = fmt.Fprintln(&sb, "Short name:", i.ShortName)
_, _ = fmt.Fprintln(&sb, "Repo tags:", i.RepoTags)
_, _ = fmt.Fprintln(&sb, "Repo digests:", i.RepoDigests)

if verbose {
_, _ = fmt.Fprintln(&sb, "Media Type:", i.MediaType)
_, _ = fmt.Fprintln(&sb, "Size in bytes:", i.SizeBytes)
_, _ = fmt.Fprintln(&sb, "OS:", i.OS)
_, _ = fmt.Fprintln(&sb, "OS Version:", i.OSVersion)
_, _ = fmt.Fprintln(&sb, "Architecture:", i.Architecture)
_, _ = fmt.Fprintln(&sb, "Variant:", i.Variant)

_, _ = fmt.Fprintln(&sb, "----------- Layers -----------")
for _, layer := range i.Layers {
if layer.SizeBytes != 0 { // Skip layers that have a history command associated but are empty
_, _ = fmt.Fprintln(&sb, layer)
}
}
}

return sb.String()
}

// String returns a string representation of ContainerImageLayer
func (layer ContainerImageLayer) String() string {
var sb strings.Builder

_, _ = fmt.Fprintln(&sb, "Media Type:", layer.MediaType)
_, _ = fmt.Fprintln(&sb, "Digest:", layer.Digest)
_, _ = fmt.Fprintln(&sb, "Size in bytes:", layer.SizeBytes)
_, _ = fmt.Fprintln(&sb, "URLs:", layer.URLs)

// layer.History is not included here because it's way too verbose (includes
// the command in the Dockerfile that generated the layer).

return sb.String()
}

var _ Entity = &ContainerImageMetadata{}

// CollectorEvent is an event generated by a metadata collector, to be handled
// by the metadata store.
type CollectorEvent struct {
Expand Down