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 }}
@@ -90,7 +90,9 @@
-
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 }