mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-05-21 16:43:57 +00:00
implement custom checks, implement resource ranges as custom check
This commit is contained in:
29
checks/cpuLimitsMissing.yaml
Normal file
29
checks/cpuLimitsMissing.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
name: CPULimitsMissing
|
||||
id: cpuLimitsMissing
|
||||
successMessage: CPU limits are set
|
||||
failureMessage: CPU limits should be set
|
||||
category: Resources
|
||||
target: Container
|
||||
containers:
|
||||
exclude:
|
||||
- initContainer
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
properties:
|
||||
resources:
|
||||
type: object
|
||||
required:
|
||||
- limits
|
||||
properties:
|
||||
limits:
|
||||
type: object
|
||||
required:
|
||||
- cpu
|
||||
properties:
|
||||
cpu:
|
||||
type: string
|
||||
not:
|
||||
const: ''
|
||||
29
checks/cpuRequestsMissing.yaml
Normal file
29
checks/cpuRequestsMissing.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
name: CPURequestsMissing
|
||||
id: cpuRequestsMissing
|
||||
successMessage: CPU requests are set
|
||||
failureMessage: CPU requests should be set
|
||||
category: Resources
|
||||
target: Container
|
||||
containers:
|
||||
exclude:
|
||||
- initContainer
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
properties:
|
||||
resources:
|
||||
type: object
|
||||
required:
|
||||
- requests
|
||||
properties:
|
||||
requests:
|
||||
type: object
|
||||
required:
|
||||
- cpu
|
||||
properties:
|
||||
cpu:
|
||||
type: string
|
||||
not:
|
||||
const: ''
|
||||
29
checks/memoryLimitsMissing.yaml
Normal file
29
checks/memoryLimitsMissing.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
name: MemoryLimitsMissing
|
||||
id: memoryLimitsMissing
|
||||
successMessage: Memory limits are set
|
||||
failureMessage: Memory limits should be set
|
||||
category: Resources
|
||||
target: Container
|
||||
containers:
|
||||
exclude:
|
||||
- initContainer
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
properties:
|
||||
resources:
|
||||
type: object
|
||||
required:
|
||||
- limits
|
||||
properties:
|
||||
limits:
|
||||
type: object
|
||||
required:
|
||||
- memory
|
||||
properties:
|
||||
memory:
|
||||
type: string
|
||||
not:
|
||||
const: ''
|
||||
29
checks/memoryRequestsMissing.yaml
Normal file
29
checks/memoryRequestsMissing.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
name: MemoryRequestsMissing
|
||||
id: memoryRequestsMissing
|
||||
successMessage: Memory requests are set
|
||||
failureMessage: Memory requests should be set
|
||||
category: Resources
|
||||
target: Container
|
||||
containers:
|
||||
exclude:
|
||||
- initContainer
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
properties:
|
||||
resources:
|
||||
type: object
|
||||
required:
|
||||
- requests
|
||||
properties:
|
||||
requests:
|
||||
type: object
|
||||
required:
|
||||
- memory
|
||||
properties:
|
||||
memory:
|
||||
type: string
|
||||
not:
|
||||
const: ''
|
||||
@@ -1,45 +1,11 @@
|
||||
resources:
|
||||
cpuRequestsMissing: warning
|
||||
cpuRequestRanges:
|
||||
warning:
|
||||
below: 50m
|
||||
above: 1000m
|
||||
error:
|
||||
below: 500m
|
||||
above: 2000m
|
||||
cpuLimitsMissing: warning
|
||||
cpuLimitRanges:
|
||||
warning:
|
||||
below: 50m
|
||||
above: 1000m
|
||||
error:
|
||||
below: 500m
|
||||
above: 2000m
|
||||
memoryRequestsMissing: warning
|
||||
memoryRequestRanges:
|
||||
warning:
|
||||
below: 50M
|
||||
above: 2G
|
||||
error:
|
||||
below: 100M
|
||||
above: 4G
|
||||
memoryLimitsMissing: warning
|
||||
memoryLimitRanges:
|
||||
warning:
|
||||
below: 50M
|
||||
above: 2G
|
||||
error:
|
||||
below: 100M
|
||||
above: 4G
|
||||
images:
|
||||
tagNotSpecified: error
|
||||
pullPolicyNotAlways: warning
|
||||
whitelist:
|
||||
error:
|
||||
- gcr.io/*
|
||||
blacklist:
|
||||
warning:
|
||||
- docker.io/*
|
||||
healthChecks:
|
||||
readinessProbeMissing: warning
|
||||
livenessProbeMissing: warning
|
||||
@@ -53,16 +19,8 @@ security:
|
||||
runAsPrivileged: error
|
||||
notReadOnlyRootFileSystem: warning
|
||||
privilegeEscalationAllowed: error
|
||||
capabilities:
|
||||
error:
|
||||
ifAnyAdded:
|
||||
- SYS_ADMIN
|
||||
- ALL
|
||||
ifAnyNotDropped:
|
||||
- ALL
|
||||
warning:
|
||||
ifAnyAddedBeyond:
|
||||
- NONE
|
||||
dangerousCapabilities: error
|
||||
insecureCapabilities: warning
|
||||
controllers_to_scan:
|
||||
- Deployments
|
||||
- StatefulSets
|
||||
|
||||
@@ -23,21 +23,22 @@ import (
|
||||
"strings"
|
||||
|
||||
packr "github.com/gobuffalo/packr/v2"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
)
|
||||
|
||||
// Configuration contains all of the config for the validation checks.
|
||||
type Configuration struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
Resources Resources `json:"resources"`
|
||||
HealthChecks HealthChecks `json:"healthChecks"`
|
||||
Images Images `json:"images"`
|
||||
Networking Networking `json:"networking"`
|
||||
Security Security `json:"security"`
|
||||
ControllersToScan []SupportedController `json:"controllers_to_scan"`
|
||||
Exemptions []Exemption `json:"exemptions"`
|
||||
DisallowExemptions bool `json:"disallowExemptions"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Resources Resources `json:"resources"`
|
||||
HealthChecks HealthChecks `json:"healthChecks"`
|
||||
Images Images `json:"images"`
|
||||
Networking Networking `json:"networking"`
|
||||
Security Security `json:"security"`
|
||||
Checks map[string]Severity `json:"checks"`
|
||||
ControllersToScan []SupportedController `json:"controllers_to_scan"`
|
||||
CustomChecks map[string]SchemaCheck `json:"customChecks"`
|
||||
Exemptions []Exemption `json:"exemptions"`
|
||||
DisallowExemptions bool `json:"disallowExemptions"`
|
||||
}
|
||||
|
||||
// Exemption represents an exemption to normal rules
|
||||
@@ -48,26 +49,10 @@ type Exemption struct {
|
||||
|
||||
// Resources contains config for resource requests and limits.
|
||||
type Resources struct {
|
||||
CPURequestsMissing Severity `json:"cpuRequestsMissing"`
|
||||
CPURequestRanges ResourceRanges `json:"cpuRequestRanges"`
|
||||
CPULimitsMissing Severity `json:"cpuLimitsMissing"`
|
||||
CPULimitRanges ResourceRanges `json:"cpuLimitRanges"`
|
||||
MemoryRequestsMissing Severity `json:"memoryRequestsMissing"`
|
||||
MemoryRequestRanges ResourceRanges `json:"memoryRequestRanges"`
|
||||
MemoryLimitsMissing Severity `json:"memoryLimitsMissing"`
|
||||
MemoryLimitRanges ResourceRanges `json:"memoryLimitRanges"`
|
||||
}
|
||||
|
||||
// ResourceRanges contains config for requests or limits for a specific resource.
|
||||
type ResourceRanges struct {
|
||||
Warning ResourceRange `json:"warning"`
|
||||
Error ResourceRange `json:"error"`
|
||||
}
|
||||
|
||||
// ResourceRange can contain below and above conditions for validation.
|
||||
type ResourceRange struct {
|
||||
Below *resource.Quantity `json:"below"`
|
||||
Above *resource.Quantity `json:"above"`
|
||||
CPURequestsMissing Severity `json:"cpuRequestsMissing"`
|
||||
CPULimitsMissing Severity `json:"cpuLimitsMissing"`
|
||||
MemoryRequestsMissing Severity `json:"memoryRequestsMissing"`
|
||||
MemoryLimitsMissing Severity `json:"memoryLimitsMissing"`
|
||||
}
|
||||
|
||||
// HealthChecks contains config for readiness and liveness probes.
|
||||
@@ -140,9 +125,14 @@ func Parse(rawBytes []byte) (Configuration, error) {
|
||||
for {
|
||||
if err := d.Decode(&conf); err != nil {
|
||||
if err == io.EOF {
|
||||
return conf, nil
|
||||
break
|
||||
}
|
||||
return conf, fmt.Errorf("Decoding config failed: %v", err)
|
||||
}
|
||||
}
|
||||
for key, check := range conf.CustomChecks {
|
||||
check.ID = key
|
||||
conf.CustomChecks[key] = check
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
@@ -24,111 +24,41 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
var resourceConfInvalid1 = `test`
|
||||
var confInvalid = `test`
|
||||
|
||||
var resourceConfYAML1 = `---
|
||||
var confValidYAML = `
|
||||
resources:
|
||||
cpuRequestRanges:
|
||||
error:
|
||||
below: 100m
|
||||
above: 1
|
||||
warning:
|
||||
below: 200m
|
||||
above: 800m
|
||||
memoryRequestRanges:
|
||||
error:
|
||||
below: 100M
|
||||
above: 3G
|
||||
warning:
|
||||
below: 200M
|
||||
above: 2G
|
||||
cpuLimitRanges:
|
||||
error:
|
||||
below: 100m
|
||||
above: 2
|
||||
warning:
|
||||
below: 300m
|
||||
above: 1800m
|
||||
memoryLimitRanges:
|
||||
error:
|
||||
below: 200M
|
||||
above: 6G
|
||||
warning:
|
||||
below: 300M
|
||||
above: 4G
|
||||
cpuRequestsMissing: warning
|
||||
controllers_to_scan:
|
||||
- Deployments
|
||||
- StatefulSets
|
||||
- Jobs
|
||||
- CronJobs
|
||||
- DaemonSets
|
||||
- ReplicationControllers
|
||||
`
|
||||
|
||||
var resourceConfJSON1 = `{
|
||||
"resources": {
|
||||
"cpuRequestRanges": {
|
||||
"error": {
|
||||
"below": "100m",
|
||||
"above": 1
|
||||
},
|
||||
"warning": {
|
||||
"below": "200m",
|
||||
"above": "800m"
|
||||
}
|
||||
},
|
||||
"memoryRequestRanges": {
|
||||
"error": {
|
||||
"below": "100M",
|
||||
"above": "3G"
|
||||
},
|
||||
"warning": {
|
||||
"below": "200M",
|
||||
"above": "2G"
|
||||
}
|
||||
},
|
||||
"cpuLimitRanges": {
|
||||
"error": {
|
||||
"below": "100m",
|
||||
"above": 2
|
||||
},
|
||||
"warning": {
|
||||
"below": "300m",
|
||||
"above": "1800m"
|
||||
}
|
||||
},
|
||||
"memoryLimitRanges": {
|
||||
"error": {
|
||||
"below": "200M",
|
||||
"above": "6G"
|
||||
},
|
||||
"warning": {
|
||||
"below": "300M",
|
||||
"above": "4G"
|
||||
}
|
||||
}
|
||||
},
|
||||
"controllers_to_scan": ["Deployments", "StatefulSets", "Jobs", "CronJobs", "DaemonSets", "ReplicationControllers"]
|
||||
}`
|
||||
var confValidJSON = `
|
||||
{
|
||||
"resources": {
|
||||
"cpuRequestsMissing": "warning"
|
||||
},
|
||||
"controllers_to_scan": ["Deployments"]
|
||||
}
|
||||
`
|
||||
|
||||
func TestParseError(t *testing.T) {
|
||||
_, err := Parse([]byte(resourceConfInvalid1))
|
||||
_, err := Parse([]byte(confInvalid))
|
||||
expectedErr := "Decoding config failed: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type config.Configuration"
|
||||
assert.EqualError(t, err, expectedErr)
|
||||
}
|
||||
|
||||
func TestParseYaml(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(resourceConfYAML1))
|
||||
parsedConf, err := Parse([]byte(confValidYAML))
|
||||
assert.NoError(t, err, "Expected no error when parsing YAML config")
|
||||
|
||||
testParsedConfig(t, &parsedConf)
|
||||
}
|
||||
|
||||
func TestParseJson(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(resourceConfJSON1))
|
||||
parsedConf, err := Parse([]byte(confValidJSON))
|
||||
assert.NoError(t, err, "Expected no error when parsing JSON config")
|
||||
|
||||
testParsedConfig(t, &parsedConf)
|
||||
@@ -139,7 +69,7 @@ func TestConfigFromURL(t *testing.T) {
|
||||
var parsedConf Configuration
|
||||
srv := &http.Server{Addr: ":8081"}
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
io.WriteString(w, resourceConfYAML1)
|
||||
io.WriteString(w, confValidYAML)
|
||||
})
|
||||
|
||||
go func() {
|
||||
@@ -166,30 +96,7 @@ func TestConfigNoServerError(t *testing.T) {
|
||||
}
|
||||
|
||||
func testParsedConfig(t *testing.T, config *Configuration) {
|
||||
cpuRequests := config.Resources.CPURequestRanges
|
||||
assert.Equal(t, int64(100), cpuRequests.Error.Below.ScaledValue(resource.Milli))
|
||||
assert.Equal(t, int64(1000), cpuRequests.Error.Above.ScaledValue(resource.Milli))
|
||||
assert.Equal(t, int64(200), cpuRequests.Warning.Below.ScaledValue(resource.Milli))
|
||||
assert.Equal(t, int64(800), cpuRequests.Warning.Above.ScaledValue(resource.Milli))
|
||||
|
||||
memRequests := config.Resources.MemoryRequestRanges
|
||||
assert.Equal(t, int64(100), memRequests.Error.Below.ScaledValue(resource.Mega))
|
||||
assert.Equal(t, int64(3000), memRequests.Error.Above.ScaledValue(resource.Mega))
|
||||
assert.Equal(t, int64(200), memRequests.Warning.Below.ScaledValue(resource.Mega))
|
||||
assert.Equal(t, int64(2000), memRequests.Warning.Above.ScaledValue(resource.Mega))
|
||||
|
||||
cpuLimits := config.Resources.CPULimitRanges
|
||||
assert.Equal(t, int64(100), cpuLimits.Error.Below.ScaledValue(resource.Milli))
|
||||
assert.Equal(t, int64(2000), cpuLimits.Error.Above.ScaledValue(resource.Milli))
|
||||
assert.Equal(t, int64(300), cpuLimits.Warning.Below.ScaledValue(resource.Milli))
|
||||
assert.Equal(t, int64(1800), cpuLimits.Warning.Above.ScaledValue(resource.Milli))
|
||||
|
||||
memLimits := config.Resources.MemoryLimitRanges
|
||||
assert.Equal(t, int64(200), memLimits.Error.Below.ScaledValue(resource.Mega))
|
||||
assert.Equal(t, int64(6000), memLimits.Error.Above.ScaledValue(resource.Mega))
|
||||
assert.Equal(t, int64(300), memLimits.Warning.Below.ScaledValue(resource.Mega))
|
||||
assert.Equal(t, int64(4000), memLimits.Warning.Above.ScaledValue(resource.Mega))
|
||||
|
||||
controllersToScan := config.ControllersToScan
|
||||
assert.ElementsMatch(t, []SupportedController{Deployments, StatefulSets, Jobs, CronJobs, DaemonSets, ReplicationControllers}, controllersToScan)
|
||||
assert.Equal(t, SeverityWarning, config.Resources.CPURequestsMissing)
|
||||
assert.Equal(t, Severity(""), config.Resources.CPULimitsMissing)
|
||||
assert.ElementsMatch(t, []SupportedController{Deployments}, config.ControllersToScan)
|
||||
}
|
||||
|
||||
@@ -16,12 +16,6 @@ func (conf Configuration) IsActionable(subConf interface{}, ruleName, controller
|
||||
if severity, ok := fieldVal.(Severity); ok && !severity.IsActionable() {
|
||||
return false
|
||||
}
|
||||
if ranges, ok := fieldVal.(ResourceRanges); ok {
|
||||
if ranges.Warning.Above == nil && ranges.Warning.Below == nil &&
|
||||
ranges.Error.Above == nil && ranges.Error.Below == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if conf.DisallowExemptions {
|
||||
return true
|
||||
}
|
||||
|
||||
164
pkg/config/schema.go
Normal file
164
pkg/config/schema.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/qri-io/jsonschema"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
type resourceMinimum string
|
||||
type resourceMaximum string
|
||||
|
||||
func newResourceMinimum() jsonschema.Validator {
|
||||
return new(resourceMinimum)
|
||||
}
|
||||
|
||||
func newResourceMaximum() jsonschema.Validator {
|
||||
return new(resourceMaximum)
|
||||
}
|
||||
|
||||
func (min resourceMinimum) Validate(path string, data interface{}, errs *[]jsonschema.ValError) {
|
||||
err := validateRange(path, string(min), data, true)
|
||||
if err != nil {
|
||||
*errs = append(*errs, *err...)
|
||||
}
|
||||
}
|
||||
|
||||
func (max resourceMaximum) Validate(path string, data interface{}, errs *[]jsonschema.ValError) {
|
||||
err := validateRange(path, string(max), data, false)
|
||||
if err != nil {
|
||||
*errs = append(*errs, *err...)
|
||||
}
|
||||
}
|
||||
|
||||
func parseQuantity(i interface{}) (resource.Quantity, *[]jsonschema.ValError) {
|
||||
resStr, ok := i.(string)
|
||||
if !ok {
|
||||
return resource.Quantity{}, &[]jsonschema.ValError{
|
||||
{Message: fmt.Sprintf("Resource quantity %v is not a string", i)},
|
||||
}
|
||||
}
|
||||
q, err := resource.ParseQuantity(resStr)
|
||||
if err != nil {
|
||||
return resource.Quantity{}, &[]jsonschema.ValError{
|
||||
{Message: fmt.Sprintf("Could not parse resource quantity: %s", resStr)},
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func validateRange(path string, limit interface{}, data interface{}, isMinimum bool) *[]jsonschema.ValError {
|
||||
limitQuantity, err := parseQuantity(limit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
actualQuantity, err := parseQuantity(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmp := limitQuantity.Cmp(actualQuantity)
|
||||
if isMinimum {
|
||||
if cmp == 1 {
|
||||
return &[]jsonschema.ValError{
|
||||
{Message: fmt.Sprintf("%s quantity %v is > %v", path, actualQuantity, limitQuantity)},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if cmp == -1 {
|
||||
return &[]jsonschema.ValError{
|
||||
{Message: fmt.Sprintf("%s quantity %v is < %v", path, actualQuantity, limitQuantity)},
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
jsonschema.RegisterValidator("resourceMinimum", newResourceMinimum)
|
||||
jsonschema.RegisterValidator("resourceMaximum", newResourceMaximum)
|
||||
}
|
||||
|
||||
type includeExcludeList struct {
|
||||
Include []string `yaml:"include"`
|
||||
Exclude []string `yaml:"exclude"`
|
||||
}
|
||||
|
||||
type TargetKind string
|
||||
|
||||
const (
|
||||
TargetContainer TargetKind = "Container"
|
||||
TargetPod TargetKind = "Pod"
|
||||
)
|
||||
|
||||
// SchemaCheck is a Polaris check that runs using JSON Schema
|
||||
type SchemaCheck struct {
|
||||
Name string `yaml:"name"`
|
||||
ID string `yaml:"id"`
|
||||
Category string `yaml:"category"`
|
||||
SuccessMessage string `yaml:"successMessage"`
|
||||
FailureMessage string `yaml:"failureMessage"`
|
||||
Controllers includeExcludeList `yaml:"controllers"`
|
||||
Containers includeExcludeList `yaml:"containers"`
|
||||
Target TargetKind `yaml:"target"`
|
||||
SchemaTarget TargetKind `yaml:"schemaTarget"`
|
||||
Schema jsonschema.RootSchema `yaml:"schema"`
|
||||
}
|
||||
|
||||
func (check SchemaCheck) CheckPod(pod *corev1.PodSpec) (bool, error) {
|
||||
return check.CheckObject(pod)
|
||||
}
|
||||
|
||||
func (check SchemaCheck) CheckContainer(container *corev1.Container) (bool, error) {
|
||||
return check.CheckObject(container)
|
||||
}
|
||||
|
||||
func (check SchemaCheck) CheckObject(obj interface{}) (bool, error) {
|
||||
bytes, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
errs, err := check.Schema.ValidateBytes(bytes)
|
||||
return len(errs) == 0, err
|
||||
}
|
||||
|
||||
func (check SchemaCheck) IsActionable(target TargetKind, controllerType SupportedController, isInit bool) bool {
|
||||
if check.Target != target {
|
||||
return false
|
||||
}
|
||||
isIncluded := len(check.Controllers.Include) == 0
|
||||
for _, inclusion := range check.Controllers.Include {
|
||||
if GetSupportedControllerFromString(inclusion) == controllerType {
|
||||
isIncluded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isIncluded {
|
||||
return false
|
||||
}
|
||||
for _, exclusion := range check.Controllers.Exclude {
|
||||
if GetSupportedControllerFromString(exclusion) == controllerType {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if check.Target == TargetContainer {
|
||||
isIncluded := len(check.Containers.Include) == 0
|
||||
for _, inclusion := range check.Containers.Include {
|
||||
if (inclusion == "initContainer" && isInit) || (inclusion == "container" && !isInit) {
|
||||
isIncluded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isIncluded {
|
||||
return false
|
||||
}
|
||||
for _, exclusion := range check.Containers.Exclude {
|
||||
if (exclusion == "initContainer" && isInit) || (exclusion == "container" && !isInit) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -15,12 +15,8 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/validator/messages"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
// ContainerValidation tracks validation failures associated with a Container.
|
||||
@@ -56,8 +52,6 @@ func ValidateContainer(container *corev1.Container, parentPodResult *PodResult,
|
||||
cv.parentPodSpec = parentPodResult.podSpec
|
||||
}
|
||||
|
||||
cv.validateResources(conf, controllerName)
|
||||
|
||||
err := applyContainerSchemaChecks(conf, controllerName, controllerType, &cv)
|
||||
// FIXME: don't panic
|
||||
if err != nil {
|
||||
@@ -72,82 +66,3 @@ func ValidateContainer(container *corev1.Container, parentPodResult *PodResult,
|
||||
|
||||
return cRes
|
||||
}
|
||||
|
||||
func (cv *ContainerValidation) validateResources(conf *config.Configuration, controllerName string) {
|
||||
// Only validate resources for primary containers. Although it can
|
||||
// be helpful to set these in certain cases, it usually isn't
|
||||
if cv.IsInitContainer {
|
||||
return
|
||||
}
|
||||
|
||||
category := messages.CategoryResources
|
||||
res := cv.Container.Resources
|
||||
|
||||
missingName := "CPURequestsMissing"
|
||||
rangeName := "CPURequestRanges"
|
||||
id := config.GetIDFromField(conf.Resources, missingName)
|
||||
if conf.IsActionable(conf.Resources, missingName, controllerName) && res.Requests.Cpu().MilliValue() == 0 {
|
||||
cv.addFailure(messages.CPURequestsFailure, conf.Resources.CPURequestsMissing, category, id)
|
||||
} else if conf.IsActionable(conf.Resources, rangeName, controllerName) {
|
||||
id := config.GetIDFromField(conf.Resources, rangeName)
|
||||
cv.validateResourceRange(id, messages.CPURequestsLabel, &conf.Resources.CPURequestRanges, res.Requests.Cpu())
|
||||
} else if conf.IsActionable(conf.Resources, missingName, controllerName) {
|
||||
cv.addSuccess(fmt.Sprintf(messages.ResourcePresentSuccess, messages.CPURequestsLabel), category, id)
|
||||
}
|
||||
|
||||
missingName = "CPULimitsMissing"
|
||||
rangeName = "CPULimitRanges"
|
||||
id = config.GetIDFromField(conf.Resources, missingName)
|
||||
if conf.IsActionable(conf.Resources, missingName, controllerName) && res.Limits.Cpu().MilliValue() == 0 {
|
||||
cv.addFailure(messages.CPULimitsFailure, conf.Resources.CPULimitsMissing, category, id)
|
||||
} else if conf.IsActionable(conf.Resources, rangeName, controllerName) {
|
||||
id := config.GetIDFromField(conf.Resources, rangeName)
|
||||
cv.validateResourceRange(id, messages.CPULimitsLabel, &conf.Resources.CPULimitRanges, res.Requests.Cpu())
|
||||
} else if conf.IsActionable(conf.Resources, missingName, controllerName) {
|
||||
cv.addSuccess(fmt.Sprintf(messages.ResourcePresentSuccess, messages.CPULimitsLabel), category, id)
|
||||
}
|
||||
|
||||
missingName = "MemoryRequestsMissing"
|
||||
rangeName = "MemoryRequestRanges"
|
||||
id = config.GetIDFromField(conf.Resources, missingName)
|
||||
if conf.IsActionable(conf.Resources, missingName, controllerName) && res.Requests.Memory().MilliValue() == 0 {
|
||||
cv.addFailure(messages.MemoryRequestsFailure, conf.Resources.MemoryRequestsMissing, category, id)
|
||||
} else if conf.IsActionable(conf.Resources, rangeName, controllerName) {
|
||||
id := config.GetIDFromField(conf.Resources, rangeName)
|
||||
cv.validateResourceRange(id, messages.MemoryRequestsLabel, &conf.Resources.MemoryRequestRanges, res.Requests.Memory())
|
||||
} else if conf.IsActionable(conf.Resources, missingName, controllerName) {
|
||||
cv.addSuccess(fmt.Sprintf(messages.ResourcePresentSuccess, messages.MemoryRequestsLabel), category, id)
|
||||
}
|
||||
|
||||
missingName = "MemoryLimitsMissing"
|
||||
rangeName = "MemoryLimitRanges"
|
||||
id = config.GetIDFromField(conf.Resources, missingName)
|
||||
if conf.IsActionable(conf.Resources, missingName, controllerName) && res.Limits.Memory().MilliValue() == 0 {
|
||||
cv.addFailure(messages.MemoryLimitsFailure, conf.Resources.MemoryLimitsMissing, category, id)
|
||||
} else if conf.IsActionable(conf.Resources, rangeName, controllerName) {
|
||||
id := config.GetIDFromField(conf.Resources, rangeName)
|
||||
cv.validateResourceRange(id, messages.MemoryLimitsLabel, &conf.Resources.MemoryLimitRanges, res.Limits.Memory())
|
||||
} else if conf.IsActionable(conf.Resources, missingName, controllerName) {
|
||||
cv.addSuccess(fmt.Sprintf(messages.ResourcePresentSuccess, messages.MemoryLimitsLabel), category, id)
|
||||
}
|
||||
}
|
||||
|
||||
func (cv *ContainerValidation) validateResourceRange(id, resourceName string, rangeConf *config.ResourceRanges, res *resource.Quantity) {
|
||||
warnAbove := rangeConf.Warning.Above
|
||||
warnBelow := rangeConf.Warning.Below
|
||||
errorAbove := rangeConf.Error.Above
|
||||
errorBelow := rangeConf.Error.Below
|
||||
category := messages.CategoryResources
|
||||
|
||||
if errorAbove != nil && errorAbove.MilliValue() < res.MilliValue() {
|
||||
cv.addError(fmt.Sprintf(messages.ResourceAmountTooHighFailure, resourceName, errorAbove.String()), category, id)
|
||||
} else if warnAbove != nil && warnAbove.MilliValue() < res.MilliValue() {
|
||||
cv.addWarning(fmt.Sprintf(messages.ResourceAmountTooHighFailure, resourceName, warnAbove.String()), category, id)
|
||||
} else if errorBelow != nil && errorBelow.MilliValue() > res.MilliValue() {
|
||||
cv.addError(fmt.Sprintf(messages.ResourceAmountTooLowFailure, resourceName, errorBelow.String()), category, id)
|
||||
} else if warnBelow != nil && warnBelow.MilliValue() > res.MilliValue() {
|
||||
cv.addWarning(fmt.Sprintf(messages.ResourceAmountTooLowFailure, resourceName, warnBelow.String()), category, id)
|
||||
} else if errorAbove != nil && warnAbove != nil && errorBelow != nil && warnBelow != nil {
|
||||
cv.addSuccess(fmt.Sprintf(messages.ResourceAmountSuccess, resourceName), category, id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,39 +24,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
var resourceConf1 = `---
|
||||
resources:
|
||||
cpuRequestRanges:
|
||||
error:
|
||||
below: 100m
|
||||
above: 1
|
||||
warning:
|
||||
below: 200m
|
||||
above: 800m
|
||||
memoryRequestRanges:
|
||||
error:
|
||||
below: 100M
|
||||
above: 3G
|
||||
warning:
|
||||
below: 200M
|
||||
above: 2G
|
||||
cpuLimitRanges:
|
||||
error:
|
||||
below: 100m
|
||||
above: 2
|
||||
warning:
|
||||
below: 300m
|
||||
above: 1800m
|
||||
memoryLimitRanges:
|
||||
error:
|
||||
below: 200M
|
||||
above: 6G
|
||||
warning:
|
||||
below: 300M
|
||||
above: 4G
|
||||
`
|
||||
|
||||
var resourceConf2 = `---
|
||||
var resourceConfMinimal = `---
|
||||
resources:
|
||||
cpuRequestsMissing: warning
|
||||
memoryRequestsMissing: warning
|
||||
@@ -80,44 +48,66 @@ exemptions:
|
||||
- foo
|
||||
`
|
||||
|
||||
var resourceConfRangeExemptions = `---
|
||||
resources:
|
||||
cpuRequestRanges:
|
||||
error:
|
||||
below: 100m
|
||||
above: 1
|
||||
warning:
|
||||
below: 200m
|
||||
above: 800m
|
||||
memoryRequestRanges:
|
||||
error:
|
||||
below: 100M
|
||||
above: 3G
|
||||
warning:
|
||||
below: 200M
|
||||
above: 2G
|
||||
cpuLimitRanges:
|
||||
error:
|
||||
below: 100m
|
||||
above: 2
|
||||
warning:
|
||||
below: 300m
|
||||
above: 1800m
|
||||
memoryLimitRanges:
|
||||
error:
|
||||
below: 200M
|
||||
above: 6G
|
||||
warning:
|
||||
below: 300M
|
||||
above: 4G
|
||||
exemptions:
|
||||
- rules:
|
||||
- cpuRequestRanges
|
||||
- memoryRequestRanges
|
||||
- cpuLimitRanges
|
||||
- memoryLimitRanges
|
||||
controllerNames:
|
||||
- foo
|
||||
var resourceConfRanges = `
|
||||
checks:
|
||||
memoryRequestsRange: error
|
||||
memoryLimitsRange: warning
|
||||
customChecks:
|
||||
memoryLimitsRange:
|
||||
containers:
|
||||
exclude:
|
||||
- initContainer
|
||||
successMessage: Memory limits are within the required range
|
||||
failureMessage: Memory limits should be within the required range
|
||||
category: Resources
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
properties:
|
||||
resources:
|
||||
type: object
|
||||
required:
|
||||
- limits
|
||||
properties:
|
||||
limits:
|
||||
type: object
|
||||
required:
|
||||
- memory
|
||||
properties:
|
||||
memory:
|
||||
type: string
|
||||
resourceMinimum: 200M
|
||||
resourceMaximum: 6G
|
||||
memoryRequestsRange:
|
||||
successMessage: Memory requests are within the required range
|
||||
failureMessage: Memory requests should be within the required range
|
||||
category: Resources
|
||||
target: Container
|
||||
containers:
|
||||
exclude:
|
||||
- initContainer
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
properties:
|
||||
resources:
|
||||
type: object
|
||||
required:
|
||||
- requests
|
||||
properties:
|
||||
requests:
|
||||
required:
|
||||
- memory
|
||||
properties:
|
||||
memory:
|
||||
type: string
|
||||
resourceMinimum: 200M
|
||||
resourceMaximum: 3G
|
||||
`
|
||||
|
||||
func testValidateResources(t *testing.T, container *corev1.Container, resourceConf *string, controllerName string, expectedErrors []*ResultMessage, expectedWarnings []*ResultMessage, expectedSuccesses []*ResultMessage) {
|
||||
@@ -129,7 +119,10 @@ func testValidateResources(t *testing.T, container *corev1.Container, resourceCo
|
||||
parsedConf, err := conf.Parse([]byte(*resourceConf))
|
||||
assert.NoError(t, err, "Expected no error when parsing config")
|
||||
|
||||
cv.validateResources(&parsedConf, controllerName)
|
||||
err = applyContainerSchemaChecks(&parsedConf, controllerName, conf.Deployments, &cv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
assert.Len(t, cv.Warnings, len(expectedWarnings))
|
||||
assert.ElementsMatch(t, expectedWarnings, cv.Warnings)
|
||||
@@ -151,7 +144,10 @@ func TestValidateResourcesEmptyConfig(t *testing.T) {
|
||||
ResourceValidation: &ResourceValidation{},
|
||||
}
|
||||
|
||||
cv.validateResources(&conf.Configuration{}, "")
|
||||
err := applyContainerSchemaChecks(&conf.Configuration{}, "", conf.Deployments, &cv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assert.Len(t, cv.Errors, 0)
|
||||
}
|
||||
|
||||
@@ -192,7 +188,7 @@ func TestValidateResourcesEmptyContainer(t *testing.T) {
|
||||
|
||||
expectedSuccesses := []*ResultMessage{}
|
||||
|
||||
testValidateResources(t, &container, &resourceConf2, "foo", expectedErrors, expectedWarnings, expectedSuccesses)
|
||||
testValidateResources(t, &container, &resourceConfMinimal, "foo", expectedErrors, expectedWarnings, expectedSuccesses)
|
||||
}
|
||||
|
||||
func TestValidateResourcesPartiallyValid(t *testing.T) {
|
||||
@@ -216,37 +212,25 @@ func TestValidateResourcesPartiallyValid(t *testing.T) {
|
||||
|
||||
expectedWarnings := []*ResultMessage{
|
||||
{
|
||||
ID: "cpuRequestRanges",
|
||||
ID: "memoryLimitsRange",
|
||||
Type: "warning",
|
||||
Message: "CPU requests should be higher than 200m",
|
||||
Category: "Resources",
|
||||
},
|
||||
{
|
||||
ID: "cpuLimitRanges",
|
||||
Type: "warning",
|
||||
Message: "CPU limits should be higher than 300m",
|
||||
Message: "Memory limits should be within the required range",
|
||||
Category: "Resources",
|
||||
},
|
||||
}
|
||||
|
||||
expectedErrors := []*ResultMessage{
|
||||
{
|
||||
ID: "memoryRequestRanges",
|
||||
ID: "memoryRequestsRange",
|
||||
Type: "error",
|
||||
Message: "Memory requests should be higher than 100M",
|
||||
Category: "Resources",
|
||||
},
|
||||
{
|
||||
ID: "memoryLimitRanges",
|
||||
Type: "error",
|
||||
Message: "Memory limits should be higher than 200M",
|
||||
Message: "Memory requests should be within the required range",
|
||||
Category: "Resources",
|
||||
},
|
||||
}
|
||||
|
||||
expectedSuccesses := []*ResultMessage{}
|
||||
|
||||
testValidateResources(t, &container, &resourceConf1, "foo", expectedErrors, expectedWarnings, expectedSuccesses)
|
||||
testValidateResources(t, &container, &resourceConfRanges, "foo", expectedErrors, expectedWarnings, expectedSuccesses)
|
||||
}
|
||||
|
||||
func TestValidateResourcesInit(t *testing.T) {
|
||||
@@ -260,13 +244,20 @@ func TestValidateResourcesInit(t *testing.T) {
|
||||
IsInitContainer: true,
|
||||
}
|
||||
|
||||
parsedConf, err := conf.Parse([]byte(resourceConf1))
|
||||
parsedConf, err := conf.Parse([]byte(resourceConfRanges))
|
||||
assert.NoError(t, err, "Expected no error when parsing config")
|
||||
|
||||
cvEmpty.validateResources(&parsedConf, "")
|
||||
assert.Len(t, cvEmpty.Errors, 4)
|
||||
err = applyContainerSchemaChecks(&parsedConf, "", conf.Deployments, &cvEmpty)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assert.Len(t, cvEmpty.Errors, 1)
|
||||
assert.Len(t, cvEmpty.Warnings, 1)
|
||||
|
||||
cvInit.validateResources(&parsedConf, "")
|
||||
err = applyContainerSchemaChecks(&parsedConf, "", conf.Deployments, &cvInit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assert.Len(t, cvInit.Errors, 0)
|
||||
}
|
||||
|
||||
@@ -299,32 +290,20 @@ func TestValidateResourcesFullyValid(t *testing.T) {
|
||||
|
||||
expectedSuccesses := []*ResultMessage{
|
||||
{
|
||||
ID: "cpuRequestRanges",
|
||||
ID: "memoryRequestsRange",
|
||||
Type: "success",
|
||||
Message: "CPU requests are within the expected range",
|
||||
Message: "Memory requests are within the required range",
|
||||
Category: "Resources",
|
||||
},
|
||||
{
|
||||
ID: "memoryRequestRanges",
|
||||
ID: "memoryLimitsRange",
|
||||
Type: "success",
|
||||
Message: "Memory requests are within the expected range",
|
||||
Category: "Resources",
|
||||
},
|
||||
{
|
||||
ID: "cpuLimitRanges",
|
||||
Type: "success",
|
||||
Message: "CPU limits are within the expected range",
|
||||
Category: "Resources",
|
||||
},
|
||||
{
|
||||
ID: "memoryLimitRanges",
|
||||
Type: "success",
|
||||
Message: "Memory limits are within the expected range",
|
||||
Message: "Memory limits are within the required range",
|
||||
Category: "Resources",
|
||||
},
|
||||
}
|
||||
|
||||
testValidateResources(t, &container, &resourceConf1, "foo", []*ResultMessage{}, []*ResultMessage{}, expectedSuccesses)
|
||||
testValidateResources(t, &container, &resourceConfRanges, "foo", []*ResultMessage{}, []*ResultMessage{}, expectedSuccesses)
|
||||
|
||||
expectedSuccesses = []*ResultMessage{
|
||||
{
|
||||
@@ -353,7 +332,7 @@ func TestValidateResourcesFullyValid(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
testValidateResources(t, &container, &resourceConf2, "foo", []*ResultMessage{}, []*ResultMessage{}, expectedSuccesses)
|
||||
testValidateResources(t, &container, &resourceConfMinimal, "foo", []*ResultMessage{}, []*ResultMessage{}, expectedSuccesses)
|
||||
}
|
||||
|
||||
func TestValidateHealthChecks(t *testing.T) {
|
||||
@@ -1339,6 +1318,7 @@ func TestValidateResourcesExemption(t *testing.T) {
|
||||
testValidateResources(t, &container, &disallowExemptionsConf, "foo", expectedErrors, expectedWarnings, expectedSuccesses)
|
||||
}
|
||||
|
||||
/*
|
||||
func TestValidateResourceRangeExemption(t *testing.T) {
|
||||
container := corev1.Container{
|
||||
Name: "Empty",
|
||||
@@ -1350,6 +1330,7 @@ func TestValidateResourceRangeExemption(t *testing.T) {
|
||||
|
||||
testValidateResources(t, &container, &resourceConfRangeExemptions, "foo", expectedErrors, expectedWarnings, expectedSuccesses)
|
||||
}
|
||||
*/
|
||||
|
||||
func resetCV(cv ContainerValidation) ContainerValidation {
|
||||
cv.Errors = []*ResultMessage{}
|
||||
|
||||
@@ -2,49 +2,21 @@ package validator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
packr "github.com/gobuffalo/packr/v2"
|
||||
"github.com/qri-io/jsonschema"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
)
|
||||
|
||||
type includeExcludeList struct {
|
||||
Include []string `yaml:"include"`
|
||||
Exclude []string `yaml:"exclude"`
|
||||
}
|
||||
|
||||
type target string
|
||||
|
||||
const (
|
||||
targetContainer target = "Container"
|
||||
targetPod target = "Pod"
|
||||
)
|
||||
|
||||
// SchemaCheck is a Polaris check that runs using JSON Schema
|
||||
type SchemaCheck struct {
|
||||
Name string `yaml:"name"`
|
||||
ID string `yaml:"id"`
|
||||
Category string `yaml:"category"`
|
||||
SuccessMessage string `yaml:"successMessage"`
|
||||
FailureMessage string `yaml:"failureMessage"`
|
||||
Controllers includeExcludeList `yaml:"controllers"`
|
||||
Containers includeExcludeList `yaml:"containers"`
|
||||
Target target `yaml:"target"`
|
||||
SchemaTarget target `yaml:"schemaTarget"`
|
||||
Schema jsonschema.RootSchema `yaml:"schema"`
|
||||
}
|
||||
|
||||
var (
|
||||
schemaBox = (*packr.Box)(nil)
|
||||
checks = map[target][]SchemaCheck{
|
||||
targetContainer: []SchemaCheck{},
|
||||
targetPod: []SchemaCheck{},
|
||||
checks = map[config.TargetKind][]config.SchemaCheck{
|
||||
config.TargetContainer: []config.SchemaCheck{},
|
||||
config.TargetPod: []config.SchemaCheck{},
|
||||
}
|
||||
// We explicitly set the order to avoid thrash in the
|
||||
// tests as we migrate toward JSON schema
|
||||
@@ -54,6 +26,10 @@ var (
|
||||
"hostPID",
|
||||
"hostNetwork",
|
||||
// Container checks
|
||||
"memoryLimitsMissing",
|
||||
"memoryRequestsMissing",
|
||||
"cpuLimitsMissing",
|
||||
"cpuRequestsMissing",
|
||||
"readinessProbe",
|
||||
"livenessProbe",
|
||||
"pullPolicyNotAlways",
|
||||
@@ -83,9 +59,9 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
func parseCheck(rawBytes []byte) (SchemaCheck, error) {
|
||||
func parseCheck(rawBytes []byte) (config.SchemaCheck, error) {
|
||||
reader := bytes.NewReader(rawBytes)
|
||||
check := SchemaCheck{}
|
||||
check := config.SchemaCheck{}
|
||||
d := yaml.NewYAMLOrJSONDecoder(reader, 4096)
|
||||
for {
|
||||
if err := d.Decode(&check); err != nil {
|
||||
@@ -97,72 +73,16 @@ func parseCheck(rawBytes []byte) (SchemaCheck, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (check SchemaCheck) checkPod(pod *corev1.PodSpec) (bool, error) {
|
||||
return check.checkObject(pod)
|
||||
}
|
||||
|
||||
func (check SchemaCheck) checkContainer(container *corev1.Container) (bool, error) {
|
||||
return check.checkObject(container)
|
||||
}
|
||||
|
||||
func (check SchemaCheck) checkObject(obj interface{}) (bool, error) {
|
||||
bytes, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
errors, err := check.Schema.ValidateBytes(bytes)
|
||||
return len(errors) == 0, err
|
||||
}
|
||||
|
||||
func (check SchemaCheck) isActionable(target target, controllerType config.SupportedController, isInit bool) bool {
|
||||
if check.Target != target {
|
||||
return false
|
||||
}
|
||||
isIncluded := len(check.Controllers.Include) == 0
|
||||
for _, inclusion := range check.Controllers.Include {
|
||||
if config.GetSupportedControllerFromString(inclusion) == controllerType {
|
||||
isIncluded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isIncluded {
|
||||
return false
|
||||
}
|
||||
for _, exclusion := range check.Controllers.Exclude {
|
||||
if config.GetSupportedControllerFromString(exclusion) == controllerType {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if check.Target == targetContainer {
|
||||
isIncluded := len(check.Containers.Include) == 0
|
||||
for _, inclusion := range check.Containers.Include {
|
||||
if (inclusion == "initContainer" && isInit) || (inclusion == "container" && !isInit) {
|
||||
isIncluded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isIncluded {
|
||||
return false
|
||||
}
|
||||
for _, exclusion := range check.Containers.Exclude {
|
||||
if (exclusion == "initContainer" && isInit) || (exclusion == "container" && !isInit) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func applyPodSchemaChecks(conf *config.Configuration, pod *corev1.PodSpec, controllerName string, controllerType config.SupportedController, pv *PodValidation) error {
|
||||
for _, check := range checks[targetPod] {
|
||||
for _, check := range checks[config.TargetPod] {
|
||||
if !conf.IsActionable(check.Category, check.Name, controllerName) {
|
||||
continue
|
||||
}
|
||||
if !check.isActionable(targetPod, controllerType, false) {
|
||||
if !check.IsActionable(config.TargetPod, controllerType, false) {
|
||||
continue
|
||||
}
|
||||
severity := conf.GetSeverity(check.Category, check.Name)
|
||||
passes, err := check.checkPod(pod)
|
||||
passes, err := check.CheckPod(pod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -176,22 +96,54 @@ func applyPodSchemaChecks(conf *config.Configuration, pod *corev1.PodSpec, contr
|
||||
}
|
||||
|
||||
func applyContainerSchemaChecks(conf *config.Configuration, controllerName string, controllerType config.SupportedController, cv *ContainerValidation) error {
|
||||
for _, check := range checks[targetContainer] {
|
||||
for _, check := range checks[config.TargetContainer] {
|
||||
if !conf.IsActionable(check.Category, check.Name, controllerName) {
|
||||
continue
|
||||
}
|
||||
if !check.isActionable(targetContainer, controllerType, cv.IsInitContainer) {
|
||||
if !check.IsActionable(config.TargetContainer, controllerType, cv.IsInitContainer) {
|
||||
continue
|
||||
}
|
||||
severity := conf.GetSeverity(check.Category, check.Name)
|
||||
var passes bool
|
||||
var err error
|
||||
if check.SchemaTarget == targetPod {
|
||||
if check.SchemaTarget == config.TargetPod {
|
||||
cv.parentPodSpec.Containers = []corev1.Container{*cv.Container}
|
||||
passes, err = check.checkPod(&cv.parentPodSpec)
|
||||
passes, err = check.CheckPod(&cv.parentPodSpec)
|
||||
cv.parentPodSpec.Containers = []corev1.Container{}
|
||||
} else {
|
||||
passes, err = check.checkContainer(cv.Container)
|
||||
passes, err = check.CheckContainer(cv.Container)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if passes {
|
||||
cv.addSuccess(check.SuccessMessage, check.Category, check.ID)
|
||||
} else {
|
||||
cv.addFailure(check.FailureMessage, severity, check.Category, check.ID)
|
||||
}
|
||||
}
|
||||
for checkName, severity := range conf.Checks {
|
||||
check, ok := conf.CustomChecks[checkName]
|
||||
if !ok {
|
||||
return fmt.Errorf("Custom check %s not found", checkName)
|
||||
}
|
||||
// FIXME: check actionability here
|
||||
/*
|
||||
if !conf.IsActionable(check.Category, check.Name, controllerName) {
|
||||
continue
|
||||
}
|
||||
*/
|
||||
if !check.IsActionable(config.TargetContainer, controllerType, cv.IsInitContainer) {
|
||||
continue
|
||||
}
|
||||
var passes bool
|
||||
var err error
|
||||
if check.SchemaTarget == config.TargetPod {
|
||||
cv.parentPodSpec.Containers = []corev1.Container{*cv.Container}
|
||||
passes, err = check.CheckPod(&cv.parentPodSpec)
|
||||
cv.parentPodSpec.Containers = []corev1.Container{}
|
||||
} else {
|
||||
passes, err = check.CheckContainer(cv.Container)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user