cli-utils/pkg/apply/destroyer.go

169 lines
5.4 KiB
Go

// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package apply
import (
"fmt"
"github.com/go-errors/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"sigs.k8s.io/cli-utils/pkg/apply/event"
"sigs.k8s.io/cli-utils/pkg/apply/prune"
"sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
"sigs.k8s.io/cli-utils/pkg/common"
"sigs.k8s.io/cli-utils/pkg/inventory"
"sigs.k8s.io/cli-utils/pkg/provider"
)
// NewDestroyer returns a new destroyer. It will set up the ApplyOptions and
// PruneOptions which are responsible for capturing any command line flags.
// It currently requires IOStreams, but this is a legacy from when
// the ApplyOptions were responsible for printing progress. This is now
// handled by a separate printer with the KubectlPrinterAdapter bridging
// between the two.
func NewDestroyer(provider provider.Provider) *Destroyer {
return &Destroyer{
PruneOptions: prune.NewPruneOptions(),
provider: provider,
}
}
// Destroyer performs the step of grabbing all the previous inventory objects and
// prune them. This also deletes all the previous inventory objects
type Destroyer struct {
provider provider.Provider
PruneOptions *prune.PruneOptions
invClient inventory.InventoryClient
DryRunStrategy common.DryRunStrategy
}
type DestroyerOption struct {
InventoryPolicy inventory.InventoryPolicy
}
// Initialize sets up the Destroyer for actually doing an destroy against
// a cluster. This involves validating command line inputs and configuring
// clients for communicating with the cluster.
func (d *Destroyer) Initialize() error {
invClient, err := d.provider.InventoryClient()
if err != nil {
return errors.WrapPrefix(err, "error creating inventory client", 1)
}
d.invClient = invClient
err = d.PruneOptions.Initialize(d.provider.Factory(), invClient)
if err != nil {
return errors.WrapPrefix(err, "error setting up PruneOptions", 1)
}
d.PruneOptions.Destroy = true
return nil
}
// Run performs the destroy step. Passes the inventory object. This
// happens asynchronously on progress and any errors are reported
// back on the event channel.
func (d *Destroyer) Run(inv inventory.InventoryInfo, option *DestroyerOption) <-chan event.Event {
ch := make(chan event.Event)
go func() {
defer close(ch)
d.invClient.SetDryRunStrategy(d.DryRunStrategy)
// Start the event transformer goroutine so we can transform
// the Prune events emitted from the Prune function to Delete
// Events. That we use Prune to implement destroy is an
// implementation detail and the events should not be Prune events.
tempChannel, completedChannel := runPruneEventTransformer(ch)
taskContext := taskrunner.NewTaskContext(tempChannel)
err := d.PruneOptions.Prune(inv, nil, sets.NewString(), taskContext, prune.Options{
DryRunStrategy: d.DryRunStrategy,
PropagationPolicy: metav1.DeletePropagationBackground,
InventoryPolicy: option.InventoryPolicy,
})
if err != nil {
ch <- event.Event{
Type: event.ErrorType,
ErrorEvent: event.ErrorEvent{
Err: errors.WrapPrefix(err, "error pruning resources in cluster", 1),
},
}
return
}
// Now delete the inventory object as well.
err = d.invClient.DeleteInventoryObj(inv)
if err != nil {
ch <- event.Event{
Type: event.ErrorType,
ErrorEvent: event.ErrorEvent{
Err: errors.WrapPrefix(err, "error deleting inventory object", 1),
},
}
return
}
// Close the tempChannel to signal to the event transformer that
// it should terminate.
close(tempChannel)
// Wait for the event transformer to complete processing all
// events and shut down before we continue.
<-completedChannel
ch <- event.Event{
Type: event.ActionGroupType,
ActionGroupEvent: event.ActionGroupEvent{
GroupName: "destroyer",
Type: event.Finished,
Action: event.DeleteAction,
},
}
}()
return ch
}
// runPruneEventTransformer creates a channel for events and
// starts a goroutine that will read from the channel until it
// is closed. All events will be republished as Delete events
// on the provided eventChannel. The function will also return
// a channel that it will close once the goroutine is shutting
// down.
func runPruneEventTransformer(eventChannel chan event.Event) (chan event.Event, <-chan struct{}) {
completedChannel := make(chan struct{})
tempEventChannel := make(chan event.Event)
go func() {
defer close(completedChannel)
for msg := range tempEventChannel {
// If it is not a Prune event, no need to make any transformation.
if msg.Type != event.PruneType {
eventChannel <- msg
} else {
eventChannel <- event.Event{
Type: event.DeleteType,
DeleteEvent: event.DeleteEvent{
Operation: transformPruneOperation(msg.PruneEvent.Operation),
Object: msg.PruneEvent.Object,
Identifier: msg.PruneEvent.Identifier,
Error: msg.PruneEvent.Error,
},
}
}
}
}()
return tempEventChannel, completedChannel
}
func transformPruneOperation(pruneOp event.PruneEventOperation) event.DeleteEventOperation {
var deleteOp event.DeleteEventOperation
switch pruneOp {
case event.PruneSkipped:
deleteOp = event.DeleteSkipped
case event.Pruned:
deleteOp = event.Deleted
case event.PruneUnspecified:
deleteOp = event.DeleteUnspecified
default:
panic(fmt.Errorf("unknown prune operation %s", pruneOp.String()))
}
return deleteOp
}