Skip to content

Commit

Permalink
[workloadmeta] Add ContainerImageMetadata Kind
Browse files Browse the repository at this point in the history
  • Loading branch information
davidor committed Dec 1, 2022
1 parent eda82c1 commit a1e9812
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 3 deletions.
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"
)

// 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

0 comments on commit a1e9812

Please sign in to comment.