diff --git a/context.go b/context.go index 71e126b5..4358fbe3 100644 --- a/context.go +++ b/context.go @@ -2,11 +2,14 @@ package dockergen import ( "bufio" + "bytes" + "fmt" + "io" "os" "regexp" "sync" - "fmt" - "github.com/fsouza/go-dockerclient" + + docker "github.com/fsouza/go-dockerclient" ) var ( @@ -158,57 +161,85 @@ type Docker struct { CurrentContainerID string } -func GetCurrentContainerID() string { - filepaths := []string{"/proc/self/cgroup", "/proc/self/mountinfo"} +// GetCurrentContainerID attempts to extract the current container ID from the provided file paths. +// If no files paths are provided, it will default to /proc/1/cpuset, /proc/self/cgroup and /proc/self/mountinfo. +// It attempts to match the HOSTNAME first then use the fallback method, and returns with the first valid match. +func GetCurrentContainerID(filepaths ...string) (id string) { + if len(filepaths) == 0 { + filepaths = []string{"/proc/1/cpuset", "/proc/self/cgroup", "/proc/self/mountinfo"} + } + + var files []io.Reader for _, filepath := range filepaths { file, err := os.Open(filepath) if err != nil { continue } - reader := bufio.NewReader(file) - scanner := bufio.NewScanner(reader) - scanner.Split(bufio.ScanLines) - for scanner.Scan() { - _, lines, err := bufio.ScanLines([]byte(scanner.Text()), true) - if err == nil { - strLines := string(lines) - if id := matchDockerCurrentContainerID(strLines); id != "" { - return id - } else if id := matchECSCurrentContainerID(strLines); id != "" { - return id - } + defer file.Close() + files = append(files, file) + } + + reader := io.MultiReader(files...) + var buffer bytes.Buffer + tee := io.TeeReader(reader, &buffer) + + // We try to match a 64 character hex string starting with the hostname first + scanner := bufio.NewScanner(tee) + scanner.Split(bufio.ScanLines) + for scanner.Scan() { + _, lines, err := bufio.ScanLines([]byte(scanner.Text()), true) + if err == nil { + strLines := string(lines) + if id = matchContainerIDWithHostname(strLines); len(id) == 64 { + return } } } - return "" + // If we didn't get any ID that matches the hostname, fall back to matching the first 64 character hex string + scanner = bufio.NewScanner(&buffer) + scanner.Split(bufio.ScanLines) + for scanner.Scan() { + _, lines, err := bufio.ScanLines([]byte(scanner.Text()), true) + if err == nil { + strLines := string(lines) + if id = matchContainerID(strLines); len(id) == 64 { + return + } + } + } + + return } -func matchDockerCurrentContainerID(lines string) string { +func matchContainerIDWithHostname(lines string) string { hostname := os.Getenv("HOSTNAME") - regex := fmt.Sprintf("(%s[[:alnum:]]{52})", hostname) - re := regexp.MustCompilePOSIX(regex) + re := regexp.MustCompilePOSIX("^[[:alnum:]]{12}$") - if re.MatchString(lines) { - submatches := re.FindStringSubmatch(string(lines)) - containerID := submatches[1] + if re.MatchString(hostname) { + regex := fmt.Sprintf("(%s[[:alnum:]]{52})", hostname) + re := regexp.MustCompilePOSIX(regex) - return containerID + if re.MatchString(lines) { + submatches := re.FindStringSubmatch(string(lines)) + containerID := submatches[1] + + return containerID + } } return "" } -func matchECSCurrentContainerID(lines string) string { - regex := "/ecs\\/[^\\/]+\\/(.+)$" +func matchContainerID(lines string) string { + regex := "([[:alnum:]]{64})" re := regexp.MustCompilePOSIX(regex) - if re.MatchString(string(lines)) { + if re.MatchString(lines) { submatches := re.FindStringSubmatch(string(lines)) containerID := submatches[1] return containerID } - return "" } diff --git a/context_test.go b/context_test.go index 4e64cc11..917e077b 100644 --- a/context_test.go +++ b/context_test.go @@ -1,52 +1,98 @@ package dockergen import ( + "fmt" + "io/ioutil" + "log" + "os" "testing" ) func TestGetCurrentContainerID(t *testing.T) { - currentContainerID := GetCurrentContainerID() + hostname := os.Getenv("HOSTNAME") + defer os.Setenv("HOSTNAME", hostname) - if len(currentContainerID) != 0 && len(currentContainerID) != 64 { - t.Fail() + ids := []string{ + "0fa939e22e6938e7517f663de83e79a5087a18b1b997a36e0c933a917cddb295", + "e881f8c51a72db7da515e9d5cab8ed105b869579eb9923fdcf4ee80933160802", + "eede6bd9e72f5d783a4bfb845bd71f310e974cb26987328a5d15704e23a8d6cb", } -} -func TestGetCurrentContainerID_ECS(t *testing.T) { - cgroup := - `9:perf_event:/ecs/628967a1-46b4-4a8a-84ff-605128f4679e/3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f -8:memory:/ecs/628967a1-46b4-4a8a-84ff-605128f4679e/3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f -7:hugetlb:/ecs/628967a1-46b4-4a8a-84ff-605128f4679e/3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f -6:freezer:/ecs/628967a1-46b4-4a8a-84ff-605128f4679e/3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f -5:devices:/ecs/628967a1-46b4-4a8a-84ff-605128f4679e/3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f -4:cpuset:/ecs/628967a1-46b4-4a8a-84ff-605128f4679e/3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f -3:cpuacct:/ecs/628967a1-46b4-4a8a-84ff-605128f4679e/3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f -2:cpu:/ecs/628967a1-46b4-4a8a-84ff-605128f4679e/3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f -1:blkio:/ecs/628967a1-46b4-4a8a-84ff-605128f4679e/3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f` - - if got, exp := matchECSCurrentContainerID(cgroup), "3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f"; got != exp { - t.Fatalf("id mismatch: got %v, exp %v", got, exp) + contents := map[string]string{ + "cpuset": fmt.Sprintf("/docker/%v", ids[0]), + "cgroup": fmt.Sprintf(`13:name=systemd:/docker-ce/docker/%[1]v +12:pids:/docker-ce/docker/%[1]v +11:hugetlb:/docker-ce/docker/%[1]v +10:net_prio:/docker-ce/docker/%[1]v +9:perf_event:/docker-ce/docker/%[1]v +8:net_cls:/docker-ce/docker/%[1]v +7:freezer:/docker-ce/docker/%[1]v +6:devices:/docker-ce/docker/%[1]v +5:memory:/docker-ce/docker/%[1]v +4:blkio:/docker-ce/docker/%[1]v +3:cpuacct:/docker-ce/docker/%[1]v +2:cpu:/docker-ce/docker/%[1]v +1:cpuset:/docker-ce/docker/%[1]v`, ids[1]), + "mountinfo": fmt.Sprintf(`705 661 0:96 / / rw,relatime master:192 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/CVAK3VWZFQCUGTLHRJHPEKJ4UL:/var/lib/docker/overlay2/l/XMJZ73SKVWVECU7TJCOY62F3H2:/var/lib/docker/overlay2/l/AVNBXO52GHDY3MZU3R4RCSNMCE:/var/lib/docker/overlay2/l/L4IJZ33E6NAMXJ5W3SKJSVX5TS:/var/lib/docker/overlay2/l/JXAUAD5TDJCXA34FGS6NYGUZKT:/var/lib/docker/overlay2/l/TBQDSAFKBSTFMUS3QCFWN5NRLB:/var/lib/docker/overlay2/l/MXIUXRGB7MU4Y4NUNZE2VXTXIN:/var/lib/docker/overlay2/l/HN7E4YWJG7TMG7BXLZTGICTBOA:/var/lib/docker/overlay2/l/65XQPC72Z5VRY4THGASZIQXS57:/var/lib/docker/overlay2/l/BVQKC7LU6D7MOSLBDKFHY7YSO3:/var/lib/docker/overlay2/l/R4GGX3SFPMLXTNM3WKMVOKDTOY:/var/lib/docker/overlay2/l/VHGYTU73JLTRCGX45ZF2VGW4FK,upperdir=/var/lib/docker/overlay2/e1fab975d5ffd51474b11a964c82c3bfda1c0e82aec6845a1f12c8150bf61419/diff,workdir=/var/lib/docker/overlay2/e1fab975d5ffd51474b11a964c82c3bfda1c0e82aec6845a1f12c8150bf61419/work,index=off +706 705 0:105 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw +707 705 0:106 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64 +708 707 0:107 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +709 705 0:108 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro +710 709 0:25 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw,nsdelegate,memory_recursiveprot +711 707 0:104 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw +712 707 0:109 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k,inode64 +713 705 8:3 /var/lib/docker/containers/%[1]v/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/sda3 rw +714 705 8:3 /var/lib/docker/containers/%[1]v/hostname /etc/hostname rw,relatime - ext4 /dev/sda3 rw +715 705 8:3 /var/lib/docker/containers/%[1]v/hosts /etc/hosts rw,relatime - ext4 /dev/sda3 rw +716 705 8:3 /var/lib/docker/volumes/ca8074e1a2eb12edc86c59c5108bb48c31bb7ace4b90beb0da8137a9baa45812/_data /etc/nginx/certs rw,relatime master:1 - ext4 /dev/sda3 rw +717 705 8:3 /var/lib/docker/volumes/2cf8a52c907469a56f6e2cc7d1959d74a4dd04131e7edcd53eaf909db28f770f/_data /etc/nginx/dhparam rw,relatime master:1 - ext4 /dev/sda3 rw +662 707 0:107 /0 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +663 706 0:105 /bus /proc/bus ro,relatime - proc proc rw +664 706 0:105 /fs /proc/fs ro,relatime - proc proc rw +665 706 0:105 /irq /proc/irq ro,relatime - proc proc rw +666 706 0:105 /sys /proc/sys ro,relatime - proc proc rw +667 706 0:105 /sysrq-trigger /proc/sysrq-trigger ro,relatime - proc proc rw +668 706 0:110 / /proc/acpi ro,relatime - tmpfs tmpfs ro,inode64 +669 706 0:106 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64 +670 706 0:106 /null /proc/keys rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64 +671 706 0:106 /null /proc/latency_stats rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64 +672 706 0:106 /null /proc/timer_list rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64 +673 706 0:106 /null /proc/sched_debug rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64 +674 706 0:111 / /proc/scsi ro,relatime - tmpfs tmpfs ro,inode64 +675 709 0:112 / /sys/firmware ro,relatime - tmpfs tmpfs ro,inode64`, ids[2]), + } + + keys := []string{ + "cpuset", + "cgroup", + "mountinfo", } -} -func TestGetCurrentContainerID_DockerCE(t *testing.T) { - cgroup := - `13:name=systemd:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb -12:pids:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb -11:hugetlb:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb -10:net_prio:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb -9:perf_event:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb -8:net_cls:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb -7:freezer:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb -6:devices:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb -5:memory:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb -4:blkio:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb -3:cpuacct:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb -2:cpu:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb -1:cpuset:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb` - - if got, exp := matchDockerCurrentContainerID(cgroup), "18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb"; got != exp { - t.Fatalf("id mismatch: got %v, exp %v", got, exp) + var filepaths []string + // Create temporary files with test content + for _, key := range keys { + file, err := ioutil.TempFile("", key) + if err != nil { + log.Fatal(err) + } + defer os.Remove(file.Name()) + if _, err = file.WriteString(contents[key]); err != nil { + log.Fatal(err) + } + filepaths = append(filepaths, file.Name()) } + // Each time the HOSTNAME is set to a short form ID, GetCurrentContainerID() should match and return the corresponding full ID + for _, id := range ids { + os.Setenv("HOSTNAME", id[0:12]) + if got, exp := GetCurrentContainerID(filepaths...), id; got != exp { + t.Fatalf("id mismatch with HOSTNAME %v: got %v, exp %v", id[0:12], got, exp) + } + } + + // If the Hostname isn't a short form ID, we should match the first valid ID (64 character hex string) instead + os.Setenv("HOSTNAME", "customhostname") + if got, exp := GetCurrentContainerID(filepaths...), ids[0]; got != exp { + t.Fatalf("id mismatch with custom HOSTNAME: got %v, exp %v", got, exp) + } }