Skip to content

Commit 5d821cc

Browse files
authored
feat(cli): Add --with-prune option for diff command (#469)
Allows to show what would be pruned directly from within `tk diff`
1 parent 971289f commit 5d821cc

File tree

8 files changed

+62
-10
lines changed

8 files changed

+62
-10
lines changed

cmd/tk/workflow.go

+1
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ func diffCmd() *cli.Command {
113113
var opts tanka.DiffOpts
114114
cmd.Flags().StringVar(&opts.Strategy, "diff-strategy", "", "force the diff-strategy to use. Automatically chosen if not set.")
115115
cmd.Flags().BoolVarP(&opts.Summarize, "summarize", "s", false, "print summary of the differences, not the actual contents")
116+
cmd.Flags().BoolVarP(&opts.WithPrune, "with-prune", "p", false, "include objects deleted from the configuration in the differences")
116117

117118
vars := workflowFlags(cmd.Flags())
118119
getJsonnetOpts := jsonnetFlags(cmd.Flags())

pkg/kubernetes/apply.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,13 @@ See https://tanka.dev/garbage-collection for more details.`)
9191
func (k *Kubernetes) uids(state manifest.List) (map[string]bool, error) {
9292
uids := make(map[string]bool)
9393

94-
live, err := k.ctl.GetByState(state)
95-
if err != nil {
94+
live, err := k.ctl.GetByState(state, client.GetByStateOpts{
95+
IgnoreNotFound: true,
96+
})
97+
if _, ok := err.(client.ErrorNothingReturned); ok {
98+
// return empty map of uids when kubectl returns nothing
99+
return uids, nil
100+
} else if err != nil {
96101
return nil, err
97102
}
98103

pkg/kubernetes/client/client.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ type Client interface {
99
// Get the specified object(s) from the cluster
1010
Get(namespace, kind, name string) (manifest.Manifest, error)
1111
GetByLabels(namespace, kind string, labels map[string]string) (manifest.List, error)
12-
GetByState(data manifest.List) (manifest.List, error)
12+
GetByState(data manifest.List, opts GetByStateOpts) (manifest.List, error)
1313

1414
// Apply the configuration to the cluster. `data` must contain a plaintext
1515
// format that is `kubectl-apply(1)` compatible
@@ -51,3 +51,12 @@ type ApplyOpts struct {
5151
// DeleteOpts allow to specify additional parameters for delete operations
5252
// Currently not different from ApplyOpts, but may be required in the future
5353
type DeleteOpts ApplyOpts
54+
55+
// GetByStateOpts allow to specify additional parameters for GetByState function
56+
// Currently there is just ignoreNotFound parameter which is only useful for
57+
// GetByState() so we only have GetByStateOpts instead of more generic GetOpts
58+
// for all get operations
59+
type GetByStateOpts struct {
60+
// ignoreNotFound allows to ignore errors caused by missing objects
61+
IgnoreNotFound bool
62+
}

pkg/kubernetes/client/errors.go

+7
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,10 @@ type ErrorNoCluster string
3434
func (e ErrorNoCluster) Error() string {
3535
return fmt.Sprintf("no cluster that matches the apiServer `%s` was found. Please check your $KUBECONFIG", string(e))
3636
}
37+
38+
// ErrorNothingReturned means that there was no output returned
39+
type ErrorNothingReturned struct{}
40+
41+
func (e ErrorNothingReturned) Error() string {
42+
return "kubectl returned no output"
43+
}

pkg/kubernetes/client/get.go

+15-4
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ func (k Kubectl) GetByLabels(namespace, kind string, labels map[string]string) (
3737

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

5152
type getOpts struct {
52-
allNamespaces bool
53-
stdin string
53+
allNamespaces bool
54+
ignoreNotFound bool
55+
stdin string
5456
}
5557

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

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

93+
// return error if nothing was returned
94+
// because parsing empty output as json would cause errors
95+
if sout.Len() == 0 {
96+
return nil, ErrorNothingReturned{}
97+
}
98+
8899
// parse result
89100
var m manifest.Manifest
90101
if err := json.Unmarshal(sout.Bytes(), &m); err != nil {

pkg/kubernetes/diff.go

+17-3
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,27 @@ Please upgrade kubectl to at least version 1.18.1.`)
4848
return nil, err
4949
}
5050

51-
// reports all resources as new
52-
staticDiff := StaticDiffer(true)
51+
// reports all resources as created
52+
staticDiffAllCreated := StaticDiffer(true)
53+
54+
// reports all resources as deleted
55+
staticDiffAllDeleted := StaticDiffer(false)
56+
57+
// include orphaned resources in the diff if it was requested by the user
58+
orphaned := manifest.List{}
59+
if opts.WithPrune {
60+
// find orphaned resources
61+
orphaned, err = k.Orphaned(state)
62+
if err != nil {
63+
return nil, err
64+
}
65+
}
5366

5467
// run the diff
5568
d, err := multiDiff{
5669
{differ: liveDiff, state: live},
57-
{differ: staticDiff, state: soon},
70+
{differ: staticDiffAllCreated, state: soon},
71+
{differ: staticDiffAllDeleted, state: orphaned},
5872
}.diff()
5973

6074
switch {

pkg/kubernetes/kubernetes.go

+2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ func (k *Kubernetes) Close() error {
6363
type DiffOpts struct {
6464
// Use `diffstat(1)` to create a histogram of the changes instead
6565
Summarize bool
66+
// Find orphaned resources and include them in the diff
67+
WithPrune bool
6668

6769
// Set the diff-strategy. If unset, the value set in the spec is used
6870
Strategy string

pkg/tanka/workflow.go

+3
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ type DiffOpts struct {
9393
Strategy string
9494
// Summarize prints a summary, instead of the actual diff
9595
Summarize bool
96+
// WithPrune includes objects to be deleted by prune command in the diff
97+
WithPrune bool
9698
}
9799

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

0 commit comments

Comments
 (0)