diff --git a/app/origin_node.go b/app/origin_node.go index cc7b85b10d..0cd96fcf79 100644 --- a/app/origin_node.go +++ b/app/origin_node.go @@ -36,6 +36,8 @@ func originNodeForProcess(node report.NodeMetadata) OriginNode { for _, tuple := range []struct{ key, human string }{ {"docker_id", "Container ID"}, {"docker_name", "Container name"}, + {"docker_image_id", "Container image ID"}, + {"docker_image_name", "Container image name"}, {"cgroup", "cgroup"}, } { if val, ok := node[tuple.key]; ok { diff --git a/probe/docker_process_mapper.go b/probe/docker_process_mapper.go index 93ef81e159..7ff3362e36 100644 --- a/probe/docker_process_mapper.go +++ b/probe/docker_process_mapper.go @@ -12,6 +12,7 @@ import ( type dockerMapper struct { sync.RWMutex d map[int]*docker.Container + images map[string]*docker.APIImages procRoot string } @@ -35,6 +36,7 @@ func (m *dockerMapper) loop(d time.Duration) { type dockerClient interface { ListContainers(docker.ListContainersOptions) ([]docker.APIContainers, error) InspectContainer(string) (*docker.Container, error) + ListImages(docker.ListImagesOptions) ([]docker.APIImages, error) } func newRealDockerClient(endpoint string) (dockerClient, error) { @@ -88,17 +90,32 @@ func (m *dockerMapper) update() { } } + imageList, err := client.ListImages(docker.ListImagesOptions{}) + if err != nil { + log.Printf("docker mapper: %s", err) + return + } + + imageMap := map[string]*docker.APIImages{} + for i, _ := range imageList { + image := &imageList[i] + imageMap[image.ID] = image + } + m.Lock() m.d = pmap + m.images = imageMap m.Unlock() } -type dockerIDMapper struct { +type dockerProcessMapper struct { *dockerMapper + key string + f func(*docker.Container) string } -func (m dockerIDMapper) Key() string { return "docker_id" } -func (m dockerIDMapper) Map(pid uint) (string, error) { +func (m *dockerProcessMapper) Key() string { return m.key } +func (m *dockerProcessMapper) Map(pid uint) (string, error) { m.RLock() container, ok := m.d[int(pid)] m.RUnlock() @@ -107,22 +124,37 @@ func (m dockerIDMapper) Map(pid uint) (string, error) { return "", fmt.Errorf("no container found for PID %d", pid) } - return container.ID, nil + return m.f(container), nil } -type dockerNameMapper struct { - *dockerMapper +func (m *dockerMapper) idMapper() processMapper { + return &dockerProcessMapper{m, "docker_id", func(c *docker.Container) string { + return c.ID + }} } -func (m dockerNameMapper) Key() string { return "docker_name" } -func (m dockerNameMapper) Map(pid uint) (string, error) { - m.RLock() - container, ok := m.d[int(pid)] - m.RUnlock() +func (m *dockerMapper) nameMapper() processMapper { + return &dockerProcessMapper{m, "docker_name", func(c *docker.Container) string { + return c.Name + }} +} - if !ok { - return "", fmt.Errorf("no container found for PID %d", pid) - } +func (m *dockerMapper) imageIDMapper() processMapper { + return &dockerProcessMapper{m, "docker_image_id", func(c *docker.Container) string { + return c.Image + }} +} + +func (m *dockerMapper) imageNameMapper() processMapper { + return &dockerProcessMapper{m, "docker_image_name", func(c *docker.Container) string { + m.RLock() + image, ok := m.images[c.Image] + m.RUnlock() + + if !ok || len(image.RepoTags) == 0 { + return "" + } - return container.Name, nil + return image.RepoTags[0] + }} } diff --git a/probe/docker_process_mapper_test.go b/probe/docker_process_mapper_test.go index 0059d7804a..fb1d371b72 100644 --- a/probe/docker_process_mapper_test.go +++ b/probe/docker_process_mapper_test.go @@ -10,6 +10,7 @@ import ( type mockDockerClient struct { containers []docker.APIContainers containerInfo map[string]*docker.Container + images []docker.APIImages } func (m mockDockerClient) ListContainers(options docker.ListContainersOptions) ([]docker.APIContainers, error) { @@ -20,6 +21,10 @@ func (m mockDockerClient) InspectContainer(id string) (*docker.Container, error) return m.containerInfo[id], nil } +func (m mockDockerClient) ListImages(options docker.ListImagesOptions) ([]docker.APIImages, error) { + return m.images, nil +} + func TestDockerProcessMapper(t *testing.T) { oldPIDTreeStub, oldDockerClientStub := newPIDTreeStub, newDockerClient defer func() { @@ -46,19 +51,23 @@ func TestDockerProcessMapper(t *testing.T) { "foo": { ID: "foo", Name: "bar", + Image: "baz", State: docker.State{Pid: 1, Running: true}, }, }, + images: []docker.APIImages{{ID: "baz", RepoTags: []string{"tag"}}}, }, nil } dockerMapper := newDockerMapper("/proc", 10*time.Second) - dockerIDMapper := dockerIDMapper{dockerMapper} - dockerNameMapper := dockerNameMapper{dockerMapper} + dockerIDMapper := dockerMapper.idMapper() + dockerNameMapper := dockerMapper.nameMapper() + dockerImageIDMapper := dockerMapper.imageIDMapper() + dockerImageNameMapper := dockerMapper.imageNameMapper() - for pid, want := range map[uint]struct{ id, name string }{ - 1: {"foo", "bar"}, - 2: {"foo", "bar"}, + for pid, want := range map[uint]struct{ id, name, imageID, imageName string }{ + 1: {"foo", "bar", "baz", "tag"}, + 2: {"foo", "bar", "baz", "tag"}, } { haveID, err := dockerIDMapper.Map(pid) if err != nil || want.id != haveID { @@ -68,5 +77,13 @@ func TestDockerProcessMapper(t *testing.T) { if err != nil || want.name != haveName { t.Errorf("%d: want %q, have %q (%v)", pid, want.name, haveName, err) } + haveImageID, err := dockerImageIDMapper.Map(pid) + if err != nil || want.imageID != haveImageID { + t.Errorf("%d: want %q, have %q (%v)", pid, want.name, haveName, err) + } + haveImageName, err := dockerImageNameMapper.Map(pid) + if err != nil || want.imageName != haveImageName { + t.Errorf("%d: want %q, have %q (%v)", pid, want.name, haveName, err) + } } } diff --git a/probe/main.go b/probe/main.go index ace5a50a13..ced33e09d1 100644 --- a/probe/main.go +++ b/probe/main.go @@ -79,7 +79,8 @@ func main() { if *dockerMapper { docker := newDockerMapper(*procRoot, *dockerInterval) - pms = append(pms, &dockerIDMapper{docker}, &dockerNameMapper{docker}) + pms = append(pms, docker.idMapper(), docker.nameMapper(), + docker.imageIDMapper(), docker.imageNameMapper()) } log.Printf("listening on %s", *listen)