From c1c27fd57664d12e18d414216d7a159822945a20 Mon Sep 17 00:00:00 2001 From: Varsha Varadarajan Date: Tue, 11 Jun 2019 17:06:47 -0400 Subject: [PATCH] Add run command to run all checks * Optionally takes in a group flag to filter checks by group name. * Optionally takes in a name flag to run a specific check. --- checks/registry.go | 9 ++ cmd/clusterlint/main.go | 115 ++++++++++++++++-- go.mod | 1 + go.sum | 4 + vendor/golang.org/x/sync/AUTHORS | 3 + vendor/golang.org/x/sync/CONTRIBUTORS | 3 + vendor/golang.org/x/sync/LICENSE | 27 ++++ vendor/golang.org/x/sync/PATENTS | 22 ++++ vendor/golang.org/x/sync/errgroup/errgroup.go | 66 ++++++++++ vendor/modules.txt | 38 +++--- 10 files changed, 262 insertions(+), 26 deletions(-) create mode 100644 vendor/golang.org/x/sync/AUTHORS create mode 100644 vendor/golang.org/x/sync/CONTRIBUTORS create mode 100644 vendor/golang.org/x/sync/LICENSE create mode 100644 vendor/golang.org/x/sync/PATENTS create mode 100644 vendor/golang.org/x/sync/errgroup/errgroup.go diff --git a/checks/registry.go b/checks/registry.go index bbed265..754add6 100644 --- a/checks/registry.go +++ b/checks/registry.go @@ -84,3 +84,12 @@ func GetGroup(name string) []Check { return ret } + +func Get(name string) (Check, error) { + registry.mu.RLock() + defer registry.mu.RUnlock() + if registry.checks[name] != nil { + return registry.checks[name], nil + } + return nil, fmt.Errorf("Check not found: %s", name) +} diff --git a/cmd/clusterlint/main.go b/cmd/clusterlint/main.go index 7287471..1f38238 100644 --- a/cmd/clusterlint/main.go +++ b/cmd/clusterlint/main.go @@ -3,13 +3,16 @@ package main import ( "flag" "fmt" + "log" "os" "path/filepath" + "sync" "github.com/digitalocean/clusterlint" "github.com/digitalocean/clusterlint/checks" _ "github.com/digitalocean/clusterlint/checks/noop" "github.com/urfave/cli" + "golang.org/x/sync/errgroup" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -46,27 +49,117 @@ func main() { return nil }, }, + { + Name: "run", + Usage: "run all checks in the registry", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "group, g", + Usage: "run all checks in group `GROUP`", + }, + cli.StringFlag{ + Name: "name, n", + Usage: "run a specific check", + }, + }, + Action: func(c *cli.Context) error { + group := c.String("group") + name := c.String("name") + runChecks(group, name) + return nil + }, + }, } err := app.Run(os.Args) if err != nil { panic("boo") } - // api := &KubernetesAPI{Client: buildClient()} - // api.fetch() } +// listChecks lists the names and desc of all checks in the group if found +// lists all checks in the registry if group is not specified func listChecks(group string) { - var allChecks []checks.Check - if group == "" { - allChecks = checks.List() - } else { - allChecks = checks.GetGroup(group) - } + allChecks := getChecks(group) for _, check := range allChecks { fmt.Printf("%s : %s\n", check.Name(), check.Description()) } } +func runChecks(group, name string) { + api := &KubernetesAPI{Client: buildClient()} + objects := api.fetch() + if "" == name { + runChecksForGroup(group, objects) + } else { + runCheck(name, objects) + } +} + +// runChecksForGroup runs all checks in the specified group if found +// runs all checks in the registry if group is not specified +func runChecksForGroup(group string, objects *clusterlint.KubeObjects) { + allChecks := getChecks(group) + var warnings, errors []error + var mu sync.Mutex + var g errgroup.Group + + for _, check := range allChecks { + check := check + g.Go(func() error { + log.Println("Running check: ", check.Name()) + w, e, err := check.Run(objects) + if err != nil { + return err + } + mu.Lock() + warnings = append(warnings, w...) + errors = append(errors, e...) + mu.Unlock() + return nil + }) + } + err := g.Wait() + showErrorsAndWarnings(warnings, errors) + if err != nil { + handleError(err) + } +} + +// runCheck runs a specific check identified by check.Name() +// errors out if the check is not found in the registry +func runCheck(name string, objects *clusterlint.KubeObjects) { + check, err := checks.Get(name) + if err != nil { + handleError(err) + } + + log.Println("Running check: ", name) + warnings, errors, err := check.Run(objects) + showErrorsAndWarnings(warnings, errors) + handleError(err) +} + +//showErrorsAndWarnings displays all the errors and warnings returned by checks +func showErrorsAndWarnings(warnings, errors []error) { + for _, warning := range warnings { + log.Println("Warning: ", warning.Error()) + } + for _, err := range errors { + log.Println("Error: ", err.Error()) + } +} + +// getChecks retrieves all checks within given group +// returns all checks in the registry if group in unspecified +func getChecks(group string) []checks.Check { + if group == "" { + return checks.List() + } + return checks.GetGroup(group) +} + +// fetch initializes a KubeObjects instance with live cluster objects +// Currently limited to core k8s API objects func (k KubernetesAPI) fetch() *clusterlint.KubeObjects { client := k.Client.CoreV1() opts := metav1.ListOptions{} @@ -112,6 +205,8 @@ func (k KubernetesAPI) fetch() *clusterlint.KubeObjects { return objects } +// buildClient parses command line args and initializes the k8s client +// to invoke APIs func buildClient() kubernetes.Interface { k8sconfig := flag.String("kubeconfig", filepath.Join(os.Getenv("HOME"), ".kube", "config"), "absolute path to the kubeconfig file") context := flag.String("context", "", "context for the kubernetes client. default: current context") @@ -128,6 +223,7 @@ func buildClient() kubernetes.Interface { return client } +// buildConfigFromFlags initializes client config with given context func buildConfigFromFlags(context, kubeconfigPath *string) (*rest.Config, error) { return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( &clientcmd.ClientConfigLoadingRules{ExplicitPath: *kubeconfigPath}, @@ -136,8 +232,9 @@ func buildConfigFromFlags(context, kubeconfigPath *string) (*rest.Config, error) }).ClientConfig() } +// handleError logs error to stdout and exits func handleError(err error) { if err != nil { - panic(err.Error()) + log.Fatal(err.Error()) } } diff --git a/go.mod b/go.mod index a6f9201..61f2b4f 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.12 require ( github.com/stretchr/testify v1.2.2 github.com/urfave/cli v1.19.1 + golang.org/x/sync v0.0.0-20190423024810-112230192c58 k8s.io/api v0.0.0-20190602125759-c1e9adbde704 k8s.io/apimachinery v0.0.0-20190602125621-c0632ccbde11 k8s.io/client-go v0.0.0-20190602130007-e65ca70987a6 diff --git a/go.sum b/go.sum index 0e4e5f0..e965f47 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/urfave/cli v1.19.1 h1:0mKm4ZoB74PxYmZVua162y1dGt1qc10MyymYRBf3lb8= +github.com/urfave/cli v1.19.1/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= golang.org/x/crypto v0.0.0-20181025213731-e84da0312774 h1:a4tQYYYuK9QdeO/+kEvNYyuR21S+7ve5EANok6hABhI= golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -53,6 +55,8 @@ golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0 golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313 h1:pczuHS43Cp2ktBEEmLwScxgjWsBSzdaQiKzUyf3DTTc= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/golang.org/x/sync/AUTHORS b/vendor/golang.org/x/sync/AUTHORS new file mode 100644 index 0000000..15167cd --- /dev/null +++ b/vendor/golang.org/x/sync/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/sync/CONTRIBUTORS b/vendor/golang.org/x/sync/CONTRIBUTORS new file mode 100644 index 0000000..1c4577e --- /dev/null +++ b/vendor/golang.org/x/sync/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/sync/LICENSE b/vendor/golang.org/x/sync/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/vendor/golang.org/x/sync/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/sync/PATENTS b/vendor/golang.org/x/sync/PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/vendor/golang.org/x/sync/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/sync/errgroup/errgroup.go b/vendor/golang.org/x/sync/errgroup/errgroup.go new file mode 100644 index 0000000..9857fe5 --- /dev/null +++ b/vendor/golang.org/x/sync/errgroup/errgroup.go @@ -0,0 +1,66 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package errgroup provides synchronization, error propagation, and Context +// cancelation for groups of goroutines working on subtasks of a common task. +package errgroup + +import ( + "context" + "sync" +) + +// A Group is a collection of goroutines working on subtasks that are part of +// the same overall task. +// +// A zero Group is valid and does not cancel on error. +type Group struct { + cancel func() + + wg sync.WaitGroup + + errOnce sync.Once + err error +} + +// WithContext returns a new Group and an associated Context derived from ctx. +// +// The derived Context is canceled the first time a function passed to Go +// returns a non-nil error or the first time Wait returns, whichever occurs +// first. +func WithContext(ctx context.Context) (*Group, context.Context) { + ctx, cancel := context.WithCancel(ctx) + return &Group{cancel: cancel}, ctx +} + +// Wait blocks until all function calls from the Go method have returned, then +// returns the first non-nil error (if any) from them. +func (g *Group) Wait() error { + g.wg.Wait() + if g.cancel != nil { + g.cancel() + } + return g.err +} + +// Go calls the given function in a new goroutine. +// +// The first call to return a non-nil error cancels the group; its error will be +// returned by Wait. +func (g *Group) Go(f func() error) { + g.wg.Add(1) + + go func() { + defer g.wg.Done() + + if err := f(); err != nil { + g.errOnce.Do(func() { + g.err = err + if g.cancel != nil { + g.cancel() + } + }) + } + }() +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 4527ea4..643c7b6 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -31,6 +31,8 @@ github.com/pmezard/go-difflib/difflib github.com/spf13/pflag # github.com/stretchr/testify v1.2.2 github.com/stretchr/testify/assert +# github.com/urfave/cli v1.19.1 +github.com/urfave/cli # golang.org/x/crypto v0.0.0-20181025213731-e84da0312774 golang.org/x/crypto/ssh/terminal # golang.org/x/net v0.0.0-20190206173232-65e2d4e15006 @@ -43,6 +45,8 @@ golang.org/x/net/context/ctxhttp # golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a golang.org/x/oauth2 golang.org/x/oauth2/internal +# golang.org/x/sync v0.0.0-20190423024810-112230192c58 +golang.org/x/sync/errgroup # golang.org/x/sys v0.0.0-20190312061237-fead79001313 golang.org/x/sys/unix golang.org/x/sys/windows @@ -115,18 +119,18 @@ k8s.io/apimachinery/pkg/labels k8s.io/apimachinery/pkg/selection k8s.io/apimachinery/pkg/util/runtime k8s.io/apimachinery/pkg/watch +k8s.io/apimachinery/pkg/api/errors +k8s.io/apimachinery/pkg/runtime/serializer/streaming +k8s.io/apimachinery/pkg/util/net +k8s.io/apimachinery/pkg/util/sets k8s.io/apimachinery/pkg/util/errors k8s.io/apimachinery/pkg/util/validation k8s.io/apimachinery/pkg/conversion/queryparams k8s.io/apimachinery/pkg/util/json k8s.io/apimachinery/pkg/util/naming -k8s.io/apimachinery/pkg/util/sets k8s.io/apimachinery/third_party/forked/golang/reflect -k8s.io/apimachinery/pkg/util/net -k8s.io/apimachinery/pkg/api/errors k8s.io/apimachinery/pkg/runtime/serializer k8s.io/apimachinery/pkg/version -k8s.io/apimachinery/pkg/runtime/serializer/streaming k8s.io/apimachinery/pkg/util/clock k8s.io/apimachinery/pkg/util/validation/field k8s.io/apimachinery/pkg/runtime/serializer/json @@ -142,6 +146,7 @@ k8s.io/apimachinery/pkg/util/mergepatch k8s.io/apimachinery/third_party/forked/golang/json # k8s.io/client-go v0.0.0-20190602130007-e65ca70987a6 k8s.io/client-go/kubernetes +k8s.io/client-go/rest k8s.io/client-go/tools/clientcmd k8s.io/client-go/discovery k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1 @@ -180,21 +185,25 @@ k8s.io/client-go/kubernetes/typed/settings/v1alpha1 k8s.io/client-go/kubernetes/typed/storage/v1 k8s.io/client-go/kubernetes/typed/storage/v1alpha1 k8s.io/client-go/kubernetes/typed/storage/v1beta1 -k8s.io/client-go/rest k8s.io/client-go/util/flowcontrol -k8s.io/client-go/tools/auth +k8s.io/client-go/pkg/version +k8s.io/client-go/plugin/pkg/client/auth/exec +k8s.io/client-go/rest/watch k8s.io/client-go/tools/clientcmd/api +k8s.io/client-go/tools/metrics +k8s.io/client-go/transport +k8s.io/client-go/util/cert +k8s.io/client-go/tools/auth k8s.io/client-go/tools/clientcmd/api/latest k8s.io/client-go/util/homedir k8s.io/client-go/kubernetes/fake k8s.io/client-go/kubernetes/scheme k8s.io/client-go/tools/reference -k8s.io/client-go/pkg/version -k8s.io/client-go/plugin/pkg/client/auth/exec -k8s.io/client-go/rest/watch -k8s.io/client-go/tools/metrics -k8s.io/client-go/transport -k8s.io/client-go/util/cert +k8s.io/client-go/pkg/apis/clientauthentication +k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1 +k8s.io/client-go/pkg/apis/clientauthentication/v1beta1 +k8s.io/client-go/util/connrotation +k8s.io/client-go/util/keyutil k8s.io/client-go/tools/clientcmd/api/v1 k8s.io/client-go/discovery/fake k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1/fake @@ -234,11 +243,6 @@ k8s.io/client-go/kubernetes/typed/storage/v1/fake k8s.io/client-go/kubernetes/typed/storage/v1alpha1/fake k8s.io/client-go/kubernetes/typed/storage/v1beta1/fake k8s.io/client-go/testing -k8s.io/client-go/pkg/apis/clientauthentication -k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1 -k8s.io/client-go/pkg/apis/clientauthentication/v1beta1 -k8s.io/client-go/util/connrotation -k8s.io/client-go/util/keyutil # k8s.io/klog v0.3.2 k8s.io/klog # k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30