Repository: GoogleContainerTools/kpt
Pull request #2642 (2022-01-11 05:38:49)

Added docs for using exec in fn render
This PR adds end-user documentation for running executable binaries as functions during `fn render`.


Patch:
diff --git a/go.mod b/go.mod
index 8fb16e1e..4dda367e 100644
--- a/go.mod
+++ b/go.mod
@@ -14,7 +14,7 @@ require (
 	github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca
 	golang.org/x/mod v0.5.1
 	gotest.tools v2.2.0+incompatible
-	k8s.io/api v0.22.3
+	k8s.io/api v0.22.3 // indirect
 	k8s.io/apiextensions-apiserver v0.22.2
 	k8s.io/apimachinery v0.22.3
 	k8s.io/cli-runtime v0.22.2
@@ -22,8 +22,7 @@ require (
 	k8s.io/klog/v2 v2.10.0
 	k8s.io/kube-openapi v0.0.0-20211109043139-026bd182f079 // indirect
 	k8s.io/kubectl v0.22.2
-	k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e
-	sigs.k8s.io/cli-utils v0.26.1-0.20220108032703-d7d63f4b6289
+	sigs.k8s.io/cli-utils v0.27.0
 	sigs.k8s.io/kustomize/api v0.8.11
 	sigs.k8s.io/kustomize/kyaml v0.13.1-0.20211203194734-cd2c6a1ad117
 )
diff --git a/go.sum b/go.sum
index 58ca02e0..c2f25062 100644
--- a/go.sum
+++ b/go.sum
@@ -1022,8 +1022,8 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
-sigs.k8s.io/cli-utils v0.26.1-0.20220108032703-d7d63f4b6289 h1:4kv1Ge3sdzNPT96uRa5zU+vNczErzkoQYanbQmRgn60=
-sigs.k8s.io/cli-utils v0.26.1-0.20220108032703-d7d63f4b6289/go.mod h1:8ll2fyx+bzjbwmwUnKBQU+2LDbMDsxy44DiDZ+drALg=
+sigs.k8s.io/cli-utils v0.27.0 h1:BxI7lPNn0fBZa5g4UwR+ShJyL4CCxELA6tLHbr2YrpU=
+sigs.k8s.io/cli-utils v0.27.0/go.mod h1:8ll2fyx+bzjbwmwUnKBQU+2LDbMDsxy44DiDZ+drALg=
 sigs.k8s.io/controller-runtime v0.10.1 h1:+eLHgY/VrJWnfg6iXUqhCUqNXgPH1NZeP9drNAAgWlg=
 sigs.k8s.io/controller-runtime v0.10.1/go.mod h1:CQp8eyUQZ/Q7PJvnIrB6/hgfTC1kBkGylwsLgOQi1WY=
 sigs.k8s.io/kustomize/api v0.8.11 h1:LzQzlq6Z023b+mBtc6v72N2mSHYmN8x7ssgbf/hv0H8=
diff --git a/internal/cmdapply/cmdapply.go b/internal/cmdapply/cmdapply.go
index 733469f1..c7611b0a 100644
--- a/internal/cmdapply/cmdapply.go
+++ b/internal/cmdapply/cmdapply.go
@@ -25,16 +25,16 @@ import (
 	"github.com/GoogleContainerTools/kpt/internal/util/argutil"
 	"github.com/GoogleContainerTools/kpt/internal/util/strings"
 	"github.com/GoogleContainerTools/kpt/pkg/live"
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/flagutils"
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/printers"
 	"github.com/spf13/cobra"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
 	"k8s.io/kubectl/pkg/cmd/util"
+	"sigs.k8s.io/cli-utils/cmd/flagutils"
 	"sigs.k8s.io/cli-utils/pkg/apply"
 	"sigs.k8s.io/cli-utils/pkg/common"
 	"sigs.k8s.io/cli-utils/pkg/inventory"
+	"sigs.k8s.io/cli-utils/pkg/printers"
 )
 
 // NewRunner returns a command runner
@@ -80,6 +80,8 @@ func NewRunner(ctx context.Context, factory util.Factory,
 		"If true, install the inventory ResourceGroup CRD before applying.")
 	c.Flags().BoolVar(&r.dryRun, "dry-run", false,
 		"dry-run apply for the resources in the package.")
+	c.Flags().BoolVar(&r.printStatusEvents, "status-events", false,
+		"Print status events (always enabled for table output)")
 	return r
 }
 
@@ -105,6 +107,7 @@ type Runner struct {
 	pruneTimeout                 time.Duration
 	inventoryPolicyString        string
 	dryRun                       bool
+	printStatusEvents            bool
 
 	inventoryPolicy inventory.InventoryPolicy
 	prunePropPolicy v1.DeletionPropagation
@@ -226,8 +229,17 @@ func runApply(r *Runner, invInfo inventory.InventoryInfo, objs []*unstructured.U
 		InventoryPolicy:        r.inventoryPolicy,
 	})
 
+	// Print the preview strategy unless the output format is json.
+	if dryRunStrategy.ClientOrServerDryRun() && r.output != printers.JSONPrinter {
+		if dryRunStrategy.ServerDryRun() {
+			fmt.Println("Dry-run strategy: server")
+		} else {
+			fmt.Println("Dry-run strategy: client")
+		}
+	}
+
 	// The printer will print updates from the channel. It will block
 	// until the channel is closed.
 	printer := printers.GetPrinter(r.output, r.ioStreams)
-	return printer.Print(ch, dryRunStrategy)
+	return printer.Print(ch, dryRunStrategy, r.printStatusEvents)
 }
diff --git a/internal/cmddestroy/cmddestroy.go b/internal/cmddestroy/cmddestroy.go
index f658860e..928fa082 100644
--- a/internal/cmddestroy/cmddestroy.go
+++ b/internal/cmddestroy/cmddestroy.go
@@ -23,14 +23,14 @@ import (
 	"github.com/GoogleContainerTools/kpt/internal/util/argutil"
 	"github.com/GoogleContainerTools/kpt/internal/util/strings"
 	"github.com/GoogleContainerTools/kpt/pkg/live"
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/flagutils"
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/printers"
 	"github.com/spf13/cobra"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
 	"k8s.io/kubectl/pkg/cmd/util"
+	"sigs.k8s.io/cli-utils/cmd/flagutils"
 	"sigs.k8s.io/cli-utils/pkg/apply"
 	"sigs.k8s.io/cli-utils/pkg/common"
 	"sigs.k8s.io/cli-utils/pkg/inventory"
+	"sigs.k8s.io/cli-utils/pkg/printers"
 )
 
 func NewRunner(ctx context.Context, factory util.Factory,
@@ -59,6 +59,8 @@ func NewRunner(ctx context.Context, factory util.Factory,
 			fmt.Sprintf("%q and %q.", flagutils.InventoryPolicyStrict, flagutils.InventoryPolicyAdopt))
 	c.Flags().BoolVar(&r.dryRun, "dry-run", false,
 		"dry-run apply for the resources in the package.")
+	c.Flags().BoolVar(&r.printStatusEvents, "status-events", false,
+		"Print status events (always enabled for table output)")
 	return r
 }
 
@@ -80,6 +82,7 @@ type Runner struct {
 	output                string
 	inventoryPolicyString string
 	dryRun                bool
+	printStatusEvents     bool
 
 	inventoryPolicy inventory.InventoryPolicy
 
@@ -168,8 +171,16 @@ func runDestroy(r *Runner, inv inventory.InventoryInfo, dryRunStrategy common.Dr
 	}
 	ch := destroyer.Run(context.Background(), inv, options)
 
+	// Print the preview strategy unless the output format is json.
+	if dryRunStrategy.ClientOrServerDryRun() && r.output != printers.JSONPrinter {
+		if dryRunStrategy.ServerDryRun() {
+			fmt.Println("Dry-run strategy: server")
+		} else {
+			fmt.Println("Dry-run strategy: client")
+		}
+	}
 	// The printer will print updates from the channel. It will block
 	// until the channel is closed.
 	printer := printers.GetPrinter(r.output, r.ioStreams)
-	return printer.Print(ch, dryRunStrategy)
+	return printer.Print(ch, dryRunStrategy, r.printStatusEvents)
 }
diff --git a/internal/docs/generated/fndocs/docs.go b/internal/docs/generated/fndocs/docs.go
index f37a533c..e8d677b9 100644
--- a/internal/docs/generated/fndocs/docs.go
+++ b/internal/docs/generated/fndocs/docs.go
@@ -239,6 +239,11 @@ Args:
 
 Flags:
 
+  --allow-exec:
+    Allow executable binaries to run as function. Note that executable binaries
+    can perform privileged operations on your system, so ensure that binaries
+    referred in the pipeline are trusted and safe to execute.
+  
   --image-pull-policy:
     If the image should be pulled before rendering the package(s). It can be set
     to one of always, ifNotPresent, never. If unspecified, always will be the
diff --git a/internal/errors/resolver/live.go b/internal/errors/resolver/live.go
index a608d6f4..d01c6dbd 100644
--- a/internal/errors/resolver/live.go
+++ b/internal/errors/resolver/live.go
@@ -21,6 +21,7 @@ import (
 	"github.com/GoogleContainerTools/kpt/pkg/live"
 	"sigs.k8s.io/cli-utils/pkg/inventory"
 	"sigs.k8s.io/cli-utils/pkg/manifestreader"
+	"sigs.k8s.io/cli-utils/pkg/print/common"
 )
 
 //nolint:gochecknoinits
@@ -161,5 +162,14 @@ func (*liveErrorResolver) Resolve(err error) (ResolvedResult, bool) {
 			}),
 		}, true
 	}
+
+	var resultError *common.ResultError
+	if errors.As(err, &resultError) {
+		return ResolvedResult{
+			Message:  resultError.Error(),
+			ExitCode: 3,
+		}, true
+	}
+
 	return ResolvedResult{}, false
 }
diff --git a/internal/fnruntime/runner.go b/internal/fnruntime/runner.go
index 8ec6f7f5..051a5330 100644
--- a/internal/fnruntime/runner.go
+++ b/internal/fnruntime/runner.go
@@ -165,6 +165,7 @@ func (fr *FunctionRunner) Filter(input []*yaml.RNode) (output []*yaml.RNode, err
 	if !fr.disableCLIOutput {
 		pr.Printf("[PASS] %q in %v\n", fr.name, time.Since(t0).Truncate(time.Millisecond*100))
 		printFnResult(fr.ctx, fr.fnResult, printer.NewOpt())
+		printFnStderr(fr.ctx, fr.fnResult.Stderr)
 	}
 	return output, err
 }
