diff options
author | Alex Goodman <wagoodman@gmail.com> | 2019-10-09 14:49:59 -0400 |
---|---|---|
committer | Alex Goodman <wagoodman@gmail.com> | 2019-10-09 14:49:59 -0400 |
commit | c8ab7098d84bb647d48d2f8b20aa053e2870eeba (patch) | |
tree | 331a46eee139c0b6eda90168fd6f001da1fd761d /runtime/run.go | |
parent | 11a2473807e3442307ccabd7e63c4905293707fd (diff) |
add tests to cover runtime entrypoint
Diffstat (limited to 'runtime/run.go')
-rw-r--r-- | runtime/run.go | 187 |
1 files changed, 105 insertions, 82 deletions
diff --git a/runtime/run.go b/runtime/run.go index a55f583..0dbcfa8 100644 --- a/runtime/run.go +++ b/runtime/run.go @@ -2,126 +2,149 @@ package runtime import ( "fmt" + "github.com/dustin/go-humanize" "github.com/sirupsen/logrus" + "github.com/spf13/afero" "github.com/wagoodman/dive/dive" - "github.com/wagoodman/dive/runtime/ci" - "github.com/wagoodman/dive/runtime/export" - "os" - "time" - - "github.com/dustin/go-humanize" "github.com/wagoodman/dive/dive/filetree" "github.com/wagoodman/dive/dive/image" + "github.com/wagoodman/dive/runtime/ci" + "github.com/wagoodman/dive/runtime/export" "github.com/wagoodman/dive/runtime/ui" "github.com/wagoodman/dive/utils" + "os" + "time" ) -func runCi(analysis *image.AnalysisResult, options Options) { - - fmt.Printf(" efficiency: %2.4f %%\n", analysis.Efficiency*100) - fmt.Printf(" wastedBytes: %d bytes (%s)\n", analysis.WastedBytes, humanize.Bytes(analysis.WastedBytes)) - fmt.Printf(" userWastedPercent: %2.4f %%\n", analysis.WastedUserPercent*100) - - evaluator := ci.NewCiEvaluator(options.CiConfig) - - pass := evaluator.Evaluate(analysis) - - // todo: report should return a string? - evaluator.Report() - - if pass { - os.Exit(0) - } - os.Exit(1) -} - -// todo: give channel of strings which the caller uses for fmt.print? or a more complex type? -// todo: return err? or treat like a go routine? -// todo: should there be a run() so that Run() can do the above and run() be the go routine? Then we test the behavior of run() (not Run()) -func Run(options Options) { +func run(enableUi bool, options Options, imageResolver image.Resolver, events eventChannel, filesystem afero.Fs) { + var img *image.Image var err error + defer close(events) + doExport := options.ExportFile != "" doBuild := len(options.BuildArgs) > 0 - // if an image option was provided, parse it and determine the container image... - // otherwise, use the configs default value. - - // if build is given, get the handler based off of either the explicit runtime - - imageResolver, err := dive.GetImageResolver(options.Source) - if err != nil { - fmt.Printf("cannot determine image provider: %v\n", err) - os.Exit(1) - } - - var img *image.Image - if doBuild { - fmt.Println(utils.TitleFormat("Building image...")) + events.message(utils.TitleFormat("Building image...")) img, err = imageResolver.Build(options.BuildArgs) if err != nil { - fmt.Printf("cannot build image: %v\n", err) - os.Exit(1) + events.exitWithErrorMessage("cannot build image", err) + return } } else { - fmt.Println(utils.TitleFormat("Fetching image...") + " (this can take a while for large images)") + events.message(utils.TitleFormat("Fetching image...") + " (this can take a while for large images)") img, err = imageResolver.Fetch(options.Image) if err != nil { - fmt.Printf("cannot fetch image: %v\n", err) - os.Exit(1) + events.exitWithErrorMessage("cannot fetch image", err) + return } } - // todo, cleanup on error - // todo: image get should return error for cleanup? - - if doExport { - fmt.Println(utils.TitleFormat(fmt.Sprintf("Analyzing image... (export to '%s')", options.ExportFile))) - } else { - fmt.Println(utils.TitleFormat("Analyzing image...")) - } - - result, err := img.Analyze() + events.message(utils.TitleFormat("Analyzing image...")) + analysis, err := img.Analyze() if err != nil { - fmt.Printf("cannot analyze image: %v\n", err) - os.Exit(1) + events.exitWithErrorMessage("cannot analyze image", err) + return } if doExport { - err = export.NewExport(result).ToFile(options.ExportFile) + events.message(utils.TitleFormat(fmt.Sprintf("Exporting image to '%s'...", options.ExportFile))) + bytes, err := export.NewExport(analysis).Marshal() if err != nil { - fmt.Printf("cannot write export file: %v\n", err) - os.Exit(1) + events.exitWithErrorMessage("cannot marshal export payload", err) + return } + + file, err := filesystem.OpenFile(options.ExportFile, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + events.exitWithErrorMessage("cannot open export file", err) + return + } + defer file.Close() + + _, err = file.Write(bytes) + if err != nil { + events.exitWithErrorMessage("cannot write to export file", err) + } + return } if options.Ci { - runCi(result, options) - } else { - if doExport { - os.Exit(0) + events.message(fmt.Sprintf(" efficiency: %2.4f %%\n", analysis.Efficiency*100)) + events.message(fmt.Sprintf(" wastedBytes: %d bytes (%s)\n", analysis.WastedBytes, humanize.Bytes(analysis.WastedBytes))) + events.message(fmt.Sprintf(" userWastedPercent: %2.4f %%\n", analysis.WastedUserPercent*100)) + + evaluator := ci.NewCiEvaluator(options.CiConfig) + pass := evaluator.Evaluate(analysis) + events.message(evaluator.Report()) + + if !pass { + events.exitWithError(nil) } - fmt.Println(utils.TitleFormat("Building cache...")) - cache := filetree.NewFileTreeCache(result.RefTrees) + return + + } else { + events.message(utils.TitleFormat("Building cache...")) + cache := filetree.NewFileTreeCache(analysis.RefTrees) err := cache.Build() if err != nil { - logrus.Error(err) - os.Exit(1) + events.exitWithErrorMessage("cannot build cache tree", err) + return } - // it appears there is a race condition where termbox.Init() will - // block nearly indefinitely when running as the first process in - // a Docker container when started within ~25ms of container startup. - // I can't seem to determine the exact root cause, however, a large - // enough sleep will prevent this behavior (todo: remove this hack) - time.Sleep(100 * time.Millisecond) + if enableUi { + // it appears there is a race condition where termbox.Init() will + // block nearly indefinitely when running as the first process in + // a Docker container when started within ~25ms of container startup. + // I can't seem to determine the exact root cause, however, a large + // enough sleep will prevent this behavior (todo: remove this hack) + time.Sleep(100 * time.Millisecond) + + err = ui.Run(analysis, cache) + if err != nil { + events.exitWithErrorMessage("runtime error", err) + return + } + } + } +} - err = ui.Run(result, cache) - if err != nil { - logrus.Error(err) - os.Exit(1) +func Run(options Options) { + var exitCode int + var events = make(eventChannel) + + imageResolver, err := dive.GetImageResolver(options.Source) + if err != nil { + message := "cannot determine image provider" + logrus.Error(message) + logrus.Error(err) + fmt.Fprintf(os.Stderr, "%s: %+v\n", message, err) + os.Exit(1) + } + + go run(true, options, imageResolver, events, afero.NewOsFs()) + + for event := range events { + if event.stdout != "" { + fmt.Println(event.stdout) + } + + if event.stderr != "" { + logrus.Error(event.stderr) + _, err := fmt.Fprintln(os.Stderr, event.stderr) + if err != nil { + fmt.Println("error: could not write to buffer:", err) + } + } + + if event.err != nil { + logrus.Error(event.err) + } + + if event.errorOnExit { + exitCode = 1 } - os.Exit(0) } + os.Exit(exitCode) } |