From 330d7f0516203dc7644287a97c0cc02ac9a2866f Mon Sep 17 00:00:00 2001 From: Bader Boland Date: Fri, 31 Jan 2020 12:44:55 -0500 Subject: [PATCH 01/16] Add note about config change --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40817857..010b347b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # x.x.x (next release) * Added the ability to exempt a particular controller from a particular check. +* Changed configuration of controllers-to-scan to controllersToScan # 0.6.0 * Fixed webhook support in Kubernetes 1.16 From 51d639e2ef0354152ee43bb990c3d5ff2d28ba95 Mon Sep 17 00:00:00 2001 From: Bader Boland Date: Fri, 31 Jan 2020 12:56:53 -0500 Subject: [PATCH 02/16] Prevent divide by 0 --- pkg/validator/summary.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/validator/summary.go b/pkg/validator/summary.go index 6801de8a..4f0b1211 100644 --- a/pkg/validator/summary.go +++ b/pkg/validator/summary.go @@ -17,6 +17,9 @@ type CountSummaryByCategory map[string]CountSummary // GetScore returns an overall score in [0, 100] for the CountSummary func (cs CountSummary) GetScore() uint { total := (cs.Successes * 2) + cs.Warnings + (cs.Errors * 2) + if total == 0 { + total = 1 // Prevent divide by 0. + } return uint((float64(cs.Successes*2) / float64(total)) * 100) } From aaa54cc401e876e4bdb957e39113a1b7150a1928 Mon Sep 17 00:00:00 2001 From: Bader Boland Date: Fri, 31 Jan 2020 12:57:26 -0500 Subject: [PATCH 03/16] Directly return 0 for divide by 0 --- pkg/validator/summary.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/validator/summary.go b/pkg/validator/summary.go index 4f0b1211..19e5198e 100644 --- a/pkg/validator/summary.go +++ b/pkg/validator/summary.go @@ -18,7 +18,7 @@ type CountSummaryByCategory map[string]CountSummary func (cs CountSummary) GetScore() uint { total := (cs.Successes * 2) + cs.Warnings + (cs.Errors * 2) if total == 0 { - total = 1 // Prevent divide by 0. + return 0 // Prevent divide by 0. } return uint((float64(cs.Successes*2) / float64(total)) * 100) } From 31de106ba8b6f2d959d0322238e603343a76ae42 Mon Sep 17 00:00:00 2001 From: Bader Boland Date: Fri, 31 Jan 2020 14:10:53 -0500 Subject: [PATCH 04/16] Added check for parent and rollup to parent. --- CHANGELOG.md | 3 ++- pkg/validator/controller.go | 53 +++++++++++++++++++++++++++++++------ pkg/validator/output.go | 12 +++++---- 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 010b347b..2905dcec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # x.x.x (next release) * Added the ability to exempt a particular controller from a particular check. -* Changed configuration of controllers-to-scan to controllersToScan +* Breaking changes in the config format. +* Added support for finding the Owners, this will allow Polaris to work with types of Controllers it doesn't even know about. # 0.6.0 * Fixed webhook support in Kubernetes 1.16 diff --git a/pkg/validator/controller.go b/pkg/validator/controller.go index 9aa01ba6..6f58f471 100644 --- a/pkg/validator/controller.go +++ b/pkg/validator/controller.go @@ -21,6 +21,7 @@ import ( "github.com/fairwindsops/polaris/pkg/kube" "github.com/fairwindsops/polaris/pkg/validator/controllers" controller "github.com/fairwindsops/polaris/pkg/validator/controllers" + "github.com/sirupsen/logrus" ) const exemptionAnnotationKey = "polaris.fairwinds.com/exempt" @@ -32,24 +33,60 @@ func ValidateController(conf *conf.Configuration, controller controller.Interfac return ControllerResult{}, err } result := ControllerResult{ - Kind: controller.GetKind().String(), - Name: controller.GetName(), - Namespace: controller.GetObjectMeta().Namespace, - Results: ResultSet{}, - PodResult: podResult, + Kind: controller.GetKind().String(), + Name: controller.GetName(), + Namespace: controller.GetObjectMeta().Namespace, + Results: ResultSet{}, + PodResult: podResult, + CreatedTime: controller.GetObjectMeta().CreationTimestamp.Time, + } + owners := controller.GetObjectMeta().OwnerReferences + // If an owner exists then set the name to the controller. + // This allows us to handle CRDs creating Controllers or DeploymentConfigs in OpenShift. + if len(owners) > 0 { + firstOwner := owners[0] + result.Kind = firstOwner.Kind + result.Name = firstOwner.Name } return result, nil } +// 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(controllerResults []ControllerResult) []ControllerResult { + controllerMap := make(map[string][]ControllerResult) + for _, controller := range controllerResults { + key := controller.Namespace + "/" + controller.Kind + "/" + controller.Name + controllerMap[key] = append(controllerMap[key], controller) + } + results := make([]ControllerResult, 0) + for _, controllers := range controllerMap { + if len(controllers) == 1 { + results = append(results, controllers[0]) + } else { + latestController := controllers[0] + for _, controller := range controllers[1:] { + if controller.CreatedTime.After(latestController.CreatedTime) { + latestController = controller + } + } + results = append(results, latestController) + } + } + return results +} + // 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) ([]ControllerResult, error) { var controllersToAudit []controller.Interface for _, supportedControllers := range config.ControllersToScan { - loadedControllers, _ := controllers.LoadControllersByKind(supportedControllers, kubeResources) + loadedControllers, err := controllers.LoadControllersByKind(supportedControllers, kubeResources) + if err != nil { + logrus.Warn(err) + } controllersToAudit = append(controllersToAudit, loadedControllers...) } - results := []ControllerResult{} for _, controller := range controllersToAudit { if !config.DisallowExemptions && hasExemptionAnnotation(controller) { @@ -61,7 +98,7 @@ func ValidateControllers(config *conf.Configuration, kubeResources *kube.Resourc } results = append(results, result) } - return results, nil + return deduplicateControllers(results), nil } func hasExemptionAnnotation(ctrl controller.Interface) bool { diff --git a/pkg/validator/output.go b/pkg/validator/output.go index 49fc7626..9df68e38 100644 --- a/pkg/validator/output.go +++ b/pkg/validator/output.go @@ -16,6 +16,7 @@ package validator import ( "github.com/fairwindsops/polaris/pkg/config" + "time" ) const ( @@ -62,11 +63,12 @@ type ResultSet map[string]ResultMessage // ControllerResult provides results for a controller type ControllerResult struct { - Name string - Namespace string - Kind string - Results ResultSet - PodResult PodResult + Name string + Namespace string + Kind string + Results ResultSet + PodResult PodResult + CreatedTime time.Time } // PodResult provides a list of validation messages for each pod. From bb34be7e02f0d1d2832e66c48183981fb709c0b5 Mon Sep 17 00:00:00 2001 From: Bader Boland Date: Mon, 16 Mar 2020 16:41:16 -0400 Subject: [PATCH 05/16] Dynamically retrieve parents --- pkg/config/supportedcontrollers.go | 7 --- pkg/kube/resources.go | 60 +++++++++----------------- pkg/validator/controller.go | 41 +++++++++++++----- pkg/validator/controllers/interface.go | 4 ++ pkg/webhook/validator.go | 3 +- 5 files changed, 58 insertions(+), 57 deletions(-) diff --git a/pkg/config/supportedcontrollers.go b/pkg/config/supportedcontrollers.go index 2562859b..33eb8702 100644 --- a/pkg/config/supportedcontrollers.go +++ b/pkg/config/supportedcontrollers.go @@ -10,8 +10,6 @@ import ( appsv1beta1 "k8s.io/api/apps/v1beta1" appsv1beta2 "k8s.io/api/apps/v1beta2" batchv1 "k8s.io/api/batch/v1" - batchv1beta1 "k8s.io/api/batch/v1beta1" - batchv2alpha1 "k8s.io/api/batch/v2alpha1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -124,11 +122,6 @@ func (s SupportedController) ListSupportedAPIVersions() []runtime.Object { supportedVersions = []runtime.Object{ &batchv1.Job{}, } - case CronJobs: - supportedVersions = []runtime.Object{ - &batchv1beta1.CronJob{}, - &batchv2alpha1.CronJob{}, - } case ReplicationControllers: supportedVersions = []runtime.Object{ &corev1.ReplicationController{}, diff --git a/pkg/kube/resources.go b/pkg/kube/resources.go index 67a3be66..85484500 100644 --- a/pkg/kube/resources.go +++ b/pkg/kube/resources.go @@ -15,10 +15,14 @@ import ( batchv1 "k8s.io/api/batch/v1" batchv1beta1 "k8s.io/api/batch/v1beta1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sYaml "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth" // Required for other auth providers like GKE. + "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" "sigs.k8s.io/controller-runtime/pkg/client/config" ) @@ -37,6 +41,8 @@ type ResourceProvider struct { ReplicationControllers []corev1.ReplicationController Namespaces []corev1.Namespace Pods []corev1.Pod + DynamicClient *dynamic.Interface + RestMapper *meta.RESTMapper } type k8sResource struct { @@ -114,11 +120,11 @@ func CreateResourceProviderFromCluster() (*ResourceProvider, error) { logrus.Errorf("Error creating Kubernetes client: %v", err) return nil, err } - return CreateResourceProviderFromAPI(api, kubeConf.Host) + return CreateResourceProviderFromAPI(api, kubeConf.Host, kubeConf) } // CreateResourceProviderFromAPI creates a new ResourceProvider from an existing k8s interface -func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string) (*ResourceProvider, error) { +func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string, kubeConf *rest.Config) (*ResourceProvider, error) { listOpts := metav1.ListOptions{} serverVersion, err := kube.Discovery().ServerVersion() if err != nil { @@ -133,10 +139,6 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string if err != nil { return nil, err } - cronJobs, err := getCronJobs(kube) - if err != nil { - return nil, err - } daemonSets, err := getDaemonSets(kube) if err != nil { return nil, err @@ -167,6 +169,17 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string logrus.Errorf("Error fetching Pods: %v", err) return nil, err } + dynamicInterface, err := dynamic.NewForConfig(kubeConf) + if err != nil { + logrus.Errorf("Error connecting to dynamic interface: %v", err) + return nil, err + } + resources, err := restmapper.GetAPIGroupResources(kube.Discovery()) + if err != nil { + logrus.Errorf("Error getting API Group resources: %v", err) + return nil, err + } + restMapper := restmapper.NewDiscoveryRESTMapper(resources) api := ResourceProvider{ ServerVersion: serverVersion.Major + "." + serverVersion.Minor, @@ -176,12 +189,13 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string Deployments: deploys, StatefulSets: statefulSets, DaemonSets: daemonSets, - CronJobs: cronJobs, Jobs: jobs.Items, ReplicationControllers: replicationControllers.Items, Nodes: nodes.Items, Namespaces: namespaces.Items, Pods: pods.Items, + DynamicClient: &dynamicInterface, + RestMapper: &restMapper, } return &api, nil } @@ -355,35 +369,3 @@ func getDaemonSets(kube kubernetes.Interface) ([]appsv1.DaemonSet, error) { } return controllers, nil } - -func getCronJobs(kube kubernetes.Interface) ([]batchv1beta1.CronJob, error) { - listOpts := metav1.ListOptions{} - controllerList, err := kube.BatchV1beta1().CronJobs("").List(listOpts) - if err != nil { - logrus.Errorf("Error fetching CronJobs: %v", err) - return nil, err - } - controllers := controllerList.Items - - controllersV2A1, err := kube.BatchV2alpha1().CronJobs("").List(listOpts) - if err != nil { - logrus.Errorf("Error fetching CronJobs v2alpha1: %v", err) - return nil, err - } - - for _, oldController := range controllersV2A1.Items { - str, err := json.Marshal(oldController) - if err != nil { - logrus.Errorf("Error marshaling old CronJob version: %v", err) - return nil, err - } - controller := batchv1beta1.CronJob{} - err = json.Unmarshal(str, &controller) - if err != nil { - logrus.Errorf("Error unmarshaling old CronJob version: %v", err) - return nil, err - } - controllers = append(controllers, controller) - } - return controllers, nil -} diff --git a/pkg/validator/controller.go b/pkg/validator/controller.go index 6f58f471..dd2c2683 100644 --- a/pkg/validator/controller.go +++ b/pkg/validator/controller.go @@ -17,17 +17,20 @@ package validator import ( "strings" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + conf "github.com/fairwindsops/polaris/pkg/config" "github.com/fairwindsops/polaris/pkg/kube" "github.com/fairwindsops/polaris/pkg/validator/controllers" controller "github.com/fairwindsops/polaris/pkg/validator/controllers" - "github.com/sirupsen/logrus" ) const exemptionAnnotationKey = "polaris.fairwinds.com/exempt" // ValidateController validates a single controller, returns a ControllerResult. -func ValidateController(conf *conf.Configuration, controller controller.Interface) (ControllerResult, error) { +func ValidateController(conf *conf.Configuration, controller controller.Interface, kubeResources *kube.ResourceProvider) (ControllerResult, error) { podResult, err := ValidatePod(conf, controller) if err != nil { return ControllerResult{}, err @@ -43,11 +46,31 @@ func ValidateController(conf *conf.Configuration, controller controller.Interfac owners := controller.GetObjectMeta().OwnerReferences // If an owner exists then set the name to the controller. // This allows us to handle CRDs creating Controllers or DeploymentConfigs in OpenShift. - if len(owners) > 0 { + for len(owners) > 0 { firstOwner := owners[0] result.Kind = firstOwner.Kind result.Name = firstOwner.Name + if kubeResources.DynamicClient != nil { + + dynamicClient := *kubeResources.DynamicClient + restMapper := *kubeResources.RestMapper + fqKind := schema.FromAPIVersionAndKind(firstOwner.APIVersion, firstOwner.Kind) + mapping, err := restMapper.RESTMapping(fqKind.GroupKind(), fqKind.Version) + if err != nil { + logrus.Warnf("Error retrieving mapping %s of API %s and Kind %s because of error: %v ", firstOwner.Name, firstOwner.APIVersion, firstOwner.Kind, err) + return result, nil + } + getParents, err := dynamicClient.Resource(mapping.Resource).Namespace(controller.GetObjectMeta().Namespace).Get(firstOwner.Name, metav1.GetOptions{}) + if err != nil { + logrus.Warnf("Error retrieving parent object %s of API %s and Kind %s because of error: %v ", firstOwner.Name, firstOwner.APIVersion, firstOwner.Kind, err) + return result, nil + } + owners = getParents.GetOwnerReferences() + } else { + break + } } + return result, nil } @@ -80,19 +103,17 @@ func deduplicateControllers(controllerResults []ControllerResult) []ControllerRe // builds a list of ResourceResults organized by namespace. func ValidateControllers(config *conf.Configuration, kubeResources *kube.ResourceProvider) ([]ControllerResult, error) { var controllersToAudit []controller.Interface - for _, supportedControllers := range config.ControllersToScan { - loadedControllers, err := controllers.LoadControllersByKind(supportedControllers, kubeResources) - if err != nil { - logrus.Warn(err) - } - controllersToAudit = append(controllersToAudit, loadedControllers...) + loadedControllers, err := controllers.LoadControllersByKind(conf.NakedPods, kubeResources) + if err != nil { + logrus.Warn(err) } + controllersToAudit = append(controllersToAudit, loadedControllers...) results := []ControllerResult{} for _, controller := range controllersToAudit { if !config.DisallowExemptions && hasExemptionAnnotation(controller) { continue } - result, err := ValidateController(config, controller) + result, err := ValidateController(config, controller, kubeResources) if err != nil { return nil, err } diff --git a/pkg/validator/controllers/interface.go b/pkg/validator/controllers/interface.go index fbcca9aa..f9ee30a7 100644 --- a/pkg/validator/controllers/interface.go +++ b/pkg/validator/controllers/interface.go @@ -39,6 +39,10 @@ func (g GenericController) GetNamespace() string { func LoadControllersByKind(controllerKind config.SupportedController, kubeResources *kube.ResourceProvider) ([]Interface, error) { interfaces := []Interface{} switch controllerKind { + case config.NakedPods: + for _, pod := range kubeResources.Pods { + interfaces = append(interfaces, NewNakedPodController(pod)) + } case config.Deployments: for _, deploy := range kubeResources.Deployments { interfaces = append(interfaces, NewDeploymentController(deploy)) diff --git a/pkg/webhook/validator.go b/pkg/webhook/validator.go index f93064a3..2da14d75 100644 --- a/pkg/webhook/validator.go +++ b/pkg/webhook/validator.go @@ -20,6 +20,7 @@ import ( "net/http" "github.com/fairwindsops/polaris/pkg/config" + "github.com/fairwindsops/polaris/pkg/kube" validator "github.com/fairwindsops/polaris/pkg/validator" "github.com/fairwindsops/polaris/pkg/validator/controllers" @@ -142,7 +143,7 @@ func (v *Validator) Handle(ctx context.Context, req types.Request) types.Respons } if err == nil { var controllerResult validator.ControllerResult - controllerResult, err = validator.ValidateController(&v.Config, controller) + controllerResult, err = validator.ValidateController(&v.Config, controller, &kube.ResourceProvider{}) podResult = controllerResult.PodResult } } From 0d3fe6130a6647d6cf2fb0a06b2634b8b85b8ef1 Mon Sep 17 00:00:00 2001 From: Bader Boland Date: Mon, 16 Mar 2020 16:48:57 -0400 Subject: [PATCH 06/16] Remove unnecessary queries --- pkg/kube/resources.go | 42 ++++++++++-------------------------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/pkg/kube/resources.go b/pkg/kube/resources.go index 85484500..797c77a0 100644 --- a/pkg/kube/resources.go +++ b/pkg/kube/resources.go @@ -131,29 +131,11 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string logrus.Errorf("Error fetching Cluster API version: %v", err) return nil, err } - deploys, err := getDeployments(kube) - if err != nil { - return nil, err - } - statefulSets, err := getStatefulSets(kube) - if err != nil { - return nil, err - } - daemonSets, err := getDaemonSets(kube) - if err != nil { - return nil, err - } - jobs, err := kube.BatchV1().Jobs("").List(listOpts) if err != nil { logrus.Errorf("Error fetching Jobs: %v", err) return nil, err } - replicationControllers, err := kube.CoreV1().ReplicationControllers("").List(listOpts) - if err != nil { - logrus.Errorf("Error fetching ReplicationControllers: %v", err) - return nil, err - } nodes, err := kube.CoreV1().Nodes().List(listOpts) if err != nil { logrus.Errorf("Error fetching Nodes: %v", err) @@ -182,20 +164,16 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string restMapper := restmapper.NewDiscoveryRESTMapper(resources) api := ResourceProvider{ - ServerVersion: serverVersion.Major + "." + serverVersion.Minor, - SourceType: "Cluster", - SourceName: clusterName, - CreationTime: time.Now(), - Deployments: deploys, - StatefulSets: statefulSets, - DaemonSets: daemonSets, - Jobs: jobs.Items, - ReplicationControllers: replicationControllers.Items, - Nodes: nodes.Items, - Namespaces: namespaces.Items, - Pods: pods.Items, - DynamicClient: &dynamicInterface, - RestMapper: &restMapper, + ServerVersion: serverVersion.Major + "." + serverVersion.Minor, + SourceType: "Cluster", + SourceName: clusterName, + CreationTime: time.Now(), + Jobs: jobs.Items, + Nodes: nodes.Items, + Namespaces: namespaces.Items, + Pods: pods.Items, + DynamicClient: &dynamicInterface, + RestMapper: &restMapper, } return &api, nil } From 7fdebfc4db13a5ffe2097fcde60d56ce6334536a Mon Sep 17 00:00:00 2001 From: Bader Boland Date: Tue, 17 Mar 2020 09:19:33 -0400 Subject: [PATCH 07/16] Fix tests --- pkg/dashboard/templates/dashboard.gohtml | 4 ++ pkg/kube/resources.go | 18 ++++---- pkg/kube/resources_test.go | 5 +-- pkg/validator/controller.go | 8 ++-- pkg/validator/controller_test.go | 15 +++---- pkg/validator/controllers/cronjob.go | 5 --- pkg/validator/controllers/daemonset.go | 5 --- pkg/validator/controllers/deployment.go | 5 --- pkg/validator/controllers/interface.go | 43 +++---------------- pkg/validator/controllers/job.go | 5 --- pkg/validator/controllers/naked-pod.go | 5 --- .../controllers/replicationcontroller.go | 5 --- pkg/validator/controllers/statefulsets.go | 5 --- pkg/validator/fullaudit.go | 15 +++---- pkg/validator/fullaudit_test.go | 18 ++------ pkg/validator/output.go | 18 +++----- pkg/validator/pod.go | 2 - test/fixtures.go | 12 ++++++ 18 files changed, 58 insertions(+), 135 deletions(-) diff --git a/pkg/dashboard/templates/dashboard.gohtml b/pkg/dashboard/templates/dashboard.gohtml index 7b3e85cf..69a6f40b 100644 --- a/pkg/dashboard/templates/dashboard.gohtml +++ b/pkg/dashboard/templates/dashboard.gohtml @@ -43,6 +43,10 @@ Pods: {{.AuditData.ClusterInfo.Pods}} + + Controllers: + {{.AuditData.ClusterInfo.Controllers}} + Namespaces: {{.AuditData.ClusterInfo.Namespaces}} diff --git a/pkg/kube/resources.go b/pkg/kube/resources.go index 797c77a0..d8413823 100644 --- a/pkg/kube/resources.go +++ b/pkg/kube/resources.go @@ -21,7 +21,6 @@ import ( "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth" // Required for other auth providers like GKE. - "k8s.io/client-go/rest" "k8s.io/client-go/restmapper" "sigs.k8s.io/controller-runtime/pkg/client/config" ) @@ -120,11 +119,16 @@ func CreateResourceProviderFromCluster() (*ResourceProvider, error) { logrus.Errorf("Error creating Kubernetes client: %v", err) return nil, err } - return CreateResourceProviderFromAPI(api, kubeConf.Host, kubeConf) + dynamicInterface, err := dynamic.NewForConfig(kubeConf) + if err != nil { + logrus.Errorf("Error connecting to dynamic interface: %v", err) + return nil, err + } + return CreateResourceProviderFromAPI(api, kubeConf.Host, &dynamicInterface) } // CreateResourceProviderFromAPI creates a new ResourceProvider from an existing k8s interface -func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string, kubeConf *rest.Config) (*ResourceProvider, error) { +func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string, dynamic *dynamic.Interface) (*ResourceProvider, error) { listOpts := metav1.ListOptions{} serverVersion, err := kube.Discovery().ServerVersion() if err != nil { @@ -151,11 +155,7 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string logrus.Errorf("Error fetching Pods: %v", err) return nil, err } - dynamicInterface, err := dynamic.NewForConfig(kubeConf) - if err != nil { - logrus.Errorf("Error connecting to dynamic interface: %v", err) - return nil, err - } + resources, err := restmapper.GetAPIGroupResources(kube.Discovery()) if err != nil { logrus.Errorf("Error getting API Group resources: %v", err) @@ -172,7 +172,7 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string Nodes: nodes.Items, Namespaces: namespaces.Items, Pods: pods.Items, - DynamicClient: &dynamicInterface, + DynamicClient: dynamic, RestMapper: &restMapper, } return &api, nil diff --git a/pkg/kube/resources_test.go b/pkg/kube/resources_test.go index b3326ea4..ee9ccb08 100644 --- a/pkg/kube/resources_test.go +++ b/pkg/kube/resources_test.go @@ -62,7 +62,7 @@ func TestGetMultipleResourceFromBadFile(t *testing.T) { func TestGetResourceFromAPI(t *testing.T) { k8s := test.SetupTestAPI() k8s = test.SetupAddControllers(k8s, "test") - resources, err := CreateResourceProviderFromAPI(k8s, "test") + resources, err := CreateResourceProviderFromAPI(k8s, "test", nil) assert.Equal(t, nil, err, "Error should be nil") assert.Equal(t, "Cluster", resources.SourceType, "Should have type Path") @@ -70,9 +70,6 @@ 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, 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/validator/controller.go b/pkg/validator/controller.go index dd2c2683..dccd44a0 100644 --- a/pkg/validator/controller.go +++ b/pkg/validator/controller.go @@ -103,11 +103,9 @@ func deduplicateControllers(controllerResults []ControllerResult) []ControllerRe // builds a list of ResourceResults organized by namespace. func ValidateControllers(config *conf.Configuration, kubeResources *kube.ResourceProvider) ([]ControllerResult, error) { var controllersToAudit []controller.Interface - loadedControllers, err := controllers.LoadControllersByKind(conf.NakedPods, kubeResources) - if err != nil { - logrus.Warn(err) - } + loadedControllers := controllers.LoadControllers(kubeResources) controllersToAudit = append(controllersToAudit, loadedControllers...) + results := []ControllerResult{} for _, controller := range controllersToAudit { if !config.DisallowExemptions && hasExemptionAnnotation(controller) { @@ -115,10 +113,12 @@ func ValidateControllers(config *conf.Configuration, kubeResources *kube.Resourc } result, err := ValidateController(config, controller, kubeResources) if err != nil { + logrus.Warn("An error occured validating controller:", err) return nil, err } results = append(results, result) } + return deduplicateControllers(results), nil } diff --git a/pkg/validator/controller_test.go b/pkg/validator/controller_test.go index e8e832fd..548c2f4f 100644 --- a/pkg/validator/controller_test.go +++ b/pkg/validator/controller_test.go @@ -18,7 +18,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" conf "github.com/fairwindsops/polaris/pkg/config" @@ -46,7 +45,7 @@ func TestValidateController(t *testing.T) { "hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "error", Category: "Security"}, } - actualResult, err := ValidateController(&c, deployment) + actualResult, err := ValidateController(&c, deployment, &kube.ResourceProvider{}) if err != nil { panic(err) } @@ -84,7 +83,7 @@ func TestSkipHealthChecks(t *testing.T) { "readinessProbeMissing": {ID: "readinessProbeMissing", Message: "Readiness probe should be configured", Success: false, Severity: "error", Category: "Health Checks"}, "livenessProbeMissing": {ID: "livenessProbeMissing", Message: "Liveness probe should be configured", Success: false, Severity: "warning", Category: "Health Checks"}, } - actualResult, err := ValidateController(&c, deployment) + actualResult, err := ValidateController(&c, deployment, &kube.ResourceProvider{}) if err != nil { panic(err) } @@ -101,7 +100,7 @@ func TestSkipHealthChecks(t *testing.T) { Errors: uint(0), } expectedResults = ResultSet{} - actualResult, err = ValidateController(&c, job) + actualResult, err = ValidateController(&c, job, &kube.ResourceProvider{}) if err != nil { panic(err) } @@ -117,7 +116,7 @@ func TestSkipHealthChecks(t *testing.T) { Errors: uint(0), } expectedResults = ResultSet{} - actualResult, err = ValidateController(&c, cronjob) + actualResult, err = ValidateController(&c, cronjob, &kube.ResourceProvider{}) if err != nil { panic(err) } @@ -138,7 +137,7 @@ func TestControllerExemptions(t *testing.T) { }, } resources := &kube.ResourceProvider{ - Deployments: []appsv1.Deployment{test.MockDeploy()}, + Pods: []corev1.Pod{test.MockNakedPod()}, } expectedSum := CountSummary{ @@ -151,10 +150,10 @@ func TestControllerExemptions(t *testing.T) { panic(err) } assert.Equal(t, 1, len(actualResults)) - assert.Equal(t, "Deployment", actualResults[0].Kind) + assert.Equal(t, "NakedPod", actualResults[0].Kind) assert.EqualValues(t, expectedSum, actualResults[0].GetSummary()) - resources.Deployments[0].ObjectMeta.Annotations = map[string]string{ + resources.Pods[0].ObjectMeta.Annotations = map[string]string{ exemptionAnnotationKey: "true", } actualResults, err = ValidateControllers(&c, resources) diff --git a/pkg/validator/controllers/cronjob.go b/pkg/validator/controllers/cronjob.go index e55fc485..885ae359 100644 --- a/pkg/validator/controllers/cronjob.go +++ b/pkg/validator/controllers/cronjob.go @@ -13,11 +13,6 @@ type CronJobController struct { K8SResource kubeAPIBatchV1beta1.CronJob } -// GetPodTemplate returns the original template spec -func (c CronJobController) GetPodTemplate() *kubeAPICoreV1.PodTemplateSpec { - return &c.K8SResource.Spec.JobTemplate.Spec.Template -} - // GetPodSpec returns the original kubernetes template pod spec func (c CronJobController) GetPodSpec() *kubeAPICoreV1.PodSpec { return &c.K8SResource.Spec.JobTemplate.Spec.Template.Spec diff --git a/pkg/validator/controllers/daemonset.go b/pkg/validator/controllers/daemonset.go index a3c67dad..96d8f7d5 100644 --- a/pkg/validator/controllers/daemonset.go +++ b/pkg/validator/controllers/daemonset.go @@ -13,11 +13,6 @@ type DaemonSetController struct { K8SResource kubeAPIAppsV1.DaemonSet } -// GetPodTemplate returns the original template spec -func (d DaemonSetController) GetPodTemplate() *kubeAPICoreV1.PodTemplateSpec { - return &d.K8SResource.Spec.Template -} - // GetPodSpec returns the original kubernetes template pod spec func (d DaemonSetController) GetPodSpec() *kubeAPICoreV1.PodSpec { return &d.K8SResource.Spec.Template.Spec diff --git a/pkg/validator/controllers/deployment.go b/pkg/validator/controllers/deployment.go index e02a60bd..a2d4d5e9 100644 --- a/pkg/validator/controllers/deployment.go +++ b/pkg/validator/controllers/deployment.go @@ -13,11 +13,6 @@ type DeploymentController struct { K8SResource kubeAPIAppsV1.Deployment } -// GetPodTemplate returns the original template spec -func (d DeploymentController) GetPodTemplate() *kubeAPICoreV1.PodTemplateSpec { - return &d.K8SResource.Spec.Template -} - // GetPodSpec returns the original kubernetes template pod spec func (d DeploymentController) GetPodSpec() *kubeAPICoreV1.PodSpec { return &d.K8SResource.Spec.Template.Spec diff --git a/pkg/validator/controllers/interface.go b/pkg/validator/controllers/interface.go index f9ee30a7..9b3f6d7e 100644 --- a/pkg/validator/controllers/interface.go +++ b/pkg/validator/controllers/interface.go @@ -1,8 +1,6 @@ package controllers import ( - "fmt" - "github.com/fairwindsops/polaris/pkg/config" "github.com/fairwindsops/polaris/pkg/kube" kubeAPICoreV1 "k8s.io/api/core/v1" @@ -13,7 +11,6 @@ import ( type Interface interface { GetName() string GetNamespace() string - GetPodTemplate() *kubeAPICoreV1.PodTemplateSpec GetPodSpec() *kubeAPICoreV1.PodSpec GetKind() config.SupportedController GetObjectMeta() kubeAPIMetaV1.ObjectMeta @@ -35,41 +32,11 @@ func (g GenericController) GetNamespace() string { return g.Namespace } -// LoadControllersByKind loads a list of controllers from the kubeResources by detecting their type -func LoadControllersByKind(controllerKind config.SupportedController, kubeResources *kube.ResourceProvider) ([]Interface, error) { +// LoadControllers loads a list of controllers from the kubeResources Pods +func LoadControllers(kubeResources *kube.ResourceProvider) []Interface { interfaces := []Interface{} - switch controllerKind { - case config.NakedPods: - for _, pod := range kubeResources.Pods { - interfaces = append(interfaces, NewNakedPodController(pod)) - } - case config.Deployments: - for _, deploy := range kubeResources.Deployments { - interfaces = append(interfaces, NewDeploymentController(deploy)) - } - case config.StatefulSets: - for _, statefulSet := range kubeResources.StatefulSets { - interfaces = append(interfaces, NewStatefulSetController(statefulSet)) - } - case config.DaemonSets: - for _, daemonSet := range kubeResources.DaemonSets { - interfaces = append(interfaces, NewDaemonSetController(daemonSet)) - } - case config.Jobs: - for _, job := range kubeResources.Jobs { - interfaces = append(interfaces, NewJobController(job)) - } - case config.CronJobs: - for _, cronJob := range kubeResources.CronJobs { - interfaces = append(interfaces, NewCronJobController(cronJob)) - } - case config.ReplicationControllers: - for _, replicationController := range kubeResources.ReplicationControllers { - interfaces = append(interfaces, NewReplicationControllerController(replicationController)) - } + for _, pod := range kubeResources.Pods { + interfaces = append(interfaces, NewNakedPodController(pod)) } - if len(interfaces) > 0 { - return interfaces, nil - } - return nil, fmt.Errorf("Controller type (%s) does not have a generator", controllerKind) + return interfaces } diff --git a/pkg/validator/controllers/job.go b/pkg/validator/controllers/job.go index 3efe181c..699f8588 100644 --- a/pkg/validator/controllers/job.go +++ b/pkg/validator/controllers/job.go @@ -13,11 +13,6 @@ type JobController struct { K8SResource kubeAPIBatchV1.Job } -// GetPodTemplate returns the original template spec -func (j JobController) GetPodTemplate() *kubeAPICoreV1.PodTemplateSpec { - return &j.K8SResource.Spec.Template -} - // GetPodSpec returns the original kubernetes template pod spec func (j JobController) GetPodSpec() *kubeAPICoreV1.PodSpec { return &j.K8SResource.Spec.Template.Spec diff --git a/pkg/validator/controllers/naked-pod.go b/pkg/validator/controllers/naked-pod.go index c5ab8e66..6086f15a 100644 --- a/pkg/validator/controllers/naked-pod.go +++ b/pkg/validator/controllers/naked-pod.go @@ -12,11 +12,6 @@ type NakedPodController struct { K8SResource kubeAPICoreV1.Pod } -// GetPodTemplate returns the original template spec -func (n NakedPodController) GetPodTemplate() *kubeAPICoreV1.PodTemplateSpec { - return nil -} - // GetPodSpec returns the original kubernetes template pod spec func (n NakedPodController) GetPodSpec() *kubeAPICoreV1.PodSpec { return &n.K8SResource.Spec diff --git a/pkg/validator/controllers/replicationcontroller.go b/pkg/validator/controllers/replicationcontroller.go index cb510f88..43cf110e 100644 --- a/pkg/validator/controllers/replicationcontroller.go +++ b/pkg/validator/controllers/replicationcontroller.go @@ -15,11 +15,6 @@ type ReplicationControllerController struct { K8SResource kubeAPICoreV1.ReplicationController } -// GetPodTemplate returns the original template spec -func (r ReplicationControllerController) GetPodTemplate() *kubeAPICoreV1.PodTemplateSpec { - return r.K8SResource.Spec.Template -} - // GetPodSpec returns the original kubernetes template pod spec func (r ReplicationControllerController) GetPodSpec() *kubeAPICoreV1.PodSpec { return &r.K8SResource.Spec.Template.Spec diff --git a/pkg/validator/controllers/statefulsets.go b/pkg/validator/controllers/statefulsets.go index 4ac1f62b..e49598f1 100644 --- a/pkg/validator/controllers/statefulsets.go +++ b/pkg/validator/controllers/statefulsets.go @@ -13,11 +13,6 @@ type StatefulSetController struct { K8SResource kubeAPIAppsV1.StatefulSet } -// GetPodTemplate returns the kubernetes template spec -func (s StatefulSetController) GetPodTemplate() *kubeAPICoreV1.PodTemplateSpec { - return &s.K8SResource.Spec.Template -} - // GetPodSpec returns the podspec from the original kubernetes resource func (s StatefulSetController) GetPodSpec() *kubeAPICoreV1.PodSpec { return &s.K8SResource.Spec.Template.Spec diff --git a/pkg/validator/fullaudit.go b/pkg/validator/fullaudit.go index 06eb6e93..7c3e8ed4 100644 --- a/pkg/validator/fullaudit.go +++ b/pkg/validator/fullaudit.go @@ -34,16 +34,11 @@ func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) ( SourceName: kubeResources.SourceName, DisplayName: displayName, ClusterInfo: ClusterInfo{ - Version: kubeResources.ServerVersion, - Nodes: len(kubeResources.Nodes), - Pods: len(kubeResources.Pods), - Namespaces: len(kubeResources.Namespaces), - Deployments: len(kubeResources.Deployments), - StatefulSets: len(kubeResources.StatefulSets), - DaemonSets: len(kubeResources.DaemonSets), - Jobs: len(kubeResources.Jobs), - CronJobs: len(kubeResources.CronJobs), - ReplicationControllers: len(kubeResources.ReplicationControllers), + Version: kubeResources.ServerVersion, + Nodes: len(kubeResources.Nodes), + Pods: len(kubeResources.Pods), + Namespaces: len(kubeResources.Namespaces), + Controllers: len(results), }, Results: results, } diff --git a/pkg/validator/fullaudit_test.go b/pkg/validator/fullaudit_test.go index c5cdd7f8..3b3750d4 100644 --- a/pkg/validator/fullaudit_test.go +++ b/pkg/validator/fullaudit_test.go @@ -13,7 +13,7 @@ func TestGetTemplateData(t *testing.T) { k8s := test.SetupTestAPI() k8s = test.SetupAddControllers(k8s, "test") k8s = test.SetupAddExtraControllerVersions(k8s, "test-extra") - resources, err := kube.CreateResourceProviderFromAPI(k8s, "test") + resources, err := kube.CreateResourceProviderFromAPI(k8s, "test", nil) assert.Equal(t, err, nil, "error should be nil") c := conf.Configuration{ @@ -33,8 +33,8 @@ func TestGetTemplateData(t *testing.T) { sum := CountSummary{ Successes: uint(0), - Warnings: uint(9), - Errors: uint(9), + Warnings: uint(1), + Errors: uint(1), } actualAudit, err := RunAudit(c, resources) @@ -48,17 +48,7 @@ func TestGetTemplateData(t *testing.T) { kind string results int }{ - {kind: "Deployment", results: 2}, - {kind: "Deployment", results: 2}, - {kind: "Deployment", results: 2}, - {kind: "StatefulSet", results: 2}, - {kind: "StatefulSet", results: 2}, - {kind: "StatefulSet", results: 2}, - {kind: "DaemonSet", results: 2}, - {kind: "DaemonSet", results: 2}, - {kind: "Job", results: 0}, - {kind: "CronJob", results: 0}, - {kind: "ReplicationController", results: 2}, + {kind: "NakedPod", results: 2}, } assert.Equal(t, len(expected), len(actualAudit.Results)) diff --git a/pkg/validator/output.go b/pkg/validator/output.go index 9df68e38..845daaeb 100644 --- a/pkg/validator/output.go +++ b/pkg/validator/output.go @@ -15,8 +15,9 @@ package validator import ( - "github.com/fairwindsops/polaris/pkg/config" "time" + + "github.com/fairwindsops/polaris/pkg/config" ) const ( @@ -37,16 +38,11 @@ type AuditData struct { // ClusterInfo contains Polaris results as well as some high-level stats type ClusterInfo struct { - Version string - Nodes int - Pods int - Namespaces int - Deployments int - StatefulSets int - DaemonSets int - Jobs int - CronJobs int - ReplicationControllers int + Version string + Nodes int + Pods int + Namespaces int + Controllers int } // ResultMessage is the result of a given check diff --git a/pkg/validator/pod.go b/pkg/validator/pod.go index 420ef39f..32d9e29e 100644 --- a/pkg/validator/pod.go +++ b/pkg/validator/pod.go @@ -25,7 +25,6 @@ func ValidatePod(conf *config.Configuration, controller controllers.Interface) ( if err != nil { return PodResult{}, err } - pRes := PodResult{ Results: podResults, ContainerResults: []ContainerResult{}, @@ -35,6 +34,5 @@ func ValidatePod(conf *config.Configuration, controller controllers.Interface) ( if err != nil { return pRes, err } - return pRes, nil } diff --git a/test/fixtures.go b/test/fixtures.go index 303b0613..272ab38c 100644 --- a/test/fixtures.go +++ b/test/fixtures.go @@ -32,6 +32,13 @@ func MockPod() corev1.PodTemplateSpec { return p } +// MockNakedPod created a pod object. +func MockNakedPod() corev1.Pod { + return corev1.Pod{ + Spec: MockPod().Spec, + } +} + // MockDeploy creates a Deployment object. func MockDeploy() appsv1.Deployment { p := MockPod() @@ -132,6 +139,11 @@ func SetupAddControllers(k kubernetes.Interface, namespace string) kubernetes.In panic(err) } + p1 := MockNakedPod() + if _, err := k.CoreV1().Pods(namespace).Create(&p1); err != nil { + panic(err) + } + return k } From 3c685279be9ef1b760f79db473fa8b2f519fc981 Mon Sep 17 00:00:00 2001 From: Bader Boland Date: Tue, 17 Mar 2020 12:41:44 -0400 Subject: [PATCH 08/16] Cut out logic specific to controller types --- go.mod | 1 + go.sum | 2 + pkg/kube/resources.go | 240 ++++++++----------------------------- pkg/kube/resources_test.go | 20 +--- 4 files changed, 59 insertions(+), 204 deletions(-) diff --git a/go.mod b/go.mod index 4e947447..5e78c882 100644 --- a/go.mod +++ b/go.mod @@ -85,6 +85,7 @@ require ( google.golang.org/grpc v1.20.1 gopkg.in/inf.v0 v0.9.1 gopkg.in/yaml.v2 v2.2.2 + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c k8s.io/api v0.0.0-20181213150558-05914d821849 k8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93 k8s.io/client-go v0.0.0-20181213151034-8d9ed539ba31 diff --git a/go.sum b/go.sum index 59ea8831..5fbca1e7 100644 --- a/go.sum +++ b/go.sum @@ -452,6 +452,8 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/kube/resources.go b/pkg/kube/resources.go index d8413823..869ad2bb 100644 --- a/pkg/kube/resources.go +++ b/pkg/kube/resources.go @@ -2,7 +2,6 @@ package kube import ( "bytes" - "encoding/json" "io/ioutil" "os" "path/filepath" @@ -11,9 +10,7 @@ import ( "time" "github.com/sirupsen/logrus" - appsv1 "k8s.io/api/apps/v1" - batchv1 "k8s.io/api/batch/v1" - batchv1beta1 "k8s.io/api/batch/v1beta1" + "gopkg.in/yaml.v3" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -27,21 +24,15 @@ import ( // ResourceProvider contains k8s resources to be audited type ResourceProvider struct { - ServerVersion string - CreationTime time.Time - SourceName string - SourceType string - Nodes []corev1.Node - Deployments []appsv1.Deployment - StatefulSets []appsv1.StatefulSet - DaemonSets []appsv1.DaemonSet - Jobs []batchv1.Job - CronJobs []batchv1beta1.CronJob - ReplicationControllers []corev1.ReplicationController - Namespaces []corev1.Namespace - Pods []corev1.Pod - DynamicClient *dynamic.Interface - RestMapper *meta.RESTMapper + ServerVersion string + CreationTime time.Time + SourceName string + SourceType string + Nodes []corev1.Node + Namespaces []corev1.Namespace + Pods []corev1.Pod + DynamicClient *dynamic.Interface + RestMapper *meta.RESTMapper } type k8sResource struct { @@ -59,18 +50,12 @@ func CreateResourceProvider(directory string) (*ResourceProvider, error) { // CreateResourceProviderFromPath returns a new ResourceProvider using the YAML files in a directory func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error) { resources := ResourceProvider{ - ServerVersion: "unknown", - SourceType: "Path", - SourceName: directory, - Nodes: []corev1.Node{}, - Deployments: []appsv1.Deployment{}, - StatefulSets: []appsv1.StatefulSet{}, - DaemonSets: []appsv1.DaemonSet{}, - Jobs: []batchv1.Job{}, - CronJobs: []batchv1beta1.CronJob{}, - ReplicationControllers: []corev1.ReplicationController{}, - Namespaces: []corev1.Namespace{}, - Pods: []corev1.Pod{}, + ServerVersion: "unknown", + SourceType: "Path", + SourceName: directory, + Nodes: []corev1.Node{}, + Namespaces: []corev1.Namespace{}, + Pods: []corev1.Pod{}, } addYaml := func(contents string) error { @@ -135,11 +120,7 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string logrus.Errorf("Error fetching Cluster API version: %v", err) return nil, err } - jobs, err := kube.BatchV1().Jobs("").List(listOpts) - if err != nil { - logrus.Errorf("Error fetching Jobs: %v", err) - return nil, err - } + nodes, err := kube.CoreV1().Nodes().List(listOpts) if err != nil { logrus.Errorf("Error fetching Nodes: %v", err) @@ -168,7 +149,6 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string SourceType: "Cluster", SourceName: clusterName, CreationTime: time.Now(), - Jobs: jobs.Items, Nodes: nodes.Items, Namespaces: namespaces.Items, Pods: pods.Items, @@ -178,41 +158,28 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string return &api, nil } +func getPodSpec(yaml map[string]interface{}) interface{} { + if childYaml, ok := yaml["spec"]; ok { + return getPodSpec(childYaml.(map[string]interface{})) + } + if childYaml, ok := yaml["template"]; ok { + return getPodSpec(childYaml.(map[string]interface{})) + } + return yaml +} + func addResourceFromString(contents string, resources *ResourceProvider) error { contentBytes := []byte(contents) decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(contentBytes), 1000) resource := k8sResource{} err := decoder.Decode(&resource) + decoder = k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(contentBytes), 1000) + if err != nil { logrus.Errorf("Invalid YAML: %s", string(contents)) return err } - decoder = k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(contentBytes), 1000) - if resource.Kind == "Deployment" { - controller := appsv1.Deployment{} - err = decoder.Decode(&controller) - resources.Deployments = append(resources.Deployments, controller) - } else if resource.Kind == "StatefulSet" { - controller := appsv1.StatefulSet{} - err = decoder.Decode(&controller) - resources.StatefulSets = append(resources.StatefulSets, controller) - } else if resource.Kind == "DaemonSet" { - controller := appsv1.DaemonSet{} - err = decoder.Decode(&controller) - resources.DaemonSets = append(resources.DaemonSets, controller) - } else if resource.Kind == "Job" { - controller := batchv1.Job{} - err = decoder.Decode(&controller) - resources.Jobs = append(resources.Jobs, controller) - } else if resource.Kind == "CronJob" { - controller := batchv1beta1.CronJob{} - err = decoder.Decode(&controller) - resources.CronJobs = append(resources.CronJobs, controller) - } else if resource.Kind == "ReplicationController" { - controller := corev1.ReplicationController{} - err = decoder.Decode(&controller) - resources.ReplicationControllers = append(resources.ReplicationControllers, controller) - } else if resource.Kind == "Namespace" { + if resource.Kind == "Namespace" { ns := corev1.Namespace{} err = decoder.Decode(&ns) resources.Namespaces = append(resources.Namespaces, ns) @@ -220,130 +187,27 @@ func addResourceFromString(contents string, resources *ResourceProvider) error { pod := corev1.Pod{} err = decoder.Decode(&pod) resources.Pods = append(resources.Pods, pod) + } else { + yamlNode := make(map[string]interface{}) + err = yaml.Unmarshal(contentBytes, &yamlNode) + if err != nil { + logrus.Errorf("Invalid YAML: %s", string(contents)) + return err + } + finalDoc := make(map[string]interface{}) + finalDoc["metadata"] = yamlNode["metadata"] + finalDoc["apiVersion"] = "v1" + finalDoc["kind"] = "Pod" + finalDoc["spec"] = getPodSpec(yamlNode) + marshelledYaml, err := yaml.Marshal(finalDoc) + if err != nil { + logrus.Errorf("Could not marshell yaml: %v", err) + return err + } + decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(marshelledYaml), 1000) + 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 -} - -func getDeployments(kube kubernetes.Interface) ([]appsv1.Deployment, error) { - listOpts := metav1.ListOptions{} - deployList, err := kube.AppsV1().Deployments("").List(listOpts) - if err != nil { - logrus.Errorf("Error fetching Deployments: %v", err) - return nil, err - } - deploys := deployList.Items - - oldDeploys := make([]interface{}, 0) - deploysV1B1, err := kube.AppsV1beta1().Deployments("").List(listOpts) - if err != nil { - logrus.Errorf("Error fetching Deployments v1beta1: %v", err) - return nil, err - } - for _, oldDeploy := range deploysV1B1.Items { - oldDeploys = append(oldDeploys, oldDeploy) - } - deploysV1B2, err := kube.AppsV1beta2().Deployments("").List(listOpts) - if err != nil { - logrus.Errorf("Error fetching Deployments v1beta2: %v", err) - return nil, err - } - for _, oldDeploy := range deploysV1B2.Items { - oldDeploys = append(oldDeploys, oldDeploy) - } - - for _, oldDeploy := range oldDeploys { - str, err := json.Marshal(oldDeploy) - if err != nil { - logrus.Errorf("Error marshaling old deployment version: %v", err) - return nil, err - } - deploy := appsv1.Deployment{} - err = json.Unmarshal(str, &deploy) - if err != nil { - logrus.Errorf("Error unmarshaling old deployment version: %v", err) - return nil, err - } - deploys = append(deploys, deploy) - } - return deploys, nil -} - -func getStatefulSets(kube kubernetes.Interface) ([]appsv1.StatefulSet, error) { - listOpts := metav1.ListOptions{} - controllerList, err := kube.AppsV1().StatefulSets("").List(listOpts) - if err != nil { - logrus.Errorf("Error fetching StatefulSets: %v", err) - return nil, err - } - controllers := controllerList.Items - - oldControllers := make([]interface{}, 0) - controllersV1B1, err := kube.AppsV1beta1().StatefulSets("").List(listOpts) - if err != nil { - logrus.Errorf("Error fetching StatefulSets v1beta1: %v", err) - return nil, err - } - for _, oldController := range controllersV1B1.Items { - oldControllers = append(oldControllers, oldController) - } - controllersV1B2, err := kube.AppsV1beta2().StatefulSets("").List(listOpts) - if err != nil { - logrus.Errorf("Error fetching StatefulSets v1beta2: %v", err) - return nil, err - } - for _, oldController := range controllersV1B2.Items { - oldControllers = append(oldControllers, oldController) - } - - for _, oldController := range oldControllers { - str, err := json.Marshal(oldController) - if err != nil { - logrus.Errorf("Error marshaling old StatefulSet version: %v", err) - return nil, err - } - controller := appsv1.StatefulSet{} - err = json.Unmarshal(str, &controller) - if err != nil { - logrus.Errorf("Error unmarshaling old StatefulSet version: %v", err) - return nil, err - } - controllers = append(controllers, controller) - } - return controllers, nil -} - -func getDaemonSets(kube kubernetes.Interface) ([]appsv1.DaemonSet, error) { - listOpts := metav1.ListOptions{} - controllerList, err := kube.AppsV1().DaemonSets("").List(listOpts) - if err != nil { - logrus.Errorf("Error fetching DaemonSets: %v", err) - return nil, err - } - controllers := controllerList.Items - - controllersV1B2, err := kube.AppsV1beta2().DaemonSets("").List(listOpts) - if err != nil { - logrus.Errorf("Error fetching DaemonSets v1beta2: %v", err) - return nil, err - } - - for _, oldController := range controllersV1B2.Items { - str, err := json.Marshal(oldController) - if err != nil { - logrus.Errorf("Error marshaling old DaemonSet version: %v", err) - return nil, err - } - controller := appsv1.DaemonSet{} - err = json.Unmarshal(str, &controller) - if err != nil { - logrus.Errorf("Error unmarshaling old DaemonSet version: %v", err) - return nil, err - } - controllers = append(controllers, controller) - } - return controllers, nil + return err } diff --git a/pkg/kube/resources_test.go b/pkg/kube/resources_test.go index ee9ccb08..27638369 100644 --- a/pkg/kube/resources_test.go +++ b/pkg/kube/resources_test.go @@ -20,18 +20,10 @@ func TestGetResourcesFromPath(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, "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) - - assert.Equal(t, 2, len(resources.Pods), "Should have two pods") + assert.Equal(t, 8, len(resources.Pods), "Should have two pods") assert.Equal(t, "", resources.Pods[0].ObjectMeta.Namespace, "Should have one pod in default namespace") - assert.Equal(t, "two", resources.Pods[1].ObjectMeta.Namespace, "Should have one pod in namespace 'two'") + + assert.Equal(t, "two", resources.Pods[5].ObjectMeta.Namespace, "Should have one pod in namespace 'two'") } func TestGetMultipleResourceFromSingleFile(t *testing.T) { @@ -46,10 +38,6 @@ func TestGetMultipleResourceFromSingleFile(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, "dashboard", resources.Deployments[0].Spec.Template.Spec.Containers[0].Name) - - assert.Equal(t, 2, len(resources.Namespaces), "Should have a namespace") assert.Equal(t, "polaris", resources.Namespaces[0].ObjectMeta.Name) assert.Equal(t, "polaris-2", resources.Namespaces[1].ObjectMeta.Name) } @@ -70,6 +58,6 @@ 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, 0, len(resources.Pods), "Should have a pod") + assert.Equal(t, 1, len(resources.Pods), "Should have a pod") } From 8c769e445cb20596f8beb4f59127d7f44492938c Mon Sep 17 00:00:00 2001 From: Bader Boland Date: Tue, 17 Mar 2020 13:23:58 -0400 Subject: [PATCH 09/16] Cut out duplicitive code. --- pkg/kube/resources.go | 10 +++--- pkg/validator/controllers/cronjob.go | 36 +++++-------------- pkg/validator/controllers/daemonset.go | 29 +++------------ pkg/validator/controllers/deployment.go | 35 ++++-------------- pkg/validator/controllers/interface.go | 22 ++++++++++-- pkg/validator/controllers/job.go | 29 +++------------ pkg/validator/controllers/naked-pod.go | 35 +++++------------- .../controllers/replicationcontroller.go | 31 +++------------- pkg/validator/controllers/statefulsets.go | 30 +++------------- 9 files changed, 65 insertions(+), 192 deletions(-) diff --git a/pkg/kube/resources.go b/pkg/kube/resources.go index 869ad2bb..84e65db6 100644 --- a/pkg/kube/resources.go +++ b/pkg/kube/resources.go @@ -159,11 +159,11 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string } func getPodSpec(yaml map[string]interface{}) interface{} { - if childYaml, ok := yaml["spec"]; ok { - return getPodSpec(childYaml.(map[string]interface{})) - } - if childYaml, ok := yaml["template"]; ok { - return getPodSpec(childYaml.(map[string]interface{})) + allowedChildren := []string{"jobTemplate", "spec", "template"} + for _, child := range allowedChildren { + if childYaml, ok := yaml[child]; ok { + return getPodSpec(childYaml.(map[string]interface{})) + } } return yaml } diff --git a/pkg/validator/controllers/cronjob.go b/pkg/validator/controllers/cronjob.go index 885ae359..77ee1644 100644 --- a/pkg/validator/controllers/cronjob.go +++ b/pkg/validator/controllers/cronjob.go @@ -3,36 +3,16 @@ package controllers import ( "github.com/fairwindsops/polaris/pkg/config" kubeAPIBatchV1beta1 "k8s.io/api/batch/v1beta1" - kubeAPICoreV1 "k8s.io/api/core/v1" - kubeAPIMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// CronJobController is an implementation of controller for deployments -type CronJobController struct { - GenericController - K8SResource kubeAPIBatchV1beta1.CronJob -} - -// GetPodSpec returns the original kubernetes template pod spec -func (c CronJobController) GetPodSpec() *kubeAPICoreV1.PodSpec { - return &c.K8SResource.Spec.JobTemplate.Spec.Template.Spec -} - -// GetKind returns the supportedcontroller enum type -func (c CronJobController) GetKind() config.SupportedController { - return config.CronJobs -} - -// GetObjectMeta returns the metadata -func (c CronJobController) GetObjectMeta() kubeAPIMetaV1.ObjectMeta { - return c.K8SResource.ObjectMeta -} - // NewCronJobController builds a new controller interface for Deployments -func NewCronJobController(originalDeploymentResource kubeAPIBatchV1beta1.CronJob) Interface { - controller := CronJobController{} - controller.Name = originalDeploymentResource.Name - controller.Namespace = originalDeploymentResource.Namespace - controller.K8SResource = originalDeploymentResource +func NewCronJobController(originalResource kubeAPIBatchV1beta1.CronJob) Interface { + controller := GenericController{} + controller.Name = originalResource.Name + controller.Namespace = originalResource.Namespace + controller.PodSpec = originalResource.Spec.JobTemplate.Spec.Template.Spec + controller.ObjectMeta = originalResource.ObjectMeta + controller.Kind = config.CronJobs + return controller } diff --git a/pkg/validator/controllers/daemonset.go b/pkg/validator/controllers/daemonset.go index 96d8f7d5..e3aaabe5 100644 --- a/pkg/validator/controllers/daemonset.go +++ b/pkg/validator/controllers/daemonset.go @@ -3,36 +3,15 @@ package controllers import ( "github.com/fairwindsops/polaris/pkg/config" kubeAPIAppsV1 "k8s.io/api/apps/v1" - kubeAPICoreV1 "k8s.io/api/core/v1" - kubeAPIMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// DaemonSetController is an implementation of controller for deployments -type DaemonSetController struct { - GenericController - K8SResource kubeAPIAppsV1.DaemonSet -} - -// GetPodSpec returns the original kubernetes template pod spec -func (d DaemonSetController) GetPodSpec() *kubeAPICoreV1.PodSpec { - return &d.K8SResource.Spec.Template.Spec -} - -// GetObjectMeta returns the metadata -func (d DaemonSetController) GetObjectMeta() kubeAPIMetaV1.ObjectMeta { - return d.K8SResource.ObjectMeta -} - -// GetKind returns the supportedcontroller enum type -func (d DaemonSetController) GetKind() config.SupportedController { - return config.DaemonSets -} - // NewDaemonSetController builds a new controller interface for Deployments func NewDaemonSetController(originalResource kubeAPIAppsV1.DaemonSet) Interface { - controller := DaemonSetController{} + controller := GenericController{} controller.Name = originalResource.Name controller.Namespace = originalResource.Namespace - controller.K8SResource = originalResource + controller.PodSpec = originalResource.Spec.Template.Spec + controller.ObjectMeta = originalResource.ObjectMeta + controller.Kind = config.DaemonSets return controller } diff --git a/pkg/validator/controllers/deployment.go b/pkg/validator/controllers/deployment.go index a2d4d5e9..aa0f68e2 100644 --- a/pkg/validator/controllers/deployment.go +++ b/pkg/validator/controllers/deployment.go @@ -3,36 +3,15 @@ package controllers import ( "github.com/fairwindsops/polaris/pkg/config" kubeAPIAppsV1 "k8s.io/api/apps/v1" - kubeAPICoreV1 "k8s.io/api/core/v1" - kubeAPIMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// DeploymentController is an implementation of controller for deployments -type DeploymentController struct { - GenericController - K8SResource kubeAPIAppsV1.Deployment -} - -// GetPodSpec returns the original kubernetes template pod spec -func (d DeploymentController) GetPodSpec() *kubeAPICoreV1.PodSpec { - return &d.K8SResource.Spec.Template.Spec -} - -// GetObjectMeta returns the metadata -func (d DeploymentController) GetObjectMeta() kubeAPIMetaV1.ObjectMeta { - return d.K8SResource.ObjectMeta -} - -// GetKind returns the supportedcontroller enum type -func (d DeploymentController) GetKind() config.SupportedController { - return config.Deployments -} - // NewDeploymentController builds a new controller interface for Deployments -func NewDeploymentController(originalDeploymentResource kubeAPIAppsV1.Deployment) Interface { - controller := DeploymentController{} - controller.Name = originalDeploymentResource.Name - controller.Namespace = originalDeploymentResource.Namespace - controller.K8SResource = originalDeploymentResource +func NewDeploymentController(originalResource kubeAPIAppsV1.Deployment) Interface { + controller := GenericController{} + controller.Name = originalResource.Name + controller.Namespace = originalResource.Namespace + controller.PodSpec = originalResource.Spec.Template.Spec + controller.ObjectMeta = originalResource.ObjectMeta + controller.Kind = config.Deployments return controller } diff --git a/pkg/validator/controllers/interface.go b/pkg/validator/controllers/interface.go index 9b3f6d7e..1d8190e3 100644 --- a/pkg/validator/controllers/interface.go +++ b/pkg/validator/controllers/interface.go @@ -18,8 +18,26 @@ type Interface interface { // GenericController is a base implementation with some free methods for inherited structs type GenericController struct { - Name string - Namespace string + Name string + Namespace string + PodSpec kubeAPICoreV1.PodSpec + ObjectMeta kubeAPIMetaV1.ObjectMeta + Kind config.SupportedController +} + +// GetPodSpec returns the original kubernetes template pod spec +func (g GenericController) GetPodSpec() *kubeAPICoreV1.PodSpec { + return &g.PodSpec +} + +// GetObjectMeta returns the metadata +func (g GenericController) GetObjectMeta() kubeAPIMetaV1.ObjectMeta { + return g.ObjectMeta +} + +// GetKind returns the supportedcontroller enum type +func (g GenericController) GetKind() config.SupportedController { + return g.Kind } // GetName is inherited by all controllers using generic controller to get the name of the controller diff --git a/pkg/validator/controllers/job.go b/pkg/validator/controllers/job.go index 699f8588..ecebce36 100644 --- a/pkg/validator/controllers/job.go +++ b/pkg/validator/controllers/job.go @@ -3,36 +3,15 @@ package controllers import ( "github.com/fairwindsops/polaris/pkg/config" kubeAPIBatchV1 "k8s.io/api/batch/v1" - kubeAPICoreV1 "k8s.io/api/core/v1" - kubeAPIMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// JobController is an implementation of controller for deployments -type JobController struct { - GenericController - K8SResource kubeAPIBatchV1.Job -} - -// GetPodSpec returns the original kubernetes template pod spec -func (j JobController) GetPodSpec() *kubeAPICoreV1.PodSpec { - return &j.K8SResource.Spec.Template.Spec -} - -// GetObjectMeta returns the metadata -func (j JobController) GetObjectMeta() kubeAPIMetaV1.ObjectMeta { - return j.K8SResource.ObjectMeta -} - -// GetKind returns the supportedcontroller enum type -func (j JobController) GetKind() config.SupportedController { - return config.Jobs -} - // NewJobController builds a new controller interface for Deployments func NewJobController(originalResource kubeAPIBatchV1.Job) Interface { - controller := JobController{} + controller := GenericController{} controller.Name = originalResource.Name controller.Namespace = originalResource.Namespace - controller.K8SResource = originalResource + controller.PodSpec = originalResource.Spec.Template.Spec + controller.ObjectMeta = originalResource.ObjectMeta + controller.Kind = config.Jobs return controller } diff --git a/pkg/validator/controllers/naked-pod.go b/pkg/validator/controllers/naked-pod.go index 6086f15a..da5fddaa 100644 --- a/pkg/validator/controllers/naked-pod.go +++ b/pkg/validator/controllers/naked-pod.go @@ -3,35 +3,16 @@ package controllers import ( "github.com/fairwindsops/polaris/pkg/config" kubeAPICoreV1 "k8s.io/api/core/v1" - kubeAPIMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// NakedPodController is an implementation of controller for deployments -type NakedPodController struct { - GenericController - K8SResource kubeAPICoreV1.Pod -} - -// GetPodSpec returns the original kubernetes template pod spec -func (n NakedPodController) GetPodSpec() *kubeAPICoreV1.PodSpec { - return &n.K8SResource.Spec -} - -// GetObjectMeta returns the metadata -func (n NakedPodController) GetObjectMeta() kubeAPIMetaV1.ObjectMeta { - return n.K8SResource.ObjectMeta -} - -// GetKind returns the supportedcontroller enum type -func (n NakedPodController) GetKind() config.SupportedController { - return config.NakedPods -} - // NewNakedPodController builds a new controller interface for NakedPods -func NewNakedPodController(originalNakedPodResource kubeAPICoreV1.Pod) Interface { - controller := NakedPodController{} - controller.Name = originalNakedPodResource.Name - controller.Namespace = originalNakedPodResource.Namespace - controller.K8SResource = originalNakedPodResource +func NewNakedPodController(originalResource kubeAPICoreV1.Pod) Interface { + controller := GenericController{} + controller.Name = originalResource.Name + controller.Namespace = originalResource.Namespace + controller.PodSpec = originalResource.Spec + controller.ObjectMeta = originalResource.ObjectMeta + controller.Kind = config.NakedPods + return controller } diff --git a/pkg/validator/controllers/replicationcontroller.go b/pkg/validator/controllers/replicationcontroller.go index 43cf110e..310f627d 100644 --- a/pkg/validator/controllers/replicationcontroller.go +++ b/pkg/validator/controllers/replicationcontroller.go @@ -3,38 +3,15 @@ package controllers import ( "github.com/fairwindsops/polaris/pkg/config" kubeAPICoreV1 "k8s.io/api/core/v1" - kubeAPIMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// NOTE: Maybe this name of ReplicationController is duplicative but it's more explicit since -// that's how kubernetes refers the the object. - -// ReplicationControllerController is an implementation of controller for deployments -type ReplicationControllerController struct { - GenericController - K8SResource kubeAPICoreV1.ReplicationController -} - -// GetPodSpec returns the original kubernetes template pod spec -func (r ReplicationControllerController) GetPodSpec() *kubeAPICoreV1.PodSpec { - return &r.K8SResource.Spec.Template.Spec -} - -// GetObjectMeta returns the metadata -func (r ReplicationControllerController) GetObjectMeta() kubeAPIMetaV1.ObjectMeta { - return r.K8SResource.ObjectMeta -} - -// GetKind returns the supportedcontroller enum type -func (r ReplicationControllerController) GetKind() config.SupportedController { - return config.ReplicationControllers -} - // NewReplicationControllerController builds a new controller interface for Deployments func NewReplicationControllerController(originalResource kubeAPICoreV1.ReplicationController) Interface { - controller := ReplicationControllerController{} + controller := GenericController{} controller.Name = originalResource.Name controller.Namespace = originalResource.Namespace - controller.K8SResource = originalResource + controller.PodSpec = originalResource.Spec.Template.Spec + controller.ObjectMeta = originalResource.ObjectMeta + controller.Kind = config.ReplicationControllers return controller } diff --git a/pkg/validator/controllers/statefulsets.go b/pkg/validator/controllers/statefulsets.go index e49598f1..0a25c33c 100644 --- a/pkg/validator/controllers/statefulsets.go +++ b/pkg/validator/controllers/statefulsets.go @@ -3,36 +3,16 @@ package controllers import ( "github.com/fairwindsops/polaris/pkg/config" kubeAPIAppsV1 "k8s.io/api/apps/v1" - kubeAPICoreV1 "k8s.io/api/core/v1" - kubeAPIMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// StatefulSetController is an implementation of controller for deployments -type StatefulSetController struct { - GenericController - K8SResource kubeAPIAppsV1.StatefulSet -} - -// GetPodSpec returns the podspec from the original kubernetes resource -func (s StatefulSetController) GetPodSpec() *kubeAPICoreV1.PodSpec { - return &s.K8SResource.Spec.Template.Spec -} - -// GetObjectMeta returns the metadata -func (s StatefulSetController) GetObjectMeta() kubeAPIMetaV1.ObjectMeta { - return s.K8SResource.ObjectMeta -} - -// GetKind returns the supportedcontroller enum type -func (s StatefulSetController) GetKind() config.SupportedController { - return config.StatefulSets -} - // NewStatefulSetController builds a statefulset controller func NewStatefulSetController(originalResource kubeAPIAppsV1.StatefulSet) Interface { - controller := StatefulSetController{} + controller := GenericController{} controller.Name = originalResource.Name controller.Namespace = originalResource.Namespace - controller.K8SResource = originalResource + controller.PodSpec = originalResource.Spec.Template.Spec + controller.ObjectMeta = originalResource.ObjectMeta + controller.Kind = config.StatefulSets + return controller } From fb0d7c5d82198160b413ec7eb530ff8209e67063 Mon Sep 17 00:00:00 2001 From: Bader Boland Date: Tue, 17 Mar 2020 16:19:58 -0400 Subject: [PATCH 10/16] Add additional logging --- pkg/validator/controller.go | 47 ++++++++++++----------- pkg/validator/controllers/cronjob.go | 5 ++- pkg/validator/controllers/statefulsets.go | 5 ++- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/pkg/validator/controller.go b/pkg/validator/controller.go index dccd44a0..59c02a44 100644 --- a/pkg/validator/controller.go +++ b/pkg/validator/controller.go @@ -36,13 +36,17 @@ func ValidateController(conf *conf.Configuration, controller controller.Interfac return ControllerResult{}, err } result := ControllerResult{ - Kind: controller.GetKind().String(), - Name: controller.GetName(), - Namespace: controller.GetObjectMeta().Namespace, - Results: ResultSet{}, - PodResult: podResult, - CreatedTime: controller.GetObjectMeta().CreationTimestamp.Time, + Kind: controller.GetKind().String(), + Name: controller.GetName(), + Namespace: controller.GetObjectMeta().Namespace, + Results: ResultSet{}, + PodResult: podResult, } + if kubeResources.DynamicClient == nil { + return result, nil + } + result.CreatedTime = controller.GetObjectMeta().CreationTimestamp.Time + owners := controller.GetObjectMeta().OwnerReferences // If an owner exists then set the name to the controller. // This allows us to handle CRDs creating Controllers or DeploymentConfigs in OpenShift. @@ -50,25 +54,22 @@ func ValidateController(conf *conf.Configuration, controller controller.Interfac firstOwner := owners[0] result.Kind = firstOwner.Kind result.Name = firstOwner.Name - if kubeResources.DynamicClient != nil { - dynamicClient := *kubeResources.DynamicClient - restMapper := *kubeResources.RestMapper - fqKind := schema.FromAPIVersionAndKind(firstOwner.APIVersion, firstOwner.Kind) - mapping, err := restMapper.RESTMapping(fqKind.GroupKind(), fqKind.Version) - if err != nil { - logrus.Warnf("Error retrieving mapping %s of API %s and Kind %s because of error: %v ", firstOwner.Name, firstOwner.APIVersion, firstOwner.Kind, err) - return result, nil - } - getParents, err := dynamicClient.Resource(mapping.Resource).Namespace(controller.GetObjectMeta().Namespace).Get(firstOwner.Name, metav1.GetOptions{}) - if err != nil { - logrus.Warnf("Error retrieving parent object %s of API %s and Kind %s because of error: %v ", firstOwner.Name, firstOwner.APIVersion, firstOwner.Kind, err) - return result, nil - } - owners = getParents.GetOwnerReferences() - } else { - break + dynamicClient := *kubeResources.DynamicClient + restMapper := *kubeResources.RestMapper + fqKind := schema.FromAPIVersionAndKind(firstOwner.APIVersion, firstOwner.Kind) + mapping, err := restMapper.RESTMapping(fqKind.GroupKind(), fqKind.Version) + if err != nil { + logrus.Warnf("Error retrieving mapping %s of API %s and Kind %s because of error: %v ", firstOwner.Name, firstOwner.APIVersion, firstOwner.Kind, err) + return result, nil } + getParents, err := dynamicClient.Resource(mapping.Resource).Namespace(controller.GetObjectMeta().Namespace).Get(firstOwner.Name, metav1.GetOptions{}) + if err != nil { + logrus.Warnf("Error retrieving parent object %s of API %s and Kind %s because of error: %v ", firstOwner.Name, firstOwner.APIVersion, firstOwner.Kind, err) + return result, nil + } + owners = getParents.GetOwnerReferences() + } return result, nil diff --git a/pkg/validator/controllers/cronjob.go b/pkg/validator/controllers/cronjob.go index 77ee1644..aa5f10d7 100644 --- a/pkg/validator/controllers/cronjob.go +++ b/pkg/validator/controllers/cronjob.go @@ -2,6 +2,7 @@ package controllers import ( "github.com/fairwindsops/polaris/pkg/config" + "github.com/sirupsen/logrus" kubeAPIBatchV1beta1 "k8s.io/api/batch/v1beta1" ) @@ -13,6 +14,8 @@ func NewCronJobController(originalResource kubeAPIBatchV1beta1.CronJob) Interfac controller.PodSpec = originalResource.Spec.JobTemplate.Spec.Template.Spec controller.ObjectMeta = originalResource.ObjectMeta controller.Kind = config.CronJobs - + if controller.Name == "" { + logrus.Warn("Name is missing from controller", originalResource.Namespace) + } return controller } diff --git a/pkg/validator/controllers/statefulsets.go b/pkg/validator/controllers/statefulsets.go index 0a25c33c..199d56c7 100644 --- a/pkg/validator/controllers/statefulsets.go +++ b/pkg/validator/controllers/statefulsets.go @@ -2,6 +2,7 @@ package controllers import ( "github.com/fairwindsops/polaris/pkg/config" + "github.com/sirupsen/logrus" kubeAPIAppsV1 "k8s.io/api/apps/v1" ) @@ -13,6 +14,8 @@ func NewStatefulSetController(originalResource kubeAPIAppsV1.StatefulSet) Interf controller.PodSpec = originalResource.Spec.Template.Spec controller.ObjectMeta = originalResource.ObjectMeta controller.Kind = config.StatefulSets - + if controller.Name == "" { + logrus.Warn("Name is missing from controller", originalResource.Namespace) + } return controller } From c43ace22a3fef8d9a8d273a5c00c8c7e6a42aa97 Mon Sep 17 00:00:00 2001 From: Bader Boland Date: Tue, 17 Mar 2020 16:59:15 -0400 Subject: [PATCH 11/16] Add support for CronJobs in support versions list --- pkg/config/supportedcontrollers.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/config/supportedcontrollers.go b/pkg/config/supportedcontrollers.go index 33eb8702..48a9cdde 100644 --- a/pkg/config/supportedcontrollers.go +++ b/pkg/config/supportedcontrollers.go @@ -10,6 +10,7 @@ import ( appsv1beta1 "k8s.io/api/apps/v1beta1" appsv1beta2 "k8s.io/api/apps/v1beta2" batchv1 "k8s.io/api/batch/v1" + batchV1beta1 "k8s.io/api/batch/v1beta1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -122,6 +123,10 @@ func (s SupportedController) ListSupportedAPIVersions() []runtime.Object { supportedVersions = []runtime.Object{ &batchv1.Job{}, } + case CronJobs: + supportedVersions = []runtime.Object{ + &batchV1beta1.CronJob{}, + } case ReplicationControllers: supportedVersions = []runtime.Object{ &corev1.ReplicationController{}, From 61ecb69ab1e172301fd411361a1f64ad5c7de80f Mon Sep 17 00:00:00 2001 From: Bader Boland Date: Tue, 17 Mar 2020 17:00:30 -0400 Subject: [PATCH 12/16] Adding support for v2alpha1 for cron --- pkg/config/supportedcontrollers.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/config/supportedcontrollers.go b/pkg/config/supportedcontrollers.go index 48a9cdde..921e1cc6 100644 --- a/pkg/config/supportedcontrollers.go +++ b/pkg/config/supportedcontrollers.go @@ -11,6 +11,7 @@ import ( appsv1beta2 "k8s.io/api/apps/v1beta2" batchv1 "k8s.io/api/batch/v1" batchV1beta1 "k8s.io/api/batch/v1beta1" + batchv2alpha1 "k8s.io/api/batch/v2alpha1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -126,6 +127,7 @@ func (s SupportedController) ListSupportedAPIVersions() []runtime.Object { case CronJobs: supportedVersions = []runtime.Object{ &batchV1beta1.CronJob{}, + &batchv2alpha1.CronJob{}, } case ReplicationControllers: supportedVersions = []runtime.Object{ From 68fe23018a0d36e5dfc5868836eb4f0796634c6d Mon Sep 17 00:00:00 2001 From: Bader Boland Date: Mon, 23 Mar 2020 09:27:36 -0400 Subject: [PATCH 13/16] Feedback from PR --- pkg/config/supportedcontrollers.go | 4 +- pkg/kube/resources.go | 25 ++--- pkg/validator/container.go | 4 +- pkg/validator/container_test.go | 14 +-- pkg/validator/controller.go | 42 +------ pkg/validator/controllers/cronjob.go | 2 +- pkg/validator/controllers/daemonset.go | 2 +- pkg/validator/controllers/deployment.go | 2 +- pkg/validator/controllers/generic.go | 106 ++++++++++++++++++ pkg/validator/controllers/interface.go | 60 ---------- pkg/validator/controllers/job.go | 2 +- pkg/validator/controllers/naked-pod.go | 2 +- .../controllers/replicationcontroller.go | 2 +- pkg/validator/controllers/statefulsets.go | 2 +- pkg/validator/fullaudit.go | 2 +- pkg/validator/pod.go | 2 +- pkg/validator/schema.go | 6 +- pkg/webhook/validator.go | 2 +- 18 files changed, 146 insertions(+), 135 deletions(-) create mode 100644 pkg/validator/controllers/generic.go delete mode 100644 pkg/validator/controllers/interface.go diff --git a/pkg/config/supportedcontrollers.go b/pkg/config/supportedcontrollers.go index 921e1cc6..2562859b 100644 --- a/pkg/config/supportedcontrollers.go +++ b/pkg/config/supportedcontrollers.go @@ -10,7 +10,7 @@ import ( appsv1beta1 "k8s.io/api/apps/v1beta1" appsv1beta2 "k8s.io/api/apps/v1beta2" batchv1 "k8s.io/api/batch/v1" - batchV1beta1 "k8s.io/api/batch/v1beta1" + batchv1beta1 "k8s.io/api/batch/v1beta1" batchv2alpha1 "k8s.io/api/batch/v2alpha1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" @@ -126,7 +126,7 @@ func (s SupportedController) ListSupportedAPIVersions() []runtime.Object { } case CronJobs: supportedVersions = []runtime.Object{ - &batchV1beta1.CronJob{}, + &batchv1beta1.CronJob{}, &batchv2alpha1.CronJob{}, } case ReplicationControllers: diff --git a/pkg/kube/resources.go b/pkg/kube/resources.go index 84e65db6..10467b4e 100644 --- a/pkg/kube/resources.go +++ b/pkg/kube/resources.go @@ -9,10 +9,10 @@ import ( "strings" "time" + "github.com/fairwindsops/polaris/pkg/validator/controllers" "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sYaml "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/client-go/dynamic" @@ -30,9 +30,7 @@ type ResourceProvider struct { SourceType string Nodes []corev1.Node Namespaces []corev1.Namespace - Pods []corev1.Pod - DynamicClient *dynamic.Interface - RestMapper *meta.RESTMapper + Controllers []controllers.GenericController } type k8sResource struct { @@ -55,7 +53,7 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error) SourceName: directory, Nodes: []corev1.Node{}, Namespaces: []corev1.Namespace{}, - Pods: []corev1.Pod{}, + Controllers: []controllers.GenericController{}, } addYaml := func(contents string) error { @@ -143,7 +141,6 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string return nil, err } restMapper := restmapper.NewDiscoveryRESTMapper(resources) - api := ResourceProvider{ ServerVersion: serverVersion.Major + "." + serverVersion.Minor, SourceType: "Cluster", @@ -151,9 +148,7 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string CreationTime: time.Now(), Nodes: nodes.Items, Namespaces: namespaces.Items, - Pods: pods.Items, - DynamicClient: dynamic, - RestMapper: &restMapper, + Controllers: controllers.LoadControllers(pods.Items, dynamic, &restMapper), } return &api, nil } @@ -186,7 +181,7 @@ func addResourceFromString(contents string, resources *ResourceProvider) error { } else if resource.Kind == "Pod" { pod := corev1.Pod{} err = decoder.Decode(&pod) - resources.Pods = append(resources.Pods, pod) + resources.Controllers = append(resources.Controllers, controllers.NewGenericPodController(pod, nil, nil)) } else { yamlNode := make(map[string]interface{}) err = yaml.Unmarshal(contentBytes, &yamlNode) @@ -199,15 +194,17 @@ func addResourceFromString(contents string, resources *ResourceProvider) error { finalDoc["apiVersion"] = "v1" finalDoc["kind"] = "Pod" finalDoc["spec"] = getPodSpec(yamlNode) - marshelledYaml, err := yaml.Marshal(finalDoc) + marshaledYaml, err := yaml.Marshal(finalDoc) if err != nil { - logrus.Errorf("Could not marshell yaml: %v", err) + logrus.Errorf("Could not marshal yaml: %v", err) return err } - decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(marshelledYaml), 1000) + decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(marshaledYaml), 1000) pod := corev1.Pod{} err = decoder.Decode(&pod) - resources.Pods = append(resources.Pods, pod) + newController := controllers.NewGenericPodController(pod, nil, nil) + newController.KindString = resource.Kind + resources.Controllers = append(resources.Controllers, newController) } return err } diff --git a/pkg/validator/container.go b/pkg/validator/container.go index 66f7f5a2..91add9ad 100644 --- a/pkg/validator/container.go +++ b/pkg/validator/container.go @@ -22,7 +22,7 @@ import ( ) // ValidateContainer validates a single container from a given controller -func ValidateContainer(conf *config.Configuration, controller controllers.Interface, container *corev1.Container, isInit bool) (ContainerResult, error) { +func ValidateContainer(conf *config.Configuration, controller controllers.GenericController, container *corev1.Container, isInit bool) (ContainerResult, error) { results, err := applyContainerSchemaChecks(conf, controller, container, isInit) if err != nil { return ContainerResult{}, err @@ -37,7 +37,7 @@ func ValidateContainer(conf *config.Configuration, controller controllers.Interf } // ValidateAllContainers validates both init and regular containers -func ValidateAllContainers(conf *config.Configuration, controller controllers.Interface) ([]ContainerResult, error) { +func ValidateAllContainers(conf *config.Configuration, controller controllers.GenericController) ([]ContainerResult, error) { results := []ContainerResult{} pod := controller.GetPodSpec() for _, container := range pod.InitContainers { diff --git a/pkg/validator/container_test.go b/pkg/validator/container_test.go index c298586b..f3f4d822 100644 --- a/pkg/validator/container_test.go +++ b/pkg/validator/container_test.go @@ -51,7 +51,7 @@ exemptions: - foo ` -func getEmptyController(name string) controllers.Interface { +func getEmptyController(name string) controllers.GenericController { return controllers.NewDeploymentController(appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -66,7 +66,7 @@ func testValidate(t *testing.T, container *corev1.Container, resourceConf *strin testValidateWithController(t, container, resourceConf, getEmptyController(controllerName), expectedErrors, expectedWarnings, expectedSuccesses) } -func testValidateWithController(t *testing.T, container *corev1.Container, resourceConf *string, controller controllers.Interface, expectedErrors []ResultMessage, expectedWarnings []ResultMessage, expectedSuccesses []ResultMessage) { +func testValidateWithController(t *testing.T, container *corev1.Container, resourceConf *string, controller controllers.GenericController, expectedErrors []ResultMessage, expectedWarnings []ResultMessage, expectedSuccesses []ResultMessage) { parsedConf, err := conf.Parse([]byte(*resourceConf)) assert.NoError(t, err, "Expected no error when parsing config") @@ -1184,18 +1184,18 @@ func TestValidateResourcesEmptyContainerCPURequestsExempt(t *testing.T) { } expectedSuccesses := []ResultMessage{} - + controller := controllers.NewDeploymentController(appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", - Annotations: map[string]string { - "polaris.fairwinds.com/cpuRequestsMissing-exempt": "true", // Exempt this controller from cpuRequestsMissing + Annotations: map[string]string{ + "polaris.fairwinds.com/cpuRequestsMissing-exempt": "true", // Exempt this controller from cpuRequestsMissing "polaris.fairwinds.com/memoryRequestsMissing-exempt": "truthy", // Don't actually exempt this controller from memoryRequestsMissing - } , + }, }, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{}, }, }) testValidateWithController(t, &container, &resourceConfMinimal, controller, expectedErrors, expectedWarnings, expectedSuccesses) -} \ No newline at end of file +} diff --git a/pkg/validator/controller.go b/pkg/validator/controller.go index 59c02a44..2869a808 100644 --- a/pkg/validator/controller.go +++ b/pkg/validator/controller.go @@ -18,59 +18,27 @@ import ( "strings" "github.com/sirupsen/logrus" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" conf "github.com/fairwindsops/polaris/pkg/config" "github.com/fairwindsops/polaris/pkg/kube" - "github.com/fairwindsops/polaris/pkg/validator/controllers" controller "github.com/fairwindsops/polaris/pkg/validator/controllers" ) const exemptionAnnotationKey = "polaris.fairwinds.com/exempt" // ValidateController validates a single controller, returns a ControllerResult. -func ValidateController(conf *conf.Configuration, controller controller.Interface, kubeResources *kube.ResourceProvider) (ControllerResult, error) { +func ValidateController(conf *conf.Configuration, controller controller.GenericController, kubeResources *kube.ResourceProvider) (ControllerResult, error) { podResult, err := ValidatePod(conf, controller) if err != nil { return ControllerResult{}, err } result := ControllerResult{ - Kind: controller.GetKind().String(), + Kind: controller.GetKindString(), Name: controller.GetName(), Namespace: controller.GetObjectMeta().Namespace, Results: ResultSet{}, PodResult: podResult, } - if kubeResources.DynamicClient == nil { - return result, nil - } - result.CreatedTime = controller.GetObjectMeta().CreationTimestamp.Time - - owners := controller.GetObjectMeta().OwnerReferences - // If an owner exists then set the name to the controller. - // This allows us to handle CRDs creating Controllers or DeploymentConfigs in OpenShift. - for len(owners) > 0 { - firstOwner := owners[0] - result.Kind = firstOwner.Kind - result.Name = firstOwner.Name - - dynamicClient := *kubeResources.DynamicClient - restMapper := *kubeResources.RestMapper - fqKind := schema.FromAPIVersionAndKind(firstOwner.APIVersion, firstOwner.Kind) - mapping, err := restMapper.RESTMapping(fqKind.GroupKind(), fqKind.Version) - if err != nil { - logrus.Warnf("Error retrieving mapping %s of API %s and Kind %s because of error: %v ", firstOwner.Name, firstOwner.APIVersion, firstOwner.Kind, err) - return result, nil - } - getParents, err := dynamicClient.Resource(mapping.Resource).Namespace(controller.GetObjectMeta().Namespace).Get(firstOwner.Name, metav1.GetOptions{}) - if err != nil { - logrus.Warnf("Error retrieving parent object %s of API %s and Kind %s because of error: %v ", firstOwner.Name, firstOwner.APIVersion, firstOwner.Kind, err) - return result, nil - } - owners = getParents.GetOwnerReferences() - - } return result, nil } @@ -103,8 +71,8 @@ func deduplicateControllers(controllerResults []ControllerResult) []ControllerRe // 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) ([]ControllerResult, error) { - var controllersToAudit []controller.Interface - loadedControllers := controllers.LoadControllers(kubeResources) + var controllersToAudit []controller.GenericController + loadedControllers := kubeResources.Controllers controllersToAudit = append(controllersToAudit, loadedControllers...) results := []ControllerResult{} @@ -123,7 +91,7 @@ func ValidateControllers(config *conf.Configuration, kubeResources *kube.Resourc return deduplicateControllers(results), nil } -func hasExemptionAnnotation(ctrl controller.Interface) bool { +func hasExemptionAnnotation(ctrl controller.GenericController) bool { annot := ctrl.GetObjectMeta().Annotations val := annot[exemptionAnnotationKey] return strings.ToLower(val) == "true" diff --git a/pkg/validator/controllers/cronjob.go b/pkg/validator/controllers/cronjob.go index aa5f10d7..76e7c20f 100644 --- a/pkg/validator/controllers/cronjob.go +++ b/pkg/validator/controllers/cronjob.go @@ -7,7 +7,7 @@ import ( ) // NewCronJobController builds a new controller interface for Deployments -func NewCronJobController(originalResource kubeAPIBatchV1beta1.CronJob) Interface { +func NewCronJobController(originalResource kubeAPIBatchV1beta1.CronJob) GenericController { controller := GenericController{} controller.Name = originalResource.Name controller.Namespace = originalResource.Namespace diff --git a/pkg/validator/controllers/daemonset.go b/pkg/validator/controllers/daemonset.go index e3aaabe5..40f6a44d 100644 --- a/pkg/validator/controllers/daemonset.go +++ b/pkg/validator/controllers/daemonset.go @@ -6,7 +6,7 @@ import ( ) // NewDaemonSetController builds a new controller interface for Deployments -func NewDaemonSetController(originalResource kubeAPIAppsV1.DaemonSet) Interface { +func NewDaemonSetController(originalResource kubeAPIAppsV1.DaemonSet) GenericController { controller := GenericController{} controller.Name = originalResource.Name controller.Namespace = originalResource.Namespace diff --git a/pkg/validator/controllers/deployment.go b/pkg/validator/controllers/deployment.go index aa0f68e2..6825d1c0 100644 --- a/pkg/validator/controllers/deployment.go +++ b/pkg/validator/controllers/deployment.go @@ -6,7 +6,7 @@ import ( ) // NewDeploymentController builds a new controller interface for Deployments -func NewDeploymentController(originalResource kubeAPIAppsV1.Deployment) Interface { +func NewDeploymentController(originalResource kubeAPIAppsV1.Deployment) GenericController { controller := GenericController{} controller.Name = originalResource.Name controller.Namespace = originalResource.Namespace diff --git a/pkg/validator/controllers/generic.go b/pkg/validator/controllers/generic.go new file mode 100644 index 00000000..953f0819 --- /dev/null +++ b/pkg/validator/controllers/generic.go @@ -0,0 +1,106 @@ +package controllers + +import ( + "time" + + "github.com/fairwindsops/polaris/pkg/config" + "github.com/sirupsen/logrus" + kubeAPICoreV1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + kubeAPIMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" +) + +// GenericController is a base implementation with some free methods for inherited structs +type GenericController struct { + Name string + Namespace string + PodSpec kubeAPICoreV1.PodSpec + ObjectMeta kubeAPIMetaV1.ObjectMeta + Kind config.SupportedController + KindString string + CreatedTime time.Time +} + +// GetPodSpec returns the original kubernetes template pod spec +func (g GenericController) GetPodSpec() *kubeAPICoreV1.PodSpec { + return &g.PodSpec +} + +// GetObjectMeta returns the metadata +func (g GenericController) GetObjectMeta() kubeAPIMetaV1.ObjectMeta { + return g.ObjectMeta +} + +// GetKind returns the supportedcontroller enum type +func (g GenericController) GetKind() config.SupportedController { + return g.Kind +} + +// GetKindString returns a string representing what kind of object the top level controller is. +func (g GenericController) GetKindString() string { + return g.KindString +} + +// GetName is inherited by all controllers using generic controller to get the name of the controller +func (g GenericController) GetName() string { + return g.Name +} + +// GetNamespace is inherited by all controllers using generic controller to get the namespace of the controller +func (g GenericController) GetNamespace() string { + return g.Namespace +} + +// LoadControllers loads a list of controllers from the kubeResources Pods +func LoadControllers(pods []kubeAPICoreV1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper) []GenericController { + interfaces := []GenericController{} + for _, pod := range pods { + interfaces = append(interfaces, NewGenericPodController(pod, dynamicClientPointer, restMapperPointer)) + } + // TODO DeDupe + return interfaces +} + +// NewGenericPodController builds a new controller interface for anytype of Pod +func NewGenericPodController(originalResource kubeAPICoreV1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper) GenericController { + controller := GenericController{} + controller.Name = originalResource.Name + controller.Namespace = originalResource.Namespace + controller.PodSpec = originalResource.Spec + controller.ObjectMeta = originalResource.ObjectMeta + controller.Kind = config.NakedPods + controller.KindString = "Pod" + controller.CreatedTime = controller.GetObjectMeta().CreationTimestamp.Time + + owners := controller.GetObjectMeta().OwnerReferences + if dynamicClientPointer == nil || restMapperPointer == nil { + return controller + } + // If an owner exists then set the name to the controller. + // This allows us to handle CRDs creating Controllers or DeploymentConfigs in OpenShift. + for len(owners) > 0 { + firstOwner := owners[0] + controller.KindString = firstOwner.Kind + controller.Name = firstOwner.Name + + dynamicClient := *dynamicClientPointer + restMapper := *restMapperPointer + fqKind := schema.FromAPIVersionAndKind(firstOwner.APIVersion, firstOwner.Kind) + mapping, err := restMapper.RESTMapping(fqKind.GroupKind(), fqKind.Version) + if err != nil { + logrus.Warnf("Error retrieving mapping %s of API %s and Kind %s because of error: %v ", firstOwner.Name, firstOwner.APIVersion, firstOwner.Kind, err) + return controller + } + getParents, err := dynamicClient.Resource(mapping.Resource).Namespace(controller.GetObjectMeta().Namespace).Get(firstOwner.Name, kubeAPIMetaV1.GetOptions{}) + if err != nil { + logrus.Warnf("Error retrieving parent object %s of API %s and Kind %s because of error: %v ", firstOwner.Name, firstOwner.APIVersion, firstOwner.Kind, err) + return controller + } + owners = getParents.GetOwnerReferences() + + } + + return controller +} diff --git a/pkg/validator/controllers/interface.go b/pkg/validator/controllers/interface.go deleted file mode 100644 index 1d8190e3..00000000 --- a/pkg/validator/controllers/interface.go +++ /dev/null @@ -1,60 +0,0 @@ -package controllers - -import ( - "github.com/fairwindsops/polaris/pkg/config" - "github.com/fairwindsops/polaris/pkg/kube" - kubeAPICoreV1 "k8s.io/api/core/v1" - kubeAPIMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// Interface is an interface for k8s controllers (e.g. Deployments and StatefulSets) -type Interface interface { - GetName() string - GetNamespace() string - GetPodSpec() *kubeAPICoreV1.PodSpec - GetKind() config.SupportedController - GetObjectMeta() kubeAPIMetaV1.ObjectMeta -} - -// GenericController is a base implementation with some free methods for inherited structs -type GenericController struct { - Name string - Namespace string - PodSpec kubeAPICoreV1.PodSpec - ObjectMeta kubeAPIMetaV1.ObjectMeta - Kind config.SupportedController -} - -// GetPodSpec returns the original kubernetes template pod spec -func (g GenericController) GetPodSpec() *kubeAPICoreV1.PodSpec { - return &g.PodSpec -} - -// GetObjectMeta returns the metadata -func (g GenericController) GetObjectMeta() kubeAPIMetaV1.ObjectMeta { - return g.ObjectMeta -} - -// GetKind returns the supportedcontroller enum type -func (g GenericController) GetKind() config.SupportedController { - return g.Kind -} - -// GetName is inherited by all controllers using generic controller to get the name of the controller -func (g GenericController) GetName() string { - return g.Name -} - -// GetNamespace is inherited by all controllers using generic controller to get the namespace of the controller -func (g GenericController) GetNamespace() string { - return g.Namespace -} - -// LoadControllers loads a list of controllers from the kubeResources Pods -func LoadControllers(kubeResources *kube.ResourceProvider) []Interface { - interfaces := []Interface{} - for _, pod := range kubeResources.Pods { - interfaces = append(interfaces, NewNakedPodController(pod)) - } - return interfaces -} diff --git a/pkg/validator/controllers/job.go b/pkg/validator/controllers/job.go index ecebce36..bd26f330 100644 --- a/pkg/validator/controllers/job.go +++ b/pkg/validator/controllers/job.go @@ -6,7 +6,7 @@ import ( ) // NewJobController builds a new controller interface for Deployments -func NewJobController(originalResource kubeAPIBatchV1.Job) Interface { +func NewJobController(originalResource kubeAPIBatchV1.Job) GenericController { controller := GenericController{} controller.Name = originalResource.Name controller.Namespace = originalResource.Namespace diff --git a/pkg/validator/controllers/naked-pod.go b/pkg/validator/controllers/naked-pod.go index da5fddaa..2b319cf7 100644 --- a/pkg/validator/controllers/naked-pod.go +++ b/pkg/validator/controllers/naked-pod.go @@ -6,7 +6,7 @@ import ( ) // NewNakedPodController builds a new controller interface for NakedPods -func NewNakedPodController(originalResource kubeAPICoreV1.Pod) Interface { +func NewNakedPodController(originalResource kubeAPICoreV1.Pod) GenericController { controller := GenericController{} controller.Name = originalResource.Name controller.Namespace = originalResource.Namespace diff --git a/pkg/validator/controllers/replicationcontroller.go b/pkg/validator/controllers/replicationcontroller.go index 310f627d..480c8fcb 100644 --- a/pkg/validator/controllers/replicationcontroller.go +++ b/pkg/validator/controllers/replicationcontroller.go @@ -6,7 +6,7 @@ import ( ) // NewReplicationControllerController builds a new controller interface for Deployments -func NewReplicationControllerController(originalResource kubeAPICoreV1.ReplicationController) Interface { +func NewReplicationControllerController(originalResource kubeAPICoreV1.ReplicationController) GenericController { controller := GenericController{} controller.Name = originalResource.Name controller.Namespace = originalResource.Namespace diff --git a/pkg/validator/controllers/statefulsets.go b/pkg/validator/controllers/statefulsets.go index 199d56c7..342cc9bd 100644 --- a/pkg/validator/controllers/statefulsets.go +++ b/pkg/validator/controllers/statefulsets.go @@ -7,7 +7,7 @@ import ( ) // NewStatefulSetController builds a statefulset controller -func NewStatefulSetController(originalResource kubeAPIAppsV1.StatefulSet) Interface { +func NewStatefulSetController(originalResource kubeAPIAppsV1.StatefulSet) GenericController { controller := GenericController{} controller.Name = originalResource.Name controller.Namespace = originalResource.Namespace diff --git a/pkg/validator/fullaudit.go b/pkg/validator/fullaudit.go index 7c3e8ed4..1fe4b020 100644 --- a/pkg/validator/fullaudit.go +++ b/pkg/validator/fullaudit.go @@ -36,7 +36,7 @@ func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) ( ClusterInfo: ClusterInfo{ Version: kubeResources.ServerVersion, Nodes: len(kubeResources.Nodes), - Pods: len(kubeResources.Pods), + Pods: len(kubeResources.Controllers), // TODO validate that this is still valuable Namespaces: len(kubeResources.Namespaces), Controllers: len(results), }, diff --git a/pkg/validator/pod.go b/pkg/validator/pod.go index 32d9e29e..f6678191 100644 --- a/pkg/validator/pod.go +++ b/pkg/validator/pod.go @@ -20,7 +20,7 @@ import ( ) // ValidatePod validates that each pod conforms to the Polaris config, returns a ResourceResult. -func ValidatePod(conf *config.Configuration, controller controllers.Interface) (PodResult, error) { +func ValidatePod(conf *config.Configuration, controller controllers.GenericController) (PodResult, error) { podResults, err := applyPodSchemaChecks(conf, controller) if err != nil { return PodResult{}, err diff --git a/pkg/validator/schema.go b/pkg/validator/schema.go index 1d5033f5..dbf8df05 100644 --- a/pkg/validator/schema.go +++ b/pkg/validator/schema.go @@ -74,7 +74,7 @@ func parseCheck(rawBytes []byte) (config.SchemaCheck, error) { } } -func resolveCheck(conf *config.Configuration, checkID string, controller controllers.Interface, target config.TargetKind, isInitContainer bool) (*config.SchemaCheck, error) { +func resolveCheck(conf *config.Configuration, checkID string, controller controllers.GenericController, target config.TargetKind, isInitContainer bool) (*config.SchemaCheck, error) { check, ok := conf.CustomChecks[checkID] if !ok { check, ok = builtInChecks[checkID] @@ -110,7 +110,7 @@ func getExemptKey(checkID string) string { return fmt.Sprintf("polaris.fairwinds.com/%s-exempt", checkID) } -func applyPodSchemaChecks(conf *config.Configuration, controller controllers.Interface) (ResultSet, error) { +func applyPodSchemaChecks(conf *config.Configuration, controller controllers.GenericController) (ResultSet, error) { results := ResultSet{} checkIDs := getSortedKeys(conf.Checks) objectAnnotations := controller.GetObjectMeta().Annotations @@ -137,7 +137,7 @@ func applyPodSchemaChecks(conf *config.Configuration, controller controllers.Int return results, nil } -func applyContainerSchemaChecks(conf *config.Configuration, controller controllers.Interface, container *corev1.Container, isInit bool) (ResultSet, error) { +func applyContainerSchemaChecks(conf *config.Configuration, controller controllers.GenericController, container *corev1.Container, isInit bool) (ResultSet, error) { results := ResultSet{} checkIDs := getSortedKeys(conf.Checks) objectAnnotations := controller.GetObjectMeta().Annotations diff --git a/pkg/webhook/validator.go b/pkg/webhook/validator.go index 2da14d75..dd681360 100644 --- a/pkg/webhook/validator.go +++ b/pkg/webhook/validator.go @@ -99,7 +99,7 @@ func (v *Validator) Handle(ctx context.Context, req types.Request) types.Respons podResult, err = validator.ValidatePod(&v.Config, nakedPod) } } else { - var controller controllers.Interface + var controller controllers.GenericController if yes := v.Config.CheckIfKindIsConfiguredForValidation(req.AdmissionRequest.Kind.Kind); !yes { logrus.Warnf("Skipping, kind (%s) isn't something we are configured to scan", req.AdmissionRequest.Kind.Kind) return admission.ValidationResponse(true, fmt.Sprintf("Skipping: (%s) isn't something we're configured to scan.", req.AdmissionRequest.Kind.Kind)) From 5769acf820e61543c4a2a368e54c63520a160cca Mon Sep 17 00:00:00 2001 From: Bader Boland Date: Wed, 25 Mar 2020 13:25:26 -0400 Subject: [PATCH 14/16] deduplicate results right away --- pkg/validator/controller.go | 27 +-------------------------- pkg/validator/controllers/generic.go | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/pkg/validator/controller.go b/pkg/validator/controller.go index 2869a808..febae73f 100644 --- a/pkg/validator/controller.go +++ b/pkg/validator/controller.go @@ -43,31 +43,6 @@ func ValidateController(conf *conf.Configuration, controller controller.GenericC return result, nil } -// 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(controllerResults []ControllerResult) []ControllerResult { - controllerMap := make(map[string][]ControllerResult) - for _, controller := range controllerResults { - key := controller.Namespace + "/" + controller.Kind + "/" + controller.Name - controllerMap[key] = append(controllerMap[key], controller) - } - results := make([]ControllerResult, 0) - for _, controllers := range controllerMap { - if len(controllers) == 1 { - results = append(results, controllers[0]) - } else { - latestController := controllers[0] - for _, controller := range controllers[1:] { - if controller.CreatedTime.After(latestController.CreatedTime) { - latestController = controller - } - } - results = append(results, latestController) - } - } - return results -} - // 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) ([]ControllerResult, error) { @@ -88,7 +63,7 @@ func ValidateControllers(config *conf.Configuration, kubeResources *kube.Resourc results = append(results, result) } - return deduplicateControllers(results), nil + return results, nil } func hasExemptionAnnotation(ctrl controller.GenericController) bool { diff --git a/pkg/validator/controllers/generic.go b/pkg/validator/controllers/generic.go index 953f0819..41bf25b9 100644 --- a/pkg/validator/controllers/generic.go +++ b/pkg/validator/controllers/generic.go @@ -59,8 +59,25 @@ func LoadControllers(pods []kubeAPICoreV1.Pod, dynamicClientPointer *dynamic.Int for _, pod := range pods { interfaces = append(interfaces, NewGenericPodController(pod, dynamicClientPointer, restMapperPointer)) } - // TODO DeDupe - return interfaces + return deduplicateControllers(interfaces) +} + +// 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(controllers []GenericController) []GenericController { + controllerMap := make(map[string]GenericController) + for _, controller := range controllers { + key := controller.GetNamespace() + "/" + controller.GetKindString() + "/" + controller.Name + oldController, ok := controllerMap[key] + if !ok || controller.CreatedTime.After(oldController.CreatedTime) { + controllerMap[key] = controller + } + } + results := make([]GenericController, 0) + for _, controller := range controllerMap { + results = append(results, controller) + } + return results } // NewGenericPodController builds a new controller interface for anytype of Pod From a5828a2d3becc934f0a01f23c5b4dc9c9e0272ac Mon Sep 17 00:00:00 2001 From: Bader Boland Date: Wed, 25 Mar 2020 14:23:18 -0400 Subject: [PATCH 15/16] Fix tests --- go.mod | 7 ++++--- go.sum | 23 +++++++++++++++++++++-- pkg/kube/resources_test.go | 25 +++++++++++++++++++------ pkg/validator/controller.go | 8 +++----- pkg/validator/controller_test.go | 16 +++++++++------- pkg/validator/controllers/generic.go | 3 +++ pkg/validator/fullaudit_test.go | 8 +++++--- pkg/validator/pod_test.go | 10 +++++----- pkg/validator/schema.go | 4 +--- pkg/webhook/validator.go | 3 +-- test/fixtures.go | 19 ++++++++++++++++--- 11 files changed, 87 insertions(+), 39 deletions(-) diff --git a/go.mod b/go.mod index ae7bba20..af35f450 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( contrib.go.opencensus.io/exporter/ocagent v0.4.12 git.apache.org/thrift.git v0.12.0 // indirect github.com/Azure/go-autorest v12.4.3+incompatible + github.com/Azure/go-autorest/autorest v0.10.0 // indirect github.com/appscode/jsonpatch v0.0.0-20190108182946-7c0e3b262f30 github.com/beorn7/perks v1.0.0 github.com/census-instrumentation/opencensus-proto v0.2.0 @@ -68,7 +69,7 @@ require ( gitlab.com/golang-commonmark/mdurl v0.0.0-20180912090424-e5bce34c34f2 gitlab.com/golang-commonmark/puny v0.0.0-20180912090636-2cd490539afe go.opencensus.io v0.21.0 - go.uber.org/atomic v1.4.0 + go.uber.org/atomic v1.6.0 go.uber.org/multierr v1.5.0 go.uber.org/zap v1.14.0 golang.org/x/crypto v0.0.0-20191219195013-becbf705a915 @@ -78,13 +79,13 @@ require ( golang.org/x/sys v0.0.0-20191218084908-4a24b4065292 golang.org/x/text v0.3.2 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 - golang.org/x/tools v0.0.0-20191219212307-145a1e401f50 + golang.org/x/tools v0.0.0-20191224055732-dd894d0a8a40 google.golang.org/api v0.5.0 google.golang.org/appengine v1.6.0 google.golang.org/genproto v0.0.0-20190516172635-bb713bdc0e52 google.golang.org/grpc v1.20.1 gopkg.in/inf.v0 v0.9.1 - gopkg.in/yaml.v2 v2.2.2 + gopkg.in/yaml.v2 v2.2.7 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c k8s.io/api v0.0.0-20181213150558-05914d821849 k8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93 diff --git a/go.sum b/go.sum index 5ced1a25..350ba534 100644 --- a/go.sum +++ b/go.sum @@ -5,11 +5,28 @@ cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISt contrib.go.opencensus.io/exporter/ocagent v0.4.12 h1:jGFvw3l57ViIVEPKKEUXPcLYIXJmQxLUh6ey1eJhwyc= contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/Azure/go-autorest v1.1.1 h1:4G9tVCqooRY3vDTB2bA1Z01PlSALtnUbji0AfzthUSs= github.com/Azure/go-autorest v12.0.0+incompatible h1:N+VqClcomLGD/sHb3smbSYYtNMgKpVV3Cd5r5i8z6bQ= github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v12.4.3+incompatible h1:tCkdkgLZqAk+43nZu3wda9n413Q2g+z7xp1wmjiJTPY= github.com/Azure/go-autorest v12.4.3+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.0.0+incompatible h1:r/ug62X9o8vikt53/nkAPmFmzfSrCCAplPH7wa+mK0U= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.10.0 h1:mvdtztBqcL8se7MdrUweNieTNi4kfNG6GOJuurQJpuY= +github.com/Azure/go-autorest/autorest v0.10.0/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.2 h1:O1X4oexUxnZCaEUGsvMnr8ZGj8HI37tNezwY4npRqA0= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= @@ -360,6 +377,7 @@ golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191219195013-becbf705a915 h1:aJ0ex187qoXrJHPo8ZasVTASQB7llQP6YeNzgDALPRk= golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -490,10 +508,11 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/kube/resources_test.go b/pkg/kube/resources_test.go index 27638369..742cc407 100644 --- a/pkg/kube/resources_test.go +++ b/pkg/kube/resources_test.go @@ -20,10 +20,16 @@ func TestGetResourcesFromPath(t *testing.T) { assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes") - assert.Equal(t, 8, len(resources.Pods), "Should have two pods") - assert.Equal(t, "", resources.Pods[0].ObjectMeta.Namespace, "Should have one pod in default namespace") + assert.Equal(t, 1, len(resources.Namespaces), "Should have a namespace") + assert.Equal(t, "two", resources.Namespaces[0].ObjectMeta.Name) - assert.Equal(t, "two", resources.Pods[5].ObjectMeta.Namespace, "Should have one pod in namespace 'two'") + assert.Equal(t, 8, len(resources.Controllers), "Should have eight controllers") + namespaceCount := map[string]int{} + for _, controller := range resources.Controllers { + namespaceCount[controller.GetNamespace()]++ + } + assert.Equal(t, 7, namespaceCount[""], "Should have seven controller in default namespace") + assert.Equal(t, 1, namespaceCount["two"], "Should have one controller in namespace 'two'") } func TestGetMultipleResourceFromSingleFile(t *testing.T) { @@ -38,6 +44,10 @@ func TestGetMultipleResourceFromSingleFile(t *testing.T) { assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes") + assert.Equal(t, 4, len(resources.Controllers), "Should have four controllers") + 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) assert.Equal(t, "polaris-2", resources.Namespaces[1].ObjectMeta.Name) } @@ -48,9 +58,11 @@ func TestGetMultipleResourceFromBadFile(t *testing.T) { } func TestGetResourceFromAPI(t *testing.T) { - k8s := test.SetupTestAPI() + k8s, dynamicInterface := test.SetupTestAPI() k8s = test.SetupAddControllers(k8s, "test") - resources, err := CreateResourceProviderFromAPI(k8s, "test", nil) + // TODO find a way to mock out the dynamic client + // and create fake pods in order to find all of the controllers. + resources, err := CreateResourceProviderFromAPI(k8s, "test", &dynamicInterface) assert.Equal(t, nil, err, "Error should be nil") assert.Equal(t, "Cluster", resources.SourceType, "Should have type Path") @@ -58,6 +70,7 @@ 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, 1, len(resources.Pods), "Should have a pod") + assert.Equal(t, 1, len(resources.Controllers), "Should have 1 controller") + assert.Equal(t, "", resources.Controllers[0].ObjectMeta.Name) } diff --git a/pkg/validator/controller.go b/pkg/validator/controller.go index febae73f..4fe9db81 100644 --- a/pkg/validator/controller.go +++ b/pkg/validator/controller.go @@ -27,7 +27,7 @@ import ( const exemptionAnnotationKey = "polaris.fairwinds.com/exempt" // ValidateController validates a single controller, returns a ControllerResult. -func ValidateController(conf *conf.Configuration, controller controller.GenericController, kubeResources *kube.ResourceProvider) (ControllerResult, error) { +func ValidateController(conf *conf.Configuration, controller controller.GenericController) (ControllerResult, error) { podResult, err := ValidatePod(conf, controller) if err != nil { return ControllerResult{}, err @@ -46,16 +46,14 @@ func ValidateController(conf *conf.Configuration, controller controller.GenericC // 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) ([]ControllerResult, error) { - var controllersToAudit []controller.GenericController - loadedControllers := kubeResources.Controllers - controllersToAudit = append(controllersToAudit, loadedControllers...) + controllersToAudit := kubeResources.Controllers results := []ControllerResult{} for _, controller := range controllersToAudit { if !config.DisallowExemptions && hasExemptionAnnotation(controller) { continue } - result, err := ValidateController(config, controller, kubeResources) + result, err := ValidateController(config, controller) if err != nil { logrus.Warn("An error occured validating controller:", err) return nil, err diff --git a/pkg/validator/controller_test.go b/pkg/validator/controller_test.go index 548c2f4f..14dd955d 100644 --- a/pkg/validator/controller_test.go +++ b/pkg/validator/controller_test.go @@ -45,7 +45,7 @@ func TestValidateController(t *testing.T) { "hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "error", Category: "Security"}, } - actualResult, err := ValidateController(&c, deployment, &kube.ResourceProvider{}) + actualResult, err := ValidateController(&c, deployment) if err != nil { panic(err) } @@ -83,7 +83,7 @@ func TestSkipHealthChecks(t *testing.T) { "readinessProbeMissing": {ID: "readinessProbeMissing", Message: "Readiness probe should be configured", Success: false, Severity: "error", Category: "Health Checks"}, "livenessProbeMissing": {ID: "livenessProbeMissing", Message: "Liveness probe should be configured", Success: false, Severity: "warning", Category: "Health Checks"}, } - actualResult, err := ValidateController(&c, deployment, &kube.ResourceProvider{}) + actualResult, err := ValidateController(&c, deployment) if err != nil { panic(err) } @@ -100,7 +100,7 @@ func TestSkipHealthChecks(t *testing.T) { Errors: uint(0), } expectedResults = ResultSet{} - actualResult, err = ValidateController(&c, job, &kube.ResourceProvider{}) + actualResult, err = ValidateController(&c, job) if err != nil { panic(err) } @@ -116,7 +116,7 @@ func TestSkipHealthChecks(t *testing.T) { Errors: uint(0), } expectedResults = ResultSet{} - actualResult, err = ValidateController(&c, cronjob, &kube.ResourceProvider{}) + actualResult, err = ValidateController(&c, cronjob) if err != nil { panic(err) } @@ -136,8 +136,10 @@ func TestControllerExemptions(t *testing.T) { conf.Deployments, }, } + newController := test.MockGenericController() + newController.KindString = "Deployment" resources := &kube.ResourceProvider{ - Pods: []corev1.Pod{test.MockNakedPod()}, + Controllers: []controller.GenericController{newController}, } expectedSum := CountSummary{ @@ -150,10 +152,10 @@ func TestControllerExemptions(t *testing.T) { panic(err) } assert.Equal(t, 1, len(actualResults)) - assert.Equal(t, "NakedPod", actualResults[0].Kind) + assert.Equal(t, "Deployment", actualResults[0].Kind) assert.EqualValues(t, expectedSum, actualResults[0].GetSummary()) - resources.Pods[0].ObjectMeta.Annotations = map[string]string{ + resources.Controllers[0].ObjectMeta.Annotations = map[string]string{ exemptionAnnotationKey: "true", } actualResults, err = ValidateControllers(&c, resources) diff --git a/pkg/validator/controllers/generic.go b/pkg/validator/controllers/generic.go index 41bf25b9..85698d0c 100644 --- a/pkg/validator/controllers/generic.go +++ b/pkg/validator/controllers/generic.go @@ -40,6 +40,9 @@ func (g GenericController) GetKind() config.SupportedController { // GetKindString returns a string representing what kind of object the top level controller is. func (g GenericController) GetKindString() string { + if g.KindString == "" { + return g.Kind.String() + } return g.KindString } diff --git a/pkg/validator/fullaudit_test.go b/pkg/validator/fullaudit_test.go index 3b3750d4..0135ed30 100644 --- a/pkg/validator/fullaudit_test.go +++ b/pkg/validator/fullaudit_test.go @@ -10,10 +10,12 @@ import ( ) func TestGetTemplateData(t *testing.T) { - k8s := test.SetupTestAPI() + k8s, dynamicClient := test.SetupTestAPI() k8s = test.SetupAddControllers(k8s, "test") k8s = test.SetupAddExtraControllerVersions(k8s, "test-extra") - resources, err := kube.CreateResourceProviderFromAPI(k8s, "test", nil) + // TODO figure out how to mock out dynamic client. + // and add in pods for all controllers to fill out tests. + resources, err := kube.CreateResourceProviderFromAPI(k8s, "test", &dynamicClient) assert.Equal(t, err, nil, "error should be nil") c := conf.Configuration{ @@ -48,7 +50,7 @@ func TestGetTemplateData(t *testing.T) { kind string results int }{ - {kind: "NakedPod", results: 2}, + {kind: "Pod", results: 2}, } assert.Equal(t, len(expected), len(actualAudit.Results)) diff --git a/pkg/validator/pod_test.go b/pkg/validator/pod_test.go index a0e72b12..1c2139ec 100644 --- a/pkg/validator/pod_test.go +++ b/pkg/validator/pod_test.go @@ -36,7 +36,7 @@ func TestValidatePod(t *testing.T) { }, } - k8s := test.SetupTestAPI() + k8s, _ := test.SetupTestAPI() k8s = test.SetupAddControllers(k8s, "test") p := test.MockPod() deployment := controllers.NewDeploymentController(appsv1.Deployment{Spec: appsv1.DeploymentSpec{Template: p}}) @@ -73,7 +73,7 @@ func TestInvalidIPCPod(t *testing.T) { }, } - k8s := test.SetupTestAPI() + k8s, _ := test.SetupTestAPI() k8s = test.SetupAddControllers(k8s, "test") p := test.MockPod() p.Spec.HostIPC = true @@ -110,7 +110,7 @@ func TestInvalidNeworkPod(t *testing.T) { }, } - k8s := test.SetupTestAPI() + k8s, _ := test.SetupTestAPI() k8s = test.SetupAddControllers(k8s, "test") p := test.MockPod() p.Spec.HostNetwork = true @@ -148,7 +148,7 @@ func TestInvalidPIDPod(t *testing.T) { }, } - k8s := test.SetupTestAPI() + k8s, _ := test.SetupTestAPI() k8s = test.SetupAddControllers(k8s, "test") p := test.MockPod() p.Spec.HostPID = true @@ -192,7 +192,7 @@ func TestExemption(t *testing.T) { }, } - k8s := test.SetupTestAPI() + k8s, _ := test.SetupTestAPI() k8s = test.SetupAddControllers(k8s, "test") p := test.MockPod() p.Spec.HostIPC = true diff --git a/pkg/validator/schema.go b/pkg/validator/schema.go index dbf8df05..89f8c872 100644 --- a/pkg/validator/schema.go +++ b/pkg/validator/schema.go @@ -120,9 +120,7 @@ func applyPodSchemaChecks(conf *config.Configuration, controller controllers.Gen continue } check, err := resolveCheck(conf, checkID, controller, config.TargetPod, false) - if err != nil { - return nil, err - } + if err != nil { return nil, err } else if check == nil { diff --git a/pkg/webhook/validator.go b/pkg/webhook/validator.go index dd681360..b395ae56 100644 --- a/pkg/webhook/validator.go +++ b/pkg/webhook/validator.go @@ -20,7 +20,6 @@ import ( "net/http" "github.com/fairwindsops/polaris/pkg/config" - "github.com/fairwindsops/polaris/pkg/kube" validator "github.com/fairwindsops/polaris/pkg/validator" "github.com/fairwindsops/polaris/pkg/validator/controllers" @@ -143,7 +142,7 @@ func (v *Validator) Handle(ctx context.Context, req types.Request) types.Respons } if err == nil { var controllerResult validator.ControllerResult - controllerResult, err = validator.ValidateController(&v.Config, controller, &kube.ResourceProvider{}) + controllerResult, err = validator.ValidateController(&v.Config, controller) podResult = controllerResult.PodResult } } diff --git a/test/fixtures.go b/test/fixtures.go index 272ab38c..7a5ad47c 100644 --- a/test/fixtures.go +++ b/test/fixtures.go @@ -1,12 +1,16 @@ package test import ( + "github.com/fairwindsops/polaris/pkg/validator/controllers" appsv1 "k8s.io/api/apps/v1" appsv1beta1 "k8s.io/api/apps/v1beta1" appsv1beta2 "k8s.io/api/apps/v1beta2" batchv1 "k8s.io/api/batch/v1" batchv1beta1 "k8s.io/api/batch/v1beta1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/dynamic" + dynamicFake "k8s.io/client-go/dynamic/fake" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" ) @@ -32,7 +36,14 @@ func MockPod() corev1.PodTemplateSpec { return p } -// MockNakedPod created a pod object. +// MockGenericController creates a generic controller object for testing. +func MockGenericController() controllers.GenericController { + return controllers.GenericController{ + PodSpec: MockPod().Spec, + } +} + +// MockNakedPod creates a pod object. func MockNakedPod() corev1.Pod { return corev1.Pod{ Spec: MockPod().Spec, @@ -103,8 +114,10 @@ func MockReplicationController() corev1.ReplicationController { } // SetupTestAPI creates a test kube API struct. -func SetupTestAPI() kubernetes.Interface { - return fake.NewSimpleClientset() +func SetupTestAPI() (kubernetes.Interface, dynamic.Interface) { + scheme := runtime.NewScheme() + + return fake.NewSimpleClientset(), dynamicFake.NewSimpleDynamicClient(scheme) } // SetupAddControllers creates mock controllers and adds them to the test clientset. From 3c46f405a93ed03d225cfdae0f010ef35b55d77b Mon Sep 17 00:00:00 2001 From: Bader Boland Date: Wed, 25 Mar 2020 16:50:12 -0400 Subject: [PATCH 16/16] Cleanup SupportedControllers --- pkg/config/schema.go | 6 +-- pkg/kube/resources.go | 32 +++++++++++- pkg/validator/controller.go | 2 +- pkg/validator/controller_test.go | 2 +- pkg/validator/controllers/cronjob.go | 2 +- pkg/validator/controllers/daemonset.go | 2 +- pkg/validator/controllers/deployment.go | 2 +- pkg/validator/controllers/generic.go | 49 +++---------------- pkg/validator/controllers/job.go | 2 +- pkg/validator/controllers/naked-pod.go | 2 +- .../controllers/replicationcontroller.go | 2 +- pkg/validator/controllers/statefulsets.go | 2 +- 12 files changed, 49 insertions(+), 56 deletions(-) diff --git a/pkg/config/schema.go b/pkg/config/schema.go index 320a3843..2870eb2c 100644 --- a/pkg/config/schema.go +++ b/pkg/config/schema.go @@ -144,13 +144,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, controllerType SupportedController, isInit bool) bool { +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 GetSupportedControllerFromString(inclusion) == controllerType { + if inclusion == controllerType { isIncluded = true break } @@ -159,7 +159,7 @@ func (check SchemaCheck) IsActionable(target TargetKind, controllerType Supporte return false } for _, exclusion := range check.Controllers.Exclude { - if GetSupportedControllerFromString(exclusion) == controllerType { + if exclusion == controllerType { return false } } diff --git a/pkg/kube/resources.go b/pkg/kube/resources.go index 10467b4e..fc8d74f6 100644 --- a/pkg/kube/resources.go +++ b/pkg/kube/resources.go @@ -13,6 +13,7 @@ import ( "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sYaml "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/client-go/dynamic" @@ -148,11 +149,38 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string CreationTime: time.Now(), Nodes: nodes.Items, Namespaces: namespaces.Items, - Controllers: controllers.LoadControllers(pods.Items, dynamic, &restMapper), + Controllers: LoadControllers(pods.Items, dynamic, &restMapper), } return &api, nil } +// LoadControllers loads a list of controllers from the kubeResources Pods +func LoadControllers(pods []corev1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper) []controllers.GenericController { + interfaces := []controllers.GenericController{} + for _, pod := range pods { + interfaces = append(interfaces, controllers.NewGenericPodController(pod, dynamicClientPointer, restMapperPointer)) + } + return deduplicateControllers(interfaces) +} + +// 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 []controllers.GenericController) []controllers.GenericController { + controllerMap := make(map[string]controllers.GenericController) + for _, controller := range inputControllers { + key := controller.GetNamespace() + "/" + controller.GetKind() + "/" + controller.Name + oldController, ok := controllerMap[key] + if !ok || controller.CreatedTime.After(oldController.CreatedTime) { + controllerMap[key] = controller + } + } + results := make([]controllers.GenericController, 0) + for _, controller := range controllerMap { + results = append(results, controller) + } + return results +} + func getPodSpec(yaml map[string]interface{}) interface{} { allowedChildren := []string{"jobTemplate", "spec", "template"} for _, child := range allowedChildren { @@ -203,7 +231,7 @@ func addResourceFromString(contents string, resources *ResourceProvider) error { pod := corev1.Pod{} err = decoder.Decode(&pod) newController := controllers.NewGenericPodController(pod, nil, nil) - newController.KindString = resource.Kind + newController.Kind = resource.Kind resources.Controllers = append(resources.Controllers, newController) } return err diff --git a/pkg/validator/controller.go b/pkg/validator/controller.go index 4fe9db81..b3d5ee5c 100644 --- a/pkg/validator/controller.go +++ b/pkg/validator/controller.go @@ -33,7 +33,7 @@ func ValidateController(conf *conf.Configuration, controller controller.GenericC return ControllerResult{}, err } result := ControllerResult{ - Kind: controller.GetKindString(), + Kind: controller.GetKind(), Name: controller.GetName(), Namespace: controller.GetObjectMeta().Namespace, Results: ResultSet{}, diff --git a/pkg/validator/controller_test.go b/pkg/validator/controller_test.go index 14dd955d..a3335309 100644 --- a/pkg/validator/controller_test.go +++ b/pkg/validator/controller_test.go @@ -137,7 +137,7 @@ func TestControllerExemptions(t *testing.T) { }, } newController := test.MockGenericController() - newController.KindString = "Deployment" + newController.Kind = "Deployment" resources := &kube.ResourceProvider{ Controllers: []controller.GenericController{newController}, } diff --git a/pkg/validator/controllers/cronjob.go b/pkg/validator/controllers/cronjob.go index 76e7c20f..33a2c72e 100644 --- a/pkg/validator/controllers/cronjob.go +++ b/pkg/validator/controllers/cronjob.go @@ -13,7 +13,7 @@ func NewCronJobController(originalResource kubeAPIBatchV1beta1.CronJob) GenericC controller.Namespace = originalResource.Namespace controller.PodSpec = originalResource.Spec.JobTemplate.Spec.Template.Spec controller.ObjectMeta = originalResource.ObjectMeta - controller.Kind = config.CronJobs + controller.Kind = config.CronJobs.String() if controller.Name == "" { logrus.Warn("Name is missing from controller", originalResource.Namespace) } diff --git a/pkg/validator/controllers/daemonset.go b/pkg/validator/controllers/daemonset.go index 40f6a44d..c8ce6d3e 100644 --- a/pkg/validator/controllers/daemonset.go +++ b/pkg/validator/controllers/daemonset.go @@ -12,6 +12,6 @@ func NewDaemonSetController(originalResource kubeAPIAppsV1.DaemonSet) GenericCon controller.Namespace = originalResource.Namespace controller.PodSpec = originalResource.Spec.Template.Spec controller.ObjectMeta = originalResource.ObjectMeta - controller.Kind = config.DaemonSets + controller.Kind = config.DaemonSets.String() return controller } diff --git a/pkg/validator/controllers/deployment.go b/pkg/validator/controllers/deployment.go index 6825d1c0..2972d442 100644 --- a/pkg/validator/controllers/deployment.go +++ b/pkg/validator/controllers/deployment.go @@ -12,6 +12,6 @@ func NewDeploymentController(originalResource kubeAPIAppsV1.Deployment) GenericC controller.Namespace = originalResource.Namespace controller.PodSpec = originalResource.Spec.Template.Spec controller.ObjectMeta = originalResource.ObjectMeta - controller.Kind = config.Deployments + controller.Kind = config.Deployments.String() return controller } diff --git a/pkg/validator/controllers/generic.go b/pkg/validator/controllers/generic.go index 85698d0c..3d78023c 100644 --- a/pkg/validator/controllers/generic.go +++ b/pkg/validator/controllers/generic.go @@ -3,7 +3,6 @@ package controllers import ( "time" - "github.com/fairwindsops/polaris/pkg/config" "github.com/sirupsen/logrus" kubeAPICoreV1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" @@ -18,8 +17,7 @@ type GenericController struct { Namespace string PodSpec kubeAPICoreV1.PodSpec ObjectMeta kubeAPIMetaV1.ObjectMeta - Kind config.SupportedController - KindString string + Kind string CreatedTime time.Time } @@ -34,18 +32,10 @@ func (g GenericController) GetObjectMeta() kubeAPIMetaV1.ObjectMeta { } // GetKind returns the supportedcontroller enum type -func (g GenericController) GetKind() config.SupportedController { +func (g GenericController) GetKind() string { return g.Kind } -// GetKindString returns a string representing what kind of object the top level controller is. -func (g GenericController) GetKindString() string { - if g.KindString == "" { - return g.Kind.String() - } - return g.KindString -} - // GetName is inherited by all controllers using generic controller to get the name of the controller func (g GenericController) GetName() string { return g.Name @@ -56,33 +46,6 @@ func (g GenericController) GetNamespace() string { return g.Namespace } -// LoadControllers loads a list of controllers from the kubeResources Pods -func LoadControllers(pods []kubeAPICoreV1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper) []GenericController { - interfaces := []GenericController{} - for _, pod := range pods { - interfaces = append(interfaces, NewGenericPodController(pod, dynamicClientPointer, restMapperPointer)) - } - return deduplicateControllers(interfaces) -} - -// 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(controllers []GenericController) []GenericController { - controllerMap := make(map[string]GenericController) - for _, controller := range controllers { - key := controller.GetNamespace() + "/" + controller.GetKindString() + "/" + controller.Name - oldController, ok := controllerMap[key] - if !ok || controller.CreatedTime.After(oldController.CreatedTime) { - controllerMap[key] = controller - } - } - results := make([]GenericController, 0) - for _, controller := range controllerMap { - results = append(results, controller) - } - return results -} - // NewGenericPodController builds a new controller interface for anytype of Pod func NewGenericPodController(originalResource kubeAPICoreV1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper) GenericController { controller := GenericController{} @@ -90,8 +53,7 @@ func NewGenericPodController(originalResource kubeAPICoreV1.Pod, dynamicClientPo controller.Namespace = originalResource.Namespace controller.PodSpec = originalResource.Spec controller.ObjectMeta = originalResource.ObjectMeta - controller.Kind = config.NakedPods - controller.KindString = "Pod" + controller.Kind = "Pod" controller.CreatedTime = controller.GetObjectMeta().CreationTimestamp.Time owners := controller.GetObjectMeta().OwnerReferences @@ -101,8 +63,11 @@ func NewGenericPodController(originalResource kubeAPICoreV1.Pod, dynamicClientPo // If an owner exists then set the name to the controller. // This allows us to handle CRDs creating Controllers or DeploymentConfigs in OpenShift. for len(owners) > 0 { + if len(owners) > 1 { + logrus.Warn("More than 1 owner found") + } firstOwner := owners[0] - controller.KindString = firstOwner.Kind + controller.Kind = firstOwner.Kind controller.Name = firstOwner.Name dynamicClient := *dynamicClientPointer diff --git a/pkg/validator/controllers/job.go b/pkg/validator/controllers/job.go index bd26f330..3377249f 100644 --- a/pkg/validator/controllers/job.go +++ b/pkg/validator/controllers/job.go @@ -12,6 +12,6 @@ func NewJobController(originalResource kubeAPIBatchV1.Job) GenericController { controller.Namespace = originalResource.Namespace controller.PodSpec = originalResource.Spec.Template.Spec controller.ObjectMeta = originalResource.ObjectMeta - controller.Kind = config.Jobs + controller.Kind = config.Jobs.String() return controller } diff --git a/pkg/validator/controllers/naked-pod.go b/pkg/validator/controllers/naked-pod.go index 2b319cf7..b96a76ee 100644 --- a/pkg/validator/controllers/naked-pod.go +++ b/pkg/validator/controllers/naked-pod.go @@ -12,7 +12,7 @@ func NewNakedPodController(originalResource kubeAPICoreV1.Pod) GenericController controller.Namespace = originalResource.Namespace controller.PodSpec = originalResource.Spec controller.ObjectMeta = originalResource.ObjectMeta - controller.Kind = config.NakedPods + controller.Kind = config.NakedPods.String() return controller } diff --git a/pkg/validator/controllers/replicationcontroller.go b/pkg/validator/controllers/replicationcontroller.go index 480c8fcb..1a9fa32f 100644 --- a/pkg/validator/controllers/replicationcontroller.go +++ b/pkg/validator/controllers/replicationcontroller.go @@ -12,6 +12,6 @@ func NewReplicationControllerController(originalResource kubeAPICoreV1.Replicati controller.Namespace = originalResource.Namespace controller.PodSpec = originalResource.Spec.Template.Spec controller.ObjectMeta = originalResource.ObjectMeta - controller.Kind = config.ReplicationControllers + controller.Kind = config.ReplicationControllers.String() return controller } diff --git a/pkg/validator/controllers/statefulsets.go b/pkg/validator/controllers/statefulsets.go index 342cc9bd..fab6b865 100644 --- a/pkg/validator/controllers/statefulsets.go +++ b/pkg/validator/controllers/statefulsets.go @@ -13,7 +13,7 @@ func NewStatefulSetController(originalResource kubeAPIAppsV1.StatefulSet) Generi controller.Namespace = originalResource.Namespace controller.PodSpec = originalResource.Spec.Template.Spec controller.ObjectMeta = originalResource.ObjectMeta - controller.Kind = config.StatefulSets + controller.Kind = config.StatefulSets.String() if controller.Name == "" { logrus.Warn("Name is missing from controller", originalResource.Namespace) }