Compare commits

..
1 Commits
Author SHA1 Message Date
Robert Brennan c90284ea1a use create-namespace in helm docs 2021-03-25 12:48:19 +00:00
33 changed files with 747 additions and 718 deletions
+1 -1
View File
@@ -1 +1 @@
* @rbren @makoscafee @baderbuddy @shaswa
* @rbren @makoscafee @jordandoig @baderbuddy @shaswa
@@ -1,18 +0,0 @@
successMessage: disruptionsAllowed is greater than zero
failureMessage: disruptionsAllowed is not greater than zero
category: Reliability
target: PodDisruptionBudget
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- status
properties:
status:
type: object
required:
- disruptionsAllowed
properties:
disruptionsAllowed:
type: integer
minimum: 1
+1 -1
View File
@@ -1,7 +1,7 @@
successMessage: Ingress has TLS configured
failureMessage: Ingress does not have TLS configured
category: Security
target: networking.k8s.io/Ingress
target: Ingress
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
+1 -1
View File
@@ -79,7 +79,7 @@ var auditCmd = &cobra.Command{
func runAndReportAudit(ctx context.Context, c conf.Configuration, auditPath, workload, outputFile, outputURL, outputFormat string, useColor bool) validator.AuditData {
// Create a kubernetes client resource provider
k, err := kube.CreateResourceProvider(ctx, auditPath, workload, c)
k, err := kube.CreateResourceProvider(ctx, auditPath, workload)
if err != nil {
logrus.Errorf("Error fetching Kubernetes resources %v", err)
os.Exit(1)
+1 -1
View File
@@ -42,7 +42,7 @@ const baseConfig = {
],
themeConfig: {
docsRepo: "",
docsDir: 'docs',
docsDir: 'docs-md',
editLinks: true,
editLinkText: "Help us improve this page",
logo: '/img/fairwinds-logo.svg',
+3 -1
View File
@@ -28,7 +28,9 @@ kubectl apply -f https://github.com/fairwindsops/polaris/releases/latest/downloa
### Helm
```bash
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm upgrade --install polaris fairwinds-stable/polaris --namespace polaris \
helm upgrade --install polaris fairwinds-stable/polaris \
--namespace polaris \
--create-namespace \
--set webhook.enable=true --set dashboard.enable=false
```
+5 -1
View File
@@ -19,7 +19,11 @@ kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
### Helm
```bash
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm upgrade --install polaris fairwinds-stable/polaris --namespace polaris
helm upgrade --install polaris fairwinds-stable/polaris \
--namespace polaris \
--create-namespace
kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
```
-1
View File
@@ -22,7 +22,6 @@ require (
github.com/spf13/cobra v1.1.3
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
github.com/thoas/go-funk v0.7.0
go.uber.org/zap v1.16.0 // indirect
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
-2
View File
@@ -617,8 +617,6 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/thoas/go-funk v0.7.0 h1:GmirKrs6j6zJbhJIficOsz2aAI7700KsU/5YrdHRM1Y=
github.com/thoas/go-funk v0.7.0/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+3 -5
View File
@@ -2,12 +2,10 @@ package config
import (
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// IsActionable determines whether a check is actionable given the current configuration
func (conf Configuration) IsActionable(ruleID string, objMeta metav1.Object, containerName string) bool {
func (conf Configuration) IsActionable(ruleID, namespace, controllerName, containerName string) bool {
if severity, ok := conf.Checks[ruleID]; !ok || !severity.IsActionable() {
return false
}
@@ -15,7 +13,7 @@ func (conf Configuration) IsActionable(ruleID string, objMeta metav1.Object, con
return true
}
for _, exemption := range conf.Exemptions {
if exemption.Namespace != "" && exemption.Namespace != objMeta.GetNamespace() {
if exemption.Namespace != "" && exemption.Namespace != namespace {
continue
}
@@ -29,7 +27,7 @@ func (conf Configuration) IsActionable(ruleID string, objMeta metav1.Object, con
}
if len(exemption.Rules) == 0 || checkIfRuleMatches {
if !isExemptionCheckMatched(exemption.ControllerNames, objMeta.GetName()) {
if !isExemptionCheckMatched(exemption.ControllerNames, controllerName) {
continue
}
if isExemptionCheckMatched(exemption.ContainerNames, containerName) {
+49 -63
View File
@@ -18,9 +18,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
var confContainerTest = `
@@ -71,37 +68,26 @@ exemptions:
- namespace: polaris
`
func createMeta(namespace, name string) metav1.Object {
unst := unstructured.Unstructured{}
obj, err := meta.Accessor(&unst)
if err != nil {
panic(err)
}
obj.SetName(name)
obj.SetNamespace(namespace)
return obj
}
func TestNamespaceExemptionForSpecifiedRules(t *testing.T) {
parsedConf, err := Parse([]byte(confContainerTest))
assert.NoError(t, err)
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", ""), "")
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "", "")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller1"), "container11")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "controller1", "container11")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", ""), "container11")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "", "container11")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller1"), "")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "controller1", "")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("pullPolicyNotAlways", createMeta("prometheus", "controller1"), "")
actionable = parsedConf.IsActionable("pullPolicyNotAlways", "prometheus", "controller1", "")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "")
assert.True(t, actionable)
}
@@ -109,19 +95,19 @@ func TestNamespaceExemptionForAllRules(t *testing.T) {
parsedConf, err := Parse([]byte(confContainerTest))
assert.NoError(t, err)
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", ""), "")
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "polaris", "", "")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", "controller1"), "container11")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "polaris", "controller1", "container11")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", ""), "container11")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "polaris", "", "container11")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", "controller1"), "")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "polaris", "controller1", "")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("pullPolicyNotAlways", createMeta("polaris", "controller1"), "")
actionable = parsedConf.IsActionable("pullPolicyNotAlways", "polaris", "controller1", "")
assert.False(t, actionable)
}
@@ -129,28 +115,28 @@ func TestControllerExemption(t *testing.T) {
parsedConf, err := Parse([]byte(confContainerTest))
assert.NoError(t, err)
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller2"), "")
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller2", "")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller2"), "container21")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller2", "container21")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller2"), "container21")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "controller2", "container21")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller2"), "")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "controller2", "")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller3"), "")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller3", "")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller3"), "")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller3", "")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller3"), "container31")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller3", "container31")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller4"), "")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller4", "")
assert.True(t, actionable)
}
@@ -158,22 +144,22 @@ func TestOnlyContainerExemption(t *testing.T) {
parsedConf, err := Parse([]byte(confContainerTest))
assert.NoError(t, err)
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container41")
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container41")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container42")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container42")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller4"), "container41")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller4", "container41")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container41")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "container41")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller4"), "container41")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller4", "container41")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container51")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container51")
assert.True(t, actionable)
}
@@ -181,25 +167,25 @@ func TestNamespaceAndContainerExemption(t *testing.T) {
parsedConf, err := Parse([]byte(confContainerTest))
assert.NoError(t, err)
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container51")
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "container51")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("priorityClassNotSet", createMeta("kube-system", ""), "container51")
actionable = parsedConf.IsActionable("priorityClassNotSet", "kube-system", "", "container51")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller5"), "container51")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller5", "container51")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller5"), "")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller5", "")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("insights-agent", ""), "container51")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "insights-agent", "", "container51")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container51")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container51")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller5"), "container51")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller5", "container51")
assert.True(t, actionable)
}
@@ -207,25 +193,25 @@ func TestControllerAndContainerExemption(t *testing.T) {
parsedConf, err := Parse([]byte(confContainerTest))
assert.NoError(t, err)
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller6"), "container61")
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller6", "container61")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("priorityClassNotSet", createMeta("", "controller6"), "container61")
actionable = parsedConf.IsActionable("priorityClassNotSet", "", "controller6", "container61")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller6"), "container61")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller6", "container61")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller6"), "")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller6", "")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller7"), "container61")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller7", "container61")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container61")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container61")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container61")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "container61")
assert.True(t, actionable)
}
@@ -233,33 +219,33 @@ func TestContainerExemption(t *testing.T) {
parsedConf, err := Parse([]byte(confContainerTest))
assert.NoError(t, err)
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container71")
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container71")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container71")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "container71")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller7"), "container71")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller7", "container71")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller7"), "")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller7", "")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller7"), "container71")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller7", "container71")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("insights-agent", "controller7"), "container71")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "insights-agent", "controller7", "container71")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller6"), "container71")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller6", "container71")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller7"), "container61")
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller7", "container61")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("priorityClassNotSet", createMeta("kube-system", "controller7"), "container71")
actionable = parsedConf.IsActionable("priorityClassNotSet", "kube-system", "controller7", "container71")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("pullPolicyNotAlways", createMeta("kube-system", "controller8"), "container71")
actionable = parsedConf.IsActionable("pullPolicyNotAlways", "kube-system", "controller8", "container71")
assert.True(t, actionable)
}
+8 -19
View File
@@ -3,10 +3,8 @@ package config
import (
"encoding/json"
"fmt"
"strings"
"github.com/qri-io/jsonschema"
"github.com/thoas/go-funk"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
)
@@ -15,21 +13,16 @@ import (
type TargetKind string
const (
// TargetController points to the controller's spec
TargetController TargetKind = "Controller"
// TargetContainer points to the container spec
TargetContainer TargetKind = "Container"
// TargetPod points to the pod spec
TargetPod TargetKind = "Pod"
// TargetController points to the controller's spec
TargetController TargetKind = "Controller"
// TargetIngress points to the ingress spec
TargetIngress TargetKind = "Ingress"
)
// HandledTargets is a list of target names that are explicitly handled
var HandledTargets = []TargetKind{
TargetController,
TargetContainer,
TargetPod,
}
// SchemaCheck is a Polaris check that runs using JSON Schema
type SchemaCheck struct {
ID string `yaml:"id"`
@@ -161,17 +154,13 @@ func (check SchemaCheck) CheckObject(obj interface{}) (bool, error) {
}
// IsActionable decides if this check applies to a particular target
func (check SchemaCheck) IsActionable(target TargetKind, kind string, isInit bool) bool {
if funk.Contains(HandledTargets, target) {
if check.Target != target {
return false
}
} else if string(check.Target) != kind && !strings.HasSuffix(string(check.Target), "/"+kind) {
func (check SchemaCheck) IsActionable(target TargetKind, controllerType string, isInit bool) bool {
if check.Target != target {
return false
}
isIncluded := len(check.Controllers.Include) == 0
for _, inclusion := range check.Controllers.Include {
if inclusion == kind {
if inclusion == controllerType {
isIncluded = true
break
}
@@ -180,7 +169,7 @@ func (check SchemaCheck) IsActionable(target TargetKind, kind string, isInit boo
return false
}
for _, exclusion := range check.Controllers.Exclude {
if exclusion == kind {
if exclusion == controllerType {
return false
}
}
+2 -2
View File
@@ -173,7 +173,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
router.HandleFunc("/results.json", func(w http.ResponseWriter, r *http.Request) {
adjustedConf := getConfigForQuery(c, r.URL.Query())
if auditData == nil {
k, err := kube.CreateResourceProvider(r.Context(), auditPath, "", c)
k, err := kube.CreateResourceProvider(r.Context(), auditPath, "")
if err != nil {
logrus.Errorf("Error fetching Kubernetes resources %v", err)
http.Error(w, "Error fetching Kubernetes resources", http.StatusInternalServerError)
@@ -206,7 +206,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
adjustedConf := getConfigForQuery(c, r.URL.Query())
if auditData == nil {
k, err := kube.CreateResourceProvider(r.Context(), auditPath, "", c)
k, err := kube.CreateResourceProvider(r.Context(), auditPath, "")
if err != nil {
logrus.Errorf("Error fetching Kubernetes resources %v", err)
http.Error(w, "Error fetching Kubernetes resources", http.StatusInternalServerError)
+70 -127
View File
@@ -12,15 +12,12 @@ import (
"strings"
"time"
conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/sirupsen/logrus"
"github.com/thoas/go-funk"
corev1 "k8s.io/api/core/v1"
v1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
k8sYaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
@@ -37,59 +34,8 @@ type ResourceProvider struct {
SourceType string
Nodes []corev1.Node
Namespaces []corev1.Namespace
Resources resourceKindMap
}
type resourceKindMap map[string][]GenericResource
func (rkm resourceKindMap) addResource(r GenericResource) {
rkm[r.Kind] = append(rkm[r.Kind], r)
}
func (rkm resourceKindMap) addResources(rs []GenericResource) {
for _, r := range rs {
rkm.addResource(r)
}
}
func (rkm resourceKindMap) GetLength() int {
total := 0
for _, rs := range rkm {
total += len(rs)
}
return total
}
func (rkm resourceKindMap) GetNumberOfControllers() int {
total := 0
for _, rs := range rkm {
for _, r := range rs {
if r.PodSpec != nil {
total++
}
}
}
return total
}
// This is here for backward compatibility reasons
func maybeTransformKindIntoGroupKind(k conf.TargetKind) string {
if k == "Ingress" {
return "networking.k8s.io/Ingress"
}
return ""
}
func newResourceProvider(version, sourceType, sourceName string) ResourceProvider {
return ResourceProvider{
ServerVersion: version,
SourceType: sourceType,
SourceName: sourceName,
CreationTime: time.Now(),
Nodes: make([]corev1.Node, 0),
Namespaces: make([]corev1.Namespace, 0),
Resources: make(map[string][]GenericResource),
}
Controllers []GenericWorkload
Ingresses []v1beta1.Ingress
}
type k8sResource struct {
@@ -99,18 +45,18 @@ type k8sResource struct {
var podSpecFields = []string{"jobTemplate", "spec", "template"}
// CreateResourceProvider returns a new ResourceProvider object to interact with k8s resources
func CreateResourceProvider(ctx context.Context, directory, workload string, c conf.Configuration) (*ResourceProvider, error) {
func CreateResourceProvider(ctx context.Context, directory, workload string) (*ResourceProvider, error) {
if workload != "" {
return CreateResourceProviderFromResource(ctx, workload)
return CreateResourceProviderFromWorkload(ctx, workload)
}
if directory != "" {
return CreateResourceProviderFromPath(directory)
}
return CreateResourceProviderFromCluster(ctx, c)
return CreateResourceProviderFromCluster(ctx)
}
// CreateResourceProviderFromResource creates a new ResourceProvider that just contains one workload
func CreateResourceProviderFromResource(ctx context.Context, workload string) (*ResourceProvider, error) {
// CreateResourceProviderFromWorkload creates a new ResourceProvider that just contains one workload
func CreateResourceProviderFromWorkload(ctx context.Context, workload string) (*ResourceProvider, error) {
kubeConf, configError := config.GetConfig()
if configError != nil {
logrus.Errorf("Error fetching KubeConfig: %v", configError)
@@ -126,7 +72,14 @@ func CreateResourceProviderFromResource(ctx context.Context, workload string) (*
logrus.Errorf("Error fetching Cluster API version: %v", err)
return nil, err
}
resources := newResourceProvider(serverVersion.Major+"."+serverVersion.Minor, "Resource", workload)
resources := ResourceProvider{
ServerVersion: serverVersion.Major + "." + serverVersion.Minor,
SourceType: "Workload",
SourceName: workload,
CreationTime: time.Now(),
Nodes: []corev1.Node{},
Namespaces: []corev1.Namespace{},
}
parts := strings.Split(workload, "/")
if len(parts) != 4 {
@@ -153,24 +106,31 @@ func CreateResourceProviderFromResource(ctx context.Context, workload string) (*
logrus.Errorf("Could not find workload %s: %v", workload, err)
return nil, err
}
workloadObj, err := NewGenericResourceFromUnstructured(obj)
workloadObj, err := NewGenericWorkloadFromUnstructured(kind, obj)
if err != nil {
logrus.Errorf("Could not parse workload %s: %v", workload, err)
return nil, err
}
resources.Resources.addResource(workloadObj)
resources.Controllers = []GenericWorkload{workloadObj}
return &resources, nil
}
// CreateResourceProviderFromPath returns a new ResourceProvider using the YAML files in a directory
func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error) {
resources := newResourceProvider("unknown", "Path", directory)
resources := ResourceProvider{
ServerVersion: "unknown",
SourceType: "Path",
SourceName: directory,
Nodes: []corev1.Node{},
Namespaces: []corev1.Namespace{},
Controllers: []GenericWorkload{},
}
if directory == "-" {
fi, err := os.Stdin.Stat()
if err == nil && fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
if err := resources.addResourcesFromReader(os.Stdin); err != nil {
if err := addResourcesFromReader(os.Stdin, &resources); err != nil {
return nil, err
}
return &resources, nil
@@ -186,7 +146,7 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error)
logrus.Errorf("Error reading file: %v", path)
return err
}
return resources.addResourcesFromYaml(string(contents))
return addResourcesFromYaml(string(contents), &resources)
}
err := filepath.Walk(directory, visitFile)
@@ -197,7 +157,7 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error)
}
// CreateResourceProviderFromCluster creates a new ResourceProvider using live data from a cluster
func CreateResourceProviderFromCluster(ctx context.Context, c conf.Configuration) (*ResourceProvider, error) {
func CreateResourceProviderFromCluster(ctx context.Context) (*ResourceProvider, error) {
kubeConf, configError := config.GetConfig()
if configError != nil {
logrus.Errorf("Error fetching KubeConfig: %v", configError)
@@ -213,18 +173,17 @@ func CreateResourceProviderFromCluster(ctx context.Context, c conf.Configuration
logrus.Errorf("Error connecting to dynamic interface: %v", err)
return nil, err
}
return CreateResourceProviderFromAPI(ctx, api, kubeConf.Host, &dynamicInterface, c)
return CreateResourceProviderFromAPI(ctx, api, kubeConf.Host, &dynamicInterface)
}
// CreateResourceProviderFromAPI creates a new ResourceProvider from an existing k8s interface
func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interface, clusterName string, dynamic *dynamic.Interface, c conf.Configuration) (*ResourceProvider, error) {
func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interface, clusterName string, dynamic *dynamic.Interface) (*ResourceProvider, error) {
listOpts := metav1.ListOptions{}
serverVersion, err := kube.Discovery().ServerVersion()
if err != nil {
logrus.Errorf("Error fetching Cluster API version: %v", err)
return nil, err
}
provider := newResourceProvider(serverVersion.Major+"."+serverVersion.Minor, "Cluster", clusterName)
nodes, err := kube.CoreV1().Nodes().List(ctx, listOpts)
if err != nil {
@@ -241,6 +200,11 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
logrus.Errorf("Error fetching Pods: %v", err)
return nil, err
}
ingressList, err := kube.ExtensionsV1beta1().Ingresses("").List(ctx, listOpts)
if err != nil {
logrus.Errorf("Error fetching Ingresses: %v", err)
return nil, err
}
resources, err := restmapper.GetAPIGroupResources(kube.Discovery())
if err != nil {
@@ -249,35 +213,6 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
}
restMapper := restmapper.NewDiscoveryRESTMapper(resources)
var additionalKinds []conf.TargetKind
for _, check := range c.CustomChecks {
if !funk.Contains(conf.HandledTargets, check.Target) {
additionalKinds = append(additionalKinds, check.Target)
}
}
for _, kind := range additionalKinds {
groupKind := schema.ParseGroupKind(maybeTransformKindIntoGroupKind(kind))
mapping, err := (restMapper).RESTMapping(groupKind)
if err != nil {
logrus.Warnf("Error retrieving mapping of Kind %s because of error: %v", kind, err)
return nil, err
}
objects, err := (*dynamic).Resource(mapping.Resource).Namespace("").List(ctx, metav1.ListOptions{})
if err != nil {
logrus.Warnf("Error retrieving parent object API %s and Kind %s because of error: %v", mapping.Resource.Version, mapping.Resource.Resource, err)
return nil, err
}
for _, obj := range objects.Items {
res, err := NewGenericResourceFromUnstructured(&obj)
if err != nil {
return nil, err
}
provider.Resources.addResource(res)
}
}
objectCache := map[string]unstructured.Unstructured{}
controllers, err := LoadControllers(ctx, pods.Items, dynamic, &restMapper, objectCache)
@@ -285,15 +220,23 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
logrus.Errorf("Error loading controllers from pods: %v", err)
return nil, err
}
provider.Nodes = nodes.Items
provider.Namespaces = namespaces.Items
provider.Resources.addResources(controllers)
return &provider, nil
api := ResourceProvider{
ServerVersion: serverVersion.Major + "." + serverVersion.Minor,
SourceType: "Cluster",
SourceName: clusterName,
CreationTime: time.Now(),
Nodes: nodes.Items,
Namespaces: namespaces.Items,
Controllers: controllers,
Ingresses: ingressList.Items,
}
return &api, nil
}
// LoadControllers loads a list of controllers from the kubeResources Pods
func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) ([]GenericResource, error) {
interfaces := []GenericResource{}
func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) ([]GenericWorkload, error) {
interfaces := []GenericWorkload{}
deduped := map[string]corev1.Pod{}
for _, pod := range pods {
owners := pod.ObjectMeta.OwnerReferences
@@ -304,7 +247,7 @@ func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointe
deduped[pod.ObjectMeta.Namespace+"/"+owners[0].Kind+"/"+owners[0].Name] = pod
}
for _, pod := range deduped {
workload, err := ResolveControllerFromPod(ctx, pod, dynamicClientPointer, restMapperPointer, objectCache)
workload, err := NewGenericWorkload(ctx, pod, dynamicClientPointer, restMapperPointer, objectCache)
if err != nil {
return nil, err
}
@@ -315,8 +258,8 @@ func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointe
// Because the controllers with an Owner take on the name of the Owner, this eliminates any duplicates.
// In cases like CronJobs older children can hang around, so this takes the most recent.
func deduplicateControllers(inputControllers []GenericResource) []GenericResource {
controllerMap := make(map[string]GenericResource)
func deduplicateControllers(inputControllers []GenericWorkload) []GenericWorkload {
controllerMap := make(map[string]GenericWorkload)
for _, controller := range inputControllers {
key := controller.ObjectMeta.GetNamespace() + "/" + controller.Kind + "/" + controller.ObjectMeta.GetName()
oldController, ok := controllerMap[key]
@@ -324,34 +267,32 @@ func deduplicateControllers(inputControllers []GenericResource) []GenericResourc
controllerMap[key] = controller
}
}
results := make([]GenericResource, len(controllerMap))
idx := 0
results := make([]GenericWorkload, 0)
for _, controller := range controllerMap {
results[idx] = controller
idx++
results = append(results, controller)
}
return results
}
func (resources *ResourceProvider) addResourcesFromReader(reader io.Reader) error {
func addResourcesFromReader(reader io.Reader, resources *ResourceProvider) error {
contents, err := ioutil.ReadAll(reader)
if err != nil {
logrus.Errorf("Error reading from %v: %v", reader, err)
return err
}
if err := resources.addResourcesFromYaml(string(contents)); err != nil {
if err := addResourcesFromYaml(string(contents), resources); err != nil {
return err
}
return nil
}
func (resources *ResourceProvider) addResourcesFromYaml(contents string) error {
func addResourcesFromYaml(contents string, resources *ResourceProvider) error {
specs := regexp.MustCompile("[\r\n]-+[\r\n]").Split(string(contents), -1)
for _, spec := range specs {
if strings.TrimSpace(spec) == "" {
continue
}
err := resources.addResourceFromString(spec)
err := addResourceFromString(spec, resources)
if err != nil {
logrus.Errorf("Error parsing YAML: (%v)", err)
return err
@@ -360,7 +301,7 @@ func (resources *ResourceProvider) addResourcesFromYaml(contents string) error {
return nil
}
func (resources *ResourceProvider) addResourceFromString(contents string) error {
func addResourceFromString(contents string, resources *ResourceProvider) error {
contentBytes := []byte(contents)
decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(contentBytes), 1000)
resource := k8sResource{}
@@ -375,25 +316,27 @@ func (resources *ResourceProvider) addResourceFromString(contents string) error
ns := corev1.Namespace{}
err = decoder.Decode(&ns)
resources.Namespaces = append(resources.Namespaces, ns)
}
if resource.Kind == "Pod" {
} else if resource.Kind == "Pod" {
pod := corev1.Pod{}
err = decoder.Decode(&pod)
if err != nil {
return err
}
workload, err := NewGenericResourceFromPod(pod, pod)
workload, err := NewGenericWorkloadFromPod(pod, pod)
if err != nil {
return err
}
resources.Resources.addResource(workload)
resources.Controllers = append(resources.Controllers, workload)
} else if resource.Kind == "Ingress" {
ingress := v1beta1.Ingress{}
err = decoder.Decode(&ingress)
resources.Ingresses = append(resources.Ingresses, ingress)
} else {
newResource, err := NewGenericResourceFromBytes(contentBytes)
if err != nil {
newController, err := GetWorkloadFromBytes(contentBytes)
if err != nil || newController == nil {
return err
}
resources.Resources.addResource(newResource)
resources.Controllers = append(resources.Controllers, *newController)
}
return err
}
+31 -29
View File
@@ -3,40 +3,36 @@ package kube
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"testing"
"time"
conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/test"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
)
func TestGetResourcesFromPath(t *testing.T) {
provider, err := CreateResourceProviderFromPath("./test_files/test_1")
resources, err := CreateResourceProviderFromPath("./test_files/test_1")
assert.Equal(t, nil, err, "Error should be nil")
assert.Equal(t, "Path", provider.SourceType, "Should have type Path")
assert.Equal(t, "./test_files/test_1", provider.SourceName, "Should have filename as name")
assert.Equal(t, "unknown", provider.ServerVersion, "Server version should be unknown")
assert.IsType(t, time.Now(), provider.CreationTime, "Creation time should be set")
assert.Equal(t, "Path", resources.SourceType, "Should have type Path")
assert.Equal(t, "./test_files/test_1", resources.SourceName, "Should have filename as name")
assert.Equal(t, "unknown", resources.ServerVersion, "Server version should be unknown")
assert.IsType(t, time.Now(), resources.CreationTime, "Creation time should be set")
assert.Equal(t, 0, len(provider.Nodes), "Should not have any nodes")
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
assert.Equal(t, 1, len(provider.Namespaces), "Should have a namespace")
assert.Equal(t, "two", provider.Namespaces[0].ObjectMeta.Name)
assert.Equal(t, 1, len(resources.Namespaces), "Should have a namespace")
assert.Equal(t, "two", resources.Namespaces[0].ObjectMeta.Name)
assert.Equal(t, 9, len(resources.Controllers), "Should have eight controllers")
namespaceCount := map[string]int{}
for kind, resources := range provider.Resources {
fmt.Println("found", kind, len(resources))
for _, controller := range resources {
namespaceCount[controller.ObjectMeta.GetNamespace()]++
}
for _, controller := range resources.Controllers {
namespaceCount[controller.ObjectMeta.GetNamespace()]++
}
assert.Equal(t, 11, provider.Resources.GetLength())
assert.Equal(t, 10, namespaceCount[""])
assert.Equal(t, 8, namespaceCount[""])
assert.Equal(t, 1, namespaceCount["two"])
}
@@ -52,8 +48,8 @@ func TestGetMultipleResourceFromSingleFile(t *testing.T) {
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
assert.Equal(t, 1, len(resources.Resources["Deployment"]), "Should have one controller")
assert.Equal(t, "dashboard", resources.Resources["Deployment"][0].PodSpec.Containers[0].Name)
assert.Equal(t, 1, len(resources.Controllers), "Should have one controller")
assert.Equal(t, "dashboard", resources.Controllers[0].PodSpec.Containers[0].Name)
assert.Equal(t, 2, len(resources.Namespaces), "Should have a namespace")
assert.Equal(t, "polaris", resources.Namespaces[0].ObjectMeta.Name)
@@ -69,14 +65,21 @@ func TestAddResourcesFromReader(t *testing.T) {
contents, err := ioutil.ReadFile("./test_files/test_2/multi.yaml")
assert.NoError(t, err)
reader := bytes.NewBuffer(contents)
resources := newResourceProvider("unknown", "Path", "-")
err = resources.addResourcesFromReader(reader)
resources := &ResourceProvider{
ServerVersion: "unknown",
SourceType: "Path",
SourceName: "-",
Nodes: []corev1.Node{},
Namespaces: []corev1.Namespace{},
Controllers: []GenericWorkload{},
}
err = addResourcesFromReader(reader, resources)
assert.NoError(t, err)
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
assert.Equal(t, 1, len(resources.Resources["Deployment"]), "Should have one controller")
assert.Equal(t, "dashboard", resources.Resources["Deployment"][0].PodSpec.Containers[0].Name)
assert.Equal(t, 1, len(resources.Controllers), "Should have one controller")
assert.Equal(t, "dashboard", resources.Controllers[0].PodSpec.Containers[0].Name)
assert.Equal(t, 2, len(resources.Namespaces), "Should have a namespace")
assert.Equal(t, "polaris", resources.Namespaces[0].ObjectMeta.Name)
@@ -85,7 +88,7 @@ func TestAddResourcesFromReader(t *testing.T) {
func TestGetResourceFromAPI(t *testing.T) {
k8s, dynamicInterface := test.SetupTestAPI(test.GetMockControllers("test")...)
resources, err := CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicInterface, conf.Configuration{})
resources, err := CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicInterface)
assert.Equal(t, nil, err, "Error should be nil")
assert.Equal(t, "Cluster", resources.SourceType, "Should have type Path")
@@ -93,7 +96,8 @@ func TestGetResourceFromAPI(t *testing.T) {
assert.IsType(t, time.Now(), resources.CreationTime, "Creation time should be set")
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
assert.Equal(t, 5, len(resources.Resources), "Should have 5 controllers")
assert.Equal(t, 0, len(resources.Ingresses), "Should not have any ingresses")
assert.Equal(t, 5, len(resources.Controllers), "Should have 5 controllers")
expectedNames := map[string]bool{
"deploy": false,
@@ -102,10 +106,8 @@ func TestGetResourceFromAPI(t *testing.T) {
"statefulset": false,
"daemonset": false,
}
for _, controllers := range resources.Resources {
for _, ctrl := range controllers {
expectedNames[ctrl.ObjectMeta.GetName()] = true
}
for _, ctrl := range resources.Controllers {
expectedNames[ctrl.ObjectMeta.GetName()] = true
}
for name, val := range expectedNames {
assert.Equal(t, true, val, name)
+85 -58
View File
@@ -1,6 +1,7 @@
package kube
import (
"bytes"
"context"
"encoding/json"
"fmt"
@@ -12,23 +13,22 @@ import (
kubeAPIMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
k8sYaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/dynamic"
)
// GenericResource is a base implementation with some free methods for inherited structs
type GenericResource struct {
// GenericWorkload is a base implementation with some free methods for inherited structs
type GenericWorkload struct {
Kind string
PodSpec kubeAPICoreV1.PodSpec
ObjectMeta kubeAPIMetaV1.Object
Resource unstructured.Unstructured
PodSpec *kubeAPICoreV1.PodSpec
OriginalObjectJSON []byte
}
// NewGenericResourceFromUnstructured creates a workload from an unstructured.Unstructured
func NewGenericResourceFromUnstructured(unst *unstructured.Unstructured) (GenericResource, error) {
workload := GenericResource{
Kind: unst.GetKind(),
Resource: *unst,
// NewGenericWorkloadFromUnstructured creates a workload from an unstructured.Unstructured
func NewGenericWorkloadFromUnstructured(kind string, unst *unstructured.Unstructured) (GenericWorkload, error) {
workload := GenericWorkload{
Kind: kind,
}
objMeta, err := meta.Accessor(unst)
@@ -49,27 +49,25 @@ func NewGenericResourceFromUnstructured(unst *unstructured.Unstructured) (Generi
return workload, err
}
podSpecMap := GetPodSpec(m)
if podSpecMap != nil {
b, err = json.Marshal(podSpecMap)
if err != nil {
return workload, err
}
podSpec := kubeAPICoreV1.PodSpec{}
err = json.Unmarshal(b, &podSpec)
if err != nil {
return workload, err
}
workload.PodSpec = &podSpec
b, err = json.Marshal(podSpecMap)
if err != nil {
return workload, err
}
podSpec := kubeAPICoreV1.PodSpec{}
err = json.Unmarshal(b, &podSpec)
if err != nil {
return workload, err
}
workload.PodSpec = podSpec
return workload, nil
}
// NewGenericResourceFromPod builds a new workload for a given Pod without looking at parents
func NewGenericResourceFromPod(podResource kubeAPICoreV1.Pod, originalObject interface{}) (GenericResource, error) {
workload := GenericResource{
// NewGenericWorkloadFromPod builds a new workload for a given Pod without looking at parents
func NewGenericWorkloadFromPod(podResource kubeAPICoreV1.Pod, originalObject interface{}) (GenericWorkload, error) {
workload := GenericWorkload{
Kind: "Pod",
PodSpec: &podResource.Spec,
PodSpec: podResource.Spec,
ObjectMeta: podResource.ObjectMeta.GetObjectMeta(),
}
if originalObject != nil {
@@ -79,12 +77,13 @@ func NewGenericResourceFromPod(podResource kubeAPICoreV1.Pod, originalObject int
}
workload.OriginalObjectJSON = bytes
err = json.Unmarshal(bytes, &workload.Resource.Object)
var unst unstructured.Unstructured
err = json.Unmarshal(bytes, &unst.Object)
if err != nil {
logrus.Error("Couldn't marshal JSON for pod ", err)
return workload, err
}
objMeta, err := meta.Accessor(&workload.Resource)
objMeta, err := meta.Accessor(&unst)
if err != nil {
logrus.Error("Couldn't create meta accessor for unstructred ", err)
return workload, err
@@ -94,36 +93,26 @@ func NewGenericResourceFromPod(podResource kubeAPICoreV1.Pod, originalObject int
return workload, nil
}
// NewGenericResourceFromBytes parses a generic kubernetes resource
func NewGenericResourceFromBytes(contentBytes []byte) (GenericResource, error) {
unst := unstructured.Unstructured{}
err := yaml.Unmarshal(contentBytes, &unst.Object)
if err != nil {
return GenericResource{}, err
}
return NewGenericResourceFromUnstructured(&unst)
}
// ResolveControllerFromPod builds a new workload for a given Pod
func ResolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericResource, error) {
workload, err := resolveControllerFromPod(ctx, podResource, dynamicClient, restMapper, objectCache)
// NewGenericWorkload builds a new workload for a given Pod
func NewGenericWorkload(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericWorkload, error) {
workload, err := newGenericWorkload(ctx, podResource, dynamicClient, restMapper, objectCache)
if err != nil {
return workload, err
}
if len(workload.OriginalObjectJSON) == 0 {
return NewGenericResourceFromPod(podResource, podResource)
return NewGenericWorkloadFromPod(podResource, podResource)
}
return workload, err
}
func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericResource, error) {
podWorkload, err := NewGenericResourceFromPod(podResource, nil)
func newGenericWorkload(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericWorkload, error) {
workload, err := NewGenericWorkloadFromPod(podResource, nil)
if err != nil {
return podWorkload, err
return workload, err
}
topKind := "Pod"
topMeta := podWorkload.ObjectMeta
owners := podResource.ObjectMeta.GetOwnerReferences()
// If an owner exists then set the name to the workload.
// This allows us to handle CRDs creating Workloads or DeploymentConfigs in OpenShift.
owners := workload.ObjectMeta.GetOwnerReferences()
lastKey := ""
for len(owners) > 0 {
if len(owners) > 1 {
@@ -133,12 +122,12 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
if firstOwner.Kind == "Node" {
break
}
topKind = firstOwner.Kind
key := fmt.Sprintf("%s/%s/%s", firstOwner.Kind, topMeta.GetNamespace(), firstOwner.Name)
workload.Kind = firstOwner.Kind
key := fmt.Sprintf("%s/%s/%s", firstOwner.Kind, workload.ObjectMeta.GetNamespace(), firstOwner.Name)
lastKey = key
abstractObject, ok := objectCache[key]
if !ok {
err := cacheAllObjectsOfKind(ctx, firstOwner.APIVersion, firstOwner.Kind, dynamicClient, restMapper, objectCache)
err = cacheAllObjectsOfKind(ctx, firstOwner.APIVersion, firstOwner.Kind, dynamicClient, restMapper, objectCache)
if err != nil {
logrus.Warnf("Error caching objects of Kind %s %v", firstOwner.Kind, err)
break
@@ -153,22 +142,26 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
objMeta, err := meta.Accessor(&abstractObject)
if err != nil {
logrus.Warnf("Error retrieving parent metadata %s of API %s and Kind %s because of error: %v ", firstOwner.Name, firstOwner.APIVersion, firstOwner.Kind, err)
return GenericResource{}, err
return workload, err
}
topMeta = objMeta
workload.ObjectMeta = objMeta
owners = abstractObject.GetOwnerReferences()
}
if lastKey != "" {
unst := objectCache[lastKey]
return NewGenericResourceFromUnstructured(&unst)
bytes, err := json.Marshal(&unst)
if err != nil {
return workload, err
}
workload.OriginalObjectJSON = bytes
} else {
bytes, err := json.Marshal(podResource)
if err != nil {
return workload, err
}
workload.OriginalObjectJSON = bytes
}
workload, err := NewGenericResourceFromPod(podResource, podResource)
if err != nil {
return workload, err
}
workload.Kind = topKind
workload.ObjectMeta = topMeta
return workload, nil
}
@@ -214,3 +207,37 @@ func GetPodSpec(yaml map[string]interface{}) interface{} {
}
return nil
}
// GetWorkloadFromBytes parses a GenericWorkload
func GetWorkloadFromBytes(contentBytes []byte) (*GenericWorkload, error) {
yamlNode := make(map[string]interface{})
err := yaml.Unmarshal(contentBytes, &yamlNode)
if err != nil {
logrus.Errorf("Invalid YAML: %s", string(contentBytes))
return nil, err
}
finalDoc := make(map[string]interface{})
finalDoc["metadata"] = yamlNode["metadata"]
finalDoc["apiVersion"] = "v1"
finalDoc["kind"] = "Pod"
podSpec := GetPodSpec(yamlNode)
if podSpec == nil {
return nil, nil
}
finalDoc["spec"] = podSpec
marshaledYaml, err := yaml.Marshal(finalDoc)
if err != nil {
logrus.Errorf("Could not marshal yaml: %v", err)
return nil, err
}
decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(marshaledYaml), 1000)
pod := kubeAPICoreV1.Pod{}
err = decoder.Decode(&pod)
newController, err := NewGenericWorkloadFromPod(pod, yamlNode)
if err != nil {
return nil, err
}
newController.Kind = yamlNode["kind"].(string)
return &newController, nil
}
-89
View File
@@ -1,89 +0,0 @@
// Copyright 2019 FairwindsOps Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package validator
import (
"encoding/json"
"testing"
conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
"github.com/stretchr/testify/assert"
network "k8s.io/api/networking/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func TestValidatePDB(t *testing.T) {
c := conf.Configuration{
Checks: map[string]conf.Severity{
"pdbDisruptionsAllowedGreaterThanZero": conf.SeverityWarning,
},
}
pdb := unstructured.Unstructured{}
res, err := kube.NewGenericResourceFromUnstructured(&pdb)
res.Kind = "PodDisruptionBudget"
actualResult, err := applyNonControllerSchemaChecks(&c, res)
if err != nil {
panic(err)
}
results := actualResult.Results["pdbDisruptionsAllowedGreaterThanZero"]
assert.False(t, results.Success)
assert.Equal(t, conf.SeverityWarning, results.Severity)
assert.Equal(t, "Reliability", results.Category)
assert.EqualValues(t, "disruptionsAllowed is not greater than zero", results.Message)
}
func TestValidateIngress(t *testing.T) {
c := conf.Configuration{
Checks: map[string]conf.Severity{
"tlsSettingsMissing": conf.SeverityWarning,
},
}
tls := network.IngressTLS{
Hosts: []string{"test"},
SecretName: "secret",
}
ingress := network.Ingress{}
ingress.Spec.TLS = []network.IngressTLS{tls}
b, err := json.Marshal(ingress)
if err != nil {
panic(err)
}
unst := unstructured.Unstructured{}
err = json.Unmarshal(b, &unst.Object)
if err != nil {
panic(err)
}
res, err := kube.NewGenericResourceFromUnstructured(&unst)
if err != nil {
panic(err)
}
res.Kind = "Ingress"
actualResult, err := applyNonControllerSchemaChecks(&c, res)
if err != nil {
panic(err)
}
results := actualResult.Results["tlsSettingsMissing"]
assert.True(t, results.Success)
assert.Equal(t, conf.SeverityWarning, results.Severity)
assert.Equal(t, "Security", results.Category)
assert.EqualValues(t, "Ingress has TLS configured", results.Message)
}
+58
View File
@@ -0,0 +1,58 @@
// Copyright 2019 FairwindsOps Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package validator
import (
"github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
corev1 "k8s.io/api/core/v1"
)
// ValidateContainer validates a single container from a given controller
func ValidateContainer(conf *config.Configuration, controller kube.GenericWorkload, container *corev1.Container, isInit bool) (ContainerResult, error) {
results, err := applyContainerSchemaChecks(conf, controller, container, isInit)
if err != nil {
return ContainerResult{}, err
}
cRes := ContainerResult{
Name: container.Name,
Results: results,
}
return cRes, nil
}
// ValidateAllContainers validates both init and regular containers
func ValidateAllContainers(conf *config.Configuration, controller kube.GenericWorkload) ([]ContainerResult, error) {
results := []ContainerResult{}
pod := controller.PodSpec
for _, container := range pod.InitContainers {
result, err := ValidateContainer(conf, controller, &container, true)
if err != nil {
return nil, err
}
results = append(results, result)
}
for _, container := range pod.Containers {
result, err := ValidateContainer(conf, controller, &container, false)
if err != nil {
return nil, err
}
results = append(results, result)
}
return results, nil
}
+6 -6
View File
@@ -50,8 +50,8 @@ exemptions:
- foo
`
func getEmptyWorkload(t *testing.T, name string) kube.GenericResource {
workload, err := kube.NewGenericResourceFromPod(corev1.Pod{
func getEmptyWorkload(t *testing.T, name string) kube.GenericWorkload {
workload, err := kube.NewGenericWorkloadFromPod(corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
@@ -64,7 +64,7 @@ func testValidate(t *testing.T, container *corev1.Container, resourceConf *strin
testValidateWithWorkload(t, container, resourceConf, getEmptyWorkload(t, controllerName), expectedDangers, expectedWarnings, expectedSuccesses)
}
func testValidateWithWorkload(t *testing.T, container *corev1.Container, resourceConf *string, workload kube.GenericResource, expectedDangers []ResultMessage, expectedWarnings []ResultMessage, expectedSuccesses []ResultMessage) {
func testValidateWithWorkload(t *testing.T, container *corev1.Container, resourceConf *string, workload kube.GenericWorkload, expectedDangers []ResultMessage, expectedWarnings []ResultMessage, expectedSuccesses []ResultMessage) {
parsedConf, err := conf.Parse([]byte(*resourceConf))
assert.NoError(t, err, "Expected no error when parsing config")
@@ -921,7 +921,7 @@ func TestValidateSecurity(t *testing.T) {
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
workload, err := kube.NewGenericResourceFromPod(corev1.Pod{Spec: *tt.pod}, nil)
workload, err := kube.NewGenericWorkloadFromPod(corev1.Pod{Spec: *tt.pod}, nil)
assert.NoError(t, err)
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.securityConf}, workload, tt.container, false)
if err != nil {
@@ -1066,7 +1066,7 @@ func TestValidateRunAsRoot(t *testing.T) {
}
for idx, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
workload, err := kube.NewGenericResourceFromPod(corev1.Pod{Spec: *tt.pod}, nil)
workload, err := kube.NewGenericWorkloadFromPod(corev1.Pod{Spec: *tt.pod}, nil)
assert.NoError(t, err)
results, err := applyContainerSchemaChecks(&config, workload, tt.container, false)
if err != nil {
@@ -1168,7 +1168,7 @@ func TestValidateResourcesEmptyContainerCPURequestsExempt(t *testing.T) {
expectedSuccesses := []ResultMessage{}
workload, err := kube.NewGenericResourceFromPod(corev1.Pod{
workload, err := kube.NewGenericWorkloadFromPod(corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Annotations: map[string]string{
+64
View File
@@ -0,0 +1,64 @@
// Copyright 2019 FairwindsOps Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package validator
import (
"github.com/sirupsen/logrus"
conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
)
// ValidateController validates a single controller, returns a Result.
func ValidateController(conf *conf.Configuration, controller kube.GenericWorkload) (Result, error) {
podResult, err := ValidatePod(conf, controller)
if err != nil {
return Result{}, err
}
var controllerResult ResultSet
controllerResult, err = applyControllerSchemaChecks(conf, controller)
if err != nil {
return Result{}, err
}
result := Result{
Kind: controller.Kind,
Name: controller.ObjectMeta.GetName(),
Namespace: controller.ObjectMeta.GetNamespace(),
Results: controllerResult,
PodResult: &podResult,
}
return result, nil
}
// ValidateControllers validates that each deployment conforms to the Polaris config,
// builds a list of ResourceResults organized by namespace.
func ValidateControllers(config *conf.Configuration, kubeResources *kube.ResourceProvider) ([]Result, error) {
controllersToAudit := kubeResources.Controllers
results := []Result{}
for _, controller := range controllersToAudit {
result, err := ValidateController(config, controller)
if err != nil {
logrus.Warn("An error occurred validating controller:", err)
return nil, err
}
results = append(results, result)
}
return results, nil
}
+41 -37
View File
@@ -34,7 +34,7 @@ func TestValidateController(t *testing.T) {
"hostPIDSet": conf.SeverityDanger,
},
}
deployment, err := kube.NewGenericResourceFromPod(test.MockPod(), nil)
deployment, err := kube.NewGenericWorkloadFromPod(test.MockPod(), nil)
assert.NoError(t, err)
deployment.Kind = "Deployment"
expectedSum := CountSummary{
@@ -49,7 +49,7 @@ func TestValidateController(t *testing.T) {
}
var actualResult Result
actualResult, err = applyControllerSchemaChecks(&c, deployment)
actualResult, err = ValidateController(&c, deployment)
if err != nil {
panic(err)
}
@@ -72,31 +72,33 @@ func TestControllerLevelChecks(t *testing.T) {
Severity: "danger",
Category: "Reliability",
}
for _, controller := range res.Resources["Deployment"] {
actualResult, err := applyControllerSchemaChecks(&c, controller)
if err != nil {
panic(err)
}
if controller.ObjectMeta.GetName() == "test-deployment-2" {
expectedResult.Success = true
expectedResult.Message = "Multiple replicas are scheduled"
} else if controller.ObjectMeta.GetName() == "test-deployment" {
expectedResult.Success = false
expectedResult.Message = "Only one replica is scheduled"
}
expectedResults := ResultSet{
"multipleReplicasForDeployment": expectedResult,
}
for _, controller := range res.Controllers {
if controller.Kind == "Deployment" {
actualResult, err := ValidateController(&c, controller)
if err != nil {
panic(err)
}
if controller.ObjectMeta.GetName() == "test-deployment-2" {
expectedResult.Success = true
expectedResult.Message = "Multiple replicas are scheduled"
} else if controller.ObjectMeta.GetName() == "test-deployment" {
expectedResult.Success = false
expectedResult.Message = "Only one replica is scheduled"
}
expectedResults := ResultSet{
"multipleReplicasForDeployment": expectedResult,
}
assert.Equal(t, "Deployment", actualResult.Kind)
assert.Equal(t, 1, len(actualResult.Results), "should be equal")
assert.EqualValues(t, expectedResults, actualResult.Results, controller.ObjectMeta.GetName())
assert.Equal(t, "Deployment", actualResult.Kind)
assert.Equal(t, 1, len(actualResult.Results), "should be equal")
assert.EqualValues(t, expectedResults, actualResult.Results, controller.ObjectMeta.GetName())
}
}
}
res, err := kube.CreateResourceProviderFromPath("../kube/test_files/test_1")
assert.Equal(t, nil, err, "Error should be nil")
assert.Equal(t, 11, res.Resources.GetLength())
assert.Equal(t, 9, len(res.Controllers), "Should have eight controllers")
testResources(res)
replicaSpec := map[string]interface{}{"replicas": 2}
@@ -109,9 +111,9 @@ func TestControllerLevelChecks(t *testing.T) {
two := int32(2)
d2.Spec.Replicas = &two
k8s, dynamicClient := test.SetupTestAPI(&d1, &p1, &d2, &p2)
res, err = kube.CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicClient, conf.Configuration{})
res, err = kube.CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicClient)
assert.Equal(t, err, nil, "error should be nil")
assert.Equal(t, 2, res.Resources.GetLength(), "Should have two controllers")
assert.Equal(t, 2, len(res.Controllers), "Should have two controllers")
testResources(res)
}
@@ -124,7 +126,7 @@ func TestSkipHealthChecks(t *testing.T) {
}
pod := test.MockPod()
pod.Spec.InitContainers = []corev1.Container{test.MockContainer("test")}
deployment, err := kube.NewGenericResourceFromPod(pod, nil)
deployment, err := kube.NewGenericWorkloadFromPod(pod, nil)
assert.NoError(t, err)
deployment.Kind = "Deployment"
expectedSum := CountSummary{
@@ -137,7 +139,7 @@ func TestSkipHealthChecks(t *testing.T) {
"livenessProbeMissing": {ID: "livenessProbeMissing", Message: "Liveness probe should be configured", Success: false, Severity: "warning", Category: "Reliability"},
}
var actualResult Result
actualResult, err = applyControllerSchemaChecks(&c, deployment)
actualResult, err = ValidateController(&c, deployment)
if err != nil {
panic(err)
}
@@ -147,7 +149,7 @@ func TestSkipHealthChecks(t *testing.T) {
assert.EqualValues(t, ResultSet{}, actualResult.PodResult.ContainerResults[0].Results)
assert.EqualValues(t, expectedResults, actualResult.PodResult.ContainerResults[1].Results)
job, err := kube.NewGenericResourceFromPod(test.MockPod(), nil)
job, err := kube.NewGenericWorkloadFromPod(test.MockPod(), nil)
assert.NoError(t, err)
job.Kind = "Job"
expectedSum = CountSummary{
@@ -156,7 +158,7 @@ func TestSkipHealthChecks(t *testing.T) {
Dangers: uint(0),
}
expectedResults = ResultSet{}
actualResult, err = applyControllerSchemaChecks(&c, job)
actualResult, err = ValidateController(&c, job)
if err != nil {
panic(err)
}
@@ -165,7 +167,7 @@ func TestSkipHealthChecks(t *testing.T) {
assert.EqualValues(t, expectedSum, actualResult.GetSummary())
assert.EqualValues(t, expectedResults, actualResult.PodResult.ContainerResults[0].Results)
cronjob, err := kube.NewGenericResourceFromPod(test.MockPod(), nil)
cronjob, err := kube.NewGenericWorkloadFromPod(test.MockPod(), nil)
assert.NoError(t, err)
cronjob.Kind = "CronJob"
expectedSum = CountSummary{
@@ -174,7 +176,7 @@ func TestSkipHealthChecks(t *testing.T) {
Dangers: uint(0),
}
expectedResults = ResultSet{}
actualResult, err = applyControllerSchemaChecks(&c, cronjob)
actualResult, err = ValidateController(&c, cronjob)
if err != nil {
panic(err)
}
@@ -201,16 +203,18 @@ func TestControllerExemptions(t *testing.T) {
Warnings: uint(0),
Dangers: uint(0),
}
var actualResults []Result
pod := test.MockPod()
pod.ObjectMeta.Namespace = "foo"
workload, err := kube.NewGenericResourceFromPod(pod, nil)
workload, err := kube.NewGenericWorkloadFromPod(pod, nil)
assert.NoError(t, err)
workload.Kind = "Deployment"
resources := []kube.GenericResource{workload}
resources := &kube.ResourceProvider{
Controllers: []kube.GenericWorkload{workload},
}
var actualResults []Result
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, resources)
actualResults, err = ValidateControllers(&c, resources)
if err != nil {
panic(err)
}
@@ -221,7 +225,7 @@ func TestControllerExemptions(t *testing.T) {
c.Exemptions = []conf.Exemption{{
Namespace: "foo",
}}
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, resources)
actualResults, err = ValidateControllers(&c, resources)
if err != nil {
panic(err)
}
@@ -230,10 +234,10 @@ func TestControllerExemptions(t *testing.T) {
assert.EqualValues(t, expectedExemptSum, actualResults[0].GetSummary())
c.Exemptions = nil
resources[0].ObjectMeta.SetAnnotations(map[string]string{
resources.Controllers[0].ObjectMeta.SetAnnotations(map[string]string{
exemptionAnnotationKey: "true",
})
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, resources)
actualResults, err = ValidateControllers(&c, resources)
if err != nil {
panic(err)
}
@@ -242,7 +246,7 @@ func TestControllerExemptions(t *testing.T) {
assert.EqualValues(t, expectedExemptSum, actualResults[0].GetSummary())
c.DisallowExemptions = true
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, resources)
actualResults, err = ValidateControllers(&c, resources)
if err != nil {
panic(err)
}
+12 -8
View File
@@ -22,14 +22,17 @@ func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider, o
displayName = kubeResources.SourceName
}
results := []Result{}
for _, resources := range kubeResources.Resources {
kindResults, err := ApplyAllSchemaChecksToAllResources(&config, resources)
if err != nil {
return AuditData{}, err
}
results = append(results, kindResults...)
results, err := ValidateControllers(&config, kubeResources)
if err != nil {
return AuditData{}, err
}
controllerCount := len(results)
ingressResults, err := ValidateIngresses(&config, kubeResources)
if err != nil {
return AuditData{}, err
}
results = append(results, ingressResults...)
auditData := AuditData{
PolarisOutputVersion: PolarisOutputVersion,
@@ -40,8 +43,9 @@ func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider, o
ClusterInfo: ClusterInfo{
Version: kubeResources.ServerVersion,
Nodes: len(kubeResources.Nodes),
Pods: len(kubeResources.Controllers), // TODO validate that this is still valuable
Namespaces: len(kubeResources.Namespaces),
Controllers: kubeResources.Resources.GetNumberOfControllers(),
Controllers: controllerCount,
},
Results: results,
}
+7 -8
View File
@@ -11,6 +11,11 @@ import (
)
func TestGetTemplateData(t *testing.T) {
k8s, dynamicClient := test.SetupTestAPI(test.GetMockControllers("test")...)
resources, err := kube.CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicClient)
assert.Equal(t, err, nil, "error should be nil")
assert.Equal(t, 5, len(resources.Controllers))
c := conf.Configuration{
Checks: map[string]conf.Severity{
"readinessProbeMissing": conf.SeverityDanger,
@@ -18,11 +23,6 @@ func TestGetTemplateData(t *testing.T) {
},
}
k8s, dynamicClient := test.SetupTestAPI(test.GetMockControllers("test")...)
resources, err := kube.CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicClient, c)
assert.Equal(t, err, nil, "error should be nil")
assert.Equal(t, 5, len(resources.Resources))
sum := CountSummary{
Successes: uint(0),
Warnings: uint(3),
@@ -57,9 +57,8 @@ func TestGetTemplateData(t *testing.T) {
continue
}
found = true
if assert.Equal(t, 1, len(result.PodResult.ContainerResults), "bad container results for "+result.Kind) {
assert.Equal(t, expected.results, len(result.PodResult.ContainerResults[0].Results))
}
assert.Equal(t, 1, len(result.PodResult.ContainerResults))
assert.Equal(t, expected.results, len(result.PodResult.ContainerResults[0].Results))
}
assert.Equal(t, found, true)
}
+51
View File
@@ -0,0 +1,51 @@
// Copyright 2019 FairwindsOps Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package validator
import (
conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
"k8s.io/api/extensions/v1beta1"
)
// ValidateIngresses validates all the ingresses in a ResourceProvider
func ValidateIngresses(config *conf.Configuration, kubeResources *kube.ResourceProvider) ([]Result, error) {
var results []Result
for _, ingress := range kubeResources.Ingresses {
result, err := ValidateIngress(config, ingress)
if err != nil {
return []Result{}, err
}
results = append(results, result)
}
return results, nil
}
// ValidateIngress validates a single ingress
func ValidateIngress(config *conf.Configuration, ingress v1beta1.Ingress) (Result, error) {
results, err := applyIngressSchemaChecks(config, ingress)
if err != nil {
return Result{}, err
}
result := Result{
Kind: "Ingress",
Name: ingress.ObjectMeta.GetName(),
Namespace: ingress.ObjectMeta.GetNamespace(),
Results: results,
}
return result, nil
}
+64
View File
@@ -0,0 +1,64 @@
// Copyright 2019 FairwindsOps Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package validator
import (
"testing"
conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/test"
"github.com/stretchr/testify/assert"
extv1beta1 "k8s.io/api/extensions/v1beta1"
)
func TestValidateIngress(t *testing.T) {
c := conf.Configuration{
Checks: map[string]conf.Severity{
"tlsSettingsMissing": conf.SeverityWarning,
},
}
ingress := test.MockIngress()
var actualResult Result
actualResult, err := ValidateIngress(&c, ingress)
if err != nil {
panic(err)
}
results := actualResult.Results["tlsSettingsMissing"]
assert.False(t, results.Success)
assert.Equal(t, conf.Severity("warning"), results.Severity)
assert.Equal(t, "Security", results.Category)
assert.EqualValues(t, "Ingress does not have TLS configured", results.Message)
tls := extv1beta1.IngressTLS{
Hosts: []string{"test"},
SecretName: "secret",
}
ingress.Spec.TLS = []extv1beta1.IngressTLS{tls}
actualResult, err = ValidateIngress(&c, ingress)
if err != nil {
panic(err)
}
results = actualResult.Results["tlsSettingsMissing"]
assert.True(t, results.Success)
assert.Equal(t, conf.Severity("warning"), results.Severity)
assert.Equal(t, "Security", results.Category)
assert.EqualValues(t, "Ingress has TLS configured", results.Message)
}
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2019 FairwindsOps Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package validator
import (
"github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
)
// ValidatePod validates that each pod conforms to the Polaris config, returns a ResourceResult.
func ValidatePod(conf *config.Configuration, controller kube.GenericWorkload) (PodResult, error) {
podResults, err := applyPodSchemaChecks(conf, controller)
if err != nil {
return PodResult{}, err
}
pRes := PodResult{
Results: podResults,
ContainerResults: []ContainerResult{},
}
pRes.ContainerResults, err = ValidateAllContainers(conf, controller)
if err != nil {
return pRes, err
}
return pRes, nil
}
+25 -20
View File
@@ -36,7 +36,7 @@ func TestValidatePod(t *testing.T) {
}
p := test.MockPod()
deployment, err := kube.NewGenericResourceFromPod(p, nil)
deployment, err := kube.NewGenericWorkloadFromPod(p, nil)
assert.NoError(t, err)
expectedSum := CountSummary{
Successes: uint(4),
@@ -50,14 +50,15 @@ func TestValidatePod(t *testing.T) {
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
}
actualPodResult, err := applyControllerSchemaChecks(&c, deployment)
var actualPodResult PodResult
actualPodResult, err = ValidatePod(&c, deployment)
if err != nil {
panic(err)
}
assert.Equal(t, 1, len(actualPodResult.PodResult.ContainerResults), "should be equal")
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
assert.EqualValues(t, expectedResults, actualPodResult.PodResult.Results)
assert.EqualValues(t, expectedResults, actualPodResult.Results)
}
func TestInvalidIPCPod(t *testing.T) {
@@ -72,7 +73,7 @@ func TestInvalidIPCPod(t *testing.T) {
p := test.MockPod()
p.Spec.HostIPC = true
workload, err := kube.NewGenericResourceFromPod(p, nil)
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
assert.NoError(t, err)
expectedSum := CountSummary{
Successes: uint(3),
@@ -85,14 +86,15 @@ func TestInvalidIPCPod(t *testing.T) {
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
}
actualPodResult, err := applyControllerSchemaChecks(&c, workload)
var actualPodResult PodResult
actualPodResult, err = ValidatePod(&c, workload)
if err != nil {
panic(err)
}
assert.Equal(t, 1, len(actualPodResult.PodResult.ContainerResults), "should be equal")
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
assert.EqualValues(t, expectedResults, actualPodResult.PodResult.Results)
assert.EqualValues(t, expectedResults, actualPodResult.Results)
}
func TestInvalidNetworkPod(t *testing.T) {
@@ -107,7 +109,7 @@ func TestInvalidNetworkPod(t *testing.T) {
p := test.MockPod()
p.Spec.HostNetwork = true
workload, err := kube.NewGenericResourceFromPod(p, nil)
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
assert.NoError(t, err)
expectedSum := CountSummary{
Successes: uint(3),
@@ -121,14 +123,15 @@ func TestInvalidNetworkPod(t *testing.T) {
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
}
actualPodResult, err := applyControllerSchemaChecks(&c, workload)
var actualPodResult PodResult
actualPodResult, err = ValidatePod(&c, workload)
if err != nil {
panic(err)
}
assert.Equal(t, 1, len(actualPodResult.PodResult.ContainerResults), "should be equal")
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
assert.EqualValues(t, expectedResults, actualPodResult.PodResult.Results)
assert.EqualValues(t, expectedResults, actualPodResult.Results)
}
func TestInvalidPIDPod(t *testing.T) {
@@ -143,7 +146,7 @@ func TestInvalidPIDPod(t *testing.T) {
p := test.MockPod()
p.Spec.HostPID = true
workload, err := kube.NewGenericResourceFromPod(p, nil)
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
assert.NoError(t, err)
expectedSum := CountSummary{
Successes: uint(3),
@@ -157,14 +160,15 @@ func TestInvalidPIDPod(t *testing.T) {
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Success: true, Severity: "warning", Category: "Security"},
}
actualPodResult, err := applyControllerSchemaChecks(&c, workload)
var actualPodResult PodResult
actualPodResult, err = ValidatePod(&c, workload)
if err != nil {
panic(err)
}
assert.Equal(t, 1, len(actualPodResult.PodResult.ContainerResults), "should be equal")
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
assert.EqualValues(t, expectedResults, actualPodResult.PodResult.Results)
assert.EqualValues(t, expectedResults, actualPodResult.Results)
}
func TestExemption(t *testing.T) {
@@ -188,7 +192,7 @@ func TestExemption(t *testing.T) {
p.ObjectMeta = metav1.ObjectMeta{
Name: "foo",
}
workload, err := kube.NewGenericResourceFromPod(p, nil)
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
assert.NoError(t, err)
expectedSum := CountSummary{
Successes: uint(3),
@@ -200,12 +204,13 @@ func TestExemption(t *testing.T) {
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
}
actualPodResult, err := applyControllerSchemaChecks(&c, workload)
var actualPodResult PodResult
actualPodResult, err = ValidatePod(&c, workload)
if err != nil {
panic(err)
}
assert.Equal(t, 1, len(actualPodResult.PodResult.ContainerResults), "should be equal")
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
assert.EqualValues(t, expectedResults, actualPodResult.PodResult.Results)
assert.EqualValues(t, expectedResults, actualPodResult.Results)
}
+87 -156
View File
@@ -9,6 +9,7 @@ import (
"github.com/gobuffalo/packr/v2"
corev1 "k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/yaml"
@@ -45,19 +46,11 @@ var (
"dangerousCapabilities",
"insecureCapabilities",
"priorityClassNotSet",
// Other checks
// Ingress checks
"tlsSettingsMissing",
"pdbDisruptionsAllowedGreaterThanZero",
}
)
type schemaTestCase struct {
Target config.TargetKind
Resource kube.GenericResource
IsInitContianer bool
Container *corev1.Container
}
func init() {
schemaBox = packr.New("Schemas", "../../checks")
for _, checkID := range checkOrder {
@@ -88,10 +81,7 @@ func parseCheck(rawBytes []byte) (config.SchemaCheck, error) {
}
}
func resolveCheck(conf *config.Configuration, checkID string, test schemaTestCase) (*config.SchemaCheck, error) {
if !conf.DisallowExemptions && hasExemptionAnnotation(test.Resource.ObjectMeta, checkID) {
return nil, nil
}
func resolveCheck(conf *config.Configuration, checkID, kind string, target config.TargetKind, meta metaV1.Object, containerName string, isInitContainer bool) (*config.SchemaCheck, error) {
check, ok := conf.CustomChecks[checkID]
if !ok {
check, ok = builtInChecks[checkID]
@@ -100,14 +90,12 @@ func resolveCheck(conf *config.Configuration, checkID string, test schemaTestCas
return nil, fmt.Errorf("Check %s not found", checkID)
}
containerName := ""
if test.Container != nil {
containerName = test.Container.Name
}
if !conf.IsActionable(check.ID, test.Resource.ObjectMeta, containerName) {
namespace := meta.GetNamespace()
name := meta.GetName()
if !conf.IsActionable(check.ID, namespace, name, containerName) {
return nil, nil
}
if !check.IsActionable(test.Target, test.Resource.Kind, test.IsInitContianer) {
if !check.IsActionable(target, kind, isInitContainer) {
return nil, nil
}
return &check, nil
@@ -131,8 +119,8 @@ func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bo
const exemptionAnnotationKey = "polaris.fairwinds.com/exempt"
const exemptionAnnotationPattern = "polaris.fairwinds.com/%s-exempt"
func hasExemptionAnnotation(objMeta metaV1.Object, checkID string) bool {
annot := objMeta.GetAnnotations()
func hasExemptionAnnotation(ctrl kube.GenericWorkload, checkID string) bool {
annot := ctrl.ObjectMeta.GetAnnotations()
val := annot[exemptionAnnotationKey]
if strings.ToLower(val) == "true" {
return true
@@ -145,158 +133,101 @@ func hasExemptionAnnotation(objMeta metaV1.Object, checkID string) bool {
return false
}
// ApplyAllSchemaChecksToAllResources applies available checks to a list of resources
func ApplyAllSchemaChecksToAllResources(conf *config.Configuration, resources []kube.GenericResource) ([]Result, error) {
results := []Result{}
for _, resource := range resources {
result, err := ApplyAllSchemaChecks(conf, resource)
if err != nil {
return results, err
}
results = append(results, result)
}
return results, nil
}
// ApplyAllSchemaChecks applies available checks to a single resource
func ApplyAllSchemaChecks(conf *config.Configuration, resource kube.GenericResource) (Result, error) {
if resource.PodSpec == nil {
return applyNonControllerSchemaChecks(conf, resource)
}
return applyControllerSchemaChecks(conf, resource)
}
func applyNonControllerSchemaChecks(conf *config.Configuration, resource kube.GenericResource) (Result, error) {
finalResult := Result{
Kind: resource.Kind,
Name: resource.ObjectMeta.GetName(),
Namespace: resource.ObjectMeta.GetNamespace(),
}
resultSet, err := applyTopLevelSchemaChecks(conf, resource, false)
finalResult.Results = resultSet
return finalResult, err
}
func applyControllerSchemaChecks(conf *config.Configuration, resource kube.GenericResource) (Result, error) {
finalResult := Result{
Kind: resource.Kind,
Name: resource.ObjectMeta.GetName(),
Namespace: resource.ObjectMeta.GetNamespace(),
}
resultSet, err := applyTopLevelSchemaChecks(conf, resource, true)
if err != nil {
return finalResult, err
}
finalResult.Results = resultSet
podRS, err := applyPodSchemaChecks(conf, resource)
if err != nil {
return finalResult, err
}
podRes := PodResult{
Results: podRS,
ContainerResults: []ContainerResult{},
}
finalResult.PodResult = &podRes
for _, container := range resource.PodSpec.InitContainers {
results, err := applyContainerSchemaChecks(conf, resource, &container, true)
if err != nil {
return finalResult, err
}
cRes := ContainerResult{
Name: container.Name,
Results: results,
}
podRes.ContainerResults = append(podRes.ContainerResults, cRes)
}
for _, container := range resource.PodSpec.Containers {
results, err := applyContainerSchemaChecks(conf, resource, &container, false)
if err != nil {
return finalResult, err
}
cRes := ContainerResult{
Name: container.Name,
Results: results,
}
podRes.ContainerResults = append(podRes.ContainerResults, cRes)
}
return finalResult, nil
}
func applyTopLevelSchemaChecks(conf *config.Configuration, res kube.GenericResource, isController bool) (ResultSet, error) {
test := schemaTestCase{
Resource: res,
}
if isController {
test.Target = config.TargetController
}
return applySchemaChecks(conf, test)
}
func applyPodSchemaChecks(conf *config.Configuration, controller kube.GenericResource) (ResultSet, error) {
test := schemaTestCase{
Target: config.TargetPod,
Resource: controller,
}
return applySchemaChecks(conf, test)
}
func applyContainerSchemaChecks(conf *config.Configuration, controller kube.GenericResource, container *corev1.Container, isInit bool) (ResultSet, error) {
test := schemaTestCase{
Target: config.TargetContainer,
Resource: controller,
Container: container,
IsInitContianer: isInit,
}
return applySchemaChecks(conf, test)
}
func applySchemaChecks(conf *config.Configuration, test schemaTestCase) (ResultSet, error) {
func applyPodSchemaChecks(conf *config.Configuration, controller kube.GenericWorkload) (ResultSet, error) {
results := ResultSet{}
checkIDs := getSortedKeys(conf.Checks)
for _, checkID := range checkIDs {
result, err := applySchemaCheck(conf, checkID, test)
if !conf.DisallowExemptions && hasExemptionAnnotation(controller, checkID) {
continue
}
check, err := resolveCheck(conf, checkID, controller.Kind, config.TargetPod, controller.ObjectMeta, "", false)
if err != nil {
return results, err
return nil, err
} else if check == nil {
continue
}
if result != nil {
results[checkID] = *result
passes, err := check.CheckPod(&controller.PodSpec)
if err != nil {
return nil, err
}
results[check.ID] = makeResult(conf, check, passes)
}
return results, nil
}
func applySchemaCheck(conf *config.Configuration, checkID string, test schemaTestCase) (*ResultMessage, error) {
check, err := resolveCheck(conf, checkID, test)
if err != nil {
return nil, err
} else if check == nil {
return nil, nil
func applyControllerSchemaChecks(conf *config.Configuration, controller kube.GenericWorkload) (ResultSet, error) {
results := ResultSet{}
checkIDs := getSortedKeys(conf.Checks)
for _, checkID := range checkIDs {
if !conf.DisallowExemptions && hasExemptionAnnotation(controller, checkID) {
continue
}
check, err := resolveCheck(conf, checkID, controller.Kind, config.TargetController, controller.ObjectMeta, "", false)
if err != nil {
return nil, err
} else if check == nil {
continue
}
passes, err := check.CheckController(controller.OriginalObjectJSON)
if err != nil {
return nil, err
}
results[check.ID] = makeResult(conf, check, passes)
}
var passes bool
if check.SchemaTarget != "" {
if check.SchemaTarget == config.TargetPod && check.Target == config.TargetContainer {
podCopy := *test.Resource.PodSpec
return results, nil
}
func applyContainerSchemaChecks(conf *config.Configuration, controller kube.GenericWorkload, container *corev1.Container, isInit bool) (ResultSet, error) {
results := ResultSet{}
checkIDs := getSortedKeys(conf.Checks)
for _, checkID := range checkIDs {
if !conf.DisallowExemptions && hasExemptionAnnotation(controller, checkID) {
continue
}
check, err := resolveCheck(conf, checkID, controller.Kind, config.TargetContainer, controller.ObjectMeta, container.Name, isInit)
if err != nil {
return nil, err
} else if check == nil {
continue
}
var passes bool
if check.SchemaTarget == config.TargetPod {
podCopy := controller.PodSpec
podCopy.InitContainers = []corev1.Container{}
podCopy.Containers = []corev1.Container{*test.Container}
podCopy.Containers = []corev1.Container{*container}
passes, err = check.CheckPod(&podCopy)
} else {
return nil, fmt.Errorf("Unknown combination of target (%s) and schema target (%s)", check.Target, check.SchemaTarget)
passes, err = check.CheckContainer(container)
}
} else if check.Target == config.TargetPod {
passes, err = check.CheckPod(test.Resource.PodSpec)
} else if check.Target == config.TargetContainer {
passes, err = check.CheckContainer(test.Container)
} else {
passes, err = check.CheckObject(test.Resource.Resource.Object)
if err != nil {
return nil, err
}
results[check.ID] = makeResult(conf, check, passes)
}
if err != nil {
return nil, err
return results, nil
}
func applyIngressSchemaChecks(conf *config.Configuration, ingress v1beta1.Ingress) (ResultSet, error) {
results := ResultSet{}
checkIDs := getSortedKeys(conf.Checks)
for _, checkID := range checkIDs {
check, err := resolveCheck(conf, checkID, ingress.Kind, config.TargetIngress, ingress.ObjectMeta.GetObjectMeta(), "", false)
if err != nil {
return nil, err
} else if check == nil {
continue
}
passes, err := check.CheckObject(ingress)
if err != nil {
return nil, err
}
results[check.ID] = makeResult(conf, check, passes)
}
result := makeResult(conf, check, passes)
return &result, nil
return results, nil
}
func getSortedKeys(m map[string]config.Severity) []string {
+2 -2
View File
@@ -99,13 +99,13 @@ func (v *Validator) handleInternal(req admission.Request) (*validator.PodResult,
} else {
pod, originalObject, err = GetObjectFromRawRequest(req.Object.Raw)
}
controller, err := kube.NewGenericResourceFromPod(pod, originalObject)
controller, err := kube.NewGenericWorkloadFromPod(pod, originalObject)
if err != nil {
return nil, err
}
controller.Kind = req.AdmissionRequest.Kind.Kind
var controllerResult validator.Result
controllerResult, err = validator.ApplyAllSchemaChecks(&v.Config, controller)
controllerResult, err = validator.ValidateController(&v.Config, controller)
if err != nil {
return nil, err
}
@@ -1,17 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minimal-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /testpath
pathType: Prefix
backend:
service:
name: test
port:
number: 80
@@ -1,21 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minimal-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
tls:
- secretName: example
hosts:
- example.com
rules:
- http:
paths:
- path: /testpath
pathType: Prefix
backend:
service:
name: test
port:
number: 80
+12 -19
View File
@@ -1,7 +1,6 @@
package test
import (
"fmt"
"io/ioutil"
"path/filepath"
"runtime"
@@ -53,28 +52,22 @@ func init() {
func TestChecks(t *testing.T) {
for _, tc := range testCases {
res, err := kube.NewGenericResourceFromBytes(tc.input)
if err != nil {
fmt.Println("error parsing", string(tc.input))
panic(err)
}
workload, err := kube.GetWorkloadFromBytes(tc.input)
assert.NoError(t, err)
c, err := config.Parse([]byte("checks:\n " + tc.check + ": danger"))
assert.NoError(t, err)
result, err := validator.ApplyAllSchemaChecks(&c, res)
var result validator.Result
result, err = validator.ValidateController(&c, *workload)
assert.NoError(t, err)
summary := result.GetSummary()
total := summary.Successes + summary.Dangers
msg := fmt.Sprintf("Check %s ran %d times instead of 1", tc.check, total)
if assert.Equal(t, uint(1), total, msg) {
if tc.failure {
message := "Check " + tc.check + " passed unexpectedly"
assert.Equal(t, uint(0), summary.Successes, message)
assert.Equal(t, uint(1), summary.Dangers, message)
} else {
message := "Check " + tc.check + " failed unexpectedly"
assert.Equal(t, uint(1), summary.Successes, message)
assert.Equal(t, uint(0), summary.Dangers, message)
}
if tc.failure {
message := "Check " + tc.check + " passed unexpectedly"
assert.Equal(t, uint(0), summary.Successes, message)
assert.Equal(t, uint(1), summary.Dangers, message)
} else {
message := "Check " + tc.check + " failed unexpectedly"
assert.Equal(t, uint(1), summary.Successes, message)
assert.Equal(t, uint(0), summary.Dangers, message)
}
}
}
+20 -5
View File
@@ -19,7 +19,7 @@ import (
"k8s.io/client-go/kubernetes/fake"
)
func newUnstructured(apiVersion, kind, namespace, name string, spec map[string]interface{}) unstructured.Unstructured {
func newUnstructured(apiVersion, kind, namespace, name string, spec interface{}) unstructured.Unstructured {
return unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": apiVersion,
@@ -69,7 +69,7 @@ func MockIngress() extv1beta1.Ingress {
}
// MockController creates a mock controller and pod
func MockController(apiVersion, kind, namespace, name string, spec map[string]interface{}, podSpec corev1.PodSpec, dest interface{}) corev1.Pod {
func MockController(apiVersion, kind, namespace, name string, spec interface{}, podSpec corev1.PodSpec, dest interface{}) corev1.Pod {
unst := newUnstructured(apiVersion, kind, namespace, name, spec)
pod := corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
@@ -143,10 +143,25 @@ func MockJob(namespace, name string) (batchv1.Job, corev1.Pod) {
func MockCronJob(namespace, name string) (batchv1beta1.CronJob, corev1.Pod) {
cj := batchv1beta1.CronJob{}
p := MockPod()
spec := map[string]interface{}{}
b, err := json.Marshal(p.Spec)
if err != nil {
panic(err)
}
pSpec := map[string]interface{}{}
err = json.Unmarshal(b, &pSpec)
if err != nil {
panic(err)
}
spec := map[string]interface{}{
"job_template": map[string]interface{}{
"spec": map[string]interface{}{
"template": map[string]interface{}{
"spec": pSpec,
},
},
},
}
pod := MockController("batch/v1beta1", "CronJob", namespace, name, spec, p.Spec, &cj)
cj.Spec.JobTemplate.Spec.Template.Spec = pod.Spec
return cj, pod
}