mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-05-14 05:06:59 +00:00
Merge pull request #4 from reactiveops/jg/add-reporting-support
add support for reporting
This commit is contained in:
53
pkg/report/report.go
Normal file
53
pkg/report/report.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/reactiveops/fairwinds/pkg/types"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
|
||||
)
|
||||
|
||||
var log = logf.Log.WithName("Fairwinds report")
|
||||
|
||||
// Results contains the validation check results.
|
||||
type Results struct {
|
||||
Pass bool
|
||||
FailMsg string
|
||||
Containers []types.ContainerResults
|
||||
InitContainers []types.ContainerResults
|
||||
}
|
||||
|
||||
// Format structures the validation results to return back to k8s API.
|
||||
func (r *Results) Format() (bool, string) {
|
||||
var sb strings.Builder
|
||||
|
||||
for _, container := range r.Containers {
|
||||
if len(container.Failures) == 0 {
|
||||
r.Pass = true
|
||||
}
|
||||
|
||||
r.Pass = false
|
||||
s := fmt.Sprintf("\nContainer: %s\n Failure/s:\n", container.Name)
|
||||
sb.WriteString(s)
|
||||
for _, failure := range container.Failures {
|
||||
sb.WriteString(failure.Reason())
|
||||
}
|
||||
}
|
||||
|
||||
for _, container := range r.InitContainers {
|
||||
if len(container.Failures) == 0 && r.Pass == true {
|
||||
return r.Pass, r.FailMsg
|
||||
}
|
||||
|
||||
r.Pass = false
|
||||
s := fmt.Sprintf("\nInitContainer: %s\n Failure/s:\n", container.Name)
|
||||
sb.WriteString(s)
|
||||
for _, failure := range container.Failures {
|
||||
sb.WriteString(failure.Reason())
|
||||
}
|
||||
}
|
||||
|
||||
r.FailMsg = sb.String()
|
||||
return r.Pass, r.FailMsg
|
||||
}
|
||||
42
pkg/types/types.go
Normal file
42
pkg/types/types.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Failure contains information about the failing validation.
|
||||
type Failure struct {
|
||||
Name string
|
||||
Expected string
|
||||
Actual string
|
||||
}
|
||||
|
||||
// NewFailure is a factory function for a Failure.
|
||||
func NewFailure(name, expected, actual string) *Failure {
|
||||
return &Failure{
|
||||
Name: name,
|
||||
Expected: expected,
|
||||
Actual: actual,
|
||||
}
|
||||
}
|
||||
|
||||
// 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",
|
||||
f.Name,
|
||||
f.Expected,
|
||||
f.Actual,
|
||||
)
|
||||
}
|
||||
|
||||
// ContainerResults has the results of the validation checks for containers.
|
||||
type ContainerResults struct {
|
||||
Name string
|
||||
Failures []Failure
|
||||
}
|
||||
|
||||
// AddFailure creates a new Failure and adds it to ContainerResults.
|
||||
func (c *ContainerResults) AddFailure(name, expected, actual string) {
|
||||
f := NewFailure(name, expected, actual)
|
||||
c.Failures = append(c.Failures, *f)
|
||||
}
|
||||
@@ -15,35 +15,26 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
conf "github.com/reactiveops/fairwinds/pkg/config"
|
||||
"github.com/reactiveops/fairwinds/pkg/types"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
type containerResults struct {
|
||||
Name string
|
||||
Reason string
|
||||
}
|
||||
|
||||
func validateContainer(conf conf.Configuration, container corev1.Container) containerResults {
|
||||
var sb strings.Builder
|
||||
results := containerResults{
|
||||
func validateContainer(conf conf.Configuration, container corev1.Container) types.ContainerResults {
|
||||
results := types.ContainerResults{
|
||||
Name: container.Name,
|
||||
}
|
||||
results = resources(conf.Resources, container, results)
|
||||
results = probes(conf.Resources, container, results)
|
||||
results = tag(conf.Resources, container, results)
|
||||
|
||||
sb.WriteString(resources(conf.Resources, container))
|
||||
sb.WriteString(probes(container))
|
||||
sb.WriteString(tag(container))
|
||||
|
||||
results.Reason = sb.String()
|
||||
return results
|
||||
}
|
||||
|
||||
func resources(conf conf.ResourceRequestsAndLimits, c corev1.Container) string {
|
||||
var sb strings.Builder
|
||||
func resources(conf conf.ResourceRequestsAndLimits, c corev1.Container, results types.ContainerResults) types.ContainerResults {
|
||||
confCPUmin, err := resource.ParseQuantity(conf.Requests["cpu"].Min)
|
||||
if err != nil {
|
||||
log.Error(err, "cpu min parse quan")
|
||||
@@ -53,53 +44,47 @@ func resources(conf conf.ResourceRequestsAndLimits, c corev1.Container) string {
|
||||
// log.Error(err, "cpu max parse quan")
|
||||
// }
|
||||
|
||||
ctrRequests := c.Resources.Requests.Cpu().MilliValue()
|
||||
configMin := confCPUmin.MilliValue()
|
||||
if ctrRequests < configMin {
|
||||
s := fmt.Sprintf("- CPU requests are too low. Expected greater than: %d, Actual: %d.\n", configMin, ctrRequests)
|
||||
sb.WriteString(s)
|
||||
containerRequests := c.Resources.Requests.Cpu()
|
||||
if containerRequests.MilliValue() < confCPUmin.MilliValue() {
|
||||
results.AddFailure("CPU requests", confCPUmin.String(), containerRequests.String())
|
||||
}
|
||||
|
||||
if c.Resources.Requests.Memory().IsZero() {
|
||||
sb.WriteString("- Memory requests are not set.\n")
|
||||
results.AddFailure("Memory requests", "placeholder", "placeholder")
|
||||
}
|
||||
if c.Resources.Limits.Cpu().IsZero() {
|
||||
sb.WriteString("- CPU limits are not set.\n")
|
||||
results.AddFailure("CPU limits", "placeholder", "placeholder")
|
||||
}
|
||||
if c.Resources.Limits.Memory().IsZero() {
|
||||
sb.WriteString("- Memory limits are not set.\n")
|
||||
results.AddFailure("Memory limits", "placeholder", "placeholder")
|
||||
}
|
||||
return sb.String()
|
||||
return results
|
||||
}
|
||||
|
||||
func probes(c corev1.Container) string {
|
||||
var sb strings.Builder
|
||||
func probes(conf conf.ResourceRequestsAndLimits, c corev1.Container, results types.ContainerResults) types.ContainerResults {
|
||||
if c.ReadinessProbe == nil {
|
||||
sb.WriteString("- Readiness Probe is not set.\n")
|
||||
results.AddFailure("Readiness Probe", "placeholder", "placeholder")
|
||||
}
|
||||
|
||||
if c.LivenessProbe == nil {
|
||||
sb.WriteString("- Liveness Probe is not set.\n")
|
||||
results.AddFailure("Liveness Probe", "placeholder", "placeholder")
|
||||
}
|
||||
return sb.String()
|
||||
return results
|
||||
}
|
||||
|
||||
func tag(c corev1.Container) string {
|
||||
var sb strings.Builder
|
||||
func tag(conf conf.ResourceRequestsAndLimits, c corev1.Container, results types.ContainerResults) types.ContainerResults {
|
||||
img := strings.Split(c.Image, ":")
|
||||
if len(img) == 1 || img[1] == "latest" {
|
||||
sb.WriteString("- Image tag is latest.\n")
|
||||
results.AddFailure("Image Tag", "not latest", "latest")
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
return results
|
||||
}
|
||||
|
||||
func hostPort(c corev1.Container) string {
|
||||
var sb strings.Builder
|
||||
func hostPort(conf conf.ResourceRequestsAndLimits, c corev1.Container, results types.ContainerResults) types.ContainerResults {
|
||||
for _, port := range c.Ports {
|
||||
if port.HostPort != 0 {
|
||||
sb.WriteString("- Host Port set.\n")
|
||||
results.AddFailure("Host port", "placeholder", "placeholder")
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
return results
|
||||
}
|
||||
|
||||
@@ -16,11 +16,10 @@ package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
conf "github.com/reactiveops/fairwinds/pkg/config"
|
||||
"github.com/reactiveops/fairwinds/pkg/report"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
|
||||
@@ -50,31 +49,28 @@ func (v *PodValidator) Handle(ctx context.Context, req types.Request) types.Resp
|
||||
return admission.ErrorResponse(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
allowed, reason := validatePods(v.Config, pod)
|
||||
results := validatePods(v.Config, pod, report.Results{})
|
||||
allowed, reason := results.Format()
|
||||
|
||||
return admission.ValidationResponse(allowed, reason)
|
||||
}
|
||||
|
||||
func validatePods(conf conf.Configuration, pod *corev1.Pod) (bool, string) {
|
||||
var sb strings.Builder
|
||||
allowed := true
|
||||
func validatePods(conf conf.Configuration, pod *corev1.Pod, results report.Results) report.Results {
|
||||
for _, container := range pod.Spec.InitContainers {
|
||||
c := validateContainer(conf, container)
|
||||
if c.Reason != "" {
|
||||
sb.WriteString(fmt.Sprintf("\nContainer Name: %s\n%s", c.Name, c.Reason))
|
||||
allowed = false
|
||||
}
|
||||
results.InitContainers = append(
|
||||
results.InitContainers,
|
||||
validateContainer(conf, container),
|
||||
)
|
||||
}
|
||||
|
||||
for _, container := range pod.Spec.Containers {
|
||||
c := validateContainer(conf, container)
|
||||
if c.Reason != "" {
|
||||
sb.WriteString(fmt.Sprintf("\nName: %s\n%s", c.Name, c.Reason))
|
||||
allowed = false
|
||||
}
|
||||
results.Containers = append(
|
||||
results.Containers,
|
||||
validateContainer(conf, container),
|
||||
)
|
||||
}
|
||||
|
||||
return allowed, sb.String()
|
||||
return results
|
||||
}
|
||||
|
||||
// PodValidator implements inject.Client.
|
||||
|
||||
Reference in New Issue
Block a user