summaryrefslogtreecommitdiffstats
path: root/runtime/run.go
blob: 8def88a3021409dee85269622d6aa96863672632 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package runtime

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"strconv"

	"github.com/dustin/go-humanize"
	"github.com/logrusorgru/aurora"
	"github.com/wagoodman/dive/filetree"
	"github.com/wagoodman/dive/image"
	"github.com/wagoodman/dive/runtime/ci"
	"github.com/wagoodman/dive/ui"
	"github.com/wagoodman/dive/utils"
)

func title(s string) string {
	return aurora.Bold(s).String()
}

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)

	fmt.Println(title("Run CI Validations..."))
	evaluator := ci.NewEvaluator()

	err := evaluator.LoadConfig(options.CiConfigFile)
	if err != nil {
		fmt.Println("  Using default CI config")
	} else {
		fmt.Printf("  Using CI config: %s\n", options.CiConfigFile)
	}

	pass := evaluator.Evaluate(analysis)
	evaluator.Report()

	if pass {
		utils.Exit(0)
	}
	utils.Exit(1)
}

func runBuild(buildArgs []string) string {
	iidfile, err := ioutil.TempFile("/tmp", "dive.*.iid")
	if err != nil {
		utils.Cleanup()
		log.Fatal(err)
	}
	defer os.Remove(iidfile.Name())

	allArgs := append([]string{"--iidfile", iidfile.Name()}, buildArgs...)
	err = utils.RunDockerCmd("build", allArgs...)
	if err != nil {
		utils.Cleanup()
		log.Fatal(err)
	}

	imageId, err := ioutil.ReadFile(iidfile.Name())
	if err != nil {
		utils.Cleanup()
		log.Fatal(err)
	}

	return string(imageId)
}

func Run(options Options) {
	doExport := options.ExportFile != ""
	doBuild := len(options.BuildArgs) > 0
	isCi, _ := strconv.ParseBool(os.Getenv("CI"))

	if doBuild {
		fmt.Println(title("Building image..."))
		options.ImageId = runBuild(options.BuildArgs)
	}

	analyzer := image.GetAnalyzer(options.ImageId)

	fmt.Println(title("Fetching image...") + " (this can take a while with large images)")
	reader, err := analyzer.Fetch()
	if err != nil {
		fmt.Printf("cannot fetch image: %v\n", err)
		utils.Exit(1)
	}
	defer reader.Close()

	fmt.Println(title("Parsing image..."))
	err = analyzer.Parse(reader)
	if err != nil {
		fmt.Printf("cannot parse image: %v\n", err)
		utils.Exit(1)
	}

	if doExport {
		fmt.Println(title(fmt.Sprintf("Analyzing image... (export to '%s')", options.ExportFile)))
	} else {
		fmt.Println(title("Analyzing image..."))
	}

	result, err := analyzer.Analyze()
	if err != nil {
		fmt.Printf("cannot analyze image: %v\n", err)
		utils.Exit(1)
	}

	if doExport {
		err = newExport(result).toFile(options.ExportFile)
		if err != nil {
			fmt.Printf("cannot write export file: %v\n", err)
			utils.Exit(1)
		}
	}

	if isCi {
		runCi(result, options)
	} else {
		if doExport {
			utils.Exit(0)
		}

		fmt.Println(title("Building cache..."))
		cache := filetree.NewFileTreeCache(result.RefTrees)
		cache.Build()

		ui.Run(result, cache)
	}
}