Files
polaris/vendor/github.com/gobuffalo/genny/runner.go
Bobby Brennan 54a4f92695 Pack static assets into packr box for portability
copy config.yaml to docker image

enable external usage of dashboard package

gofmt

fix comment

use packr for assets

add gobuffalo/packr dependency

add dependencies

fix pointer issues

add output-file option
2019-04-18 18:25:16 +00:00

334 lines
7.1 KiB
Go

package genny
import (
"context"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"sync"
"github.com/markbates/oncer"
"github.com/markbates/safe"
"github.com/sirupsen/logrus"
)
type RunFn func(r *Runner) error
// Runner will run the generators
type Runner struct {
Logger Logger // Logger to use for the run
Context context.Context // context to use for the run
ExecFn func(*exec.Cmd) error // function to use when executing files
FileFn func(File) (File, error) // function to use when writing files
ChdirFn func(string, func() error) error // function to use when changing directories
DeleteFn func(string) error // function used to delete files/folders
RequestFn func(*http.Request, *http.Client) (*http.Response, error) // function used to make http requests
LookPathFn func(string) (string, error) // function used to make exec.LookPath lookups
Root string // the root of the write path
Disk *Disk
steps map[string]*Step
generators []*Generator
moot *sync.RWMutex
results Results
curGen *Generator
}
func (r *Runner) Results() Results {
r.moot.Lock()
defer r.moot.Unlock()
r.results.Files = r.Disk.Files()
return r.results
}
func (r *Runner) WithRun(fn RunFn) {
g := New()
g.RunFn(fn)
r.With(g)
}
// With adds a Generator to the Runner
func (r *Runner) With(g *Generator) error {
r.moot.Lock()
step, ok := r.steps[g.StepName]
if !ok {
var err error
step, err = NewStep(g, len(r.steps))
if err != nil {
return err
}
}
r.moot.Unlock()
return r.WithStep(g.StepName, step)
}
func (r *Runner) WithGroup(gg *Group) {
for _, g := range gg.Generators {
r.With(g)
}
}
// WithNew takes a Generator and an error.
// Perfect for new-ing up generators
/*
// foo.New(Options) (*genny.Generator, error)
if err := run.WithNew(foo.New(opts)); err != nil {
return err
}
*/
func (r *Runner) WithNew(g *Generator, err error) error {
if err != nil {
return err
}
r.With(g)
return nil
}
// WithFn will evaluate the function and if successful it will add
// the Generator to the Runner, otherwise it will return the error
// Deprecated
func (r *Runner) WithFn(fn func() (*Generator, error)) error {
oncer.Deprecate(5, "genny.Runner#WithFn", "")
return safe.RunE(func() error {
g, err := fn()
if err != nil {
return err
}
r.With(g)
return nil
})
}
func (r *Runner) WithStep(name string, step *Step) error {
r.moot.Lock()
defer r.moot.Unlock()
if len(name) == 0 {
name = stepName()
}
r.steps[name] = step
return nil
}
func (r *Runner) Steps() []*Step {
r.moot.RLock()
var steps []*Step
for _, step := range r.steps {
steps = append(steps, step)
}
sort.Slice(steps, func(a, b int) bool {
return steps[a].index < steps[b].index
})
r.moot.RUnlock()
return steps
}
func (r *Runner) FindStep(name string) (*Step, error) {
r.moot.RLock()
s, ok := r.steps[name]
r.moot.RUnlock()
if !ok {
return nil, fmt.Errorf("could not find step %s", name)
}
return s, nil
}
func (r *Runner) ReplaceStep(name string, s *Step) error {
os, err := r.FindStep(name)
if err != nil {
return err
}
s.index = os.index
return r.WithStep(name, s)
}
func (r *Runner) Run() error {
if f, ok := r.Logger.(io.Closer); ok {
defer f.Close()
}
steps := r.Steps()
// payload := events.Payload{
// "runner": r,
// "steps": steps,
// }
// events.EmitPayload(EvtStarted, payload)
for _, step := range steps {
if err := step.Run(r); err != nil {
// payload = events.Payload{
// "runner": r,
// "step": step,
// }
// events.EmitError(EvtFinishedErr, err, payload)
return err
}
}
// events.EmitPayload(EvtFinished, payload)
return nil
}
// Exec can be used inside of Generators to run commands
func (r *Runner) Exec(cmd *exec.Cmd) error {
r.results.Commands = append(r.results.Commands, cmd)
r.Logger.Debug("Exec: ", strings.Join(cmd.Args, " "))
if r.ExecFn == nil {
return nil
}
return safe.RunE(func() error {
return r.ExecFn(cmd)
})
}
func (r *Runner) LookPath(s string) (string, error) {
r.Logger.Debug("LookPath: ", s)
if r.LookPathFn != nil {
return r.LookPathFn(s)
}
return s, nil
}
// File can be used inside of Generators to write files
func (r *Runner) File(f File) error {
if r.curGen != nil {
var err error
f, err = r.curGen.Transform(f)
if err != nil {
return err
}
}
name := f.Name()
if !filepath.IsAbs(name) {
name = filepath.Join(r.Root, name)
}
_, isDir := f.(Dir)
if isDir {
r.Logger.Debug("Dir: ", name)
} else {
r.Logger.Debug("File: ", name)
}
if r.FileFn != nil {
err := safe.RunE(func() error {
var e error
if f, e = r.FileFn(f); e != nil {
return e
}
if s, ok := f.(io.Seeker); ok {
s.Seek(0, 0)
}
return nil
})
if err != nil {
return err
}
}
f = NewFile(f.Name(), f)
if s, ok := f.(io.Seeker); ok {
s.Seek(0, 0)
}
r.Disk.Add(f)
return nil
}
func (r *Runner) FindFile(name string) (File, error) {
return r.Disk.Find(name)
}
// Chdir will change to the specified directory
// and revert back to the current directory when
// the runner function has returned.
// If the directory does not exist, it will be
// created for you.
func (r *Runner) Chdir(path string, fn func() error) error {
if len(path) == 0 {
return fn()
}
r.Logger.Debug("Chdir: ", path)
if r.ChdirFn != nil {
return safe.RunE(func() error {
return r.ChdirFn(path, fn)
})
}
if err := safe.RunE(fn); err != nil {
return err
}
return nil
}
func (r *Runner) Delete(path string) error {
r.Logger.Debug("Delete: ", path)
defer r.Disk.Remove(path)
if r.DeleteFn != nil {
return safe.RunE(func() error {
return r.DeleteFn(path)
})
}
return nil
}
func (r *Runner) Request(req *http.Request) (*http.Response, error) {
return r.RequestWithClient(req, http.DefaultClient)
}
func (r *Runner) RequestWithClient(req *http.Request, c *http.Client) (*http.Response, error) {
key := fmt.Sprintf("[%s] %s\n", strings.ToUpper(req.Method), req.URL)
r.Logger.Debug("Request: ", key)
store := func(res *http.Response, err error) (*http.Response, error) {
r.moot.Lock()
r.results.Requests = append(r.results.Requests, RequestResult{
Request: req,
Response: res,
Client: c,
Error: err,
})
r.moot.Unlock()
return res, err
}
if r.RequestFn == nil {
return store(nil, nil)
}
var res *http.Response
err := safe.RunE(func() error {
var e error
res, e = r.RequestFn(req, c)
if e != nil {
return e
}
return nil
})
return store(res, err)
}
// NewRunner will NOT execute commands and write files
// it is NOT destructive it is just the most basic Runner
// you can have.
func NewRunner(ctx context.Context) *Runner {
pwd, _ := os.Getwd()
l := logrus.New()
l.Out = os.Stdout
l.SetLevel(logrus.DebugLevel)
r := &Runner{
Logger: l,
Context: ctx,
Root: pwd,
moot: &sync.RWMutex{},
steps: map[string]*Step{},
}
r.Disk = newDisk(r)
return r
}