exec.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package exec
  2. import (
  3. "fmt"
  4. "sync"
  5. "github.com/porter-dev/porter/cli/cmd/switchboard/models"
  6. )
  7. // TODO: this exec func should probably accept channels or something
  8. type ExecFunc func(resource *models.Resource) error
  9. type ExecNode struct {
  10. isExecFinished bool
  11. isExecStarted bool
  12. parents []*ExecNode
  13. resource *models.Resource
  14. }
  15. func (e *ExecNode) IsFinished() bool {
  16. return e.isExecFinished
  17. }
  18. func (e *ExecNode) SetFinished() {
  19. e.isExecFinished = true
  20. }
  21. func (e *ExecNode) IsStarted() bool {
  22. return e.isExecStarted
  23. }
  24. func (e *ExecNode) SetStarted() {
  25. e.isExecStarted = true
  26. }
  27. func (e *ExecNode) ShouldStart() bool {
  28. // if the exec has started or finished, return false
  29. if e.IsStarted() || e.IsFinished() {
  30. return false
  31. }
  32. // if all parents have finished execution, the exec process should start
  33. parentsFinished := true
  34. for _, parent := range e.parents {
  35. parentsFinished = parentsFinished && parent.IsFinished()
  36. }
  37. return parentsFinished
  38. }
  39. // GetExecNodes
  40. func GetExecNodes(group *models.ResourceGroup) ([]*ExecNode, error) {
  41. // create a map of resource names to exec nodes
  42. resourceMap := make(map[string]*ExecNode)
  43. for _, resource := range group.Resources {
  44. // check that name does not already exist
  45. if _, exists := resourceMap[resource.Name]; exists {
  46. return nil, fmt.Errorf("duplicate resource name encountered for \"%s\"", resource.Name)
  47. }
  48. resourceMap[resource.Name] = &ExecNode{
  49. resource: resource,
  50. parents: make([]*ExecNode, 0),
  51. }
  52. }
  53. // Now that resources are registered, iterate through the resources again
  54. // to find the dependencies. If a dependency does not exist, throw an error
  55. res := make([]*ExecNode, 0)
  56. for _, execNode := range resourceMap {
  57. for _, dependency := range execNode.resource.Dependencies {
  58. // check that the resource described by the dependency exists
  59. if _, exists := resourceMap[dependency]; !exists {
  60. return nil, fmt.Errorf("parent resource \"%s\" does not exist", dependency)
  61. }
  62. execNode.parents = append(execNode.parents, resourceMap[dependency])
  63. }
  64. res = append(res, execNode)
  65. }
  66. // TODO: check against circular dependencies
  67. return res, nil
  68. }
  69. // Execute simply calls exec on nodes in parallel, in batches. This could be much more
  70. // efficient.
  71. func Execute(nodes []*ExecNode, execFunc ExecFunc) error {
  72. var outErr error
  73. for {
  74. var wg sync.WaitGroup
  75. // get the list of nodes which are ready to execute, and execute those nodes
  76. for _, node := range nodes {
  77. nodeP := node
  78. if nodeP.ShouldStart() {
  79. wg.Add(1)
  80. go func() {
  81. defer wg.Done()
  82. nodeP.SetStarted()
  83. err := execFunc(nodeP.resource)
  84. if err != nil {
  85. outErr = err
  86. return
  87. }
  88. nodeP.SetFinished()
  89. }()
  90. }
  91. }
  92. if outErr != nil {
  93. break
  94. }
  95. wg.Wait()
  96. if allFinished := areAllNodesFinished(nodes); allFinished {
  97. break
  98. }
  99. }
  100. return outErr
  101. }
  102. func areAllNodesFinished(nodes []*ExecNode) bool {
  103. areFinished := true
  104. for _, node := range nodes {
  105. areFinished = areFinished && node.IsFinished()
  106. }
  107. return areFinished
  108. }