github.com/newrelic/go-agent@v3.26.0+incompatible/internal/sysinfo/docker.go (about) 1 // Copyright 2020 New Relic Corporation. All rights reserved. 2 // SPDX-License-Identifier: Apache-2.0 3 4 package sysinfo 5 6 import ( 7 "bufio" 8 "bytes" 9 "errors" 10 "fmt" 11 "io" 12 "os" 13 "regexp" 14 "runtime" 15 ) 16 17 var ( 18 // ErrDockerNotFound is returned if a Docker ID is not found in 19 // /proc/self/cgroup 20 ErrDockerNotFound = errors.New("Docker ID not found") 21 ) 22 23 // DockerID attempts to detect Docker. 24 func DockerID() (string, error) { 25 if "linux" != runtime.GOOS { 26 return "", ErrFeatureUnsupported 27 } 28 29 f, err := os.Open("/proc/self/cgroup") 30 if err != nil { 31 return "", err 32 } 33 defer f.Close() 34 35 return parseDockerID(f) 36 } 37 38 var ( 39 // The DockerID must be a 64-character lowercase hex string 40 // be greedy and match anything 64-characters or longer to spot invalid IDs 41 dockerIDLength = 64 42 dockerIDRegexRaw = fmt.Sprintf("[0-9a-f]{%d,}", dockerIDLength) 43 dockerIDRegex = regexp.MustCompile(dockerIDRegexRaw) 44 ) 45 46 func parseDockerID(r io.Reader) (string, error) { 47 // Each line in the cgroup file consists of three colon delimited fields. 48 // 1. hierarchy ID - we don't care about this 49 // 2. subsystems - comma separated list of cgroup subsystem names 50 // 3. control group - control group to which the process belongs 51 // 52 // Example 53 // 5:cpuacct,cpu,cpuset:/daemons 54 55 var id string 56 57 for scanner := bufio.NewScanner(r); scanner.Scan(); { 58 line := scanner.Bytes() 59 cols := bytes.SplitN(line, []byte(":"), 3) 60 61 if len(cols) < 3 { 62 continue 63 } 64 65 // We're only interested in the cpu subsystem. 66 if !isCPUCol(cols[1]) { 67 continue 68 } 69 70 id = dockerIDRegex.FindString(string(cols[2])) 71 72 if err := validateDockerID(id); err != nil { 73 // We can stop searching at this point, the CPU 74 // subsystem should only occur once, and its cgroup is 75 // not docker or not a format we accept. 76 return "", err 77 } 78 return id, nil 79 } 80 81 return "", ErrDockerNotFound 82 } 83 84 func isCPUCol(col []byte) bool { 85 // Sometimes we have multiple subsystems in one line, as in this example 86 // from: 87 // https://source.datanerd.us/newrelic/cross_agent_tests/blob/master/docker_container_id/docker-1.1.2-native-driver-systemd.txt 88 // 89 // 3:cpuacct,cpu:/system.slice/docker-67f98c9e6188f9c1818672a15dbe46237b6ee7e77f834d40d41c5fb3c2f84a2f.scope 90 splitCSV := func(r rune) bool { return r == ',' } 91 subsysCPU := []byte("cpu") 92 93 for _, subsys := range bytes.FieldsFunc(col, splitCSV) { 94 if bytes.Equal(subsysCPU, subsys) { 95 return true 96 } 97 } 98 return false 99 } 100 101 func isHex(r rune) bool { 102 return ('0' <= r && r <= '9') || ('a' <= r && r <= 'f') 103 } 104 105 func validateDockerID(id string) error { 106 if len(id) != 64 { 107 return fmt.Errorf("%s is not %d characters long", id, dockerIDLength) 108 } 109 110 for _, c := range id { 111 if !isHex(c) { 112 return fmt.Errorf("Character: %c is not hex in string %s", c, id) 113 } 114 } 115 116 return nil 117 }