dependencies.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  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) error {
  55. return visit(ctx, project, upDirectionTraversalConfig, fn, ServiceStopped)
  56. }
  57. // InReverseDependencyOrder applies the function to the services of the project in reverse order of dependencies
  58. func InReverseDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, string) error) error {
  59. return visit(ctx, project, downDirectionTraversalConfig, fn, ServiceStarted)
  60. }
  61. func visit(ctx context.Context, project *types.Project, traversalConfig graphTraversalConfig, fn func(context.Context, string) error, initialStatus ServiceStatus) error {
  62. g := NewGraph(project.Services, initialStatus)
  63. if b, err := g.HasCycles(); b {
  64. return err
  65. }
  66. nodes := traversalConfig.extremityNodesFn(g)
  67. eg, _ := errgroup.WithContext(ctx)
  68. eg.Go(func() error {
  69. return run(ctx, g, eg, nodes, traversalConfig, fn)
  70. })
  71. return eg.Wait()
  72. }
  73. // Note: this could be `graph.walk` or whatever
  74. func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex, traversalConfig graphTraversalConfig, fn func(context.Context, string) error) error {
  75. for _, node := range nodes {
  76. // Don't start this service yet if all of its children have
  77. // not been started yet.
  78. if len(traversalConfig.filterAdjacentByStatusFn(graph, node.Service, traversalConfig.adjacentServiceStatusToSkip)) != 0 {
  79. continue
  80. }
  81. node := node
  82. eg.Go(func() error {
  83. err := fn(ctx, node.Service)
  84. if err != nil {
  85. return err
  86. }
  87. graph.UpdateStatus(node.Service, traversalConfig.targetServiceStatus)
  88. return run(ctx, graph, eg, traversalConfig.adjacentNodesFn(node), traversalConfig, fn)
  89. })
  90. }
  91. return nil
  92. }
  93. // Graph represents project as service dependencies
  94. type Graph struct {
  95. Vertices map[string]*Vertex
  96. lock sync.RWMutex
  97. }
  98. // Vertex represents a service in the dependencies structure
  99. type Vertex struct {
  100. Key string
  101. Service string
  102. Status ServiceStatus
  103. Children map[string]*Vertex
  104. Parents map[string]*Vertex
  105. }
  106. func getParents(v *Vertex) []*Vertex {
  107. return v.GetParents()
  108. }
  109. // GetParents returns a slice with the parent vertexes of the a Vertex
  110. func (v *Vertex) GetParents() []*Vertex {
  111. var res []*Vertex
  112. for _, p := range v.Parents {
  113. res = append(res, p)
  114. }
  115. return res
  116. }
  117. func getChildren(v *Vertex) []*Vertex {
  118. return v.GetChildren()
  119. }
  120. // GetChildren returns a slice with the child vertexes of the a Vertex
  121. func (v *Vertex) GetChildren() []*Vertex {
  122. var res []*Vertex
  123. for _, p := range v.Children {
  124. res = append(res, p)
  125. }
  126. return res
  127. }
  128. // NewGraph returns the dependency graph of the services
  129. func NewGraph(services types.Services, initialStatus ServiceStatus) *Graph {
  130. graph := &Graph{
  131. lock: sync.RWMutex{},
  132. Vertices: map[string]*Vertex{},
  133. }
  134. for _, s := range services {
  135. graph.AddVertex(s.Name, s.Name, initialStatus)
  136. }
  137. for _, s := range services {
  138. for _, name := range s.GetDependencies() {
  139. _ = graph.AddEdge(s.Name, name)
  140. }
  141. }
  142. return graph
  143. }
  144. // NewVertex is the constructor function for the Vertex
  145. func NewVertex(key string, service string, initialStatus ServiceStatus) *Vertex {
  146. return &Vertex{
  147. Key: key,
  148. Service: service,
  149. Status: initialStatus,
  150. Parents: map[string]*Vertex{},
  151. Children: map[string]*Vertex{},
  152. }
  153. }
  154. // AddVertex adds a vertex to the Graph
  155. func (g *Graph) AddVertex(key string, service string, initialStatus ServiceStatus) {
  156. g.lock.Lock()
  157. defer g.lock.Unlock()
  158. v := NewVertex(key, service, initialStatus)
  159. g.Vertices[key] = v
  160. }
  161. // AddEdge adds a relationship of dependency between vertexes `source` and `destination`
  162. func (g *Graph) AddEdge(source string, destination string) error {
  163. g.lock.Lock()
  164. defer g.lock.Unlock()
  165. sourceVertex := g.Vertices[source]
  166. destinationVertex := g.Vertices[destination]
  167. if sourceVertex == nil {
  168. return fmt.Errorf("could not find %s", source)
  169. }
  170. if destinationVertex == nil {
  171. return fmt.Errorf("could not find %s", destination)
  172. }
  173. // If they are already connected
  174. if _, ok := sourceVertex.Children[destination]; ok {
  175. return nil
  176. }
  177. sourceVertex.Children[destination] = destinationVertex
  178. destinationVertex.Parents[source] = sourceVertex
  179. return nil
  180. }
  181. func leaves(g *Graph) []*Vertex {
  182. return g.Leaves()
  183. }
  184. // Leaves returns the slice of leaves of the graph
  185. func (g *Graph) Leaves() []*Vertex {
  186. g.lock.Lock()
  187. defer g.lock.Unlock()
  188. var res []*Vertex
  189. for _, v := range g.Vertices {
  190. if len(v.Children) == 0 {
  191. res = append(res, v)
  192. }
  193. }
  194. return res
  195. }
  196. func roots(g *Graph) []*Vertex {
  197. return g.Roots()
  198. }
  199. // Roots returns the slice of "Roots" of the graph
  200. func (g *Graph) Roots() []*Vertex {
  201. g.lock.Lock()
  202. defer g.lock.Unlock()
  203. var res []*Vertex
  204. for _, v := range g.Vertices {
  205. if len(v.Parents) == 0 {
  206. res = append(res, v)
  207. }
  208. }
  209. return res
  210. }
  211. // UpdateStatus updates the status of a certain vertex
  212. func (g *Graph) UpdateStatus(key string, status ServiceStatus) {
  213. g.lock.Lock()
  214. defer g.lock.Unlock()
  215. g.Vertices[key].Status = status
  216. }
  217. func filterChildren(g *Graph, k string, s ServiceStatus) []*Vertex {
  218. return g.FilterChildren(k, s)
  219. }
  220. // FilterChildren returns children of a certain vertex that are in a certain status
  221. func (g *Graph) FilterChildren(key string, status ServiceStatus) []*Vertex {
  222. g.lock.Lock()
  223. defer g.lock.Unlock()
  224. var res []*Vertex
  225. vertex := g.Vertices[key]
  226. for _, child := range vertex.Children {
  227. if child.Status == status {
  228. res = append(res, child)
  229. }
  230. }
  231. return res
  232. }
  233. func filterParents(g *Graph, k string, s ServiceStatus) []*Vertex {
  234. return g.FilterParents(k, s)
  235. }
  236. // FilterParents returns the parents of a certain vertex that are in a certain status
  237. func (g *Graph) FilterParents(key string, status ServiceStatus) []*Vertex {
  238. g.lock.Lock()
  239. defer g.lock.Unlock()
  240. var res []*Vertex
  241. vertex := g.Vertices[key]
  242. for _, parent := range vertex.Parents {
  243. if parent.Status == status {
  244. res = append(res, parent)
  245. }
  246. }
  247. return res
  248. }
  249. // HasCycles detects cycles in the graph
  250. func (g *Graph) HasCycles() (bool, error) {
  251. discovered := []string{}
  252. finished := []string{}
  253. for _, vertex := range g.Vertices {
  254. path := []string{
  255. vertex.Key,
  256. }
  257. if !utils.StringContains(discovered, vertex.Key) && !utils.StringContains(finished, vertex.Key) {
  258. var err error
  259. discovered, finished, err = g.visit(vertex.Key, path, discovered, finished)
  260. if err != nil {
  261. return true, err
  262. }
  263. }
  264. }
  265. return false, nil
  266. }
  267. func (g *Graph) visit(key string, path []string, discovered []string, finished []string) ([]string, []string, error) {
  268. discovered = append(discovered, key)
  269. for _, v := range g.Vertices[key].Children {
  270. path := append(path, v.Key)
  271. if utils.StringContains(discovered, v.Key) {
  272. return nil, nil, fmt.Errorf("cycle found: %s", strings.Join(path, " -> "))
  273. }
  274. if !utils.StringContains(finished, v.Key) {
  275. if _, _, err := g.visit(v.Key, path, discovered, finished); err != nil {
  276. return nil, nil, err
  277. }
  278. }
  279. }
  280. discovered = remove(discovered, key)
  281. finished = append(finished, key)
  282. return discovered, finished, nil
  283. }
  284. func remove(slice []string, item string) []string {
  285. var s []string
  286. for _, i := range slice {
  287. if i != item {
  288. s = append(s, i)
  289. }
  290. }
  291. return s
  292. }