diff --git a/config.yml b/config.yml index 6f1d3f1c..099cc096 100644 --- a/config.yml +++ b/config.yml @@ -1,7 +1,7 @@ resources: requests: cpu: - min: 100m + min: 50m max: 1 memory: min: 100M @@ -13,3 +13,12 @@ resources: memory: min: 150M max: 4G +healthChecks: + readiness: + require: true + liveness: + require: true +images: + tagRequired: true + whitelistRepos: + - gcr.io diff --git a/pkg/config/config.go b/pkg/config/config.go index f8831ec3..66a3bae1 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -11,26 +11,43 @@ import ( "k8s.io/apimachinery/pkg/util/yaml" ) -// ResourceMinMax sets a range for a min and max setting for a resource. -type ResourceMinMax struct { - Min *resource.Quantity - Max *resource.Quantity +// Configuration contains all of the config for the validation checks. +type Configuration struct { + Resources RequestsAndLimits `json:"resources"` + HealthChecks Probes `json:"healthChecks"` + Images Images `json:"images"` +} + +// RequestsAndLimits contains config for resource requests and limits. +type RequestsAndLimits struct { + Requests ResourceList `json:"requests"` + Limits ResourceList `json:"limits"` } // ResourceList maps the resource name to a range on min and max values. type ResourceList map[corev1.ResourceName]ResourceMinMax -// RequestsAndLimits contains config for resource requests and limits. -type RequestsAndLimits struct { - Requests ResourceList - Limits ResourceList +// ResourceMinMax sets a range for a min and max setting for a resource. +type ResourceMinMax struct { + Min *resource.Quantity `json:"min"` + Max *resource.Quantity `json:"max"` } -// Configuration contains all of the config for the validation checks. -type Configuration struct { - Resources RequestsAndLimits - HealthChecks Probes - Images Images +// Probes contains config for the readiness and liveness probes. +type Probes struct { + Readiness ResourceRequire `json:"readiness"` + Liveness ResourceRequire `json:"liveness"` +} + +// ResourceRequire indicates if this resource should be validated. +type ResourceRequire struct { + Require bool `json:"require"` +} + +// Images contains the config for images. +type Images struct { + TagRequired bool `json:"tagRequired"` + WhitelistRepos []string `json:"whitelistRepos"` } // ParseFile parses config from a file. @@ -52,24 +69,7 @@ func Parse(rawBytes []byte) (Configuration, error) { if err == io.EOF { return conf, nil } - return Configuration{}, fmt.Errorf("Decoding config failed: %v", err) + return conf, fmt.Errorf("Decoding config failed: %v", err) } } } - -// Probes contains config for the readiness and liveness probes. -type Probes struct { - Readiness ResourceRequire - Liveness ResourceRequire -} - -// ResourceRequire indicates if this resource should be validated. -type ResourceRequire struct { - Require bool -} - -// Images contains the config for images. -type Images struct { - TagRequired bool - WhitelistRepos []string -} diff --git a/pkg/dashboard/dashboard.go b/pkg/dashboard/dashboard.go index 35c7702b..bd5ea818 100644 --- a/pkg/dashboard/dashboard.go +++ b/pkg/dashboard/dashboard.go @@ -54,9 +54,9 @@ func getDashboardData(c conf.Configuration) (DashboardData, error) { dashboardData := DashboardData{ ClusterSummary: &validator.ResultSummary{ - Successes: 46, - Warnings: 8, - Failures: 5, + Successes: 0, + Warnings: 4, + Failures: 0, }, NamespacedResults: map[string]*validator.NamespacedResult{}, } @@ -67,9 +67,9 @@ func getDashboardData(c conf.Configuration) (DashboardData, error) { Name: deploy.Name, Type: "Deployment", Summary: &validator.ResultSummary{ - Successes: 16, - Warnings: 4, - Failures: 2, + Successes: 0, + Warnings: 2, + Failures: 0, }, } @@ -77,26 +77,21 @@ func getDashboardData(c conf.Configuration) (DashboardData, error) { dashboardData.NamespacedResults[deploy.Namespace] = &validator.NamespacedResult{ Results: []validator.ResourceResult{}, Summary: &validator.ResultSummary{ - Successes: 16, - Warnings: 4, - Failures: 2, + Successes: 0, + Warnings: 3, + Failures: 0, }, } } - for _, containerValidation := range validationFailures.InitContainerValidations { - for _, failure := range containerValidation.Failures { - dashboardData.ClusterSummary.Failures++ - dashboardData.NamespacedResults[deploy.Namespace].Summary.Failures++ - resResult.Summary.Failures++ - resResult.Messages = append(resResult.Messages, validator.ResultMessage{ - Message: failure.Reason(), - Type: "failure", - }) + containerValidations := append(validationFailures.InitContainerValidations, validationFailures.ContainerValidations...) + for _, containerValidation := range containerValidations { + for _, success := range containerValidation.Successes { + dashboardData.ClusterSummary.Successes++ + dashboardData.NamespacedResults[deploy.Namespace].Summary.Successes++ + resResult.Summary.Successes++ + resResult.Messages = append(resResult.Messages, success) } - } - - for _, containerValidation := range validationFailures.ContainerValidations { for _, failure := range containerValidation.Failures { dashboardData.ClusterSummary.Failures++ dashboardData.NamespacedResults[deploy.Namespace].Summary.Failures++ diff --git a/pkg/types/types.go b/pkg/types/types.go index 0d2cab67..e2c23d00 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -13,7 +13,7 @@ type Failure struct { // Reason returns a string that describes the reason for a Failure. func (f *Failure) Reason() string { - return fmt.Sprintf("- %s: Expected: %s, Actual: %s.\n", + return fmt.Sprintf("%s: Expected: %s, Actual: %s.", f.Name, f.Expected, f.Actual, diff --git a/pkg/validator/container.go b/pkg/validator/container.go index 0838cb65..b8acd58e 100644 --- a/pkg/validator/container.go +++ b/pkg/validator/container.go @@ -15,6 +15,7 @@ package validator import ( + "fmt" "strings" conf "github.com/reactiveops/fairwinds/pkg/config" @@ -27,6 +28,7 @@ import ( type ContainerValidation struct { Container corev1.Container Failures []types.Failure + Successes []ResultMessage } func validateContainer(conf conf.Configuration, container corev1.Container) ContainerValidation { @@ -49,6 +51,13 @@ func (cv *ContainerValidation) addFailure(name, expected, actual string) { }) } +func (cv *ContainerValidation) addSuccess(message string) { + cv.Successes = append(cv.Successes, ResultMessage{ + Message: message, + Type: "success", + }) +} + func (cv *ContainerValidation) validateResources(conf conf.RequestsAndLimits) { actualRes := cv.Container.Resources cv.withinRange("requests.cpu", conf.Requests["cpu"], actualRes.Requests.Cpu()) @@ -64,15 +73,26 @@ func (cv *ContainerValidation) withinRange(resourceName string, expectedRange co cv.addFailure(resourceName, expectedMin.String(), actual.String()) } else if expectedMax != nil && expectedMax.MilliValue() < actual.MilliValue() { cv.addFailure(resourceName, expectedMax.String(), actual.String()) + } else { + cv.addSuccess(fmt.Sprintf("Resource %s within expected range", resourceName)) } } func (cv *ContainerValidation) validateHealthChecks(conf conf.Probes) { - if conf.Readiness.Require && cv.Container.ReadinessProbe == nil { - cv.addFailure("readiness", "probe needs to be configured", "nil") + if conf.Readiness.Require { + if cv.Container.ReadinessProbe == nil { + cv.addFailure("readiness", "probe needs to be configured", "nil") + } else { + cv.addSuccess("Readiness probe configured") + } } - if conf.Liveness.Require && cv.Container.LivenessProbe == nil { - cv.addFailure("liveness", "probe needs to be configured", "nil") + + if conf.Liveness.Require { + if cv.Container.LivenessProbe == nil { + cv.addFailure("liveness", "probe needs to be configured", "nil") + } else { + cv.addSuccess("Liveness probe configured") + } } } @@ -81,6 +101,8 @@ func (cv *ContainerValidation) validateImage(conf conf.Images) { img := strings.Split(cv.Container.Image, ":") if len(img) == 1 || img[1] == "latest" { cv.addFailure("Image Tag", "not latest", "latest") + } else { + cv.addSuccess("Image tag specified") } } } diff --git a/pkg/validator/report.go b/pkg/validator/report.go index 0b61d7c9..8ffe502c 100644 --- a/pkg/validator/report.go +++ b/pkg/validator/report.go @@ -42,7 +42,7 @@ func validContainer(sb *strings.Builder, cv *ContainerValidation) bool { s := fmt.Sprintf("\nContainer: %s\n Failure/s:\n", cv.Container.Name) sb.WriteString(s) for _, failure := range cv.Failures { - sb.WriteString(failure.Reason()) + sb.WriteString(fmt.Sprintf("- %s\n", failure.Reason())) } return false } diff --git a/pkg/validator/types.go b/pkg/validator/types.go index 2686619b..deb12f2c 100644 --- a/pkg/validator/types.go +++ b/pkg/validator/types.go @@ -1,22 +1,5 @@ package validator -// namespaceResults: [{ -// name: "kube-system", -// resourceResults: [{ -// name: "example-deployment", -// type: "DaemonSet", -// summary: { -// successes: 28, -// warnings: 12, -// failures: 18, -// }, -// messages: [{ -// message: "Resource requests are not set", -// type: "failure", -// }] -// }] -// }] - type NamespacedResult struct { Summary *ResultSummary Results []ResourceResult