github.com/mweagle/Sparta@v1.15.0/util.go (about) 1 package sparta 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "strings" 8 "sync" 9 10 gocf "github.com/mweagle/go-cloudformation" 11 "github.com/sirupsen/logrus" 12 ) 13 14 // describeInfoValue is a utility function that accepts 15 // some type of dynamic gocf value and transforms it into 16 // something that is `describe` output compatible 17 func describeInfoValue(dynamicValue interface{}) string { 18 switch typedArn := dynamicValue.(type) { 19 case string: 20 return typedArn 21 case gocf.Stringable: 22 data, dataErr := json.Marshal(typedArn) 23 if dataErr != nil { 24 data = []byte(fmt.Sprintf("%v", typedArn)) 25 } 26 return string(data) 27 default: 28 panic(fmt.Sprintf("Unsupported dynamic value type for `describe`: %+v", typedArn)) 29 } 30 } 31 32 // relativePath returns the relative path of logPath if it's relative to the current 33 // workint directory 34 func relativePath(logPath string) string { 35 cwd, cwdErr := os.Getwd() 36 if cwdErr == nil { 37 relPath := strings.TrimPrefix(logPath, cwd) 38 if relPath != logPath { 39 logPath = fmt.Sprintf(".%s", relPath) 40 } 41 } 42 return logPath 43 } 44 45 // workResult is the result from a worker task 46 type workResult interface { 47 Result() interface{} 48 Error() error 49 } 50 51 // taskResult is a convenience type for a task poll return value 52 type taskResult struct { 53 result interface{} 54 err error 55 } 56 57 func (tr *taskResult) Result() interface{} { 58 return tr.result 59 } 60 func (tr *taskResult) Error() error { 61 return tr.err 62 } 63 64 func newTaskResult(taskValue interface{}, err error) workResult { 65 return &taskResult{ 66 result: taskValue, 67 err: err, 68 } 69 } 70 71 type taskFunc func() workResult 72 73 // workTask encapsulates a work item that should go in a work pool. 74 type workTask struct { 75 // Result is the result of the work action 76 Result workResult 77 task taskFunc 78 } 79 80 // Run runs a Task and does appropriate accounting via a given sync.WorkGroup. 81 func (t *workTask) Run(wg *sync.WaitGroup) { 82 t.Result = t.task() 83 wg.Done() 84 } 85 86 // newWorkTask initializes a new task based on a given work function. 87 func newWorkTask(f taskFunc) *workTask { 88 return &workTask{task: f} 89 } 90 91 // workerPool is a worker group that runs a number of tasks at a configured 92 // concurrency. 93 type workerPool struct { 94 Tasks []*workTask 95 96 concurrency int 97 tasksChan chan *workTask 98 wg sync.WaitGroup 99 } 100 101 // newWorkerPool initializes a new pool with the given tasks and at the given 102 // concurrency. 103 func newWorkerPool(tasks []*workTask, concurrency int) *workerPool { 104 return &workerPool{ 105 Tasks: tasks, 106 concurrency: concurrency, 107 tasksChan: make(chan *workTask), 108 } 109 } 110 111 // HasErrors indicates whether there were any errors from tasks run. Its result 112 // is only meaningful after Run has been called. 113 func (p *workerPool) workResults() ([]interface{}, []error) { 114 result := []interface{}{} 115 errors := []error{} 116 117 for _, eachResult := range p.Tasks { 118 if eachResult.Result.Error() != nil { 119 errors = append(errors, eachResult.Result.Error()) 120 } else { 121 result = append(result, eachResult.Result.Result()) 122 } 123 } 124 return result, errors 125 } 126 127 // Run runs all work within the pool and blocks until it's finished. 128 func (p *workerPool) Run() ([]interface{}, []error) { 129 for i := 0; i < p.concurrency; i++ { 130 go p.work() 131 } 132 133 p.wg.Add(len(p.Tasks)) 134 for _, task := range p.Tasks { 135 p.tasksChan <- task 136 } 137 138 // all workers return 139 close(p.tasksChan) 140 141 p.wg.Wait() 142 return p.workResults() 143 } 144 145 // The work loop for any single goroutine. 146 func (p *workerPool) work() { 147 for task := range p.tasksChan { 148 task.Run(&p.wg) 149 } 150 } 151 152 func logSectionHeader(text string, 153 dividerWidth int, 154 logger *logrus.Logger) { 155 // Add a nice divider if there are Stack specific output 156 outputHeader := fmt.Sprintf("%s ", text) 157 suffix := strings.Repeat("▬", dividerWidth-len(outputHeader)) 158 logger.Info(fmt.Sprintf("%s%s", outputHeader, suffix)) 159 }