mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
336 lines
7.3 KiB
Go
336 lines
7.3 KiB
Go
package genny
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/gobuffalo/events"
|
|
"github.com/markbates/oncer"
|
|
"github.com/markbates/safe"
|
|
"github.com/pkg/errors"
|
|
"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 errors.WithStack(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 errors.WithStack(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 errors.WithStack(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, errors.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 errors.WithStack(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 errors.WithStack(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 errors.WithStack(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 errors.WithStack(e)
|
|
}
|
|
if s, ok := f.(io.Seeker); ok {
|
|
s.Seek(0, 0)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return errors.WithStack(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 errors.WithStack(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 errors.WithStack(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
|
|
}
|