diff --git a/cmd/tk/workflow.go b/cmd/tk/workflow.go index 5eb3b9e10..799eca640 100644 --- a/cmd/tk/workflow.go +++ b/cmd/tk/workflow.go @@ -52,7 +52,7 @@ func applyCmd() *cobra.Command { log.Fatalln("Reconciling:", err) } - if !diff(desired, false) { + if !diff(desired, false, kubernetes.DiffOpts{}) { log.Println("Warning: There are no differences. Your apply may not do anything at all.") } @@ -79,7 +79,14 @@ func diffCmd() *cobra.Command { "flags/diff-strategy": "diffStrategy", }, } - vars := workflowFlags(cmd.Flags()) + + // flags + var ( + vars = workflowFlags(cmd.Flags()) + diffStrategy = cmd.Flags().String("diff-strategy", "", "force the diff-strategy to use. Automatically chosen if not set.") + summarize = cmd.Flags().BoolP("summarize", "s", false, "quick summary of the differences, hides file contents") + ) + cmd.Run = func(cmd *cobra.Command, args []string) { if kube == nil { log.Fatalln(kubernetes.ErrorMissingConfig{Verb: "diff"}) @@ -91,7 +98,7 @@ func diffCmd() *cobra.Command { } if kube.Spec.DiffStrategy == "" { - kube.Spec.DiffStrategy = cmd.Flag("diff-strategy").Value.String() + kube.Spec.DiffStrategy = *diffStrategy } desired, err := kube.Reconcile(raw, stringsToRegexps(vars.targets)...) @@ -99,21 +106,20 @@ func diffCmd() *cobra.Command { log.Fatalln("Reconciling:", err) } - if diff(desired, interactive) { + if diff(desired, interactive, kubernetes.DiffOpts{Summarize: *summarize}) { os.Exit(16) } log.Println("No differences.") } - cmd.Flags().String("diff-strategy", "", "force the diff-strategy to use. Automatically chosen if not set.") return cmd } // computes the diff, prints to screen. // set `pager` to false to disable the pager. // When non-interactive, no paging happens anyways. -func diff(state []kubernetes.Manifest, pager bool) (changed bool) { - changes, err := kube.Diff(state) +func diff(state []kubernetes.Manifest, pager bool, opts kubernetes.DiffOpts) (changed bool) { + changes, err := kube.Diff(state, opts) if err != nil { log.Fatalln("Diffing:", err) } diff --git a/pkg/kubernetes/kubernetes.go b/pkg/kubernetes/kubernetes.go index 25688f12d..99dab47a5 100644 --- a/pkg/kubernetes/kubernetes.go +++ b/pkg/kubernetes/kubernetes.go @@ -120,8 +120,13 @@ func (k *Kubernetes) Apply(state []Manifest, opts ApplyOpts) error { return k.client.Apply(yaml, k.Spec.Namespace, opts) } +// DiffOpts allow to specify additional parameters for diff operations +type DiffOpts struct { + Summarize bool +} + // Diff takes the desired state and returns the differences from the cluster -func (k *Kubernetes) Diff(state []Manifest) (*string, error) { +func (k *Kubernetes) Diff(state []Manifest, opts DiffOpts) (*string, error) { if k == nil { return nil, ErrorMissingConfig{"diff"} } @@ -139,7 +144,19 @@ func (k *Kubernetes) Diff(state []Manifest) (*string, error) { } } - return k.differs[k.Spec.DiffStrategy](yaml) + d, err := k.differs[k.Spec.DiffStrategy](yaml) + switch { + case err != nil: + return nil, err + case d == nil: + return nil, nil + } + + if opts.Summarize { + return diffstat(*d) + } + + return d, nil } func objectspec(m Manifest) string { diff --git a/pkg/kubernetes/util.go b/pkg/kubernetes/util.go index f5389f09c..a415c6eb8 100644 --- a/pkg/kubernetes/util.go +++ b/pkg/kubernetes/util.go @@ -8,8 +8,11 @@ import ( "os/exec" "path/filepath" "regexp" + "strings" ) +// diff computes the differences between the strings `is` and `should` using the +// UNIX `diff(1)` utility. func diff(name, is, should string) (string, error) { dir, err := ioutil.TempDir("", "diff") if err != nil { @@ -46,6 +49,22 @@ func diff(name, is, should string) (string, error) { return out, nil } +// diffstat uses `diffstat(1)` utility to summarize a `diff(1)` output +func diffstat(d string) (*string, error) { + cmd := exec.Command("diffstat", "-C") + buf := bytes.Buffer{} + cmd.Stdout = &buf + cmd.Stderr = os.Stderr + cmd.Stdin = strings.NewReader(d) + + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("invoking diffstat(1): %s", err.Error()) + } + + out := buf.String() + return &out, nil +} + // FilteredErr is a filtered Stderr. If one of the regular expressions match, the current input is discarded. type FilteredErr []*regexp.Regexp