diff --git a/context.go b/context.go index 4358fbe3..513b3e36 100644 --- a/context.go +++ b/context.go @@ -2,9 +2,7 @@ package dockergen import ( "bufio" - "bytes" "fmt" - "io" "os" "regexp" "sync" @@ -169,43 +167,42 @@ func GetCurrentContainerID(filepaths ...string) (id string) { filepaths = []string{"/proc/1/cpuset", "/proc/self/cgroup", "/proc/self/mountinfo"} } - var files []io.Reader - + // We try to match a 64 character hex string starting with the hostname first for _, filepath := range filepaths { file, err := os.Open(filepath) if err != nil { continue } 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 + scanner := bufio.NewScanner(file) + 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 + } } } } // 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 + for _, filepath := range filepaths { + file, err := os.Open(filepath) + if err != nil { + continue + } + defer file.Close() + scanner := bufio.NewScanner(file) + scanner.Split(bufio.ScanLines) + for scanner.Scan() { + _, lines, err := bufio.ScanLines([]byte(scanner.Text()), true) + if err == nil { + strLines := string(lines) + if id = matchContainerID("([[:alnum:]]{64})", strLines); len(id) == 64 { + return + } } } } @@ -219,22 +216,21 @@ func matchContainerIDWithHostname(lines string) string { if re.MatchString(hostname) { regex := fmt.Sprintf("(%s[[:alnum:]]{52})", hostname) - re := regexp.MustCompilePOSIX(regex) - if re.MatchString(lines) { - submatches := re.FindStringSubmatch(string(lines)) - containerID := submatches[1] - - return containerID - } + return matchContainerID(regex, lines) } return "" } -func matchContainerID(lines string) string { - regex := "([[:alnum:]]{64})" - re := regexp.MustCompilePOSIX(regex) +func matchContainerID(regex, lines string) string { + // Attempt to detect if we're on a line from a /proc//mountinfo file and modify the regexp accordingly + // https://www.kernel.org/doc/Documentation/filesystems/proc.txt section 3.5 + re := regexp.MustCompilePOSIX("^[0-9]+ [0-9]+ [0-9]+:[0-9]+ /") + if re.MatchString(lines) { + regex = fmt.Sprintf("containers/%v", regex) + } + re = regexp.MustCompilePOSIX(regex) if re.MatchString(lines) { submatches := re.FindStringSubmatch(string(lines)) containerID := submatches[1] diff --git a/context_test.go b/context_test.go index 917e077b..2beec59c 100644 --- a/context_test.go +++ b/context_test.go @@ -8,17 +8,20 @@ import ( "testing" ) -func TestGetCurrentContainerID(t *testing.T) { - hostname := os.Getenv("HOSTNAME") - defer os.Setenv("HOSTNAME", hostname) - - ids := []string{ +var ( + ids = []string{ "0fa939e22e6938e7517f663de83e79a5087a18b1b997a36e0c933a917cddb295", "e881f8c51a72db7da515e9d5cab8ed105b869579eb9923fdcf4ee80933160802", "eede6bd9e72f5d783a4bfb845bd71f310e974cb26987328a5d15704e23a8d6cb", } - contents := map[string]string{ + fileKeys = []string{ + "cpuset", + "cgroup", + "mountinfo", + } + + 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 @@ -61,16 +64,15 @@ func TestGetCurrentContainerID(t *testing.T) { 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(t *testing.T) { + hostname := os.Getenv("HOSTNAME") + defer os.Setenv("HOSTNAME", hostname) var filepaths []string // Create temporary files with test content - for _, key := range keys { + for _, key := range fileKeys { file, err := ioutil.TempFile("", key) if err != nil { log.Fatal(err) @@ -96,3 +98,38 @@ func TestGetCurrentContainerID(t *testing.T) { t.Fatalf("id mismatch with custom HOSTNAME: got %v, exp %v", got, exp) } } + +func TestGetCurrentContainerIDMountInfo(t *testing.T) { + // Test specific to cases like https://github.com/nginx-proxy/docker-gen/issues/355 + // where only the /proc//mountinfo file contains information + hostname := os.Getenv("HOSTNAME") + defer os.Setenv("HOSTNAME", hostname) + os.Setenv("HOSTNAME", "customhostname") + + id := ids[2] + + content := map[string]string{ + "cpuset": "/", + "cgroup": "0::/", + "mountinfo": contents["mountinfo"], + } + + var filepaths []string + // Create temporary files with test content + for _, key := range fileKeys { + file, err := ioutil.TempFile("", key) + if err != nil { + log.Fatal(err) + } + defer os.Remove(file.Name()) + if _, err = file.WriteString(content[key]); err != nil { + log.Fatal(err) + } + filepaths = append(filepaths, file.Name()) + } + + // We should match the correct 64 characters long ID in mountinfo, not the first encountered + if got, exp := GetCurrentContainerID(filepaths...), id; got != exp { + t.Fatalf("id mismatch on mountinfo: got %v, exp %v", got, exp) + } +}