diff --git a/pkg/apply/applier.go b/pkg/apply/applier.go index 7803bcd..1921642 100644 --- a/pkg/apply/applier.go +++ b/pkg/apply/applier.go @@ -32,6 +32,7 @@ func NewApplier(factory util.Factory, ioStreams genericclioptions.IOStreams) *Ap return &Applier{ ApplyOptions: apply.NewApplyOptions(ioStreams), StatusOptions: NewStatusOptions(), + PruneOptions: prune.NewPruneOptions(), factory: factory, ioStreams: ioStreams, } @@ -51,6 +52,7 @@ type Applier struct { ApplyOptions *apply.ApplyOptions StatusOptions *StatusOptions + PruneOptions *prune.PruneOptions resolver resolver } @@ -63,9 +65,10 @@ func (a *Applier) Initialize(cmd *cobra.Command) error { if err != nil { return errors.WrapPrefix(err, "error setting up ApplyOptions", 1) } - // Default PostProcessor is configured in "Complete" function, - // so the prune must happen after "Complete". - a.ApplyOptions.PostProcessorFn = pruneExec(a.factory, a.ApplyOptions) + err = a.PruneOptions.Initialize(a.factory, a.ApplyOptions.Namespace) + if err != nil { + return errors.WrapPrefix(err, "error setting up PruntOptions", 1) + } resolver, err := a.newResolver(a.StatusOptions.period) if err != nil { @@ -126,6 +129,7 @@ func (a *Applier) Run(ctx context.Context) <-chan Event { // The adapter is used to intercept what is meant to be printing // in the ApplyOptions, and instead turn those into events. a.ApplyOptions.ToPrinter = adapter.toPrinterFunc() + a.PruneOptions.ToPrinter = adapter.toPrinterFunc() // This provides us with a slice of all the objects that will be // applied to the cluster. infos, _ := a.ApplyOptions.GetObjects() @@ -154,6 +158,20 @@ func (a *Applier) Run(ctx context.Context) <-chan Event { } } } + infos, _ = a.ApplyOptions.GetObjects() + err = a.PruneOptions.Prune(infos) + if err != nil { + // If we see an error here we just report it on the channel and then + // give up. Eventually we might be able to determine which errors + // are fatal and which might allow us to continue. + ch <- Event{ + EventType: ErrorEventType, + ErrorEvent: ErrorEvent{ + Err: errors.WrapPrefix(err, "error pruning resources", 1), + }, + } + return + } }() return ch } @@ -228,16 +246,3 @@ func prependGroupingObject(o *apply.ApplyOptions) func() error { return nil } } - -// Prune deletes previously applied objects that have been -// omitted in the current apply. The previously applied objects -// are reached through ConfigMap grouping objects. -func pruneExec(f util.Factory, o *apply.ApplyOptions) func() error { - return func() error { - po, err := prune.NewPruneOptions(f, o) - if err != nil { - return err - } - return po.Prune() - } -} diff --git a/pkg/apply/prune/prune.go b/pkg/apply/prune/prune.go index 33cf558..059d707 100644 --- a/pkg/apply/prune/prune.go +++ b/pkg/apply/prune/prune.go @@ -14,14 +14,12 @@ package prune import ( "fmt" "io" - "strings" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/printers" "k8s.io/cli-runtime/pkg/resource" "k8s.io/client-go/dynamic" - "k8s.io/kubectl/pkg/cmd/apply" "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/validation" ) @@ -45,7 +43,7 @@ type PruneOptions struct { pastGroupingObjects []*resource.Info retrievedGroupingObjects bool - toPrinter func(string) (printers.ResourcePrinter, error) + ToPrinter func(string) (printers.ResourcePrinter, error) out io.Writer validator validation.Schema @@ -57,42 +55,30 @@ type PruneOptions struct { // information to run the prune. Returns an error if an error occurs // gathering this information. // TODO: Add dry-run options. -func NewPruneOptions(f util.Factory, ao *apply.ApplyOptions) (*PruneOptions, error) { +func NewPruneOptions() *PruneOptions { po := &PruneOptions{} + return po +} + +func (po *PruneOptions) Initialize(factory util.Factory, namespace string) error { var err error // Fields copied from ApplyOptions. - po.namespace = ao.Namespace - po.toPrinter = ao.ToPrinter - po.out = ao.Out + po.namespace = namespace // Client/Builder fields from the Factory. - po.client, err = f.DynamicClient() + po.client, err = factory.DynamicClient() if err != nil { - return nil, err + return err } - po.builder = f.NewBuilder() - po.mapper, err = f.ToRESTMapper() + po.builder = factory.NewBuilder() + po.mapper, err = factory.ToRESTMapper() if err != nil { - return nil, err + return err } - po.validator, err = f.Validator(false) + po.validator, err = factory.Validator(false) if err != nil { - return nil, err + return err } - // Retrieve/store the grouping object for current apply. - currentObjects, err := ao.GetObjects() - if err != nil { - return nil, err - } - currentGroupingObject, found := FindGroupingObject(currentObjects) - if !found { - return nil, fmt.Errorf("current grouping object not found during prune") - } - po.currentGroupingObject = currentGroupingObject - // Initialize past grouping objects as empty. - po.pastGroupingObjects = []*resource.Info{} - po.retrievedGroupingObjects = false - - return po, nil + return nil } // getPreviousGroupingObjects returns the set of grouping objects @@ -216,7 +202,16 @@ func (po *PruneOptions) calcPruneSet(pastGroupingInfos []*resource.Info) (*Inven // (retrieved from previous grouping objects) but omitted in // the current apply. Prune also delete all previous grouping // objects. Returns an error if there was a problem. -func (po *PruneOptions) Prune() error { +func (po *PruneOptions) Prune(currentObjects []*resource.Info) error { + currentGroupingObject, found := FindGroupingObject(currentObjects) + if !found { + return fmt.Errorf("current grouping object not found during prune") + } + po.currentGroupingObject = currentGroupingObject + // Initialize past grouping objects as empty. + po.pastGroupingObjects = []*resource.Info{} + po.retrievedGroupingObjects = false + // Retrieve previous grouping objects, and calculate the // union of the previous applies as an inventory set. pastGroupingInfos, err := po.getPreviousGroupingObjects() @@ -233,11 +228,24 @@ func (po *PruneOptions) Prune() error { if err != nil { return err } - err = po.client.Resource(mapping.Resource).Namespace(inv.Namespace).Delete(inv.Name, &metav1.DeleteOptions{}) + // Fetching the resource here before deletion seems a bit unnecessary, but + // it allows us to work with the ResourcePrinter. + namespacedClient := po.client.Resource(mapping.Resource).Namespace(inv.Namespace) + obj, err := namespacedClient.Get(inv.Name, metav1.GetOptions{}) if err != nil { return err } - fmt.Fprintf(po.out, "%s/%s deleted\n", strings.ToLower(inv.GroupKind.Kind), inv.Name) + err = namespacedClient.Delete(inv.Name, &metav1.DeleteOptions{}) + if err != nil { + return err + } + printer, err := po.ToPrinter("deleted") + if err != nil { + return err + } + if err = printer.PrintObj(obj, po.out); err != nil { + return err + } } // Delete previous grouping objects. for _, pastGroupInfo := range pastGroupingInfos { @@ -247,7 +255,7 @@ func (po *PruneOptions) Prune() error { if err != nil { return err } - printer, err := po.toPrinter("deleted") + printer, err := po.ToPrinter("deleted") if err != nil { return err }