* rework CI validation workflow and makefile * enable push * fix job names * fix license check * fix snapshot builds * fix acceptance tests * fix linting * disable pull request event * rework windows runner caching * disable release pipeline and add issue templates
161 lines
4.3 KiB
Go
161 lines
4.3 KiB
Go
package runtime
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/dustin/go-humanize"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/spf13/afero"
|
|
|
|
"github.com/wagoodman/dive/dive"
|
|
"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"
|
|
)
|
|
|
|
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 doBuild {
|
|
events.message(utils.TitleFormat("Building image..."))
|
|
img, err = imageResolver.Build(options.BuildArgs)
|
|
if err != nil {
|
|
events.exitWithErrorMessage("cannot build image", err)
|
|
return
|
|
}
|
|
} else {
|
|
events.message(utils.TitleFormat("Image Source: ") + options.Source.String() + "://" + options.Image)
|
|
events.message(utils.TitleFormat("Fetching image...") + " (this can take a while for large images)")
|
|
img, err = imageResolver.Fetch(options.Image)
|
|
if err != nil {
|
|
events.exitWithErrorMessage("cannot fetch image", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
events.message(utils.TitleFormat("Analyzing image..."))
|
|
analysis, err := img.Analyze()
|
|
if err != nil {
|
|
events.exitWithErrorMessage("cannot analyze image", err)
|
|
return
|
|
}
|
|
|
|
if doExport {
|
|
events.message(utils.TitleFormat(fmt.Sprintf("Exporting image to '%s'...", options.ExportFile)))
|
|
bytes, err := export.NewExport(analysis).Marshal()
|
|
if err != nil {
|
|
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 {
|
|
events.message(fmt.Sprintf(" efficiency: %2.4f %%", analysis.Efficiency*100))
|
|
events.message(fmt.Sprintf(" wastedBytes: %d bytes (%s)", analysis.WastedBytes, humanize.Bytes(analysis.WastedBytes)))
|
|
events.message(fmt.Sprintf(" userWastedPercent: %2.4f %%", analysis.WastedUserPercent*100))
|
|
|
|
evaluator := ci.NewCiEvaluator(options.CiConfig)
|
|
pass := evaluator.Evaluate(analysis)
|
|
events.message(evaluator.Report())
|
|
|
|
if !pass {
|
|
events.exitWithError(nil)
|
|
}
|
|
|
|
return
|
|
} else {
|
|
events.message(utils.TitleFormat("Building cache..."))
|
|
treeStack := filetree.NewComparer(analysis.RefTrees)
|
|
errors := treeStack.BuildCache()
|
|
if errors != nil {
|
|
for _, err := range errors {
|
|
events.message(" " + err.Error())
|
|
}
|
|
if !options.IgnoreErrors {
|
|
events.exitWithError(fmt.Errorf("file tree has path errors (use '--ignore-errors' to attempt to continue)"))
|
|
return
|
|
}
|
|
}
|
|
|
|
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(options.Image, analysis, treeStack)
|
|
if err != nil {
|
|
events.exitWithError(err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 != "" {
|
|
_, 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)
|
|
_, err := fmt.Fprintln(os.Stderr, event.err.Error())
|
|
if err != nil {
|
|
fmt.Println("error: could not write to buffer:", err)
|
|
}
|
|
}
|
|
|
|
if event.errorOnExit {
|
|
exitCode = 1
|
|
}
|
|
}
|
|
os.Exit(exitCode)
|
|
}
|