From 18c38a6bf40eda2215ca348710dc767a55317e1d Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Fri, 22 May 2015 14:06:38 +0000 Subject: [PATCH] Expose docker image id and name --- app/origin_node.go | 2 + probe/docker_process_mapper.go | 74 +++++++++++++++++++++-------- probe/docker_process_mapper_test.go | 39 ++++++++++----- probe/main.go | 7 ++- 4 files changed, 89 insertions(+), 33 deletions(-) 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..ed57526c41 100644 --- a/probe/docker_process_mapper.go +++ b/probe/docker_process_mapper.go @@ -11,14 +11,15 @@ import ( type dockerMapper struct { sync.RWMutex - d map[int]*docker.Container - procRoot string + containers map[int]*docker.Container + images map[string]*docker.APIImages + procRoot string } func newDockerMapper(procRoot string, interval time.Duration) *dockerMapper { m := dockerMapper{ - procRoot: procRoot, - d: map[int]*docker.Container{}, + procRoot: procRoot, + containers: map[int]*docker.Container{}, } m.update() go m.loop(interval) @@ -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,41 +90,71 @@ 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.containers = 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)] + container, ok := m.containers[int(pid)] m.RUnlock() if !ok { 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..5ca17bf54d 100644 --- a/probe/docker_process_mapper_test.go +++ b/probe/docker_process_mapper_test.go @@ -8,16 +8,21 @@ import ( ) type mockDockerClient struct { - containers []docker.APIContainers - containerInfo map[string]*docker.Container + apiContainers []docker.APIContainers + containers map[string]*docker.Container + apiImages []docker.APIImages } func (m mockDockerClient) ListContainers(options docker.ListContainersOptions) ([]docker.APIContainers, error) { - return m.containers, nil + return m.apiContainers, nil } func (m mockDockerClient) InspectContainer(id string) (*docker.Container, error) { - return m.containerInfo[id], nil + return m.containers[id], nil +} + +func (m mockDockerClient) ListImages(options docker.ListImagesOptions) ([]docker.APIImages, error) { + return m.apiImages, nil } func TestDockerProcessMapper(t *testing.T) { @@ -41,24 +46,28 @@ func TestDockerProcessMapper(t *testing.T) { newDockerClient = func(endpoint string) (dockerClient, error) { return mockDockerClient{ - containers: []docker.APIContainers{{ID: "foo"}}, - containerInfo: map[string]*docker.Container{ + apiContainers: []docker.APIContainers{{ID: "foo"}}, + containers: map[string]*docker.Container{ "foo": { ID: "foo", Name: "bar", + Image: "baz", State: docker.State{Pid: 1, Running: true}, }, }, + apiImages: []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.imageID, haveImageID, err) + } + haveImageName, err := dockerImageNameMapper.Map(pid) + if err != nil || want.imageName != haveImageName { + t.Errorf("%d: want %q, have %q (%v)", pid, want.imageName, haveImageName, err) + } } } diff --git a/probe/main.go b/probe/main.go index ace5a50a13..f2bf1ffc26 100644 --- a/probe/main.go +++ b/probe/main.go @@ -79,7 +79,12 @@ 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)