docker.go 2.6 KB

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