Skip to content

Commit

Permalink
feat(cli): Add --with-prune option for diff command (grafana#469)
Browse files Browse the repository at this point in the history
Allows to show what would be pruned directly from within `tk diff`
  • Loading branch information
curusarn authored Jan 17, 2021
1 parent 971289f commit 5d821cc
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 10 deletions.
1 change: 1 addition & 0 deletions cmd/tk/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func diffCmd() *cli.Command {
var opts tanka.DiffOpts
cmd.Flags().StringVar(&opts.Strategy, "diff-strategy", "", "force the diff-strategy to use. Automatically chosen if not set.")
cmd.Flags().BoolVarP(&opts.Summarize, "summarize", "s", false, "print summary of the differences, not the actual contents")
cmd.Flags().BoolVarP(&opts.WithPrune, "with-prune", "p", false, "include objects deleted from the configuration in the differences")

vars := workflowFlags(cmd.Flags())
getJsonnetOpts := jsonnetFlags(cmd.Flags())
Expand Down
9 changes: 7 additions & 2 deletions pkg/kubernetes/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,13 @@ See https://tanka.dev/garbage-collection for more details.`)
func (k *Kubernetes) uids(state manifest.List) (map[string]bool, error) {
uids := make(map[string]bool)

live, err := k.ctl.GetByState(state)
if err != nil {
live, err := k.ctl.GetByState(state, client.GetByStateOpts{
IgnoreNotFound: true,
})
if _, ok := err.(client.ErrorNothingReturned); ok {
// return empty map of uids when kubectl returns nothing
return uids, nil
} else if err != nil {
return nil, err
}

Expand Down
11 changes: 10 additions & 1 deletion pkg/kubernetes/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type Client interface {
// Get the specified object(s) from the cluster
Get(namespace, kind, name string) (manifest.Manifest, error)
GetByLabels(namespace, kind string, labels map[string]string) (manifest.List, error)
GetByState(data manifest.List) (manifest.List, error)
GetByState(data manifest.List, opts GetByStateOpts) (manifest.List, error)

// Apply the configuration to the cluster. `data` must contain a plaintext
// format that is `kubectl-apply(1)` compatible
Expand Down Expand Up @@ -51,3 +51,12 @@ type ApplyOpts struct {
// DeleteOpts allow to specify additional parameters for delete operations
// Currently not different from ApplyOpts, but may be required in the future
type DeleteOpts ApplyOpts

// GetByStateOpts allow to specify additional parameters for GetByState function
// Currently there is just ignoreNotFound parameter which is only useful for
// GetByState() so we only have GetByStateOpts instead of more generic GetOpts
// for all get operations
type GetByStateOpts struct {
// ignoreNotFound allows to ignore errors caused by missing objects
IgnoreNotFound bool
}
7 changes: 7 additions & 0 deletions pkg/kubernetes/client/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,10 @@ type ErrorNoCluster string
func (e ErrorNoCluster) Error() string {
return fmt.Sprintf("no cluster that matches the apiServer `%s` was found. Please check your $KUBECONFIG", string(e))
}

// ErrorNothingReturned means that there was no output returned
type ErrorNothingReturned struct{}

func (e ErrorNothingReturned) Error() string {
return "kubectl returned no output"
}
19 changes: 15 additions & 4 deletions pkg/kubernetes/client/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ func (k Kubectl) GetByLabels(namespace, kind string, labels map[string]string) (

// GetByState returns the full object, including runtime fields for each
// resource in the state
func (k Kubectl) GetByState(data manifest.List) (manifest.List, error) {
func (k Kubectl) GetByState(data manifest.List, opts GetByStateOpts) (manifest.List, error) {
list, err := k.get("", "", []string{"-f", "-"}, getOpts{
stdin: data.String(),
ignoreNotFound: opts.IgnoreNotFound,
stdin: data.String(),
})
if err != nil {
return nil, err
Expand All @@ -49,15 +50,19 @@ func (k Kubectl) GetByState(data manifest.List) (manifest.List, error) {
}

type getOpts struct {
allNamespaces bool
stdin string
allNamespaces bool
ignoreNotFound bool
stdin string
}

func (k Kubectl) get(namespace, kind string, selector []string, opts getOpts) (manifest.Manifest, error) {
// build cli flags and args
argv := []string{
"-o", "json",
}
if opts.ignoreNotFound {
argv = append(argv, "--ignore-not-found")
}

if opts.allNamespaces {
argv = append(argv, "--all-namespaces")
Expand Down Expand Up @@ -85,6 +90,12 @@ func (k Kubectl) get(namespace, kind string, selector []string, opts getOpts) (m
return nil, parseGetErr(err, serr.String())
}

// return error if nothing was returned
// because parsing empty output as json would cause errors
if sout.Len() == 0 {
return nil, ErrorNothingReturned{}
}

// parse result
var m manifest.Manifest
if err := json.Unmarshal(sout.Bytes(), &m); err != nil {
Expand Down
20 changes: 17 additions & 3 deletions pkg/kubernetes/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,27 @@ Please upgrade kubectl to at least version 1.18.1.`)
return nil, err
}

// reports all resources as new
staticDiff := StaticDiffer(true)
// reports all resources as created
staticDiffAllCreated := StaticDiffer(true)

// reports all resources as deleted
staticDiffAllDeleted := StaticDiffer(false)

// include orphaned resources in the diff if it was requested by the user
orphaned := manifest.List{}
if opts.WithPrune {
// find orphaned resources
orphaned, err = k.Orphaned(state)
if err != nil {
return nil, err
}
}

// run the diff
d, err := multiDiff{
{differ: liveDiff, state: live},
{differ: staticDiff, state: soon},
{differ: staticDiffAllCreated, state: soon},
{differ: staticDiffAllDeleted, state: orphaned},
}.diff()

switch {
Expand Down
2 changes: 2 additions & 0 deletions pkg/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ func (k *Kubernetes) Close() error {
type DiffOpts struct {
// Use `diffstat(1)` to create a histogram of the changes instead
Summarize bool
// Find orphaned resources and include them in the diff
WithPrune bool

// Set the diff-strategy. If unset, the value set in the spec is used
Strategy string
Expand Down
3 changes: 3 additions & 0 deletions pkg/tanka/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ type DiffOpts struct {
Strategy string
// Summarize prints a summary, instead of the actual diff
Summarize bool
// WithPrune includes objects to be deleted by prune command in the diff
WithPrune bool
}

// Diff parses the environment at the given directory (a `baseDir`) and returns
Expand All @@ -115,6 +117,7 @@ func Diff(baseDir string, opts DiffOpts) (*string, error) {
return kube.Diff(l.Resources, kubernetes.DiffOpts{
Summarize: opts.Summarize,
Strategy: opts.Strategy,
WithPrune: opts.WithPrune,
})
}

Expand Down

0 comments on commit 5d821cc

Please sign in to comment.