diff --git a/runtime/ci/evaluator.go b/runtime/ci_evaluator.go similarity index 68% rename from runtime/ci/evaluator.go rename to runtime/ci_evaluator.go index 9e1a7f9..d1b159d 100644 --- a/runtime/ci/evaluator.go +++ b/runtime/ci_evaluator.go @@ -1,8 +1,10 @@ -package ci +package runtime import ( "fmt" + "github.com/dustin/go-humanize" "sort" + "strconv" "strings" "github.com/spf13/viper" @@ -11,12 +13,13 @@ import ( "github.com/wagoodman/dive/image" ) -type Evaluator struct { - Rules []Rule - Results map[string]RuleResult - Tally ResultTally - Pass bool - Misconfigured bool +type CiEvaluator struct { + Rules []CiRule + Results map[string]RuleResult + Tally ResultTally + Pass bool + Misconfigured bool + InefficientFiles []ReferenceFile } type ResultTally struct { @@ -27,19 +30,19 @@ type ResultTally struct { Total int } -func NewEvaluator(config *viper.Viper) *Evaluator { - return &Evaluator{ +func NewCiEvaluator(config *viper.Viper) *CiEvaluator { + return &CiEvaluator{ Rules: loadCiRules(config), Results: make(map[string]RuleResult), Pass: true, } } -func (ci *Evaluator) isRuleEnabled(rule Rule) bool { +func (ci *CiEvaluator) isRuleEnabled(rule CiRule) bool { return rule.Configuration() != "disabled" } -func (ci *Evaluator) Evaluate(analysis *image.AnalysisResult) bool { +func (ci *CiEvaluator) Evaluate(analysis *image.AnalysisResult) bool { canEvaluate := true for _, rule := range ci.Rules { if !ci.isRuleEnabled(rule) { @@ -72,6 +75,18 @@ func (ci *Evaluator) Evaluate(analysis *image.AnalysisResult) bool { return ci.Pass } + // capture inefficient files + for idx := 0; idx < len(analysis.Inefficiencies); idx++ { + fileData := analysis.Inefficiencies[len(analysis.Inefficiencies)-1-idx] + + ci.InefficientFiles = append(ci.InefficientFiles, ReferenceFile{ + References: len(fileData.Nodes), + SizeBytes: uint64(fileData.CumulativeSize), + Path: fileData.Path, + }) + } + + // evaluate results against the configured CI rules for _, rule := range ci.Rules { if !ci.isRuleEnabled(rule) { ci.Results[rule.Key()] = RuleResult{ @@ -117,7 +132,22 @@ func (ci *Evaluator) Evaluate(analysis *image.AnalysisResult) bool { return ci.Pass } -func (ci *Evaluator) Report() { +func (ci *CiEvaluator) Report() { + fmt.Println(title("Inefficient Files:")) + + template := "%5s %12s %-s\n" + fmt.Printf(template, "Count", "Wasted Space", "File Path") + + if len(ci.InefficientFiles) == 0 { + fmt.Println("None") + } else { + for _, file := range ci.InefficientFiles { + fmt.Printf(template, strconv.Itoa(file.References), humanize.Bytes(uint64(file.SizeBytes)), file.Path) + } + } + + fmt.Println(title("Results:")) + status := "PASS" rules := make([]string, 0, len(ci.Results)) diff --git a/runtime/ci/evaluator_test.go b/runtime/ci_evaluator_test.go similarity index 94% rename from runtime/ci/evaluator_test.go rename to runtime/ci_evaluator_test.go index f5ffa4e..c7124ad 100644 --- a/runtime/ci/evaluator_test.go +++ b/runtime/ci_evaluator_test.go @@ -1,4 +1,4 @@ -package ci +package runtime import ( "strings" @@ -10,7 +10,7 @@ import ( func Test_Evaluator(t *testing.T) { - result, err := image.TestLoadDockerImageTar("../../.data/test-docker-image.tar") + result, err := image.TestLoadDockerImageTar("../.data/test-docker-image.tar") if err != nil { t.Fatalf("Test_Export: unable to fetch analysis: %v", err) } @@ -35,7 +35,7 @@ func Test_Evaluator(t *testing.T) { ciConfig.SetDefault("rules.highestWastedBytes", test.wastedBytes) ciConfig.SetDefault("rules.highestUserWastedPercent", test.wastedPercent) - evaluator := NewEvaluator(ciConfig) + evaluator := NewCiEvaluator(ciConfig) pass := evaluator.Evaluate(result) diff --git a/runtime/ci/rules.go b/runtime/ci_rule.go similarity index 97% rename from runtime/ci/rules.go rename to runtime/ci_rule.go index a2e1c37..d14295a 100644 --- a/runtime/ci/rules.go +++ b/runtime/ci_rule.go @@ -1,4 +1,4 @@ -package ci +package runtime import ( "fmt" @@ -21,7 +21,7 @@ const ( RuleConfigured ) -type Rule interface { +type CiRule interface { Key() string Configuration() string Validate() error @@ -86,8 +86,8 @@ func (status RuleStatus) String() string { } } -func loadCiRules(config *viper.Viper) []Rule { - var rules = make([]Rule, 0) +func loadCiRules(config *viper.Viper) []CiRule { + var rules = make([]CiRule, 0) var ruleKey = "lowestEfficiency" rules = append(rules, newGenericCiRule( ruleKey, diff --git a/runtime/export.go b/runtime/export.go index d2cdb55..e769015 100644 --- a/runtime/export.go +++ b/runtime/export.go @@ -7,10 +7,29 @@ import ( "github.com/wagoodman/dive/image" ) +type export struct { + Layer []exportLayer `json:"layer"` + Image exportImage `json:"image"` +} + +type exportLayer struct { + Index int `json:"index"` + DigestID string `json:"digestId"` + SizeBytes uint64 `json:"sizeBytes"` + Command string `json:"command"` +} + +type exportImage struct { + SizeBytes uint64 `json:"sizeBytes"` + InefficientBytes uint64 `json:"inefficientBytes"` + EfficiencyScore float64 `json:"efficiencyScore"` + InefficientFiles []ReferenceFile `json:"ReferenceFile"` +} + func newExport(analysis *image.AnalysisResult) *export { data := export{} data.Layer = make([]exportLayer, len(analysis.Layers)) - data.Image.InefficientFiles = make([]inefficientFiles, len(analysis.Inefficiencies)) + data.Image.InefficientFiles = make([]ReferenceFile, len(analysis.Inefficiencies)) // export layers in order for revIdx := len(analysis.Layers) - 1; revIdx >= 0; revIdx-- { @@ -32,10 +51,10 @@ func newExport(analysis *image.AnalysisResult) *export { for idx := 0; idx < len(analysis.Inefficiencies); idx++ { fileData := analysis.Inefficiencies[len(analysis.Inefficiencies)-1-idx] - data.Image.InefficientFiles[idx] = inefficientFiles{ - Count: len(fileData.Nodes), - SizeBytes: uint64(fileData.CumulativeSize), - File: fileData.Path, + data.Image.InefficientFiles[idx] = ReferenceFile{ + References: len(fileData.Nodes), + SizeBytes: uint64(fileData.CumulativeSize), + Path: fileData.Path, } } diff --git a/runtime/export_test.go b/runtime/export_test.go index 2859ae4..709225c 100644 --- a/runtime/export_test.go +++ b/runtime/export_test.go @@ -109,7 +109,7 @@ func Test_Export(t *testing.T) { "sizeBytes": 1220598, "inefficientBytes": 32025, "efficiencyScore": 0.9844212134184309, - "inefficientFiles": [ + "ReferenceFile": [ { "count": 2, "sizeBytes": 12810, diff --git a/runtime/options.go b/runtime/options.go new file mode 100644 index 0000000..37620c9 --- /dev/null +++ b/runtime/options.go @@ -0,0 +1,13 @@ +package runtime + +import ( + "github.com/spf13/viper" +) + +type Options struct { + Ci bool + ImageId string + ExportFile string + CiConfig *viper.Viper + BuildArgs []string +} diff --git a/runtime/reference_file.go b/runtime/reference_file.go new file mode 100644 index 0000000..bef54eb --- /dev/null +++ b/runtime/reference_file.go @@ -0,0 +1,7 @@ +package runtime + +type ReferenceFile struct { + References int `json:"count"` + SizeBytes uint64 `json:"sizeBytes"` + Path string `json:"file"` +} diff --git a/runtime/run.go b/runtime/run.go index e6bd46e..a427bfa 100644 --- a/runtime/run.go +++ b/runtime/run.go @@ -10,7 +10,6 @@ import ( "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" ) @@ -24,9 +23,7 @@ func runCi(analysis *image.AnalysisResult, options Options) { fmt.Printf(" wastedBytes: %d bytes (%s)\n", analysis.WastedBytes, humanize.Bytes(analysis.WastedBytes)) fmt.Printf(" userWastedPercent: %2.4f %%\n", analysis.WastedUserPercent*100) - evaluator := ci.NewEvaluator(options.CiConfig) - - fmt.Println(title("Run CI Validations...")) + evaluator := NewCiEvaluator(options.CiConfig) pass := evaluator.Evaluate(analysis) evaluator.Report() diff --git a/runtime/types.go b/runtime/types.go deleted file mode 100644 index 02f64ca..0000000 --- a/runtime/types.go +++ /dev/null @@ -1,38 +0,0 @@ -package runtime - -import ( - "github.com/spf13/viper" -) - -type Options struct { - Ci bool - ImageId string - ExportFile string - CiConfig *viper.Viper - BuildArgs []string -} - -type export struct { - Layer []exportLayer `json:"layer"` - Image exportImage `json:"image"` -} - -type exportLayer struct { - Index int `json:"index"` - DigestID string `json:"digestId"` - SizeBytes uint64 `json:"sizeBytes"` - Command string `json:"command"` -} - -type exportImage struct { - SizeBytes uint64 `json:"sizeBytes"` - InefficientBytes uint64 `json:"inefficientBytes"` - EfficiencyScore float64 `json:"efficiencyScore"` - InefficientFiles []inefficientFiles `json:"inefficientFiles"` -} - -type inefficientFiles struct { - Count int `json:"count"` - SizeBytes uint64 `json:"sizeBytes"` - File string `json:"file"` -}