Repository: GoogleContainerTools/kpt
Pull request #3482 (2022-08-22 21:45:48)

Docs: update document of the status command
Add user guides for new features of the status command, this includes the inv-type flag, and filtering options on namespaces, inventory names, and status


Patch:
diff --git a/commands/livecmd.go b/commands/livecmd.go
index 69e12f71..2c43cc2e 100644
--- a/commands/livecmd.go
+++ b/commands/livecmd.go
@@ -23,8 +23,9 @@ import (
 	"github.com/GoogleContainerTools/kpt/internal/cmdinstallrg"
 	"github.com/GoogleContainerTools/kpt/internal/cmdliveinit"
 	"github.com/GoogleContainerTools/kpt/internal/cmdmigrate"
+	"github.com/GoogleContainerTools/kpt/internal/cmdstatus"
 	"github.com/GoogleContainerTools/kpt/internal/docs/generated/livedocs"
-	"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/status"
+	"github.com/GoogleContainerTools/kpt/pkg/live"
 	"github.com/spf13/cobra"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
 	"k8s.io/klog/v2"
@@ -45,13 +46,15 @@ func GetLiveCommand(ctx context.Context, _, version string) *cobra.Command {
 	}
 
 	f := newFactory(liveCmd, version)
+	invFactory := live.NewClusterClientFactory()
+	loader := cmdstatus.NewRGInventoryLoader(ctx, f)
 
 	// Init command which updates a Kptfile for the ResourceGroup inventory object.
 	klog.V(2).Infoln("init command updates Kptfile for ResourceGroup inventory")
 	initCmd := cmdliveinit.NewCommand(ctx, f, ioStreams)
 	applyCmd := cmdapply.NewCommand(ctx, f, ioStreams, false)
 	destroyCmd := cmddestroy.NewCommand(ctx, f, ioStreams)
-	statusCmd := status.NewCommand(ctx, f)
+	statusCmd := cmdstatus.NewCommand(ctx, f, invFactory, loader)
 	installRGCmd := cmdinstallrg.NewCommand(ctx, f, ioStreams)
 	liveCmd.AddCommand(initCmd, applyCmd, destroyCmd, statusCmd, installRGCmd)
 
diff --git a/go.mod b/go.mod
index c47c5136..9a8afa4f 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/GoogleContainerTools/kpt
 go 1.18
 
 require (
-	github.com/GoogleContainerTools/kpt/porch/api v0.0.0-20220707151439-cb4c4e496c92
+	github.com/GoogleContainerTools/kpt/porch/api v0.0.0-20220821193112-4792e5fa18ee
 	github.com/bytecodealliance/wasmtime-go v0.39.0
 	github.com/cpuguy83/go-md2man/v2 v2.0.2
 	github.com/go-errors/errors v1.4.2
@@ -29,7 +29,7 @@ require (
 	k8s.io/component-base v0.24.0
 	k8s.io/klog/v2 v2.60.1
 	k8s.io/kubectl v0.24.0
-	sigs.k8s.io/cli-utils v0.32.1-0.20220804181346-99ba319afc0b
+	sigs.k8s.io/cli-utils v0.32.1-0.20220822160143-c10935a7e36b
 	sigs.k8s.io/controller-runtime v0.11.1
 	sigs.k8s.io/kustomize/api v0.12.1
 	sigs.k8s.io/kustomize/kyaml v0.13.9
diff --git a/go.sum b/go.sum
index 00189ddf..b000733c 100644
--- a/go.sum
+++ b/go.sum
@@ -85,8 +85,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
 github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
-github.com/GoogleContainerTools/kpt/porch/api v0.0.0-20220707151439-cb4c4e496c92 h1:0h6lonywmlgUWEc62Nvjt3IqkzudXDbplILJc7wlayI=
-github.com/GoogleContainerTools/kpt/porch/api v0.0.0-20220707151439-cb4c4e496c92/go.mod h1:51Vk7QZ+XUzHCvQBi7t9tiWqTXvy6T13cv/inUXJJ0s=
+github.com/GoogleContainerTools/kpt/porch/api v0.0.0-20220821193112-4792e5fa18ee h1:FSW33xqr8SeoZWQ0MgKtjosMmaUBoHa/z0Tlp493528=
+github.com/GoogleContainerTools/kpt/porch/api v0.0.0-20220821193112-4792e5fa18ee/go.mod h1:VUrDv94BkVOEE/MEwwqVr7C1K0sWxF/QsoIZvpQacnA=
 github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU=
 github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
 github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
@@ -1641,6 +1641,8 @@ rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw=
 sigs.k8s.io/cli-utils v0.32.1-0.20220804181346-99ba319afc0b h1:Z3TNRZdGPgm0ZA1GUIDtDD0ItA9/jPfp6eceYWw/fvY=
 sigs.k8s.io/cli-utils v0.32.1-0.20220804181346-99ba319afc0b/go.mod h1:g/zB9hJ5eUN7zIEBIxrO0CwhXU4YISJ+BkLJzvWwlEs=
+sigs.k8s.io/cli-utils v0.32.1-0.20220822160143-c10935a7e36b h1:JTNhaBfNDGoVRfV3rpYxWfn70WO6pFq+1ZUDlRhZTNU=
+sigs.k8s.io/cli-utils v0.32.1-0.20220822160143-c10935a7e36b/go.mod h1:g/zB9hJ5eUN7zIEBIxrO0CwhXU4YISJ+BkLJzvWwlEs=
 sigs.k8s.io/controller-runtime v0.11.1 h1:7YIHT2QnHJArj/dk9aUkYhfqfK5cIxPOX5gPECfdZLU=
 sigs.k8s.io/controller-runtime v0.11.1/go.mod h1:KKwLiTooNGu+JmLZGn9Sl3Gjmfj66eMbCQznLP5zcqA=
 sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y=
diff --git a/internal/alpha/printers/table/printer.go b/internal/alpha/printers/table/printer.go
index 29ee4c52..5798bdd3 100644
--- a/internal/alpha/printers/table/printer.go
+++ b/internal/alpha/printers/table/printer.go
@@ -6,11 +6,14 @@ package table
 import (
 	"fmt"
 	"io"
+	"strings"
 	"time"
 
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
 	"sigs.k8s.io/cli-utils/pkg/apply/event"
 	"sigs.k8s.io/cli-utils/pkg/common"
+	pollingevent "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
 	printcommon "sigs.k8s.io/cli-utils/pkg/print/common"
 	"sigs.k8s.io/cli-utils/pkg/print/table"
 )
@@ -72,87 +75,23 @@ func (t *Printer) Print(ch <-chan event.Event, _ common.DryRunStrategy, _ bool)
 }
 
 // columns defines the columns we want to print
-//TODO: We should have the number of columns and their widths be
+// 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.ApplyStatus != event.ApplyFailed {
-					text = resInfo.ApplyStatus.String()
-				}
-			case event.PruneAction:
-				if resInfo.PruneStatus != event.PruneFailed {
-					text = resInfo.PruneStatus.String()
-				}
-			}
-
-			if len(text) > width {
-				text = text[:width]
-			}
-			_, err := fmt.Fprint(w, text)
-			return len(text), err
-		},
-	}
-
-	reconciledColumnDef = table.ColumnDef{
-		// Column containing the reconciliation status.
-		ColumnName:   "reconciled",
-		ColumnHeader: "RECONCILED",
-		ColumnWidth:  10,
-		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.WaitAction:
-				text = resInfo.WaitStatus.String()
-			}
-
-			if len(text) > width {
-				text = text[:width]
-			}
-			_, err := fmt.Fprint(w, text)
-			return len(text), err
-		},
+	unifiedStatusColumnDef = table.ColumnDef{
+		// Column containing the overall progress.
+		ColumnName:        "progress",
+		ColumnHeader:      "PROGRESS",
+		ColumnWidth:       80,
+		PrintResourceFunc: printProgress,
 	}
 
-	columns = []table.ColumnDefinition{
+	alphaColumns = []table.ColumnDefinition{
 		table.MustColumn("namespace"),
 		table.MustColumn("resource"),
-		actionColumnDef,
-		table.MustColumn("status"),
-		reconciledColumnDef,
-		table.MustColumn("conditions"),
-		table.MustColumn("age"),
-		table.MustColumn("message"),
+
+		// We are trying out a "single column" model here
+		unifiedStatusColumnDef,
 	}
 )
 
@@ -163,7 +102,7 @@ func (t *Printer) runPrintLoop(coll *resourceStateCollector, stop chan struct{})
 
 	baseTablePrinter := table.BaseTablePrinter{
 		IOStreams: t.IOStreams,
-		Columns:   columns,
+		Columns:   alphaColumns,
 	}
 
 	linesPrinted := baseTablePrinter.PrintTable(coll.LatestState(), 0)
@@ -187,3 +126,147 @@ func (t *Printer) runPrintLoop(coll *resourceStateCollector, stop chan struct{})
 	}()
 	return finished
 }
