diff --git a/cmd/delete.go b/cmd/delete.go index 4cf497a988624eab70ad26e9a1a04a4cd1d4e295..c043450f5cdcd28d33c5a029f5a7ad54afc68ac0 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -4,7 +4,7 @@ import ( "fmt" "sort" - "github.com/golang/glog" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "k8s.io/client-go/pkg/api/errors" "k8s.io/client-go/pkg/api/v1" @@ -57,7 +57,7 @@ var deleteCmd = &cobra.Command{ for _, obj := range objs { desc := fmt.Sprintf("%s/%s", obj.GetKind(), fqName(obj)) - glog.Info("Deleting ", desc) + log.Info("Deleting ", desc) c, err := clientForResource(clientpool, disco, obj, defaultNs) if err != nil { @@ -69,7 +69,7 @@ var deleteCmd = &cobra.Command{ return fmt.Errorf("Error deleting %s: %s", desc, err) } - glog.V(2).Info("Deleted object: ", obj) + log.Debugf("Deleted object: ", obj) } return nil diff --git a/cmd/diff.go b/cmd/diff.go index 61128acadb804bb2d1db2972917c46e83625f6aa..a5e38a7b6d7443e84bffe3e8b706b6abe308b8d9 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -6,8 +6,8 @@ import ( "os" "sort" - "github.com/golang/glog" "github.com/mattn/go-isatty" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/yudai/gojsondiff" "github.com/yudai/gojsondiff/formatter" @@ -45,7 +45,7 @@ var diffCmd = &cobra.Command{ for _, obj := range objs { desc := fmt.Sprintf("%s/%s", obj.GetKind(), fqName(obj)) - glog.V(2).Info("Fetching ", desc) + log.Debugf("Fetching ", desc) c, err := clientForResource(clientpool, disco, obj, defaultNs) if err != nil { @@ -54,7 +54,7 @@ var diffCmd = &cobra.Command{ liveObj, err := c.Get(obj.GetName()) if err != nil && errors.IsNotFound(err) { - glog.V(2).Infof("%s doesn't exist on the server", desc) + log.Debugf("%s doesn't exist on the server", desc) liveObj = nil } else if err != nil { return fmt.Errorf("Error fetching %s: %v", desc, err) diff --git a/cmd/root.go b/cmd/root.go index 073805f0ac48292161519ed5a954db23fed1b89d..b7d6355d3400df192ba00eb98ed406fd0adbe8dd 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,15 +5,17 @@ import ( "encoding/json" goflag "flag" "fmt" + "io" "io/ioutil" "net/http" "os" "path/filepath" "strings" - "github.com/golang/glog" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" jsonnet "github.com/strickyak/jsonnet_cgo" + "golang.org/x/crypto/ssh/terminal" "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" "k8s.io/client-go/pkg/api/unversioned" @@ -24,6 +26,7 @@ import ( ) const ( + flagVerbose = "verbose" flagJpath = "jpath" flagExtVar = "ext-str" flagExtVarFile = "ext-str-file" @@ -36,6 +39,7 @@ const ( var clientConfig clientcmd.ClientConfig func init() { + RootCmd.PersistentFlags().CountP(flagVerbose, "v", "Increase verbosity. May be given multiple times.") RootCmd.PersistentFlags().StringP(flagJpath, "J", "", "Additional jsonnet library search path") RootCmd.PersistentFlags().StringSliceP(flagExtVar, "V", nil, "Values of external variables") RootCmd.PersistentFlags().StringSlice(flagExtVarFile, nil, "Read external variable from a file") @@ -53,8 +57,6 @@ func init() { clientcmd.BindOverrideFlags(&overrides, RootCmd.PersistentFlags(), kflags) clientConfig = clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, &overrides, os.Stdin) - // Standard goflags (glog in particular) - RootCmd.PersistentFlags().AddGoFlagSet(goflag.CommandLine) RootCmd.PersistentFlags().Set("logtostderr", "true") } @@ -64,12 +66,78 @@ var RootCmd = &cobra.Command{ Short: "Synchronise Kubernetes resources with config files", SilenceErrors: true, SilenceUsage: true, - PersistentPreRun: func(cmd *cobra.Command, args []string) { + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { goflag.CommandLine.Parse([]string{}) - glog.CopyStandardLogTo("INFO") + flags := cmd.Flags() + out := cmd.OutOrStderr() + log.SetOutput(out) + + logFmt := NewLogFormatter(out) + log.SetFormatter(logFmt) + + verbosity, err := flags.GetCount(flagVerbose) + if err != nil { + return err + } + log.SetLevel(logLevel(verbosity)) + + return nil }, } +func logLevel(verbosity int) log.Level { + switch verbosity { + case 0: + return log.WarnLevel + case 1: + return log.InfoLevel + default: + return log.DebugLevel + } +} + +type logFormatter struct { + escapes *terminal.EscapeCodes + colorise bool +} + +// NewLogFormatter creates a new log.Formatter customised for writer +func NewLogFormatter(out io.Writer) log.Formatter { + var ret = logFormatter{} + if f, ok := out.(*os.File); ok { + ret.colorise = terminal.IsTerminal(int(f.Fd())) + ret.escapes = terminal.NewTerminal(f, "").Escape + } + return &ret +} + +func (f *logFormatter) levelEsc(level log.Level) []byte { + switch level { + case log.DebugLevel: + return []byte{} + case log.WarnLevel: + return f.escapes.Yellow + case log.ErrorLevel, log.FatalLevel, log.PanicLevel: + return f.escapes.Red + default: + return f.escapes.Blue + } +} + +func (f *logFormatter) Format(e *log.Entry) ([]byte, error) { + buf := bytes.Buffer{} + if f.colorise { + buf.Write(f.levelEsc(e.Level)) + fmt.Fprintf(&buf, "%-5s ", strings.ToUpper(e.Level.String())) + buf.Write(f.escapes.Reset) + } + + buf.WriteString(strings.TrimSpace(e.Message)) + buf.WriteString("\n") + + return buf.Bytes(), nil +} + // JsonnetVM constructs a new jsonnet.VM, according to command line // flags func JsonnetVM(cmd *cobra.Command) (*jsonnet.VM, error) { @@ -78,7 +146,7 @@ func JsonnetVM(cmd *cobra.Command) (*jsonnet.VM, error) { jpath := os.Getenv("KUBECFG_JPATH") for _, p := range filepath.SplitList(jpath) { - glog.V(2).Infoln("Adding jsonnet search path", p) + log.Debugln("Adding jsonnet search path", p) vm.JpathAdd(p) } @@ -87,7 +155,7 @@ func JsonnetVM(cmd *cobra.Command) (*jsonnet.VM, error) { return nil, err } for _, p := range filepath.SplitList(jpath) { - glog.V(2).Infoln("Adding jsonnet search path", p) + log.Debugln("Adding jsonnet search path", p) vm.JpathAdd(p) } @@ -188,7 +256,7 @@ func buildResolver(cmd *cobra.Command) (utils.Resolver, error) { ret.OnErr = func(error) error { return nil } case "warn": ret.OnErr = func(err error) error { - glog.Warning(err.Error()) + log.Warning(err.Error()) return nil } case "error": @@ -280,7 +348,7 @@ func serverResourceForGroupVersionKind(disco discovery.DiscoveryInterface, gvk u for _, r := range resources.APIResources { if r.Kind == gvk.Kind { - glog.V(4).Infof("Chose API '%s' for %s", r.Name, gvk) + log.Debugf("Chose API '%s' for %s", r.Name, gvk) return &r, nil } } @@ -306,7 +374,7 @@ func clientForResource(pool dynamic.ClientPool, disco discovery.DiscoveryInterfa namespace = defNs } - glog.V(4).Infof("Fetching client for %s namespace=%s", resource, namespace) + log.Debugf("Fetching client for %s namespace=%s", resource, namespace) rc := client.Resource(resource, namespace) return rc, nil } diff --git a/cmd/update.go b/cmd/update.go index a2ade46c24963eea14fde0706842ea8ab1c8b137..8151e9096174a0a2aee724a11d831a67cf55afc8 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -5,7 +5,7 @@ import ( "fmt" "sort" - "github.com/golang/glog" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "k8s.io/client-go/pkg/api" "k8s.io/client-go/pkg/api/errors" @@ -54,7 +54,7 @@ var updateCmd = &cobra.Command{ for _, obj := range objs { desc := fmt.Sprintf("%s/%s", obj.GetKind(), fqName(obj)) - glog.Info("Updating ", desc) + log.Info("Updating ", desc) c, err := clientForResource(clientpool, disco, obj, defaultNs) if err != nil { @@ -67,7 +67,7 @@ var updateCmd = &cobra.Command{ } newobj, err := c.Patch(obj.GetName(), api.MergePatchType, asPatch) if create && errors.IsNotFound(err) { - glog.Info(" Creating non-existent ", desc) + log.Info(" Creating non-existent ", desc) newobj, err = c.Create(obj) } if err != nil { @@ -75,7 +75,7 @@ var updateCmd = &cobra.Command{ return fmt.Errorf("Error updating %s: %s", desc, err) } - glog.V(2).Info("Updated object: ", diff.ObjectDiff(obj, newobj)) + log.Debug("Updated object: ", diff.ObjectDiff(obj, newobj)) } return nil diff --git a/main.go b/main.go index 4a4fb81c0bfa357434827d55e4ea2758e8232b0a..9ce52b9cb4de943dd80171cdb499137d728b5575 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,7 @@ package main import ( - "fmt" - "os" + log "github.com/sirupsen/logrus" "github.com/ksonnet/kubecfg/cmd" ) @@ -14,7 +13,11 @@ func main() { cmd.Version = version if err := cmd.RootCmd.Execute(); err != nil { - fmt.Fprintln(os.Stderr, "Error:", err) - os.Exit(1) + // PersistentPreRunE may not have been run for early + // errors, like invalid command line flags. + logFmt := cmd.NewLogFormatter(log.StandardLogger().Out) + log.SetFormatter(logFmt) + + log.Fatal(err.Error()) } } diff --git a/utils/acquire.go b/utils/acquire.go index ed45d65f5fb45eee1da71b5d8538e4e879b530d5..b1fbecf561d16197818bab36377565fc547db9d2 100644 --- a/utils/acquire.go +++ b/utils/acquire.go @@ -9,7 +9,7 @@ import ( "os" "path/filepath" - "github.com/golang/glog" + log "github.com/sirupsen/logrus" jsonnet "github.com/strickyak/jsonnet_cgo" "k8s.io/client-go/pkg/runtime" "k8s.io/client-go/pkg/util/yaml" @@ -63,7 +63,6 @@ func yamlReader(r io.ReadCloser) ([]runtime.Object, error) { } else if err != nil { return nil, err } - glog.V(4).Infof("Read %d bytes of YAML: %s", len(bytes), bytes) if len(bytes) == 0 { continue } @@ -71,7 +70,6 @@ func yamlReader(r io.ReadCloser) ([]runtime.Object, error) { if err != nil { return nil, err } - glog.V(4).Infof("Converted to JSON: %s", jsondata) obj, _, err := runtime.UnstructuredJSONScheme.Decode(jsondata, nil, nil) if err != nil { return nil, err @@ -117,7 +115,7 @@ func jsonnetReader(vm *jsonnet.VM, path string) ([]runtime.Object, error) { return nil, err } - glog.V(4).Infof("jsonnet result is: %s\n", jsonstr) + log.Debugf("jsonnet result is: %s", jsonstr) var top interface{} if err = json.Unmarshal([]byte(jsonstr), &top); err != nil { diff --git a/utils/registry.go b/utils/registry.go index 23a163c05635f27756142126814e39bc4f58b74f..1ec7cfab57c2ed2b7ab64bda8f320e192ceb5429 100644 --- a/utils/registry.go +++ b/utils/registry.go @@ -9,7 +9,7 @@ import ( "regexp" "strings" - "github.com/golang/glog" + log "github.com/sirupsen/logrus" ) var ( @@ -35,7 +35,7 @@ func NewRegistryClient(client *http.Client, url string) *Registry { func (r *Registry) ManifestDigest(reponame, tag string) (string, error) { url := fmt.Sprintf("%s/v2/%s/manifests/%s", r.URL, reponame, tag) - glog.V(1).Infof("HEAD %s", url) + log.Debugf("HEAD %s", url) req, err := http.NewRequest(http.MethodHead, url, nil) if err != nil { @@ -57,7 +57,7 @@ func (r *Registry) ManifestDigest(reponame, tag string) (string, error) { return "", errors.New("No digest in response") } - glog.V(1).Infof("Found digest %s", digest) + log.Debugf("Found digest %s", digest) return digest, nil } @@ -135,16 +135,16 @@ type authTransport struct { // RoundTrip is required for the http.RoundTripper interface func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) { - glog.V(1).Infof("=> %v", req) + log.Debugf("=> %v", req) resp, err := t.Transport.RoundTrip(req) - glog.V(1).Infof("<= err=%v resp=%v", err, resp) + log.Debugf("<= err=%v resp=%v", err, resp) if err == nil && resp.StatusCode == http.StatusUnauthorized && matchesDomain(req.URL, t.HostDomain) { schemes := parseAuthHeader(resp.Header) for _, scheme := range schemes { if scheme.Scheme == "basic" { - glog.V(2).Infof("Retrying with basic auth") + log.Debugf("Retrying with basic auth") req.SetBasicAuth(t.Username, t.Password) - glog.V(1).Infof("=> %v", req) + log.Debugf("=> %v", req) return t.Transport.RoundTrip(req) } if scheme.Scheme == "bearer" { @@ -152,9 +152,9 @@ func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) { if err != nil { return resp, err } - glog.V(2).Infof("Retrying with bearer auth") + log.Debugf("Retrying with bearer auth") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - glog.V(1).Infof("=> %v", req) + log.Debugf("=> %v", req) return t.Transport.RoundTrip(req) } } @@ -187,7 +187,7 @@ func (t *authTransport) bearerAuth(realm, service, scope string) (string, error) req.SetBasicAuth(t.Username, t.Password) } - glog.V(3).Infof("Performing oauth request to %s", req.URL) + log.Debugf("Performing oauth request to %s", req.URL) resp, err := t.Client.Do(req) if err != nil { return "", err @@ -205,7 +205,7 @@ func (t *authTransport) bearerAuth(realm, service, scope string) (string, error) if err := json.NewDecoder(resp.Body).Decode(&token); err != nil { return "", err } - glog.V(4).Infof("Got oauth token %q", token.Token) + log.Debugf("Got oauth token %q", token.Token) t.tokenCache[cacheKey] = token.Token return token.Token, err }