diff --git a/checks/run_checks.go b/checks/run_checks.go index 1a48f63..1a25171 100644 --- a/checks/run_checks.go +++ b/checks/run_checks.go @@ -28,7 +28,7 @@ import ( // Run applies the filters and runs the resultant check list in parallel func Run(ctx context.Context, client *kube.Client, checkFilter CheckFilter, diagnosticFilter DiagnosticFilter, objectFilter kube.ObjectFilter) (*CheckResult, error) { - objects, err := client.FetchObjects(ctx) + objects, err := client.FetchObjects(ctx,objectFilter) if err != nil { return nil, err } @@ -40,8 +40,6 @@ func Run(ctx context.Context, client *kube.Client, checkFilter CheckFilter, diag if len(all) == 0 { return nil, errors.New("No checks to run. Are you sure that you provided the right names for groups and checks?") } - objectFilter.Filter(objects) - var diagnostics []Diagnostic var mu sync.Mutex var g errgroup.Group diff --git a/cmd/clusterlint/main.go b/cmd/clusterlint/main.go index 5087324..7d95332 100644 --- a/cmd/clusterlint/main.go +++ b/cmd/clusterlint/main.go @@ -96,12 +96,12 @@ func main() { Name: "C, ignore-checks", Usage: "run a specific check", }, - cli.StringSliceFlag{ - Name: "n, namespaces", + cli.StringFlag{ + Name: "n, namespace", Usage: "run checks in specific namespace", }, - cli.StringSliceFlag{ - Name: "N, ignore-namespaces", + cli.StringFlag{ + Name: "N, ignore-namespace", Usage: "run checks not in specific namespace", }, cli.StringFlag{ @@ -181,7 +181,7 @@ func runChecks(c *cli.Context) error { diagnosticFilter := checks.DiagnosticFilter{Severity: checks.Severity(c.String("level"))} - objectFilter, err := kube.NewObjectFilter(c.StringSlice("n"), c.StringSlice("N")) + objectFilter, err := kube.NewObjectFilter(c.String("n"), c.String("N")) if err != nil { return err } diff --git a/kube/object_filter.go b/kube/object_filter.go index b9ac077..72d5093 100644 --- a/kube/object_filter.go +++ b/kube/object_filter.go @@ -19,179 +19,34 @@ package kube import ( // Load client-go authentication plugins "fmt" - corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" _ "k8s.io/client-go/plugin/pkg/client/auth" ) // ObjectFilter stores k8s object's fields that needs to be included or excluded while running checks type ObjectFilter struct { - IncludeNamespaces []string - ExcludeNamespaces []string + IncludeNamespace string + ExcludeNamespace string } - // NewObjectFilter is a constructor to initialize an instance of ObjectFilter -func NewObjectFilter(includeNamespaces, excludeNamespaces []string) (ObjectFilter, error) { - if len(includeNamespaces) > 0 && len(excludeNamespaces) > 0 { +func NewObjectFilter(includeNamespace, excludeNamespace string) (ObjectFilter, error) { + if len(includeNamespace) > 0 && len(excludeNamespace) > 0 { return ObjectFilter{}, fmt.Errorf("cannot specify both include and exclude namespace conditions") } return ObjectFilter{ - IncludeNamespaces: includeNamespaces, - ExcludeNamespaces: excludeNamespaces, + IncludeNamespace: includeNamespace, + ExcludeNamespace: excludeNamespace, }, nil } -// FilterChecks filters all to return set of checks based on the ObjectFilter -func (f ObjectFilter) Filter(objects *Objects) { - if len(f.IncludeNamespaces) > 0 { - var ps []corev1.Pod - for _, p := range objects.Pods.Items { - if contains(f.IncludeNamespaces, p.Namespace) { - ps = append(ps, p) - } - } - objects.Pods.Items = ps - - var pts []corev1.PodTemplate - for _, pt := range objects.PodTemplates.Items { - if contains(f.IncludeNamespaces, pt.Namespace) { - pts = append(pts, pt) - } - } - objects.PodTemplates.Items = pts - - var pvcs []corev1.PersistentVolumeClaim - for _, pvc := range objects.PersistentVolumeClaims.Items { - if contains(f.IncludeNamespaces, pvc.Namespace) { - pvcs = append(pvcs, pvc) - } - } - objects.PersistentVolumeClaims.Items = pvcs - - var cms []corev1.ConfigMap - for _, cm := range objects.ConfigMaps.Items { - if contains(f.IncludeNamespaces, cm.Namespace) { - cms = append(cms, cm) - } - } - objects.ConfigMaps.Items = cms - - var svcs []corev1.Service - for _, svc := range objects.Services.Items { - if contains(f.IncludeNamespaces, svc.Namespace) { - svcs = append(svcs, svc) - } - } - objects.Services.Items = svcs - - var scrts []corev1.Secret - for _, scrt := range objects.Secrets.Items { - if contains(f.IncludeNamespaces, scrt.Namespace) { - scrts = append(scrts, scrt) - } - } - objects.Secrets.Items = scrts - - var sas []corev1.ServiceAccount - for _, sa := range objects.ServiceAccounts.Items { - if contains(f.IncludeNamespaces, sa.Namespace) { - sas = append(sas, sa) - } - } - objects.ServiceAccounts.Items = sas - - var rqs []corev1.ResourceQuota - for _, rq := range objects.ResourceQuotas.Items { - if contains(f.IncludeNamespaces, rq.Namespace) { - rqs = append(rqs, rq) - } - } - objects.ResourceQuotas.Items = rqs - - var lrs []corev1.LimitRange - for _, lr := range objects.LimitRanges.Items { - if contains(f.IncludeNamespaces, lr.Namespace) { - lrs = append(lrs, lr) - } - } - objects.LimitRanges.Items = lrs - - return +// NamespaceOptions returns ListOptions for filtering by namespace +func (f ObjectFilter) NamespaceOptions(opts metav1.ListOptions) metav1.ListOptions { + if len(f.IncludeNamespace) > 0 { + opts.FieldSelector = fields.OneTermEqualSelector("metadata.namespace",f.IncludeNamespace).String() } - - if len(f.ExcludeNamespaces) > 0 { - var ps []corev1.Pod - for _, p := range objects.Pods.Items { - if !contains(f.ExcludeNamespaces, p.Namespace) { - ps = append(ps, p) - } - } - objects.Pods.Items = ps - - var pts []corev1.PodTemplate - for _, pt := range objects.PodTemplates.Items { - if !contains(f.ExcludeNamespaces, pt.Namespace) { - pts = append(pts, pt) - } - } - objects.PodTemplates.Items = pts - - var pvcs []corev1.PersistentVolumeClaim - for _, pvc := range objects.PersistentVolumeClaims.Items { - if !contains(f.ExcludeNamespaces, pvc.Namespace) { - pvcs = append(pvcs, pvc) - } - } - objects.PersistentVolumeClaims.Items = pvcs - - var cms []corev1.ConfigMap - for _, cm := range objects.ConfigMaps.Items { - if !contains(f.ExcludeNamespaces, cm.Namespace) { - cms = append(cms, cm) - } - } - objects.ConfigMaps.Items = cms - - var svcs []corev1.Service - for _, svc := range objects.Services.Items { - if !contains(f.ExcludeNamespaces, svc.Namespace) { - svcs = append(svcs, svc) - } - } - objects.Services.Items = svcs - - var scrts []corev1.Secret - for _, scrt := range objects.Secrets.Items { - if !contains(f.ExcludeNamespaces, scrt.Namespace) { - scrts = append(scrts, scrt) - } - } - objects.Secrets.Items = scrts - - var sas []corev1.ServiceAccount - for _, sa := range objects.ServiceAccounts.Items { - if !contains(f.ExcludeNamespaces, sa.Namespace) { - sas = append(sas, sa) - } - } - objects.ServiceAccounts.Items = sas - - var rqs []corev1.ResourceQuota - for _, rq := range objects.ResourceQuotas.Items { - if !contains(f.ExcludeNamespaces, rq.Namespace) { - rqs = append(rqs, rq) - } - } - objects.ResourceQuotas.Items = rqs - - var lrs []corev1.LimitRange - for _, lr := range objects.LimitRanges.Items { - if !contains(f.ExcludeNamespaces, lr.Namespace) { - lrs = append(lrs, lr) - } - } - objects.LimitRanges.Items = lrs - - return + if len(f.ExcludeNamespace) > 0 { + opts.FieldSelector = fields.OneTermNotEqualSelector("metadata.namespace",f.ExcludeNamespace).String() } -} - + return opts +} \ No newline at end of file diff --git a/kube/object_filter_test.go b/kube/object_filter_test.go index 73ec499..95fd950 100644 --- a/kube/object_filter_test.go +++ b/kube/object_filter_test.go @@ -18,103 +18,32 @@ package kube import ( "fmt" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "testing" "github.com/stretchr/testify/assert" ) func TestNamespaceError(t *testing.T) { - _, err := NewObjectFilter([]string{"kube-system"}, []string{"kube-system"}) + _, err := NewObjectFilter("kube-system", "kube-system") assert.Error(t, err) assert.Equal(t, fmt.Errorf("cannot specify both include and exclude namespace conditions"), err) } -func TestFilter(t *testing.T) { - filter, err := NewObjectFilter([]string{"namespace_1"},nil) +func TestNamespaceOptions(t *testing.T) { + filter, err := NewObjectFilter("namespace-1","") assert.NoError(t, err) - objects := namespaceObjects() - filter.Filter(objects) - assert.Equal(t, namespace1Objects(), objects) + assert.Equal(t, + metav1.ListOptions{FieldSelector: fields.OneTermEqualSelector("metadata.namespace","namespace-1").String()}, + filter.NamespaceOptions(metav1.ListOptions{}), + ) - filter, err = NewObjectFilter(nil,[]string{"namespace_2"}) + filter, err = NewObjectFilter("","namespace-2") assert.NoError(t, err) - objects = namespaceObjects() - filter.Filter(objects) - assert.Equal(t, namespace1Objects(), objects) -} - -func objects() *Objects { - return &Objects{ - Pods: &corev1.PodList{}, - PodTemplates: &corev1.PodTemplateList{}, - PersistentVolumeClaims: &corev1.PersistentVolumeClaimList{}, - ConfigMaps: &corev1.ConfigMapList{}, - Services: &corev1.ServiceList{}, - Secrets: &corev1.SecretList{}, - ServiceAccounts: &corev1.ServiceAccountList{}, - ResourceQuotas: &corev1.ResourceQuotaList{}, - LimitRanges: &corev1.LimitRangeList{}, - } -} - -func namespaceObjects() *Objects { - objs := objects() - objs.Pods = &corev1.PodList{Items: []corev1.Pod{ - {ObjectMeta: metav1.ObjectMeta{Name: "pod_1", Namespace: "namespace_1"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "pod_2", Namespace: "namespace_2"}}, - }} - objs.PodTemplates = &corev1.PodTemplateList{Items: []corev1.PodTemplate{ - {ObjectMeta: metav1.ObjectMeta{Name: "template_1", Namespace: "namespace_1"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "template_2", Namespace: "namespace_2"}}, - }} - objs.PersistentVolumeClaims = &corev1.PersistentVolumeClaimList{Items: []corev1.PersistentVolumeClaim{ - {ObjectMeta: metav1.ObjectMeta{Name: "pvc_1", Namespace: "namespace_1"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "pvc_2", Namespace: "namespace_2"}}, - }} - objs.ConfigMaps = &corev1.ConfigMapList{Items: []corev1.ConfigMap{ - {ObjectMeta: metav1.ObjectMeta{Name: "cm_1", Namespace: "namespace_1"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "cm_2", Namespace: "namespace_2"}}, - }} - objs.Services = &corev1.ServiceList{Items: []corev1.Service{ - {ObjectMeta: metav1.ObjectMeta{Name: "svc_1", Namespace: "namespace_1"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "svc_2", Namespace: "namespace_2"}}, - }} - objs.Secrets = &corev1.SecretList{Items: []corev1.Secret{ - {ObjectMeta: metav1.ObjectMeta{Name: "secret_1", Namespace: "namespace_1"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "secret_2", Namespace: "namespace_2"}}, - }} - objs.ServiceAccounts = &corev1.ServiceAccountList{Items: []corev1.ServiceAccount{ - {ObjectMeta: metav1.ObjectMeta{Name: "sa_1", Namespace: "namespace_1"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "sa_2", Namespace: "namespace_2"}}, - }} - return objs -} - -func namespace1Objects() *Objects { - objs := objects() - objs.Pods = &corev1.PodList{Items: []corev1.Pod{ - {ObjectMeta: metav1.ObjectMeta{Name: "pod_1", Namespace: "namespace_1"}}, - }} - objs.PodTemplates = &corev1.PodTemplateList{Items: []corev1.PodTemplate{ - {ObjectMeta: metav1.ObjectMeta{Name: "template_1", Namespace: "namespace_1"}}, - }} - objs.PersistentVolumeClaims = &corev1.PersistentVolumeClaimList{Items: []corev1.PersistentVolumeClaim{ - {ObjectMeta: metav1.ObjectMeta{Name: "pvc_1", Namespace: "namespace_1"}}, - }} - objs.ConfigMaps = &corev1.ConfigMapList{Items: []corev1.ConfigMap{ - {ObjectMeta: metav1.ObjectMeta{Name: "cm_1", Namespace: "namespace_1"}}, - }} - objs.Services = &corev1.ServiceList{Items: []corev1.Service{ - {ObjectMeta: metav1.ObjectMeta{Name: "svc_1", Namespace: "namespace_1"}}, - }} - objs.Secrets = &corev1.SecretList{Items: []corev1.Secret{ - {ObjectMeta: metav1.ObjectMeta{Name: "secret_1", Namespace: "namespace_1"}}, - }} - objs.ServiceAccounts = &corev1.ServiceAccountList{Items: []corev1.ServiceAccount{ - {ObjectMeta: metav1.ObjectMeta{Name: "sa_1", Namespace: "namespace_1"}}, - }} - return objs + assert.Equal(t, + metav1.ListOptions{FieldSelector: fields.OneTermNotEqualSelector("metadata.namespace","namespace-2").String()}, + filter.NamespaceOptions(metav1.ListOptions{}), + ) } \ No newline at end of file diff --git a/kube/objects.go b/kube/objects.go index 7488edd..ac4701e 100644 --- a/kube/objects.go +++ b/kube/objects.go @@ -64,7 +64,7 @@ type Client struct { // FetchObjects returns the objects from a Kubernetes cluster. // ctx is currently unused during API calls. More info: https://github.com/kubernetes/community/pull/1166 -func (c *Client) FetchObjects(ctx context.Context) (*Objects, error) { +func (c *Client) FetchObjects(ctx context.Context,filter ObjectFilter) (*Objects, error) { client := c.KubeClient.CoreV1() admissionControllerClient := c.KubeClient.AdmissionregistrationV1beta1() opts := metav1.ListOptions{} @@ -85,39 +85,39 @@ func (c *Client) FetchObjects(ctx context.Context) (*Objects, error) { return }) g.Go(func() (err error) { - objects.Pods, err = client.Pods(corev1.NamespaceAll).List(opts) + objects.Pods, err = client.Pods(corev1.NamespaceAll).List(filter.NamespaceOptions(opts)) return }) g.Go(func() (err error) { - objects.PodTemplates, err = client.PodTemplates(corev1.NamespaceAll).List(opts) + objects.PodTemplates, err = client.PodTemplates(corev1.NamespaceAll).List(filter.NamespaceOptions(opts)) return }) g.Go(func() (err error) { - objects.PersistentVolumeClaims, err = client.PersistentVolumeClaims(corev1.NamespaceAll).List(opts) + objects.PersistentVolumeClaims, err = client.PersistentVolumeClaims(corev1.NamespaceAll).List(filter.NamespaceOptions(opts)) return }) g.Go(func() (err error) { - objects.ConfigMaps, err = client.ConfigMaps(corev1.NamespaceAll).List(opts) + objects.ConfigMaps, err = client.ConfigMaps(corev1.NamespaceAll).List(filter.NamespaceOptions(opts)) return }) g.Go(func() (err error) { - objects.Secrets, err = client.Secrets(corev1.NamespaceAll).List(opts) + objects.Secrets, err = client.Secrets(corev1.NamespaceAll).List(filter.NamespaceOptions(opts)) return }) g.Go(func() (err error) { - objects.Services, err = client.Services(corev1.NamespaceAll).List(opts) + objects.Services, err = client.Services(corev1.NamespaceAll).List(filter.NamespaceOptions(opts)) return }) g.Go(func() (err error) { - objects.ServiceAccounts, err = client.ServiceAccounts(corev1.NamespaceAll).List(opts) + objects.ServiceAccounts, err = client.ServiceAccounts(corev1.NamespaceAll).List(filter.NamespaceOptions(opts)) return }) g.Go(func() (err error) { - objects.ResourceQuotas, err = client.ResourceQuotas(corev1.NamespaceAll).List(opts) + objects.ResourceQuotas, err = client.ResourceQuotas(corev1.NamespaceAll).List(filter.NamespaceOptions(opts)) return }) g.Go(func() (err error) { - objects.LimitRanges, err = client.LimitRanges(corev1.NamespaceAll).List(opts) + objects.LimitRanges, err = client.LimitRanges(corev1.NamespaceAll).List(filter.NamespaceOptions(opts)) return }) g.Go(func() (err error) { diff --git a/kube/objects_test.go b/kube/objects_test.go index fbb54a5..31fb736 100644 --- a/kube/objects_test.go +++ b/kube/objects_test.go @@ -40,7 +40,7 @@ func TestFetchObjects(t *testing.T) { Labels: map[string]string{"doks_key": "bar"}}, }) - actual, err := api.FetchObjects(context.Background()) + actual, err := api.FetchObjects(context.Background(),ObjectFilter{}) assert.NoError(t, err) assert.NotNil(t, actual.Nodes)