dependencies.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. // +build local
  2. /*
  3. Copyright 2020 Docker Compose CLI authors
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. package local
  15. import (
  16. "context"
  17. "fmt"
  18. "strings"
  19. "sync"
  20. "github.com/compose-spec/compose-go/types"
  21. "golang.org/x/sync/errgroup"
  22. )
  23. // ServiceStatus indicates the status of a service
  24. type ServiceStatus int
  25. // Services status flags
  26. const (
  27. ServiceStopped ServiceStatus = iota
  28. ServiceStarted
  29. )
  30. func inDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, types.ServiceConfig) error) error {
  31. g := NewGraph(project.Services)
  32. if b, err := g.HasCycles(); b {
  33. return err
  34. }
  35. leaves := g.Leaves()
  36. eg, _ := errgroup.WithContext(ctx)
  37. eg.Go(func() error {
  38. return run(ctx, g, eg, leaves, fn)
  39. })
  40. return eg.Wait()
  41. }
  42. // Note: this could be `graph.walk` or whatever
  43. func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex, fn func(context.Context, types.ServiceConfig) error) error {
  44. for _, node := range nodes {
  45. n := node
  46. // Don't start this service yet if all of its children have
  47. // not been started yet.
  48. if len(graph.FilterChildren(n.Service.Name, ServiceStopped)) != 0 {
  49. continue
  50. }
  51. eg.Go(func() error {
  52. err := fn(ctx, n.Service)
  53. if err != nil {
  54. return err
  55. }
  56. graph.UpdateStatus(n.Service.Name, ServiceStarted)
  57. return run(ctx, graph, eg, n.GetParents(), fn)
  58. })
  59. }
  60. return nil
  61. }
  62. // Graph represents project as service dependencies
  63. type Graph struct {
  64. Vertices map[string]*Vertex
  65. lock sync.RWMutex
  66. }
  67. // Vertex represents a service in the dependencies structure
  68. type Vertex struct {
  69. Key string
  70. Service types.ServiceConfig
  71. Status ServiceStatus
  72. Children map[string]*Vertex
  73. Parents map[string]*Vertex
  74. }
  75. // GetParents returns a slice with the parent vertexes of the current Vertex
  76. func (v *Vertex) GetParents() []*Vertex {
  77. var res []*Vertex
  78. for _, p := range v.Parents {
  79. res = append(res, p)
  80. }
  81. return res
  82. }
  83. // NewGraph returns the dependency graph of the services
  84. func NewGraph(services types.Services) *Graph {
  85. graph := &Graph{
  86. lock: sync.RWMutex{},
  87. Vertices: map[string]*Vertex{},
  88. }
  89. for _, s := range services {
  90. graph.AddVertex(s.Name, s)
  91. }
  92. for _, s := range services {
  93. for _, name := range s.GetDependencies() {
  94. _ = graph.AddEdge(s.Name, name)
  95. }
  96. }
  97. return graph
  98. }
  99. // NewVertex is the constructor function for the Vertex
  100. func NewVertex(key string, service types.ServiceConfig) *Vertex {
  101. return &Vertex{
  102. Key: key,
  103. Service: service,
  104. Status: ServiceStopped,
  105. Parents: map[string]*Vertex{},
  106. Children: map[string]*Vertex{},
  107. }
  108. }
  109. // AddVertex adds a vertex to the Graph
  110. func (g *Graph) AddVertex(key string, service types.ServiceConfig) {
  111. g.lock.Lock()
  112. defer g.lock.Unlock()
  113. v := NewVertex(key, service)
  114. g.Vertices[key] = v
  115. }
  116. // AddEdge adds a relationship of dependency between vertexes `source` and `destination`
  117. func (g *Graph) AddEdge(source string, destination string) error {
  118. g.lock.Lock()
  119. defer g.lock.Unlock()
  120. sourceVertex := g.Vertices[source]
  121. destinationVertex := g.Vertices[destination]
  122. if sourceVertex == nil {
  123. return fmt.Errorf("could not find %s", source)
  124. }
  125. if destinationVertex == nil {
  126. return fmt.Errorf("could not find %s", destination)
  127. }
  128. // If they are already connected
  129. if _, ok := sourceVertex.Children[destination]; ok {
  130. return nil
  131. }
  132. sourceVertex.Children[destination] = destinationVertex
  133. destinationVertex.Parents[source] = sourceVertex
  134. return nil
  135. }
  136. // Leaves returns the slice of leaves of the graph
  137. func (g *Graph) Leaves() []*Vertex {
  138. g.lock.Lock()
  139. defer g.lock.Unlock()
  140. var res []*Vertex
  141. for _, v := range g.Vertices {
  142. if len(v.Children) == 0 {
  143. res = append(res, v)
  144. }
  145. }
  146. return res
  147. }
  148. // UpdateStatus updates the status of a certain vertex
  149. func (g *Graph) UpdateStatus(key string, status ServiceStatus) {
  150. g.lock.Lock()
  151. defer g.lock.Unlock()
  152. g.Vertices[key].Status = status
  153. }
  154. // FilterChildren returns children of a certain vertex that are in a certain status
  155. func (g *Graph) FilterChildren(key string, status ServiceStatus) []*Vertex {
  156. g.lock.Lock()
  157. defer g.lock.Unlock()
  158. var res []*Vertex
  159. vertex := g.Vertices[key]
  160. for _, child := range vertex.Children {
  161. if child.Status == status {
  162. res = append(res, child)
  163. }
  164. }
  165. return res
  166. }
  167. // HasCycles detects cycles in the graph
  168. func (g *Graph) HasCycles() (bool, error) {
  169. discovered := []string{}
  170. finished := []string{}
  171. for _, vertex := range g.Vertices {
  172. path := []string{
  173. vertex.Key,
  174. }
  175. if !contains(discovered, vertex.Key) && !contains(finished, vertex.Key) {
  176. var err error
  177. discovered, finished, err = g.visit(vertex.Key, path, discovered, finished)
  178. if err != nil {
  179. return true, err
  180. }
  181. }
  182. }
  183. return false, nil
  184. }
  185. func (g *Graph) visit(key string, path []string, discovered []string, finished []string) ([]string, []string, error) {
  186. discovered = append(discovered, key)
  187. for _, v := range g.Vertices[key].Children {
  188. path := append(path, v.Key)
  189. if contains(discovered, v.Key) {
  190. return nil, nil, fmt.Errorf("cycle found: %s", strings.Join(path, " -> "))
  191. }
  192. if !contains(finished, v.Key) {
  193. if _, _, err := g.visit(v.Key, path, discovered, finished); err != nil {
  194. return nil, nil, err
  195. }
  196. }
  197. }
  198. discovered = remove(discovered, key)
  199. finished = append(finished, key)
  200. return discovered, finished, nil
  201. }
  202. func remove(slice []string, item string) []string {
  203. var s []string
  204. for _, i := range slice {
  205. if i != item {
  206. s = append(s, i)
  207. }
  208. }
  209. return s
  210. }