links.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. package links
  2. import (
  3. "fmt"
  4. "github.com/docker/docker/engine"
  5. "github.com/docker/docker/nat"
  6. "path"
  7. "strings"
  8. )
  9. type Link struct {
  10. ParentIP string
  11. ChildIP string
  12. Name string
  13. ChildEnvironment []string
  14. Ports []nat.Port
  15. IsEnabled bool
  16. eng *engine.Engine
  17. }
  18. func NewLink(parentIP, childIP, name string, env []string, exposedPorts map[nat.Port]struct{}, eng *engine.Engine) (*Link, error) {
  19. var (
  20. i int
  21. ports = make([]nat.Port, len(exposedPorts))
  22. )
  23. for p := range exposedPorts {
  24. ports[i] = p
  25. i++
  26. }
  27. l := &Link{
  28. Name: name,
  29. ChildIP: childIP,
  30. ParentIP: parentIP,
  31. ChildEnvironment: env,
  32. Ports: ports,
  33. eng: eng,
  34. }
  35. return l, nil
  36. }
  37. func (l *Link) Alias() string {
  38. _, alias := path.Split(l.Name)
  39. return alias
  40. }
  41. func nextContiguous(ports []nat.Port, value int, index int) int {
  42. if index+1 == len(ports) {
  43. return index
  44. }
  45. for i := index + 1; i < len(ports); i++ {
  46. if ports[i].Int() > value+1 {
  47. return i - 1
  48. }
  49. value++
  50. }
  51. return len(ports) - 1
  52. }
  53. func (l *Link) ToEnv() []string {
  54. env := []string{}
  55. alias := strings.Replace(strings.ToUpper(l.Alias()), "-", "_", -1)
  56. if p := l.getDefaultPort(); p != nil {
  57. env = append(env, fmt.Sprintf("%s_PORT=%s://%s:%s", alias, p.Proto(), l.ChildIP, p.Port()))
  58. }
  59. //sort the ports so that we can bulk the continuous ports together
  60. nat.Sort(l.Ports, func(ip, jp nat.Port) bool {
  61. // If the two ports have the same number, tcp takes priority
  62. // Sort in desc order
  63. return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && strings.ToLower(ip.Proto()) == "tcp")
  64. })
  65. for i := 0; i < len(l.Ports); {
  66. p := l.Ports[i]
  67. j := nextContiguous(l.Ports, p.Int(), i)
  68. if j > i+1 {
  69. env = append(env, fmt.Sprintf("%s_PORT_%s_%s_START=%s://%s:%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Proto(), l.ChildIP, p.Port()))
  70. env = append(env, fmt.Sprintf("%s_PORT_%s_%s_ADDR=%s", alias, p.Port(), strings.ToUpper(p.Proto()), l.ChildIP))
  71. env = append(env, fmt.Sprintf("%s_PORT_%s_%s_PROTO=%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Proto()))
  72. env = append(env, fmt.Sprintf("%s_PORT_%s_%s_PORT_START=%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Port()))
  73. q := l.Ports[j]
  74. env = append(env, fmt.Sprintf("%s_PORT_%s_%s_END=%s://%s:%s", alias, p.Port(), strings.ToUpper(q.Proto()), q.Proto(), l.ChildIP, q.Port()))
  75. env = append(env, fmt.Sprintf("%s_PORT_%s_%s_PORT_END=%s", alias, p.Port(), strings.ToUpper(q.Proto()), q.Port()))
  76. i = j + 1
  77. continue
  78. }
  79. env = append(env, fmt.Sprintf("%s_PORT_%s_%s=%s://%s:%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Proto(), l.ChildIP, p.Port()))
  80. env = append(env, fmt.Sprintf("%s_PORT_%s_%s_ADDR=%s", alias, p.Port(), strings.ToUpper(p.Proto()), l.ChildIP))
  81. env = append(env, fmt.Sprintf("%s_PORT_%s_%s_PORT=%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Port()))
  82. env = append(env, fmt.Sprintf("%s_PORT_%s_%s_PROTO=%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Proto()))
  83. i++
  84. }
  85. // Load the linked container's name into the environment
  86. env = append(env, fmt.Sprintf("%s_NAME=%s", alias, l.Name))
  87. if l.ChildEnvironment != nil {
  88. for _, v := range l.ChildEnvironment {
  89. parts := strings.Split(v, "=")
  90. if len(parts) != 2 {
  91. continue
  92. }
  93. // Ignore a few variables that are added during docker build (and not really relevant to linked containers)
  94. if parts[0] == "HOME" || parts[0] == "PATH" {
  95. continue
  96. }
  97. env = append(env, fmt.Sprintf("%s_ENV_%s=%s", alias, parts[0], parts[1]))
  98. }
  99. }
  100. return env
  101. }
  102. // Default port rules
  103. func (l *Link) getDefaultPort() *nat.Port {
  104. var p nat.Port
  105. i := len(l.Ports)
  106. if i == 0 {
  107. return nil
  108. } else if i > 1 {
  109. nat.Sort(l.Ports, func(ip, jp nat.Port) bool {
  110. // If the two ports have the same number, tcp takes priority
  111. // Sort in desc order
  112. return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && strings.ToLower(ip.Proto()) == "tcp")
  113. })
  114. }
  115. p = l.Ports[0]
  116. return &p
  117. }
  118. func (l *Link) Enable() error {
  119. // -A == iptables append flag
  120. if err := l.toggle("-A", false); err != nil {
  121. return err
  122. }
  123. l.IsEnabled = true
  124. return nil
  125. }
  126. func (l *Link) Disable() {
  127. // We do not care about errors here because the link may not
  128. // exist in iptables
  129. // -D == iptables delete flag
  130. l.toggle("-D", true)
  131. l.IsEnabled = false
  132. }
  133. func (l *Link) toggle(action string, ignoreErrors bool) error {
  134. job := l.eng.Job("link", action)
  135. job.Setenv("ParentIP", l.ParentIP)
  136. job.Setenv("ChildIP", l.ChildIP)
  137. job.SetenvBool("IgnoreErrors", ignoreErrors)
  138. out := make([]string, len(l.Ports))
  139. for i, p := range l.Ports {
  140. out[i] = string(p)
  141. }
  142. job.SetenvList("Ports", out)
  143. if err := job.Run(); err != nil {
  144. // TODO: get ouput from job
  145. return err
  146. }
  147. return nil
  148. }