Merge pull request #216 from wagoodman/include-inefficient-files-in-ci-output
Add file report to CI results
This commit is contained in:
commit
d89bfed1fe
@ -1,8 +1,10 @@
|
|||||||
package ci
|
package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@ -11,12 +13,13 @@ import (
|
|||||||
"github.com/wagoodman/dive/image"
|
"github.com/wagoodman/dive/image"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Evaluator struct {
|
type CiEvaluator struct {
|
||||||
Rules []Rule
|
Rules []CiRule
|
||||||
Results map[string]RuleResult
|
Results map[string]RuleResult
|
||||||
Tally ResultTally
|
Tally ResultTally
|
||||||
Pass bool
|
Pass bool
|
||||||
Misconfigured bool
|
Misconfigured bool
|
||||||
|
InefficientFiles []ReferenceFile
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResultTally struct {
|
type ResultTally struct {
|
||||||
@ -27,19 +30,19 @@ type ResultTally struct {
|
|||||||
Total int
|
Total int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEvaluator(config *viper.Viper) *Evaluator {
|
func NewCiEvaluator(config *viper.Viper) *CiEvaluator {
|
||||||
return &Evaluator{
|
return &CiEvaluator{
|
||||||
Rules: loadCiRules(config),
|
Rules: loadCiRules(config),
|
||||||
Results: make(map[string]RuleResult),
|
Results: make(map[string]RuleResult),
|
||||||
Pass: true,
|
Pass: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ci *Evaluator) isRuleEnabled(rule Rule) bool {
|
func (ci *CiEvaluator) isRuleEnabled(rule CiRule) bool {
|
||||||
return rule.Configuration() != "disabled"
|
return rule.Configuration() != "disabled"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ci *Evaluator) Evaluate(analysis *image.AnalysisResult) bool {
|
func (ci *CiEvaluator) Evaluate(analysis *image.AnalysisResult) bool {
|
||||||
canEvaluate := true
|
canEvaluate := true
|
||||||
for _, rule := range ci.Rules {
|
for _, rule := range ci.Rules {
|
||||||
if !ci.isRuleEnabled(rule) {
|
if !ci.isRuleEnabled(rule) {
|
||||||
@ -72,6 +75,18 @@ func (ci *Evaluator) Evaluate(analysis *image.AnalysisResult) bool {
|
|||||||
return ci.Pass
|
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 {
|
for _, rule := range ci.Rules {
|
||||||
if !ci.isRuleEnabled(rule) {
|
if !ci.isRuleEnabled(rule) {
|
||||||
ci.Results[rule.Key()] = RuleResult{
|
ci.Results[rule.Key()] = RuleResult{
|
||||||
@ -117,7 +132,22 @@ func (ci *Evaluator) Evaluate(analysis *image.AnalysisResult) bool {
|
|||||||
return ci.Pass
|
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"
|
status := "PASS"
|
||||||
|
|
||||||
rules := make([]string, 0, len(ci.Results))
|
rules := make([]string, 0, len(ci.Results))
|
@ -1,4 +1,4 @@
|
|||||||
package ci
|
package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
func Test_Evaluator(t *testing.T) {
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("Test_Export: unable to fetch analysis: %v", err)
|
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.highestWastedBytes", test.wastedBytes)
|
||||||
ciConfig.SetDefault("rules.highestUserWastedPercent", test.wastedPercent)
|
ciConfig.SetDefault("rules.highestUserWastedPercent", test.wastedPercent)
|
||||||
|
|
||||||
evaluator := NewEvaluator(ciConfig)
|
evaluator := NewCiEvaluator(ciConfig)
|
||||||
|
|
||||||
pass := evaluator.Evaluate(result)
|
pass := evaluator.Evaluate(result)
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package ci
|
package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -21,7 +21,7 @@ const (
|
|||||||
RuleConfigured
|
RuleConfigured
|
||||||
)
|
)
|
||||||
|
|
||||||
type Rule interface {
|
type CiRule interface {
|
||||||
Key() string
|
Key() string
|
||||||
Configuration() string
|
Configuration() string
|
||||||
Validate() error
|
Validate() error
|
||||||
@ -86,8 +86,8 @@ func (status RuleStatus) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadCiRules(config *viper.Viper) []Rule {
|
func loadCiRules(config *viper.Viper) []CiRule {
|
||||||
var rules = make([]Rule, 0)
|
var rules = make([]CiRule, 0)
|
||||||
var ruleKey = "lowestEfficiency"
|
var ruleKey = "lowestEfficiency"
|
||||||
rules = append(rules, newGenericCiRule(
|
rules = append(rules, newGenericCiRule(
|
||||||
ruleKey,
|
ruleKey,
|
@ -7,10 +7,29 @@ import (
|
|||||||
"github.com/wagoodman/dive/image"
|
"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 {
|
func newExport(analysis *image.AnalysisResult) *export {
|
||||||
data := export{}
|
data := export{}
|
||||||
data.Layer = make([]exportLayer, len(analysis.Layers))
|
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
|
// export layers in order
|
||||||
for revIdx := len(analysis.Layers) - 1; revIdx >= 0; revIdx-- {
|
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++ {
|
for idx := 0; idx < len(analysis.Inefficiencies); idx++ {
|
||||||
fileData := analysis.Inefficiencies[len(analysis.Inefficiencies)-1-idx]
|
fileData := analysis.Inefficiencies[len(analysis.Inefficiencies)-1-idx]
|
||||||
|
|
||||||
data.Image.InefficientFiles[idx] = inefficientFiles{
|
data.Image.InefficientFiles[idx] = ReferenceFile{
|
||||||
Count: len(fileData.Nodes),
|
References: len(fileData.Nodes),
|
||||||
SizeBytes: uint64(fileData.CumulativeSize),
|
SizeBytes: uint64(fileData.CumulativeSize),
|
||||||
File: fileData.Path,
|
Path: fileData.Path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ func Test_Export(t *testing.T) {
|
|||||||
"sizeBytes": 1220598,
|
"sizeBytes": 1220598,
|
||||||
"inefficientBytes": 32025,
|
"inefficientBytes": 32025,
|
||||||
"efficiencyScore": 0.9844212134184309,
|
"efficiencyScore": 0.9844212134184309,
|
||||||
"inefficientFiles": [
|
"ReferenceFile": [
|
||||||
{
|
{
|
||||||
"count": 2,
|
"count": 2,
|
||||||
"sizeBytes": 12810,
|
"sizeBytes": 12810,
|
||||||
|
13
runtime/options.go
Normal file
13
runtime/options.go
Normal file
@ -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
|
||||||
|
}
|
7
runtime/reference_file.go
Normal file
7
runtime/reference_file.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package runtime
|
||||||
|
|
||||||
|
type ReferenceFile struct {
|
||||||
|
References int `json:"count"`
|
||||||
|
SizeBytes uint64 `json:"sizeBytes"`
|
||||||
|
Path string `json:"file"`
|
||||||
|
}
|
@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/logrusorgru/aurora"
|
"github.com/logrusorgru/aurora"
|
||||||
"github.com/wagoodman/dive/filetree"
|
"github.com/wagoodman/dive/filetree"
|
||||||
"github.com/wagoodman/dive/image"
|
"github.com/wagoodman/dive/image"
|
||||||
"github.com/wagoodman/dive/runtime/ci"
|
|
||||||
"github.com/wagoodman/dive/ui"
|
"github.com/wagoodman/dive/ui"
|
||||||
"github.com/wagoodman/dive/utils"
|
"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(" wastedBytes: %d bytes (%s)\n", analysis.WastedBytes, humanize.Bytes(analysis.WastedBytes))
|
||||||
fmt.Printf(" userWastedPercent: %2.4f %%\n", analysis.WastedUserPercent*100)
|
fmt.Printf(" userWastedPercent: %2.4f %%\n", analysis.WastedUserPercent*100)
|
||||||
|
|
||||||
evaluator := ci.NewEvaluator(options.CiConfig)
|
evaluator := NewCiEvaluator(options.CiConfig)
|
||||||
|
|
||||||
fmt.Println(title("Run CI Validations..."))
|
|
||||||
|
|
||||||
pass := evaluator.Evaluate(analysis)
|
pass := evaluator.Evaluate(analysis)
|
||||||
evaluator.Report()
|
evaluator.Report()
|
||||||
|
@ -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"`
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user