+
+func printProgress(w io.Writer, width int, r table.Resource) (int, error) {
+	var resInfo *resourceInfo
+	switch res := r.(type) {
+	case *resourceInfo:
+		resInfo = res
+	default:
+		return 0, fmt.Errorf("unexpected type %T", r)
+	}
+
+	text, details, err := getProgress(resInfo)
+	if err != nil {
+		return 0, err
+	}
+
+	if details != "" {
+		text += " " + details
+	}
+
+	if len(text) > width {
+		text = text[:width]
+	}
+	n, err := fmt.Fprint(w, text)
+	if err != nil {
+		return n, err
+	}
+	return len(text), err
+}
+
+func getProgress(resInfo *resourceInfo) (string, string, error) {
+	printStatus := false
+	var text string
+	var details string
+	switch resInfo.ResourceAction {
+	case event.ApplyAction:
+		switch resInfo.ApplyStatus {
+		case event.ApplyPending:
+			text = "PendingApply"
+		case event.ApplySuccessful:
+			text = "Applied"
+			printStatus = true
+		case event.ApplySkipped:
+			text = "SkippedApply"
+		case event.ApplyFailed:
+			text = "ApplyFailed"
+
+		default:
+			return "", "", fmt.Errorf("unknown ApplyStatus: %v", resInfo.ApplyStatus)
+		}
+
+	case event.PruneAction:
+		switch resInfo.PruneStatus {
+		case event.PrunePending:
+			text = "PendingDeletion"
+		case event.PruneSuccessful:
+			text = "Deleted"
+		case event.PruneSkipped:
+			text = "DeletionSkipped"
+		case event.PruneFailed:
+			text = "DeletionFailed"
+
+		default:
+			return "", "", fmt.Errorf("unknown PruneStatus: %v", resInfo.PruneStatus)
+		}
+
+	default:
+		return "", "", fmt.Errorf("unknown ResourceAction %v", resInfo.ResourceAction)
+	}
+
+	rs := resInfo.ResourceStatus()
+	if printStatus && rs != nil {
+		s := rs.Status.String()
+
+		color, setColor := printcommon.ColorForStatus(rs.Status)
+		if setColor {
+			s = printcommon.SprintfWithColor(color, s)
+		}
+
+		text = s
+
+		if resInfo.ResourceAction == event.WaitAction {
+			text += " WaitStatus:" + resInfo.WaitStatus.String()
+		}
+
+		conditionStrings := getConditions(rs)
+		text += " Conditions:" + strings.Join(conditionStrings, ",")
+
+		var message string
+		if rs.Error != nil {
+			message = rs.Error.Error()
+		} else {
+			message = rs.Message
+		}
+		// TODO: Map from status?
+		if message == "Resource is Ready" || message == "Resource is current" {
+			message = ""
+		}
+		if message != "" {
+			details += "Message:" + message
+		}
+	}
+
+	return text, details, nil
+}
+
+func getConditions(rs *pollingevent.ResourceStatus) []string {
+	u := rs.Resource
+	if u == nil {
+		return nil
+	}
+
+	// TODO: Should we be using kstatus here?
+
+	conditions, found, err := unstructured.NestedSlice(u.Object,
+		"status", "conditions")
+	if !found || err != nil || len(conditions) == 0 {
+		return nil
+	}
+
+	var conditionStrings []string
+	for _, cond := range conditions {
+		condition := cond.(map[string]interface{})
+		conditionType := condition["type"].(string)
+		conditionStatus := condition["status"].(string)
+		conditionReason := condition["reason"].(string)
+		var color printcommon.Color
+		switch conditionStatus {
+		case "True":
+			color = printcommon.GREEN
+		case "False":
+			color = printcommon.RED
+		default:
+			color = printcommon.YELLOW
+		}
+
+		text := conditionReason
+		if text == "" {
+			text = conditionType
+		}
+		s := printcommon.SprintfWithColor(color, text)
+		conditionStrings = append(conditionStrings, s)
+	}
+	return conditionStrings
+}
diff --git a/internal/cmdapply/cmdapply.go b/internal/cmdapply/cmdapply.go
index ec83f30e..1b293fb7 100644
--- a/internal/cmdapply/cmdapply.go
+++ b/internal/cmdapply/cmdapply.go
@@ -223,7 +223,7 @@ func runApply(r *Runner, invInfo inventory.Info, objs []*unstructured.Unstructur
 
 	// Run the applier. It will return a channel where we can receive updates
 	// to keep track of progress and any issues.
-	invClient, err := inventory.NewClient(r.factory, live.WrapInventoryObj, live.InvToUnstructuredFunc, inventory.StatusPolicyAll)
+	invClient, err := inventory.NewClient(r.factory, live.WrapInventoryObj, live.InvToUnstructuredFunc, inventory.StatusPolicyAll, live.ResourceGroupGVK)
 	if err != nil {
 		return err
 	}
diff --git a/internal/cmddestroy/cmddestroy.go b/internal/cmddestroy/cmddestroy.go
index b47999ef..3fee25d7 100644
--- a/internal/cmddestroy/cmddestroy.go
+++ b/internal/cmddestroy/cmddestroy.go
@@ -159,7 +159,7 @@ func (r *Runner) runE(c *cobra.Command, args []string) error {
 func runDestroy(r *Runner, inv inventory.Info, dryRunStrategy common.DryRunStrategy) error {
 	// Run the destroyer. It will return a channel where we can receive updates
 	// to keep track of progress and any issues.
-	invClient, err := inventory.NewClient(r.factory, live.WrapInventoryObj, live.InvToUnstructuredFunc, inventory.StatusPolicyAll)
+	invClient, err := inventory.NewClient(r.factory, live.WrapInventoryObj, live.InvToUnstructuredFunc, inventory.StatusPolicyAll, live.ResourceGroupGVK)
 	if err != nil {
 		return err
 	}
diff --git a/internal/cmdmigrate/migratecmd.go b/internal/cmdmigrate/migratecmd.go
index 090ca2a1..85f2dd28 100644
--- a/internal/cmdmigrate/migratecmd.go
+++ b/internal/cmdmigrate/migratecmd.go
@@ -338,11 +338,11 @@ func validateParams(reader io.Reader, args []string) error {
 }
 
 func rgInvClient(factory util.Factory) (inventory.Client, error) {
-	return inventory.NewClient(factory, live.WrapInventoryObj, live.InvToUnstructuredFunc, inventory.StatusPolicyAll)
+	return inventory.NewClient(factory, live.WrapInventoryObj, live.InvToUnstructuredFunc, inventory.StatusPolicyAll, live.ResourceGroupGVK)
 }
 
 func cmInvClient(factory util.Factory) (inventory.Client, error) {
-	return inventory.NewClient(factory, inventory.WrapInventoryObj, inventory.InvInfoToConfigMap, inventory.StatusPolicyAll)
+	return inventory.NewClient(factory, inventory.WrapInventoryObj, inventory.InvInfoToConfigMap, inventory.StatusPolicyAll, live.ResourceGroupGVK)
 }
 
 // migrateKptfileToRG extracts inventory information from a package's Kptfile
diff --git a/internal/cmdreporeg/command.go b/internal/cmdreporeg/command.go
index c523089a..2d908007 100644
--- a/internal/cmdreporeg/command.go
+++ b/internal/cmdreporeg/command.go
@@ -53,6 +53,7 @@ func newRunner(ctx context.Context, rcg *genericclioptions.ConfigFlags) *runner
 
 	c.Flags().StringVar(&r.directory, "directory", "/", "Directory within the repository where to look for packages.")
 	c.Flags().StringVar(&r.branch, "branch", "main", "Branch in the repository where finalized packages are committed.")
+	c.Flags().BoolVar(&r.createBranch, "create-branch", false, "Create the package branch if it doesn't already exist.")
 	c.Flags().StringVar(&r.name, "name", "", "Name of the package repository. If unspecified, will use the name portion (last segment) of the repository URL.")
 	c.Flags().StringVar(&r.description, "description", "", "Brief description of the package repository.")
 	c.Flags().BoolVar(&r.deployment, "deployment", false, "Repository is a deployment repository; packages in a deployment repository are considered deployment-ready.")
@@ -76,6 +77,7 @@ type runner struct {
 	// Flags
 	directory        string
 	branch           string
+	createBranch     bool
 	name             string
 	description      string
 	deployment       bool
@@ -123,9 +125,10 @@ func (r *runner) runE(cmd *cobra.Command, args []string) error {
 		// 	return errors.E(op, err)
 		// }
 		git = &configapi.GitRepository{
-			Repo:      repository,
-			Branch:    r.branch,
-			Directory: r.directory,
+			Repo:         repository,
+			Branch:       r.branch,
+			CreateBranch: r.createBranch,
+			Directory:    r.directory,
 		}
 
 		if r.name == "" {
diff --git a/internal/cmdstatus/cmdstatus.go b/internal/cmdstatus/cmdstatus.go
new file mode 100644
index 00000000..18c4054d
--- /dev/null
+++ b/internal/cmdstatus/cmdstatus.go
@@ -0,0 +1,83 @@
+// Copyright 2020 The Kubernetes Authors.
+// SPDX-License-Identifier: Apache-2.0
+
+package cmdstatus
+
+import (
+	"context"
+	"os"
+
+	"github.com/GoogleContainerTools/kpt/internal/docs/generated/livedocs"
+	"github.com/GoogleContainerTools/kpt/internal/util/argutil"
+	"github.com/GoogleContainerTools/kpt/pkg/live"
+	kptstatus "github.com/GoogleContainerTools/kpt/pkg/status"
+	"github.com/spf13/cobra"
+	"k8s.io/kubectl/pkg/cmd/util"
+	"sigs.k8s.io/cli-utils/cmd/status"
+	"sigs.k8s.io/cli-utils/pkg/apply/poller"
+	"sigs.k8s.io/cli-utils/pkg/inventory"
+)
+
+func NewRunner(ctx context.Context, factory util.Factory,
+	invFactory inventory.ClientFactory, loader status.Loader) *status.Runner {
+	r := status.GetRunner(ctx, factory, invFactory, loader)
+	r.PollerFactoryFunc = pollerFactoryFunc
+	r.Command.Use = "status [PKG_PATH | -]"
+	r.Command.Short = livedocs.StatusShort
+	r.Command.Long = livedocs.StatusShort + "\n" + livedocs.StatusLong
+	r.Command.Example = livedocs.StatusExamples
+	return r
+}
+
+func NewCommand(ctx context.Context, factory util.Factory,
+	invFactory inventory.ClientFactory, loader status.Loader) *cobra.Command {
+	return NewRunner(ctx, factory, invFactory, loader).Command
+}
+
+func pollerFactoryFunc(f util.Factory) (poller.Poller, error) {
+	return kptstatus.NewStatusPoller(f)
+}
+
+type RGInventoryLoader struct {
+	factory util.Factory
+	ctx     context.Context
+}
+
+func NewRGInventoryLoader(ctx context.Context, factory util.Factory) *RGInventoryLoader {
+	return &RGInventoryLoader{
+		factory: factory,
+		ctx:     ctx,
+	}
+}
+
+func (rir *RGInventoryLoader) GetInvInfo(cmd *cobra.Command, args []string) (inventory.Info, error) {
+	if len(args) == 0 {
+		// default to the current working directory
+		cwd, err := os.Getwd()
+		if err != nil {
+			return nil, err
+		}
+		args = append(args, cwd)
+	}
+
+	path := args[0]
+	var err error
+	if args[0] != "-" {
+		path, err = argutil.ResolveSymlink(rir.ctx, path)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	_, inv, err := live.Load(rir.factory, path, cmd.InOrStdin())
+	if err != nil {
+		return nil, err
+	}
+
+	invInfo, err := live.ToInventoryInfo(inv)
+	if err != nil {
+		return nil, err
+	}
+
+	return invInfo, nil
+}
diff --git a/internal/cmdstatus/fake-loader.go b/internal/cmdstatus/fake-loader.go
new file mode 100644
index 00000000..6aff107c
--- /dev/null
+++ b/internal/cmdstatus/fake-loader.go
@@ -0,0 +1,62 @@
+package cmdstatus
+
+import (
+	"context"
+	"os"
+
+	"github.com/GoogleContainerTools/kpt/internal/util/argutil"
+	"github.com/GoogleContainerTools/kpt/pkg/live"
+	"github.com/spf13/cobra"
+	"k8s.io/kubectl/pkg/cmd/util"
+	"sigs.k8s.io/cli-utils/cmd/status"
+	"sigs.k8s.io/cli-utils/pkg/inventory"
+	"sigs.k8s.io/cli-utils/pkg/object"
+)
+
+type FakeLoader struct {
+	ctx       context.Context
+	factory   util.Factory
+	InvClient *inventory.FakeClient
+}
+
+var _ status.Loader = &FakeLoader{}
+
+func NewFakeLoader(ctx context.Context, f util.Factory, objs object.ObjMetadataSet) *FakeLoader {
+	return &FakeLoader{
+		ctx:       ctx,
+		factory:   f,
+		InvClient: inventory.NewFakeClient(objs),
+	}
+}
+
+func (r *FakeLoader) GetInvInfo(cmd *cobra.Command, args []string) (inventory.Info, error) {
+	if len(args) == 0 {
+		// default to the current working directory
+		cwd, err := os.Getwd()
+		if err != nil {
+			return nil, err
+		}
+		args = append(args, cwd)
+	}
+
+	path := args[0]
+	var err error
+	if args[0] != "-" {
+		path, err = argutil.ResolveSymlink(r.ctx, path)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	_, inv, err := live.Load(r.factory, path, cmd.InOrStdin())
+	if err != nil {
+		return nil, err
+	}
+
+	invInfo, err := live.ToInventoryInfo(inv)
+	if err != nil {
+		return nil, err
+	}
+
+	return invInfo, nil
+}
diff --git a/internal/docs/generated/livedocs/docs.go b/internal/docs/generated/livedocs/docs.go
index 6dc9cb14..297cce23 100644
--- a/internal/docs/generated/livedocs/docs.go
+++ b/internal/docs/generated/livedocs/docs.go
@@ -290,6 +290,29 @@ Flags:
     Determines how long the command should run before exiting. This deadline will
     be enforced regardless of the value of the --poll-until flag. The default is
     to wait forever.
+    
+  --inv-type:
+    Ways to get the inventory information. Must be one of the following:
+    
+    * local: Get the inventory information from the local file.
+      This will only get the inventory information of the package at the given/default path.
+    * remote: Get the inventory information by calling List API to the cluster.
+      This will retrieve a list of inventory information from the cluster.
+    
+    The default value is ‘local’.
+    
+  --inv-names:
+    Filter for printing statuses of packages with specified inventory names.
+    For multiple inventory names, use comma to them.
+    This must be used with --inv-type=remote.
+    
+  --namespaces:
+    Filter for printing statuses of packages under specified namespaces.
+    For multiple namespaces, use comma to separate them.
+    
+  --statuses:
+    Filter for printing packages with specified statuses.
+    For multiple statuses, use comma to separate them.
 `
 var StatusExamples = `
   # Monitor status for the resources belonging to the package in the current
@@ -299,4 +322,11 @@ var StatusExamples = `
   # Monitor status for the resources belonging to the package in the my-app
   # directory. Output in table format:
   $ kpt live status my-app --poll-until=forever --output=table
+
+  # Monitor status for the all resources on the cluster
+  # with certain inventory names and under certain namespaces.
+  $ kpt live status --inv-type remote --inv-names inv1,inv2 --namespaces ns1,ns2
+
+  # Monitor resources on the cluster that has Current or InProgress status
+  $ kpt live status --inv-type remote --statuses Current,InProgress
 `
diff --git a/package-examples/cert-manager-basic/cainjector/role-cert-manager-cainjector:leaderelection.yaml b/package-examples/cert-manager-basic/cainjector/role-cert-manager-cainjector-leaderelection.yaml
similarity index 100%
rename from package-examples/cert-manager-basic/cainjector/role-cert-manager-cainjector:leaderelection.yaml
rename to package-examples/cert-manager-basic/cainjector/role-cert-manager-cainjector-leaderelection.yaml
diff --git a/package-examples/cert-manager-basic/cainjector/rolebinding-cert-manager-cainjector:leaderelection.yaml b/package-examples/cert-manager-basic/cainjector/rolebinding-cert-manager-cainjector-leaderelection.yaml
similarity index 100%
rename from package-examples/cert-manager-basic/cainjector/rolebinding-cert-manager-cainjector:leaderelection.yaml
rename to package-examples/cert-manager-basic/cainjector/rolebinding-cert-manager-cainjector-leaderelection.yaml
diff --git a/package-examples/cert-manager-basic/cert-manager/clusterrole-cert-manager-controller-approve:cert-manager-io.yaml b/package-examples/cert-manager-basic/cert-manager/clusterrole-cert-manager-controller-approve-cert-manager-io.yaml
similarity index 100%
rename from package-examples/cert-manager-basic/cert-manager/clusterrole-cert-manager-controller-approve:cert-manager-io.yaml
rename to package-examples/cert-manager-basic/cert-manager/clusterrole-cert-manager-controller-approve-cert-manager-io.yaml
diff --git a/package-examples/cert-manager-basic/cert-manager/clusterrolebinding-cert-manager-controller-approve:cert-manager-io.yaml b/package-examples/cert-manager-basic/cert-manager/clusterrolebinding-cert-manager-controller-approve-cert-manager-io.yaml
similarity index 100%
rename from package-examples/cert-manager-basic/cert-manager/clusterrolebinding-cert-manager-controller-approve:cert-manager-io.yaml
rename to package-examples/cert-manager-basic/cert-manager/clusterrolebinding-cert-manager-controller-approve-cert-manager-io.yaml
diff --git a/package-examples/cert-manager-basic/cert-manager/role-cert-manager:leaderelection.yaml b/package-examples/cert-manager-basic/cert-manager/role-cert-manager-leaderelection.yaml
similarity index 100%
rename from package-examples/cert-manager-basic/cert-manager/role-cert-manager:leaderelection.yaml
rename to package-examples/cert-manager-basic/cert-manager/role-cert-manager-leaderelection.yaml
diff --git a/package-examples/cert-manager-basic/cert-manager/rolebinding-cert-manager:leaderelection.yaml b/package-examples/cert-manager-basic/cert-manager/rolebinding-cert-manager-leaderelection.yaml
similarity index 100%
rename from package-examples/cert-manager-basic/cert-manager/rolebinding-cert-manager:leaderelection.yaml
rename to package-examples/cert-manager-basic/cert-manager/rolebinding-cert-manager-leaderelection.yaml
diff --git a/package-examples/cert-manager-basic/webhook/clusterrole-cert-manager-webhook:subjectaccessreviews.yaml b/package-examples/cert-manager-basic/webhook/clusterrole-cert-manager-webhook-subjectaccessreviews.yaml
similarity index 100%
rename from package-examples/cert-manager-basic/webhook/clusterrole-cert-manager-webhook:subjectaccessreviews.yaml
rename to package-examples/cert-manager-basic/webhook/clusterrole-cert-manager-webhook-subjectaccessreviews.yaml
diff --git a/package-examples/cert-manager-basic/webhook/clusterrolebinding-cert-manager-webhook:subjectaccessreviews.yaml b/package-examples/cert-manager-basic/webhook/clusterrolebinding-cert-manager-webhook-subjectaccessreviews.yaml
similarity index 100%
rename from package-examples/cert-manager-basic/webhook/clusterrolebinding-cert-manager-webhook:subjectaccessreviews.yaml
rename to package-examples/cert-manager-basic/webhook/clusterrolebinding-cert-manager-webhook-subjectaccessreviews.yaml
diff --git a/package-examples/cert-manager-basic/webhook/role-cert-manager-webhook:dynamic-serving.yaml b/package-examples/cert-manager-basic/webhook/role-cert-manager-webhook-dynamic-serving.yaml
similarity index 100%
rename from package-examples/cert-manager-basic/webhook/role-cert-manager-webhook:dynamic-serving.yaml
rename to package-examples/cert-manager-basic/webhook/role-cert-manager-webhook-dynamic-serving.yaml
diff --git a/package-examples/cert-manager-basic/webhook/rolebinding-cert-manager-webhook:dynamic-serving.yaml b/package-examples/cert-manager-basic/webhook/rolebinding-cert-manager-webhook-dynamic-serving.yaml
similarity index 100%
rename from package-examples/cert-manager-basic/webhook/rolebinding-cert-manager-webhook:dynamic-serving.yaml
rename to package-examples/cert-manager-basic/webhook/rolebinding-cert-manager-webhook-dynamic-serving.yaml
diff --git a/pkg/live/inventory-client-factory.go b/pkg/live/inventory-client-factory.go
new file mode 100644
index 00000000..0a846e3e
--- /dev/null
+++ b/pkg/live/inventory-client-factory.go
@@ -0,0 +1,18 @@
+package live
+
+import (
+	cmdutil "k8s.io/kubectl/pkg/cmd/util"
+	"sigs.k8s.io/cli-utils/pkg/inventory"
+)
+
+// ClusterClientFactory is a factory that creates instances of ClusterClient inventory client.
+type ClusterClientFactory struct {
+	StatusPolicy inventory.StatusPolicy
+}
+
+func NewClusterClientFactory() *ClusterClientFactory {
+	return &ClusterClientFactory{StatusPolicy: inventory.StatusPolicyNone}
+}
+func (ccf *ClusterClientFactory) NewClient(factory cmdutil.Factory) (inventory.Client, error) {
+	return inventory.NewClient(factory, WrapInventoryObj, InvToUnstructuredFunc, ccf.StatusPolicy, ResourceGroupGVK)
+}
diff --git a/pkg/live/planner/cluster.go b/pkg/live/planner/cluster.go
index d66ea6f3..8129593d 100644
--- a/pkg/live/planner/cluster.go
+++ b/pkg/live/planner/cluster.go
@@ -53,7 +53,7 @@ func NewClusterPlanner(f util.Factory) (*ClusterPlanner, error) {
 		return nil, err
 	}
 
-	invClient, err := inventory.NewClient(f, live.WrapInventoryObj, live.InvToUnstructuredFunc, inventory.StatusPolicyNone)
+	invClient, err := inventory.NewClient(f, live.WrapInventoryObj, live.InvToUnstructuredFunc, inventory.StatusPolicyNone, live.ResourceGroupGVK)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/status/configconnector.go b/pkg/status/configconnector.go
index 53c33924..4d9d999f 100644
--- a/pkg/status/configconnector.go
+++ b/pkg/status/configconnector.go
@@ -54,7 +54,7 @@ var _ engine.StatusReader = &ConfigConnectorStatusReader{}
 
 // Supports returns true for all Config Connector resources.
 func (c *ConfigConnectorStatusReader) Supports(gk schema.GroupKind) bool {
-	return Supports(gk)
+	return strings.HasSuffix(gk.Group, "cnrm.cloud.google.com")
 }
 
 func (c *ConfigConnectorStatusReader) ReadStatus(ctx context.Context, reader engine.ClusterReader, id object.ObjMetadata) (*event.ResourceStatus, error) {
@@ -96,7 +96,7 @@ func (c *ConfigConnectorStatusReader) ReadStatusForObject(_ context.Context, _ e
 		return newResourceStatus(id, status.TerminatingStatus, u, "Resource scheduled for deletion"), nil
 	}
 
-	res, err := Compute(u)
+	res, err := c.Compute(u)
 
 	if err != nil {
 		return newUnknownResourceStatus(id, u, err), nil
@@ -104,11 +104,7 @@ func (c *ConfigConnectorStatusReader) ReadStatusForObject(_ context.Context, _ e
 	return newResourceStatus(id, res.Status, u, res.Message), nil
 }
 
-func Supports(gk schema.GroupKind) bool {
-	return strings.HasSuffix(gk.Group, "cnrm.cloud.google.com")
-}
-
-func Compute(u *unstructured.Unstructured) (*status.Result, error) {
+func (c *ConfigConnectorStatusReader) Compute(u *unstructured.Unstructured) (*status.Result, error) {
 	if u.GroupVersionKind().Kind == "ConfigConnectorContext" {
 		return computeStatusForConfigConnectorContext(u)
 	}
diff --git a/pkg/status/poller.go b/pkg/status/poller.go
index 529d805b..2d5ae290 100644
--- a/pkg/status/poller.go
+++ b/pkg/status/poller.go
@@ -36,6 +36,9 @@ func NewStatusPoller(f util.Factory) (*polling.StatusPoller, error) {
 			&ConfigConnectorStatusReader{
 				Mapper: mapper,
 			},
+			&RolloutStatusReader{
+				Mapper: mapper,
+			},
 		},
 	})
 }
@@ -55,7 +58,10 @@ func NewStatusWatcher(f util.Factory) (watcher.StatusWatcher, error) {
 		DynamicClient: dynamicClient,
 		Mapper:        mapper,
 		ResyncPeriod:  1 * time.Hour,
-		StatusReader:  statusreaders.NewStatusReader(mapper, NewConfigConnectorStatusReader(mapper)),
+		StatusReader: statusreaders.NewStatusReader(
+			mapper,
+			NewConfigConnectorStatusReader(mapper),
+			NewRolloutStatusReader(mapper)),
 		ClusterReader: &clusterreader.DynamicClusterReader{
 			DynamicClient: dynamicClient,
 			Mapper:        mapper,
diff --git a/pkg/status/rollout.go b/pkg/status/rollout.go
new file mode 100644
index 00000000..a8952f0a
--- /dev/null
+++ b/pkg/status/rollout.go
@@ -0,0 +1,230 @@
+// Copyright 2022 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.
+
+package status
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"strconv"
+
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/api/meta"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/cli-utils/pkg/kstatus/polling/engine"
+	"sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
+	"sigs.k8s.io/cli-utils/pkg/kstatus/status"
+	"sigs.k8s.io/cli-utils/pkg/object"
+	"sigs.k8s.io/yaml"
+)
+
+const (
+	ArgoGroup   = "argoproj.io"
+	Rollout     = "Rollout"
+	Degraded    = "Degraded"
+	Failed      = "Failed"
+	Healthy     = "Healthy"
+	Paused      = "Paused"
+	Progressing = "Progressing"
+)
+
+type RolloutStatusReader struct {
+	Mapper meta.RESTMapper
+}
+
+func NewRolloutStatusReader(mapper meta.RESTMapper) engine.StatusReader {
+	return &RolloutStatusReader{
+		Mapper: mapper,
+	}
+}
+
+var _ engine.StatusReader = &RolloutStatusReader{}
+
+// Supports returns true for all rollout resources.
+func (r *RolloutStatusReader) Supports(gk schema.GroupKind) bool {
+	return gk.Group == ArgoGroup && gk.Kind == Rollout
+}
+
+func (r *RolloutStatusReader) Compute(u *unstructured.Unstructured) (*status.Result, error) {
+	result := status.Result{
+		Status:     status.UnknownStatus,
+		Message:    status.GetStringField(u.Object, ".status.message", ""),
+		Conditions: make([]status.Condition, 0),
+	}
+	// ensure that the meta generation is observed
+	generation, found, err := unstructured.NestedInt64(u.Object, "metadata", "generation")
+	if err != nil {
+		return &result, fmt.Errorf("looking up metadata.generation from resource: %w", err)
+	}
+	if !found {
+		return &result, fmt.Errorf("metadata.generation not found")
+	}
+
+	// Argo Rollouts defines the observedGeneration field in the Rollout object as a string
+	// so read it as a string here
+	observedGenerationString, found, err := unstructured.NestedString(u.Object, "status", "observedGeneration")
+	if err != nil {
+		return &result, fmt.Errorf("looking up status.observedGeneration from resource: %w", err)
+	}
+	if !found {
+		// We know that Rollout resources uses the ObservedGeneration pattern, so consider it
+		// an error if it is not found.
+		return &result, fmt.Errorf("status.ObservedGeneration not found")
+	}
+	// If no errors detected and the field is found
+	// Parse it to become an integer
+	observedGeneration, err := strconv.ParseInt(observedGenerationString, 10, 64)
+	if err != nil {
+		return &result, fmt.Errorf("looking up status.observedGeneration from resource: %w", err)
+	}
+
+	if generation != observedGeneration {
+		msg := fmt.Sprintf("%s generation is %d, but latest observed generation is %d", u.GetKind(), generation, observedGeneration)
+		result.Status = status.InProgressStatus
+		result.Message = msg
+		return &result, nil
+	}
+
+	phase, phaseFound, err := unstructured.NestedString(u.Object, "status", "phase")
+	if err != nil {
+		return &result, fmt.Errorf("looking up status.phase from resource: %w", err)
+	}
+	if !phaseFound {
+		// We know that Rollout resources uses the phase pattern, so consider it
+		// an error if it is not found.
+		return &result, fmt.Errorf("status.phase not found")
+	}
+
+	conditions, condFound, err := unstructured.NestedSlice(u.Object, "status", "conditions")
+	if err != nil {
+		return &result, fmt.Errorf("looking up status.conditions from resource: %w", err)
+	}
+	if condFound {
+		data, err := yaml.Marshal(conditions)
+		if err != nil {
+			return &result, fmt.Errorf("failed to marshal conditions for %s/%s", u.GetNamespace(), u.GetName())
+		}
+		err = yaml.Unmarshal(data, &result.Conditions)
+		if err != nil {
+			return &result, fmt.Errorf("failed to unmarshal conditions for %s/%s", u.GetNamespace(), u.GetName())
+		}
+	}
+
+	specReplicas := status.GetIntField(u.Object, ".spec.replicas", 1) // Controller uses 1 as default if not specified.
+	statusReplicas := status.GetIntField(u.Object, ".status.replicas", 0)
+	updatedReplicas := status.GetIntField(u.Object, ".status.updatedReplicas", 0)
+	readyReplicas := status.GetIntField(u.Object, ".status.readyReplicas", 0)
+	availableReplicas := status.GetIntField(u.Object, ".status.availableReplicas", 0)
+
+	if specReplicas > statusReplicas {
+		message := fmt.Sprintf("replicas: %d/%d", statusReplicas, specReplicas)
+		result.Status = status.InProgressStatus
+		result.Message = message
+
+		return &result, nil
+	}
+
+	if statusReplicas > specReplicas {
+		message := fmt.Sprintf("Pending termination: %d", statusReplicas-specReplicas)
+		result.Status = status.InProgressStatus
+		result.Message = message
+		return &result, nil
+	}
+
+	if updatedReplicas > availableReplicas {
+		message := fmt.Sprintf("Available: %d/%d", availableReplicas, updatedReplicas)
+		result.Status = status.InProgressStatus
+		result.Message = message
+		return &result, nil
+	}
+
+	if specReplicas > readyReplicas {
+		message := fmt.Sprintf("Ready: %d/%d", readyReplicas, specReplicas)
+		result.Status = status.InProgressStatus
+		result.Message = message
+		return &result, nil
+	}
+
+	message := status.GetStringField(u.Object, ".status.message", "")
+	if message != "" {
+		message += " "
+	}
+	message += fmt.Sprintf("Ready Replicas: %d, Updated Replicas: %d", readyReplicas, updatedReplicas)
+	result.Message = message
+
+	switch phase {
+	case Degraded, Failed:
+		result.Status = status.FailedStatus
+	case Healthy:
+		result.Status = status.CurrentStatus
+	case Paused, Progressing:
+		result.Status = status.InProgressStatus
+	default:
+		// Undefined status
+		result.Status = status.UnknownStatus
+	}
+	return &result, nil
+}
+
+func (r *RolloutStatusReader) ReadStatus(ctx context.Context, reader engine.ClusterReader, id object.ObjMetadata) (
+	*event.ResourceStatus, error) {
+	gvk, err := toGVK(id.GroupKind, r.Mapper)
+	if err != nil {
+		return newUnknownResourceStatus(id, nil, err), nil
+	}
+
+	key := types.NamespacedName{
+		Name:      id.Name,
+		Namespace: id.Namespace,
+	}
+
+	var u unstructured.Unstructured
+	u.SetGroupVersionKind(gvk)
+	err = reader.Get(ctx, key, &u)
+	if err != nil {
+		if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
+			return nil, err
+		}
+		if apierrors.IsNotFound(err) {
+			return newResourceStatus(id, status.NotFoundStatus, &u, "Resource not found"), nil
+		}
+		return newUnknownResourceStatus(id, nil, err), nil
+	}
+
+	return r.ReadStatusForObject(ctx, reader, &u)
+}
+
+func (r *RolloutStatusReader) ReadStatusForObject(_ context.Context, _ engine.ClusterReader, u *unstructured.Unstructured) (
+	*event.ResourceStatus, error) {
+	id := object.UnstructuredToObjMetadata(u)
+
+	// First check if the resource is in the process of being deleted.
+	deletionTimestamp, found, err := unstructured.NestedString(u.Object, "metadata", "deletionTimestamp")
+	if err != nil {
+		return newUnknownResourceStatus(id, u, err), nil
+	}
+	if found && deletionTimestamp != "" {
+		return newResourceStatus(id, status.TerminatingStatus, u, "Resource scheduled for deletion"), nil
+	}
+
+	res, err := r.Compute(u)
+	if err != nil {
+		return newUnknownResourceStatus(id, u, err), nil
+	}
+
+	return newResourceStatus(id, res.Status, u, res.Message), nil
+}
diff --git a/porch/Makefile b/porch/Makefile
index 3ad8ea7f..5683477c 100644
--- a/porch/Makefile
+++ b/porch/Makefile
@@ -46,6 +46,8 @@ PORCH_CONTROLLERS_IMAGE ?= porch-controllers
 PORCH_WRAPPER_SERVER_IMAGE ?= porch-wrapper-server
 TEST_GIT_SERVER_IMAGE ?= test-git-server
 
+.DEFAULT_GOAL := all
+
 "$(MYGOBIN)/stringer":
 	go install golang.org/x/tools/cmd/stringer@v0.1.12
 
diff --git a/porch/README.md b/porch/README.md
index 59350f62..ffa350ec 100644
--- a/porch/README.md
+++ b/porch/README.md
@@ -1,7 +1,7 @@
 # Package Orchestration Server
 
 Package Orchestration Server (a.k.a. Porch) is a k8s extension apiserver
-which manages lifecycle of KRM configuration packages.
+which manages the lifecycle of KRM configuration packages.
 
 You can run Porch:
 
diff --git a/porch/api/generated/clientset/versioned/fake/register.go b/porch/api/generated/clientset/versioned/fake/register.go
index 36b9da3c..2b1d2df7 100644
--- a/porch/api/generated/clientset/versioned/fake/register.go
+++ b/porch/api/generated/clientset/versioned/fake/register.go
@@ -35,14 +35,14 @@ var localSchemeBuilder = runtime.SchemeBuilder{
 // AddToScheme adds all types of this clientset into the given scheme. This allows composition
 // of clientsets, like in:
 //
-//   import (
-//     "k8s.io/client-go/kubernetes"
-//     clientsetscheme "k8s.io/client-go/kubernetes/scheme"
-//     aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
-//   )
+//	import (
+//	  "k8s.io/client-go/kubernetes"
+//	  clientsetscheme "k8s.io/client-go/kubernetes/scheme"
+//	  aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
+//	)
 //
-//   kclientset, _ := kubernetes.NewForConfig(c)
-//   _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
+//	kclientset, _ := kubernetes.NewForConfig(c)
+//	_ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
 //
 // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
 // correctly.
diff --git a/porch/api/generated/clientset/versioned/scheme/register.go b/porch/api/generated/clientset/versioned/scheme/register.go
index cee00d25..90fc1da2 100644
--- a/porch/api/generated/clientset/versioned/scheme/register.go
+++ b/porch/api/generated/clientset/versioned/scheme/register.go
@@ -35,14 +35,14 @@ var localSchemeBuilder = runtime.SchemeBuilder{
 // AddToScheme adds all types of this clientset into the given scheme. This allows composition
 // of clientsets, like in:
 //
-//   import (
-//     "k8s.io/client-go/kubernetes"
-//     clientsetscheme "k8s.io/client-go/kubernetes/scheme"
-//     aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
-//   )
+//	import (
+//	  "k8s.io/client-go/kubernetes"
+//	  clientsetscheme "k8s.io/client-go/kubernetes/scheme"
+//	  aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
+//	)
 //
-//   kclientset, _ := kubernetes.NewForConfig(c)
-//   _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
+//	kclientset, _ := kubernetes.NewForConfig(c)
+//	_ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
 //
 // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
 // correctly.
diff --git a/porch/controllers/config/crd/bases/config.cloud.google.com_remoterootsyncsets.yaml b/porch/controllers/config/crd/bases/config.porch.kpt.dev_remoterootsyncsets.yaml
similarity index 99%
rename from porch/controllers/config/crd/bases/config.cloud.google.com_remoterootsyncsets.yaml
rename to porch/controllers/config/crd/bases/config.porch.kpt.dev_remoterootsyncsets.yaml
index 86a6d2f3..8966c0b7 100644
--- a/porch/controllers/config/crd/bases/config.cloud.google.com_remoterootsyncsets.yaml
+++ b/porch/controllers/config/crd/bases/config.porch.kpt.dev_remoterootsyncsets.yaml
@@ -19,9 +19,9 @@ metadata:
   annotations:
     controller-gen.kubebuilder.io/version: v0.8.0
   creationTimestamp: null
-  name: remoterootsyncsets.config.cloud.google.com
+  name: remoterootsyncsets.config.porch.kpt.dev
 spec:
-  group: config.cloud.google.com
+  group: config.porch.kpt.dev
   names:
     kind: RemoteRootSyncSet
     listKind: RemoteRootSyncSetList
diff --git a/porch/controllers/config/crd/bases/config.cloud.google.com_rootsyncsets.yaml b/porch/controllers/config/crd/bases/config.porch.kpt.dev_rootsyncsets.yaml
similarity index 99%
rename from porch/controllers/config/crd/bases/config.cloud.google.com_rootsyncsets.yaml
rename to porch/controllers/config/crd/bases/config.porch.kpt.dev_rootsyncsets.yaml
index 657232c1..2ac1a385 100644
--- a/porch/controllers/config/crd/bases/config.cloud.google.com_rootsyncsets.yaml
+++ b/porch/controllers/config/crd/bases/config.porch.kpt.dev_rootsyncsets.yaml
@@ -19,9 +19,9 @@ metadata:
   annotations:
     controller-gen.kubebuilder.io/version: v0.8.0
   creationTimestamp: null
-  name: rootsyncsets.config.cloud.google.com
+  name: rootsyncsets.config.porch.kpt.dev
 spec:
-  group: config.cloud.google.com
+  group: config.porch.kpt.dev
   names:
     kind: RootSyncSet
     listKind: RootSyncSetList
diff --git a/porch/controllers/config/crd/bases/porch.kpt.dev_workloadidentitybindings.yaml b/porch/controllers/config/crd/bases/config.porch.kpt.dev_workloadidentitybindings.yaml
similarity index 98%
rename from porch/controllers/config/crd/bases/porch.kpt.dev_workloadidentitybindings.yaml
rename to porch/controllers/config/crd/bases/config.porch.kpt.dev_workloadidentitybindings.yaml
index 0868e3eb..645e231c 100644
--- a/porch/controllers/config/crd/bases/porch.kpt.dev_workloadidentitybindings.yaml
+++ b/porch/controllers/config/crd/bases/config.porch.kpt.dev_workloadidentitybindings.yaml
@@ -19,9 +19,9 @@ metadata:
   annotations:
     controller-gen.kubebuilder.io/version: v0.8.0
   creationTimestamp: null
-  name: workloadidentitybindings.porch.kpt.dev
+  name: workloadidentitybindings.config.porch.kpt.dev
 spec:
-  group: porch.kpt.dev
+  group: config.porch.kpt.dev
   names:
     kind: WorkloadIdentityBinding
     listKind: WorkloadIdentityBindingList
diff --git a/porch/controllers/config/rbac/role.yaml b/porch/controllers/config/rbac/role.yaml
index f7f91cab..920baf25 100644
--- a/porch/controllers/config/rbac/role.yaml
+++ b/porch/controllers/config/rbac/role.yaml
@@ -17,7 +17,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata:
   creationTimestamp: null
-  name: configmanagement-operator
+  name: porch-controllers
 rules:
 - apiGroups:
   - ""
@@ -27,9 +27,9 @@ rules:
   - create
   - patch
 - apiGroups:
-  - config.cloud.google.com
+  - config.porch.kpt.dev
   resources:
-  - remoterootsyncs
+  - remoterootsyncsets
   verbs:
   - create
   - delete
@@ -39,21 +39,21 @@ rules:
   - update
   - watch
 - apiGroups:
-  - config.cloud.google.com
+  - config.porch.kpt.dev
   resources:
-  - remoterootsyncs/finalizers
+  - remoterootsyncsets/finalizers
   verbs:
   - update
 - apiGroups:
-  - config.cloud.google.com
+  - config.porch.kpt.dev
   resources:
-  - remoterootsyncs/status
+  - remoterootsyncsets/status
   verbs:
   - get
   - patch
   - update
 - apiGroups:
-  - config.cloud.google.com
+  - config.porch.kpt.dev
   resources:
   - rootsyncsets
   verbs:
@@ -65,13 +65,13 @@ rules:
   - update
   - watch
 - apiGroups:
-  - config.cloud.google.com
+  - config.porch.kpt.dev
   resources:
   - rootsyncsets/finalizers
   verbs:
   - update
 - apiGroups:
-  - config.cloud.google.com
+  - config.porch.kpt.dev
   resources:
   - rootsyncsets/status
   verbs:
@@ -79,9 +79,9 @@ rules:
   - patch
   - update
 - apiGroups:
-  - coordination.k8s.io
+  - config.porch.kpt.dev
   resources:
-  - leases
+  - workloadidentitybindings
   verbs:
   - create
   - delete
@@ -91,28 +91,28 @@ rules:
   - update
   - watch
 - apiGroups:
-  - porch.kpt.dev
+  - config.porch.kpt.dev
   resources:
-  - workloadidentitybindings
+  - workloadidentitybindings/finalizers
   verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
   - update
-  - watch
 - apiGroups:
-  - porch.kpt.dev
+  - config.porch.kpt.dev
   resources:
-  - workloadidentitybindings/finalizers
+  - workloadidentitybindings/status
   verbs:
+  - get
+  - patch
   - update
 - apiGroups:
-  - porch.kpt.dev
+  - coordination.k8s.io
   resources:
-  - workloadidentitybindings/status
+  - leases
   verbs:
+  - create
+  - delete
   - get
+  - list
   - patch
   - update
+  - watch
diff --git a/porch/controllers/main.go b/porch/controllers/main.go
index c3b41932..dd734cb5 100644
--- a/porch/controllers/main.go
+++ b/porch/controllers/main.go
@@ -14,7 +14,7 @@
 
 package main
 
-//go:generate go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.8.0 rbac:roleName=configmanagement-operator webhook paths="./..."
+//go:generate go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.8.0 rbac:roleName=porch-controllers webhook paths="./..."
 
 //go:generate go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.8.0 crd paths="./..." output:crd:artifacts:config=config/crd/bases
 
@@ -88,7 +88,7 @@ func run(ctx context.Context) error {
 		Port:                       9443,
 		HealthProbeBindAddress:     ":8081",
 		LeaderElection:             false,
-		LeaderElectionID:           "porch-operators.config.cloud.google.com",
+		LeaderElectionID:           "porch-operators.config.porch.kpt.dev",
 		LeaderElectionResourceLock: resourcelock.LeasesResourceLock,
 	}
 
diff --git a/porch/controllers/remoterootsync/api/v1alpha1/groupversion_info.go b/porch/controllers/remoterootsync/api/v1alpha1/groupversion_info.go
index b3c81d39..aa52eab1 100644
--- a/porch/controllers/remoterootsync/api/v1alpha1/groupversion_info.go
+++ b/porch/controllers/remoterootsync/api/v1alpha1/groupversion_info.go
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Package v1alpha1 contains API Schema definitions for the config.cloud.google.com v1alpha1 API group
+// Package v1alpha1 contains API Schema definitions for the config.porch.kpt.dev v1alpha1 API group
 // +kubebuilder:object:generate=true
-// +groupName=config.cloud.google.com
+// +groupName=config.porch.kpt.dev
 package v1alpha1
 
 import (
@@ -26,7 +26,7 @@ import (
 
 var (
 	// GroupVersion is group version used to register these objects
-	GroupVersion = schema.GroupVersion{Group: "config.cloud.google.com", Version: "v1alpha1"}
+	GroupVersion = schema.GroupVersion{Group: "config.porch.kpt.dev", Version: "v1alpha1"}
 
 	// SchemeBuilder is used to add go types to the GroupVersionKind scheme
 	SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
diff --git a/porch/controllers/remoterootsync/config/samples/apply-to-gke.yaml b/porch/controllers/remoterootsync/config/samples/apply-to-gke.yaml
index 1d8f44b4..76514b81 100644
--- a/porch/controllers/remoterootsync/config/samples/apply-to-gke.yaml
+++ b/porch/controllers/remoterootsync/config/samples/apply-to-gke.yaml
@@ -26,7 +26,7 @@ spec:
 
 ---
 
-apiVersion: config.cloud.google.com/v1alpha1
+apiVersion: config.porch.kpt.dev/v1alpha1
 kind: RemoteRootSyncSet
 metadata:
   name: example-1
diff --git a/porch/controllers/remoterootsync/config/samples/hack-self-apply.yaml b/porch/controllers/remoterootsync/config/samples/hack-self-apply.yaml
index c439d2f6..9fda6390 100644
--- a/porch/controllers/remoterootsync/config/samples/hack-self-apply.yaml
+++ b/porch/controllers/remoterootsync/config/samples/hack-self-apply.yaml
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 
-apiVersion: config.cloud.google.com/v1alpha1
+apiVersion: config.porch.kpt.dev/v1alpha1
 kind: RemoteRootSyncSet
 metadata:
   name: loopback-apply
diff --git a/porch/controllers/remoterootsync/pkg/controllers/remoterootsyncset/remoterootsync_controller.go b/porch/controllers/remoterootsync/pkg/controllers/remoterootsyncset/remoterootsync_controller.go
index 997ef297..d8113e26 100644
--- a/porch/controllers/remoterootsync/pkg/controllers/remoterootsyncset/remoterootsync_controller.go
+++ b/porch/controllers/remoterootsync/pkg/controllers/remoterootsyncset/remoterootsync_controller.go
@@ -62,9 +62,9 @@ type RemoteRootSyncSetReconciler struct {
 	localRESTConfig *rest.Config
 }
 
-//+kubebuilder:rbac:groups=config.cloud.google.com,resources=remoterootsyncs,verbs=get;list;watch;create;update;patch;delete
-//+kubebuilder:rbac:groups=config.cloud.google.com,resources=remoterootsyncs/status,verbs=get;update;patch
-//+kubebuilder:rbac:groups=config.cloud.google.com,resources=remoterootsyncs/finalizers,verbs=update
+//+kubebuilder:rbac:groups=config.porch.kpt.dev,resources=remoterootsyncsets,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=config.porch.kpt.dev,resources=remoterootsyncsets/status,verbs=get;update;patch
+//+kubebuilder:rbac:groups=config.porch.kpt.dev,resources=remoterootsyncsets/finalizers,verbs=update
 
 // Reconcile implements the main kubernetes reconciliation loop.
 func (r *RemoteRootSyncSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
@@ -72,7 +72,7 @@ func (r *RemoteRootSyncSetReconciler) Reconcile(ctx context.Context, req ctrl.Re
 	if err := r.Get(ctx, req.NamespacedName, &subject); err != nil {
 		return ctrl.Result{}, client.IgnoreNotFound(err)
 	}
-	myFinalizerName := "config.cloud.google.com/finalizer"
+	myFinalizerName := "config.porch.kpt.dev/finalizer"
 	if subject.ObjectMeta.DeletionTimestamp.IsZero() {
 		// The object is not being deleted, so if it does not have our finalizer,
 		// then lets add the finalizer and update the object. This is equivalent
diff --git a/porch/controllers/rootsyncset/api/v1alpha1/groupversion_info.go b/porch/controllers/rootsyncset/api/v1alpha1/groupversion_info.go
index 31b116ac..6866e810 100644
--- a/porch/controllers/rootsyncset/api/v1alpha1/groupversion_info.go
+++ b/porch/controllers/rootsyncset/api/v1alpha1/groupversion_info.go
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Package v1alpha1 contains API Schema definitions for the config.cloud.google.com v1alpha1 API group
+// Package v1alpha1 contains API Schema definitions for the config.porch.kpt.dev v1alpha1 API group
 //+kubebuilder:object:generate=true
-//+groupName=config.cloud.google.com
+//+groupName=config.porch.kpt.dev
 package v1alpha1
 
 import (
@@ -26,7 +26,7 @@ import (
 
 var (
 	// GroupVersion is group version used to register these objects
-	GroupVersion = schema.GroupVersion{Group: "config.cloud.google.com", Version: "v1alpha1"}
+	GroupVersion = schema.GroupVersion{Group: "config.porch.kpt.dev", Version: "v1alpha1"}
 
 	// SchemeBuilder is used to add go types to the GroupVersionKind scheme
 	SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
diff --git a/porch/controllers/rootsyncset/pkg/controllers/rootsyncset/controller.go b/porch/controllers/rootsyncset/pkg/controllers/rootsyncset/controller.go
index 572ac49d..803dbf24 100644
--- a/porch/controllers/rootsyncset/pkg/controllers/rootsyncset/controller.go
+++ b/porch/controllers/rootsyncset/pkg/controllers/rootsyncset/controller.go
@@ -55,9 +55,9 @@ type RootSyncSetReconciler struct {
 	WorkloadIdentityHelper
 }
 
-//+kubebuilder:rbac:groups=config.cloud.google.com,resources=rootsyncsets,verbs=get;list;watch;create;update;patch;delete
-//+kubebuilder:rbac:groups=config.cloud.google.com,resources=rootsyncsets/status,verbs=get;update;patch
-//+kubebuilder:rbac:groups=config.cloud.google.com,resources=rootsyncsets/finalizers,verbs=update
+//+kubebuilder:rbac:groups=config.porch.kpt.dev,resources=rootsyncsets,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=config.porch.kpt.dev,resources=rootsyncsets/status,verbs=get;update;patch
+//+kubebuilder:rbac:groups=config.porch.kpt.dev,resources=rootsyncsets/finalizers,verbs=update
 
 // Reconcile is part of the main kubernetes reconciliation loop which aims to
 // move the current state of the cluster closer to the desired state.
@@ -74,7 +74,7 @@ func (r *RootSyncSetReconciler) Reconcile(ctx context.Context, req ctrl.Request)
 	if err := r.Get(ctx, req.NamespacedName, &rootsyncset); err != nil {
 		return ctrl.Result{}, client.IgnoreNotFound(err)
 	}
-	myFinalizerName := "config.cloud.google.com/finalizer"
+	myFinalizerName := "config.porch.kpt.dev/finalizer"
 	if rootsyncset.ObjectMeta.DeletionTimestamp.IsZero() {
 		// The object is not being deleted, so if it does not have our finalizer,
 		// then lets add the finalizer and update the object. This is equivalent
@@ -137,7 +137,7 @@ func (r *RootSyncSetReconciler) Reconcile(ctx context.Context, req ctrl.Request)
 	return ctrl.Result{}, nil
 }
 
-//BuildObjectsToApply config root sync
+// BuildObjectsToApply config root sync
 func BuildObjectsToApply(rootsyncset *v1alpha1.RootSyncSet) (schema.GroupVersionResource, *unstructured.Unstructured, error) {
 	gv, err := schema.ParseGroupVersion(rootSyncApiVersion)
 	if err != nil {
diff --git a/porch/controllers/workloadidentitybinding/api/v1alpha1/groupversion_info.go b/porch/controllers/workloadidentitybinding/api/v1alpha1/groupversion_info.go
index dfb0987d..aa52eab1 100644
--- a/porch/controllers/workloadidentitybinding/api/v1alpha1/groupversion_info.go
+++ b/porch/controllers/workloadidentitybinding/api/v1alpha1/groupversion_info.go
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Package v1alpha1 contains API Schema definitions for the porch.kpt.dev v1alpha1 API group
+// Package v1alpha1 contains API Schema definitions for the config.porch.kpt.dev v1alpha1 API group
 // +kubebuilder:object:generate=true
-// +groupName=porch.kpt.dev
+// +groupName=config.porch.kpt.dev
 package v1alpha1
 
 import (
@@ -26,7 +26,7 @@ import (
 
 var (
 	// GroupVersion is group version used to register these objects
-	GroupVersion = schema.GroupVersion{Group: "porch.kpt.dev", Version: "v1alpha1"}
+	GroupVersion = schema.GroupVersion{Group: "config.porch.kpt.dev", Version: "v1alpha1"}
 
 	// SchemeBuilder is used to add go types to the GroupVersionKind scheme
 	SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
diff --git a/porch/controllers/workloadidentitybinding/config/samples/simple.yaml b/porch/controllers/workloadidentitybinding/config/samples/simple.yaml
index 4c8a0bd6..02dee97a 100644
--- a/porch/controllers/workloadidentitybinding/config/samples/simple.yaml
+++ b/porch/controllers/workloadidentitybinding/config/samples/simple.yaml
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-apiVersion: porch.kpt.dev/v1alpha1
+apiVersion: config.porch.kpt.dev/v1alpha1
 kind: WorkloadIdentityBinding
 metadata:
   name: cnrm-controller-manager-example-project-id
diff --git a/porch/controllers/workloadidentitybinding/pkg/controllers/workloadidentitybinding/controller.go b/porch/controllers/workloadidentitybinding/pkg/controllers/workloadidentitybinding/controller.go
index be4da9f9..07f50f83 100644
--- a/porch/controllers/workloadidentitybinding/pkg/controllers/workloadidentitybinding/controller.go
+++ b/porch/controllers/workloadidentitybinding/pkg/controllers/workloadidentitybinding/controller.go
@@ -40,9 +40,9 @@ type WorkloadIdentityBindingReconciler struct {
 	restMapper    meta.RESTMapper
 }
 
-//+kubebuilder:rbac:groups=porch.kpt.dev,resources=workloadidentitybindings,verbs=get;list;watch;create;update;patch;delete
-//+kubebuilder:rbac:groups=porch.kpt.dev,resources=workloadidentitybindings/status,verbs=get;update;patch
-//+kubebuilder:rbac:groups=porch.kpt.dev,resources=workloadidentitybindings/finalizers,verbs=update
+//+kubebuilder:rbac:groups=config.porch.kpt.dev,resources=workloadidentitybindings,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=config.porch.kpt.dev,resources=workloadidentitybindings/status,verbs=get;update;patch
+//+kubebuilder:rbac:groups=config.porch.kpt.dev,resources=workloadidentitybindings/finalizers,verbs=update
 
 // Reconcile implements the main kubernetes reconciliation loop.
 func (r *WorkloadIdentityBindingReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
@@ -50,7 +50,7 @@ func (r *WorkloadIdentityBindingReconciler) Reconcile(ctx context.Context, req c
 	if err := r.Get(ctx, req.NamespacedName, &subject); err != nil {
 		return ctrl.Result{}, client.IgnoreNotFound(err)
 	}
-	// myFinalizerName := "config.cloud.google.com/finalizer"
+	// myFinalizerName := "config.porch.kpt.dev/finalizer"
 	// if subject.ObjectMeta.DeletionTimestamp.IsZero() {
 	// 	// The object is not being deleted, so if it does not have our finalizer,
 	// 	// then lets add the finalizer and update the object. This is equivalent
@@ -105,10 +105,14 @@ func (r *WorkloadIdentityBindingReconciler) Reconcile(ctx context.Context, req c
 
 func updateStatus(subject *api.WorkloadIdentityBinding, results *applyset.ApplyResults, err error) bool {
 	conditions := &subject.Status.Conditions
-	if err == nil {
-		meta.SetStatusCondition(conditions, metav1.Condition{Type: "Ready", Status: metav1.ConditionTrue, Reason: "Ready"})
-	} else {
+	if err != nil {
 		meta.SetStatusCondition(conditions, metav1.Condition{Type: "Ready", Status: metav1.ConditionFalse, Reason: "Error"})
+	} else if !results.AllApplied() {
+		meta.SetStatusCondition(conditions, metav1.Condition{Type: "Ready", Status: metav1.ConditionFalse, Reason: "ApplyInProgress"})
+	} else if !results.AllHealthy() {
+		meta.SetStatusCondition(conditions, metav1.Condition{Type: "Ready", Status: metav1.ConditionFalse, Reason: "NotHealthy"})
+	} else {
+		meta.SetStatusCondition(conditions, metav1.Condition{Type: "Ready", Status: metav1.ConditionTrue, Reason: "Ready"})
 	}
 
 	// TODO: Check apply results and think about status conditions
diff --git a/porch/deployments/porch/9-controllers.yaml b/porch/deployments/porch/9-controllers.yaml
index ddefb83a..5861fb28 100644
--- a/porch/deployments/porch/9-controllers.yaml
+++ b/porch/deployments/porch/9-controllers.yaml
@@ -49,21 +49,49 @@ spec:
           value: "1"
 
 ---
-
+# TODO: Part of this is currently copied from porch/controller/config/rbac/role.yaml
+# We should find a better solution.
 kind: ClusterRole
 apiVersion: rbac.authorization.k8s.io/v1
 metadata:
   name: porch-controllers
 rules:
+- apiGroups: [""]
+  resources: ["events"]
+  verbs: ["create", "patch"]
+- apiGroups: ["coordination.k8s.io"]
+  resources: ["leases"]
+  verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
 - apiGroups: ["config.porch.kpt.dev"]
   resources: ["repositories"]
   verbs: ["get", "list", "watch", "create", "update", "patch"]
-- apiGroups: ["config.cloud.google.com"]
+- apiGroups: ["config.porch.kpt.dev"]
   resources: ["remoterootsyncsets"]
   verbs: ["get", "list", "watch", "create", "update", "patch"]
-- apiGroups: ["config.cloud.google.com"]
+- apiGroups: ["config.porch.kpt.dev"]
   resources: ["remoterootsyncsets/status"]
   verbs: ["get", "list", "watch", "create", "update", "patch"]
+- apiGroups: ["config.porch.kpt.dev"]
+  resources: ["remoterootsyncsets/finalizers"]
+  verbs: ["update"]
+- apiGroups: ["config.porch.kpt.dev"]
+  resources: ["rootsyncsets"]
+  verbs: ["get", "list", "watch", "create", "update", "patch"]
+- apiGroups: ["config.porch.kpt.dev"]
+  resources: ["rootsyncsets/status"]
+  verbs: ["get", "list", "watch", "create", "update", "patch"]
+- apiGroups: ["config.porch.kpt.dev"]
+  resources: ["rootsyncsets/finalizers"]
+  verbs: ["update"]
+- apiGroups: ["config.porch.kpt.dev"]
+  resources: ["workloadidentitybindings"]
+  verbs: ["get", "list", "watch", "create", "update", "patch"]
+- apiGroups: ["config.porch.kpt.dev"]
+  resources: ["workloadidentitybindings/status"]
+  verbs: ["get", "list", "watch", "create", "update", "patch"]
+- apiGroups: ["config.porch.kpt.dev"]
+  resources: ["workloadidentitybindings/finalizers"]
+  verbs: ["update"]
 
 ---
 
diff --git a/porch/go.mod b/porch/go.mod
index 05edbda1..5e6fc7ac 100644
--- a/porch/go.mod
+++ b/porch/go.mod
@@ -17,7 +17,7 @@ require (
 	github.com/GoogleContainerTools/kpt-functions-catalog/functions/go/set-namespace v0.4.1
 	github.com/GoogleContainerTools/kpt-functions-catalog/functions/go/starlark v0.4.3
 	github.com/GoogleContainerTools/kpt-functions-sdk/go/fn v0.0.0-20220506190241-f85503febd54
-	github.com/GoogleContainerTools/kpt/porch/api v0.0.0-20220707151439-cb4c4e496c92
+	github.com/GoogleContainerTools/kpt/porch/api v0.0.0-20220821193112-4792e5fa18ee
 	github.com/bluekeyes/go-gitdiff v0.6.1
 	github.com/go-git/go-billy/v5 v5.3.1
 	github.com/go-git/go-git/v5 v5.4.3-0.20220408232334-4f916225cb2f
@@ -184,7 +184,7 @@ require (
 	k8s.io/kube-openapi v0.0.0-20220413171646-5e7f5fdc6da6 // indirect
 	k8s.io/kubectl v0.24.0 // indirect
 	sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30 // indirect
-	sigs.k8s.io/cli-utils v0.32.1-0.20220804181346-99ba319afc0b // indirect
+	sigs.k8s.io/cli-utils v0.32.1-0.20220822160143-c10935a7e36b // indirect
 	sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
 	sigs.k8s.io/kustomize/api v0.12.1 // indirect
 	sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
diff --git a/porch/go.sum b/porch/go.sum
index ba3552ee..5a461e53 100644
--- a/porch/go.sum
+++ b/porch/go.sum
@@ -1769,8 +1769,8 @@ 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.30 h1:dUk62HQ3ZFhD48Qr8MIXCiKA8wInBQCtuE4QGfFW7yA=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw=
-sigs.k8s.io/cli-utils v0.32.1-0.20220804181346-99ba319afc0b h1:Z3TNRZdGPgm0ZA1GUIDtDD0ItA9/jPfp6eceYWw/fvY=
-sigs.k8s.io/cli-utils v0.32.1-0.20220804181346-99ba319afc0b/go.mod h1:g/zB9hJ5eUN7zIEBIxrO0CwhXU4YISJ+BkLJzvWwlEs=
+sigs.k8s.io/cli-utils v0.32.1-0.20220822160143-c10935a7e36b h1:JTNhaBfNDGoVRfV3rpYxWfn70WO6pFq+1ZUDlRhZTNU=
+sigs.k8s.io/cli-utils v0.32.1-0.20220822160143-c10935a7e36b/go.mod h1:g/zB9hJ5eUN7zIEBIxrO0CwhXU4YISJ+BkLJzvWwlEs=
 sigs.k8s.io/controller-runtime v0.11.1 h1:7YIHT2QnHJArj/dk9aUkYhfqfK5cIxPOX5gPECfdZLU=
 sigs.k8s.io/controller-runtime v0.11.1/go.mod h1:KKwLiTooNGu+JmLZGn9Sl3Gjmfj66eMbCQznLP5zcqA=
 sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y=
diff --git a/porch/scripts/create-deployment-blueprint.sh b/porch/scripts/create-deployment-blueprint.sh
index 7a37115a..931b7ce1 100755
--- a/porch/scripts/create-deployment-blueprint.sh
+++ b/porch/scripts/create-deployment-blueprint.sh
@@ -168,10 +168,15 @@ function customize-sa {
 
 function main() {
   # RemoteRootSync controller
-  cp "${PORCH_DIR}/controllers/config/crd/bases/config.cloud.google.com_remoterootsyncsets.yaml" \
+  cp "${PORCH_DIR}/controllers/config/crd/bases/config.porch.kpt.dev_remoterootsyncsets.yaml" \
      "${DESTINATION}/0-remoterootsyncsets.yaml"
-  cp "${PORCH_DIR}/controllers/config/rbac/role.yaml" \
-     "${DESTINATION}/0-remoterootsync-role.yaml"
+  # WorkloadIdentityBinding controller
+  cp "${PORCH_DIR}/controllers/config/crd/bases/config.porch.kpt.dev_workloadidentitybindings.yaml" \
+     "${DESTINATION}/0-workloadidentitybindings.yaml"
+  # RootSyncSet controller
+  cp "${PORCH_DIR}/controllers/config/crd/bases/config.porch.kpt.dev_rootsyncsets.yaml" \
+     "${DESTINATION}/0-rootsyncsets.yaml"
+
   # Repository CRD
   cp "./api/porchconfig/v1alpha1/config.porch.kpt.dev_repositories.yaml" \
      "${DESTINATION}/0-repositories.yaml"
diff --git a/site/reference/cli/live/status/README.md b/site/reference/cli/live/status/README.md
index e134852e..0cc2a18a 100644
--- a/site/reference/cli/live/status/README.md
+++ b/site/reference/cli/live/status/README.md
@@ -64,6 +64,29 @@ PKG_PATH | -:
   Determines how long the command should run before exiting. This deadline will
   be enforced regardless of the value of the --poll-until flag. The default is
   to wait forever.
+  
+--inv-type:
+  Ways to get the inventory information. Must be one of the following:
+  
+  * local: Get the inventory information from the local file.
+    This will only get the inventory information of the package at the given/default path.
+  * remote: Get the inventory information by calling List API to the cluster.
+    This will retrieve a list of inventory information from the cluster.
+  
+  The default value is ‘local’.
+  
+--inv-names:
+  Filter for printing statuses of packages with specified inventory names.
+  For multiple inventory names, use comma to them.
+  This must be used with --inv-type=remote.
+  
+--namespaces:
+  Filter for printing statuses of packages under specified namespaces.
+  For multiple namespaces, use comma to separate them.
+  
+--statuses:
+  Filter for printing packages with specified statuses.
+  For multiple statuses, use comma to separate them.
 ```
 
 <!--mdtogo-->
@@ -84,6 +107,17 @@ $ kpt live status
 $ kpt live status my-app --poll-until=forever --output=table
 ```
 
+```shell
+# Monitor status for the all resources on the cluster
+# with certain inventory names and under certain namespaces.
+$ kpt live status --inv-type remote --inv-names inv1,inv2 --namespaces ns1,ns2
+```
+
+```shell
+# Monitor resources on the cluster that has Current or InProgress status
+$ kpt live status --inv-type remote --statuses Current,InProgress
+```
+
 <!--mdtogo-->
 
 [inventory template]: /reference/cli/live/apply/#prune
diff --git a/thirdparty/cli-utils/status/cmdstatus.go b/thirdparty/cli-utils/status/cmdstatus.go
deleted file mode 100644
index cabfab62..00000000
--- a/thirdparty/cli-utils/status/cmdstatus.go
+++ /dev/null
@@ -1,240 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package status
-
-import (
-	"context"
-	"fmt"
-	"os"
-	"time"
-
-	"github.com/GoogleContainerTools/kpt/internal/docs/generated/livedocs"
-	"github.com/GoogleContainerTools/kpt/internal/printer"
-	"github.com/GoogleContainerTools/kpt/internal/util/argutil"
-	"github.com/GoogleContainerTools/kpt/internal/util/strings"
-	"github.com/GoogleContainerTools/kpt/pkg/live"
-	"github.com/GoogleContainerTools/kpt/pkg/status"
-	statusprinters "github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/status/printers"
-	"github.com/go-errors/errors"
-	"github.com/spf13/cobra"
-	"k8s.io/cli-runtime/pkg/genericclioptions"
-	"k8s.io/kubectl/pkg/cmd/util"
-	"k8s.io/kubectl/pkg/util/slice"
-	"sigs.k8s.io/cli-utils/pkg/apply/poller"
-	"sigs.k8s.io/cli-utils/pkg/inventory"
-	"sigs.k8s.io/cli-utils/pkg/kstatus/polling"
-	"sigs.k8s.io/cli-utils/pkg/kstatus/polling/aggregator"
-	"sigs.k8s.io/cli-utils/pkg/kstatus/polling/collector"
-	"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 (
-	Known   = "known"
-	Current = "current"
-	Deleted = "deleted"
-	Forever = "forever"
-)
-
-var (
-	PollUntilOptions = []string{Known, Current, Deleted, Forever}
-)
-
-func NewRunner(ctx context.Context, factory util.Factory) *Runner {
-	r := &Runner{
-		ctx:               ctx,
-		pollerFactoryFunc: pollerFactoryFunc,
-		invClientFunc:     invClient,
-		factory:           factory,
-	}
-	c := &cobra.Command{
-		Use:     "status [PKG_PATH | -]",
-		PreRunE: r.preRunE,
-		RunE:    r.runE,
-		Short:   livedocs.StatusShort,
-		Long:    livedocs.StatusShort + "\n" + livedocs.StatusLong,
-		Example: livedocs.StatusExamples,
-	}
-	r.Command = c
-	c.Flags().DurationVar(&r.period, "poll-period", 2*time.Second,
-		"Polling period for resource statuses.")
-	c.Flags().StringVar(&r.pollUntil, "poll-until", "known",
-		fmt.Sprintf("When to stop polling. Must be one of %s", strings.JoinStringsWithQuotes(PollUntilOptions)))
-	c.Flags().StringVar(&r.output, "output", "events", "Output format.")
-	c.Flags().DurationVar(&r.timeout, "timeout", 0,
-		"How long to wait before exiting")
-	return r
-}
-
-func NewCommand(ctx context.Context, factory util.Factory) *cobra.Command {
-	return NewRunner(ctx, factory).Command
-}
-
-// Runner captures the parameters for the command and contains
-// the run function.
-type Runner struct {
-	ctx           context.Context
-	Command       *cobra.Command
-	factory       util.Factory
-	invClientFunc func(util.Factory) (inventory.Client, error)
-
-	period    time.Duration
-	pollUntil string
-	timeout   time.Duration
-	output    string
-
-	pollerFactoryFunc func(util.Factory) (poller.Poller, error)
-}
-
-func (r *Runner) preRunE(*cobra.Command, []string) error {
-	if !slice.ContainsString(PollUntilOptions, r.pollUntil, nil) {
-		return fmt.Errorf("pollUntil must be one of %s",
-			strings.JoinStringsWithQuotes(PollUntilOptions))
-	}
-
-	if found := printers.ValidatePrinterType(r.output); !found {
-		return fmt.Errorf("unknown output type %q", r.output)
-	}
-	return nil
-}
-
-// runE implements the logic of the command and will delegate to the
-// poller to compute status for each of the resources. One of the printer
-// implementations takes care of printing the output.
-func (r *Runner) runE(c *cobra.Command, args []string) error {
-	pr := printer.FromContextOrDie(r.ctx)
-	if len(args) == 0 {
-		// default to the current working directory
-		cwd, err := os.Getwd()
-		if err != nil {
-			return err
-		}
-		args = append(args, cwd)
-	}
-
-	path := args[0]
-	var err error
-	if args[0] != "-" {
-		path, err = argutil.ResolveSymlink(r.ctx, path)
-		if err != nil {
-			return err
-		}
-	}
-
-	_, inv, err := live.Load(r.factory, path, c.InOrStdin())
-	if err != nil {
-		return err
-	}
-
-	invInfo, err := live.ToInventoryInfo(inv)
-	if err != nil {
-		return err
-	}
-
-	invClient, err := r.invClientFunc(r.factory)
-	if err != nil {
-		return err
-	}
-
-	// Based on the inventory template manifest we look up the inventory
-	// from the live state using the inventory client.
-	identifiers, err := invClient.GetClusterObjs(invInfo)
-	if err != nil {
-		return err
-	}
-
-	// Exit here if the inventory is empty.
-	if len(identifiers) == 0 {
-		pr.Printf("no resources found in the inventory\n")
-		return nil
-	}
-
-	statusPoller, err := r.pollerFactoryFunc(r.factory)
-	if err != nil {
-		return err
-	}
-
-	// Fetch a printer implementation based on the desired output format as
-	// specified in the output flag.
-	printer, err := statusprinters.CreatePrinter(r.output, genericclioptions.IOStreams{
-		Out:    pr.OutStream(),
-		ErrOut: pr.ErrStream(),
-	})
-	if err != nil {
-		return errors.WrapPrefix(err, "error creating printer", 1)
-	}
-
-	// If the user has specified a timeout, we create a context with timeout,
-	// otherwise we create a context with cancel.
-	var ctx context.Context
-	var cancel func()
-	if r.timeout != 0 {
-		ctx, cancel = context.WithTimeout(r.ctx, r.timeout)
-	} else {
-		ctx, cancel = context.WithCancel(r.ctx)
-	}
-	defer cancel()
-
-	// Choose the appropriate ObserverFunc based on the criteria for when
-	// the command should exit.
-	var cancelFunc collector.ObserverFunc
-	switch r.pollUntil {
-	case Known:
-		cancelFunc = allKnownNotifierFunc(cancel)
-	case Current:
-		cancelFunc = desiredStatusNotifierFunc(cancel, kstatus.CurrentStatus)
-	case Deleted:
-		cancelFunc = desiredStatusNotifierFunc(cancel, kstatus.NotFoundStatus)
-	case Forever:
-		cancelFunc = func(*collector.ResourceStatusCollector, event.Event) {}
-	default:
-		return fmt.Errorf("unknown value for pollUntil: %q", r.pollUntil)
-	}
-
-	eventChannel := statusPoller.Poll(ctx, identifiers, polling.PollOptions{
-		PollInterval: r.period,
-	})
-
-	return printer.Print(eventChannel, identifiers, cancelFunc)
-}
-
-// desiredStatusNotifierFunc returns an Observer function for the
-// ResourceStatusCollector that will cancel the context (using the cancelFunc)
-// when all resources have reached the desired status.
-func desiredStatusNotifierFunc(cancelFunc context.CancelFunc,
-	desired kstatus.Status) collector.ObserverFunc {
-	return func(rsc *collector.ResourceStatusCollector, _ event.Event) {
-		var rss []*event.ResourceStatus
-		for _, rs := range rsc.ResourceStatuses {
-			rss = append(rss, rs)
-		}
-		aggStatus := aggregator.AggregateStatus(rss, desired)
-		if aggStatus == desired {
-			cancelFunc()
-		}
-	}
-}
-
-// allKnownNotifierFunc returns an Observer function for the
-// ResourceStatusCollector that will cancel the context (using the cancelFunc)
-// when all resources have a known status.
-func allKnownNotifierFunc(cancelFunc context.CancelFunc) collector.ObserverFunc {
-	return func(rsc *collector.ResourceStatusCollector, _ event.Event) {
-		for _, rs := range rsc.ResourceStatuses {
-			if rs.Status == kstatus.UnknownStatus {
-				return
-			}
-		}
-		cancelFunc()
-	}
-}
-
-func pollerFactoryFunc(f util.Factory) (poller.Poller, error) {
-	return status.NewStatusPoller(f)
-}
-
-func invClient(f util.Factory) (inventory.Client, error) {
-	return inventory.NewClient(f, live.WrapInventoryObj, live.InvToUnstructuredFunc, inventory.StatusPolicyAll)
-}
diff --git a/thirdparty/cli-utils/status/printers/list/base.go b/thirdparty/cli-utils/status/printers/list/base.go
deleted file mode 100644
index 06ec2845..00000000
--- a/thirdparty/cli-utils/status/printers/list/base.go
+++ /dev/null
@@ -1,74 +0,0 @@
-// 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.
-
-package list
-
-import (
-	"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
-// status information as a list of events as they happen.
-type BaseListPrinter struct {
-	Formatter list.Formatter
-}
-
-// Print takes an event channel and outputs the status events on the channel
-// until the channel is closed. The provided cancelFunc is consulted on
-// every event and is responsible for stopping the poller when appropriate.
-// This function will block.
-func (ep *BaseListPrinter) Print(ch <-chan pollevent.Event, identifiers []object.ObjMetadata,
-	cancelFunc collector.ObserverFunc) error {
-	coll := collector.NewResourceStatusCollector(identifiers)
-	// The actual work is done by the collector, which will invoke the
-	// callback on every event. In the callback we print the status
-	// information and call the cancelFunc which is responsible for
-	// stopping the poller at the correct time.
-	done := coll.ListenWithObserver(ch, collector.ObserverFunc(
-		func(statusCollector *collector.ResourceStatusCollector, e pollevent.Event) {
-			err := ep.printStatusEvent(e)
-			if err != nil {
-				panic(err)
-			}
-			cancelFunc(statusCollector, e)
-		}),
-	)
-	// Block until the done channel is closed.
-	<-done
-	if o := coll.LatestObservation(); o.Error != nil {
-		return o.Error
-	}
-	return nil
-}
-
-func (ep *BaseListPrinter) printStatusEvent(se pollevent.Event) error {
-	switch se.Type {
-	case pollevent.ResourceUpdateEvent:
-		id := se.Resource.Identifier
-		return ep.Formatter.FormatStatusEvent(event.StatusEvent{
-			Identifier:       id,
-			Resource:         se.Resource.Resource,
-			PollResourceInfo: se.Resource,
-		})
-	case pollevent.ErrorEvent:
-		return ep.Formatter.FormatErrorEvent(event.ErrorEvent{
-			Err: se.Error,
-		})
-	}
-	return nil
-}
diff --git a/thirdparty/cli-utils/status/printers/printer/printer.go b/thirdparty/cli-utils/status/printers/printer/printer.go
deleted file mode 100644
index 4c7aeb32..00000000
--- a/thirdparty/cli-utils/status/printers/printer/printer.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package printer
-
-import (
-	"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"
-)
-
-// Printer defines an interface for outputting information about status of
-// resources. Different implementations allow output formats tailored to
-// different use cases.
-type Printer interface {
-
-	// Print tells the printer to start outputting data. The stop parameter
-	// is a channel that the caller will use to signal to the printer that it
-	// needs to stop and shut down. The channel returned can be used by the
-	// printer implementation to signal that it has outputted all the data it
-	// needs to, and that it has completed shutting down. The latter is important
-	// to make sure the printer has a chance to output all data before the
-	// program terminates.
-	Print(ch <-chan event.Event, identifiers []object.ObjMetadata, cancelFunc collector.ObserverFunc) error
-}
diff --git a/thirdparty/cli-utils/status/printers/printers.go b/thirdparty/cli-utils/status/printers/printers.go
deleted file mode 100644
index 43386344..00000000
--- a/thirdparty/cli-utils/status/printers/printers.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package printers
-
-import (
-	"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
-// actual implementation is based on the printerType requested.
-func CreatePrinter(printerType string, ioStreams genericclioptions.IOStreams) (printer.Printer, error) {
-	switch printerType {
-	case printers.TablePrinter:
-		return table.NewTablePrinter(ioStreams), nil
-	case printers.JSONPrinter:
-		return &list.BaseListPrinter{
-			Formatter: json.NewFormatter(ioStreams, common.DryRunNone),
-		}, nil
-	default:
-		return &list.BaseListPrinter{
-			Formatter: events.NewFormatter(ioStreams, common.DryRunNone),
-		}, nil
-	}
-}
diff --git a/thirdparty/cli-utils/status/printers/table/adapter.go b/thirdparty/cli-utils/status/printers/table/adapter.go
deleted file mode 100644
index 40e37ba4..00000000
--- a/thirdparty/cli-utils/status/printers/table/adapter.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package table
-
-import (
-	"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
-// provides a set of functions that matches the interfaces
-// needed by the BaseTablePrinter.
-type CollectorAdapter struct {
-	collector *collector.ResourceStatusCollector
-}
-
-type ResourceInfo struct {
-	resourceStatus *pe.ResourceStatus
-}
-
-func (r *ResourceInfo) Identifier() object.ObjMetadata {
-	return r.resourceStatus.Identifier
-}
-
-func (r *ResourceInfo) ResourceStatus() *pe.ResourceStatus {
-	return r.resourceStatus
-}
-
-func (r *ResourceInfo) SubResources() []table.Resource {
-	var subResources []table.Resource
-	for _, rs := range r.resourceStatus.GeneratedResources {
-		subResources = append(subResources, &ResourceInfo{
-			resourceStatus: rs,
-		})
-	}
-	return subResources
-}
-
-type ResourceState struct {
-	resources []table.Resource
-	err       error
-}
-
-func (rss *ResourceState) Resources() []table.Resource {
-	return rss.resources
-}
-
-func (rss *ResourceState) Error() error {
-	return rss.err
-}
-
-func (ca *CollectorAdapter) LatestStatus() *ResourceState {
-	observation := ca.collector.LatestObservation()
-	var resources []table.Resource
-	for _, resourceStatus := range observation.ResourceStatuses {
-		resources = append(resources, &ResourceInfo{
-			resourceStatus: resourceStatus,
-		})
-	}
-	return &ResourceState{
-		resources: resources,
-		err:       observation.Error,
-	}
-}
diff --git a/thirdparty/cli-utils/status/printers/table/table_printer.go b/thirdparty/cli-utils/status/printers/table/table_printer.go
deleted file mode 100644
index fa55e408..00000000
--- a/thirdparty/cli-utils/status/printers/table/table_printer.go
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2020 The Kubernetes Authors.
-// SPDX-License-Identifier: Apache-2.0
-
-package table
-
-import (
-	"time"
-
-	"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 (
-	// updateInterval defines how often the printer will update the UI.
-	updateInterval = 1 * time.Second
-)
-
-// tablePrinter is an implementation of the Printer interface that outputs
-// status information about resources in a table format with in-place updates.
-type tablePrinter struct {
-	ioStreams genericclioptions.IOStreams
-}
-
-// NewTablePrinter returns a new instance of the tablePrinter.
-func NewTablePrinter(ioStreams genericclioptions.IOStreams) *tablePrinter {
-	return &tablePrinter{
-		ioStreams: ioStreams,
-	}
-}
-
-// Print take an event channel and outputs the status events on the channel
-// until the channel is closed .
-//nolint:interfacer
-func (t *tablePrinter) Print(ch <-chan event.Event, identifiers []object.ObjMetadata,
-	cancelFunc collector.ObserverFunc) error {
-	coll := collector.NewResourceStatusCollector(identifiers)
-	stop := make(chan struct{})
-
-	// Start the goroutine that is responsible for
-	// printing the latest state on a regular cadence.
-	printCompleted := t.runPrintLoop(&CollectorAdapter{
-		collector: coll,
-	}, stop)
-
-	// Make the collector start listening on the eventChannel.
-	done := coll.ListenWithObserver(ch, cancelFunc)
-
-	// Block until all the collector has shut down. This means the
-	// eventChannel has been closed and all events have been processed.
-	<-done
-	var err error
-	if o := coll.LatestObservation(); o.Error != nil {
-		err = o.Error
-	}
-
-	// 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
-}
-
-var columns = []table.ColumnDefinition{
-	table.MustColumn("namespace"),
-	table.MustColumn("resource"),
-	table.MustColumn("status"),
-	table.MustColumn("conditions"),
-	table.MustColumn("age"),
-	table.MustColumn("message"),
-}
-
-// Print prints the table of resources with their statuses until the
-// provided stop channel is closed.
-func (t *tablePrinter) runPrintLoop(coll *CollectorAdapter, stop <-chan struct{}) <-chan struct{} {
-	finished := make(chan struct{})
-
-	baseTablePrinter := table.BaseTablePrinter{
-		IOStreams: t.ioStreams,
-		Columns:   columns,
-	}
-
-	linesPrinted := baseTablePrinter.PrintTable(coll.LatestStatus(), 0)
-
-	go func() {
-		defer close(finished)
-		ticker := time.NewTicker(updateInterval)
-		for {
-			select {
-			case <-stop:
-				ticker.Stop()
-				linesPrinted = baseTablePrinter.PrintTable(
-					coll.LatestStatus(), linesPrinted)
-				return
-			case <-ticker.C:
-				linesPrinted = baseTablePrinter.PrintTable(
-					coll.LatestStatus(), linesPrinted)
-			}
-		}
-	}()
-
-	return finished
-}


Test patch:
diff --git a/e2e/live/end-to-end-test.sh b/e2e/live/end-to-end-test.sh
index 6cb9ac72..f69897ec 100755
--- a/e2e/live/end-to-end-test.sh
+++ b/e2e/live/end-to-end-test.sh
@@ -780,9 +780,11 @@ assertContains "Error: Inventory information has already been added to the packa
 printResult
 
 echo "Testing init force Kptfile/ResourceGroup"
-echo "kpt live init --force e2e/live/testdata/rg-test-case-1a"
-${BIN_DIR}/kpt live init --force e2e/live/testdata/rg-test-case-1a 2>&1 | tee $OUTPUT_DIR/status
+echo "kpt live init --force --name inventory-18030002 e2e/live/testdata/rg-test-case-1a"
+${BIN_DIR}/kpt live init --force --name inventory-18030002 e2e/live/testdata/rg-test-case-1a 2>&1 | tee $OUTPUT_DIR/status
 assertContains "initializing \"resourcegroup.yaml\" data (namespace: rg-test-namespace)...success"
+cat e2e/live/testdata/rg-test-case-1a/resourcegroup.yaml 2>&1 | tee $OUTPUT_DIR/status
+assertContains "name: inventory-18030002"
 printResult
 
 echo "Testing init quiet Kptfile/ResourceGroup"
@@ -905,10 +907,10 @@ ${BIN_DIR}/kpt live status link-to-rg-test-case-1a 2>&1 | tee $OUTPUT_DIR/status
 assertNotContains "installing inventory ResourceGroup CRD"
 assertKptLiveApplyEquals << EOF
 [WARN] resolved symlink "link-to-rg-test-case-1a" to "e2e/live/testdata/rg-test-case-1a", please note that the symlinks within the package are ignored
-namespace/rg-test-namespace is Current: Resource is current
-pod/pod-a is Current: Pod is Ready
-pod/pod-b is Current: Pod is Ready
-pod/pod-c is Current: Pod is Ready
+inventory-18030002/namespace//rg-test-namespace is Current: Resource is current
+inventory-18030002/pod/rg-test-namespace/pod-a is Current: Pod is Ready
+inventory-18030002/pod/rg-test-namespace/pod-b is Current: Pod is Ready
+inventory-18030002/pod/rg-test-namespace/pod-c is Current: Pod is Ready
 EOF
 printResult
 
@@ -1099,10 +1101,10 @@ 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 - 2>&1 | tee $OUTPUT_DIR/status
-assertContains "namespace/stdin-test-namespace is Current: Resource is current"
-assertContains "pod/pod-a is Current: Pod is Ready"
-assertContains "pod/pod-b is Current: Pod is Ready"
-assertContains "pod/pod-c is Current: Pod is Ready"
+assertContains "inventory-18030002/namespace//stdin-test-namespace is Current: Resource is current"
+assertContains "inventory-18030002/pod/stdin-test-namespace/pod-a is Current: Pod is Ready"
+assertContains "inventory-18030002/pod/stdin-test-namespace/pod-b is Current: Pod is Ready"
+assertContains "inventory-18030002/pod/stdin-test-namespace/pod-c is Current: Pod is Ready"
 printResult
 
 echo "cat e2e/live/testdata/stdin-test/pods.yaml | kpt live destroy -"
diff --git a/e2e/live/testdata/rg-test-case-1a/Kptfile b/e2e/live/testdata/rg-test-case-1a/Kptfile
index 09651424..fd2be7a4 100644
--- a/e2e/live/testdata/rg-test-case-1a/Kptfile
+++ b/e2e/live/testdata/rg-test-case-1a/Kptfile
@@ -8,4 +8,4 @@ upstream:
     repo: git@github.com:yuwenma/blueprint-helloworld
     directory: /
     ref: master
-
+  
\ No newline at end of file
diff --git a/internal/cmdreporeg/command_test.go b/internal/cmdreporeg/command_test.go
index 7d077ff5..67e28c82 100644
--- a/internal/cmdreporeg/command_test.go
+++ b/internal/cmdreporeg/command_test.go
@@ -100,6 +100,7 @@ func TestRepoReg(t *testing.T) {
 				"--deployment",
 				"--directory=/catalog",
 				"--branch=main-branch",
+				"--create-branch",
 				"--namespace=repository-namespace",
 			},
 			actions: []httpAction{
diff --git a/internal/cmdreporeg/testdata/full-repository.yaml b/internal/cmdreporeg/testdata/full-repository.yaml
index 486034f4..a3650ae6 100644
--- a/internal/cmdreporeg/testdata/full-repository.yaml
+++ b/internal/cmdreporeg/testdata/full-repository.yaml
@@ -10,6 +10,7 @@ spec:
     description: '"Test Repository Description"'
     git:
         branch: main-branch
+        createBranch: true
         directory: /catalog
         repo: https://github.com/platkrm/test-blueprints.git
         secretRef:
diff --git a/thirdparty/cli-utils/status/cmdstatus_test.go b/internal/cmdstatus/cmdstatus_test.go
similarity index 87%
rename from thirdparty/cli-utils/status/cmdstatus_test.go
rename to internal/cmdstatus/cmdstatus_test.go
index 73781914..f961e570 100644
--- a/thirdparty/cli-utils/status/cmdstatus_test.go
+++ b/internal/cmdstatus/cmdstatus_test.go
@@ -1,7 +1,7 @@
 // Copyright 2020 The Kubernetes Authors.
 // SPDX-License-Identifier: Apache-2.0
 
-package status
+package cmdstatus
 
 import (
 	"bytes"
@@ -64,7 +64,7 @@ func TestStatusCommand(t *testing.T) {
 		},
 		"invalid value for pollUntil": {
 			pollUntil:      "doesNotExist",
-			expectedErrMsg: "pollUntil must be one of \"known\", \"current\", \"deleted\", \"forever\"",
+			expectedErrMsg: "pollUntil must be one of known,current,deleted,forever",
 		},
 		"no inventory in live state": {
 			kptfileInv: &kptfilev1.Inventory{
@@ -105,8 +105,8 @@ func TestStatusCommand(t *testing.T) {
 				},
 			},
 			expectedOutput: `
-deployment.apps/foo is InProgress: inProgress
-statefulset.apps/bar is Current: current
+foo/deployment.apps/default/foo is InProgress: inProgress
+foo/statefulset.apps/default/bar is Current: current
 `,
 		},
 		"wait for all current": {
@@ -156,10 +156,10 @@ statefulset.apps/bar is Current: current
 				},
 			},
 			expectedOutput: `
-deployment.apps/foo is InProgress: inProgress
-statefulset.apps/bar is InProgress: inProgress
-statefulset.apps/bar is Current: current
-deployment.apps/foo is Current: current
+foo/deployment.apps/default/foo is InProgress: inProgress
+foo/statefulset.apps/default/bar is InProgress: inProgress
+foo/statefulset.apps/default/bar is Current: current
+foo/deployment.apps/default/foo is Current: current
 `,
 		},
 		"wait for all deleted": {
@@ -193,8 +193,8 @@ deployment.apps/foo is Current: current
 				},
 			},
 			expectedOutput: `
-statefulset.apps/bar is NotFound: notFound
-deployment.apps/foo is NotFound: notFound
+foo/statefulset.apps/default/bar is NotFound: notFound
+foo/deployment.apps/default/foo is NotFound: notFound
 `,
 		},
 		"forever with timeout": {
@@ -229,8 +229,8 @@ deployment.apps/foo is NotFound: notFound
 				},
 			},
 			expectedOutput: `
-statefulset.apps/bar is InProgress: inProgress
-deployment.apps/foo is InProgress: inProgress
+foo/statefulset.apps/default/bar is InProgress: inProgress
+foo/deployment.apps/default/foo is InProgress: inProgress
 `,
 		},
 	}
@@ -250,11 +250,11 @@ deployment.apps/foo is InProgress: inProgress
 			defer revert()
 
 			var outBuf bytes.Buffer
-			runner := NewRunner(fake.CtxWithPrinter(&outBuf, &outBuf), tf)
-			runner.invClientFunc = func(f cmdutil.Factory) (inventory.Client, error) {
-				return inventory.NewFakeClient(tc.inventory), nil
-			}
-			runner.pollerFactoryFunc = func(c cmdutil.Factory) (poller.Poller, error) {
+			ctx := fake.CtxWithPrinter(&outBuf, &outBuf)
+			invFactory := inventory.FakeClientFactory(tc.inventory)
+			loader := NewFakeLoader(ctx, tf, tc.inventory)
+			runner := NewRunner(ctx, tf, invFactory, loader)
+			runner.PollerFactoryFunc = func(c cmdutil.Factory) (poller.Poller, error) {
 				return &fakePoller{tc.events}, nil
 			}
 
@@ -270,6 +270,7 @@ deployment.apps/foo is InProgress: inProgress
 				}...)
 			}
 			runner.Command.SetArgs(args)
+			runner.Command.SetOut(&outBuf)
 			err := runner.Command.Execute()
 
 			if tc.expectedErrMsg != "" {
diff --git a/pkg/status/rollout_test.go b/pkg/status/rollout_test.go
new file mode 100644
index 00000000..de66b959
--- /dev/null
+++ b/pkg/status/rollout_test.go
@@ -0,0 +1,339 @@
+// 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.
+
+package status
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/cli-utils/pkg/kstatus/polling/testutil"
+	"sigs.k8s.io/cli-utils/pkg/kstatus/status"
+	"sigs.k8s.io/cli-utils/pkg/object"
+	fakemapper "sigs.k8s.io/cli-utils/pkg/testutil"
+)
+
+func TestRolloutSupports(t *testing.T) {
+	testCases := map[string]struct {
+		gk       schema.GroupKind
+		supports bool
+	}{
+		"matches rollout group": {
+			gk: schema.GroupKind{
+				Group: "argoproj.io",
+				Kind:  "Rollout",
+			},
+			supports: true,
+		},
+		"doesn't match other resources": {
+			gk: schema.GroupKind{
+				Group: "apps",
+				Kind:  "StatefulSet",
+			},
+			supports: false,
+		},
+	}
+
+	for tn, tc := range testCases {
+		t.Run(tn, func(t *testing.T) {
+			fakeMapper := fakemapper.NewFakeRESTMapper()
+
+			statusReader := &RolloutStatusReader{
+				Mapper: fakeMapper,
+			}
+
+			supports := statusReader.Supports(tc.gk)
+
+			assert.Equal(t, tc.supports, supports)
+		})
+	}
+}
+
+func TestRolloutReadStatus(t *testing.T) {
+	testCases := map[string]struct {
+		resource       string
+		gvk            schema.GroupVersionKind
+		expectedStatus status.Status
+		expectedMsg    string
+	}{
+		"Resource where observedGeneration doesn't match generation is InProgress": {
+			resource: `
+apiVersion: argoproj.io/v1alpha1
+kind: Rollout
+metadata:
+  name: pubsub.googleapis.com
+  namespace: cnrm
+  generation: 42
+status:
+  observedGeneration: "41"
+`,
+			gvk: schema.GroupVersionKind{
+				Group:   "argoproj.io",
+				Version: "v1alpha1",
+				Kind:    "Rollout",
+			},
+			expectedStatus: status.InProgressStatus,
+			expectedMsg:    "Rollout generation is 42, but latest observed generation is 41",
+		},
+		"Resource when spec.replicas more than status.replicas is InProgress": {
+			resource: `
+apiVersion: argoproj.io/v1alpha1
+kind: Rollout
+metadata:
+  name: pubsub.googleapis.com
+  namespace: cnrm
+  generation: 42
+spec:
+  replicas: 2
+status:
+  observedGeneration: "42"
+  replicas: 1
+  phase: Progressing
+`,
+			gvk: schema.GroupVersionKind{
+				Group:   "argoproj.io",
+				Kind:    "Rollout",
+				Version: "v1alpha1",
+			},
+			expectedStatus: status.InProgressStatus,
+			expectedMsg:    "replicas: 1/2",
+		},
+		"Resource when status.replicas more than spec.replicas is InProgress": {
+			resource: `
+apiVersion: argoproj.io/v1alpha1
+kind: Rollout
+metadata:
+  name: pubsub.googleapis.com
+  namespace: cnrm
+  generation: 42
+spec:
+  replicas: 1
+status:
+  observedGeneration: "42"
+  replicas: 2
+  phase: Progressing
+`,
+			gvk: schema.GroupVersionKind{
+				Group:   "argoproj.io",
+				Kind:    "Rollout",
+				Version: "v1alpha1",
+			},
+			expectedStatus: status.InProgressStatus,
+			expectedMsg:    "Pending termination: 1",
+		},
+		"Resource when status.updatedReplicas more than spec.availableReplicas is InProgress": {
+			resource: `
+apiVersion: argoproj.io/v1alpha1
+kind: Rollout
+metadata:
+  name: pubsub.googleapis.com
+  namespace: cnrm
+  generation: 42
+spec:
+  replicas: 2
+status:
+  observedGeneration: "42"
+  replicas: 2
+  updatedReplicas: 2
+  availableReplicas: 1
+  phase: Progressing
+`,
+			gvk: schema.GroupVersionKind{
+				Group:   "argoproj.io",
+				Kind:    "Rollout",
+				Version: "v1alpha1",
+			},
+			expectedStatus: status.InProgressStatus,
+			expectedMsg:    "Available: 1/2",
+		},
+		"Resource when spec.replicas more than spec.readyReplicas is InProgress": {
+			resource: `
+apiVersion: argoproj.io/v1alpha1
+kind: Rollout
+metadata:
+  name: pubsub.googleapis.com
+  namespace: cnrm
+  generation: 42
+spec:
+  replicas: 2
+status:
+  observedGeneration: "42"
+  replicas: 2
+  updatedReplicas: 2
+  availableReplicas: 2
+  readyReplicas: 1
+  phase: Progressing
+`,
+			gvk: schema.GroupVersionKind{
+				Group:   "argoproj.io",
+				Kind:    "Rollout",
+				Version: "v1alpha1",
+			},
+			expectedStatus: status.InProgressStatus,
+			expectedMsg:    "Ready: 1/2",
+		},
+		"Resource when status.phase is Degraded is Failed": {
+			resource: `
+apiVersion: argoproj.io/v1alpha1
+kind: Rollout
+metadata:
+  name: pubsub.googleapis.com
+  namespace: cnrm
+  generation: 42
+spec:
+  replicas: 2
+status:
+  observedGeneration: "42"
+  replicas: 2
+  updatedReplicas: 2
+  availableReplicas: 2
+  readyReplicas: 2
+  phase: Degraded
+`,
+			gvk: schema.GroupVersionKind{
+				Group:   "argoproj.io",
+				Kind:    "Rollout",
+				Version: "v1alpha1",
+			},
+			expectedStatus: status.FailedStatus,
+			expectedMsg:    "Ready Replicas: 2, Updated Replicas: 2",
+		},
+		"Resource when status.phase is Failed is Failed": {
+			resource: `
+apiVersion: argoproj.io/v1alpha1
+kind: Rollout
+metadata:
+  name: pubsub.googleapis.com
+  namespace: cnrm
+  generation: 42
+spec:
+  replicas: 2
+status:
+  observedGeneration: "42"
+  replicas: 2
+  updatedReplicas: 2
+  availableReplicas: 2
+  readyReplicas: 2
+  phase: Failed
+`,
+			gvk: schema.GroupVersionKind{
+				Group:   "argoproj.io",
+				Kind:    "Rollout",
+				Version: "v1alpha1",
+			},
+			expectedStatus: status.FailedStatus,
+			expectedMsg:    "Ready Replicas: 2, Updated Replicas: 2",
+		},
+		"Resource when status.phase is Healthy is Current": {
+			resource: `
+apiVersion: argoproj.io/v1alpha1
+kind: Rollout
+metadata:
+  name: pubsub.googleapis.com
+  namespace: cnrm
+  generation: 42
+spec:
+  replicas: 2
+status:
+  observedGeneration: "42"
+  replicas: 2
+  updatedReplicas: 2
+  availableReplicas: 2
+  readyReplicas: 2
+  phase: Healthy
+`,
+			gvk: schema.GroupVersionKind{
+				Group:   "argoproj.io",
+				Kind:    "Rollout",
+				Version: "v1alpha1",
+			},
+			expectedStatus: status.CurrentStatus,
+			expectedMsg:    "Ready Replicas: 2, Updated Replicas: 2",
+		},
+		"Resource when status.phase is Paused is InProgress": {
+			resource: `
+apiVersion: argoproj.io/v1alpha1
+kind: Rollout
+metadata:
+  name: pubsub.googleapis.com
+  namespace: cnrm
+  generation: 42
+spec:
+  replicas: 2
+status:
+  observedGeneration: "42"
+  replicas: 2
+  updatedReplicas: 2
+  availableReplicas: 2
+  readyReplicas: 2
+  phase: Paused
+`,
+			gvk: schema.GroupVersionKind{
+				Group:   "argoproj.io",
+				Kind:    "Rollout",
+				Version: "v1alpha1",
+			},
+			expectedStatus: status.InProgressStatus,
+			expectedMsg:    "Ready Replicas: 2, Updated Replicas: 2",
+		},
+		"Resource when status.phase is Progressing is InProgress": {
+			resource: `
+apiVersion: argoproj.io/v1alpha1
+kind: Rollout
+metadata:
+  name: pubsub.googleapis.com
+  namespace: cnrm
+  generation: 42
+spec:
+  replicas: 2
+status:
+  observedGeneration: "42"
+  replicas: 2
+  updatedReplicas: 2
+  availableReplicas: 2
+  readyReplicas: 2
+  phase: Progressing
+`,
+			gvk: schema.GroupVersionKind{
+				Group:   "argoproj.io",
+				Kind:    "Rollout",
+				Version: "v1alpha1",
+			},
+			expectedStatus: status.InProgressStatus,
+			expectedMsg:    "Ready Replicas: 2, Updated Replicas: 2",
+		},
+	}
+
+	for tn, tc := range testCases {
+		t.Run(tn, func(t *testing.T) {
+			obj := testutil.YamlToUnstructured(t, tc.resource)
+
+			fakeClusterReader := &fakeClusterReader{
+				getResource: obj,
+			}
+
+			fakeMapper := fakemapper.NewFakeRESTMapper(tc.gvk)
+			statusReader := &RolloutStatusReader{
+				Mapper: fakeMapper,
+			}
+
+			res, err := statusReader.ReadStatus(context.Background(), fakeClusterReader, object.UnstructuredToObjMetadata(obj))
+			assert.NoError(t, err)
+			assert.Equal(t, tc.expectedStatus, res.Status)
+			assert.Equal(t, tc.expectedMsg, res.Message)
+		})
+	}
+}