@@ -345,16 +346,22 @@ func printFnResult(ctx context.Context, fnResult *fnresult.Result, opt *printer.
 // on kpt CLI.
 func printFnExecErr(ctx context.Context, fnErr *ExecError) {
 	pr := printer.FromContextOrDie(ctx)
-	if len(fnErr.Stderr) > 0 {
+	printFnStderr(ctx, fnErr.Stderr)
+	pr.Printf("  Exit code: %d\n\n", fnErr.ExitCode)
+}
+
+// printFnStderr prints given stdErr in a user friendly format on kpt CLI.
+func printFnStderr(ctx context.Context, stdErr string) {
+	pr := printer.FromContextOrDie(ctx)
+	if len(stdErr) > 0 {
 		errLines := &multiLineFormatter{
 			Title:          "Stderr",
-			Lines:          strings.Split(fnErr.Stderr, "\n"),
+			Lines:          strings.Split(stdErr, "\n"),
 			UseQuote:       true,
 			TruncateOutput: printer.TruncateOutput,
 		}
 		pr.Printf("%s", errLines.String())
 	}
-	pr.Printf("  Exit code: %d\n\n", fnErr.ExitCode)
 }
 
 // path (location) of a KRM resources is tracked in a special key in
diff --git a/site/book/04-using-functions/01-declarative-function-execution.md b/site/book/04-using-functions/01-declarative-function-execution.md
index c2f01cbd..6bf9e39e 100644
--- a/site/book/04-using-functions/01-declarative-function-execution.md
+++ b/site/book/04-using-functions/01-declarative-function-execution.md
@@ -110,13 +110,48 @@ The end result is that:
 If any of the functions in the pipeline fails for whatever reason, then the
 entire pipeline is aborted and the local filesystem is left intact.
 
-## Specifying `image`
+## Specifying `function`
+
+### `image`
 
 The `image` field specifies the container image for the function. You can specify
 an image from any container registry. If the registry is omitted, the default
 container registry for functions catalog (`gcr.io/kpt-fn`) is prepended automatically.
 For example, `set-labels:v0.1` is automatically expanded to `gcr.io/kpt-fn/set-labels:v0.1`.
 
+### `exec`
+
+The `exec` field specifies the executable command for the function. You can specify
+an executable with arguments.
+
+Example below uses `sed` executable to replace all occurances of `foo` with `bar`
+in the package resources.
+
+```yaml
+# PKG_DIR/Kptfile (Excerpt)
+apiVersion: kpt.dev/v1
+kind: Kptfile
+metadata:
+  name: app
+pipeline:
+  mutators:
+    - exec: "sed -e 's/foo/bar/'"
+```
+
+Note that you must render the package by allowing executables by specifying `--allow-exec`
+command line flag as shown below.
+
+```shell
+$ kpt fn render [PKG_DIR] --allow-exec
+```
+
+Using `exec` is not recommended for two reasons:
+
+- It makes the package non-portable since rendering the package requires the
+  executables to be present on the system.
+- Executing binaries is not very secure since they can perform privileged operations
+  on the system.
+
 ## Specifying `functionConfig`
 
 In [Chapter 2], we saw this conceptual representation of a function invocation:
diff --git a/site/book/06-deploying-packages/03-handling-dependencies.md b/site/book/06-deploying-packages/03-handling-dependencies.md
index 0a58b5d6..609aa59d 100644
--- a/site/book/06-deploying-packages/03-handling-dependencies.md
+++ b/site/book/06-deploying-packages/03-handling-dependencies.md
@@ -83,4 +83,4 @@ service/wordpress-mysql deleted
 See [depends-on] for more information.
 
 [depends-on]:
-  /reference/annotations/depends-on
+  /reference/annotations/depends-on/
diff --git a/site/reference/cli/fn/render/README.md b/site/reference/cli/fn/render/README.md
index a3c59bf8..4f551f60 100644
--- a/site/reference/cli/fn/render/README.md
+++ b/site/reference/cli/fn/render/README.md
@@ -48,6 +48,11 @@ PKG_PATH:
 #### Flags
 
 ```
+--allow-exec:
+  Allow executable binaries to run as function. Note that executable binaries
+  can perform privileged operations on your system, so ensure that binaries
+  referred in the pipeline are trusted and safe to execute.
+
 --image-pull-policy:
   If the image should be pulled before rendering the package(s). It can be set
   to one of always, ifNotPresent, never. If unspecified, always will be the
diff --git a/thirdparty/cli-utils/flagutils/utils.go b/thirdparty/cli-utils/flagutils/utils.go
deleted file mode 100644
index c91f7d34..00000000
--- a/thirdparty/cli-utils/flagutils/utils.go
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2021 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package flagutils
-
-import (
-	"fmt"
-
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"sigs.k8s.io/cli-utils/pkg/inventory"
-)
-
-const (
-	InventoryPolicyFlag   = "inventory-policy"
-	InventoryPolicyStrict = "strict"
-	InventoryPolicyAdopt  = "adopt"
-)
-
-func ConvertInventoryPolicy(policy string) (inventory.InventoryPolicy, error) {
-	switch policy {
-	case InventoryPolicyStrict:
-		return inventory.InventoryPolicyMustMatch, nil
-	case InventoryPolicyAdopt:
-		return inventory.AdoptIfNoInventory, nil
-	default:
-		return inventory.InventoryPolicyMustMatch, fmt.Errorf(
-			"inventory policy must be one of strict, adopt")
-	}
-}
-
-// PathFromArgs returns the path which is a positional arg from args list
-// returns "-" if there is length of args is 0, which implies no path is provided
-func PathFromArgs(args []string) string {
-	if len(args) == 0 {
-		return "-"
-	}
-	return args[0]
-}
-
-// ConvertPropagationPolicy converts a propagationPolicy described as a
-// string to a DeletionPropagation type that is passed into the Applier.
-func ConvertPropagationPolicy(propagationPolicy string) (metav1.DeletionPropagation, error) {
-	switch propagationPolicy {
-	case string(metav1.DeletePropagationForeground):
-		return metav1.DeletePropagationForeground, nil
-	case string(metav1.DeletePropagationBackground):
-		return metav1.DeletePropagationBackground, nil
-	case string(metav1.DeletePropagationOrphan):
-		return metav1.DeletePropagationOrphan, nil
-	default:
-		return metav1.DeletePropagationBackground, fmt.Errorf(
-			"prune propagation policy must be one of Background, Foreground, Orphan")
-	}
-}
diff --git a/thirdparty/cli-utils/print/common/ansii.go b/thirdparty/cli-utils/print/common/ansii.go
deleted file mode 100644
index 1c64dc7a..00000000
--- a/thirdparty/cli-utils/print/common/ansii.go
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package common
-
-const (
-	// RESET is the escape sequence for unsetting any previous commands.
-	RESET = 0
-	// ESC is the escape sequence used to send ANSI commands in the terminal.
-	ESC = 27
-)
diff --git a/thirdparty/cli-utils/print/common/color.go b/thirdparty/cli-utils/print/common/color.go
deleted file mode 100644
index 4b3d4d7b..00000000
--- a/thirdparty/cli-utils/print/common/color.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package common
-
-import (
-	"fmt"
-
-	"sigs.k8s.io/cli-utils/pkg/kstatus/status"
-)
-
-// color is a type that captures the ANSI code for colors on the
-// terminal.
-type Color int
-
-var (
-	RED    Color = 31
-	GREEN  Color = 32
-	YELLOW Color = 33
-)
-
-// SprintfWithColor formats according to the provided pattern and returns
-// the result as a string with the necessary ansii escape codes for
-// color
-func SprintfWithColor(color Color, format string, a ...interface{}) string {
-	return fmt.Sprintf("%c[%dm", ESC, color) +
-		fmt.Sprintf(format, a...) +
-		fmt.Sprintf("%c[%dm", ESC, RESET)
-}
-
-// ColorForStatus returns the appropriate Color, which represents
-// the ansii escape code, for different status values.
-func ColorForStatus(s status.Status) (color Color, setColor bool) {
-	switch s {
-	case status.CurrentStatus:
-		color = GREEN
-		setColor = true
-	case status.InProgressStatus:
-		color = YELLOW
-		setColor = true
-	case status.FailedStatus:
-		color = RED
-		setColor = true
-	}
-	return
-}
diff --git a/thirdparty/cli-utils/print/list/base.go b/thirdparty/cli-utils/print/list/base.go
deleted file mode 100644
index 250dc135..00000000
--- a/thirdparty/cli-utils/print/list/base.go
+++ /dev/null
@@ -1,216 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package list
-
-import (
-	"fmt"
-
-	"sigs.k8s.io/cli-utils/pkg/apply/event"
-	"sigs.k8s.io/cli-utils/pkg/common"
-	"sigs.k8s.io/cli-utils/pkg/object"
-)
-
-type Formatter interface {
-	FormatApplyEvent(ae event.ApplyEvent) error
-	FormatStatusEvent(se event.StatusEvent) error
-	FormatPruneEvent(pe event.PruneEvent) error
-	FormatDeleteEvent(de event.DeleteEvent) error
-	FormatErrorEvent(ee event.ErrorEvent) error
-	FormatActionGroupEvent(age event.ActionGroupEvent, ags []event.ActionGroup, as *ApplyStats, ps *PruneStats, ds *DeleteStats, c Collector) error
-}
-
-type FormatterFactory func(previewStrategy common.DryRunStrategy) Formatter
-
-type BaseListPrinter struct {
-	FormatterFactory FormatterFactory
-}
-
-type ApplyStats struct {
-	ServersideApplied int
-	Created           int
-	Unchanged         int
-	Configured        int
-	Failed            int
-}
-
-func (a *ApplyStats) inc(op event.ApplyEventOperation) {
-	switch op {
-	case event.ApplyUnspecified:
-	case event.ServersideApplied:
-		a.ServersideApplied++
-	case event.Created:
-		a.Created++
-	case event.Unchanged:
-		a.Unchanged++
-	case event.Configured:
-		a.Configured++
-	default:
-		panic(fmt.Errorf("unknown apply operation %s", op.String()))
-	}
-}
-
-func (a *ApplyStats) incFailed() {
-	a.Failed++
-}
-
-func (a *ApplyStats) Sum() int {
-	return a.ServersideApplied + a.Configured + a.Unchanged + a.Created + a.Failed
-}
-
-type PruneStats struct {
-	Pruned  int
-	Skipped int
-	Failed  int
-}
-
-func (p *PruneStats) incPruned() {
-	p.Pruned++
-}
-
-func (p *PruneStats) incSkipped() {
-	p.Skipped++
-}
-
-func (p *PruneStats) incFailed() {
-	p.Failed++
-}
-
-type DeleteStats struct {
-	Deleted int
-	Skipped int
-	Failed  int
-}
-
-func (d *DeleteStats) incDeleted() {
-	d.Deleted++
-}
-
-func (d *DeleteStats) incSkipped() {
-	d.Skipped++
-}
-
-func (d *DeleteStats) incFailed() {
-	d.Failed++
-}
-
-type Collector interface {
-	LatestStatus() map[object.ObjMetadata]event.StatusEvent
-}
-
-type StatusCollector struct {
-	latestStatus map[object.ObjMetadata]event.StatusEvent
-}
-
-func (sc *StatusCollector) updateStatus(id object.ObjMetadata, se event.StatusEvent) {
-	sc.latestStatus[id] = se
-}
-
-func (sc *StatusCollector) LatestStatus() map[object.ObjMetadata]event.StatusEvent {
-	return sc.latestStatus
-}
-
-// Print outputs the events from the provided channel in a simple
-// format on StdOut. As we support other printer implementations
-// this should probably be an interface.
-// This function will block until the channel is closed.
-//nolint:gocyclo
-func (b *BaseListPrinter) Print(ch <-chan event.Event, previewStrategy common.DryRunStrategy) error {
-	var actionGroups []event.ActionGroup
-	applyStats := &ApplyStats{}
-	pruneStats := &PruneStats{}
-	deleteStats := &DeleteStats{}
-	statusCollector := &StatusCollector{
-		latestStatus: make(map[object.ObjMetadata]event.StatusEvent),
-	}
-	printStatus := false
-	formatter := b.FormatterFactory(previewStrategy)
-	for e := range ch {
-		switch e.Type {
-		case event.InitType:
-			actionGroups = e.InitEvent.ActionGroups
-		case event.ErrorType:
-			_ = formatter.FormatErrorEvent(e.ErrorEvent)
-			return e.ErrorEvent.Err
-		case event.ApplyType:
-			applyStats.inc(e.ApplyEvent.Operation)
-			if e.ApplyEvent.Error != nil {
-				applyStats.incFailed()
-			}
-			if err := formatter.FormatApplyEvent(e.ApplyEvent); err != nil {
-				return err
-			}
-		case event.StatusType:
-			statusCollector.updateStatus(e.StatusEvent.Identifier, e.StatusEvent)
-			if printStatus {
-				if err := formatter.FormatStatusEvent(e.StatusEvent); err != nil {
-					return err
-				}
-			}
-		case event.PruneType:
-			switch e.PruneEvent.Operation {
-			case event.Pruned:
-				pruneStats.incPruned()
-			case event.PruneSkipped:
-				pruneStats.incSkipped()
-			}
-			if e.PruneEvent.Error != nil {
-				pruneStats.incFailed()
-			}
-			if err := formatter.FormatPruneEvent(e.PruneEvent); err != nil {
-				return err
-			}
-		case event.DeleteType:
-			switch e.DeleteEvent.Operation {
-			case event.Deleted:
-				deleteStats.incDeleted()
-			case event.DeleteSkipped:
-				deleteStats.incSkipped()
-			}
-			if e.DeleteEvent.Error != nil {
-				deleteStats.incFailed()
-			}
-			if err := formatter.FormatDeleteEvent(e.DeleteEvent); err != nil {
-				return err
-			}
-		case event.ActionGroupType:
-			if err := formatter.FormatActionGroupEvent(e.ActionGroupEvent, actionGroups, applyStats,
-				pruneStats, deleteStats, statusCollector); err != nil {
-				return err
-			}
-
-			switch e.ActionGroupEvent.Action {
-			case event.ApplyAction:
-				if e.ActionGroupEvent.Type == event.Started {
-					applyStats = &ApplyStats{}
-				}
-			case event.PruneAction:
-				if e.ActionGroupEvent.Type == event.Started {
-					pruneStats = &PruneStats{}
-				}
-			case event.DeleteAction:
-				if e.ActionGroupEvent.Type == event.Started {
-					deleteStats = &DeleteStats{}
-				}
-			case event.WaitAction:
-				if e.ActionGroupEvent.Type == event.Started {
-					printStatus = true
-				}
-			}
-		}
-	}
-	failedSum := applyStats.Failed + pruneStats.Failed + deleteStats.Failed
-	if failedSum > 0 {
-		return fmt.Errorf("%d resources failed", failedSum)
-	}
-	return nil
-}
-
-func ActionGroupByName(name string, ags []event.ActionGroup) (event.ActionGroup, bool) {
-	for _, ag := range ags {
-		if ag.Name == name {
-			return ag, true
-		}
-	}
-	return event.ActionGroup{}, false
-}
diff --git a/thirdparty/cli-utils/print/table/base.go b/thirdparty/cli-utils/print/table/base.go
deleted file mode 100644
index 4ffa28b1..00000000
--- a/thirdparty/cli-utils/print/table/base.go
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package table
-
-import (
-	"fmt"
-	"io"
-	"strings"
-	"unicode/utf8"
-
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/print/common"
-	"k8s.io/cli-runtime/pkg/genericclioptions"
-	pe "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
-	"sigs.k8s.io/cli-utils/pkg/object"
-)
-
-// ColumnDefinition defines the columns that should be printed.
-type ColumnDefinition interface {
-	Name() string
-	Header() string
-	Width() int
-	PrintResource(w io.Writer, width int, r Resource) (int, error)
-}
-
-// ResourceStates defines the interface that must be implemented
-// by the object that provides information about the resources
-// that should be printed.
-type ResourceStates interface {
-	Resources() []Resource
-	Error() error
-}
-
-// Resource defines the interface that each of the Resource
-// objects must implement.
-type Resource interface {
-	Identifier() object.ObjMetadata
-	ResourceStatus() *pe.ResourceStatus
-	SubResources() []Resource
-}
-
-// BaseTablePrinter provides functionality for printing information
-// about a set of resources into a table format.
-// The printer will print to the Out stream defined in IOStreams,
-// and will print into the format defined by the Column definitions.
-type BaseTablePrinter struct {
-	IOStreams genericclioptions.IOStreams
-	Columns   []ColumnDefinition
-}
-
-// PrintTable prints the resources defined in ResourceStates. It will
-// print subresources if they exist.
-// moveUpCount defines how many lines the printer should move up
-// before starting printing. The return value is how many lines
-// were printed.
-func (t *BaseTablePrinter) PrintTable(rs ResourceStates,
-	moveUpCount int) int {
-	for i := 0; i < moveUpCount; i++ {
-		t.moveUp()
-		t.eraseCurrentLine()
-	}
-
-	linePrintCount := 0
-	for i, column := range t.Columns {
-		format := fmt.Sprintf("%%-%ds", column.Width())
-		t.printOrDie(format, column.Header())
-		if i == len(t.Columns)-1 {
-			t.printOrDie("\n")
-			linePrintCount++
-		} else {
-			t.printOrDie("  ")
-		}
-	}
-
-	for _, resource := range rs.Resources() {
-		for i, column := range t.Columns {
-			written, err := column.PrintResource(t.IOStreams.Out, column.Width(), resource)
-			if err != nil {
-				panic(err)
-			}
-			remainingSpace := column.Width() - written
-			t.printOrDie(strings.Repeat(" ", remainingSpace))
-			if i == len(t.Columns)-1 {
-				t.printOrDie("\n")
-				linePrintCount++
-			} else {
-				t.printOrDie("  ")
-			}
-		}
-
-		linePrintCount += t.printSubTable(resource.SubResources(), "")
-	}
-
-	return linePrintCount
-}
-
-// printSubTable prints out any subresources that belong to the
-// top-level resources. This function takes care of printing the correct tree
-// structure and indentation.
-func (t *BaseTablePrinter) printSubTable(resources []Resource,
-	prefix string) int {
-	linePrintCount := 0
-	for j, resource := range resources {
-		for i, column := range t.Columns {
-			availableWidth := column.Width()
-			if column.Name() == "resource" {
-				if j < len(resources)-1 {
-					t.printOrDie(prefix + `├─ `)
-				} else {
-					t.printOrDie(prefix + `└─ `)
-				}
-				availableWidth -= utf8.RuneCountInString(prefix) + 3
-			}
-			written, err := column.PrintResource(t.IOStreams.Out,
-				availableWidth, resource)
-			if err != nil {
-				panic(err)
-			}
-			remainingSpace := availableWidth - written
-			t.printOrDie(strings.Repeat(" ", remainingSpace))
-			if i == len(t.Columns)-1 {
-				t.printOrDie("\n")
-				linePrintCount++
-			} else {
-				t.printOrDie("  ")
-			}
-		}
-
-		var prefix string
-		if j < len(resources)-1 {
-			prefix = `│  `
-		} else {
-			prefix = "   "
-		}
-		linePrintCount += t.printSubTable(resource.SubResources(), prefix)
-	}
-	return linePrintCount
-}
-
-func (t *BaseTablePrinter) printOrDie(format string, a ...interface{}) {
-	_, err := fmt.Fprintf(t.IOStreams.Out, format, a...)
-	if err != nil {
-		panic(err)
-	}
-}
-
-func (t *BaseTablePrinter) moveUp() {
-	t.printOrDie("%c[%dA", common.ESC, 1)
-}
-
-func (t *BaseTablePrinter) eraseCurrentLine() {
-	t.printOrDie("%c[2K\r", common.ESC)
-}
diff --git a/thirdparty/cli-utils/print/table/columndefs.go b/thirdparty/cli-utils/print/table/columndefs.go
deleted file mode 100644
index 90ff6f7e..00000000
--- a/thirdparty/cli-utils/print/table/columndefs.go
+++ /dev/null
@@ -1,243 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package table
-
-import (
-	"fmt"
-	"io"
-	"time"
-
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/print/common"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/utils/integer"
-)
-
-// ColumnDef is an implementation of the ColumnDefinition interface.
-// It can be used to define simple columns that doesn't need additional
-// knowledge about the actual type of the provided Resource besides the
-// information available through the interface.
-type ColumnDef struct {
-	ColumnName        string
-	ColumnHeader      string
-	ColumnWidth       int
-	PrintResourceFunc func(w io.Writer, width int, r Resource) (int, error)
-}
-
-// Name returns the name of the column.
-func (c ColumnDef) Name() string {
-	return c.ColumnName
-}
-
-// Header returns the header that should be printed for
-// the column.
-func (c ColumnDef) Header() string {
-	return c.ColumnHeader
-}
-
-// Width returns the width of the column.
-func (c ColumnDef) Width() int {
-	return c.ColumnWidth
-}
-
-// PrintResource is called by the BaseTablePrinter to output the
-// content of a particular column. This implementation just delegates
-// to the provided PrintResourceFunc.
-func (c ColumnDef) PrintResource(w io.Writer, width int, r Resource) (int, error) {
-	return c.PrintResourceFunc(w, width, r)
-}
-
-// MustColumn returns the pre-defined column definition with the
-// provided name. If the name doesn't exist, it will panic.
-func MustColumn(name string) ColumnDef {
-	c, found := columnDefinitions[name]
-	if !found {
-		panic(fmt.Errorf("unknown column name %q", name))
-	}
-	return c
-}
-
-var (
-	columnDefinitions = map[string]ColumnDef{
-		// namespace defines a column that output the namespace of the
-		// resource, or nothing in the case of clusterscoped resources.
-		"namespace": {
-			ColumnName:   "namespace",
-			ColumnHeader: "NAMESPACE",
-			ColumnWidth:  10,
-			PrintResourceFunc: func(w io.Writer, width int, r Resource) (int,
-				error) {
-				namespace := r.Identifier().Namespace
-				if len(namespace) > width {
-					namespace = namespace[:width]
-				}
-				_, err := fmt.Fprint(w, namespace)
-				return len(namespace), err
-			},
-		},
-		// resource defines a column that outputs the kind and name of a
-		// resource.
-		"resource": {
-			ColumnName:   "resource",
-			ColumnHeader: "RESOURCE",
-			ColumnWidth:  40,
-			PrintResourceFunc: func(w io.Writer, width int, r Resource) (int,
-				error) {
-				text := fmt.Sprintf("%s/%s", r.Identifier().GroupKind.Kind,
-					r.Identifier().Name)
-				if len(text) > width {
-					text = text[:width]
-				}
-				_, err := fmt.Fprint(w, text)
-				return len(text), err
-			},
-		},
-		// status defines a column that outputs the status of a resource. It
-		// will use ansii escape codes to color the output.
-		"status": {
-			ColumnName:   "status",
-			ColumnHeader: "STATUS",
-			ColumnWidth:  10,
-			PrintResourceFunc: func(w io.Writer, width int, r Resource) (int,
-				error) {
-				rs := r.ResourceStatus()
-				if rs == nil {
-					return 0, nil
-				}
-				s := rs.Status.String()
-				if len(s) > width {
-					s = s[:width]
-				}
-				color, setColor := common.ColorForStatus(rs.Status)
-				var outputStatus string
-				if setColor {
-					outputStatus = common.SprintfWithColor(color, s)
-				} else {
-					outputStatus = s
-				}
-				_, err := fmt.Fprint(w, outputStatus)
-				return len(s), err
-			},
-		},
-		// conditions defines a column that outputs the conditions for
-		// a resource. The output will be in colors.
-		"conditions": {
-			ColumnName:   "conditions",
-			ColumnHeader: "CONDITIONS",
-			ColumnWidth:  40,
-			PrintResourceFunc: func(w io.Writer, width int, r Resource) (int,
-				error) {
-				rs := r.ResourceStatus()
-				if rs == nil {
-					return 0, nil
-				}
-				u := rs.Resource
-				if u == nil {
-					return fmt.Fprintf(w, "-")
-				}
-
-				conditions, found, err := unstructured.NestedSlice(u.Object,
-					"status", "conditions")
-				if !found || err != nil || len(conditions) == 0 {
-					return fmt.Fprintf(w, "<None>")
-				}
-
-				realLength := 0
-				for i, cond := range conditions {
-					condition := cond.(map[string]interface{})
-					conditionType := condition["type"].(string)
-					conditionStatus := condition["status"].(string)
-					var color common.Color
-					switch conditionStatus {
-					case "True":
-						color = common.GREEN
-					case "False":
-						color = common.RED
-					default:
-						color = common.YELLOW
-					}
-					remainingWidth := width - realLength
-					if len(conditionType) > remainingWidth {
-						conditionType = conditionType[:remainingWidth]
-					}
-					_, err := fmt.Fprint(w, common.SprintfWithColor(color, conditionType))
-					if err != nil {
-						return realLength, err
-					}
-					realLength += len(conditionType)
-					if i < len(conditions)-1 && width-realLength > 2 {
-						_, err = fmt.Fprintf(w, ",")
-						if err != nil {
-							return realLength, err
-						}
-						realLength += 1
-					}
-				}
-				return realLength, nil
-			},
-		},
-		// age defines a column that outputs the age of a resource computed
-		// by looking at the creationTimestamp field.
-		"age": {
-			ColumnName:   "age",
-			ColumnHeader: "AGE",
-			ColumnWidth:  6,
-			PrintResourceFunc: func(w io.Writer, width int, r Resource) (i int, err error) {
-				rs := r.ResourceStatus()
-				if rs == nil {
-					return 0, nil
-				}
-				u := rs.Resource
-				if u == nil {
-					return fmt.Fprint(w, "-")
-				}
-
-				timestamp, found, err := unstructured.NestedString(u.Object,
-					"metadata", "creationTimestamp")
-				if !found || err != nil || timestamp == "" {
-					return fmt.Fprint(w, "-")
-				}
-				parsedTime, err := time.Parse(time.RFC3339, timestamp)
-				if err != nil {
-					return fmt.Fprint(w, "-")
-				}
-				age := time.Since(parsedTime)
-				switch {
-				case age.Seconds() <= 90:
-					return fmt.Fprintf(w, "%ds",
-						integer.RoundToInt32(age.Round(time.Second).Seconds()))
-				case age.Minutes() <= 90:
-					return fmt.Fprintf(w, "%dm",
-						integer.RoundToInt32(age.Round(time.Minute).Minutes()))
-				default:
-					return fmt.Fprintf(w, "%dh",
-						integer.RoundToInt32(age.Round(time.Hour).Hours()))
-				}
-			},
-		},
-		// message defines a column that outputs the message from a
-		// ResourceStatus, or if there is a non-nil error, output the text
-		// from the error instead.
-		"message": {
-			ColumnName:   "message",
-			ColumnHeader: "MESSAGE",
-			ColumnWidth:  40,
-			PrintResourceFunc: func(w io.Writer, width int, r Resource) (i int, err error) {
-				rs := r.ResourceStatus()
-				if rs == nil {
-					return 0, nil
-				}
-				var message string
-				if rs.Error != nil {
-					message = rs.Error.Error()
-				} else {
-					message = rs.Message
-				}
-				if len(message) > width {
-					message = message[:width]
-				}
-				return fmt.Fprint(w, message)
-			},
-		},
-	}
-)
diff --git a/thirdparty/cli-utils/printers/events/formatter.go b/thirdparty/cli-utils/printers/events/formatter.go
deleted file mode 100644
index 4e3d707a..00000000
--- a/thirdparty/cli-utils/printers/events/formatter.go
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package events
-
-import (
-	"fmt"
-	"io"
-	"strings"
-
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/print/list"
-	"k8s.io/apimachinery/pkg/runtime/schema"
-	"k8s.io/cli-runtime/pkg/genericclioptions"
-	"sigs.k8s.io/cli-utils/pkg/apply/event"
-	"sigs.k8s.io/cli-utils/pkg/common"
-	"sigs.k8s.io/cli-utils/pkg/object"
-)
-
-func NewFormatter(ioStreams genericclioptions.IOStreams,
-	dryRunStrategy common.DryRunStrategy) list.Formatter {
-	return &formatter{
-		print: getPrintFunc(ioStreams.Out, dryRunStrategy),
-	}
-}
-
-type formatter struct {
-	print printFunc
-}
-
-func (ef *formatter) FormatApplyEvent(ae event.ApplyEvent) error {
-	gk := ae.Identifier.GroupKind
-	name := ae.Identifier.Name
-	if ae.Error != nil {
-		ef.print("%s apply failed: %s", resourceIDToString(gk, name),
-			ae.Error.Error())
-	} else {
-		ef.print("%s %s", resourceIDToString(gk, name),
-			strings.ToLower(ae.Operation.String()))
-	}
-	return nil
-}
-
-func (ef *formatter) FormatStatusEvent(se event.StatusEvent) error {
-	id := se.Identifier
-	ef.printResourceStatus(id, se)
-	return nil
-}
-
-func (ef *formatter) FormatPruneEvent(pe event.PruneEvent) error {
-	gk := pe.Identifier.GroupKind
-	if pe.Error != nil {
-		ef.print("%s prune failed: %s", resourceIDToString(gk, pe.Identifier.Name),
-			pe.Error.Error())
-		return nil
-	}
-
-	switch pe.Operation {
-	case event.Pruned:
-		ef.print("%s pruned", resourceIDToString(gk, pe.Identifier.Name))
-	case event.PruneSkipped:
-		ef.print("%s prune skipped", resourceIDToString(gk, pe.Identifier.Name))
-	}
-	return nil
-}
-
-func (ef *formatter) FormatDeleteEvent(de event.DeleteEvent) error {
-	gk := de.Identifier.GroupKind
-	name := de.Identifier.Name
-
-	if de.Error != nil {
-		ef.print("%s deletion failed: %s", resourceIDToString(gk, name),
-			de.Error.Error())
-		return nil
-	}
-
-	switch de.Operation {
-	case event.Deleted:
-		ef.print("%s deleted", resourceIDToString(gk, name))
-	case event.DeleteSkipped:
-		ef.print("%s delete skipped", resourceIDToString(gk, name))
-	}
-	return nil
-}
-
-func (ef *formatter) FormatErrorEvent(_ event.ErrorEvent) error {
-	return nil
-}
-
-func (ef *formatter) FormatActionGroupEvent(age event.ActionGroupEvent, ags []event.ActionGroup,
-	as *list.ApplyStats, ps *list.PruneStats, ds *list.DeleteStats, c list.Collector) error {
-	if age.Action == event.ApplyAction && age.Type == event.Finished {
-		output := fmt.Sprintf("%d resource(s) applied. %d created, %d unchanged, %d configured, %d failed",
-			as.Sum(), as.Created, as.Unchanged, as.Configured, as.Failed)
-		// Only print information about serverside apply if some of the
-		// resources actually were applied serverside.
-		if as.ServersideApplied > 0 {
-			output += fmt.Sprintf(", %d serverside applied", as.ServersideApplied)
-		}
-		ef.print(output)
-	}
-
-	if age.Action == event.PruneAction && age.Type == event.Finished {
-		ef.print("%d resource(s) pruned, %d skipped, %d failed", ps.Pruned, ps.Skipped, ps.Failed)
-	}
-
-	if age.Action == event.DeleteAction && age.Type == event.Finished {
-		ef.print("%d resource(s) deleted, %d skipped", ds.Deleted, ds.Skipped)
-	}
-
-	if age.Action == event.WaitAction && age.Type == event.Started {
-		ag, found := list.ActionGroupByName(age.GroupName, ags)
-		if !found {
-			panic(fmt.Errorf("unknown action group name %q", age.GroupName))
-		}
-		for id, se := range c.LatestStatus() {
-			// Only print information about objects that we actually care about
-			// for this wait task.
-			if found := ag.Identifiers.Contains(id); found {
-				ef.printResourceStatus(id, se)
-			}
-		}
-	}
-	return nil
-}
-
-func (ef *formatter) printResourceStatus(id object.ObjMetadata, se event.StatusEvent) {
-	ef.print("%s is %s: %s", resourceIDToString(id.GroupKind, id.Name),
-		se.PollResourceInfo.Status.String(), se.PollResourceInfo.Message)
-}
-
-// resourceIDToString returns the string representation of a GroupKind and a resource name.
-func resourceIDToString(gk schema.GroupKind, name string) string {
-	return fmt.Sprintf("%s/%s", strings.ToLower(gk.String()), name)
-}
-
-type printFunc func(format string, a ...interface{})
-
-func getPrintFunc(w io.Writer, dryRunStrategy common.DryRunStrategy) printFunc {
-	return func(format string, a ...interface{}) {
-		if dryRunStrategy.ClientDryRun() {
-			format += " (dry-run)"
-		} else if dryRunStrategy.ServerDryRun() {
-			format += " (dry-run-server)"
-		}
-		fmt.Fprintf(w, format+"\n", a...)
-	}
-}
diff --git a/thirdparty/cli-utils/printers/events/printer.go b/thirdparty/cli-utils/printers/events/printer.go
deleted file mode 100644
index f030ac0d..00000000
--- a/thirdparty/cli-utils/printers/events/printer.go
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package events
-
-import (
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/print/list"
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/printers/printer"
-	"k8s.io/cli-runtime/pkg/genericclioptions"
-	"sigs.k8s.io/cli-utils/pkg/common"
-)
-
-func NewPrinter(ioStreams genericclioptions.IOStreams) printer.Printer {
-	return &list.BaseListPrinter{
-		FormatterFactory: func(previewStrategy common.DryRunStrategy) list.Formatter {
-			return NewFormatter(ioStreams, previewStrategy)
-		},
-	}
-}
diff --git a/thirdparty/cli-utils/printers/json/doc.go b/thirdparty/cli-utils/printers/json/doc.go
deleted file mode 100644
index ba1cb879..00000000
--- a/thirdparty/cli-utils/printers/json/doc.go
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-// Package json provides a printer that outputs the eventstream in json
-// format. Each event is printed as a json object, so the output will
-// appear as a stream of json objects, each representing a single event.
-//
-// Every event will contain the following properties:
-//  * timestamp: RFC3339-formatted timestamp describing when the event happened.
-//  * type: Describes the type of the operation which the event is related to. Values
-//    can be apply, status, prune, delete, or error.
-//  * eventType: Describes the type of the event. The set of possible values depends on the
-//    the value of the type field.
-//
-// All the different types have a similar structure, with the exception of the
-// error type. There is one event type pertaining to a specific resource, it being that
-// it is applied, pruned or its status was updated, and there is a separate event type
-// when all resources have been applied, deleted and so on. For any event that
-// pertains to a particular resource, the fields group, kind, name and namespace
-// will always be present.
-//
-// Events of type apply can have two different values for eventType, each which comes
-// with a specific set of fields:
-//  * resourceApplied: A resource has been applied to the cluster.
-//    * fields identifying the resource.
-//    * operation: The operation that was performed on the resource. Must be one of
-//      created, configured, unchanged and serversideApplied.
-//  * completed: All resources have been applied.
-//    * count: Total number of resources applied
-//    * createdCount: Number of resources created.
-//    * configuredCount: Number of resources configured.
-//    * unchangedCount: Number of resources unchanged.
-//    * serversideAppliedCount: Number of resources applied serverside.
-//
-// Events of type status is a notification when either the status of resource
-// has changed, or when a set of resources has reached their desired status. Events
-// of type status can have three different values for eventType:
-//  * resourceStatus: The status has changed for a resource.
-//    * fields identifying the resource.
-//    * status: The new status for the resource.
-//    * message: Text that provides more information about the resource status.
-//  * completed: All resources have reached the desired status.
-//  * error: An error occurred when trying to get the status for a resource.
-//    * fields identifying the resource.
-//    * error: The error message.
-//
-// Events of type prune can have two different values for eventType, each which comes
-// with a specific set of fields:
-//  * resourcePruned: A resource has been pruned or was intended to be pruned but has been
-//    skipped due to the presence of a lifecycle directive.
-//    * fields identifying the resource.
-//    * operation: The operation that was performed on the resource. Must be one
-//      of pruned or skipped.
-//  * completed: All resources have been pruned or skipped.
-//    * count: Total number of resources pruned or skipped.
-//    * prunedCount: Number of resources pruned.
-//    * skippedCount: Number of resources skipped.
-//
-// Events of type delete can have two different values for eventType, each which comes
-// with a specific set of fields:
-//  * resourceDeleted: A resource has been deleted or was intended to be deleted but has been
-//    skipped due to the presence of a lifecycle directive.
-//    * fields identifying the resource.
-//    * operation: The operation that was performed on the resource. Must be one
-//      of deleted or skipped.
-//  * completed: All resources have been deleted or skipped.
-//    * count: Total number of resources deleted or skipped.
-//    * deletedCount: Number of resources deleted.
-//    * skippedCount: Number of resources skipped.
-//
-// Events of type error means there is an unrecoverable error and further
-// processing will stop. Only a single value for eventType is possible:
-//  * error: A fatal error has happened.
-//    * error: The error message.
-package json
diff --git a/thirdparty/cli-utils/printers/json/formatter.go b/thirdparty/cli-utils/printers/json/formatter.go
deleted file mode 100644
index 5fd718b0..00000000
--- a/thirdparty/cli-utils/printers/json/formatter.go
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package json
-
-import (
-	"encoding/json"
-	"fmt"
-	"time"
-
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/print/list"
-	"k8s.io/cli-runtime/pkg/genericclioptions"
-	"sigs.k8s.io/cli-utils/pkg/apply/event"
-	"sigs.k8s.io/cli-utils/pkg/common"
-	"sigs.k8s.io/cli-utils/pkg/object"
-)
-
-func NewFormatter(ioStreams genericclioptions.IOStreams,
-	dryRunStrategy common.DryRunStrategy) list.Formatter {
-	return &formatter{
-		ioStreams:      ioStreams,
-		dryRunStrategy: dryRunStrategy,
-	}
-}
-
-type formatter struct {
-	dryRunStrategy common.DryRunStrategy
-	ioStreams      genericclioptions.IOStreams
-}
-
-func (jf *formatter) FormatApplyEvent(ae event.ApplyEvent) error {
-	eventInfo := jf.baseResourceEvent(ae.Identifier)
-	if ae.Error != nil {
-		eventInfo["error"] = ae.Error.Error()
-		return jf.printEvent("apply", "resourceFailed", eventInfo)
-	}
-	eventInfo["operation"] = ae.Operation.String()
-	return jf.printEvent("apply", "resourceApplied", eventInfo)
-}
-
-func (jf *formatter) FormatStatusEvent(se event.StatusEvent) error {
-	return jf.printResourceStatus(se)
-}
-
-func (jf *formatter) printResourceStatus(se event.StatusEvent) error {
-	eventInfo := jf.baseResourceEvent(se.Identifier)
-	eventInfo["status"] = se.PollResourceInfo.Status.String()
-	eventInfo["message"] = se.PollResourceInfo.Message
-	return jf.printEvent("status", "resourceStatus", eventInfo)
-}
-
-func (jf *formatter) FormatPruneEvent(pe event.PruneEvent) error {
-	eventInfo := jf.baseResourceEvent(pe.Identifier)
-	if pe.Error != nil {
-		eventInfo["error"] = pe.Error.Error()
-		return jf.printEvent("prune", "resourceFailed", eventInfo)
-	}
-	eventInfo["operation"] = pe.Operation.String()
-	return jf.printEvent("prune", "resourcePruned", eventInfo)
-}
-
-func (jf *formatter) FormatDeleteEvent(de event.DeleteEvent) error {
-	eventInfo := jf.baseResourceEvent(de.Identifier)
-	if de.Error != nil {
-		eventInfo["error"] = de.Error.Error()
-		return jf.printEvent("delete", "resourceFailed", eventInfo)
-	}
-	eventInfo["operation"] = de.Operation.String()
-	return jf.printEvent("delete", "resourceDeleted", eventInfo)
-}
-
-func (jf *formatter) FormatErrorEvent(ee event.ErrorEvent) error {
-	return jf.printEvent("error", "error", map[string]interface{}{
-		"error": ee.Err.Error(),
-	})
-}
-
-func (jf *formatter) FormatActionGroupEvent(age event.ActionGroupEvent, ags []event.ActionGroup,
-	as *list.ApplyStats, ps *list.PruneStats, ds *list.DeleteStats, c list.Collector) error {
-	if age.Action == event.ApplyAction && age.Type == event.Finished {
-		if err := jf.printEvent("apply", "completed", map[string]interface{}{
-			"count":           as.Sum(),
-			"createdCount":    as.Created,
-			"unchangedCount":  as.Unchanged,
-			"configuredCount": as.Configured,
-			"serverSideCount": as.ServersideApplied,
-			"failedCount":     as.Failed,
-		}); err != nil {
-			return err
-		}
-	}
-
-	if age.Action == event.PruneAction && age.Type == event.Finished {
-		return jf.printEvent("prune", "completed", map[string]interface{}{
-			"pruned":  ps.Pruned,
-			"skipped": ps.Skipped,
-		})
-	}
-
-	if age.Action == event.DeleteAction && age.Type == event.Finished {
-		return jf.printEvent("delete", "completed", map[string]interface{}{
-			"deleted": ds.Deleted,
-			"skipped": ds.Skipped,
-		})
-	}
-
-	if age.Action == event.WaitAction && age.Type == event.Started {
-		ag, found := list.ActionGroupByName(age.GroupName, ags)
-		if !found {
-			panic(fmt.Errorf("unknown action group name %q", age.GroupName))
-		}
-		for id, se := range c.LatestStatus() {
-			// Only print information about objects that we actually care about
-			// for this wait task.
-			if found := ag.Identifiers.Contains(id); found {
-				if err := jf.printResourceStatus(se); err != nil {
-					return err
-				}
-			}
-		}
-	}
-	return nil
-}
-
-func (jf *formatter) baseResourceEvent(identifier object.ObjMetadata) map[string]interface{} {
-	return map[string]interface{}{
-		"group":     identifier.GroupKind.Group,
-		"kind":      identifier.GroupKind.Kind,
-		"namespace": identifier.Namespace,
-		"name":      identifier.Name,
-	}
-}
-
-func (jf *formatter) printEvent(t, eventType string, content map[string]interface{}) error {
-	m := make(map[string]interface{})
-	m["timestamp"] = time.Now().UTC().Format(time.RFC3339)
-	m["type"] = t
-	m["eventType"] = eventType
-	for key, val := range content {
-		m[key] = val
-	}
-	b, err := json.Marshal(m)
-	if err != nil {
-		return err
-	}
-	_, err = fmt.Fprint(jf.ioStreams.Out, string(b)+"\n")
-	return err
-}
diff --git a/thirdparty/cli-utils/printers/json/printer.go b/thirdparty/cli-utils/printers/json/printer.go
deleted file mode 100644
index 8439560c..00000000
--- a/thirdparty/cli-utils/printers/json/printer.go
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package json
-
-import (
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/print/list"
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/printers/printer"
-	"k8s.io/cli-runtime/pkg/genericclioptions"
-	"sigs.k8s.io/cli-utils/pkg/common"
-)
-
-func NewPrinter(ioStreams genericclioptions.IOStreams) printer.Printer {
-	return &list.BaseListPrinter{
-		FormatterFactory: func(previewStrategy common.DryRunStrategy) list.Formatter {
-			return NewFormatter(ioStreams, previewStrategy)
-		},
-	}
-}
diff --git a/thirdparty/cli-utils/printers/printer/printer.go b/thirdparty/cli-utils/printers/printer/printer.go
deleted file mode 100644
index 21cecf37..00000000
--- a/thirdparty/cli-utils/printers/printer/printer.go
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package printer
-
-import (
-	"sigs.k8s.io/cli-utils/pkg/apply/event"
-	"sigs.k8s.io/cli-utils/pkg/common"
-)
-
-type Printer interface {
-	Print(ch <-chan event.Event, dryRunStrategy common.DryRunStrategy) error
-}
diff --git a/thirdparty/cli-utils/printers/printers.go b/thirdparty/cli-utils/printers/printers.go
deleted file mode 100644
index 94fbdaac..00000000
--- a/thirdparty/cli-utils/printers/printers.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package printers
-
-import (
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/printers/events"
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/printers/json"
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/printers/printer"
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/printers/table"
-	"k8s.io/cli-runtime/pkg/genericclioptions"
-)
-
-const (
-	EventsPrinter = "events"
-	TablePrinter  = "table"
-	JSONPrinter   = "json"
-)
-
-func GetPrinter(printerType string, ioStreams genericclioptions.IOStreams) printer.Printer {
-	switch printerType { //nolint:gocritic
-	case TablePrinter:
-		return &table.Printer{
-			IOStreams: ioStreams,
-		}
-	case JSONPrinter:
-		return json.NewPrinter(ioStreams)
-	default:
-		return events.NewPrinter(ioStreams)
-	}
-}
-
-func SupportedPrinters() []string {
-	return []string{EventsPrinter, TablePrinter, JSONPrinter}
-}
-
-func DefaultPrinter() string {
-	return EventsPrinter
-}
-
-func ValidatePrinterType(printerType string) bool {
-	for _, p := range SupportedPrinters() {
-		if printerType == p {
-			return true
-		}
-	}
-	return false
-}
diff --git a/thirdparty/cli-utils/printers/table/collector.go b/thirdparty/cli-utils/printers/table/collector.go
deleted file mode 100644
index 5b8ab46b..00000000
--- a/thirdparty/cli-utils/printers/table/collector.go
+++ /dev/null
@@ -1,288 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package table
-
-import (
-	"sort"
-	"sync"
-
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/print/table"
-	"k8s.io/klog/v2"
-	"sigs.k8s.io/cli-utils/pkg/apply/event"
-	pe "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
-	"sigs.k8s.io/cli-utils/pkg/kstatus/status"
-	"sigs.k8s.io/cli-utils/pkg/object"
-)
-
-func newResourceStateCollector(resourceGroups []event.ActionGroup) *ResourceStateCollector {
-	resourceInfos := make(map[object.ObjMetadata]*ResourceInfo)
-	for _, group := range resourceGroups {
-		action := group.Action
-		for _, identifier := range group.Identifiers {
-			resourceInfos[identifier] = &ResourceInfo{
-				identifier: identifier,
-				resourceStatus: &pe.ResourceStatus{
-					Identifier: identifier,
-					Status:     status.UnknownStatus,
-				},
-				ResourceAction: action,
-			}
-		}
-	}
-	return &ResourceStateCollector{
-		resourceInfos: resourceInfos,
-	}
-}
-
-// ResourceStateCollector consumes the events from the applier
-// eventChannel and keeps track of the latest state for all resources.
-// It also provides functionality for fetching the latest seen
-// state and return it in format that can be used by the
-// BaseTablePrinter.
-type ResourceStateCollector struct {
-	mux sync.RWMutex
-
-	// resourceInfos contains a mapping from the unique
-	// resource identifier to a ResourceInfo object that captures
-	// the latest state for the given resource.
-	resourceInfos map[object.ObjMetadata]*ResourceInfo
-
-	err error
-}
-
-// ResourceInfo captures the latest seen state of a single resource.
-// This is used for top-level resources that have a ResourceAction
-// associated with them.
-type ResourceInfo struct {
-	// identifier contains the information that identifies a
-	// single resource.
-	identifier object.ObjMetadata
-
-	// resourceStatus contains the latest status information
-	// about the resource.
-	resourceStatus *pe.ResourceStatus
-
-	// ResourceAction defines the action we are performing
-	// on this particular resource. This can be either Apply
-	// or Prune.
-	ResourceAction event.ResourceAction
-
-	// ApplyOpResult contains the result after
-	// a resource has been applied to the cluster.
-	ApplyOpResult event.ApplyEventOperation
-
-	// PruneOpResult contains the result after
-	// a prune operation on a resource
-	PruneOpResult event.PruneEventOperation
-
-	// DeleteOpResult contains the result after
-	// a delete operation on a resource
-	DeleteOpResult event.DeleteEventOperation
-}
-
-// Identifier returns the identifier for the given resource.
-func (r *ResourceInfo) Identifier() object.ObjMetadata {
-	return r.identifier
-}
-
-// ResourceStatus returns the latest seen status for the
-// resource.
-func (r *ResourceInfo) ResourceStatus() *pe.ResourceStatus {
-	return r.resourceStatus
-}
-
-// SubResources returns a slice of Resource which contains
-// any resources created and managed by this resource.
-func (r *ResourceInfo) SubResources() []table.Resource {
-	var resources []table.Resource
-	for _, res := range r.resourceStatus.GeneratedResources {
-		resources = append(resources, &SubResourceInfo{
-			resourceStatus: res,
-		})
-	}
-	return resources
-}
-
-// SubResourceInfo captures the latest seen state of a
-// single subResource, i.e. resources that are created and
-// managed by one of the top-level resources we either apply
-// or prune.
-type SubResourceInfo struct {
-	// resourceStatus contains the latest status information
-	// about the subResource.
-	resourceStatus *pe.ResourceStatus
-}
-
-// Identifier returns the identifier for the given subResource.
-func (r *SubResourceInfo) Identifier() object.ObjMetadata {
-	return r.resourceStatus.Identifier
-}
-
-// ResourceStatus returns the latest seen status for the
-// subResource.
-func (r *SubResourceInfo) ResourceStatus() *pe.ResourceStatus {
-	return r.resourceStatus
-}
-
-// SubResources returns a slice of Resource which contains
-// any resources created and managed by this resource.
-func (r *SubResourceInfo) SubResources() []table.Resource {
-	var resources []table.Resource
-	for _, res := range r.resourceStatus.GeneratedResources {
-		resources = append(resources, &SubResourceInfo{
-			resourceStatus: res,
-		})
-	}
-	return resources
-}
-
-// Listen starts a new goroutine that will listen for events on the
-// provided eventChannel and keep track of the latest state for
-// the resources. The goroutine will exit when the provided
-// eventChannel is closed.
-// The function returns a channel. When this channel is closed, the
-// goroutine has processed all events in the eventChannel and
-// exited.
-func (r *ResourceStateCollector) Listen(eventChannel <-chan event.Event) <-chan listenerResult {
-	completed := make(chan listenerResult)
-	go func() {
-		defer close(completed)
-		for ev := range eventChannel {
-			if err := r.processEvent(ev); err != nil {
-				completed <- listenerResult{err: err}
-				return
-			}
-		}
-	}()
-	return completed
-}
-
-type listenerResult struct {
-	err error
-}
-
-// processEvent processes an event and updates the state.
-func (r *ResourceStateCollector) processEvent(ev event.Event) error {
-	r.mux.Lock()
-	defer r.mux.Unlock()
-	switch ev.Type {
-	case event.StatusType:
-		r.processStatusEvent(ev.StatusEvent)
-	case event.ApplyType:
-		r.processApplyEvent(ev.ApplyEvent)
-	case event.PruneType:
-		r.processPruneEvent(ev.PruneEvent)
-	case event.ErrorType:
-		return ev.ErrorEvent.Err
-	}
-	return nil
-}
-
-// processStatusEvent handles events pertaining to a status
-// update for a resource.
-func (r *ResourceStateCollector) processStatusEvent(e event.StatusEvent) {
-	klog.V(7).Infoln("processing status event")
-	previous, found := r.resourceInfos[e.Identifier]
-	if !found {
-		klog.V(4).Infof("%s status event not found in ResourceInfos; no processing", e.Identifier)
-		return
-	}
-	previous.resourceStatus = e.PollResourceInfo
-}
-
-// processApplyEvent handles events relating to apply operations
-func (r *ResourceStateCollector) processApplyEvent(e event.ApplyEvent) {
-	identifier := e.Identifier
-	klog.V(7).Infof("processing apply event for %s", identifier)
-	previous, found := r.resourceInfos[identifier]
-	if !found {
-		klog.V(4).Infof("%s apply event not found in ResourceInfos; no processing", identifier)
-		return
-	}
-	previous.ApplyOpResult = e.Operation
-}
-
-// processPruneEvent handles event related to prune operations.
-func (r *ResourceStateCollector) processPruneEvent(e event.PruneEvent) {
-	identifier := e.Identifier
-	klog.V(7).Infof("processing prune event for %s", identifier)
-	previous, found := r.resourceInfos[identifier]
-	if !found {
-		klog.V(4).Infof("%s prune event not found in ResourceInfos; no processing", identifier)
-		return
-	}
-	previous.PruneOpResult = e.Operation
-}
-
-// ResourceState contains the latest state for all the resources.
-type ResourceState struct {
-	resourceInfos ResourceInfos
-
-	err error
-}
-
-// Resources returns a slice containing the latest state
-// for each individual resource.
-func (r *ResourceState) Resources() []table.Resource {
-	var resources []table.Resource
-	for _, res := range r.resourceInfos {
-		resources = append(resources, res)
-	}
-	return resources
-}
-
-func (r *ResourceState) Error() error {
-	return r.err
-}
-
-// LatestState returns a ResourceState object that contains
-// a copy of the latest state for all resources.
-func (r *ResourceStateCollector) LatestState() *ResourceState {
-	r.mux.RLock()
-	defer r.mux.RUnlock()
-
-	var resourceInfos ResourceInfos
-	for _, ri := range r.resourceInfos {
-		resourceInfos = append(resourceInfos, &ResourceInfo{
-			identifier:     ri.identifier,
-			resourceStatus: ri.resourceStatus,
-			ResourceAction: ri.ResourceAction,
-			ApplyOpResult:  ri.ApplyOpResult,
-			PruneOpResult:  ri.PruneOpResult,
-			DeleteOpResult: ri.DeleteOpResult,
-		})
-	}
-	sort.Sort(resourceInfos)
-
-	return &ResourceState{
-		resourceInfos: resourceInfos,
-		err:           r.err,
-	}
-}
-
-type ResourceInfos []*ResourceInfo
-
-func (g ResourceInfos) Len() int {
-	return len(g)
-}
-
-func (g ResourceInfos) Less(i, j int) bool {
-	idI := g[i].identifier
-	idJ := g[j].identifier
-
-	if idI.Namespace != idJ.Namespace {
-		return idI.Namespace < idJ.Namespace
-	}
-	if idI.GroupKind.Group != idJ.GroupKind.Group {
-		return idI.GroupKind.Group < idJ.GroupKind.Group
-	}
-	if idI.GroupKind.Kind != idJ.GroupKind.Kind {
-		return idI.GroupKind.Kind < idJ.GroupKind.Kind
-	}
-	return idI.Name < idJ.Name
-}
-
-func (g ResourceInfos) Swap(i, j int) {
-	g[i], g[j] = g[j], g[i]
-}
diff --git a/thirdparty/cli-utils/printers/table/printer.go b/thirdparty/cli-utils/printers/table/printer.go
deleted file mode 100644
index 436add98..00000000
--- a/thirdparty/cli-utils/printers/table/printer.go
+++ /dev/null
@@ -1,149 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package table
-
-import (
-	"fmt"
-	"io"
-	"time"
-
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/print/table"
-	"k8s.io/cli-runtime/pkg/genericclioptions"
-	"sigs.k8s.io/cli-utils/pkg/apply/event"
-	"sigs.k8s.io/cli-utils/pkg/common"
-)
-
-type Printer struct {
-	IOStreams genericclioptions.IOStreams
-}
-
-func (t *Printer) Print(ch <-chan event.Event, _ common.DryRunStrategy) error {
-	// Wait for the init event that will give us the set of
-	// resources.
-	var initEvent event.InitEvent
-	for e := range ch {
-		if e.Type == event.InitType {
-			initEvent = e.InitEvent
-			break
-		}
-		// If we get an error event, we just print it and
-		// exit. The error event signals a fatal error.
-		if e.Type == event.ErrorType {
-			return e.ErrorEvent.Err
-		}
-	}
-	// Create a new collector and initialize it with the resources
-	// we are interested in.
-	coll := newResourceStateCollector(initEvent.ActionGroups)
-
-	stop := make(chan struct{})
-
-	// Start the goroutine that is responsible for
-	// printing the latest state on a regular cadence.
-	printCompleted := t.runPrintLoop(coll, stop)
-
-	// Make the collector start listening on the eventChannel.
-	done := coll.Listen(ch)
-
-	// Block until all the collector has shut down. This means the
-	// eventChannel has been closed and all events have been processed.
-	var err error
-	for msg := range done {
-		err = msg.err
-	}
-
-	// Close the stop channel to notify the print goroutine that it should
-	// shut down.
-	close(stop)
-
-	// Wait until the printCompleted channel is closed. This means
-	// the printer has updated the UI with the latest state and
-	// exited from the goroutine.
-	<-printCompleted
-	return err
-}
-
-// columns defines the columns we want to print
-//TODO: We should have the number of columns and their widths be
-// dependent on the space available.
-var (
-	actionColumnDef = table.ColumnDef{
-		// Column containing the resource type and name. Currently it does not
-		// print group or version since those are rarely needed to uniquely
-		// distinguish two resources from each other. Just name and kind should
-		// be enough in almost all cases and saves space in the output.
-		ColumnName:   "action",
-		ColumnHeader: "ACTION",
-		ColumnWidth:  12,
-		PrintResourceFunc: func(w io.Writer, width int, r table.Resource) (int,
-			error) {
-			var resInfo *ResourceInfo
-			switch res := r.(type) {
-			case *ResourceInfo:
-				resInfo = res
-			default:
-				return 0, nil
-			}
-
-			var text string
-			switch resInfo.ResourceAction {
-			case event.ApplyAction:
-				if resInfo.ApplyOpResult != event.ApplyUnspecified {
-					text = resInfo.ApplyOpResult.String()
-				}
-			case event.PruneAction:
-				if resInfo.PruneOpResult != event.PruneUnspecified {
-					text = resInfo.PruneOpResult.String()
-				}
-			}
-
-			if len(text) > width {
-				text = text[:width]
-			}
-			_, err := fmt.Fprint(w, text)
-			return len(text), err
-		},
-	}
-
-	columns = []table.ColumnDefinition{
-		table.MustColumn("namespace"),
-		table.MustColumn("resource"),
-		actionColumnDef,
-		table.MustColumn("status"),
-		table.MustColumn("conditions"),
-		table.MustColumn("age"),
-		table.MustColumn("message"),
-	}
-)
-
-// runPrintLoop starts a new goroutine that will regularly fetch the
-// latest state from the collector and update the table.
-func (t *Printer) runPrintLoop(coll *ResourceStateCollector, stop chan struct{}) chan struct{} {
-	finished := make(chan struct{})
-
-	baseTablePrinter := table.BaseTablePrinter{
-		IOStreams: t.IOStreams,
-		Columns:   columns,
-	}
-
-	linesPrinted := baseTablePrinter.PrintTable(coll.LatestState(), 0)
-
-	go func() {
-		defer close(finished)
-		ticker := time.NewTicker(500 * time.Millisecond)
-		for {
-			select {
-			case <-stop:
-				ticker.Stop()
-				latestState := coll.LatestState()
-				linesPrinted = baseTablePrinter.PrintTable(latestState, linesPrinted)
-				return
-			case <-ticker.C:
-				latestState := coll.LatestState()
-				linesPrinted = baseTablePrinter.PrintTable(latestState, linesPrinted)
-			}
-		}
-	}()
-	return finished
-}
diff --git a/thirdparty/cli-utils/status/cmdstatus.go b/thirdparty/cli-utils/status/cmdstatus.go
index d0ddce46..d1fa90ad 100644
--- a/thirdparty/cli-utils/status/cmdstatus.go
+++ b/thirdparty/cli-utils/status/cmdstatus.go
@@ -14,7 +14,6 @@ import (
 	"github.com/GoogleContainerTools/kpt/internal/util/argutil"
 	"github.com/GoogleContainerTools/kpt/internal/util/strings"
 	"github.com/GoogleContainerTools/kpt/pkg/live"
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/printers"
 	statusprinters "github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/status/printers"
 	"github.com/go-errors/errors"
 	"github.com/spf13/cobra"
@@ -30,6 +29,7 @@ import (
 	"sigs.k8s.io/cli-utils/pkg/kstatus/polling/engine"
 	"sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
 	kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status"
+	"sigs.k8s.io/cli-utils/pkg/printers"
 )
 
 const (
diff --git a/thirdparty/cli-utils/status/printers/list/base.go b/thirdparty/cli-utils/status/printers/list/base.go
index a23978c8..114ae4ab 100644
--- a/thirdparty/cli-utils/status/printers/list/base.go
+++ b/thirdparty/cli-utils/status/printers/list/base.go
@@ -15,11 +15,11 @@
 package list
 
 import (
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/print/list"
 	"sigs.k8s.io/cli-utils/pkg/apply/event"
 	"sigs.k8s.io/cli-utils/pkg/kstatus/polling/collector"
 	pollevent "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
 	"sigs.k8s.io/cli-utils/pkg/object"
+	"sigs.k8s.io/cli-utils/pkg/print/list"
 )
 
 // BaseListPrinter implements the Printer interface and outputs the resource
diff --git a/thirdparty/cli-utils/status/printers/printers.go b/thirdparty/cli-utils/status/printers/printers.go
index 86b4dbee..43386344 100644
--- a/thirdparty/cli-utils/status/printers/printers.go
+++ b/thirdparty/cli-utils/status/printers/printers.go
@@ -4,14 +4,14 @@
 package printers
 
 import (
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/printers"
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/printers/events"
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/printers/json"
 	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/status/printers/list"
 	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/status/printers/printer"
 	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/status/printers/table"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
 	"sigs.k8s.io/cli-utils/pkg/common"
+	"sigs.k8s.io/cli-utils/pkg/printers"
+	"sigs.k8s.io/cli-utils/pkg/printers/events"
+	"sigs.k8s.io/cli-utils/pkg/printers/json"
 )
 
 // CreatePrinter return an implementation of the Printer interface. The
diff --git a/thirdparty/cli-utils/status/printers/table/adapter.go b/thirdparty/cli-utils/status/printers/table/adapter.go
index f16f80b1..40e37ba4 100644
--- a/thirdparty/cli-utils/status/printers/table/adapter.go
+++ b/thirdparty/cli-utils/status/printers/table/adapter.go
@@ -4,10 +4,10 @@
 package table
 
 import (
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/print/table"
 	"sigs.k8s.io/cli-utils/pkg/kstatus/polling/collector"
 	pe "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
 	"sigs.k8s.io/cli-utils/pkg/object"
+	"sigs.k8s.io/cli-utils/pkg/print/table"
 )
 
 // CollectorAdapter wraps the ResourceStatusCollector and
diff --git a/thirdparty/cli-utils/status/printers/table/table_printer.go b/thirdparty/cli-utils/status/printers/table/table_printer.go
index 7b74bb4d..fa55e408 100644
--- a/thirdparty/cli-utils/status/printers/table/table_printer.go
+++ b/thirdparty/cli-utils/status/printers/table/table_printer.go
@@ -6,11 +6,11 @@ package table
 import (
 	"time"
 
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/print/table"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
 	"sigs.k8s.io/cli-utils/pkg/kstatus/polling/collector"
 	"sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
 	"sigs.k8s.io/cli-utils/pkg/object"
+	"sigs.k8s.io/cli-utils/pkg/print/table"
 )
 
 const (


Test patch:
diff --git a/e2e/live/end-to-end-test.sh b/e2e/live/end-to-end-test.sh
index c5ed1110..06f1c937 100755
--- a/e2e/live/end-to-end-test.sh
+++ b/e2e/live/end-to-end-test.sh
@@ -437,11 +437,10 @@ ${BIN_DIR}/kpt live apply e2e/live/testdata/rg-test-case-1a > $OUTPUT_DIR/status
 # The ResourceGroup inventory CRD is automatically installed on the initial apply.
 assertContains "installing inventory ResourceGroup CRD"
 assertContains "namespace/rg-test-namespace unchanged"
-assertContains "1 resource(s) applied. 0 created, 1 unchanged, 0 configured, 0 failed"
 assertContains "pod/pod-a created"
 assertContains "pod/pod-b created"
 assertContains "pod/pod-c created"
-assertContains "3 resource(s) applied. 3 created, 0 unchanged, 0 configured, 0 failed"
+assertContains "4 resource(s) applied. 3 created, 1 unchanged, 0 configured, 0 failed"
 wait 2
 # Validate resources in the cluster
 # ConfigMap inventory with four inventory items.
@@ -451,11 +450,10 @@ assertRGInventory "rg-test-namespace" "4"
 ${BIN_DIR}/kpt live apply e2e/live/testdata/rg-test-case-1a > $OUTPUT_DIR/status 2>&1
 assertNotContains "installing inventory ResourceGroup CRD"  # Not applied again
 assertContains "namespace/rg-test-namespace unchanged"
-assertContains "1 resource(s) applied. 0 created, 1 unchanged, 0 configured, 0 failed"
 assertContains "pod/pod-a unchanged"
 assertContains "pod/pod-b unchanged"
 assertContains "pod/pod-c unchanged"
-assertContains "3 resource(s) applied. 0 created, 3 unchanged, 0 configured, 0 failed"
+assertContains "4 resource(s) applied. 0 created, 4 unchanged, 0 configured, 0 failed"
 wait 2
 
 printResult
@@ -529,12 +527,11 @@ printResult
 echo "[ResourceGroup] Testing initial apply dry-run"
 echo "kpt live apply --dry-run e2e/live/testdata/rg-test-case-1a"
 ${BIN_DIR}/kpt live apply --dry-run e2e/live/testdata/rg-test-case-1a > $OUTPUT_DIR/status
-assertContains "namespace/rg-test-namespace created (dry-run)"
-assertContains "1 resource(s) applied. 1 created, 0 unchanged, 0 configured, 0 failed (dry-run)"
-assertContains "pod/pod-a created (dry-run)"
-assertContains "pod/pod-b created (dry-run)"
-assertContains "pod/pod-c created (dry-run)"
-assertContains "3 resource(s) applied. 3 created, 0 unchanged, 0 configured, 0 failed"
+assertContains "namespace/rg-test-namespace created"
+assertContains "pod/pod-a created"
+assertContains "pod/pod-b created"
+assertContains "pod/pod-c created"
+assertContains "4 resource(s) applied. 4 created, 0 unchanged, 0 configured, 0 failed"
 printResult
 
 # Test: Basic kpt live apply
@@ -545,11 +542,10 @@ ${BIN_DIR}/kpt live apply e2e/live/testdata/rg-test-case-1a > $OUTPUT_DIR/status
 # The ResourceGroup CRD is already installed.
 assertNotContains "installing inventory ResourceGroup CRD"
 assertContains "namespace/rg-test-namespace unchanged"
-assertContains "1 resource(s) applied. 0 created, 1 unchanged, 0 configured, 0 failed"
 assertContains "pod/pod-a created"
 assertContains "pod/pod-b created"
 assertContains "pod/pod-c created"
-assertContains "3 resource(s) applied. 3 created, 0 unchanged, 0 configured, 0 failed"
+assertContains "4 resource(s) applied. 3 created, 1 unchanged, 0 configured, 0 failed"
 wait 2
 # Validate resources in the cluster
 # ConfigMap inventory with four inventory items.
@@ -565,11 +561,10 @@ ${BIN_DIR}/kpt live apply link-to-rg-test-case-1a > $OUTPUT_DIR/status
 # The ResourceGroup CRD is already installed.
 assertNotContains "installing inventory ResourceGroup CRD"
 assertContains "namespace/rg-test-namespace unchanged"
-assertContains "1 resource(s) applied. 0 created, 1 unchanged, 0 configured, 0 failed"
 assertContains "pod/pod-a unchanged"
 assertContains "pod/pod-b unchanged"
 assertContains "pod/pod-c unchanged"
-assertContains "3 resource(s) applied. 0 created, 3 unchanged, 0 configured, 0 failed"
+assertContains "4 resource(s) applied. 0 created, 4 unchanged, 0 configured, 0 failed"
 wait 2
 # Validate resources in the cluster
 # ConfigMap inventory with four inventory items.
@@ -602,14 +597,13 @@ echo "[ResourceGroup] Testing basic apply dry-run"
 echo "kpt live apply --dry-run e2e/live/testdata/rg-test-case-1b"
 cp -f e2e/live/testdata/rg-test-case-1a/Kptfile e2e/live/testdata/rg-test-case-1b
 ${BIN_DIR}/kpt live apply --dry-run e2e/live/testdata/rg-test-case-1b > $OUTPUT_DIR/status
-assertContains "namespace/rg-test-namespace configured (dry-run)"
-assertContains "1 resource(s) applied. 0 created, 0 unchanged, 1 configured, 0 failed (dry-run)"
-assertContains "pod/pod-b configured (dry-run)"
-assertContains "pod/pod-c configured (dry-run)"
-assertContains "pod/pod-d created (dry-run)"
-assertContains "3 resource(s) applied. 1 created, 0 unchanged, 2 configured, 0 failed (dry-run)"
-assertContains "pod/pod-a pruned (dry-run)"
-assertContains "1 resource(s) pruned, 0 skipped, 0 failed (dry-run)"
+assertContains "namespace/rg-test-namespace configured"
+assertContains "pod/pod-b configured"
+assertContains "pod/pod-c configured"
+assertContains "pod/pod-d created"
+assertContains "4 resource(s) applied. 1 created, 0 unchanged, 3 configured, 0 failed"
+assertContains "pod/pod-a pruned"
+assertContains "1 resource(s) pruned, 0 skipped, 0 failed"
 wait 2
 # Validate resources in the cluster
 # ConfigMap inventory with four inventory items.
@@ -626,11 +620,10 @@ echo "kpt live apply e2e/live/testdata/rg-test-case-1b"
 ${BIN_DIR}/kpt live apply e2e/live/testdata/rg-test-case-1b > $OUTPUT_DIR/status
 assertNotContains "installing inventory ResourceGroup CRD"  # CRD already installed
 assertContains "namespace/rg-test-namespace unchanged"
-assertContains "1 resource(s) applied. 0 created, 1 unchanged, 0 configured, 0 failed"
 assertContains "pod/pod-b unchanged"
 assertContains "pod/pod-c unchanged"
 assertContains "pod/pod-d created"
-assertContains "3 resource(s) applied. 1 created, 2 unchanged, 0 configured, 0 failed"
+assertContains "4 resource(s) applied. 1 created, 3 unchanged, 0 configured, 0 failed"
 assertContains "pod/pod-a pruned"
 assertContains "1 resource(s) pruned, 0 skipped, 0 failed"
 wait 2
@@ -647,12 +640,11 @@ printResult
 echo "[ResourceGroup] Testing basic destroy dry-run"
 echo "kpt live destroy --dry-run e2e/live/testdata/rg-test-case-1b"
 ${BIN_DIR}/kpt live destroy --dry-run e2e/live/testdata/rg-test-case-1b > $OUTPUT_DIR/status
-assertContains "pod/pod-d deleted (dry-run)"
-assertContains "pod/pod-c deleted (dry-run)"
-assertContains "pod/pod-b deleted (dry-run)"
-assertContains "3 resource(s) deleted, 0 skipped (dry-run)"
-assertContains "namespace/rg-test-namespace deleted (dry-run)"
-assertContains "1 resource(s) deleted, 0 skipped (dry-run)"
+assertContains "pod/pod-d deleted"
+assertContains "pod/pod-c deleted"
+assertContains "pod/pod-b deleted"
+assertContains "namespace/rg-test-namespace deleted"
+assertContains "4 resource(s) deleted, 0 skipped"
 # Validate resources NOT DESTROYED in the cluster
 assertPodExists "pod-b" "rg-test-namespace"
 assertPodExists "pod-c" "rg-test-namespace"
@@ -667,9 +659,8 @@ ${BIN_DIR}/kpt live destroy e2e/live/testdata/rg-test-case-1b > $OUTPUT_DIR/stat
 assertContains "pod/pod-d deleted"
 assertContains "pod/pod-c deleted"
 assertContains "pod/pod-b deleted"
-assertContains "3 resource(s) deleted, 0 skipped"
 assertContains "namespace/rg-test-namespace deleted"
-assertContains "1 resource(s) deleted, 0 skipped"
+assertContains "4 resource(s) deleted, 0 skipped"
 # Validate resources NOT in the cluster
 assertPodNotExists "pod-b" "rg-test-namespace"
 assertPodNotExists "pod-c" "rg-test-namespace"
@@ -684,7 +675,7 @@ cat e2e/live/testdata/stdin-test/pods.yaml | ${BIN_DIR}/kpt live apply - > $OUTP
 assertContains "pod/pod-a created"
 assertContains "pod/pod-b created"
 assertContains "pod/pod-c created"
-assertContains "3 resource(s) applied. 3 created, 0 unchanged, 0 configured, 0 failed"
+assertContains "4 resource(s) applied. 3 created, 1 unchanged, 0 configured, 0 failed"
 printResult
 echo "cat e2e/live/testdata/stdin-test/pods.yaml | kpt live status -"
 cat e2e/live/testdata/stdin-test/pods.yaml | ${BIN_DIR}/kpt live status - > $OUTPUT_DIR/status 2>&1
@@ -698,7 +689,7 @@ cat e2e/live/testdata/stdin-test/pods.yaml | ${BIN_DIR}/kpt live destroy - > $OU
 assertContains "pod/pod-a deleted"
 assertContains "pod/pod-b deleted"
 assertContains "pod/pod-c deleted"
-assertContains "3 resource(s) deleted, 0 skipped"
+assertContains "4 resource(s) deleted, 0 skipped"
 printResult
 
 # Test: kpt live apply continue-on-error
@@ -829,11 +820,10 @@ echo "kpt live apply e2e/live/testdata/migrate-case-1b"
 cp -f e2e/live/testdata/migrate-case-1a/Kptfile e2e/live/testdata/migrate-case-1b
 ${BIN_DIR}/kpt live apply e2e/live/testdata/migrate-case-1b > $OUTPUT_DIR/status
 assertContains "namespace/test-rg-namespace unchanged"
-assertContains "1 resource(s) applied. 0 created, 1 unchanged, 0 configured, 0 failed"
 assertContains "pod/pod-b unchanged"
 assertContains "pod/pod-c unchanged"
 assertContains "pod/pod-d created"
-assertContains "3 resource(s) applied. 1 created, 2 unchanged, 0 configured, 0 failed"
+assertContains "4 resource(s) applied. 1 created, 3 unchanged, 0 configured, 0 failed"
 assertContains "pod/pod-a pruned"
 assertContains "1 resource(s) pruned, 0 skipped, 0 failed"
 wait 2
diff --git a/e2e/testdata/fn-eval/exec-function-stderr/.expected/config.yaml b/e2e/testdata/fn-eval/exec-function-stderr/.expected/config.yaml
new file mode 100644
index 00000000..f45537b8
--- /dev/null
+++ b/e2e/testdata/fn-eval/exec-function-stderr/.expected/config.yaml
@@ -0,0 +1,24 @@
+# Copyright 2021 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+testType: eval
+stdErr: |
+  [RUNNING] "./function.sh"
+  [PASS] "./function.sh" in 0s
+    Stderr:
+      "Hello world 0!"
+      "Hello world 1!"
+      "Hello world 2!"
+      "Hello world 3!"
+      ...(18 line(s) truncated, use '--truncate-output=false' to disable)
diff --git a/e2e/testdata/fn-eval/exec-function-stderr/.expected/diff.patch b/e2e/testdata/fn-eval/exec-function-stderr/.expected/diff.patch
new file mode 100644
index 00000000..cc985fa3
--- /dev/null
+++ b/e2e/testdata/fn-eval/exec-function-stderr/.expected/diff.patch
@@ -0,0 +1,21 @@
+diff --git a/resources.yaml b/resources.yaml
+index e8ae6bb..297b99f 100644
+--- a/resources.yaml
++++ b/resources.yaml
+@@ -15,7 +15,7 @@ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+   name: nginx-deployment
+-  namespace: foo
++  namespace: bar
+ spec:
+   replicas: 3
+ ---
+@@ -23,6 +23,6 @@ apiVersion: custom.io/v1
+ kind: Custom
+ metadata:
+   name: custom
+-  namespace: foo
++  namespace: bar
+ spec:
+   image: nginx:1.2.3
diff --git a/e2e/testdata/fn-eval/exec-function-stderr/.expected/exec.sh b/e2e/testdata/fn-eval/exec-function-stderr/.expected/exec.sh
new file mode 100644
index 00000000..4a180de5
--- /dev/null
+++ b/e2e/testdata/fn-eval/exec-function-stderr/.expected/exec.sh
@@ -0,0 +1,19 @@
+#! /bin/bash
+# Copyright 2021 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -eo pipefail
+
+kpt fn source \
+  | kpt fn eval --exec ./function.sh
diff --git a/e2e/testdata/fn-eval/exec-function-stderr/.krmignore b/e2e/testdata/fn-eval/exec-function-stderr/.krmignore
new file mode 100644
index 00000000..9d7a4007
--- /dev/null
+++ b/e2e/testdata/fn-eval/exec-function-stderr/.krmignore
@@ -0,0 +1,1 @@
+.expected
diff --git a/e2e/testdata/fn-eval/exec-function-stderr/function.sh b/e2e/testdata/fn-eval/exec-function-stderr/function.sh
new file mode 100755
index 00000000..c9cb9d7b
--- /dev/null
+++ b/e2e/testdata/fn-eval/exec-function-stderr/function.sh
@@ -0,0 +1,21 @@
+#! /usr/bin/env bash
+# Copyright 2021 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+sed -e 's/foo/bar/'
+
+for i in {0..20}
+do
+  >&2 echo "Hello world $i!"
+done
diff --git a/e2e/testdata/fn-eval/exec-function-stderr/resources.yaml b/e2e/testdata/fn-eval/exec-function-stderr/resources.yaml
new file mode 100644
index 00000000..e8ae6bba
--- /dev/null
+++ b/e2e/testdata/fn-eval/exec-function-stderr/resources.yaml
@@ -0,0 +1,28 @@
+# Copyright 2021 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: nginx-deployment
+  namespace: foo
+spec:
+  replicas: 3
+---
+apiVersion: custom.io/v1
+kind: Custom
+metadata:
+  name: custom
+  namespace: foo
+spec:
+  image: nginx:1.2.3
diff --git a/e2e/testdata/fn-render/exec-function-stderr/.expected/config.yaml b/e2e/testdata/fn-render/exec-function-stderr/.expected/config.yaml
new file mode 100644
index 00000000..9183295c
--- /dev/null
+++ b/e2e/testdata/fn-render/exec-function-stderr/.expected/config.yaml
@@ -0,0 +1,24 @@
+# Copyright 2021 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+allowExec: true
+stdErr: |
+  [RUNNING] "./testdata/fn-render/exec-function-stderr/function.sh"
+  [PASS] "./testdata/fn-render/exec-function-stderr/function.sh" in 0s
+    Stderr:
+      "Hello world 0!"
+      "Hello world 1!"
+      "Hello world 2!"
+      "Hello world 3!"
+      ...(18 line(s) truncated, use '--truncate-output=false' to disable)
diff --git a/e2e/testdata/fn-render/exec-function-stderr/.expected/diff.patch b/e2e/testdata/fn-render/exec-function-stderr/.expected/diff.patch
new file mode 100644
index 00000000..cc985fa3
--- /dev/null
+++ b/e2e/testdata/fn-render/exec-function-stderr/.expected/diff.patch
@@ -0,0 +1,21 @@
+diff --git a/resources.yaml b/resources.yaml
+index e8ae6bb..297b99f 100644
+--- a/resources.yaml
++++ b/resources.yaml
+@@ -15,7 +15,7 @@ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+   name: nginx-deployment
+-  namespace: foo
++  namespace: bar
+ spec:
+   replicas: 3
+ ---
+@@ -23,6 +23,6 @@ apiVersion: custom.io/v1
+ kind: Custom
+ metadata:
+   name: custom
+-  namespace: foo
++  namespace: bar
+ spec:
+   image: nginx:1.2.3
diff --git a/e2e/testdata/fn-render/exec-function-stderr/.krmignore b/e2e/testdata/fn-render/exec-function-stderr/.krmignore
new file mode 100644
index 00000000..9d7a4007
--- /dev/null
+++ b/e2e/testdata/fn-render/exec-function-stderr/.krmignore
@@ -0,0 +1,1 @@
+.expected
diff --git a/e2e/testdata/fn-render/exec-function-stderr/Kptfile b/e2e/testdata/fn-render/exec-function-stderr/Kptfile
new file mode 100644
index 00000000..6f2fe112
--- /dev/null
+++ b/e2e/testdata/fn-render/exec-function-stderr/Kptfile
@@ -0,0 +1,7 @@
+apiVersion: kpt.dev/v1
+kind: Kptfile
+metadata:
+  name: app
+pipeline:
+  mutators:
+    - exec: "./testdata/fn-render/exec-function-stderr/function.sh"
diff --git a/e2e/testdata/fn-render/exec-function-stderr/function.sh b/e2e/testdata/fn-render/exec-function-stderr/function.sh
new file mode 100755
index 00000000..c9cb9d7b
--- /dev/null
+++ b/e2e/testdata/fn-render/exec-function-stderr/function.sh
@@ -0,0 +1,21 @@
+#! /usr/bin/env bash
+# Copyright 2021 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+sed -e 's/foo/bar/'
+
+for i in {0..20}
+do
+  >&2 echo "Hello world $i!"
+done
diff --git a/e2e/testdata/fn-render/exec-function-stderr/resources.yaml b/e2e/testdata/fn-render/exec-function-stderr/resources.yaml
new file mode 100644
index 00000000..e8ae6bba
--- /dev/null
+++ b/e2e/testdata/fn-render/exec-function-stderr/resources.yaml
@@ -0,0 +1,28 @@
+# Copyright 2021 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: nginx-deployment
+  namespace: foo
+spec:
+  replicas: 3
+---
+apiVersion: custom.io/v1
+kind: Custom
+metadata:
+  name: custom
+  namespace: foo
+spec:
+  image: nginx:1.2.3
diff --git a/e2e/testdata/live-apply/apply-depends-on/config.yaml b/e2e/testdata/live-apply/apply-depends-on/config.yaml
index c12c7ab5..b9549880 100644
--- a/e2e/testdata/live-apply/apply-depends-on/config.yaml
+++ b/e2e/testdata/live-apply/apply-depends-on/config.yaml
@@ -15,13 +15,17 @@
 parallel: true
 
 kptArgs:
-  - "--reconcile-timeout=1m"
+  - "--reconcile-timeout=2m"
 
 stdOut: |
   deployment.apps/first-nginx created
-  1 resource(s) applied. 1 created, 0 unchanged, 0 configured, 0 failed
+  deployment.apps/first-nginx reconcile pending
+  deployment.apps/first-nginx reconciled
   deployment.apps/second-nginx created
-  1 resource(s) applied. 1 created, 0 unchanged, 0 configured, 0 failed
+  2 resource(s) applied. 2 created, 0 unchanged, 0 configured, 0 failed
+  deployment.apps/second-nginx reconcile pending
+  deployment.apps/second-nginx reconciled
+  2 resource(s) reconciled, 0 skipped, 0 failed to reconcile, 0 timed out
 
 inventory:
   - group: apps
diff --git a/e2e/testdata/live-apply/crd-and-cr/config.yaml b/e2e/testdata/live-apply/crd-and-cr/config.yaml
index 943b973a..a0e5ef4a 100644
--- a/e2e/testdata/live-apply/crd-and-cr/config.yaml
+++ b/e2e/testdata/live-apply/crd-and-cr/config.yaml
@@ -19,9 +19,13 @@ kptArgs:
 
 stdOut: |
   customresourcedefinition.apiextensions.k8s.io/customs.kpt.dev created
-  1 resource(s) applied. 1 created, 0 unchanged, 0 configured, 0 failed
+  customresourcedefinition.apiextensions.k8s.io/customs.kpt.dev reconcile pending
+  customresourcedefinition.apiextensions.k8s.io/customs.kpt.dev reconciled
   custom.kpt.dev/cr created
-  1 resource(s) applied. 1 created, 0 unchanged, 0 configured, 0 failed
+  2 resource(s) applied. 2 created, 0 unchanged, 0 configured, 0 failed
+  custom.kpt.dev/cr reconcile pending
+  custom.kpt.dev/cr reconciled
+  2 resource(s) reconciled, 0 skipped, 0 failed to reconcile, 0 timed out
 inventory:
   - group: apiextensions.k8s.io
     kind: CustomResourceDefinition
diff --git a/e2e/testdata/live-apply/dry-run-with-install-rg/config.yaml b/e2e/testdata/live-apply/dry-run-with-install-rg/config.yaml
index 5f8ab9af..add5fb5e 100644
--- a/e2e/testdata/live-apply/dry-run-with-install-rg/config.yaml
+++ b/e2e/testdata/live-apply/dry-run-with-install-rg/config.yaml
@@ -18,8 +18,8 @@ kptArgs:
   - "--dry-run"
   - "--install-resource-group"
 stdOut: |
-  deployment.apps/nginx-deployment created (dry-run)
-  1 resource(s) applied. 1 created, 0 unchanged, 0 configured, 0 failed (dry-run)
+  deployment.apps/nginx-deployment created
+  1 resource(s) applied. 1 created, 0 unchanged, 0 configured, 0 failed
 stdErr: |
   installing inventory ResourceGroup CRD.
 exitCode: 0
\ No newline at end of file
diff --git a/e2e/testdata/live-apply/json-output/config.yaml b/e2e/testdata/live-apply/json-output/config.yaml
index 1d950b5f..2a5526d4 100644
--- a/e2e/testdata/live-apply/json-output/config.yaml
+++ b/e2e/testdata/live-apply/json-output/config.yaml
@@ -15,16 +15,24 @@
 parallel: true
 kptArgs:
   - "--output=json"
-  - "--reconcile-timeout=1m"
+  - "--reconcile-timeout=2m"
 stdOut: |
   {"eventType":"resourceApplied","group":"","kind":"ConfigMap","name":"cm","namespace":"json-output","operation":"Created","timestamp":"<TIMESTAMP>","type":"apply"}
-  {"configuredCount":0,"count":1,"createdCount":1,"eventType":"completed","failedCount":0,"serverSideCount":0,"timestamp":"<TIMESTAMP>","type":"apply","unchangedCount":0}
+  {"eventType":"resourceReconciled","group":"","kind":"ConfigMap","name":"cm","namespace":"json-output","operation":"Pending","timestamp":"<TIMESTAMP>","type":"wait"}
+  {"eventType":"resourceReconciled","group":"","kind":"ConfigMap","name":"cm","namespace":"json-output","operation":"Reconciled","timestamp":"<TIMESTAMP>","type":"wait"}
   {"eventType":"resourceApplied","group":"apps","kind":"Deployment","name":"nginx","namespace":"json-output","operation":"Created","timestamp":"<TIMESTAMP>","type":"apply"}
-  {"configuredCount":0,"count":1,"createdCount":1,"eventType":"completed","failedCount":0,"serverSideCount":0,"timestamp":"<TIMESTAMP>","type":"apply","unchangedCount":0}
+  {"configuredCount":0,"count":2,"createdCount":2,"eventType":"completed","failedCount":0,"serverSideCount":0,"timestamp":"<TIMESTAMP>","type":"apply","unchangedCount":0}
+  {"eventType":"resourceReconciled","group":"apps","kind":"Deployment","name":"nginx","namespace":"json-output","operation":"Pending","timestamp":"<TIMESTAMP>","type":"wait"}
+  {"eventType":"resourceReconciled","group":"apps","kind":"Deployment","name":"nginx","namespace":"json-output","operation":"Reconciled","timestamp":"<TIMESTAMP>","type":"wait"}
   {"eventType":"resourcePruned","group":"apps","kind":"Deployment","name":"second-nginx","namespace":"json-output","operation":"Pruned","timestamp":"<TIMESTAMP>","type":"prune"}
-  {"eventType":"completed","pruned":1,"skipped":0,"timestamp":"<TIMESTAMP>","type":"prune"}
+  {"eventType":"resourceReconciled","group":"apps","kind":"Deployment","name":"second-nginx","namespace":"json-output","operation":"Pending","timestamp":"<TIMESTAMP>","type":"wait"}
+  {"eventType":"resourceReconciled","group":"apps","kind":"Deployment","name":"second-nginx","namespace":"json-output","operation":"Reconciled","timestamp":"<TIMESTAMP>","type":"wait"}
   {"eventType":"resourcePruned","group":"apps","kind":"Deployment","name":"first-nginx","namespace":"json-output","operation":"Pruned","timestamp":"<TIMESTAMP>","type":"prune"}
-  {"eventType":"completed","pruned":1,"skipped":0,"timestamp":"<TIMESTAMP>","type":"prune"}
+  {"eventType":"completed","failed":0,"pruned":2,"skipped":0,"timestamp":"<TIMESTAMP>","type":"prune"}
+  {"eventType":"resourceReconciled","group":"apps","kind":"Deployment","name":"first-nginx","namespace":"json-output","operation":"Pending","timestamp":"<TIMESTAMP>","type":"wait"}
+  {"eventType":"resourceReconciled","group":"apps","kind":"Deployment","name":"first-nginx","namespace":"json-output","operation":"Reconciled","timestamp":"<TIMESTAMP>","type":"wait"}
+  {"eventType":"completed","failed":0,"reconciled":4,"skipped":0,"timeout":0,"timestamp":"<TIMESTAMP>","type":"wait"}
+
 inventory:
   - kind: ConfigMap
     name: cm
diff --git a/e2e/testdata/live-apply/prune-depends-on/config.yaml b/e2e/testdata/live-apply/prune-depends-on/config.yaml
index 91e57f40..2f85051b 100644
--- a/e2e/testdata/live-apply/prune-depends-on/config.yaml
+++ b/e2e/testdata/live-apply/prune-depends-on/config.yaml
@@ -20,10 +20,17 @@ kptArgs:
 stdOut: |
   configmap/cm created
   1 resource(s) applied. 1 created, 0 unchanged, 0 configured, 0 failed
+  configmap/cm reconcile pending
+  configmap/cm reconciled
   deployment.apps/second-nginx pruned
-  1 resource(s) pruned, 0 skipped, 0 failed
+  deployment.apps/second-nginx reconcile pending
+  deployment.apps/second-nginx reconciled
   deployment.apps/first-nginx pruned
-  1 resource(s) pruned, 0 skipped, 0 failed
+  2 resource(s) pruned, 0 skipped, 0 failed to prune
+  deployment.apps/first-nginx reconcile pending
+  deployment.apps/first-nginx reconciled
+  3 resource(s) reconciled, 0 skipped, 0 failed to reconcile, 0 timed out
+
 inventory:
   - kind: ConfigMap
     name: cm
diff --git a/internal/fnruntime/runner_test.go b/internal/fnruntime/runner_test.go
index 8c3e7494..97fd2b1f 100644
--- a/internal/fnruntime/runner_test.go
+++ b/internal/fnruntime/runner_test.go
@@ -18,12 +18,14 @@ package fnruntime
 
 import (
 	"bytes"
+	"context"
 	"io/ioutil"
 	"os"
 	"path"
 	"strings"
 	"testing"
 
+	"github.com/GoogleContainerTools/kpt/internal/printer"
 	"github.com/GoogleContainerTools/kpt/internal/types"
 	kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1"
 	"github.com/stretchr/testify/assert"
@@ -577,3 +579,70 @@ file:
 		assert.Equal(t, tc.expected, string(out))
 	}
 }
+
+func TestPrintFnStderr(t *testing.T) {
+	tests := map[string]struct {
+		input          string // input
+		truncateOutput bool   // whether to truncate output
+		expected       string // expected result
+	}{
+		"no output": {
+			input:          ``,
+			truncateOutput: true,
+			expected:       ``,
+		},
+		"truncated output": {
+			input: `0
+1
+2
+3
+4
+5`,
+			truncateOutput: true,
+			expected: `  Stderr:
+    "0"
+    "1"
+    "2"
+    "3"
+    ...(2 line(s) truncated, use '--truncate-output=false' to disable)
+`,
+		},
+		"non-truncated output": {
+			input: `0
+1
+2
+3
+4
+5`,
+			truncateOutput: false,
+			expected: `  Stderr:
+    "0"
+    "1"
+    "2"
+    "3"
+    "4"
+    "5"
+`,
+		},
+	}
+	cleanupFunc := func() func() {
+		origTruncateOutput := printer.TruncateOutput
+		return func() {
+			printer.TruncateOutput = origTruncateOutput
+		}
+	}()
+	defer cleanupFunc()
+	for testName, tc := range tests {
+		t.Run(testName, func(t *testing.T) {
+			printer.TruncateOutput = tc.truncateOutput
+			out := &bytes.Buffer{}
+			err := &bytes.Buffer{}
+			ctx := printer.WithContext(context.Background(), printer.New(out, err))
+
+			printFnStderr(ctx, tc.input)
+
+			assert.Equal(t, tc.expected, err.String())
+			assert.Equal(t, "", out.String())
+		})
+	}
+}
diff --git a/thirdparty/cli-utils/flagutils/utils_test.go b/thirdparty/cli-utils/flagutils/utils_test.go
deleted file mode 100644
index 7be82bee..00000000
--- a/thirdparty/cli-utils/flagutils/utils_test.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2021 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package flagutils
-
-import (
-	"fmt"
-	"testing"
-
-	"sigs.k8s.io/cli-utils/pkg/inventory"
-)
-
-func TestConvertInventoryPolicy(t *testing.T) {
-	testcases := []struct {
-		value  string
-		policy inventory.InventoryPolicy
-		err    error
-	}{
-		{
-			value:  "strict",
-			policy: inventory.InventoryPolicyMustMatch,
-		},
-		{
-			value:  "adopt",
-			policy: inventory.AdoptIfNoInventory,
-		},
-		{
-			value: "random",
-			err:   fmt.Errorf("inventory policy must be one of strict, adopt"),
-		},
-	}
-	for _, tc := range testcases {
-		t.Run(tc.value, func(t *testing.T) {
-			policy, err := ConvertInventoryPolicy(tc.value)
-			if tc.err == nil {
-				if err != nil {
-					t.Errorf("unexpected error %v", err)
-				}
-				if policy != tc.policy {
-					t.Errorf("expected %v but got %v", policy, tc.policy)
-				}
-			}
-			if err == nil && tc.err != nil {
-				t.Errorf("expected an error, but not happened")
-			}
-		})
-	}
-}
diff --git a/thirdparty/cli-utils/print/common/color_test.go b/thirdparty/cli-utils/print/common/color_test.go
deleted file mode 100644
index 14a82837..00000000
--- a/thirdparty/cli-utils/print/common/color_test.go
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package common
-
-import (
-	"testing"
-
-	"gotest.tools/assert"
-	"sigs.k8s.io/cli-utils/pkg/kstatus/status"
-)
-
-func TestSprintfWithColor(t *testing.T) {
-	testCases := map[string]struct {
-		color          Color
-		format         string
-		args           []interface{}
-		expectedResult string
-	}{
-		"no args with color": {
-			color:          GREEN,
-			format:         "This is a test",
-			args:           []interface{}{},
-			expectedResult: "\x1b[32mThis is a test\x1b[0m",
-		},
-		"with args and color": {
-			color:          YELLOW,
-			format:         "%s %s",
-			args:           []interface{}{"sonic", "youth"},
-			expectedResult: "\x1b[33msonic youth\x1b[0m",
-		},
-	}
-
-	for tn, tc := range testCases {
-		t.Run(tn, func(t *testing.T) {
-			result := SprintfWithColor(tc.color, tc.format, tc.args...)
-			if want, got := tc.expectedResult, result; want != got {
-				t.Errorf("expected %q, but got %q", want, got)
-			}
-		})
-	}
-}
-
-func TestColorForStatus(t *testing.T) {
-	testCases := map[string]struct {
-		status           status.Status
-		expectedSetColor bool
-		expectedColor    Color
-	}{
-		"status with color": {
-			status:           status.CurrentStatus,
-			expectedSetColor: true,
-			expectedColor:    GREEN,
-		},
-		"status without color": {
-			status:           status.NotFoundStatus,
-			expectedSetColor: false,
-		},
-	}
-
-	for tn, tc := range testCases {
-		t.Run(tn, func(t *testing.T) {
-			color, setColor := ColorForStatus(tc.status)
-			assert.Equal(t, setColor, tc.expectedSetColor)
-			if tc.expectedSetColor {
-				assert.Equal(t, color, tc.expectedColor)
-			}
-		})
-	}
-}
diff --git a/thirdparty/cli-utils/print/table/base_test.go b/thirdparty/cli-utils/print/table/base_test.go
deleted file mode 100644
index 3ee95eb1..00000000
--- a/thirdparty/cli-utils/print/table/base_test.go
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package table
-
-import (
-	"fmt"
-	"io"
-	"strings"
-	"testing"
-
-	"gotest.tools/assert"
-	"k8s.io/apimachinery/pkg/runtime/schema"
-	"k8s.io/cli-runtime/pkg/genericclioptions"
-	pe "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
-	"sigs.k8s.io/cli-utils/pkg/object"
-)
-
-var (
-	endColumnDef = ColumnDef{
-		ColumnName:   "end",
-		ColumnHeader: "END",
-		ColumnWidth:  3,
-		PrintResourceFunc: func(w io.Writer, width int, r Resource) (i int,
-			err error) {
-			return fmt.Fprint(w, "end")
-		},
-	}
-)
-
-func TestBaseTablePrinter_PrintTable(t *testing.T) {
-	testCases := map[string]struct {
-		columnDefinitions []ColumnDefinition
-		resources         []Resource
-		expectedOutput    string
-	}{
-		"no resources": {
-			columnDefinitions: []ColumnDefinition{
-				MustColumn("resource"),
-				endColumnDef,
-			},
-			resources: []Resource{},
-			expectedOutput: `
-RESOURCE                                  END
-`,
-		},
-		"with resource": {
-			columnDefinitions: []ColumnDefinition{
-				MustColumn("resource"),
-				endColumnDef,
-			},
-			resources: []Resource{
-				&fakeResource{
-					resourceStatus: &pe.ResourceStatus{
-						Identifier: object.ObjMetadata{
-							Namespace: "default",
-							Name:      "Foo",
-							GroupKind: schema.GroupKind{
-								Group: "apps",
-								Kind:  "Deployment",
-							},
-						},
-					},
-				},
-			},
-			expectedOutput: `
-RESOURCE                                  END
-Deployment/Foo                            end
-`,
-		},
-		"sub resources": {
-			columnDefinitions: []ColumnDefinition{
-				MustColumn("resource"),
-				endColumnDef,
-			},
-			resources: []Resource{
-				&fakeResource{
-					resourceStatus: &pe.ResourceStatus{
-						Identifier: object.ObjMetadata{
-							Namespace: "default",
-							Name:      "Foo",
-							GroupKind: schema.GroupKind{
-								Group: "apps",
-								Kind:  "Deployment",
-							},
-						},
-						GeneratedResources: []*pe.ResourceStatus{
-							{
-								Identifier: object.ObjMetadata{
-									Namespace: "default",
-									Name:      "Bar",
-									GroupKind: schema.GroupKind{
-										Group: "apps",
-										Kind:  "ReplicaSet",
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-			expectedOutput: `
-RESOURCE                                  END
-Deployment/Foo                            end
-└─ ReplicaSet/Bar                         end
-`,
-		},
-		"trim long content": {
-			columnDefinitions: []ColumnDefinition{
-				MustColumn("resource"),
-				endColumnDef,
-			},
-			resources: []Resource{
-				&fakeResource{
-					resourceStatus: &pe.ResourceStatus{
-						Identifier: object.ObjMetadata{
-							Namespace: "default",
-							Name:      "VeryLongNameThatShouldBeTrimmed",
-							GroupKind: schema.GroupKind{
-								Group: "apps",
-								Kind:  "Deployment",
-							},
-						},
-					},
-				},
-			},
-			expectedOutput: `
-RESOURCE                                  END
-Deployment/VeryLongNameThatShouldBeTrimm  end
-`,
-		},
-	}
-
-	for tn, tc := range testCases {
-		t.Run(tn, func(t *testing.T) {
-			ioStreams, _, outBuffer, _ := genericclioptions.NewTestIOStreams()
-
-			printer := &BaseTablePrinter{
-				IOStreams: ioStreams,
-				Columns:   tc.columnDefinitions,
-			}
-
-			resourceStates := &fakeResourceStates{
-				resources: tc.resources,
-			}
-
-			printer.PrintTable(resourceStates, 0)
-
-			assert.Equal(t,
-				strings.TrimSpace(tc.expectedOutput),
-				strings.TrimSpace(outBuffer.String()))
-		})
-	}
-}
-
-type fakeResourceStates struct {
-	resources []Resource
-}
-
-func (r *fakeResourceStates) Resources() []Resource {
-	return r.resources
-}
-
-func (r *fakeResourceStates) Error() error {
-	return nil
-}
-
-type fakeResource struct {
-	resourceStatus *pe.ResourceStatus
-}
-
-func (r *fakeResource) Identifier() object.ObjMetadata {
-	return r.resourceStatus.Identifier
-}
-
-func (r *fakeResource) ResourceStatus() *pe.ResourceStatus {
-	return r.resourceStatus
-}
-
-func (r *fakeResource) SubResources() []Resource {
-	var resources []Resource
-	for _, res := range r.resourceStatus.GeneratedResources {
-		resources = append(resources, &fakeResource{
-			resourceStatus: res,
-		})
-	}
-	return resources
-}
diff --git a/thirdparty/cli-utils/print/table/columndefs_test.go b/thirdparty/cli-utils/print/table/columndefs_test.go
deleted file mode 100644
index c5180ab3..00000000
--- a/thirdparty/cli-utils/print/table/columndefs_test.go
+++ /dev/null
@@ -1,239 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package table
-
-import (
-	"bytes"
-	"fmt"
-	"testing"
-	"time"
-
-	v1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/apimachinery/pkg/runtime/schema"
-	pe "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
-	"sigs.k8s.io/cli-utils/pkg/kstatus/status"
-	"sigs.k8s.io/cli-utils/pkg/object"
-)
-
-func TestColumnDefs(t *testing.T) {
-	testCases := map[string]struct {
-		columnName     string
-		resource       Resource
-		columnWidth    int
-		expectedOutput string
-	}{
-		"namespace": {
-			columnName: "namespace",
-			resource: &fakeResource{
-				resourceStatus: &pe.ResourceStatus{
-					Identifier: object.ObjMetadata{
-						Namespace: "Foo",
-					},
-				},
-			},
-			columnWidth:    10,
-			expectedOutput: "Foo",
-		},
-		"namespace trimmed": {
-			columnName: "namespace",
-			resource: &fakeResource{
-				resourceStatus: &pe.ResourceStatus{
-					Identifier: object.ObjMetadata{
-						Namespace: "ICanHearTheHeartBeatingAsOne",
-					},
-				},
-			},
-			columnWidth:    10,
-			expectedOutput: "ICanHearTh",
-		},
-		"resource": {
-			columnName: "resource",
-			resource: &fakeResource{
-				resourceStatus: &pe.ResourceStatus{
-					Identifier: object.ObjMetadata{
-						Name: "YoLaTengo",
-						GroupKind: schema.GroupKind{
-							Kind: "RoleBinding",
-						},
-					},
-				},
-			},
-			columnWidth:    40,
-			expectedOutput: "RoleBinding/YoLaTengo",
-		},
-		"resource trimmed": {
-			columnName: "resource",
-			resource: &fakeResource{
-				resourceStatus: &pe.ResourceStatus{
-					Identifier: object.ObjMetadata{
-						Name: "SlantedAndEnchanted",
-						GroupKind: schema.GroupKind{
-							Kind: "Pavement",
-						},
-					},
-				},
-			},
-			columnWidth:    25,
-			expectedOutput: "Pavement/SlantedAndEnchan",
-		},
-		"status with color": {
-			columnName: "status",
-			resource: &fakeResource{
-				resourceStatus: &pe.ResourceStatus{
-					Status: status.CurrentStatus,
-				},
-			},
-			columnWidth:    10,
-			expectedOutput: "\x1b[32mCurrent\x1b[0m",
-		},
-		"status trimmed": {
-			columnName: "status",
-			resource: &fakeResource{
-				resourceStatus: &pe.ResourceStatus{
-					Status: status.NotFoundStatus,
-				},
-			},
-			columnWidth:    5,
-			expectedOutput: "NotFo",
-		},
-		"conditions with color": {
-			columnName: "conditions",
-			resource: &fakeResource{
-				resourceStatus: &pe.ResourceStatus{
-					Resource: mustResourceWithConditions([]condition{
-						{
-							Type:   "Ready",
-							Status: v1.ConditionUnknown,
-						},
-					}),
-				},
-			},
-			columnWidth:    10,
-			expectedOutput: "\x1b[33mReady\x1b[0m",
-		},
-		"conditions trimmed": {
-			columnName: "conditions",
-			resource: &fakeResource{
-				resourceStatus: &pe.ResourceStatus{
-					Resource: mustResourceWithConditions([]condition{
-						{
-							Type:   "Ready",
-							Status: v1.ConditionTrue,
-						},
-						{
-							Type:   "Reconciling",
-							Status: v1.ConditionFalse,
-						},
-					}),
-				},
-			},
-			columnWidth:    10,
-			expectedOutput: "\x1b[32mReady\x1b[0m,\x1b[31mReco\x1b[0m",
-		},
-		"age not found": {
-			columnName: "age",
-			resource: &fakeResource{
-				resourceStatus: &pe.ResourceStatus{
-					Resource: &unstructured.Unstructured{},
-				},
-			},
-			columnWidth:    10,
-			expectedOutput: "-",
-		},
-		"age": {
-			columnName: "age",
-			resource: &fakeResource{
-				resourceStatus: &pe.ResourceStatus{
-					Resource: mustResourceWithCreationTimestamp(45 * time.Minute),
-				},
-			},
-			columnWidth:    10,
-			expectedOutput: "45m",
-		},
-		"message without error": {
-			columnName: "message",
-			resource: &fakeResource{
-				resourceStatus: &pe.ResourceStatus{
-					Message: "this is a test",
-				},
-			},
-			columnWidth:    30,
-			expectedOutput: "this is a test",
-		},
-		"message from error": {
-			columnName: "message",
-			resource: &fakeResource{
-				resourceStatus: &pe.ResourceStatus{
-					Message: "this is a test",
-					Error:   fmt.Errorf("something went wrong somewhere"),
-				},
-			},
-			columnWidth:    50,
-			expectedOutput: "something went wrong somewhere",
-		},
-		"message trimmed": {
-			columnName: "message",
-			resource: &fakeResource{
-				resourceStatus: &pe.ResourceStatus{
-					Message: "this is a test",
-				},
-			},
-			columnWidth:    6,
-			expectedOutput: "this i",
-		},
-	}
-
-	for tn, tc := range testCases {
-		t.Run(tn, func(t *testing.T) {
-			columnDef := MustColumn(tc.columnName)
-
-			var buf bytes.Buffer
-			_, err := columnDef.PrintResource(&buf, tc.columnWidth, tc.resource)
-			if err != nil {
-				t.Error(err)
-			}
-
-			if want, got := tc.expectedOutput, buf.String(); want != got {
-				t.Errorf("expected %q, but got %q", want, got)
-			}
-		})
-	}
-}
-
-type condition struct {
-	Type   string
-	Status v1.ConditionStatus
-}
-
-func mustResourceWithConditions(conditions []condition) *unstructured.Unstructured {
-	u := &unstructured.Unstructured{
-		Object: make(map[string]interface{}),
-	}
-	var conditionsSlice []interface{}
-	for _, c := range conditions {
-		cond := make(map[string]interface{})
-		cond["type"] = c.Type
-		cond["status"] = string(c.Status)
-		conditionsSlice = append(conditionsSlice, cond)
-	}
-	err := unstructured.SetNestedSlice(u.Object, conditionsSlice,
-		"status", "conditions")
-	if err != nil {
-		panic(err)
-	}
-	return u
-}
-
-func mustResourceWithCreationTimestamp(age time.Duration) *unstructured.Unstructured {
-	u := &unstructured.Unstructured{
-		Object: make(map[string]interface{}),
-	}
-	creationTime := time.Now().Add(-age)
-	u.SetCreationTimestamp(metav1.Time{
-		Time: creationTime,
-	})
-	return u
-}
diff --git a/thirdparty/cli-utils/printers/events/formatter_test.go b/thirdparty/cli-utils/printers/events/formatter_test.go
deleted file mode 100644
index 9325c126..00000000
--- a/thirdparty/cli-utils/printers/events/formatter_test.go
+++ /dev/null
@@ -1,240 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package events
-
-import (
-	"fmt"
-	"strings"
-	"testing"
-
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/print/list"
-	"github.com/stretchr/testify/assert"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/apimachinery/pkg/runtime/schema"
-	"k8s.io/cli-runtime/pkg/genericclioptions"
-	"sigs.k8s.io/cli-utils/pkg/apply/event"
-	"sigs.k8s.io/cli-utils/pkg/common"
-	pollevent "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
-	"sigs.k8s.io/cli-utils/pkg/kstatus/status"
-	"sigs.k8s.io/cli-utils/pkg/object"
-)
-
-func TestFormatter_FormatApplyEvent(t *testing.T) {
-	testCases := map[string]struct {
-		dryRunStrategy  common.DryRunStrategy
-		event           event.ApplyEvent
-		applyStats      *list.ApplyStats
-		statusCollector list.Collector
-		expected        string
-	}{
-		"resource created without no dryrun": {
-			dryRunStrategy: common.DryRunNone,
-			event: event.ApplyEvent{
-				Operation:  event.Created,
-				Identifier: createIdentifier("apps", "Deployment", "default", "my-dep"),
-			},
-			expected: "deployment.apps/my-dep created",
-		},
-		"resource updated with client dryrun": {
-			dryRunStrategy: common.DryRunClient,
-			event: event.ApplyEvent{
-				Operation:  event.Configured,
-				Identifier: createIdentifier("apps", "Deployment", "", "my-dep"),
-			},
-			expected: "deployment.apps/my-dep configured (dry-run)",
-		},
-		"resource updated with server dryrun": {
-			dryRunStrategy: common.DryRunServer,
-			event: event.ApplyEvent{
-				Operation:  event.Configured,
-				Identifier: createIdentifier("batch", "CronJob", "foo", "my-cron"),
-			},
-			expected: "cronjob.batch/my-cron configured (dry-run-server)",
-		},
-		"apply event with error should display the error": {
-			dryRunStrategy: common.DryRunServer,
-			event: event.ApplyEvent{
-				Identifier: createIdentifier("apps", "Deployment", "", "my-dep"),
-				Error:      fmt.Errorf("this is a test error"),
-			},
-			expected: "deployment.apps/my-dep apply failed: this is a test error (dry-run-server)",
-		},
-	}
-
-	for tn, tc := range testCases {
-		t.Run(tn, func(t *testing.T) {
-			ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled
-			formatter := NewFormatter(ioStreams, tc.dryRunStrategy)
-			err := formatter.FormatApplyEvent(tc.event)
-			assert.NoError(t, err)
-
-			assert.Equal(t, strings.TrimSpace(tc.expected), strings.TrimSpace(out.String()))
-		})
-	}
-}
-
-func TestFormatter_FormatStatusEvent(t *testing.T) {
-	testCases := map[string]struct {
-		dryRunStrategy  common.DryRunStrategy
-		event           event.StatusEvent
-		statusCollector list.Collector
-		expected        string
-	}{
-		"resource update with Current status": {
-			dryRunStrategy: common.DryRunNone,
-			event: event.StatusEvent{
-				Identifier: object.ObjMetadata{
-					GroupKind: schema.GroupKind{
-						Group: "apps",
-						Kind:  "Deployment",
-					},
-					Namespace: "foo",
-					Name:      "bar",
-				},
-				PollResourceInfo: &pollevent.ResourceStatus{
-					Identifier: object.ObjMetadata{
-						GroupKind: schema.GroupKind{
-							Group: "apps",
-							Kind:  "Deployment",
-						},
-						Namespace: "foo",
-						Name:      "bar",
-					},
-					Status:  status.CurrentStatus,
-					Message: "Resource is Current",
-				},
-			},
-			expected: "deployment.apps/bar is Current: Resource is Current",
-		},
-	}
-
-	for tn, tc := range testCases {
-		t.Run(tn, func(t *testing.T) {
-			ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled
-			formatter := NewFormatter(ioStreams, tc.dryRunStrategy)
-			err := formatter.FormatStatusEvent(tc.event)
-			assert.NoError(t, err)
-
-			assert.Equal(t, strings.TrimSpace(tc.expected), strings.TrimSpace(out.String()))
-		})
-	}
-}
-
-func TestFormatter_FormatPruneEvent(t *testing.T) {
-	testCases := map[string]struct {
-		dryRunStrategy common.DryRunStrategy
-		event          event.PruneEvent
-		pruneStats     *list.PruneStats
-		expected       string
-	}{
-		"resource pruned without no dryrun": {
-			dryRunStrategy: common.DryRunNone,
-			event: event.PruneEvent{
-				Operation:  event.Pruned,
-				Identifier: createIdentifier("apps", "Deployment", "default", "my-dep"),
-			},
-			expected: "deployment.apps/my-dep pruned",
-		},
-		"resource skipped with client dryrun": {
-			dryRunStrategy: common.DryRunClient,
-			event: event.PruneEvent{
-				Operation:  event.PruneSkipped,
-				Identifier: createIdentifier("apps", "Deployment", "", "my-dep"),
-			},
-			expected: "deployment.apps/my-dep prune skipped (dry-run)",
-		},
-		"resource with prune error": {
-			dryRunStrategy: common.DryRunNone,
-			event: event.PruneEvent{
-				Identifier: createIdentifier("apps", "Deployment", "", "my-dep"),
-				Error:      fmt.Errorf("this is a test"),
-			},
-			expected: "deployment.apps/my-dep prune failed: this is a test",
-		},
-	}
-
-	for tn, tc := range testCases {
-		t.Run(tn, func(t *testing.T) {
-			ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled
-			formatter := NewFormatter(ioStreams, tc.dryRunStrategy)
-			err := formatter.FormatPruneEvent(tc.event)
-			assert.NoError(t, err)
-
-			assert.Equal(t, strings.TrimSpace(tc.expected), strings.TrimSpace(out.String()))
-		})
-	}
-}
-
-func TestFormatter_FormatDeleteEvent(t *testing.T) {
-	testCases := map[string]struct {
-		dryRunStrategy  common.DryRunStrategy
-		event           event.DeleteEvent
-		deleteStats     *list.DeleteStats
-		statusCollector list.Collector
-		expected        string
-	}{
-		"resource deleted without no dryrun": {
-			dryRunStrategy: common.DryRunNone,
-			event: event.DeleteEvent{
-				Operation:  event.Deleted,
-				Identifier: createIdentifier("apps", "Deployment", "default", "my-dep"),
-				Object:     createObject("apps", "Deployment", "default", "my-dep"),
-			},
-			expected: "deployment.apps/my-dep deleted",
-		},
-		"resource skipped with client dryrun": {
-			dryRunStrategy: common.DryRunClient,
-			event: event.DeleteEvent{
-				Operation:  event.DeleteSkipped,
-				Identifier: createIdentifier("apps", "Deployment", "", "my-dep"),
-				Object:     createObject("apps", "Deployment", "", "my-dep"),
-			},
-			expected: "deployment.apps/my-dep delete skipped (dry-run)",
-		},
-		"resource with delete error": {
-			dryRunStrategy: common.DryRunServer,
-			event: event.DeleteEvent{
-				Object:     createObject("apps", "Deployment", "", "my-dep"),
-				Identifier: createIdentifier("apps", "Deployment", "", "my-dep"),
-				Error:      fmt.Errorf("this is a test"),
-			},
-			expected: "deployment.apps/my-dep deletion failed: this is a test (dry-run-server)",
-		},
-	}
-
-	for tn, tc := range testCases {
-		t.Run(tn, func(t *testing.T) {
-			ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled
-			formatter := NewFormatter(ioStreams, tc.dryRunStrategy)
-			err := formatter.FormatDeleteEvent(tc.event)
-			assert.NoError(t, err)
-
-			assert.Equal(t, strings.TrimSpace(tc.expected), strings.TrimSpace(out.String()))
-		})
-	}
-}
-
-func createObject(group, kind, namespace, name string) *unstructured.Unstructured {
-	return &unstructured.Unstructured{
-		Object: map[string]interface{}{
-			"apiVersion": fmt.Sprintf("%s/v1", group),
-			"kind":       kind,
-			"metadata": map[string]interface{}{
-				"name":      name,
-				"namespace": namespace,
-			},
-		},
-	}
-}
-
-func createIdentifier(group, kind, namespace, name string) object.ObjMetadata {
-	return object.ObjMetadata{
-		Namespace: namespace,
-		Name:      name,
-		GroupKind: schema.GroupKind{
-			Group: group,
-			Kind:  kind,
-		},
-	}
-}
diff --git a/thirdparty/cli-utils/printers/json/formatter_test.go b/thirdparty/cli-utils/printers/json/formatter_test.go
deleted file mode 100644
index d4a01125..00000000
--- a/thirdparty/cli-utils/printers/json/formatter_test.go
+++ /dev/null
@@ -1,309 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package json
-
-import (
-	"encoding/json"
-	"strings"
-	"testing"
-
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/print/list"
-	"github.com/stretchr/testify/assert"
-	"k8s.io/apimachinery/pkg/runtime/schema"
-	"k8s.io/cli-runtime/pkg/genericclioptions"
-	"sigs.k8s.io/cli-utils/pkg/apply/event"
-	"sigs.k8s.io/cli-utils/pkg/common"
-	pollevent "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
-	"sigs.k8s.io/cli-utils/pkg/kstatus/status"
-	"sigs.k8s.io/cli-utils/pkg/object"
-)
-
-func TestFormatter_FormatApplyEvent(t *testing.T) {
-	testCases := map[string]struct {
-		dryRunStrategy common.DryRunStrategy
-		event          event.ApplyEvent
-		expected       []map[string]interface{}
-	}{
-		"resource created without dryrun": {
-			dryRunStrategy: common.DryRunNone,
-			event: event.ApplyEvent{
-				Operation:  event.Created,
-				Identifier: createIdentifier("apps", "Deployment", "default", "my-dep"),
-			},
-			expected: []map[string]interface{}{
-				{
-					"eventType": "resourceApplied",
-					"group":     "apps",
-					"kind":      "Deployment",
-					"name":      "my-dep",
-					"namespace": "default",
-					"operation": "Created",
-					"timestamp": "",
-					"type":      "apply",
-				},
-			},
-		},
-		"resource updated with client dryrun": {
-			dryRunStrategy: common.DryRunClient,
-			event: event.ApplyEvent{
-				Operation:  event.Configured,
-				Identifier: createIdentifier("apps", "Deployment", "", "my-dep"),
-			},
-			expected: []map[string]interface{}{
-				{
-					"eventType": "resourceApplied",
-					"group":     "apps",
-					"kind":      "Deployment",
-					"name":      "my-dep",
-					"namespace": "",
-					"operation": "Configured",
-					"timestamp": "",
-					"type":      "apply",
-				},
-			},
-		},
-		"resource updated with server dryrun": {
-			dryRunStrategy: common.DryRunServer,
-			event: event.ApplyEvent{
-				Operation:  event.Configured,
-				Identifier: createIdentifier("batch", "CronJob", "foo", "my-cron"),
-			},
-			expected: []map[string]interface{}{
-				{
-					"eventType": "resourceApplied",
-					"group":     "batch",
-					"kind":      "CronJob",
-					"name":      "my-cron",
-					"namespace": "foo",
-					"operation": "Configured",
-					"timestamp": "",
-					"type":      "apply",
-				},
-			},
-		},
-	}
-
-	for tn, tc := range testCases {
-		t.Run(tn, func(t *testing.T) {
-			ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled
-			formatter := NewFormatter(ioStreams, tc.dryRunStrategy)
-			err := formatter.FormatApplyEvent(tc.event)
-			assert.NoError(t, err)
-
-			objects := strings.Split(strings.TrimSpace(out.String()), "\n")
-
-			if !assert.Equal(t, len(tc.expected), len(objects)) {
-				t.FailNow()
-			}
-			for i := range tc.expected {
-				assertOutput(t, tc.expected[i], objects[i])
-			}
-		})
-	}
-}
-
-func TestFormatter_FormatStatusEvent(t *testing.T) {
-	testCases := map[string]struct {
-		previewStrategy common.DryRunStrategy
-		event           event.StatusEvent
-		expected        map[string]interface{}
-	}{
-		"resource update with Current status": {
-			previewStrategy: common.DryRunNone,
-			event: event.StatusEvent{
-				Identifier: object.ObjMetadata{
-					GroupKind: schema.GroupKind{
-						Group: "apps",
-						Kind:  "Deployment",
-					},
-					Namespace: "foo",
-					Name:      "bar",
-				},
-				PollResourceInfo: &pollevent.ResourceStatus{
-					Identifier: object.ObjMetadata{
-						GroupKind: schema.GroupKind{
-							Group: "apps",
-							Kind:  "Deployment",
-						},
-						Namespace: "foo",
-						Name:      "bar",
-					},
-					Status:  status.CurrentStatus,
-					Message: "Resource is Current",
-				},
-			},
-			expected: map[string]interface{}{
-				"eventType": "resourceStatus",
-				"group":     "apps",
-				"kind":      "Deployment",
-				"message":   "Resource is Current",
-				"name":      "bar",
-				"namespace": "foo",
-				"status":    "Current",
-				"timestamp": "",
-				"type":      "status",
-			},
-		},
-	}
-
-	for tn, tc := range testCases {
-		t.Run(tn, func(t *testing.T) {
-			ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled
-			formatter := NewFormatter(ioStreams, tc.previewStrategy)
-			err := formatter.FormatStatusEvent(tc.event)
-			assert.NoError(t, err)
-
-			assertOutput(t, tc.expected, out.String())
-		})
-	}
-}
-
-func TestFormatter_FormatPruneEvent(t *testing.T) {
-	testCases := map[string]struct {
-		dryRunStrategy common.DryRunStrategy
-		event          event.PruneEvent
-		pruneStats     *list.PruneStats
-		expected       map[string]interface{}
-	}{
-		"resource pruned without dryrun": {
-			dryRunStrategy: common.DryRunNone,
-			event: event.PruneEvent{
-				Operation:  event.Pruned,
-				Identifier: createIdentifier("apps", "Deployment", "default", "my-dep"),
-			},
-			expected: map[string]interface{}{
-				"eventType": "resourcePruned",
-				"group":     "apps",
-				"kind":      "Deployment",
-				"name":      "my-dep",
-				"namespace": "default",
-				"operation": "Pruned",
-				"timestamp": "",
-				"type":      "prune",
-			},
-		},
-		"resource skipped with client dryrun": {
-			dryRunStrategy: common.DryRunClient,
-			event: event.PruneEvent{
-				Operation:  event.PruneSkipped,
-				Identifier: createIdentifier("apps", "Deployment", "", "my-dep"),
-			},
-			expected: map[string]interface{}{
-				"eventType": "resourcePruned",
-				"group":     "apps",
-				"kind":      "Deployment",
-				"name":      "my-dep",
-				"namespace": "",
-				"operation": "PruneSkipped",
-				"timestamp": "",
-				"type":      "prune",
-			},
-		},
-	}
-
-	for tn, tc := range testCases {
-		t.Run(tn, func(t *testing.T) {
-			ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled
-			formatter := NewFormatter(ioStreams, tc.dryRunStrategy)
-			err := formatter.FormatPruneEvent(tc.event)
-			assert.NoError(t, err)
-
-			assertOutput(t, tc.expected, out.String())
-		})
-	}
-}
-
-func TestFormatter_FormatDeleteEvent(t *testing.T) {
-	testCases := map[string]struct {
-		dryRunStrategy  common.DryRunStrategy
-		event           event.DeleteEvent
-		deleteStats     *list.DeleteStats
-		statusCollector list.Collector
-		expected        map[string]interface{}
-	}{
-		"resource deleted without no dryrun": {
-			dryRunStrategy: common.DryRunNone,
-			event: event.DeleteEvent{
-				Operation:  event.Deleted,
-				Identifier: createIdentifier("apps", "Deployment", "default", "my-dep"),
-			},
-			expected: map[string]interface{}{
-				"eventType": "resourceDeleted",
-				"group":     "apps",
-				"kind":      "Deployment",
-				"name":      "my-dep",
-				"namespace": "default",
-				"operation": "Deleted",
-				"timestamp": "",
-				"type":      "delete",
-			},
-		},
-		"resource skipped with client dryrun": {
-			dryRunStrategy: common.DryRunClient,
-			event: event.DeleteEvent{
-				Operation:  event.DeleteSkipped,
-				Identifier: createIdentifier("apps", "Deployment", "", "my-dep"),
-			},
-			expected: map[string]interface{}{
-				"eventType": "resourceDeleted",
-				"group":     "apps",
-				"kind":      "Deployment",
-				"name":      "my-dep",
-				"namespace": "",
-				"operation": "DeleteSkipped",
-				"timestamp": "",
-				"type":      "delete",
-			},
-		},
-	}
-
-	for tn, tc := range testCases {
-		t.Run(tn, func(t *testing.T) {
-			ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled
-			formatter := NewFormatter(ioStreams, tc.dryRunStrategy)
-			err := formatter.FormatDeleteEvent(tc.event)
-			assert.NoError(t, err)
-
-			assertOutput(t, tc.expected, out.String())
-		})
-	}
-}
-
-// nolint:unparam
-func assertOutput(t *testing.T, expectedMap map[string]interface{}, actual string) bool {
-	var m map[string]interface{}
-	err := json.Unmarshal([]byte(actual), &m)
-	if !assert.NoError(t, err) {
-		return false
-	}
-
-	if _, found := expectedMap["timestamp"]; found {
-		if _, ok := m["timestamp"]; ok {
-			delete(expectedMap, "timestamp")
-			delete(m, "timestamp")
-		} else {
-			t.Error("expected to find key 'timestamp', but didn't")
-			return false
-		}
-	}
-
-	for key, val := range m {
-		if floatVal, ok := val.(float64); ok {
-			m[key] = int(floatVal)
-		}
-	}
-
-	return assert.Equal(t, expectedMap, m)
-}
-
-func createIdentifier(group, kind, namespace, name string) object.ObjMetadata {
-	return object.ObjMetadata{
-		Namespace: namespace,
-		Name:      name,
-		GroupKind: schema.GroupKind{
-			Group: group,
-			Kind:  kind,
-		},
-	}
-}
diff --git a/thirdparty/cli-utils/printers/table/collector_test.go b/thirdparty/cli-utils/printers/table/collector_test.go
deleted file mode 100644
index 2a561821..00000000
--- a/thirdparty/cli-utils/printers/table/collector_test.go
+++ /dev/null
@@ -1,192 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package table
-
-import (
-	"testing"
-
-	"gotest.tools/assert"
-	"k8s.io/apimachinery/pkg/runtime/schema"
-	"sigs.k8s.io/cli-utils/pkg/apply/event"
-	pe "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
-	"sigs.k8s.io/cli-utils/pkg/object"
-)
-
-var (
-	depID = object.ObjMetadata{
-		GroupKind: schema.GroupKind{
-			Group: "apps",
-			Kind:  "Deployment",
-		},
-		Name:      "Foo",
-		Namespace: "Bar",
-	}
-	customID = object.ObjMetadata{
-		GroupKind: schema.GroupKind{
-			Group: "custom.io",
-			Kind:  "Custom",
-		},
-		Name: "Custom",
-	}
-)
-
-const testMessage = "test message for ResourceStatus"
-
-func TestResourceStateCollector_New(t *testing.T) {
-	testCases := map[string]struct {
-		resourceGroups []event.ActionGroup
-		resourceInfos  map[object.ObjMetadata]*ResourceInfo
-	}{
-		"no resources": {
-			resourceGroups: []event.ActionGroup{},
-			resourceInfos:  map[object.ObjMetadata]*ResourceInfo{},
-		},
-		"several resources for apply": {
-			resourceGroups: []event.ActionGroup{
-				{
-					Action: event.ApplyAction,
-					Identifiers: []object.ObjMetadata{
-						depID, customID,
-					},
-				},
-			},
-			resourceInfos: map[object.ObjMetadata]*ResourceInfo{
-				depID: {
-					ResourceAction: event.ApplyAction,
-				},
-				customID: {
-					ResourceAction: event.ApplyAction,
-				},
-			},
-		},
-		"several resources for prune": {
-			resourceGroups: []event.ActionGroup{
-				{
-					Action: event.ApplyAction,
-					Identifiers: []object.ObjMetadata{
-						customID,
-					},
-				},
-				{
-					Action: event.PruneAction,
-					Identifiers: []object.ObjMetadata{
-						depID,
-					},
-				},
-			},
-			resourceInfos: map[object.ObjMetadata]*ResourceInfo{
-				depID: {
-					ResourceAction: event.PruneAction,
-				},
-				customID: {
-					ResourceAction: event.ApplyAction,
-				},
-			},
-		},
-	}
-
-	for tn, tc := range testCases {
-		t.Run(tn, func(t *testing.T) {
-			rsc := newResourceStateCollector(tc.resourceGroups)
-
-			assert.Equal(t, len(tc.resourceInfos), len(rsc.resourceInfos))
-			for expID, expRi := range tc.resourceInfos {
-				actRi, found := rsc.resourceInfos[expID]
-				if !found {
-					t.Errorf("expected to find id %v, but didn't", expID)
-				}
-				assert.Equal(t, expRi.ResourceAction, actRi.ResourceAction)
-			}
-		})
-	}
-}
-
-func TestResourceStateCollector_ProcessStatusEvent(t *testing.T) {
-	testCases := map[string]struct {
-		resourceGroups []event.ActionGroup
-		statusEvent    event.StatusEvent
-	}{
-		"nil StatusEvent.Resource does not crash": {
-			resourceGroups: []event.ActionGroup{},
-			statusEvent: event.StatusEvent{
-				Resource: nil,
-			},
-		},
-		"unfound Resource identifier does not crash": {
-			resourceGroups: []event.ActionGroup{
-				{
-					Action:      event.ApplyAction,
-					Identifiers: []object.ObjMetadata{depID},
-				},
-			},
-			statusEvent: event.StatusEvent{
-				PollResourceInfo: &pe.ResourceStatus{
-					Identifier: customID, // Does not match identifier in resourceGroups
-				},
-			},
-		},
-		"basic status event for applying two resources updates resourceStatus": {
-			resourceGroups: []event.ActionGroup{
-				{
-					Action: event.ApplyAction,
-					Identifiers: []object.ObjMetadata{
-						depID, customID,
-					},
-				},
-			},
-			statusEvent: event.StatusEvent{
-				PollResourceInfo: &pe.ResourceStatus{
-					Identifier: depID,
-					Message:    testMessage,
-				},
-			},
-		},
-		"several resources for prune": {
-			resourceGroups: []event.ActionGroup{
-				{
-					Action: event.ApplyAction,
-					Identifiers: []object.ObjMetadata{
-						customID,
-					},
-				},
-				{
-					Action: event.PruneAction,
-					Identifiers: []object.ObjMetadata{
-						depID,
-					},
-				},
-			},
-			statusEvent: event.StatusEvent{
-				PollResourceInfo: &pe.ResourceStatus{
-					Identifier: depID,
-					Message:    testMessage,
-				},
-			},
-		},
-	}
-
-	for tn, tc := range testCases {
-		t.Run(tn, func(t *testing.T) {
-			rsc := newResourceStateCollector(tc.resourceGroups)
-			rsc.processStatusEvent(tc.statusEvent)
-			id, found := getID(tc.statusEvent)
-			if found {
-				resourceInfo, found := rsc.resourceInfos[id]
-				if found {
-					// Validate the ResourceStatus was set from StatusEvent
-					if resourceInfo.resourceStatus != tc.statusEvent.PollResourceInfo {
-						t.Errorf("status event not processed for %s", id)
-					}
-				}
-			}
-		})
-	}
-}
-
-func getID(e event.StatusEvent) (object.ObjMetadata, bool) {
-	if e.Resource == nil {
-		return object.ObjMetadata{}, false
-	}
-	return e.Identifier, true
-}
diff --git a/thirdparty/cli-utils/printers/table/printer_test.go b/thirdparty/cli-utils/printers/table/printer_test.go
deleted file mode 100644
index 9fc34237..00000000
--- a/thirdparty/cli-utils/printers/table/printer_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package table
-
-import (
-	"bytes"
-	"testing"
-
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/print/table"
-	"sigs.k8s.io/cli-utils/pkg/apply/event"
-)
-
-var (
-	createdOpResult = event.Created
-	prunedOpResult  = event.Pruned
-)
-
-func TestActionColumnDef(t *testing.T) {
-	testCases := map[string]struct {
-		resource       table.Resource
-		columnWidth    int
-		expectedOutput string
-	}{
-		"unexpected implementation of Resource interface": {
-			resource:       &SubResourceInfo{},
-			columnWidth:    15,
-			expectedOutput: "",
-		},
-		"neither applied nor pruned": {
-			resource:       &ResourceInfo{},
-			columnWidth:    15,
-			expectedOutput: "",
-		},
-		"applied": {
-			resource: &ResourceInfo{
-				ResourceAction: event.ApplyAction,
-				ApplyOpResult:  createdOpResult,
-			},
-			columnWidth:    15,
-			expectedOutput: "Created",
-		},
-		"pruned": {
-			resource: &ResourceInfo{
-				ResourceAction: event.PruneAction,
-				PruneOpResult:  prunedOpResult,
-			},
-			columnWidth:    15,
-			expectedOutput: "Pruned",
-		},
-		"trimmed output": {
-			resource: &ResourceInfo{
-				ResourceAction: event.ApplyAction,
-				ApplyOpResult:  createdOpResult,
-			},
-			columnWidth:    5,
-			expectedOutput: "Creat",
-		},
-	}
-
-	for tn, tc := range testCases {
-		t.Run(tn, func(t *testing.T) {
-			var buf bytes.Buffer
-			_, err := actionColumnDef.PrintResource(&buf, tc.columnWidth, tc.resource)
-			if err != nil {
-				t.Error(err)
-			}
-
-			if want, got := tc.expectedOutput, buf.String(); want != got {
-				t.Errorf("expected %q, but got %q", want, got)
-			}
-		})
-	}
-}