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  }