Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): Add --with-prune option for diff command #469

Merged
merged 11 commits into from
Jan 17, 2021
1 change: 1 addition & 0 deletions cmd/tk/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,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