dependencies.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. /*
  2. Copyright 2020 Docker Compose CLI authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package compose
  14. import (
  15. "context"
  16. "fmt"
  17. "strings"
  18. "sync"
  19. "github.com/compose-spec/compose-go/types"
  20. "golang.org/x/sync/errgroup"
  21. "github.com/docker/compose/v2/pkg/utils"
  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. type graphTraversalConfig struct {
  31. extremityNodesFn func(*Graph) []*Vertex // leaves or roots
  32. adjacentNodesFn func(*Vertex) []*Vertex // getParents or getChildren
  33. filterAdjacentByStatusFn func(*Graph, string, ServiceStatus) []*Vertex // filterChildren or filterParents
  34. targetServiceStatus ServiceStatus
  35. adjacentServiceStatusToSkip ServiceStatus
  36. }
  37. var (
  38. upDirectionTraversalConfig = graphTraversalConfig{
  39. extremityNodesFn: leaves,
  40. adjacentNodesFn: getParents,
  41. filterAdjacentByStatusFn: filterChildren,
  42. adjacentServiceStatusToSkip: ServiceStopped,
  43. targetServiceStatus: ServiceStarted,
  44. }
  45. downDirectionTraversalConfig = graphTraversalConfig{
  46. extremityNodesFn: roots,
  47. adjacentNodesFn: getChildren,
  48. filterAdjacentByStatusFn: filterParents,
  49. adjacentServiceStatusToSkip: ServiceStarted,
  50. targetServiceStatus: ServiceStopped,
  51. }
  52. )
  53. // InDependencyOrder applies the function to the services of the project taking in account the dependency order
  54. func InDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, string) error, options ...func(*graphTraversalConfig)) error {
  55. graph, err := NewGraph(project.Services, ServiceStopped)
  56. if err != nil {
  57. return err
  58. }
  59. return visit(ctx, graph, upDirectionTraversalConfig, fn)
  60. }
  61. // InReverseDependencyOrder applies the function to the services of the project in reverse order of dependencies
  62. func InReverseDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, string) error) error {
  63. graph, err := NewGraph(project.Services, ServiceStarted)
  64. if err != nil {
  65. return err
  66. }
  67. return visit(ctx, graph, downDirectionTraversalConfig, fn)
  68. }
  69. func visit(ctx context.Context, g *Graph, traversalConfig graphTraversalConfig, fn func(context.Context, string) error) error {
  70. nodes := traversalConfig.extremityNodesFn(g)
  71. eg, _ := errgroup.WithContext(ctx)
  72. eg.Go(func() error {
  73. return run(ctx, g, eg, nodes, traversalConfig, fn)
  74. })
  75. return eg.Wait()
  76. }
  77. // Note: this could be `graph.walk` or whatever
  78. func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex, traversalConfig graphTraversalConfig, fn func(context.Context, string) error) error {
  79. for _, node := range nodes {
  80. // Don't start this service yet if all of its children have
  81. // not been started yet.
  82. if len(traversalConfig.filterAdjacentByStatusFn(graph, node.Key, traversalConfig.adjacentServiceStatusToSkip)) != 0 {
  83. continue
  84. }
  85. node := node
  86. eg.Go(func() error {
  87. err := fn(ctx, node.Service)
  88. if err != nil {
  89. return err
  90. }
  91. graph.UpdateStatus(node.Key, traversalConfig.targetServiceStatus)
  92. return run(ctx, graph, eg, traversalConfig.adjacentNodesFn(node), traversalConfig, fn)
  93. })
  94. }
  95. return nil
  96. }
  97. // Graph represents project as service dependencies
  98. type Graph struct {
  99. Vertices map[string]*Vertex
  100. lock sync.RWMutex
  101. }
  102. // Vertex represents a service in the dependencies structure
  103. type Vertex struct {
  104. Key string
  105. Service string
  106. Status ServiceStatus
  107. Children map[string]*Vertex
  108. Parents map[string]*Vertex
  109. }
  110. func getParents(v *Vertex) []*Vertex {
  111. return v.GetParents()
  112. }
  113. // GetParents returns a slice with the parent vertices of the a Vertex
  114. func (v *Vertex) GetParents() []*Vertex {
  115. var res []*Vertex
  116. for _, p := range v.Parents {
  117. res = append(res, p)
  118. }
  119. return res
  120. }
  121. func getChildren(v *Vertex) []*Vertex {
  122. return v.GetChildren()
  123. }
  124. // GetChildren returns a slice with the child vertices of the a Vertex
  125. func (v *Vertex) GetChildren() []*Vertex {
  126. var res []*Vertex
  127. for _, p := range v.Children {
  128. res = append(res, p)
  129. }
  130. return res
  131. }
  132. // NewGraph returns the dependency graph of the services
  133. func NewGraph(services types.Services, initialStatus ServiceStatus) (*Graph, error) {
  134. graph := &Graph{
  135. lock: sync.RWMutex{},
  136. Vertices: map[string]*Vertex{},
  137. }
  138. for _, s := range services {
  139. graph.AddVertex(s.Name, s.Name, initialStatus)
  140. }
  141. for _, s := range services {
  142. for _, name := range s.GetDependencies() {
  143. _ = graph.AddEdge(s.Name, name)
  144. }
  145. }
  146. if b, err := graph.HasCycles(); b {
  147. return nil, err
  148. }
  149. return graph, nil
  150. }
  151. // NewVertex is the constructor function for the Vertex
  152. func NewVertex(key string, service string, initialStatus ServiceStatus) *Vertex {
  153. return &Vertex{
  154. Key: key,
  155. Service: service,
  156. Status: initialStatus,
  157. Parents: map[string]*Vertex{},
  158. Children: map[string]*Vertex{},
  159. }
  160. }
  161. // AddVertex adds a vertex to the Graph
  162. func (g *Graph) AddVertex(key string, service string, initialStatus ServiceStatus) {
  163. g.lock.Lock()
  164. defer g.lock.Unlock()
  165. v := NewVertex(key, service, initialStatus)
  166. g.Vertices[key] = v
  167. }
  168. // AddEdge adds a relationship of dependency between vertices `source` and `destination`
  169. func (g *Graph) AddEdge(source string, destination string) error {
  170. g.lock.Lock()
  171. defer g.lock.Unlock()
  172. sourceVertex := g.Vertices[source]
  173. destinationVertex := g.Vertices[destination]
  174. if sourceVertex == nil {
  175. return fmt.Errorf("could not find %s", source)
  176. }
  177. if destinationVertex == nil {
  178. return fmt.Errorf("could not find %s", destination)
  179. }
  180. // If they are already connected
  181. if _, ok := sourceVertex.Children[destination]; ok {
  182. return nil
  183. }
  184. sourceVertex.Children[destination] = destinationVertex
  185. destinationVertex.Parents[source] = sourceVertex
  186. return nil
  187. }
  188. func leaves(g *Graph) []*Vertex {
  189. return g.Leaves()
  190. }
  191. // Leaves returns the slice of leaves of the graph
  192. func (g *Graph) Leaves() []*Vertex {
  193. g.lock.Lock()
  194. defer g.lock.Unlock()
  195. var res []*Vertex
  196. for _, v := range g.Vertices {
  197. if len(v.Children) == 0 {
  198. res = append(res, v)
  199. }
  200. }
  201. return res
  202. }
  203. func roots(g *Graph) []*Vertex {
  204. return g.Roots()
  205. }
  206. // Roots returns the slice of "Roots" of the graph
  207. func (g *Graph) Roots() []*Vertex {
  208. g.lock.Lock()
  209. defer g.lock.Unlock()
  210. var res []*Vertex
  211. for _, v := range g.Vertices {
  212. if len(v.Parents) == 0 {
  213. res = append(res, v)
  214. }
  215. }
  216. return res
  217. }
  218. // UpdateStatus updates the status of a certain vertex
  219. func (g *Graph) UpdateStatus(key string, status ServiceStatus) {
  220. g.lock.Lock()
  221. defer g.lock.Unlock()
  222. g.Vertices[key].Status = status
  223. }
  224. func filterChildren(g *Graph, k string, s ServiceStatus) []*Vertex {
  225. return g.FilterChildren(k, s)
  226. }
  227. // FilterChildren returns children of a certain vertex that are in a certain status
  228. func (g *Graph) FilterChildren(key string, status ServiceStatus) []*Vertex {
  229. g.lock.Lock()
  230. defer g.lock.Unlock()
  231. var res []*Vertex
  232. vertex := g.Vertices[key]
  233. for _, child := range vertex.Children {
  234. if child.Status == status {
  235. res = append(res, child)
  236. }
  237. }
  238. return res
  239. }
  240. func filterParents(g *Graph, k string, s ServiceStatus) []*Vertex {
  241. return g.FilterParents(k, s)
  242. }
  243. // FilterParents returns the parents of a certain vertex that are in a certain status
  244. func (g *Graph) FilterParents(key string, status ServiceStatus) []*Vertex {
  245. g.lock.Lock()
  246. defer g.lock.Unlock()
  247. var res []*Vertex
  248. vertex := g.Vertices[key]
  249. for _, parent := range vertex.Parents {
  250. if parent.Status == status {
  251. res = append(res, parent)
  252. }
  253. }
  254. return res
  255. }
  256. // HasCycles detects cycles in the graph
  257. func (g *Graph) HasCycles() (bool, error) {
  258. discovered := []string{}
  259. finished := []string{}
  260. for _, vertex := range g.Vertices {
  261. path := []string{
  262. vertex.Key,
  263. }
  264. if !utils.StringContains(discovered, vertex.Key) && !utils.StringContains(finished, vertex.Key) {
  265. var err error
  266. discovered, finished, err = g.visit(vertex.Key, path, discovered, finished)
  267. if err != nil {
  268. return true, err
  269. }
  270. }
  271. }
  272. return false, nil
  273. }
  274. func (g *Graph) visit(key string, path []string, discovered []string, finished []string) ([]string, []string, error) {
  275. discovered = append(discovered, key)
  276. for _, v := range g.Vertices[key].Children {
  277. path := append(path, v.Key)
  278. if utils.StringContains(discovered, v.Key) {
  279. return nil, nil, fmt.Errorf("cycle found: %s", strings.Join(path, " -> "))
  280. }
  281. if !utils.StringContains(finished, v.Key) {
  282. if _, _, err := g.visit(v.Key, path, discovered, finished); err != nil {
  283. return nil, nil, err
  284. }
  285. }
  286. }
  287. discovered = remove(discovered, key)
  288. finished = append(finished, key)
  289. return discovered, finished, nil
  290. }
  291. func remove(slice []string, item string) []string {
  292. var s []string
  293. for _, i := range slice {
  294. if i != item {
  295. s = append(s, i)
  296. }
  297. }
  298. return s
  299. }