mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-05-20 16:14:32 +00:00
Merge pull request #240 from FairwindsOps/bb/openshift
Adding support for third-party Controllers (e.g. OpenShift)
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
# x.x.x (next release)
|
||||
* Added the ability to exempt a particular controller from a particular check.
|
||||
* 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
|
||||
|
||||
8
go.mod
8
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.1
|
||||
@@ -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,14 @@ 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
|
||||
k8s.io/client-go v0.0.0-20181213151034-8d9ed539ba31
|
||||
|
||||
21
go.sum
21
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=
|
||||
@@ -375,6 +392,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=
|
||||
@@ -506,7 +524,10 @@ 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=
|
||||
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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,10 @@
|
||||
<span>Pods:</span>
|
||||
<strong>{{.AuditData.ClusterInfo.Pods}}</strong>
|
||||
</span>
|
||||
<span class="kubernetes-stat">
|
||||
<span>Controllers:</span>
|
||||
<strong>{{.AuditData.ClusterInfo.Controllers}}</strong>
|
||||
</span>
|
||||
<span class="kubernetes-stat">
|
||||
<span>Namespaces:</span>
|
||||
<strong>{{.AuditData.ClusterInfo.Namespaces}}</strong>
|
||||
|
||||
@@ -2,7 +2,6 @@ package kube
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -10,33 +9,29 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/validator/controllers"
|
||||
"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"
|
||||
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/restmapper"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
)
|
||||
|
||||
// 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
|
||||
ServerVersion string
|
||||
CreationTime time.Time
|
||||
SourceName string
|
||||
SourceType string
|
||||
Nodes []corev1.Node
|
||||
Namespaces []corev1.Namespace
|
||||
Controllers []controllers.GenericController
|
||||
}
|
||||
|
||||
type k8sResource struct {
|
||||
@@ -54,18 +49,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{},
|
||||
Controllers: []controllers.GenericController{},
|
||||
}
|
||||
|
||||
addYaml := func(contents string) error {
|
||||
@@ -114,44 +103,23 @@ func CreateResourceProviderFromCluster() (*ResourceProvider, error) {
|
||||
logrus.Errorf("Error creating Kubernetes client: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return CreateResourceProviderFromAPI(api, kubeConf.Host)
|
||||
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) (*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 {
|
||||
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
|
||||
}
|
||||
cronJobs, err := getCronJobs(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)
|
||||
@@ -168,222 +136,103 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string
|
||||
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,
|
||||
SourceType: "Cluster",
|
||||
SourceName: clusterName,
|
||||
CreationTime: time.Now(),
|
||||
Deployments: deploys,
|
||||
StatefulSets: statefulSets,
|
||||
DaemonSets: daemonSets,
|
||||
CronJobs: cronJobs,
|
||||
Jobs: jobs.Items,
|
||||
ReplicationControllers: replicationControllers.Items,
|
||||
Nodes: nodes.Items,
|
||||
Namespaces: namespaces.Items,
|
||||
Pods: pods.Items,
|
||||
ServerVersion: serverVersion.Major + "." + serverVersion.Minor,
|
||||
SourceType: "Cluster",
|
||||
SourceName: clusterName,
|
||||
CreationTime: time.Now(),
|
||||
Nodes: nodes.Items,
|
||||
Namespaces: namespaces.Items,
|
||||
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 {
|
||||
if childYaml, ok := yaml[child]; 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)
|
||||
} 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)
|
||||
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)
|
||||
marshaledYaml, err := yaml.Marshal(finalDoc)
|
||||
if err != nil {
|
||||
logrus.Errorf("Could not marshal yaml: %v", err)
|
||||
return err
|
||||
}
|
||||
decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(marshaledYaml), 1000)
|
||||
pod := corev1.Pod{}
|
||||
err = decoder.Decode(&pod)
|
||||
newController := controllers.NewGenericPodController(pod, nil, nil)
|
||||
newController.Kind = resource.Kind
|
||||
resources.Controllers = append(resources.Controllers, newController)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -20,18 +20,16 @@ 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, "", 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, 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) {
|
||||
@@ -46,8 +44,8 @@ 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, 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)
|
||||
@@ -60,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")
|
||||
// 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")
|
||||
@@ -70,9 +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.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, 1, len(resources.Controllers), "Should have 1 controller")
|
||||
|
||||
assert.Equal(t, "", resources.Deployments[0].ObjectMeta.Name)
|
||||
assert.Equal(t, "", resources.Controllers[0].ObjectMeta.Name)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,38 +17,36 @@ package validator
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
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) (ControllerResult, error) {
|
||||
func ValidateController(conf *conf.Configuration, controller controller.GenericController) (ControllerResult, error) {
|
||||
podResult, err := ValidatePod(conf, controller)
|
||||
if err != nil {
|
||||
return ControllerResult{}, err
|
||||
}
|
||||
result := ControllerResult{
|
||||
Kind: controller.GetKind().String(),
|
||||
Kind: controller.GetKind(),
|
||||
Name: controller.GetName(),
|
||||
Namespace: controller.GetObjectMeta().Namespace,
|
||||
Results: ResultSet{},
|
||||
PodResult: podResult,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ValidateControllers validates that each deployment conforms to the Polaris config,
|
||||
// builds a list of ResourceResults organized by namespace.
|
||||
func ValidateControllers(config *conf.Configuration, kubeResources *kube.ResourceProvider) ([]ControllerResult, error) {
|
||||
var controllersToAudit []controller.Interface
|
||||
for _, supportedControllers := range config.ControllersToScan {
|
||||
loadedControllers, _ := controllers.LoadControllersByKind(supportedControllers, kubeResources)
|
||||
controllersToAudit = append(controllersToAudit, loadedControllers...)
|
||||
}
|
||||
controllersToAudit := kubeResources.Controllers
|
||||
|
||||
results := []ControllerResult{}
|
||||
for _, controller := range controllersToAudit {
|
||||
@@ -57,14 +55,16 @@ func ValidateControllers(config *conf.Configuration, kubeResources *kube.Resourc
|
||||
}
|
||||
result, err := ValidateController(config, controller)
|
||||
if err != nil {
|
||||
logrus.Warn("An error occured validating controller:", err)
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
|
||||
return 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"
|
||||
|
||||
@@ -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"
|
||||
@@ -137,8 +136,10 @@ func TestControllerExemptions(t *testing.T) {
|
||||
conf.Deployments,
|
||||
},
|
||||
}
|
||||
newController := test.MockGenericController()
|
||||
newController.Kind = "Deployment"
|
||||
resources := &kube.ResourceProvider{
|
||||
Deployments: []appsv1.Deployment{test.MockDeploy()},
|
||||
Controllers: []controller.GenericController{newController},
|
||||
}
|
||||
|
||||
expectedSum := CountSummary{
|
||||
@@ -154,7 +155,7 @@ func TestControllerExemptions(t *testing.T) {
|
||||
assert.Equal(t, "Deployment", actualResults[0].Kind)
|
||||
assert.EqualValues(t, expectedSum, actualResults[0].GetSummary())
|
||||
|
||||
resources.Deployments[0].ObjectMeta.Annotations = map[string]string{
|
||||
resources.Controllers[0].ObjectMeta.Annotations = map[string]string{
|
||||
exemptionAnnotationKey: "true",
|
||||
}
|
||||
actualResults, err = ValidateControllers(&c, resources)
|
||||
|
||||
@@ -2,42 +2,20 @@ package controllers
|
||||
|
||||
import (
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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) GenericController {
|
||||
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.String()
|
||||
if controller.Name == "" {
|
||||
logrus.Warn("Name is missing from controller", originalResource.Namespace)
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
@@ -3,41 +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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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{}
|
||||
func NewDaemonSetController(originalResource kubeAPIAppsV1.DaemonSet) GenericController {
|
||||
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.String()
|
||||
return controller
|
||||
}
|
||||
|
||||
@@ -3,41 +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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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) GenericController {
|
||||
controller := GenericController{}
|
||||
controller.Name = originalResource.Name
|
||||
controller.Namespace = originalResource.Namespace
|
||||
controller.PodSpec = originalResource.Spec.Template.Spec
|
||||
controller.ObjectMeta = originalResource.ObjectMeta
|
||||
controller.Kind = config.Deployments.String()
|
||||
return controller
|
||||
}
|
||||
|
||||
91
pkg/validator/controllers/generic.go
Normal file
91
pkg/validator/controllers/generic.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"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 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() string {
|
||||
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
|
||||
}
|
||||
|
||||
// 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 = "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 {
|
||||
if len(owners) > 1 {
|
||||
logrus.Warn("More than 1 owner found")
|
||||
}
|
||||
firstOwner := owners[0]
|
||||
controller.Kind = 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
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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
|
||||
GetPodTemplate() *kubeAPICoreV1.PodTemplateSpec
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// LoadControllersByKind loads a list of controllers from the kubeResources by detecting their type
|
||||
func LoadControllersByKind(controllerKind config.SupportedController, kubeResources *kube.ResourceProvider) ([]Interface, error) {
|
||||
interfaces := []Interface{}
|
||||
switch controllerKind {
|
||||
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))
|
||||
}
|
||||
}
|
||||
if len(interfaces) > 0 {
|
||||
return interfaces, nil
|
||||
}
|
||||
return nil, fmt.Errorf("Controller type (%s) does not have a generator", controllerKind)
|
||||
}
|
||||
@@ -3,41 +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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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{}
|
||||
func NewJobController(originalResource kubeAPIBatchV1.Job) GenericController {
|
||||
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.String()
|
||||
return controller
|
||||
}
|
||||
|
||||
@@ -3,40 +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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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) GenericController {
|
||||
controller := GenericController{}
|
||||
controller.Name = originalResource.Name
|
||||
controller.Namespace = originalResource.Namespace
|
||||
controller.PodSpec = originalResource.Spec
|
||||
controller.ObjectMeta = originalResource.ObjectMeta
|
||||
controller.Kind = config.NakedPods.String()
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
@@ -3,43 +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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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{}
|
||||
func NewReplicationControllerController(originalResource kubeAPICoreV1.ReplicationController) GenericController {
|
||||
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.String()
|
||||
return controller
|
||||
}
|
||||
|
||||
@@ -2,42 +2,20 @@ package controllers
|
||||
|
||||
import (
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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{}
|
||||
func NewStatefulSetController(originalResource kubeAPIAppsV1.StatefulSet) GenericController {
|
||||
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.String()
|
||||
if controller.Name == "" {
|
||||
logrus.Warn("Name is missing from controller", originalResource.Namespace)
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
@@ -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.Controllers), // TODO validate that this is still valuable
|
||||
Namespaces: len(kubeResources.Namespaces),
|
||||
Controllers: len(results),
|
||||
},
|
||||
Results: results,
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
// 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{
|
||||
@@ -33,8 +35,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 +50,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: "Pod", results: 2},
|
||||
}
|
||||
|
||||
assert.Equal(t, len(expected), len(actualAudit.Results))
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
)
|
||||
|
||||
@@ -36,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
|
||||
@@ -62,11 +59,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.
|
||||
|
||||
@@ -20,12 +20,11 @@ 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -120,9 +120,7 @@ func applyPodSchemaChecks(conf *config.Configuration, controller controllers.Int
|
||||
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 {
|
||||
@@ -137,7 +135,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
|
||||
|
||||
@@ -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 {
|
||||
return 0 // Prevent divide by 0.
|
||||
}
|
||||
return uint((float64(cs.Successes*2) / float64(total)) * 100)
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,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))
|
||||
|
||||
@@ -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,6 +36,20 @@ func MockPod() corev1.PodTemplateSpec {
|
||||
return p
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
// MockDeploy creates a Deployment object.
|
||||
func MockDeploy() appsv1.Deployment {
|
||||
p := MockPod()
|
||||
@@ -96,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.
|
||||
@@ -132,6 +152,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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user