diff --git a/pkg/dashboard/assets/css/dashboard.css b/pkg/dashboard/assets/css/dashboard.css
index 807f0bff..e915e831 100644
--- a/pkg/dashboard/assets/css/dashboard.css
+++ b/pkg/dashboard/assets/css/dashboard.css
@@ -265,6 +265,11 @@ ul.message-list li i.message-icon {
color: #a11f4c;
}
+.controller-type {
+ display: inline-block;
+ min-width: 115px;
+}
+
a.more-info {
color: #bbb;
font-size: 12px;
diff --git a/pkg/dashboard/dashboard.go b/pkg/dashboard/dashboard.go
index 7553c5b0..7e33c6a8 100644
--- a/pkg/dashboard/dashboard.go
+++ b/pkg/dashboard/dashboard.go
@@ -87,14 +87,15 @@ type templateData struct {
// GetBaseTemplate puts together the dashboard template. Individual pieces can be overridden before rendering.
func GetBaseTemplate(name string) (*template.Template, error) {
tmpl := template.New(name).Funcs(template.FuncMap{
- "getWarningWidth": getWarningWidth,
- "getSuccessWidth": getSuccessWidth,
- "getWeatherIcon": getWeatherIcon,
- "getWeatherText": getWeatherText,
- "getGrade": getGrade,
- "getIcon": getIcon,
- "getCategoryLink": getCategoryLink,
- "getCategoryInfo": getCategoryInfo,
+ "getWarningWidth": getWarningWidth,
+ "getSuccessWidth": getSuccessWidth,
+ "getWeatherIcon": getWeatherIcon,
+ "getWeatherText": getWeatherText,
+ "getGrade": getGrade,
+ "getIcon": getIcon,
+ "getCategoryLink": getCategoryLink,
+ "getCategoryInfo": getCategoryInfo,
+ "getAllControllerResults": getAllControllerResults,
})
templateFileNames := []string{
diff --git a/pkg/dashboard/helpers.go b/pkg/dashboard/helpers.go
index f53c83bc..2165369c 100644
--- a/pkg/dashboard/helpers.go
+++ b/pkg/dashboard/helpers.go
@@ -20,6 +20,13 @@ import (
"strings"
)
+func getAllControllerResults(nr validator.NamespaceResult) []validator.ControllerResult {
+ results := []validator.ControllerResult{}
+ results = append(results, nr.DeploymentResults...)
+ results = append(results, nr.StatefulSetResults...)
+ return results
+}
+
func getWarningWidth(counts validator.CountSummary, fullWidth int) uint {
return uint(float64(counts.Successes+counts.Warnings) / float64(counts.Successes+counts.Warnings+counts.Errors) * float64(fullWidth))
}
diff --git a/pkg/dashboard/templates/dashboard.gohtml b/pkg/dashboard/templates/dashboard.gohtml
index 91585ff6..df87c3a2 100644
--- a/pkg/dashboard/templates/dashboard.gohtml
+++ b/pkg/dashboard/templates/dashboard.gohtml
@@ -78,7 +78,7 @@
Namespace: {{ $namespace }}
- {{ range .DeploymentResults }}
+ {{ range getAllControllerResults $nsResult }}
-
Deployment: {{ .Name }}
+
+ {{ .Type }}:
+ {{ .Name }}
Pod Spec:
diff --git a/pkg/kube/resources.go b/pkg/kube/resources.go
index bfd27bba..0b5d40a9 100644
--- a/pkg/kube/resources.go
+++ b/pkg/kube/resources.go
@@ -27,6 +27,7 @@ type ResourceProvider struct {
SourceType string
Nodes []corev1.Node
Deployments []appsv1.Deployment
+ StatefulSets []appsv1.StatefulSet
Namespaces []corev1.Namespace
Pods []corev1.Pod
}
@@ -51,47 +52,13 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error)
SourceName: directory,
Nodes: []corev1.Node{},
Deployments: []appsv1.Deployment{},
+ StatefulSets: []appsv1.StatefulSet{},
Namespaces: []corev1.Namespace{},
Pods: []corev1.Pod{},
}
addYaml := func(contents string) error {
- contentBytes := []byte(contents)
- decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(contentBytes), 1000)
- resource := k8sResource{}
- err := decoder.Decode(&resource)
- if err != nil {
- // TODO: should we panic if the YAML is bad?
- logrus.Errorf("Invalid YAML: %s", string(contents))
- return nil
- }
- decoder = k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(contentBytes), 1000)
- if resource.Kind == "Deployment" {
- dep := appsv1.Deployment{}
- err = decoder.Decode(&dep)
- if err != nil {
- logrus.Errorf("Error parsing deployment %v", err)
- return err
- }
- resources.Deployments = append(resources.Deployments, dep)
- } else if resource.Kind == "Namespace" {
- ns := corev1.Namespace{}
- err = decoder.Decode(&ns)
- if err != nil {
- logrus.Errorf("Error parsing namespace %v", err)
- return err
- }
- resources.Namespaces = append(resources.Namespaces, ns)
- } else if resource.Kind == "Pod" {
- pod := corev1.Pod{}
- err = decoder.Decode(&pod)
- if err != nil {
- logrus.Errorf("Error parsing pod %v", err)
- return err
- }
- resources.Pods = append(resources.Pods, pod)
- }
- return nil
+ return addResourceFromString(contents, &resources)
}
visitFile := func(path string, f os.FileInfo, err error) error {
@@ -144,27 +111,32 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string
listOpts := metav1.ListOptions{}
serverVersion, err := kube.Discovery().ServerVersion()
if err != nil {
- logrus.Errorf("Error fetching Kubernetes API version %v", err)
+ logrus.Errorf("Error fetching Cluster API version %v", err)
return nil, err
}
deploys, err := kube.AppsV1().Deployments("").List(listOpts)
if err != nil {
- logrus.Errorf("Error fetching Kubernetes Deployments %v", err)
+ logrus.Errorf("Error fetching Deployments %v", err)
+ return nil, err
+ }
+ statefulSets, err := kube.AppsV1().StatefulSets("").List(listOpts)
+ if err != nil {
+ logrus.Errorf("Error fetching StatefulSets%v", err)
return nil, err
}
nodes, err := kube.CoreV1().Nodes().List(listOpts)
if err != nil {
- logrus.Errorf("Error fetching Kubernetes Nodes %v", err)
+ logrus.Errorf("Error fetching Nodes %v", err)
return nil, err
}
namespaces, err := kube.CoreV1().Namespaces().List(listOpts)
if err != nil {
- logrus.Errorf("Error fetching Kubernetes Namespaces %v", err)
+ logrus.Errorf("Error fetching Namespaces %v", err)
return nil, err
}
pods, err := kube.CoreV1().Pods("").List(listOpts)
if err != nil {
- logrus.Errorf("Error fetching Kubernetes Pods %v", err)
+ logrus.Errorf("Error fetching Pods %v", err)
return nil, err
}
@@ -174,9 +146,45 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string
SourceName: clusterName,
CreationTime: time.Now(),
Deployments: deploys.Items,
+ StatefulSets: statefulSets.Items,
Nodes: nodes.Items,
Namespaces: namespaces.Items,
Pods: pods.Items,
}
return &api, nil
}
+
+func addResourceFromString(contents string, resources *ResourceProvider) error {
+ contentBytes := []byte(contents)
+ decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(contentBytes), 1000)
+ resource := k8sResource{}
+ err := decoder.Decode(&resource)
+ if err != nil {
+ // TODO: should we panic if the YAML is bad?
+ logrus.Errorf("Invalid YAML: %s", string(contents))
+ return nil
+ }
+ decoder = k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(contentBytes), 1000)
+ if resource.Kind == "Deployment" {
+ dep := appsv1.Deployment{}
+ err = decoder.Decode(&dep)
+ resources.Deployments = append(resources.Deployments, dep)
+ } else if resource.Kind == "StatefulSet" {
+ dep := appsv1.StatefulSet{}
+ err = decoder.Decode(&dep)
+ resources.StatefulSets = append(resources.StatefulSets, dep)
+ } else if resource.Kind == "Namespace" {
+ ns := corev1.Namespace{}
+ err = decoder.Decode(&ns)
+ resources.Namespaces = append(resources.Namespaces, ns)
+ } else if resource.Kind == "Pod" {
+ pod := corev1.Pod{}
+ err = decoder.Decode(&pod)
+ resources.Pods = append(resources.Pods, pod)
+ }
+ if err != nil {
+ logrus.Errorf("Error parsing %s: %v", resource.Kind, err)
+ return err
+ }
+ return nil
+}
diff --git a/pkg/kube/resources_test.go b/pkg/kube/resources_test.go
index 5b293491..f57a9f3c 100644
--- a/pkg/kube/resources_test.go
+++ b/pkg/kube/resources_test.go
@@ -22,6 +22,9 @@ func TestGetResourcesFromPath(t *testing.T) {
assert.Equal(t, 1, len(resources.Deployments), "Should have a deployment")
assert.Equal(t, "ubuntu", resources.Deployments[0].Spec.Template.Spec.Containers[0].Name)
+ assert.Equal(t, 1, len(resources.StatefulSets), "Should have a stateful set")
+ assert.Equal(t, "nginx", resources.StatefulSets[0].Spec.Template.Spec.Containers[0].Name)
+
assert.Equal(t, 1, len(resources.Namespaces), "Should have a namespace")
assert.Equal(t, "two", resources.Namespaces[0].ObjectMeta.Name)
@@ -52,7 +55,7 @@ func TestGetMultipleResourceFromSingleFile(t *testing.T) {
func TestGetResourceFromAPI(t *testing.T) {
k8s := test.SetupTestAPI()
- k8s = test.SetupAddDeploys(k8s, "test")
+ k8s = test.SetupAddControllers(k8s, "test")
resources, err := CreateResourceProviderFromAPI(k8s, "test")
assert.Equal(t, nil, err, "Error should be nil")
@@ -62,6 +65,7 @@ func TestGetResourceFromAPI(t *testing.T) {
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
assert.Equal(t, 1, len(resources.Deployments), "Should have a deployment")
+ assert.Equal(t, 1, len(resources.StatefulSets), "Should have a stateful set")
assert.Equal(t, 0, len(resources.Pods), "Should have a pod")
assert.Equal(t, "", resources.Deployments[0].ObjectMeta.Name)
diff --git a/pkg/kube/test_files/test_1/stateful_set.yaml b/pkg/kube/test_files/test_1/stateful_set.yaml
new file mode 100644
index 00000000..4ceda0ff
--- /dev/null
+++ b/pkg/kube/test_files/test_1/stateful_set.yaml
@@ -0,0 +1,35 @@
+apiVersion: apps/v1 # for k8s versions before 1.9.0 use apps/v1beta2 and before 1.8.0 use extensions/v1beta1
+kind: StatefulSet
+metadata:
+ name: web
+ labels:
+ app: nginx
+spec:
+ serviceName: "nginx"
+ selector:
+ matchLabels:
+ app: nginx
+ replicas: 14
+ template:
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ containers:
+ - name: nginx
+ image: k8s.gcr.io/nginx-slim:0.8
+ ports:
+ - containerPort: 80
+ name: web
+ volumeMounts:
+ - name: www
+ mountPath: /usr/share/nginx/html
+ volumeClaimTemplates:
+ - metadata:
+ name: www
+ spec:
+ accessModes: [ "ReadWriteOnce" ]
+ resources:
+ requests:
+ storage: 1Gi
+ storageClassName: thin-disk
diff --git a/pkg/validator/controller.go b/pkg/validator/controller.go
new file mode 100644
index 00000000..67f791ba
--- /dev/null
+++ b/pkg/validator/controller.go
@@ -0,0 +1,96 @@
+// Copyright 2019 ReactiveOps
+//
+// 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/reactiveops/polaris/pkg/config"
+ "github.com/reactiveops/polaris/pkg/kube"
+ appsv1 "k8s.io/api/apps/v1"
+ corev1 "k8s.io/api/core/v1"
+)
+
+// ControllerSpec is a generic type for k8s controller specs
+type ControllerSpec struct {
+ Template corev1.PodTemplateSpec
+}
+
+// Controller is a generic type for k8s controllers (e.g. Deployments and StatefulSets)
+type Controller struct {
+ Type string
+ Name string
+ Namespace string
+ Spec ControllerSpec
+}
+
+// ValidateController validates a single controller, returns a ControllerResult.
+func ValidateController(conf conf.Configuration, controller Controller) ControllerResult {
+ pod := controller.Spec.Template.Spec
+ podResult := ValidatePod(conf, &pod)
+ return ControllerResult{
+ Type: controller.Type,
+ Name: controller.Name,
+ PodResult: podResult,
+ }
+}
+
+// ValidateControllers validates that each deployment conforms to the Polaris config,
+// returns a list of ResourceResults organized by namespace.
+func ValidateControllers(config conf.Configuration, kubeResources *kube.ResourceProvider, nsResults *NamespacedResults) {
+ controllers := []Controller{}
+ for _, deploy := range kubeResources.Deployments {
+ controllers = append(controllers, ControllerFromDeployment(deploy))
+ }
+ for _, deploy := range kubeResources.StatefulSets {
+ controllers = append(controllers, ControllerFromStatefulSet(deploy))
+ }
+ for _, controller := range controllers {
+ controllerResult := ValidateController(config, controller)
+ nsResult := nsResults.getNamespaceResult(controller.Namespace)
+ nsResult.Summary.appendResults(*controllerResult.PodResult.Summary)
+ if controller.Type == "Deployment" {
+ nsResult.DeploymentResults = append(nsResult.DeploymentResults, controllerResult)
+ } else if controller.Type == "StatefulSet" {
+ nsResult.StatefulSetResults = append(nsResult.StatefulSetResults, controllerResult)
+ }
+ }
+}
+
+// ControllerFrom* functions are 100% boilerplate
+
+// ControllerFromDeployment creates a controller
+func ControllerFromDeployment(c appsv1.Deployment) Controller {
+ spec := ControllerSpec{
+ Template: c.Spec.Template,
+ }
+ return Controller{
+ Type: "Deployment",
+ Name: c.Name,
+ Namespace: c.Namespace,
+ Spec: spec,
+ }
+}
+
+// ControllerFromStatefulSet creates a controller
+func ControllerFromStatefulSet(c appsv1.StatefulSet) Controller {
+ spec := ControllerSpec{
+ Template: c.Spec.Template,
+ }
+ return Controller{
+ Type: "StatefulSet",
+ Name: c.Name,
+ Namespace: c.Namespace,
+ Spec: spec,
+ }
+}
diff --git a/pkg/validator/deployment.go b/pkg/validator/deployment.go
deleted file mode 100644
index 945c73b6..00000000
--- a/pkg/validator/deployment.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2019 ReactiveOps
-//
-// 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/reactiveops/polaris/pkg/config"
- "github.com/reactiveops/polaris/pkg/kube"
- appsv1 "k8s.io/api/apps/v1"
-)
-
-// ValidateDeployment validates a single deployment, returns a PodResult.
-func ValidateDeployment(conf conf.Configuration, deploy *appsv1.Deployment) ControllerResult {
- pod := deploy.Spec.Template.Spec
- podResult := ValidatePod(conf, &pod)
- return ControllerResult{
- Name: deploy.Name,
- Type: "Deployment",
- PodResult: podResult,
- }
-}
-
-// ValidateDeployments validates that each deployment conforms to the Polaris config,
-// returns a list of ResourceResults organized by namespace.
-func ValidateDeployments(config conf.Configuration, kubeResources *kube.ResourceProvider) (NamespacedResults, error) {
- nsResults := NamespacedResults{}
-
- for _, deploy := range kubeResources.Deployments {
- deploymentResult := ValidateDeployment(config, &deploy)
- nsResults = addResult(deploymentResult, nsResults, deploy.Namespace)
- }
-
- return nsResults, nil
-}
-
-func addResult(deploymentResult ControllerResult, nsResults NamespacedResults, nsName string) NamespacedResults {
- nsResult := &NamespaceResult{}
-
- // If there is already data stored for this namespace name,
- // then append to the ResourceResults to the existing data.
- switch nsResults[nsName] {
- case nil:
- nsResult = &NamespaceResult{
- Summary: &ResultSummary{},
- DeploymentResults: []ControllerResult{},
- }
- nsResults[nsName] = nsResult
- default:
- nsResult = nsResults[nsName]
- }
-
- nsResult.DeploymentResults = append(nsResult.DeploymentResults, deploymentResult)
- nsResult.Summary.appendResults(*deploymentResult.PodResult.Summary)
-
- return nsResults
-}
diff --git a/pkg/validator/fullaudit.go b/pkg/validator/fullaudit.go
index 4ef0e563..e484d834 100644
--- a/pkg/validator/fullaudit.go
+++ b/pkg/validator/fullaudit.go
@@ -14,13 +14,14 @@ const (
// ClusterSummary contains Polaris results as well as some high-level stats
type ClusterSummary struct {
- Results ResultSummary
- Version string
- Nodes int
- Pods int
- Namespaces int
- Deployments int
- Score uint
+ Results ResultSummary
+ Version string
+ Nodes int
+ Pods int
+ Namespaces int
+ Deployments int
+ StatefulSets int
+ Score uint
}
// AuditData contains all the data from a full Polaris audit
@@ -36,16 +37,8 @@ type AuditData struct {
// RunAudit runs a full Polaris audit and returns an AuditData object
func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) (AuditData, error) {
- // TODO: Validate StatefulSets, DaemonSets, Cron jobs
- // in addition to deployments
-
- // TODO: Once we are validating more than deployments,
- // we will need to merge the namespaceResults that get returned
- // from each validation.
- nsResults, err := ValidateDeployments(config, kubeResources)
- if err != nil {
- return AuditData{}, err
- }
+ nsResults := NamespacedResults{}
+ ValidateControllers(config, kubeResources, &nsResults)
clusterResults := ResultSummary{}
@@ -54,6 +47,9 @@ func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) (
for _, dr := range nsRes.DeploymentResults {
clusterResults.appendResults(*dr.PodResult.Summary)
}
+ for _, dr := range nsRes.StatefulSetResults {
+ clusterResults.appendResults(*dr.PodResult.Summary)
+ }
}
displayName := config.DisplayName
@@ -68,13 +64,14 @@ func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) (
SourceName: kubeResources.SourceName,
DisplayName: displayName,
ClusterSummary: ClusterSummary{
- Version: kubeResources.ServerVersion,
- Nodes: len(kubeResources.Nodes),
- Pods: len(kubeResources.Pods),
- Namespaces: len(kubeResources.Namespaces),
- Deployments: len(kubeResources.Deployments),
- Results: clusterResults,
- Score: clusterResults.Totals.GetScore(),
+ Version: kubeResources.ServerVersion,
+ Nodes: len(kubeResources.Nodes),
+ Pods: len(kubeResources.Pods),
+ Namespaces: len(kubeResources.Namespaces),
+ Deployments: len(kubeResources.Deployments),
+ StatefulSets: len(kubeResources.StatefulSets),
+ Results: clusterResults,
+ Score: clusterResults.Totals.GetScore(),
},
NamespacedResults: nsResults,
}
diff --git a/pkg/validator/fullaudit_test.go b/pkg/validator/fullaudit_test.go
index cf926072..d8b9951a 100644
--- a/pkg/validator/fullaudit_test.go
+++ b/pkg/validator/fullaudit_test.go
@@ -11,7 +11,7 @@ import (
func TestGetTemplateData(t *testing.T) {
k8s := test.SetupTestAPI()
- k8s = test.SetupAddDeploys(k8s, "test")
+ k8s = test.SetupAddControllers(k8s, "test")
resources, err := kube.CreateResourceProviderFromAPI(k8s, "test")
assert.Equal(t, err, nil, "error should be nil")
@@ -24,19 +24,19 @@ func TestGetTemplateData(t *testing.T) {
sum := ResultSummary{
Totals: CountSummary{
- Successes: uint(4),
- Warnings: uint(1),
- Errors: uint(1),
+ Successes: uint(8),
+ Warnings: uint(2),
+ Errors: uint(2),
},
ByCategory: CategorySummary{},
}
sum.ByCategory["Health Checks"] = &CountSummary{
Successes: uint(0),
- Warnings: uint(1),
- Errors: uint(1),
+ Warnings: uint(2),
+ Errors: uint(2),
}
sum.ByCategory["Resources"] = &CountSummary{
- Successes: uint(4),
+ Successes: uint(8),
Warnings: uint(0),
Errors: uint(0),
}
@@ -47,8 +47,14 @@ func TestGetTemplateData(t *testing.T) {
assert.EqualValues(t, sum, actualAudit.ClusterSummary.Results)
assert.Equal(t, actualAudit.SourceType, "Cluster", "should be from a cluster")
assert.Equal(t, actualAudit.SourceName, "test", "should be from a cluster")
+
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].DeploymentResults), "should be equal")
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].DeploymentResults), "should be equal")
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].DeploymentResults[0].PodResult.ContainerResults), "should be equal")
assert.Equal(t, 6, len(actualAudit.NamespacedResults["test"].DeploymentResults[0].PodResult.ContainerResults[0].Messages), "should be equal")
+
+ assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].StatefulSetResults), "should be equal")
+ assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].StatefulSetResults), "should be equal")
+ assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].StatefulSetResults[0].PodResult.ContainerResults), "should be equal")
+ assert.Equal(t, 6, len(actualAudit.NamespacedResults["test"].StatefulSetResults[0].PodResult.ContainerResults[0].Messages), "should be equal")
}
diff --git a/pkg/validator/pod_test.go b/pkg/validator/pod_test.go
index c3038543..87de9be2 100644
--- a/pkg/validator/pod_test.go
+++ b/pkg/validator/pod_test.go
@@ -35,7 +35,7 @@ func TestValidatePod(t *testing.T) {
}
k8s := test.SetupTestAPI()
- k8s = test.SetupAddDeploys(k8s, "test")
+ k8s = test.SetupAddControllers(k8s, "test")
pod := test.MockPod()
expectedSum := ResultSummary{
diff --git a/pkg/validator/types.go b/pkg/validator/types.go
index 49f523c6..59b9c5c4 100644
--- a/pkg/validator/types.go
+++ b/pkg/validator/types.go
@@ -28,14 +28,31 @@ const (
MessageTypeError MessageType = "error"
)
+// NamespaceResult groups container results by parent resource.
+type NamespaceResult struct {
+ Name string
+ Summary *ResultSummary
+ DeploymentResults []ControllerResult
+ StatefulSetResults []ControllerResult
+}
+
// NamespacedResults is a mapping of namespace name to the validation results.
type NamespacedResults map[string]*NamespaceResult
-// NamespaceResult groups container results by parent resource.
-type NamespaceResult struct {
- Name string
- Summary *ResultSummary
- DeploymentResults []ControllerResult
+func (nsResults NamespacedResults) getNamespaceResult(nsName string) *NamespaceResult {
+ nsResult := &NamespaceResult{}
+ switch nsResults[nsName] {
+ case nil:
+ nsResult = &NamespaceResult{
+ Summary: &ResultSummary{},
+ DeploymentResults: []ControllerResult{},
+ StatefulSetResults: []ControllerResult{},
+ }
+ nsResults[nsName] = nsResult
+ default:
+ nsResult = nsResults[nsName]
+ }
+ return nsResult
}
// CountSummary provides a high level overview of success, warnings, and errors.
diff --git a/pkg/webhook/validator.go b/pkg/webhook/validator.go
index ab2ebad9..b46d71fe 100644
--- a/pkg/webhook/validator.go
+++ b/pkg/webhook/validator.go
@@ -87,19 +87,24 @@ func (v *Validator) Handle(ctx context.Context, req types.Request) types.Respons
var err error
var podResult validator.PodResult
- allowed := true
- reason := ""
-
- switch req.AdmissionRequest.Kind.Kind {
- case "Deployment":
- deploy := appsv1.Deployment{}
- err = v.decoder.Decode(req, &deploy)
- deployResult := validator.ValidateDeployment(v.Config, &deploy)
- podResult = deployResult.PodResult
- case "Pod":
+ if req.AdmissionRequest.Kind.Kind == "Pod" {
pod := corev1.Pod{}
err = v.decoder.Decode(req, &pod)
podResult = validator.ValidatePod(v.Config, &pod.Spec)
+ } else {
+ var controller validator.Controller
+ switch req.AdmissionRequest.Kind.Kind {
+ case "Deployment":
+ deploy := appsv1.Deployment{}
+ err = v.decoder.Decode(req, &deploy)
+ controller = validator.ControllerFromDeployment(deploy)
+ case "StatefulSet":
+ statefulSet := appsv1.StatefulSet{}
+ err = v.decoder.Decode(req, &statefulSet)
+ controller = validator.ControllerFromStatefulSet(statefulSet)
+ }
+ controllerResult := validator.ValidateController(v.Config, controller)
+ podResult = controllerResult.PodResult
}
if err != nil {
@@ -107,6 +112,8 @@ func (v *Validator) Handle(ctx context.Context, req types.Request) types.Respons
return admission.ErrorResponse(http.StatusBadRequest, err)
}
+ allowed := true
+ reason := ""
if podResult.Summary.Totals.Errors > 0 {
allowed = false
reason = getFailureReason(podResult)
diff --git a/test/fixtures.go b/test/fixtures.go
index 3c3b548d..a64fe100 100644
--- a/test/fixtures.go
+++ b/test/fixtures.go
@@ -39,17 +39,32 @@ func mockDeploy() appsv1.Deployment {
return d
}
+func mockStatefulSet() appsv1.StatefulSet {
+ p := MockPod()
+ s := appsv1.StatefulSet{
+ Spec: appsv1.StatefulSetSpec{
+ Template: p,
+ },
+ }
+ return s
+}
+
// SetupTestAPI creates a test kube API struct.
func SetupTestAPI() kubernetes.Interface {
return fake.NewSimpleClientset()
}
-// SetupAddDeploys creates a mock deployment and adds it to the test clientset.
-func SetupAddDeploys(k kubernetes.Interface, namespace string) kubernetes.Interface {
+// SetupAddControllers creates mock controllers and adds them to the test clientset.
+func SetupAddControllers(k kubernetes.Interface, namespace string) kubernetes.Interface {
d1 := mockDeploy()
_, err := k.AppsV1().Deployments(namespace).Create(&d1)
if err != nil {
fmt.Println(err)
}
+ s1 := mockStatefulSet()
+ _, err = k.AppsV1().StatefulSets(namespace).Create(&s1)
+ if err != nil {
+ fmt.Println(err)
+ }
return k
}