Skip to content

Commit

Permalink
Merge pull request #4 from reactiveops/jg/add-reporting-support
Browse files Browse the repository at this point in the history
add support for reporting
  • Loading branch information
JessicaGreben authored Dec 20, 2018
2 parents 0a11f42 + e7c074f commit 8a565d1
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 56 deletions.
53 changes: 53 additions & 0 deletions pkg/report/report.go
Original file line number Diff line number Diff line change
@@ -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 changes: 42 additions & 0 deletions pkg/types/types.go
Original file line number Diff line number Diff line change
@@ -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)
}
63 changes: 24 additions & 39 deletions pkg/validator/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
}
30 changes: 13 additions & 17 deletions pkg/validator/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 8a565d1

Please sign in to comment.