* 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
169 lines
4.7 KiB
Go
169 lines
4.7 KiB
Go
package ci
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/dustin/go-humanize"
|
|
"github.com/logrusorgru/aurora"
|
|
"github.com/spf13/viper"
|
|
|
|
"github.com/wagoodman/dive/dive/image"
|
|
)
|
|
|
|
const (
|
|
RuleUnknown = iota
|
|
RulePassed
|
|
RuleFailed
|
|
RuleWarning
|
|
RuleDisabled
|
|
RuleMisconfigured
|
|
RuleConfigured
|
|
)
|
|
|
|
type CiRule interface {
|
|
Key() string
|
|
Configuration() string
|
|
Validate() error
|
|
Evaluate(result *image.AnalysisResult) (RuleStatus, string)
|
|
}
|
|
|
|
type GenericCiRule struct {
|
|
key string
|
|
configValue string
|
|
configValidator func(string) error
|
|
evaluator func(*image.AnalysisResult, string) (RuleStatus, string)
|
|
}
|
|
|
|
type RuleStatus int
|
|
|
|
type RuleResult struct {
|
|
status RuleStatus
|
|
message string
|
|
}
|
|
|
|
func newGenericCiRule(key string, configValue string, validator func(string) error, evaluator func(*image.AnalysisResult, string) (RuleStatus, string)) *GenericCiRule {
|
|
return &GenericCiRule{
|
|
key: key,
|
|
configValue: configValue,
|
|
configValidator: validator,
|
|
evaluator: evaluator,
|
|
}
|
|
}
|
|
|
|
func (rule *GenericCiRule) Key() string {
|
|
return rule.key
|
|
}
|
|
|
|
func (rule *GenericCiRule) Configuration() string {
|
|
return rule.configValue
|
|
}
|
|
|
|
func (rule *GenericCiRule) Validate() error {
|
|
return rule.configValidator(rule.configValue)
|
|
}
|
|
|
|
func (rule *GenericCiRule) Evaluate(result *image.AnalysisResult) (RuleStatus, string) {
|
|
return rule.evaluator(result, rule.configValue)
|
|
}
|
|
|
|
func (status RuleStatus) String() string {
|
|
switch status {
|
|
case RulePassed:
|
|
return "PASS"
|
|
case RuleFailed:
|
|
return aurora.Bold(aurora.Inverse(aurora.Red("FAIL"))).String()
|
|
case RuleWarning:
|
|
return aurora.Blue("WARN").String()
|
|
case RuleDisabled:
|
|
return aurora.Blue("SKIP").String()
|
|
case RuleMisconfigured:
|
|
return aurora.Bold(aurora.Inverse(aurora.Red("MISCONFIGURED"))).String()
|
|
case RuleConfigured:
|
|
return "CONFIGURED "
|
|
default:
|
|
return aurora.Inverse("Unknown").String()
|
|
}
|
|
}
|
|
|
|
func loadCiRules(config *viper.Viper) []CiRule {
|
|
var rules = make([]CiRule, 0)
|
|
var ruleKey = "lowestEfficiency"
|
|
rules = append(rules, newGenericCiRule(
|
|
ruleKey,
|
|
config.GetString(fmt.Sprintf("rules.%s", ruleKey)),
|
|
func(value string) error {
|
|
lowestEfficiency, err := strconv.ParseFloat(value, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid config value ('%v'): %v", value, err)
|
|
}
|
|
if lowestEfficiency < 0 || lowestEfficiency > 1 {
|
|
return fmt.Errorf("lowestEfficiency config value is outside allowed range (0-1), given '%s'", value)
|
|
}
|
|
return nil
|
|
},
|
|
func(analysis *image.AnalysisResult, value string) (RuleStatus, string) {
|
|
lowestEfficiency, err := strconv.ParseFloat(value, 64)
|
|
if err != nil {
|
|
return RuleFailed, fmt.Sprintf("invalid config value ('%v'): %v", value, err)
|
|
}
|
|
if lowestEfficiency > analysis.Efficiency {
|
|
return RuleFailed, fmt.Sprintf("image efficiency is too low (efficiency=%v < threshold=%v)", analysis.Efficiency, lowestEfficiency)
|
|
}
|
|
return RulePassed, ""
|
|
},
|
|
))
|
|
|
|
ruleKey = "highestWastedBytes"
|
|
rules = append(rules, newGenericCiRule(
|
|
ruleKey,
|
|
config.GetString(fmt.Sprintf("rules.%s", ruleKey)),
|
|
func(value string) error {
|
|
_, err := humanize.ParseBytes(value)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid config value ('%v'): %v", value, err)
|
|
}
|
|
return nil
|
|
},
|
|
func(analysis *image.AnalysisResult, value string) (RuleStatus, string) {
|
|
highestWastedBytes, err := humanize.ParseBytes(value)
|
|
if err != nil {
|
|
return RuleFailed, fmt.Sprintf("invalid config value ('%v'): %v", value, err)
|
|
}
|
|
if analysis.WastedBytes > highestWastedBytes {
|
|
return RuleFailed, fmt.Sprintf("too many bytes wasted (wasted-bytes=%v > threshold=%v)", analysis.WastedBytes, highestWastedBytes)
|
|
}
|
|
return RulePassed, ""
|
|
},
|
|
))
|
|
|
|
ruleKey = "highestUserWastedPercent"
|
|
rules = append(rules, newGenericCiRule(
|
|
ruleKey,
|
|
config.GetString(fmt.Sprintf("rules.%s", ruleKey)),
|
|
func(value string) error {
|
|
highestUserWastedPercent, err := strconv.ParseFloat(value, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid config value ('%v'): %v", value, err)
|
|
}
|
|
if highestUserWastedPercent < 0 || highestUserWastedPercent > 1 {
|
|
return fmt.Errorf("highestUserWastedPercent config value is outside allowed range (0-1), given '%s'", value)
|
|
}
|
|
return nil
|
|
},
|
|
func(analysis *image.AnalysisResult, value string) (RuleStatus, string) {
|
|
highestUserWastedPercent, err := strconv.ParseFloat(value, 64)
|
|
if err != nil {
|
|
return RuleFailed, fmt.Sprintf("invalid config value ('%v'): %v", value, err)
|
|
}
|
|
if highestUserWastedPercent < analysis.WastedUserPercent {
|
|
return RuleFailed, fmt.Sprintf("too many bytes wasted, relative to the user bytes added (%%-user-wasted-bytes=%v > threshold=%v)", analysis.WastedUserPercent, highestUserWastedPercent)
|
|
}
|
|
|
|
return RulePassed, ""
|
|
},
|
|
))
|
|
|
|
return rules
|
|
}
|