Repository: GoogleContainerTools/kpt
Pull request #3206 (2022-05-21 06:20:53)

docs: Update docs for inventory metadata in resourcegroup manifest
Update book and cli reference to reflect changes in https://github.com/GoogleContainerTools/kpt/pull/3172


Patch:
diff --git a/internal/cmdapply/cmdapply.go b/internal/cmdapply/cmdapply.go
index defd3c86..c57981ab 100644
--- a/internal/cmdapply/cmdapply.go
+++ b/internal/cmdapply/cmdapply.go
@@ -106,7 +106,6 @@ type Runner struct {
 	pruneTimeout                 time.Duration
 	inventoryPolicyString        string
 	dryRun                       bool
-	rgFile                       string
 	printStatusEvents            bool
 
 	inventoryPolicy inventory.Policy
@@ -165,7 +164,7 @@ func (r *Runner) runE(c *cobra.Command, args []string) error {
 		}
 	}
 
-	objs, inv, err := live.Load(r.factory, path, r.rgFile, c.InOrStdin())
+	objs, inv, err := live.Load(r.factory, path, c.InOrStdin())
 	if err != nil {
 		return err
 	}
diff --git a/internal/cmddestroy/cmddestroy.go b/internal/cmddestroy/cmddestroy.go
index 3393ffc9..6cddfd54 100644
--- a/internal/cmddestroy/cmddestroy.go
+++ b/internal/cmddestroy/cmddestroy.go
@@ -84,7 +84,6 @@ type Runner struct {
 	output                string
 	inventoryPolicyString string
 	dryRun                bool
-	rgFile                string
 	printStatusEvents     bool
 
 	inventoryPolicy inventory.Policy
@@ -130,7 +129,7 @@ func (r *Runner) runE(c *cobra.Command, args []string) error {
 		}
 	}
 
-	_, inv, err := live.Load(r.factory, path, r.rgFile, c.InOrStdin())
+	_, inv, err := live.Load(r.factory, path, c.InOrStdin())
 	if err != nil {
 		return err
 	}
diff --git a/internal/cmdfndoc/cmdfndoc.go b/internal/cmdfndoc/cmdfndoc.go
index 05c0afbf..e8ef316c 100644
--- a/internal/cmdfndoc/cmdfndoc.go
+++ b/internal/cmdfndoc/cmdfndoc.go
@@ -20,6 +20,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"os"
 	"os/exec"
 
 	"github.com/GoogleContainerTools/kpt/internal/docs/generated/fndocs"
@@ -73,10 +74,21 @@ func (r *Runner) runE(c *cobra.Command, _ []string) error {
 		r.Image,
 		"--help",
 	}
-	cmd := exec.Command("docker", dockerRunArgs...)
+	// If the env var is empty, stringToContainerRuntime defaults it to docker.
+	runtime, err := fnruntime.StringToContainerRuntime(os.Getenv(fnruntime.ContainerRuntimeEnv))
+	if err != nil {
+		return err
+	}
+
+	err = fnruntime.ContainerRuntimeAvailable(runtime)
+	if err != nil {
+		return err
+	}
+
+	cmd := exec.Command(runtime.GetBin(), dockerRunArgs...)
 	cmd.Stdout = &out
 	cmd.Stderr = &errout
-	err := cmd.Run()
+	err = cmd.Run()
 	pr := printer.FromContextOrDie(r.Ctx)
 	if err != nil {
 		pr.Printf(errout.String())
diff --git a/internal/cmdliveinit/cmdliveinit.go b/internal/cmdliveinit/cmdliveinit.go
index 75b61779..a320c49b 100644
--- a/internal/cmdliveinit/cmdliveinit.go
+++ b/internal/cmdliveinit/cmdliveinit.go
@@ -72,6 +72,7 @@ func NewRunner(ctx context.Context, factory k8scmdutil.Factory,
 
 	cmd := &cobra.Command{
 		Use:     "init [PKG_PATH]",
+		PreRunE: r.preRunE,
 		RunE:    r.runE,
 		Short:   livedocs.InitShort,
 		Long:    livedocs.InitShort + "\n" + livedocs.InitLong,
@@ -80,9 +81,10 @@ func NewRunner(ctx context.Context, factory k8scmdutil.Factory,
 	r.Command = cmd
 
 	cmd.Flags().StringVar(&r.Name, "name", "", "Inventory object name")
-	cmd.Flags().BoolVar(&r.Force, "force", false, "Set inventory values even if already set in Kptfile")
+	cmd.Flags().BoolVar(&r.Force, "force", false, "Set inventory values even if already set in Kptfile or ResourceGroup file")
 	cmd.Flags().BoolVar(&r.Quiet, "quiet", false, "If true, do not print output message for initialization")
 	cmd.Flags().StringVar(&r.InventoryID, "inventory-id", "", "Inventory id for the package")
+	cmd.Flags().StringVar(&r.RGFileName, "rg-file", rgfilev1alpha1.RGFileName, "Name of the file holding the ResourceGroup resource.")
 	return r
 }
 
@@ -99,12 +101,19 @@ type Runner struct {
 	ioStreams   genericclioptions.IOStreams
 	Force       bool   // Set inventory values even if already set in Kptfile
 	Name        string // Inventory object name
-	namespace   string // Inventory object namespace
-	RGFile      string // resourcegroup object filepath
+	RGFileName  string // resourcegroup object filename
 	InventoryID string // Inventory object unique identifier label
 	Quiet       bool   // Output message during initialization
 }
 
+func (r *Runner) preRunE(_ *cobra.Command, _ []string) error {
+	dir := filepath.Dir(filepath.Clean(r.RGFileName))
+	if dir != "." {
+		return fmt.Errorf("rg-file must be a valid filename")
+	}
+	return nil
+}
+
 func (r *Runner) runE(_ *cobra.Command, args []string) error {
 	const op errors.Op = "cmdliveinit.runE"
 	if len(args) == 0 {
@@ -137,7 +146,7 @@ func (r *Runner) runE(_ *cobra.Command, args []string) error {
 		Quiet:       r.Quiet,
 		Name:        r.Name,
 		InventoryID: r.InventoryID,
-		RGFileName:  r.RGFile,
+		RGFileName:  r.RGFileName,
 		Force:       r.Force,
 	}).Run(r.ctx)
 	if err != nil {
@@ -162,89 +171,30 @@ type ConfigureInventoryInfo struct {
 
 // Run updates the inventory info in the package given by the Path.
 func (c *ConfigureInventoryInfo) Run(ctx context.Context) error {
-	// Use ResourceGroup file for inventory logic if the resourcegroup file
-	// is set directly. For this feature gate, the resourcegroup must be directly set
-	// through our tests since we are not exposing this through the command surface as a
-	// flag, currently. When we promote this, the resourcegroup filename can be empty and
-	// the default filename value will be inferred/used.
-	if c.RGFileName != "" {
-		return c.runLiveInitWithRGFile(ctx)
-	}
-
 	const op errors.Op = "cmdliveinit.Run"
 	pr := printer.FromContextOrDie(ctx)
 
-	var name, namespace, inventoryID string
-
-	ns, err := config.FindNamespace(c.Factory.ToRawKubeConfigLoader(), c.Pkg.UniquePath.String())
+	namespace, err := config.FindNamespace(c.Factory.ToRawKubeConfigLoader(), c.Pkg.UniquePath.String())
 	if err != nil {
 		return errors.E(op, c.Pkg.UniquePath, err)
 	}
-	namespace = strings.TrimSpace(ns)
+	namespace = strings.TrimSpace(namespace)
 	if !c.Quiet {
-		pr.Printf("initializing Kptfile inventory info (namespace: %s)...", namespace)
+		pr.Printf("initializing %q data (namespace: %s)...", c.RGFileName, namespace)
 	}
 
 	// Autogenerate the name if it is not provided through the flag.
 	if c.Name == "" {
 		randomSuffix := common.RandomStr()
-		name = fmt.Sprintf("%s-%s", defaultInventoryName, randomSuffix)
-	} else {
-		name = c.Name
+		c.Name = fmt.Sprintf("%s-%s", defaultInventoryName, randomSuffix)
 	}
-	// Generate the inventory id if one is not specified through a flag.
+
+	// Autogenerate the inventory ID if not provided through the flag.
 	if c.InventoryID == "" {
-		id, err := generateID(namespace, name, time.Now())
+		c.InventoryID, err = generateID(namespace, c.Name, time.Now())
 		if err != nil {
 			return errors.E(op, c.Pkg.UniquePath, err)
 		}
-		inventoryID = id
-	} else {
-		inventoryID = c.InventoryID
-	}
-	// Finally, update these values in the Inventory section of the Kptfile.
-	err = updateKptfile(c.Pkg, &kptfilev1.Inventory{
-		Namespace:   namespace,
-		Name:        name,
-		InventoryID: inventoryID,
-	}, c.Force)
-	if !c.Quiet {
-		if err == nil {
-			pr.Printf("success\n")
-		} else {
-			pr.Printf("failed\n")
-		}
-	}
-	if err != nil {
-		return errors.E(op, c.Pkg.UniquePath, err)
-	}
-	// add metrics annotation to package resources to track the usage as the resources
-	// will be applied using kpt live group
-	at := attribution.Attributor{PackagePaths: []string{c.Pkg.UniquePath.String()}, CmdGroup: "live"}
-	at.Process()
-	return nil
-}
-
-// func runLiveInitWithRGFile is a modified version of ConfigureInventoryInfo.Run that stores the
-// package inventory information in a separate resourcegroup file. The logic for this is branched into
-// a separate function to enable feature gating.
-func (c *ConfigureInventoryInfo) runLiveInitWithRGFile(ctx context.Context) error {
-	const op errors.Op = "cmdliveinit.runLiveInitWithRGFile"
-	pr := printer.FromContextOrDie(ctx)
-
-	namespace, err := config.FindNamespace(c.Factory.ToRawKubeConfigLoader(), c.Pkg.UniquePath.String())
-	if err != nil {
-		return errors.E(op, c.Pkg.UniquePath, err)
-	}
-	namespace = strings.TrimSpace(namespace)
-	if !c.Quiet {
-		pr.Printf("initializing ResourceGroup inventory info (namespace: %s)...", namespace)
-	}
-
-	// Autogenerate the name if it is not provided through the flag.
-	if c.Name == "" {
-		randomSuffix := common.RandomStr()
-		c.Name = fmt.Sprintf("%s-%s", defaultInventoryName, randomSuffix)
 	}
 
 	// Finally, create a ResourceGroup containing the inventory information.
@@ -280,30 +230,34 @@ func createRGFile(p *pkg.Pkg, inv *kptfilev1.Inventory, filename string, force b
 	}
 
 	// Read the Kptfile to ensure that inventory information is not in Kptfile either.
+	// Ignore error if Kptfile not found as we now support live init without a Kptfile since
+	// inventory information is stored in a ResourceGroup object.
 	kf, err := p.Kptfile()
-	if err != nil {
+	if err != nil && !errors.Is(err, os.ErrNotExist) {
 		return errors.E(op, p.UniquePath, err)
 	}
 	// Validate the inventory values don't exist in Kptfile.
-	isEmpty := kptfileInventoryEmpty(kf.Inventory)
-	if !isEmpty && !force {
-		return errors.E(op, p.UniquePath, &InvInKfExistsError{})
+	isEmpty := true
+	if kf != nil {
+		isEmpty = kptfileInventoryEmpty(kf.Inventory)
+		if !isEmpty && !force {
+			return errors.E(op, p.UniquePath, &InvInKfExistsError{})
+		}
+
+		// Set the Kptfile inventory to be nil if we force write to resourcegroup instead.
+		kf.Inventory = nil
 	}
-	// Set the Kptfile inventory to be nil if we force write to resourcegroup instead.
-	kf.Inventory = nil
 
 	// Validate the inventory values don't already exist in Resourcegroup.
 	if rg != nil && !force {
-		return errors.E(op, p.UniquePath, &InvExistsError{})
+		return errors.E(op, p.UniquePath, &InvInRGExistsError{})
 	}
 	// Initialize new resourcegroup object, as rg should have been nil.
 	rg = &rgfilev1alpha1.ResourceGroup{ResourceMeta: rgfilev1alpha1.DefaultMeta}
 	// // Finally, set the inventory parameters in the ResourceGroup object and write it.
 	rg.Name = inv.Name
 	rg.Namespace = inv.Namespace
-	if inv.InventoryID != "" {
-		rg.Labels = map[string]string{rgfilev1alpha1.RGInventoryIDLabel: inv.InventoryID}
-	}
+	rg.Labels = map[string]string{rgfilev1alpha1.RGInventoryIDLabel: inv.InventoryID}
 	if err := writeRGFile(p.UniquePath.String(), rg, filename); err != nil {
 		return errors.E(op, p.UniquePath, err)
 	}
@@ -338,27 +292,6 @@ func writeRGFile(dir string, rg *rgfilev1alpha1.ResourceGroup, filename string)
 	return nil
 }
 
-// Run fills in the inventory object values into the Kptfile.
-func updateKptfile(p *pkg.Pkg, inv *kptfilev1.Inventory, force bool) error {
-	const op errors.Op = "cmdliveinit.updateKptfile"
-	// Read the Kptfile io io.dir
-	kf, err := p.Kptfile()
-	if err != nil {
-		return errors.E(op, p.UniquePath, err)
-	}
-	// Validate the inventory values don't already exist
-	isEmpty := kptfileInventoryEmpty(kf.Inventory)
-	if !isEmpty && !force {
-		return errors.E(op, p.UniquePath, &InvExistsError{})
-	}
-	// Finally, set the inventory parameters in the Kptfile and write it.
-	kf.Inventory = inv
-	if err := kptfileutil.WriteFile(p.UniquePath.String(), kf); err != nil {
-		return errors.E(op, p.UniquePath, err)
-	}
-	return nil
-}
-
 // generateID returns the string which is a SHA1 hash of the passed namespace
 // and name, with the unix timestamp string concatenated. Returns an error
 // if either the namespace or name are empty.
diff --git a/internal/cmdmigrate/migratecmd.go b/internal/cmdmigrate/migratecmd.go
index d3cac2c2..26c89544 100644
--- a/internal/cmdmigrate/migratecmd.go
+++ b/internal/cmdmigrate/migratecmd.go
@@ -20,6 +20,7 @@ import (
 	"github.com/GoogleContainerTools/kpt/internal/types"
 	"github.com/GoogleContainerTools/kpt/internal/util/argutil"
 	"github.com/GoogleContainerTools/kpt/internal/util/pathutil"
+	rgfilev1alpha1 "github.com/GoogleContainerTools/kpt/pkg/api/resourcegroup/v1alpha1"
 	"github.com/GoogleContainerTools/kpt/pkg/kptfile/kptfileutil"
 	"github.com/GoogleContainerTools/kpt/pkg/live"
 	"github.com/spf13/cobra"
@@ -93,6 +94,7 @@ func NewRunner(
 	cmd.Flags().StringVar(&r.name, "name", "", "Inventory object name")
 	cmd.Flags().BoolVar(&r.force, "force", false, "Set inventory values even if already set in Kptfile")
 	cmd.Flags().BoolVar(&r.dryRun, "dry-run", false, "Do not actually migrate, but show steps")
+	cmd.Flags().StringVar(&r.rgFile, "rg-file", rgfilev1alpha1.RGFileName, "The file path to the ResourceGroup object.")
 
 	r.Command = cmd
 	return r
@@ -107,15 +109,6 @@ func NewCommand(ctx context.Context, f util.Factory, cmLoader manifestreader.Man
 // Run executes the migration from the ConfigMap based inventory to the ResourceGroup
 // based inventory.
 func (mr *MigrateRunner) Run(reader io.Reader, args []string) error {
-	// Use ResourceGroup file for inventory logic if the resourcegroup file
-	// is set directly. For this feature gate, the resourcegroup must be directly set
-	// through our tests since we are not exposing this through the command surface as a
-	// flag, currently. When we promote this, the resourcegroup filename can be empty and
-	// the default filename value will be inferred/used.
-	if mr.rgFile != "" {
-		return mr.runLiveMigrateWithRGFile(reader, args)
-	}
-
 	// Validate the number of arguments.
 	if len(args) > 1 {
 		return fmt.Errorf("too many arguments; migrate requires one directory argument (or stdin)")
@@ -141,53 +134,22 @@ func (mr *MigrateRunner) Run(reader io.Reader, args []string) error {
 		}
 	}
 
-	// Create the inventory clients for reading inventories based on RG and
-	// ConfigMap.
-	rgInvClient, err := mr.rgInvClientFunc(mr.factory)
-	if err != nil {
-		return err
-	}
-	cmInvClient, err := mr.cmInvClientFunc(mr.factory)
-	if err != nil {
-		return err
-	}
-
 	// Apply the ResourceGroup CRD to the cluster, ignoring if it already exists.
 	if err := mr.applyCRD(); err != nil {
 		return err
 	}
-	// Retrieve the current ConfigMap inventory objects.
-	cmInvObj, err := mr.retrieveConfigMapInv(bytes.NewReader(stdinBytes), args)
-	if err != nil {
-		if _, ok := err.(*inventory.NoInventoryObjError); ok {
-			// No ConfigMap inventory means the migration has already run before.
-			klog.V(4).Infoln("swallowing no ConfigMap inventory error")
-			return nil
-		}
-		klog.V(4).Infof("error retrieving ConfigMap inventory object: %s", err)
-		return err
-	}
-	cmInventoryID := cmInvObj.ID()
-	klog.V(4).Infof("previous inventoryID: %s", cmInventoryID)
-	// Update the Kptfile with the resource group values (e.g. namespace, name, id).
-	if err := mr.updateKptfile(mr.ctx, args, cmInventoryID); err != nil {
-		return err
-	}
-	cmObjs, err := mr.retrieveInvObjs(cmInvClient, cmInvObj)
-	if err != nil {
+
+	// Check if we need to migrate from ConfigMap to ResourceGroup.
+	if err := mr.migrateCMToRG(stdinBytes, args); err != nil {
 		return err
 	}
-	if len(cmObjs) > 0 {
-		// Migrate the ConfigMap inventory objects to a ResourceGroup custom resource.
-		if err = mr.migrateObjs(rgInvClient, cmObjs, bytes.NewReader(stdinBytes), args); err != nil {
-			return err
-		}
-		// Delete the old ConfigMap inventory object.
-		if err = mr.deleteConfigMapInv(cmInvClient, cmInvObj); err != nil {
-			return err
-		}
+
+	// Migrate from Kptfile instead.
+	if mr.cmNotMigrated {
+		return mr.migrateKptfileToRG(args)
 	}
-	return mr.deleteConfigMapFile()
+
+	return nil
 }
 
 // applyCRD applies the ResourceGroup custom resource definition, returning an
@@ -213,39 +175,6 @@ func (mr *MigrateRunner) applyCRD() error {
 	return err
 }
 
-// updateKptfile installs the "inventory" fields in the Kptfile.
-func (mr *MigrateRunner) updateKptfile(ctx context.Context, args []string, prevID string) error {
-	fmt.Fprint(mr.ioStreams.Out, "  updating Kptfile inventory values...")
-	if !mr.dryRun {
-		absPath, _, err := pathutil.ResolveAbsAndRelPaths(args[0])
-		if err != nil {
-			return err
-		}
-		p, err := pkg.New(filesys.FileSystemOrOnDisk{}, absPath)
-		if err != nil {
-			return err
-		}
-		err = (&cmdliveinit.ConfigureInventoryInfo{
-			Pkg:         p,
-			Factory:     mr.factory,
-			Quiet:       true,
-			InventoryID: prevID,
-			Force:       mr.force,
-		}).Run(ctx)
-
-		if err != nil {
-			var invExistsError *cmdliveinit.InvExistsError
-			if errors.As(err, &invExistsError) {
-				fmt.Fprint(mr.ioStreams.Out, "values already exist...")
-			} else {
-				return err
-			}
-		}
-	}
-	fmt.Fprint(mr.ioStreams.Out, "success\n")
-	return nil
-}
-
 // retrieveConfigMapInv retrieves the ConfigMap inventory object or
 // an error if one occurred.
 func (mr *MigrateRunner) retrieveConfigMapInv(reader io.Reader, args []string) (inventory.Info, error) {
@@ -262,7 +191,10 @@ func (mr *MigrateRunner) retrieveConfigMapInv(reader io.Reader, args []string) (
 	if err != nil {
 		return nil, err
 	}
-	if cmInvObj == nil {
+
+	// cli-utils treats any resource that contains the inventory-id label as an inventory object. We should
+	// ignore any inventories that are stored as ResourceGroup resources since they do not need migration.
+	if cmInvObj == nil || cmInvObj.GetKind() == rgfilev1alpha1.RGFileKind {
 		// No ConfigMap inventory means the migration has already run before.
 		fmt.Fprintln(mr.ioStreams.Out, "no ConfigMap inventory...completed")
 		return nil, &inventory.NoInventoryObjError{}
@@ -313,7 +245,7 @@ func (mr *MigrateRunner) migrateObjs(rgInvClient inventory.Client,
 		}
 	}
 
-	_, inv, err := live.Load(mr.factory, path, mr.rgFile, reader)
+	_, inv, err := live.Load(mr.factory, path, reader)
 	if err != nil {
 		return err
 	}
@@ -412,53 +344,6 @@ func cmInvClient(factory util.Factory) (inventory.Client, error) {
 	return inventory.NewClient(factory, inventory.WrapInventoryObj, inventory.InvInfoToConfigMap, inventory.StatusPolicyAll)
 }
 
-// func runLiveMigrateWithRGFile is a modified version of MigrateRunner.Run that stores the
-// package inventory information in a separate resourcegroup file. The logic for this is branched into
-// a separate function to enable feature gating.
-func (mr *MigrateRunner) runLiveMigrateWithRGFile(reader io.Reader, args []string) error {
-	// Validate the number of arguments.
-	if len(args) > 1 {
-		return fmt.Errorf("too many arguments; migrate requires one directory argument (or stdin)")
-	}
-	// Validate argument is a directory.
-	if len(args) == 1 {
-		var err error
-		mr.dir, err = config.NormalizeDir(args[0])
-		if err != nil {
-			return err
-		}
-	}
-	// Store the stdin bytes if necessary so they can be used twice.
-	var stdinBytes []byte
-	var err error
-	if len(args) == 0 {
-		stdinBytes, err = ioutil.ReadAll(reader)
-		if err != nil {
-			return err
-		}
-		if len(stdinBytes) == 0 {
-			return fmt.Errorf("no arguments means stdin has data; missing bytes on stdin")
-		}
-	}
-
-	// Apply the ResourceGroup CRD to the cluster, ignoring if it already exists.
-	if err := mr.applyCRD(); err != nil {
-		return err
-	}
-
-	// Check if we need to migrate from ConfigMap to ResourceGroup.
-	if err := mr.migrateCMToRG(stdinBytes, args); err != nil {
-		return err
-	}
-
-	// Migrate from Kptfile instead.
-	if mr.cmNotMigrated {
-		return mr.migrateKptfileToRG(args)
-	}
-
-	return nil
-}
-
 // migrateKptfileToRG extracts inventory information from a package's Kptfile
 // into an external resourcegroup file.
 func (mr *MigrateRunner) migrateKptfileToRG(args []string) error {
@@ -466,7 +351,10 @@ func (mr *MigrateRunner) migrateKptfileToRG(args []string) error {
 	klog.V(4).Infoln("attempting to migrate from Kptfile inventory")
 	fmt.Fprint(mr.ioStreams.Out, "  reading existing Kptfile...")
 	if !mr.dryRun {
-		dir := args[0]
+		dir, _, err := pathutil.ResolveAbsAndRelPaths(args[0])
+		if err != nil {
+			return err
+		}
 		p, err := pkg.New(filesys.FileSystemOrOnDisk{}, dir)
 		if err != nil {
 			return err
@@ -477,8 +365,8 @@ func (mr *MigrateRunner) migrateKptfileToRG(args []string) error {
 		}
 
 		if _, err := kptfileutil.ValidateInventory(kf.Inventory); err != nil {
-			// Invalid Kptfile.
-			return err
+			// Kptfile does not contain inventory: migration is not needed.
+			return nil
 		}
 
 		// Make sure resourcegroup file does not exist.
@@ -559,7 +447,11 @@ func (mr *MigrateRunner) migrateCMToRG(stdinBytes []byte, args []string) error {
 func (mr *MigrateRunner) createRGfile(ctx context.Context, args []string, prevID string) error {
 	fmt.Fprint(mr.ioStreams.Out, "  creating ResourceGroup object file...")
 	if !mr.dryRun {
-		p, err := pkg.New(filesys.FileSystemOrOnDisk{}, args[0])
+		dir, _, err := pathutil.ResolveAbsAndRelPaths(args[0])
+		if err != nil {
+			return err
+		}
+		p, err := pkg.New(filesys.FileSystemOrOnDisk{}, dir)
 		if err != nil {
 			return err
 		}
diff --git a/internal/cmdplan/command.go b/internal/cmdplan/command.go
index 2ec5b143..1e8ac795 100644
--- a/internal/cmdplan/command.go
+++ b/internal/cmdplan/command.go
@@ -120,7 +120,7 @@ func (r *Runner) RunE(c *cobra.Command, args []string) error {
 
 	// Load the resources from disk or stdin and extract the
 	// inventory information.
-	objs, inv, err := live.Load(r.factory, path, "", c.InOrStdin())
+	objs, inv, err := live.Load(r.factory, path, c.InOrStdin())
 	if err != nil {
 		return err
 	}
diff --git a/internal/docs/generated/livedocs/docs.go b/internal/docs/generated/livedocs/docs.go
index e51373b2..6dc9cb14 100644
--- a/internal/docs/generated/livedocs/docs.go
+++ b/internal/docs/generated/livedocs/docs.go
@@ -15,8 +15,8 @@ Args:
 
   PKG_PATH | -:
     Path to the local package which should be applied to the cluster. It must
-    contain a Kptfile with inventory information. Defaults to the current working
-    directory.
+    contain a Kptfile or a ResourceGroup manifest with inventory metadata.
+    Defaults to the current working directory.
     Using '-' as the package path will cause kpt to read resources from stdin.
 
 Flags:
@@ -114,8 +114,8 @@ Args:
 
   PKG_PATH | -:
     Path to the local package which should be deleted from the cluster. It must
-    contain a Kptfile with inventory information. Defaults to the current working
-    directory.
+    contain a Kptfile or a ResourceGroup manifest with inventory metadata.
+    Defaults to the current working directory.
     Using '-' as the package path will cause kpt to read resources from stdin.
 
 Flags:
@@ -188,6 +188,10 @@ Flags:
     for the package. If not provided, kpt will check if all the resources
     in the package belong in the same namespace. If they do, that namespace will
     be used. If they do not, the namespace in the user's context will be chosen.
+  
+  --rg-file:
+    The name used for the file created for the ResourceGroup CR. Defaults to
+    'resourcegroup.yaml'.
 `
 var InitExamples = `
   # initialize a package in the current directory.
@@ -213,8 +217,9 @@ var MigrateLong = `
 Args:
 
   PKG_PATH:
-    Path to the local package. It must have a Kptfile and an existing inventory
-    template in the root of the package. It defaults to the current directory.
+    Path to the local package. It must have a Kptfile and inventory metadata
+    in the package in either the ConfigMap, Kptfile or ResourceGroup format.
+    It defaults to the current directory.
 
 Flags:
 
@@ -222,18 +227,18 @@ Flags:
     Go through the steps of migration, but don't make any changes.
   
   --force:
-    Forces the inventory values in the Kptfile to be updated, even if they are
-    already set. Defaults to false.
+    Forces the inventory values in the ResourceGroup manfiest to be updated,
+    even if they are already set. Defaults to false.
   
   --name:
     The name for the ResourceGroup resource that contains the inventory
-    for the package. Defaults to the same name as the existing ConfigMap
-    inventory object.
+    for the package. Defaults to the same name as the existing inventory
+    object.
   
   --namespace:
     The namespace for the ResourceGroup resource that contains the inventory
     for the package. If not provided, it defaults to the same namespace as the
-    existing ConfigMap inventory object.
+    existing inventory object.
 `
 var MigrateExamples = `
   # Migrate the package in the current directory.
@@ -248,7 +253,8 @@ Args:
 
   PKG_PATH | -:
     Path to the local package for which the status of the package in the cluster
-    should be displayed. It must contain a Kptfile with inventory information.
+    should be displayed. It must contain either a Kptfile or a ResourceGroup CR
+    with inventory metadata.
     Defaults to the current working directory.
     Using '-' as the package path will cause kpt to read resources from stdin.
 
diff --git a/internal/errors/resolver/live.go b/internal/errors/resolver/live.go
index 29bfad1e..6086601a 100644
--- a/internal/errors/resolver/live.go
+++ b/internal/errors/resolver/live.go
@@ -18,6 +18,7 @@ import (
 	"github.com/GoogleContainerTools/kpt/internal/cmdliveinit"
 	"github.com/GoogleContainerTools/kpt/internal/cmdutil"
 	"github.com/GoogleContainerTools/kpt/internal/errors"
+	"github.com/GoogleContainerTools/kpt/internal/pkg"
 	"github.com/GoogleContainerTools/kpt/pkg/live"
 	"sigs.k8s.io/cli-utils/pkg/inventory"
 	"sigs.k8s.io/cli-utils/pkg/manifestreader"
@@ -59,7 +60,7 @@ Error: The ResourceGroup CRD was not found in the cluster. Please install it eit
 
 	//nolint:lll
 	invInfoAlreadyExistsMsg = `
-Error: Inventory information has already been added to the package Kptfile. Changing it after a package has been applied to the cluster can lead to undesired results. Use the --force flag to suppress this error.
+Error: Inventory information has already been added to the package. Changing it after a package has been applied to the cluster can lead to undesired results. Use the --force flag to suppress this error.
 `
 
 	//nolint:lll
@@ -72,13 +73,13 @@ Error: Inventory information has already been added to the package ResourceGroup
 Error: Inventory information has already been added to the package Kptfile object. Please consider migrating to a standalone resourcegroup object using the 'kpt live migrate' command.
 `
 
-	multipleInvInfoMsg = `
-Error: Multiple Kptfile resources with inventory information found. Please make sure at most one Kptfile resource contains inventory information.
+	multipleResourceGroupsMsg = `
+Error: Multiple ResourceGroup objects found. Please make sure at most one ResourceGroup object exists within the package.
 `
 
 	//nolint:lll
 	inventoryInfoValidationMsg = `
-Error: The inventory information is not valid. Please update the information in the Kptfile or provide information with the command line flags. To generate the inventory information the first time, use the 'kpt live init' command.
+Error: The inventory information is not valid. Please update the information in the ResourceGroup file or provide information with the command line flags. To generate the inventory information the first time, use the 'kpt live init' command.
 
 Details:
 {{- range .err.Violations}}
@@ -164,11 +165,11 @@ func (*liveErrorResolver) Resolve(err error) (ResolvedResult, bool) {
 		}, true
 	}
 
-	var multipleInvInfoError *live.MultipleInventoryInfoError
-	if errors.As(err, &multipleInvInfoError) {
+	var multipleResourceGroupsError *pkg.MultipleResourceGroupsError
+	if errors.As(err, &multipleResourceGroupsError) {
 		return ResolvedResult{
-			Message: ExecuteTemplate(multipleInvInfoMsg, map[string]interface{}{
-				"err": *multipleInvInfoError,
+			Message: ExecuteTemplate(multipleResourceGroupsMsg, map[string]interface{}{
+				"err": *multipleResourceGroupsError,
 			}),
 		}, true
 	}
diff --git a/internal/fnruntime/container.go b/internal/fnruntime/container.go
index ef2281a6..3cfaa8d9 100644
--- a/internal/fnruntime/container.go
+++ b/internal/fnruntime/container.go
@@ -101,18 +101,27 @@ type ContainerFn struct {
 	FnResult *fnresult.Result
 }
 
+func (r ContainerRuntime) GetBin() string {
+	switch r {
+	case Podman:
+		return podmanBin
+	default:
+		return dockerBin
+	}
+}
+
 // Run runs the container function using docker runtime.
 // It reads the input from the given reader and writes the output
 // to the provided writer.
 func (f *ContainerFn) Run(reader io.Reader, writer io.Writer) error {
 	// If the env var is empty, stringToContainerRuntime defaults it to docker.
-	runtime, err := stringToContainerRuntime(os.Getenv(ContainerRuntimeEnv))
+	runtime, err := StringToContainerRuntime(os.Getenv(ContainerRuntimeEnv))
 	if err != nil {
 		return err
 	}
 
 	checkContainerRuntimeOnce.Do(func() {
-		err = containerRuntimeAvailable(runtime)
+		err = ContainerRuntimeAvailable(runtime)
 	})
 	if err != nil {
 		return err
@@ -339,7 +348,7 @@ func isPodmanCLIoutput(s string) bool {
 	return false
 }
 
-func stringToContainerRuntime(v string) (ContainerRuntime, error) {
+func StringToContainerRuntime(v string) (ContainerRuntime, error) {
 	switch strings.ToLower(v) {
 	case string(Docker):
 		return Docker, nil
@@ -352,7 +361,7 @@ func stringToContainerRuntime(v string) (ContainerRuntime, error) {
 	}
 }
 
-func containerRuntimeAvailable(runtime ContainerRuntime) error {
+func ContainerRuntimeAvailable(runtime ContainerRuntime) error {
 	switch runtime {
 	case Docker:
 		return dockerCmdAvailable()
diff --git a/internal/pkg/pkg.go b/internal/pkg/pkg.go
index 6bcb3610..68b0bcf6 100644
--- a/internal/pkg/pkg.go
+++ b/internal/pkg/pkg.go
@@ -26,6 +26,7 @@ import (
 	"github.com/GoogleContainerTools/kpt/internal/errors"
 	"github.com/GoogleContainerTools/kpt/internal/types"
 	"github.com/GoogleContainerTools/kpt/internal/util/git"
+	"github.com/GoogleContainerTools/kpt/internal/util/pathutil"
 	kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1"
 	rgfilev1alpha1 "github.com/GoogleContainerTools/kpt/pkg/api/resourcegroup/v1alpha1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
@@ -119,6 +120,49 @@ func (rg *RGError) Unwrap() error {
 	return rg.Err
 }
 
+// MultipleResourceGroupsError is the error returned if there are multiple
+// inventories provided in a stream or package as ResourceGroup objects.
+type MultipleResourceGroupsError struct{}
+
+func (e *MultipleResourceGroupsError) Error() string {
+	return "multiple ResourceGroup objects found in package"
+}
+
+// MultipleKfInv is the error returned if there are multiple
+// inventories provided in a stream or package as ResourceGroup objects.
+type MultipleKfInv struct{}
+
+func (e *MultipleKfInv) Error() string {
+	return "multiple Kptfile inventories found in package"
+}
+
+// MultipleInventoryInfoError is the error returned if there are multiple
+// inventories provided in a stream or package contained with both Kptfile and
+// ResourceGroup objects.
+type MultipleInventoryInfoError struct{}
+
+func (e *MultipleInventoryInfoError) Error() string {
+	return "inventory was found in both Kptfile and ResourceGroup object"
+}
+
+// NoInvInfoError is the error returned if there are no inventory information
+// provided in either a stream or locally.
+type NoInvInfoError struct{}
+
+func (e *NoInvInfoError) Error() string {
+	return "no ResourceGroup object was provided within the stream or package"
+}
+
+type InvInfoInvalid struct{}
+
+func (e *InvInfoInvalid) Error() string {
+	return "the provided ResourceGroup is not valid"
+}
+
+//nolint:lll
+// warnInvInKptfile is the warning message when the inventory information is present within the Kptfile.
+const warnInvInKptfile = "[WARN] The resourcegroup file was not found... Using Kptfile to gather inventory information. We recommend migrating to a resourcegroup file for inventories. Please migrate with `kpt live migrate`."
+
 // Pkg represents a kpt package with a one-to-one mapping to a directory on the local filesystem.
 type Pkg struct {
 	// fsys represents the FileSystem of the package, it may or may not be FileSystem on disk
@@ -573,9 +617,9 @@ func RemovePkgPathAnnotation(rn *yaml.RNode) error {
 }
 
 // ReadRGFile returns the resourcegroup object by lazy loading it from the filesytem.
-func (p *Pkg) ReadRGFile(filename string) (*rgfilev1alpha1.ResourceGroup, error) {
+func (p *Pkg) ReadRGFile(rgfile string) (*rgfilev1alpha1.ResourceGroup, error) {
 	if p.rgFile == nil {
-		rg, err := ReadRGFile(p.UniquePath.String(), filename)
+		rg, err := ReadRGFile(p.UniquePath.String(), rgfile)
 		if err != nil {
 			return nil, err
 		}
@@ -587,12 +631,29 @@ func (p *Pkg) ReadRGFile(filename string) (*rgfilev1alpha1.ResourceGroup, error)
 // TODO(rquitales): Consolidate both Kptfile and ResourceGroup file reading functions to use
 // shared logic/function.
 
-// ReadRGFile reads the KptFile in the given pkg.
-func ReadRGFile(path, filename string) (*rgfilev1alpha1.ResourceGroup, error) {
-	f, err := os.Open(filepath.Join(path, filename))
+// ReadRGFile reads the resourcegroup inventory in the given pkg.
+func ReadRGFile(pkgPath, rgfile string) (*rgfilev1alpha1.ResourceGroup, error) {
+	// Check to see if filename for ResourceGroup is a filepath, rather than being relative to the pkg path.
+	// If only a filename is provided, we assume that the resourcegroup file is relative to the pkg path.
+	var absPath string
+	if filepath.Base(rgfile) == rgfile {
+		absPath = filepath.Join(pkgPath, rgfile)
+	} else {
+		rgFilePath, _, err := pathutil.ResolveAbsAndRelPaths(rgfile)
+		if err != nil {
+			return nil, &RGError{
+				Path: types.UniquePath(rgfile),
+				Err:  err,
+			}
+		}
+
+		absPath = rgFilePath
+	}
+
+	f, err := os.Open(absPath)
 	if err != nil {
 		return nil, &RGError{
-			Path: types.UniquePath(path),
+			Path: types.UniquePath(absPath),
 			Err:  err,
 		}
 	}
@@ -601,7 +662,7 @@ func ReadRGFile(path, filename string) (*rgfilev1alpha1.ResourceGroup, error) {
 	rg, err := DecodeRGFile(f)
 	if err != nil {
 		return nil, &RGError{
-			Path: types.UniquePath(path),
+			Path: types.UniquePath(absPath),
 			Err:  err,
 		}
 	}
@@ -623,3 +684,105 @@ func DecodeRGFile(in io.Reader) (*rgfilev1alpha1.ResourceGroup, error) {
 	}
 	return rg, nil
 }
+
+// LocalInventory returns the package inventory stored within a package. If more than one, or no inventories are
+// found, an error is returned instead.
+func (p *Pkg) LocalInventory() (kptfilev1.Inventory, error) {
+	const op errors.Op = "pkg.LocalInventory"
+
+	pkgReader := &kio.LocalPackageReader{
+		PackagePath:        string(p.UniquePath),
+		PackageFileName:    kptfilev1.KptFileName,
+		IncludeSubpackages: false,
+		MatchFilesGlob:     kio.MatchAll,
+		PreserveSeqIndent:  true,
+		SetAnnotations: map[string]string{
+			pkgPathAnnotation: string(p.UniquePath),
+		},
+		WrapBareSeqNode: true,
+		FileSystem: filesys.FileSystemOrOnDisk{
+			FileSystem: p.fsys,
+		},
+	}
+	resources, err := pkgReader.Read()
+	if err != nil {
+		return kptfilev1.Inventory{}, errors.E(op, p.UniquePath, err)
+	}
+
+	resources, err = filterResourceGroups(resources)
+	if err != nil {
+		return kptfilev1.Inventory{}, errors.E(op, p.UniquePath, err)
+	}
+
+	// Multiple ResourceGroups found.
+	if len(resources) > 1 {
+		return kptfilev1.Inventory{}, &MultipleResourceGroupsError{}
+	}
+
+	// Load Kptfile and check if we have any inventory information there.
+	var hasKptfile bool
+	hasKptfile, err = IsPackageDir(p.fsys, p.UniquePath.String())
+	if err != nil {
+		return kptfilev1.Inventory{}, errors.E(op, p.UniquePath, err)
+	}
+
+	if !hasKptfile {
+		// Return the ResourceGroup object as inventory.
+		if len(resources) == 1 {
+			return kptfilev1.Inventory{
+				Name:        resources[0].GetName(),
+				Namespace:   resources[0].GetNamespace(),
+				InventoryID: resources[0].GetLabels()[rgfilev1alpha1.RGInventoryIDLabel],
+			}, nil
+		}
+
+		// No inventory information found as ResourceGroup objects, and Kptfile does not exist.
+		return kptfilev1.Inventory{}, &NoInvInfoError{}
+	}
+
+	kf, err := p.Kptfile()
+	if err != nil {
+		return kptfilev1.Inventory{}, errors.E(op, p.UniquePath, err)
+	}
+
+	// No inventory found in either Kptfile or as ResourceGroup objects.
+	if kf.Inventory == nil && len(resources) == 0 {
+		return kptfilev1.Inventory{}, &NoInvInfoError{}
+	}
+
+	// Multiple inventories found, in both Kptfile and resourcegroup objects.
+	if kf.Inventory != nil && len(resources) > 0 {
+		return kptfilev1.Inventory{}, &MultipleInventoryInfoError{}
+	}
+
+	// ResourceGroup stores the inventory and Kptfile does not contain inventory.
+	if len(resources) == 1 {
+		return kptfilev1.Inventory{
+			Name:        resources[0].GetName(),
+			Namespace:   resources[0].GetNamespace(),
+			InventoryID: resources[0].GetLabels()[rgfilev1alpha1.RGInventoryIDLabel],
+		}, nil
+	}
+
+	// Kptfile stores the inventory.
+	fmt.Println(warnInvInKptfile)
+	return *kf.Inventory, nil
+}
+
+// filterResourceGroups only retains ResourceGroup objects.
+func filterResourceGroups(input []*yaml.RNode) (output []*yaml.RNode, err error) {
+	for _, r := range input {
+		meta, err := r.GetMeta()
+		if err != nil {
+			return nil, fmt.Errorf("failed to read metadata for resource %w", err)
+		}
+		// Filter out any non-ResourceGroup files.
+		if !(meta.APIVersion == rgfilev1alpha1.RGFileAPIVersion && meta.Kind == rgfilev1alpha1.RGFileKind) {
+			continue
+		}
+
+		output = append(output, r)
+	}
+
+	return output, nil
+}
diff --git a/pkg/api/kptfile/v1/types.go b/pkg/api/kptfile/v1/types.go
index a21028b2..c5bd8883 100644
--- a/pkg/api/kptfile/v1/types.go
+++ b/pkg/api/kptfile/v1/types.go
@@ -338,8 +338,6 @@ type Inventory struct {
 }
 
 func (i Inventory) IsValid() bool {
-	// Name and Namespace are required inventory fields, so we check these 2 fields.
-	// InventoryID is an optional field since we only store it locally if the user
-	// specifies one.
-	return i.Name != "" && i.Namespace != ""
+	// Name, Namespace InventoryID are required inventory fields, so we check these 3 fields.
+	return i.Name != "" && i.Namespace != "" && i.InventoryID != ""
 }
diff --git a/pkg/live/load.go b/pkg/live/load.go
index 722ac48f..424ffab7 100644
--- a/pkg/live/load.go
+++ b/pkg/live/load.go
@@ -18,7 +18,6 @@ import (
 	"bytes"
 	"fmt"
 	"io"
-	"os"
 
 	"github.com/GoogleContainerTools/kpt/internal/errors"
 	"github.com/GoogleContainerTools/kpt/internal/pkg"
@@ -36,6 +35,10 @@ import (
 	"sigs.k8s.io/kustomize/kyaml/yaml"
 )
 
+// inventoryIDfmt is the string format used for generating an inventoryID that is stored on the live cluster
+// if one is not provided when the user runs `kpt live init`. This format should be of `namespace-name`.
+const inventoryIDfmt = "%s-%s"
+
 // InventoryInfoValidationError is the error returned if validation of the
 // inventory information fails.
 type InventoryInfoValidationError struct {
@@ -47,77 +50,33 @@ func (e *InventoryInfoValidationError) Error() string {
 		strings.JoinStringsWithQuotes(e.Violations.Fields()))
 }
 
-// MultipleInventoryInfoError is the error returned if there are multiple
-// Kptfile resources in a stream which has inventory information.
-type MultipleInventoryInfoError struct{}
-
-func (e *MultipleInventoryInfoError) Error() string {
-	return "multiple inventory information found in package"
-}
-
-// NoInvInfoError is the error returned if there are no inventory information
-// provided in either a stream or locally.
-type NoInvInfoError struct{}
-
-func (e *NoInvInfoError) Error() string {
-	return "no inventory information was provided within the stream or package"
-}
-
 // Load reads resources either from disk or from an input stream. It filters
 // out resources that should be ignored and defaults the namespace for
 // namespace-scoped resources that doesn't have the namespace set. It also looks
-// for inventory information inside Kptfile resources.
+// for inventory information inside Kptfile or resourcegroup resources.
 // It returns the resources in unstructured format and the inventory information.
 // If no inventory information is found, that is not considered an error here.
-func Load(f util.Factory, path, rgfile string, stdIn io.Reader) ([]*unstructured.Unstructured, kptfilev1.Inventory, error) {
+func Load(f util.Factory, path string, stdIn io.Reader) ([]*unstructured.Unstructured, kptfilev1.Inventory, error) {
 	if path == "-" {
-		return loadFromStream(f, stdIn, rgfile)
+		return loadFromStream(f, stdIn)
 	}
-	return loadFromDisk(f, path, rgfile)
+	return loadFromDisk(f, path)
 }
 
 // loadFromStream reads resources from the provided reader and returns the
 // filtered resources and any inventory information found in Kptfile resources.
 // If there is more than one Kptfile in the stream with inventory information, that
 // is considered an error.
-func loadFromStream(f util.Factory, r io.Reader, rgfile string) ([]*unstructured.Unstructured, kptfilev1.Inventory, error) {
+func loadFromStream(f util.Factory, r io.Reader) ([]*unstructured.Unstructured, kptfilev1.Inventory, error) {
 	var stdInBuf bytes.Buffer
 	tee := io.TeeReader(r, &stdInBuf)
 
-	// Check if stream contains inventory info.
 	invInfo, err := readInvInfoFromStream(tee)
 	if err != nil {
 		return nil, kptfilev1.Inventory{}, err
 	}
-
-	// Check resourcegroup file for inventory information if file is specified.
-	if rgfile != "" {
-		cwd, err := os.Getwd()
-		if err != nil {
-			return nil, kptfilev1.Inventory{}, err
-		}
-
-		diskInv, err := readInvInfoFromDisk(cwd, rgfile)
-		if err != nil {
-			return nil, kptfilev1.Inventory{}, err
-		}
-
-		if diskInv.IsValid() && invInfo.IsValid() {
-			return nil, kptfilev1.Inventory{}, &MultipleInventoryInfoError{}
-		}
-
-		if !diskInv.IsValid() && !invInfo.IsValid() {
-			return nil, kptfilev1.Inventory{}, &NoInvInfoError{}
-		}
-
-		if diskInv.IsValid() {
-			invInfo = diskInv
-		}
-	}
-
-	// Stream does not contain a valid inventory and no local inventory does not exist, or is not valid.
 	if !invInfo.IsValid() {
-		return nil, kptfilev1.Inventory{}, &NoInvInfoError{}
+		return nil, kptfilev1.Inventory{}, &pkg.InvInfoInvalid{}
 	}
 
 	ro, err := toReaderOptions(f)
@@ -154,16 +113,25 @@ func readInvInfoFromStream(in io.Reader) (kptfilev1.Inventory, error) {
 		return kptfilev1.Inventory{}, err
 	}
 
-	if len(invFilter.Inventories) > 1 ||
-		len(rgFilter.Inventories) > 1 ||
-		(len(invFilter.Inventories) > 0 && len(rgFilter.Inventories) > 0) {
-		return kptfilev1.Inventory{}, &MultipleInventoryInfoError{}
+	// Ensure only exactly 1 inventory exists and surface the correct type of error.
+	// Multiple Kptfile inventories found.
+	if len(invFilter.Inventories) > 1 {
+		return kptfilev1.Inventory{}, &pkg.MultipleKfInv{}
+	}
+	// Multiple ResourceGroup inventories found.
+	if len(rgFilter.Inventories) > 1 {
+		return kptfilev1.Inventory{}, &pkg.MultipleResourceGroupsError{}
+	}
+	// Multiple inventories found in Kptfile and ResourceGroup objects.
+	if len(invFilter.Inventories) > 0 && len(rgFilter.Inventories) > 0 {
+		return kptfilev1.Inventory{}, &pkg.MultipleInventoryInfoError{}
 	}
 
+	// Inventory found within Kptfile.
 	if len(invFilter.Inventories) == 1 {
 		return *invFilter.Inventories[0], nil
 	}
-
+	// Inventory found with ResourceGroup object.
 	if len(rgFilter.Inventories) == 1 {
 		invID := rgFilter.Inventories[0].Labels[rgfilev1alpha1.RGInventoryIDLabel]
 		return kptfilev1.Inventory{
@@ -173,19 +141,24 @@ func readInvInfoFromStream(in io.Reader) (kptfilev1.Inventory, error) {
 		}, nil
 	}
 
-	return kptfilev1.Inventory{}, nil
+	// No inventories found in stream.
+	return kptfilev1.Inventory{}, &pkg.NoInvInfoError{}
 }
 
 // loadFromdisk reads resources from the provided directory and any subfolder.
 // It returns the filtered resources and any inventory information found in
 // Kptfile resources.
 // Only the Kptfile in the root directory will be checked for inventory information.
-func loadFromDisk(f util.Factory, path, rgfile string) ([]*unstructured.Unstructured, kptfilev1.Inventory, error) {
-	invInfo, err := readInvInfoFromDisk(path, rgfile)
+func loadFromDisk(f util.Factory, path string) ([]*unstructured.Unstructured, kptfilev1.Inventory, error) {
+	invInfo, err := readInvInfoFromDisk(path)
 	if err != nil {
 		return nil, kptfilev1.Inventory{}, err
 	}
 
+	if !invInfo.IsValid() {
+		return nil, kptfilev1.Inventory{}, &pkg.InvInfoInvalid{}
+	}
+
 	ro, err := toReaderOptions(f)
 	if err != nil {
 		return nil, kptfilev1.Inventory{}, err
@@ -202,7 +175,7 @@ func loadFromDisk(f util.Factory, path, rgfile string) ([]*unstructured.Unstruct
 	return objs, invInfo, nil
 }
 
-func readInvInfoFromDisk(path, rgfile string) (kptfilev1.Inventory, error) {
+func readInvInfoFromDisk(path string) (kptfilev1.Inventory, error) {
 	absPath, _, err := pathutil.ResolveAbsAndRelPaths(path)
 	if err != nil {
 		return kptfilev1.Inventory{}, err
@@ -212,52 +185,7 @@ func readInvInfoFromDisk(path, rgfile string) (kptfilev1.Inventory, error) {
 		return kptfilev1.Inventory{}, err
 	}
 
-	// Read Kptfile for inventory. We ignore errors if no local Kptfile as that could
-	// be provided via STDIN.
-	kf, err := p.Kptfile()
-	if rgfile == "" {
-		if err != nil && errors.Is(err, os.ErrNotExist) {
-			return kptfilev1.Inventory{}, nil
-		}
-		if err != nil {
-			return kptfilev1.Inventory{}, err
-		}
-	}
-
-	// Check if resourcegroup exists and use inventory info from there if provided.
-	if rgfile != "" {
-		rg, err := p.ReadRGFile(rgfile)
-		if err != nil && !errors.Is(err, os.ErrNotExist) {
-			return kptfilev1.Inventory{}, nil
-		}
-
-		// Ensure we only have at most 1 instance of an inventory.
-		if kf != nil {
-			if kf.Inventory == nil && rg == nil {
-				return kptfilev1.Inventory{}, nil
-			}
-
-			if kf.Inventory != nil && rg != nil {
-				return kptfilev1.Inventory{}, &MultipleInventoryInfoError{}
-			}
-
-			if kf.Inventory != nil {
-				return *kf.Inventory, nil
-			}
-		}
-
-		return kptfilev1.Inventory{
-			Name:        rg.ObjectMeta.Name,
-			Namespace:   rg.ObjectMeta.Namespace,
-			InventoryID: rg.ObjectMeta.Labels[rgfilev1alpha1.RGInventoryIDLabel],
-		}, nil
-	}
-
-	if kf.Inventory == nil {
-		return kptfilev1.Inventory{}, nil
-	}
-
-	return *kf.Inventory, nil
+	return p.LocalInventory()
 }
 
 // InventoryFilter is an implementation of the yaml.Filter interface
@@ -356,6 +284,14 @@ func validateInventory(inventory kptfilev1.Inventory) error {
 			Reason: "\"inventory.namespace\" must not be empty",
 		})
 	}
+	if inventory.InventoryID == "" {
+		violations = append(violations, errors.Violation{
+			Field:  "inventoryID",
+			Value:  inventory.InventoryID,
+			Type:   errors.Missing,
+			Reason: "\"inventory.inventoryID\" must not be empty",
+		})
+	}
 	if len(violations) > 0 {
 		return &InventoryInfoValidationError{
 			ValidationError: errors.ValidationError{
@@ -368,6 +304,14 @@ func validateInventory(inventory kptfilev1.Inventory) error {
 
 func generateInventoryObj(inv kptfilev1.Inventory) *unstructured.Unstructured {
 	// Create and return ResourceGroup custom resource as inventory object.
+
+	// When inventoryID is not specified by the local resourcegroup, we generate one using
+	// depends-on annotation format that we store on the live cluster. This implementation detail
+	// is hidden from the local resourcegroup unless the one was explicitly generated by the client.
+	if inv.InventoryID == "" {
+		inv.InventoryID = fmt.Sprintf(inventoryIDfmt, inv.Namespace, inv.Name)
+	}
+
 	groupVersion := fmt.Sprintf("%s/%s", ResourceGroupGVK.Group, ResourceGroupGVK.Version)
 	var inventoryObj = &unstructured.Unstructured{
 		Object: map[string]interface{}{
diff --git a/site/book/06-deploying-packages/01-initializing-a-package-for-apply.md b/site/book/06-deploying-packages/01-initializing-a-package-for-apply.md
index 944d049f..28e85ecd 100644
--- a/site/book/06-deploying-packages/01-initializing-a-package-for-apply.md
+++ b/site/book/06-deploying-packages/01-initializing-a-package-for-apply.md
@@ -1,24 +1,26 @@
 Before you can apply the package to the cluster, it needs to be initialized
 using `live init`. This is a one-time client-side operation that adds metadata
-to the `Kptfile` specifying a `ResourceGroup` resource on the cluster associated
-with this package. The `ResourceGroup`resource will be created by the
-`live apply` command.
+to the `ResourceGroup` CR (by default located in `resourcegroup.yaml` file)
+specifying the name, namespace and inventoryID of the `ResourceGroup` resource
+`live apply` command will use to store the inventory (list of the resources applied).
 
 Let's initialize the `wordpress` package:
 
 ```shell
 $ kpt live init wordpress
-initializing Kptfile inventory info (namespace: default)...success
+initializing "resourcegroup.yaml" inventory info (namespace: default)...success
 ```
 
-This adds the `inventory` section to the `Kptfile`:
+This creates the `ResourceGroup` CR in the `resourcegroup.yaml` file:
 
 ```yaml
-# wordpress/Kptfile (Excerpt)
-inventory:
+apiVersion: kpt.dev/v1alpha1
+kind: ResourceGroup
+metadata:
+  name: inventory-74096247
   namespace: default
-  name: inventory-10285025
-  inventoryID: 06ca0268f3ccda82bfd44bec273530a4f72fa9a7-1623278424452000000
+  labels:
+    cli-utils.sigs.k8s.io/inventory-id: 0a32e2c0200b4bd4c19cd3e097086b4648b8902d-1653113657067255815
 ```
 
 `ResourceGroup` is a namespace-scoped resource. By default, `live init` command
@@ -30,7 +32,7 @@ and namespace of the `ResourceGroup` resource.
 ?> Refer to the [init command reference][init-doc] for usage.
 
 !> Once a package is applied to the cluster, you do not want to change the
-metadata in the `inventory` section; doing so severs the association between the
-package and the `ResourceGroup`, leading to destructive operations.
+`ResourceGroup` CR; doing so severs the association between the
+package and the inventory in the cluster, leading to destructive operations.
 
 [init-doc]: /reference/cli/live/init/
diff --git a/site/reference/cli/live/apply/README.md b/site/reference/cli/live/apply/README.md
index bf700a97..fe6aac09 100644
--- a/site/reference/cli/live/apply/README.md
+++ b/site/reference/cli/live/apply/README.md
@@ -26,8 +26,8 @@ kpt live apply [PKG_PATH | -] [flags]
 ```
 PKG_PATH | -:
   Path to the local package which should be applied to the cluster. It must
-  contain a Kptfile with inventory information. Defaults to the current working
-  directory.
+  contain a Kptfile or a ResourceGroup manifest with inventory metadata.
+  Defaults to the current working directory.
   Using '-' as the package path will cause kpt to read resources from stdin.
 ```
 
diff --git a/site/reference/cli/live/destroy/README.md b/site/reference/cli/live/destroy/README.md
index 7a4c645f..bc4c22d1 100644
--- a/site/reference/cli/live/destroy/README.md
+++ b/site/reference/cli/live/destroy/README.md
@@ -25,8 +25,8 @@ kpt live destroy [PKG_PATH | -]
 ```
 PKG_PATH | -:
   Path to the local package which should be deleted from the cluster. It must
-  contain a Kptfile with inventory information. Defaults to the current working
-  directory.
+  contain a Kptfile or a ResourceGroup manifest with inventory metadata.
+  Defaults to the current working directory.
   Using '-' as the package path will cause kpt to read resources from stdin.
 ```
 
diff --git a/site/reference/cli/live/init/README.md b/site/reference/cli/live/init/README.md
index 7a98cf4c..54619eed 100644
--- a/site/reference/cli/live/init/README.md
+++ b/site/reference/cli/live/init/README.md
@@ -50,6 +50,10 @@ PKG_PATH:
   for the package. If not provided, kpt will check if all the resources
   in the package belong in the same namespace. If they do, that namespace will
   be used. If they do not, the namespace in the user's context will be chosen.
+
+--rg-file:
+  The name used for the file created for the ResourceGroup CR. Defaults to
+  'resourcegroup.yaml'.
 ```
 
 <!--mdtogo-->
diff --git a/site/reference/cli/live/migrate/README.md b/site/reference/cli/live/migrate/README.md
index 4125bf76..0220f377 100644
--- a/site/reference/cli/live/migrate/README.md
+++ b/site/reference/cli/live/migrate/README.md
@@ -12,13 +12,19 @@ Migrate a package and the inventory object to use the ResourceGroup CRD.
 
 `migrate` moves the inventory list, which contains the Group, Kind, Name and
 Namespace for every resource in the cluster that belongs to a package, into a
-`ResourceGroup` CR and moves the inventory information into the `Kptfile`.
+`ResourceGroup` CR and moves the inventory information into the a `ResourceGroup`
+manifest on local disk.
+
+0.39.x or earlier versions of `kpt` stored the inventory metadata in a
+`ConfigMap` manifest in the package, and stored the inventory list in a
+`ConfigMap` resource in the cluster. Running this command will move the
+metadata into a `ResourceGroup` manifest in the `resourcegroup.yaml` file
+and move the inventory list into a `ResourceGroup` CR.
+
+Previous 1.0.0-alpha or 1.0.0-beta versions of kpt would store the inventory
+metadata in the `Kptfile`. Running this command will move the metadata from
+the `Kptfile` in a `ResourceGroup` manifest in the `resourcegroup.yaml` file.
 
-Previous versions of `kpt` uses `ConfigMap` resources for storing the inventory
-list and relies on an inventory template being among the applied resources. The
-inventory template is just a regular `ConfigMap` manifest with a special
-annotation that includes the information needed to look up any existing
-inventory lists.
 
 ### Synopsis
 
@@ -32,8 +38,9 @@ kpt live migrate [PKG_PATH] [flags]
 
 ```
 PKG_PATH:
-  Path to the local package. It must have a Kptfile and an existing inventory
-  template in the root of the package. It defaults to the current directory.
+  Path to the local package. It must have a Kptfile and inventory metadata
+  in the package in either the ConfigMap, Kptfile or ResourceGroup format.
+  It defaults to the current directory.
 ```
 
 #### Flags
@@ -43,18 +50,18 @@ PKG_PATH:
   Go through the steps of migration, but don't make any changes.
 
 --force:
-  Forces the inventory values in the Kptfile to be updated, even if they are
-  already set. Defaults to false.
+  Forces the inventory values in the ResourceGroup manfiest to be updated,
+  even if they are already set. Defaults to false.
 
 --name:
   The name for the ResourceGroup resource that contains the inventory
-  for the package. Defaults to the same name as the existing ConfigMap
-  inventory object.
+  for the package. Defaults to the same name as the existing inventory
+  object.
 
 --namespace:
   The namespace for the ResourceGroup resource that contains the inventory
   for the package. If not provided, it defaults to the same namespace as the
-  existing ConfigMap inventory object.
+  existing inventory object.
 ```
 
 <!--mdtogo-->
diff --git a/site/reference/cli/live/status/README.md b/site/reference/cli/live/status/README.md
index f26c82df..e134852e 100644
--- a/site/reference/cli/live/status/README.md
+++ b/site/reference/cli/live/status/README.md
@@ -25,7 +25,8 @@ kpt live status [PKG_PATH | -] [flags]
 ```
 PKG_PATH | -:
   Path to the local package for which the status of the package in the cluster
-  should be displayed. It must contain a Kptfile with inventory information.
+  should be displayed. It must contain either a Kptfile or a ResourceGroup CR
+  with inventory metadata.
   Defaults to the current working directory.
   Using '-' as the package path will cause kpt to read resources from stdin.
 ```
diff --git a/thirdparty/cli-utils/status/cmdstatus.go b/thirdparty/cli-utils/status/cmdstatus.go
index 6ddfc4ed..cabfab62 100644
--- a/thirdparty/cli-utils/status/cmdstatus.go
+++ b/thirdparty/cli-utils/status/cmdstatus.go
@@ -84,7 +84,6 @@ type Runner struct {
 	pollUntil string
 	timeout   time.Duration
 	output    string
-	rgFile    string
 
 	pollerFactoryFunc func(util.Factory) (poller.Poller, error)
 }
@@ -124,7 +123,7 @@ func (r *Runner) runE(c *cobra.Command, args []string) error {
 		}
 	}
 
-	_, inv, err := live.Load(r.factory, path, r.rgFile, c.InOrStdin())
+	_, inv, err := live.Load(r.factory, path, c.InOrStdin())
 	if err != nil {
 		return err
 	}


Test patch:
diff --git a/e2e/live/end-to-end-test.sh b/e2e/live/end-to-end-test.sh
index d8f39469..6cb9ac72 100755
--- a/e2e/live/end-to-end-test.sh
+++ b/e2e/live/end-to-end-test.sh
@@ -715,8 +715,9 @@ reconcile result: 4 attempted, 4 successful, 0 skipped, 0 failed, 0 timed out
 EOF
 printResult
 
-# Cleanup by resetting Kptfile
+# Cleanup by resetting Kptfile and deleting resourcegroup file
 cp -f e2e/live/testdata/Kptfile e2e/live/testdata/rg-test-case-1a/
+rm e2e/live/testdata/rg-test-case-1a/resourcegroup.yaml
 
 ###########################################################
 #  Tests operations with ResourceGroup inventory CRD
@@ -755,33 +756,39 @@ printResult
 echo "Testing init for Kptfile/ResourceGroup"
 cp -f e2e/live/testdata/Kptfile e2e/live/testdata/rg-test-case-1a/
 echo "kpt live init e2e/live/testdata/rg-test-case-1a"
+cp -f e2e/live/testdata/Kptfile e2e/live/testdata/rg-test-case-1a
 ${BIN_DIR}/kpt live init e2e/live/testdata/rg-test-case-1a 2>&1 | tee $OUTPUT_DIR/status
-assertContains "initializing Kptfile inventory info (namespace: rg-test-namespace)...success"
+assertContains "initializing \"resourcegroup.yaml\" data (namespace: rg-test-namespace)...success"
 # Difference in Kptfile should have inventory data
 diff e2e/live/testdata/Kptfile e2e/live/testdata/rg-test-case-1a/Kptfile 2>&1 | tee $OUTPUT_DIR/status
-assertContains "inventory:"
+assertNotContains "inventory:"
+assertNotContains "namespace: rg-test-namespace"
+assertNotContains "name: inventory-"
+assertNotContains "inventoryID:"
+# ResourceGroup file should contain inventory information
+cat e2e/live/testdata/rg-test-case-1a/resourcegroup.yaml 2>&1 | tee $OUTPUT_DIR/status
+assertContains "kind: ResourceGroup"
 assertContains "namespace: rg-test-namespace"
 assertContains "name: inventory-"
-assertContains "inventoryID:"
 printResult
 
 echo "Testing init Kptfile/ResourceGroup already initialized"
 echo "kpt live init e2e/live/testdata/rg-test-case-1a"
 ${BIN_DIR}/kpt live init e2e/live/testdata/rg-test-case-1a 2>&1 | tee $OUTPUT_DIR/status
-assertContains "initializing Kptfile inventory info (namespace: rg-test-namespace)...failed"
-assertContains "Error: Inventory information has already been added to the package Kptfile."
+assertContains "initializing \"resourcegroup.yaml\" data (namespace: rg-test-namespace)...failed"
+assertContains "Error: Inventory information has already been added to the package ResourceGroup object."
 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
-assertContains "initializing Kptfile inventory info (namespace: rg-test-namespace)...success"
+assertContains "initializing \"resourcegroup.yaml\" data (namespace: rg-test-namespace)...success"
 printResult
 
 echo "Testing init quiet Kptfile/ResourceGroup"
 echo "kpt live init --quiet e2e/live/testdata/rg-test-case-1a"
 ${BIN_DIR}/kpt live init --quiet e2e/live/testdata/rg-test-case-1a 2>&1 | tee $OUTPUT_DIR/status
-assertNotContains "initializing Kptfile inventory info"
+assertNotContains "initializing resourcegroup"
 printResult
 
 # Test: Basic kpt live apply dry-run
@@ -917,6 +924,8 @@ rm -rf link-to-rg-test-case-1a
 echo "[ResourceGroup] Testing basic apply dry-run"
 cp -f e2e/live/testdata/rg-test-case-1a/Kptfile e2e/live/testdata/rg-test-case-1b/
 echo "kpt live apply --dry-run e2e/live/testdata/rg-test-case-1b"
+cp -f e2e/live/testdata/rg-test-case-1a/Kptfile e2e/live/testdata/rg-test-case-1b
+cp -f e2e/live/testdata/rg-test-case-1a/resourcegroup.yaml e2e/live/testdata/rg-test-case-1b
 ${BIN_DIR}/kpt live apply --dry-run e2e/live/testdata/rg-test-case-1b 2>&1 | tee $OUTPUT_DIR/status
 assertKptLiveApplyEquals << EOF
 Dry-run strategy: client
@@ -1124,10 +1133,10 @@ printResult
 
 # Test: kpt live apply continue-on-error
 echo "[ResourceGroup] Testing continue-on-error"
-cp -f e2e/live/testdata/Kptfile e2e/live/testdata/continue-on-error/
-echo "kpt live init e2e/live/testdata/continue-on-error"
+echo "kpt live apply e2e/live/testdata/continue-on-error"
+cp -f e2e/live/testdata/Kptfile e2e/live/testdata/continue-on-error
 ${BIN_DIR}/kpt live init e2e/live/testdata/continue-on-error 2>&1 | tee $OUTPUT_DIR/status
-diff e2e/live/testdata/Kptfile e2e/live/testdata/continue-on-error/Kptfile 2>&1 | tee $OUTPUT_DIR/status
+diff e2e/live/testdata/Kptfile e2e/live/testdata/continue-on-error/resourcegroup.yaml 2>&1 | tee $OUTPUT_DIR/status
 assertContains "namespace: continue-err-namespace"
 printResult
 
@@ -1257,7 +1266,7 @@ echo "kpt live migrate --dry-run e2e/live/testdata/migrate-case-1a"
 ${BIN_DIR}/kpt live migrate --dry-run e2e/live/testdata/migrate-case-1a 2>&1 | tee $OUTPUT_DIR/status
 assertContains "ensuring ResourceGroup CRD exists in cluster...success"
 assertContains "retrieve the current ConfigMap inventory...success (inventory-id:"
-assertContains "updating Kptfile inventory values...success"
+assertContains "creating ResourceGroup object file...success"
 assertContains "retrieve ConfigMap inventory objs...success (4 inventory objects)"
 assertContains "migrate inventory to ResourceGroup...success"
 assertContains "deleting old ConfigMap inventory object...success"
@@ -1277,7 +1286,7 @@ echo "kpt live migrate e2e/live/testdata/migrate-case-1a"
 ${BIN_DIR}/kpt live migrate e2e/live/testdata/migrate-case-1a 2>&1 | tee $OUTPUT_DIR/status
 assertContains "ensuring ResourceGroup CRD exists in cluster...success"
 assertContains "retrieve the current ConfigMap inventory...success (inventory-id:"
-assertContains "updating Kptfile inventory values...success"
+assertContains "creating ResourceGroup object file...success"
 assertContains "retrieve ConfigMap inventory objs...success (4 inventory objects)"
 assertContains "migrate inventory to ResourceGroup...success"
 assertContains "deleting old ConfigMap inventory object...success"
@@ -1304,6 +1313,8 @@ printResult
 echo "Testing apply/prune after migrate"
 cp -f e2e/live/testdata/migrate-case-1a/Kptfile e2e/live/testdata/migrate-case-1b/
 echo "kpt live apply e2e/live/testdata/migrate-case-1b"
+cp -f e2e/live/testdata/migrate-case-1a/Kptfile e2e/live/testdata/migrate-case-1b
+cp -f e2e/live/testdata/migrate-case-1a/resourcegroup.yaml e2e/live/testdata/migrate-case-1b
 ${BIN_DIR}/kpt live apply e2e/live/testdata/migrate-case-1b 2>&1 | tee $OUTPUT_DIR/status
 assertKptLiveApplyEquals << EOF
 inventory update started
@@ -1462,6 +1473,225 @@ ${BIN_DIR}/kpt live apply e2e/live/testdata/rbac-error-step-3 2>&1 | tee $OUTPUT
 assertContains "error: Type ResourceGroup CRD needs update."
 printResult
 
+###########################################################
+#  Test Migrate from Kptfile inventory to ResourceGroup
+###########################################################
+
+createTestSuite
+waitForDefaultServiceAccount
+
+# Setup: kpt live apply ConfigMap inventory
+# Applies resources in "migrate-case-2a" directory.
+echo "Initialize Kptfile with inventory for migration to ResourceGroup tests"
+echo "kpt live init e2e/live/testdata/migrate-case-2a"
+${BIN_DIR}/kpt1.0.0 live init e2e/live/testdata/migrate-case-2a 2>&1 | tee $OUTPUT_DIR/status
+assertContains "initializing Kptfile inventory info (namespace: test-namespace)...success"
+printResult
+
+echo "Testing kpt live apply with Kptfile inventory"
+echo "kpt live apply e2e/live/testdata/migrate-case-2a"
+${BIN_DIR}/kpt1.0.0 live apply e2e/live/testdata/migrate-case-2a 2>&1 | tee $OUTPUT_DIR/status
+assertContains "installing inventory ResourceGroup CRD"
+assertContains "namespace/test-namespace unchanged"
+assertContains "pod/pod-a created"
+assertContains "pod/pod-b created"
+assertContains "pod/pod-c created"
+assertContains "4 resource(s) applied. 3 created, 1 unchanged, 0 configured, 0 failed"
+# Validate resources in the cluster
+assertRGInventory "test-namespace" "4"
+assertPodExists "pod-a" "test-namespace"
+assertPodExists "pod-b" "test-namespace"
+assertPodExists "pod-c" "test-namespace"
+printResult
+
+# Test: kpt live migrate from Kptfile inventory to ResourceGroup inventory
+# Migrates resources in "migrate-case-2" directory.
+echo "Testing migrate dry-run from Kptfile to ResourceGroup inventory"
+echo "kpt live migrate --dry-run e2e/live/testdata/migrate-case-2a"
+# Run migrate dry-run and verify that the migrate did not actually happen
+${BIN_DIR}/kpt live migrate --dry-run e2e/live/testdata/migrate-case-2a 2>&1 | tee $OUTPUT_DIR/status
+assertContains "ensuring ResourceGroup CRD exists in cluster...success"
+assertContains "retrieve the current ConfigMap inventory...no ConfigMap inventory...completed"
+assertContains "reading existing Kptfile...success"
+assertContains "inventory migration...success"
+printResult
+# Ensure resourcegroup.yaml was not created
+ls e2e/live/testdata/migrate-case-2a/resourcegroup.yaml 2>&1 | tee $OUTPUT_DIR/status
+assertContains "ls: cannot access 'e2e/live/testdata/migrate-case-2a/resourcegroup.yaml': No such file or directory"
+printResult
+
+# Now actually run the migrate and verify the new ResourceGroup file exists
+echo "Testing migrate from Kptfile to ResourceGroup inventory"
+echo "kpt live migrate e2e/live/testdata/migrate-case-2a"
+${BIN_DIR}/kpt live migrate e2e/live/testdata/migrate-case-2a 2>&1 | tee $OUTPUT_DIR/status
+assertContains "ensuring ResourceGroup CRD exists in cluster...success"
+assertContains "retrieve the current ConfigMap inventory...no ConfigMap inventory...completed"
+assertContains "reading existing Kptfile...success"
+assertContains "inventory migration...success"
+# Validate resources in the cluster
+assertPodExists "pod-a" "test-namespace"
+assertPodExists "pod-b" "test-namespace"
+assertPodExists "pod-c" "test-namespace"
+assertRGInventory "test-namespace"
+# ResourceGroup file should contain inventory information
+cat e2e/live/testdata/migrate-case-2a/resourcegroup.yaml 2>&1 | tee $OUTPUT_DIR/status
+assertContains "kind: ResourceGroup"
+assertContains "namespace: test-namespace"
+assertContains "name: inventory-"
+printResult
+
+# Run it again, and validate the output
+${BIN_DIR}/kpt live migrate e2e/live/testdata/migrate-case-2a 2>&1 | tee $OUTPUT_DIR/status
+assertContains "ensuring ResourceGroup CRD exists in cluster...success"
+assertContains "retrieve the current ConfigMap inventory...no ConfigMap inventory...completed"
+assertContains "reading existing Kptfile...inventory migration...success"
+printResult
+
+# Test: kpt live apply/prune
+# "migrate-case-2b" directory is "migrate-case-2a" directory with "pod-a" removed and "pod-d" added.
+echo "Testing apply/prune after migrate"
+echo "kpt live apply e2e/live/testdata/migrate-case-2b"
+cp -f e2e/live/testdata/migrate-case-2a/Kptfile e2e/live/testdata/migrate-case-2b
+cp -f e2e/live/testdata/migrate-case-2a/resourcegroup.yaml e2e/live/testdata/migrate-case-2b
+${BIN_DIR}/kpt live apply e2e/live/testdata/migrate-case-2b 2>&1 | tee $OUTPUT_DIR/status
+assertKptLiveApplyEquals << EOF
+installing inventory ResourceGroup CRD.
+inventory update started
+inventory update finished
+apply phase started
+namespace/test-namespace apply successful
+apply phase finished
+reconcile phase started
+namespace/test-namespace reconcile successful
+reconcile phase finished
+apply phase started
+pod/pod-b apply successful
+pod/pod-c apply successful
+pod/pod-d apply successful
+apply phase finished
+reconcile phase started
+pod/pod-b reconcile successful
+pod/pod-c reconcile successful
+pod/pod-d reconcile pending
+pod/pod-d reconcile successful
+reconcile phase finished
+prune phase started
+pod/pod-a prune successful
+prune phase finished
+reconcile phase started
+pod/pod-a reconcile pending
+pod/pod-a reconcile successful
+reconcile phase finished
+inventory update started
+inventory update finished
+apply result: 4 attempted, 4 successful, 0 skipped, 0 failed
+prune result: 1 attempted, 1 successful, 0 skipped, 0 failed
+reconcile result: 5 attempted, 5 successful, 0 skipped, 0 failed, 0 timed out
+EOF
+# Validate resources in the cluster
+# ResourceGroup inventory with four inventory items.
+assertRGInventory "test-namespace" "4"
+assertPodNotExists "pod-a" "test-namespace"
+assertPodExists "pod-b" "test-namespace"
+assertPodExists "pod-c" "test-namespace"
+assertPodExists "pod-d" "test-namespace"
+printResult
+
+###########################################################
+#  Test --rg-file flag on live init commands
+###########################################################
+
+createTestSuite
+waitForDefaultServiceAccount
+
+# Setup: kpt live init with custom resourcegroup file
+# Applies resources in "test-case-1c" directory
+echo "Testing kpt live init with custom ResourceGroup file"
+echo "kpt live init --rg-file=custom-rg.yaml e2e/live/testdata/test-case-1c"
+${BIN_DIR}/kpt live init --rg-file=custom-rg.yaml e2e/live/testdata/test-case-1c 2>&1 | tee $OUTPUT_DIR/status
+assertContains "initializing \"custom-rg.yaml\" data (namespace: test-namespace)...success"
+printResult
+# Re-running live init should fail as ResourceGroup file already exists
+${BIN_DIR}/kpt live init --rg-file=custom-rg.yaml e2e/live/testdata/test-case-1c 2>&1 | tee $OUTPUT_DIR/status
+assertContains "initializing \"custom-rg.yaml\" data (namespace: test-namespace)...failed"
+printResult
+
+# Run: kpt live apply with custom resourcegroup file
+echo "Testing kpt live apply with custom ResourceGroup filename"
+echo "kpt live apply e2e/live/testdata/test-case-1c"
+${BIN_DIR}/kpt live apply e2e/live/testdata/test-case-1c 2>&1 | tee $OUTPUT_DIR/status
+cat $OUTPUT_DIR/status
+assertKptLiveApplyEquals << EOF
+installing inventory ResourceGroup CRD.
+inventory update started
+inventory update finished
+apply phase started
+namespace/test-namespace apply successful
+apply phase finished
+reconcile phase started
+namespace/test-namespace reconcile successful
+reconcile phase finished
+apply phase started
+pod/pod-a apply successful
+pod/pod-b apply successful
+pod/pod-c apply successful
+apply phase finished
+reconcile phase started
+pod/pod-a reconcile pending
+pod/pod-b reconcile pending
+pod/pod-c reconcile pending
+pod/pod-a reconcile successful
+pod/pod-b reconcile successful
+pod/pod-c reconcile successful
+reconcile phase finished
+inventory update started
+inventory update finished
+apply result: 4 attempted, 4 successful, 0 skipped, 0 failed
+reconcile result: 4 attempted, 4 successful, 0 skipped, 0 failed, 0 timed out
+EOF
+# Validate resources in the cluster
+# ResourceGroup inventory with four inventory items.
+assertRGInventory "test-namespace" "4"
+assertPodExists "pod-a" "test-namespace"
+assertPodExists "pod-b" "test-namespace"
+assertPodExists "pod-c" "test-namespace"
+printResult
+
+echo "Testing live destroy with custom ResourceGroup filename"
+echo "kpt live destroy --rg-file=custom-rg.yaml e2e/live/testdata/test-case-1c"
+${BIN_DIR}/kpt live destroy e2e/live/testdata/test-case-1c 2>&1 | tee $OUTPUT_DIR/status
+assertKptLiveApplyEquals << EOF
+delete phase started
+pod/pod-c delete successful
+pod/pod-b delete successful
+pod/pod-a delete successful
+delete phase finished
+reconcile phase started
+pod/pod-c reconcile pending
+pod/pod-b reconcile pending
+pod/pod-a reconcile pending
+pod/pod-b reconcile successful
+pod/pod-c reconcile successful
+pod/pod-a reconcile successful
+reconcile phase finished
+delete phase started
+namespace/test-namespace delete successful
+delete phase finished
+reconcile phase started
+namespace/test-namespace reconcile pending
+namespace/test-namespace reconcile successful
+reconcile phase finished
+inventory update started
+inventory update finished
+delete result: 4 attempted, 4 successful, 0 skipped, 0 failed
+reconcile result: 4 attempted, 4 successful, 0 skipped, 0 failed, 0 timed out
+EOF
+# Validate resources DESTROYED in the cluster
+assertPodNotExists "pod-a" "test-namespace"
+assertPodNotExists "pod-b" "test-namespace"
+assertPodNotExists "pod-c" "test-namespace"
+printResult
+
 # Clean-up the k8s cluster
 echo "Cleaning up cluster"
 cp -f e2e/live/testdata/Kptfile e2e/live/testdata/rg-test-case-1a/
@@ -1480,4 +1710,4 @@ if [[ ${HAS_TEST_FAILURE} -gt 0 ]]; then
 else
     echo -e "${GREEN}SUCCESS: E2E Tests Passed${NC}"
     exit 0
-fi
+fi
\ No newline at end of file
diff --git a/e2e/live/testdata/migrate-case-2a/Kptfile b/e2e/live/testdata/migrate-case-2a/Kptfile
new file mode 100644
index 00000000..09651424
--- /dev/null
+++ b/e2e/live/testdata/migrate-case-2a/Kptfile
@@ -0,0 +1,11 @@
+apiVersion: kpt.dev/v1
+kind: Kptfile
+metadata:
+  name: test1
+upstream:
+  type: git
+  git:
+    repo: git@github.com:yuwenma/blueprint-helloworld
+    directory: /
+    ref: master
+
diff --git a/e2e/live/testdata/migrate-case-2a/namespace.yaml b/e2e/live/testdata/migrate-case-2a/namespace.yaml
new file mode 100644
index 00000000..e5e747b2
--- /dev/null
+++ b/e2e/live/testdata/migrate-case-2a/namespace.yaml
@@ -0,0 +1,7 @@
+# Copyright 2019 The Kubernetes Authors.
+# SPDX-License-Identifier: Apache-2.0
+
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: test-namespace
diff --git a/e2e/live/testdata/migrate-case-2a/pod-a.yaml b/e2e/live/testdata/migrate-case-2a/pod-a.yaml
new file mode 100644
index 00000000..05724d79
--- /dev/null
+++ b/e2e/live/testdata/migrate-case-2a/pod-a.yaml
@@ -0,0 +1,14 @@
+# Copyright 2019 The Kubernetes Authors.
+# SPDX-License-Identifier: Apache-2.0
+
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-a
+  namespace: test-namespace
+  labels:
+    name: test-pod-label-foo
+spec:
+  containers:
+  - name: kubernetes-pause
+    image: k8s.gcr.io/pause:2.0
diff --git a/e2e/live/testdata/migrate-case-2a/pod-b.yaml b/e2e/live/testdata/migrate-case-2a/pod-b.yaml
new file mode 100644
index 00000000..b69e0899
--- /dev/null
+++ b/e2e/live/testdata/migrate-case-2a/pod-b.yaml
@@ -0,0 +1,14 @@
+# Copyright 2019 The Kubernetes Authors.
+# SPDX-License-Identifier: Apache-2.0
+
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-b
+  namespace: test-namespace
+  labels:
+    name: test-pod-label
+spec:
+  containers:
+  - name: kubernetes-pause
+    image: k8s.gcr.io/pause:2.0
diff --git a/e2e/live/testdata/migrate-case-2a/pod-c.yaml b/e2e/live/testdata/migrate-case-2a/pod-c.yaml
new file mode 100644
index 00000000..647352ba
--- /dev/null
+++ b/e2e/live/testdata/migrate-case-2a/pod-c.yaml
@@ -0,0 +1,14 @@
+# Copyright 2019 The Kubernetes Authors.
+# SPDX-License-Identifier: Apache-2.0
+
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-c
+  namespace: test-namespace
+  labels:
+    name: test-pod-label
+spec:
+  containers:
+  - name: kubernetes-pause
+    image: k8s.gcr.io/pause:2.0
diff --git a/e2e/live/testdata/migrate-case-2b/Kptfile b/e2e/live/testdata/migrate-case-2b/Kptfile
new file mode 100644
index 00000000..09651424
--- /dev/null
+++ b/e2e/live/testdata/migrate-case-2b/Kptfile
@@ -0,0 +1,11 @@
+apiVersion: kpt.dev/v1
+kind: Kptfile
+metadata:
+  name: test1
+upstream:
+  type: git
+  git:
+    repo: git@github.com:yuwenma/blueprint-helloworld
+    directory: /
+    ref: master
+
diff --git a/e2e/live/testdata/migrate-case-2b/namespace.yaml b/e2e/live/testdata/migrate-case-2b/namespace.yaml
new file mode 100644
index 00000000..e5e747b2
--- /dev/null
+++ b/e2e/live/testdata/migrate-case-2b/namespace.yaml
@@ -0,0 +1,7 @@
+# Copyright 2019 The Kubernetes Authors.
+# SPDX-License-Identifier: Apache-2.0
+
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: test-namespace
diff --git a/e2e/live/testdata/migrate-case-2b/pod-b.yaml b/e2e/live/testdata/migrate-case-2b/pod-b.yaml
new file mode 100644
index 00000000..b69e0899
--- /dev/null
+++ b/e2e/live/testdata/migrate-case-2b/pod-b.yaml
@@ -0,0 +1,14 @@
+# Copyright 2019 The Kubernetes Authors.
+# SPDX-License-Identifier: Apache-2.0
+
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-b
+  namespace: test-namespace
+  labels:
+    name: test-pod-label
+spec:
+  containers:
+  - name: kubernetes-pause
+    image: k8s.gcr.io/pause:2.0
diff --git a/e2e/live/testdata/migrate-case-2b/pod-c.yaml b/e2e/live/testdata/migrate-case-2b/pod-c.yaml
new file mode 100644
index 00000000..647352ba
--- /dev/null
+++ b/e2e/live/testdata/migrate-case-2b/pod-c.yaml
@@ -0,0 +1,14 @@
+# Copyright 2019 The Kubernetes Authors.
+# SPDX-License-Identifier: Apache-2.0
+
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-c
+  namespace: test-namespace
+  labels:
+    name: test-pod-label
+spec:
+  containers:
+  - name: kubernetes-pause
+    image: k8s.gcr.io/pause:2.0
diff --git a/e2e/live/testdata/migrate-case-2b/pod-d.yaml b/e2e/live/testdata/migrate-case-2b/pod-d.yaml
new file mode 100644
index 00000000..0fe0fec2
--- /dev/null
+++ b/e2e/live/testdata/migrate-case-2b/pod-d.yaml
@@ -0,0 +1,14 @@
+# Copyright 2019 The Kubernetes Authors.
+# SPDX-License-Identifier: Apache-2.0
+
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-d
+  namespace: test-namespace
+  labels:
+    name: test-pod-label-foo
+spec:
+  containers:
+  - name: kubernetes-pause
+    image: k8s.gcr.io/pause:2.0
diff --git a/e2e/live/testdata/test-case-1c/Kptfile b/e2e/live/testdata/test-case-1c/Kptfile
new file mode 100644
index 00000000..09651424
--- /dev/null
+++ b/e2e/live/testdata/test-case-1c/Kptfile
@@ -0,0 +1,11 @@
+apiVersion: kpt.dev/v1
+kind: Kptfile
+metadata:
+  name: test1
+upstream:
+  type: git
+  git:
+    repo: git@github.com:yuwenma/blueprint-helloworld
+    directory: /
+    ref: master
+
diff --git a/e2e/live/testdata/test-case-1c/namespace.yaml b/e2e/live/testdata/test-case-1c/namespace.yaml
new file mode 100644
index 00000000..e5e747b2
--- /dev/null
+++ b/e2e/live/testdata/test-case-1c/namespace.yaml
@@ -0,0 +1,7 @@
+# Copyright 2019 The Kubernetes Authors.
+# SPDX-License-Identifier: Apache-2.0
+
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: test-namespace
diff --git a/e2e/live/testdata/test-case-1c/pod-a.yaml b/e2e/live/testdata/test-case-1c/pod-a.yaml
new file mode 100644
index 00000000..05724d79
--- /dev/null
+++ b/e2e/live/testdata/test-case-1c/pod-a.yaml
@@ -0,0 +1,14 @@
+# Copyright 2019 The Kubernetes Authors.
+# SPDX-License-Identifier: Apache-2.0
+
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-a
+  namespace: test-namespace
+  labels:
+    name: test-pod-label-foo
+spec:
+  containers:
+  - name: kubernetes-pause
+    image: k8s.gcr.io/pause:2.0
diff --git a/e2e/live/testdata/test-case-1c/pod-b.yaml b/e2e/live/testdata/test-case-1c/pod-b.yaml
new file mode 100644
index 00000000..b69e0899
--- /dev/null
+++ b/e2e/live/testdata/test-case-1c/pod-b.yaml
@@ -0,0 +1,14 @@
+# Copyright 2019 The Kubernetes Authors.
+# SPDX-License-Identifier: Apache-2.0
+
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-b
+  namespace: test-namespace
+  labels:
+    name: test-pod-label
+spec:
+  containers:
+  - name: kubernetes-pause
+    image: k8s.gcr.io/pause:2.0
diff --git a/e2e/live/testdata/test-case-1c/pod-c.yaml b/e2e/live/testdata/test-case-1c/pod-c.yaml
new file mode 100644
index 00000000..647352ba
--- /dev/null
+++ b/e2e/live/testdata/test-case-1c/pod-c.yaml
@@ -0,0 +1,14 @@
+# Copyright 2019 The Kubernetes Authors.
+# SPDX-License-Identifier: Apache-2.0
+
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-c
+  namespace: test-namespace
+  labels:
+    name: test-pod-label
+spec:
+  containers:
+  - name: kubernetes-pause
+    image: k8s.gcr.io/pause:2.0
diff --git a/e2e/testdata/live-apply/apply-depends-on/resources/Kptfile b/e2e/testdata/live-apply/apply-depends-on/resources/Kptfile
index 29cd7e66..10c54471 100644
--- a/e2e/testdata/live-apply/apply-depends-on/resources/Kptfile
+++ b/e2e/testdata/live-apply/apply-depends-on/resources/Kptfile
@@ -1,8 +1,4 @@
 apiVersion: kpt.dev/v1
 kind: Kptfile
 metadata:
-  name: apply-depends-on
-inventory:
-  namespace: apply-depends-on
-  name: apply-depends-on
-  inventoryID: apply-depends-on
+  name: apply-depends-on
\ No newline at end of file
diff --git a/e2e/testdata/live-apply/apply-depends-on/resources/resourcegroup.yaml b/e2e/testdata/live-apply/apply-depends-on/resources/resourcegroup.yaml
new file mode 100644
index 00000000..28a6b9c8
--- /dev/null
+++ b/e2e/testdata/live-apply/apply-depends-on/resources/resourcegroup.yaml
@@ -0,0 +1,7 @@
+apiVersion: kpt.dev/v1alpha1
+kind: ResourceGroup
+metadata:
+  name: apply-depends-on
+  namespace: apply-depends-on
+  labels:
+    cli-utils.sigs.k8s.io/inventory-id: apply-depends-on
\ No newline at end of file
diff --git a/e2e/testdata/live-apply/crd-and-cr/resources/Kptfile b/e2e/testdata/live-apply/crd-and-cr/resources/Kptfile
index 76c995a0..d6bc1889 100644
--- a/e2e/testdata/live-apply/crd-and-cr/resources/Kptfile
+++ b/e2e/testdata/live-apply/crd-and-cr/resources/Kptfile
@@ -2,7 +2,3 @@ apiVersion: kpt.dev/v1
 kind: Kptfile
 metadata:
   name: crd-and-cr
-inventory:
-  namespace: crd-and-cr
-  name: crd-and-cr
-  inventoryID: crd-and-cr
diff --git a/e2e/testdata/live-apply/crd-and-cr/resources/resourcegroup.yaml b/e2e/testdata/live-apply/crd-and-cr/resources/resourcegroup.yaml
new file mode 100644
index 00000000..4b87a0c4
--- /dev/null
+++ b/e2e/testdata/live-apply/crd-and-cr/resources/resourcegroup.yaml
@@ -0,0 +1,7 @@
+apiVersion: kpt.dev/v1alpha1
+kind: ResourceGroup
+metadata:
+  name: crd-and-cr
+  namespace: crd-and-cr
+  labels:
+    cli-utils.sigs.k8s.io/inventory-id: crd-and-cr
\ No newline at end of file
diff --git a/e2e/testdata/live-apply/dry-run-with-install-rg/resources/Kptfile b/e2e/testdata/live-apply/dry-run-with-install-rg/resources/Kptfile
index 8ce249cb..2550e3f4 100644
--- a/e2e/testdata/live-apply/dry-run-with-install-rg/resources/Kptfile
+++ b/e2e/testdata/live-apply/dry-run-with-install-rg/resources/Kptfile
@@ -2,7 +2,3 @@ apiVersion: kpt.dev/v1
 kind: Kptfile
 metadata:
   name: dry-run-with-install-rg
-inventory:
-  namespace: dry-run-with-install-rg
-  name: dry-run-with-install-rg
-  inventoryID: dry-run-with-install-rg
diff --git a/e2e/testdata/live-apply/dry-run-with-install-rg/resources/resourcegroup.yaml b/e2e/testdata/live-apply/dry-run-with-install-rg/resources/resourcegroup.yaml
new file mode 100644
index 00000000..b475f624
--- /dev/null
+++ b/e2e/testdata/live-apply/dry-run-with-install-rg/resources/resourcegroup.yaml
@@ -0,0 +1,7 @@
+apiVersion: kpt.dev/v1alpha1
+kind: ResourceGroup
+metadata:
+  name: dry-run-with-install-rg
+  namespace: dry-run-with-install-rg
+  labels:
+    cli-utils.sigs.k8s.io/inventory-id: dry-run-with-install-rg
\ No newline at end of file
diff --git a/e2e/testdata/live-apply/dry-run-without-rg/resources/Kptfile b/e2e/testdata/live-apply/dry-run-without-rg/resources/Kptfile
index 110cb19a..60134cc7 100644
--- a/e2e/testdata/live-apply/dry-run-without-rg/resources/Kptfile
+++ b/e2e/testdata/live-apply/dry-run-without-rg/resources/Kptfile
@@ -2,7 +2,3 @@ apiVersion: kpt.dev/v1
 kind: Kptfile
 metadata:
   name: dry-run-without-rg
-inventory:
-  namespace: dry-run-without-rg
-  name: dry-run-without-rg
-  inventoryID: dry-run-without-rg
diff --git a/e2e/testdata/live-apply/dry-run-without-rg/resources/resourcegroup.yaml b/e2e/testdata/live-apply/dry-run-without-rg/resources/resourcegroup.yaml
new file mode 100644
index 00000000..c210ca31
--- /dev/null
+++ b/e2e/testdata/live-apply/dry-run-without-rg/resources/resourcegroup.yaml
@@ -0,0 +1,7 @@
+apiVersion: kpt.dev/v1alpha1
+kind: ResourceGroup
+metadata:
+  name: dry-run-without-rg
+  namespace: dry-run-without-rg
+  labels:
+    cli-utils.sigs.k8s.io/inventory-id: dry-run-without-rg
\ No newline at end of file
diff --git a/e2e/testdata/live-apply/install-rg-on-apply/resources/Kptfile b/e2e/testdata/live-apply/install-rg-on-apply/resources/Kptfile
index 42a012dd..cd2ed9da 100644
--- a/e2e/testdata/live-apply/install-rg-on-apply/resources/Kptfile
+++ b/e2e/testdata/live-apply/install-rg-on-apply/resources/Kptfile
@@ -2,7 +2,3 @@ apiVersion: kpt.dev/v1
 kind: Kptfile
 metadata:
   name: install-rg-on-apply
-inventory:
-  namespace: install-rg-on-apply
-  name: install-rg-on-apply
-  inventoryID: install-rg-on-apply
diff --git a/e2e/testdata/live-apply/install-rg-on-apply/resources/resourcegroup.yaml b/e2e/testdata/live-apply/install-rg-on-apply/resources/resourcegroup.yaml
new file mode 100644
index 00000000..e125343c
--- /dev/null
+++ b/e2e/testdata/live-apply/install-rg-on-apply/resources/resourcegroup.yaml
@@ -0,0 +1,7 @@
+apiVersion: kpt.dev/v1alpha1
+kind: ResourceGroup
+metadata:
+  name: install-rg-on-apply
+  namespace: install-rg-on-apply
+  labels:
+    cli-utils.sigs.k8s.io/inventory-id: install-rg-on-apply
\ No newline at end of file
diff --git a/e2e/testdata/live-apply/json-output/resources/Kptfile b/e2e/testdata/live-apply/json-output/resources/Kptfile
index dff6968e..bbda252a 100644
--- a/e2e/testdata/live-apply/json-output/resources/Kptfile
+++ b/e2e/testdata/live-apply/json-output/resources/Kptfile
@@ -2,7 +2,3 @@ apiVersion: kpt.dev/v1
 kind: Kptfile
 metadata:
   name: json-output
-inventory:
-  namespace: json-output
-  name: json-output
-  inventoryID: json-output
diff --git a/e2e/testdata/live-apply/json-output/resources/resourcegroup.yaml b/e2e/testdata/live-apply/json-output/resources/resourcegroup.yaml
new file mode 100644
index 00000000..a592a77a
--- /dev/null
+++ b/e2e/testdata/live-apply/json-output/resources/resourcegroup.yaml
@@ -0,0 +1,7 @@
+apiVersion: kpt.dev/v1alpha1
+kind: ResourceGroup
+metadata:
+  name: json-output
+  namespace: json-output
+  labels:
+    cli-utils.sigs.k8s.io/inventory-id: json-output
\ No newline at end of file
diff --git a/e2e/testdata/live-apply/prune-depends-on/resources/Kptfile b/e2e/testdata/live-apply/prune-depends-on/resources/Kptfile
index b51a693c..e53a1c30 100644
--- a/e2e/testdata/live-apply/prune-depends-on/resources/Kptfile
+++ b/e2e/testdata/live-apply/prune-depends-on/resources/Kptfile
@@ -2,7 +2,3 @@ apiVersion: kpt.dev/v1
 kind: Kptfile
 metadata:
   name: prune-depends-on
-inventory:
-  namespace: prune-depends-on
-  name: prune-depends-on
-  inventoryID: prune-depends-on
diff --git a/e2e/testdata/live-apply/prune-depends-on/resources/resourcegroup.yaml b/e2e/testdata/live-apply/prune-depends-on/resources/resourcegroup.yaml
new file mode 100644
index 00000000..a23b3d39
--- /dev/null
+++ b/e2e/testdata/live-apply/prune-depends-on/resources/resourcegroup.yaml
@@ -0,0 +1,7 @@
+apiVersion: kpt.dev/v1alpha1
+kind: ResourceGroup
+metadata:
+  name: prune-depends-on
+  namespace: prune-depends-on
+  labels:
+    cli-utils.sigs.k8s.io/inventory-id: prune-depends-on
\ No newline at end of file
diff --git a/internal/cmdliveinit/cmdliveinit_test.go b/internal/cmdliveinit/cmdliveinit_test.go
index c909e04d..6ae3cfba 100644
--- a/internal/cmdliveinit/cmdliveinit_test.go
+++ b/internal/cmdliveinit/cmdliveinit_test.go
@@ -115,28 +115,6 @@ func TestCmd_generateID(t *testing.T) {
 	}
 }
 
-func TestCmd_Run_NoKptfile(t *testing.T) {
-	// Set up fake test factory
-	tf := cmdtesting.NewTestFactory().WithNamespace("test-ns")
-	defer tf.Cleanup()
-	ioStreams, _, _, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled
-
-	w, clean := testutil.SetupWorkspace(t)
-	defer clean()
-
-	revert := testutil.Chdir(t, w.WorkspaceDirectory)
-	defer revert()
-
-	runner := NewRunner(fake.CtxWithDefaultPrinter(), tf, ioStreams)
-	runner.Command.SetArgs([]string{})
-	err := runner.Command.Execute()
-
-	if !assert.Error(t, err) {
-		t.FailNow()
-	}
-	assert.Contains(t, err.Error(), "error reading Kptfile at")
-}
-
 func TestCmd_Run(t *testing.T) {
 	testCases := map[string]struct {
 		kptfile           string
@@ -153,17 +131,18 @@ func TestCmd_Run(t *testing.T) {
 		"Fields are defaulted if not provided": {
 			kptfile:         kptFile,
 			name:            "",
+			rgfilename:      "resourcegroup.yaml",
 			namespace:       "testns",
 			inventoryID:     "",
 			expectAutoGenID: true,
 			expectedInventory: kptfilev1.Inventory{
-				Namespace:   "testns",
-				Name:        "inventory-*",
-				InventoryID: "33ee4887f9638ef63efe71a9a9a632d3e9e2488e-*",
+				Namespace: "testns",
+				Name:      "inventory-*",
 			},
 		},
 		"Provided values are used": {
 			kptfile:     kptFile,
+			rgfilename:  "custom-rg.yaml",
 			name:        "my-pkg",
 			namespace:   "my-ns",
 			inventoryID: "my-inv-id",
@@ -188,6 +167,7 @@ func TestCmd_Run(t *testing.T) {
 		"Kptfile with inventory already set is error": {
 			kptfile:          kptFileWithInventory,
 			name:             inventoryName,
+			rgfilename:       "custom-rg.yaml",
 			namespace:        inventoryNamespace,
 			inventoryID:      inventoryID,
 			force:            false,
@@ -216,6 +196,7 @@ func TestCmd_Run(t *testing.T) {
 		"The force flag allows changing inventory information even if already set in Kptfile": {
 			kptfile:     kptFileWithInventory,
 			name:        inventoryName,
+			rgfilename:  "resourcegroup.yaml",
 			namespace:   inventoryNamespace,
 			inventoryID: inventoryID,
 			force:       true,
@@ -269,8 +250,7 @@ func TestCmd_Run(t *testing.T) {
 			defer revert()
 
 			runner := NewRunner(fake.CtxWithDefaultPrinter(), tf, ioStreams)
-			runner.namespace = tc.namespace
-			runner.RGFile = tc.rgfilename
+			runner.RGFileName = tc.rgfilename
 			args := []string{
 				"--name", tc.name,
 				"--inventory-id", tc.inventoryID,
@@ -323,7 +303,6 @@ func TestCmd_Run(t *testing.T) {
 			expectedInv := tc.expectedInventory
 			assertInventoryName(t, expectedInv.Name, actualInv.Name)
 			assert.Equal(t, expectedInv.Namespace, actualInv.Namespace)
-
 			if tc.expectAutoGenID {
 				assertGenInvID(t, actualInv.Name, actualInv.Namespace, actualInv.InventoryID)
 			} else {
diff --git a/internal/cmdmigrate/migratecmd_test.go b/internal/cmdmigrate/migratecmd_test.go
index 9617d811..0d287594 100644
--- a/internal/cmdmigrate/migratecmd_test.go
+++ b/internal/cmdmigrate/migratecmd_test.go
@@ -28,15 +28,6 @@ var testNamespace = "test-inventory-namespace"
 var inventoryObjName = "test-inventory-obj"
 var testInventoryLabel = "test-inventory-label"
 
-var kptfileStr = `
-apiVersion: kpt.dev/v1
-kind: Kptfile
-inventory:
-  name: test-inventory-obj
-  namespace: test-inventory-namespace
-  inventoryID: test-inventory-label
-`
-
 var rgInvObj = &unstructured.Unstructured{
 	Object: map[string]interface{}{
 		"apiVersion": "kpt.dev/v1alpha1",
@@ -100,84 +91,6 @@ var pod2 = &unstructured.Unstructured{
 	},
 }
 
-func TestKptMigrate_updateKptfile(t *testing.T) {
-	testCases := map[string]struct {
-		kptfile string
-		dryRun  bool
-		isError bool
-	}{
-		"Missing Kptfile is an error": {
-			kptfile: "",
-			dryRun:  false,
-			isError: true,
-		},
-		"Kptfile with existing inventory and is not an error": {
-			kptfile: kptFileWithInventory,
-			dryRun:  false,
-			isError: false,
-		},
-		"Dry-run will not fill in inventory fields": {
-			kptfile: kptFile,
-			dryRun:  true,
-			isError: false,
-		},
-		"Kptfile will have inventory fields filled in": {
-			kptfile: kptFile,
-			dryRun:  false,
-			isError: false,
-		},
-	}
-
-	for tn, tc := range testCases {
-		t.Run(tn, func(t *testing.T) {
-			// Set up fake test factory
-			tf := cmdtesting.NewTestFactory().WithNamespace(inventoryNamespace)
-			defer tf.Cleanup()
-			ioStreams, _, _, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled
-
-			// Set up temp directory with Ktpfile
-			dir := t.TempDir()
-			p := filepath.Join(dir, "Kptfile")
-			err := ioutil.WriteFile(p, []byte(tc.kptfile), 0600)
-			assert.NoError(t, err)
-
-			ctx := fake.CtxWithDefaultPrinter()
-			// Create MigrateRunner and call "updateKptfile"
-			cmLoader := manifestreader.NewManifestLoader(tf)
-			migrateRunner := NewRunner(ctx, tf, cmLoader, ioStreams)
-			migrateRunner.dryRun = tc.dryRun
-			migrateRunner.cmInvClientFunc = func(factory util.Factory) (inventory.Client, error) {
-				return inventory.NewFakeClient([]object.ObjMetadata{}), nil
-			}
-			err = migrateRunner.updateKptfile(ctx, []string{dir}, testInventoryID)
-			// Check if there should be an error
-			if tc.isError {
-				if err == nil {
-					t.Fatalf("expected error but received none")
-				}
-				return
-			}
-			assert.NoError(t, err)
-			kf, err := pkg.ReadKptfile(filesys.FileSystemOrOnDisk{}, dir)
-			if !assert.NoError(t, err) {
-				t.FailNow()
-			}
-			// Check the kptfile inventory section now has values.
-			if !tc.dryRun {
-				assert.Equal(t, inventoryNamespace, kf.Inventory.Namespace)
-				if len(kf.Inventory.Name) == 0 {
-					t.Errorf("inventory name not set in Kptfile")
-				}
-				if kf.Inventory.InventoryID != testInventoryID {
-					t.Errorf("inventory id not set in Kptfile: %s", kf.Inventory.InventoryID)
-				}
-			} else if kf.Inventory != nil {
-				t.Errorf("inventory shouldn't be set during dryrun")
-			}
-		})
-	}
-}
-
 func TestKptMigrate_migrateKptfileToRG(t *testing.T) {
 	testCases := map[string]struct {
 		kptfile       string
@@ -387,9 +300,10 @@ func TestKptMigrate_findResourceGroupInv(t *testing.T) {
 
 func TestKptMigrate_migrateObjs(t *testing.T) {
 	testCases := map[string]struct {
-		invObj  string
-		objs    []object.ObjMetadata
-		isError bool
+		invObj     string
+		objs       []object.ObjMetadata
+		isError    bool
+		rgFilename string
 	}{
 		"No objects to migrate is valid": {
 			invObj:  "",
@@ -397,27 +311,37 @@ func TestKptMigrate_migrateObjs(t *testing.T) {
 			isError: false,
 		},
 		"One migrate object is valid": {
-			invObj:  kptfileStr,
-			objs:    []object.ObjMetadata{object.UnstructuredToObjMetadata(pod1)},
-			isError: false,
+			invObj:     resourceGroupInventory,
+			objs:       []object.ObjMetadata{object.UnstructuredToObjMetadata(pod1)},
+			rgFilename: rgfilev1alpha1.RGFileName,
+			isError:    false,
 		},
 		"Multiple migrate objects are valid": {
-			invObj: kptfileStr,
+			invObj: resourceGroupInventory,
 			objs: []object.ObjMetadata{
 				object.UnstructuredToObjMetadata(pod1),
 				object.UnstructuredToObjMetadata(pod2),
 			},
-			isError: false,
+			rgFilename: rgfilev1alpha1.RGFileName,
+			isError:    false,
 		},
 		"Kptfile does not have inventory is valid": {
-			invObj:  kptFile,
-			objs:    []object.ObjMetadata{},
-			isError: false,
+			invObj:     resourceGroupInventory,
+			objs:       []object.ObjMetadata{},
+			rgFilename: rgfilev1alpha1.RGFileName,
+			isError:    false,
 		},
 		"One migrate object is valid with inventory in Kptfile": {
-			invObj:  kptFileWithInventory,
-			objs:    []object.ObjMetadata{object.UnstructuredToObjMetadata(pod1)},
-			isError: false,
+			invObj:     resourceGroupInventory,
+			objs:       []object.ObjMetadata{object.UnstructuredToObjMetadata(pod1)},
+			rgFilename: rgfilev1alpha1.RGFileName,
+			isError:    false,
+		},
+		"Migrate to ResourceGroup object with custom filename": {
+			invObj:     resourceGroupInventory,
+			objs:       []object.ObjMetadata{object.UnstructuredToObjMetadata(pod1)},
+			rgFilename: "test-rg.yaml",
+			isError:    false,
 		},
 	}
 
@@ -433,6 +357,8 @@ func TestKptMigrate_migrateObjs(t *testing.T) {
 			rgInvClient := inventory.NewFakeClient(tc.objs)
 			cmLoader := manifestreader.NewManifestLoader(tf)
 			migrateRunner := NewRunner(ctx, tf, cmLoader, ioStreams)
+			migrateRunner.rgFile = tc.rgFilename
+
 			err := migrateRunner.migrateObjs(rgInvClient, tc.objs, strings.NewReader(tc.invObj), []string{"-"})
 			// Check if there should be an error
 			if tc.isError {
diff --git a/pkg/live/load_test.go b/pkg/live/load_test.go
index 2746869d..df302ffa 100644
--- a/pkg/live/load_test.go
+++ b/pkg/live/load_test.go
@@ -19,7 +19,6 @@ import (
 	"os"
 	"path/filepath"
 	"sort"
-	"strings"
 	"testing"
 
 	"github.com/GoogleContainerTools/kpt/internal/testutil"
@@ -57,6 +56,7 @@ func TestLoad_LocalDisk(t *testing.T) {
 					Namespace: "foo",
 				},
 			},
+			expectedErrMsg: "no ResourceGroup object was provided within the stream or package",
 		},
 		"missing namespace for namespace scoped resources are defaulted": {
 			pkg: pkgbuilder.NewRootPkg().
@@ -71,8 +71,9 @@ func TestLoad_LocalDisk(t *testing.T) {
 					Namespace: "foo",
 				},
 			},
+			expectedErrMsg: "no ResourceGroup object was provided within the stream or package",
 		},
-		"function config is included by default": {
+		"function config is excluded": {
 			pkg: pkgbuilder.NewRootPkg().
 				WithKptfile(
 					pkgbuilder.NewKptfile().
@@ -80,7 +81,11 @@ func TestLoad_LocalDisk(t *testing.T) {
 							pkgbuilder.NewFunction("gcr.io/kpt-dev/func:latest").
 								WithConfigPath("cm.yaml"),
 						),
-				).
+				).WithRGFile(pkgbuilder.NewRGFile().WithInventory(pkgbuilder.Inventory{
+				Name:      "foo",
+				Namespace: "bar",
+				ID:        "foo-bar"},
+			)).
 				WithFile("cm.yaml", configMap).
 				WithSubPackages(
 					pkgbuilder.NewSubPkg("subpkg").
@@ -94,6 +99,11 @@ func TestLoad_LocalDisk(t *testing.T) {
 						WithFile("deployment.yaml", deploymentA),
 				),
 			namespace: "foo",
+			expectedInv: kptfile.Inventory{
+				Name:        "foo",
+				Namespace:   "bar",
+				InventoryID: "foo-bar",
+			},
 			expectedObjs: []object.ObjMetadata{
 				{
 					GroupKind: schema.GroupKind{
@@ -212,7 +222,7 @@ func TestLoad_LocalDisk(t *testing.T) {
 			}()
 
 			var buf bytes.Buffer
-			objs, inv, err := Load(tf, dir, tc.rgFile, &buf)
+			objs, inv, err := Load(tf, dir, &buf)
 
 			if tc.expectedErrMsg != "" {
 				if !assert.Error(t, err) {
@@ -242,7 +252,6 @@ func TestLoad_StdIn(t *testing.T) {
 		expectedInv    kptfile.Inventory
 		expectedErrMsg string
 		rgFile         string
-		rgInStream     bool
 	}{
 		"no inventory among the resources": {
 			pkg: pkgbuilder.NewRootPkg().
@@ -250,7 +259,7 @@ func TestLoad_StdIn(t *testing.T) {
 					pkgbuilder.NewKptfile(),
 				).
 				WithFile("deployment.yaml", deploymentA),
-			expectedErrMsg: "no inventory information was provided within the stream",
+			expectedErrMsg: "no ResourceGroup object was provided within the stream or package",
 		},
 		"missing namespace for namespace scoped resources are defaulted": {
 			pkg: pkgbuilder.NewRootPkg().
@@ -341,55 +350,7 @@ func TestLoad_StdIn(t *testing.T) {
 						),
 				),
 			namespace:      "foo",
-			expectedErrMsg: "multiple inventory information found in package",
-		},
-		"Multiple inventories, stdin and local resourcegroup, is an error": {
-			pkg: pkgbuilder.NewRootPkg().
-				WithKptfile(
-					pkgbuilder.NewKptfile().
-						WithInventory(pkgbuilder.Inventory{
-							Name:      "foo",
-							Namespace: "bar",
-							ID:        "foo-bar",
-						}),
-				).
-				WithRGFile(pkgbuilder.NewRGFile().WithInventory(pkgbuilder.Inventory{
-					Name:      "foo",
-					Namespace: "bar",
-					ID:        "foo-bar",
-				},
-				)),
-			expectedErrMsg: "multiple inventory information found in package",
-			rgFile:         rgfilev1alpha1.RGFileName,
-		},
-		"Inventory using local resourcegroup file": {
-			pkg: pkgbuilder.NewRootPkg().
-				WithKptfile(
-					pkgbuilder.NewKptfile(),
-				).
-				WithFile("cm.yaml", configMap).
-				WithRGFile(pkgbuilder.NewRGFile().WithInventory(pkgbuilder.Inventory{
-					Name:      "foo",
-					Namespace: "bar",
-					ID:        "foo-bar",
-				},
-				)),
-			namespace: "foo",
-			expectedInv: kptfile.Inventory{
-				Name:        "foo",
-				Namespace:   "bar",
-				InventoryID: "foo-bar",
-			},
-			expectedObjs: []object.ObjMetadata{
-				{
-					GroupKind: schema.GroupKind{
-						Kind: "ConfigMap",
-					},
-					Name:      "cm",
-					Namespace: "foo",
-				},
-			},
-			rgFile: rgfilev1alpha1.RGFileName,
+			expectedErrMsg: "multiple Kptfile inventories found in package",
 		},
 		"Inventory using STDIN resourcegroup file": {
 			pkg: pkgbuilder.NewRootPkg().
@@ -418,7 +379,6 @@ func TestLoad_StdIn(t *testing.T) {
 					Namespace: "foo",
 				},
 			},
-			rgInStream: true,
 		},
 		"Multiple inventories using STDIN resourcegroup and Kptfile is error": {
 			pkg: pkgbuilder.NewRootPkg().
@@ -437,8 +397,7 @@ func TestLoad_StdIn(t *testing.T) {
 					ID:        "foo-bar",
 				},
 				)),
-			expectedErrMsg: "multiple inventory information found in package",
-			rgInStream:     true,
+			expectedErrMsg: "inventory was found in both Kptfile and ResourceGroup object",
 		},
 		"Non-valid inventory using STDIN Kptfile is error": {
 			pkg: pkgbuilder.NewRootPkg().
@@ -449,7 +408,7 @@ func TestLoad_StdIn(t *testing.T) {
 						}),
 				).
 				WithFile("cm.yaml", configMap),
-			expectedErrMsg: "no inventory information was provided within the stream",
+			expectedErrMsg: "the provided ResourceGroup is not valid",
 		},
 		"Non-valid inventory in resourcegroup is error": {
 			pkg: pkgbuilder.NewRootPkg().
@@ -461,7 +420,7 @@ func TestLoad_StdIn(t *testing.T) {
 					Name: "foo",
 				},
 				)),
-			expectedErrMsg: "no inventory information was provided within the stream or package",
+			expectedErrMsg: "the provided ResourceGroup is not valid",
 			rgFile:         rgfilev1alpha1.RGFileName,
 		},
 	}
@@ -488,14 +447,6 @@ func TestLoad_StdIn(t *testing.T) {
 						MatchFilesGlob:        append([]string{kptfile.KptFileName}, kio.DefaultMatch...),
 						IncludeSubpackages:    true,
 						WrapBareSeqNode:       true,
-						FileSkipFunc: func(rp string) bool {
-							// No skipping if we don't have a resourcegroup file, or we stream it in STDIN.
-							if tc.rgFile == "" || tc.rgInStream {
-								return false
-							}
-
-							return strings.Contains(rp, tc.rgFile)
-						},
 					},
 				},
 				Outputs: []kio.Writer{
@@ -508,15 +459,10 @@ func TestLoad_StdIn(t *testing.T) {
 				t.FailNow()
 			}
 
-			if tc.rgFile != "" {
-				os.Remove(filepath.Join(dir, kptfile.KptFileName))
-			}
-
-			if tc.rgInStream {
-				os.Remove(filepath.Join(dir, tc.rgFile))
-			}
+			os.Remove(filepath.Join(dir, kptfile.KptFileName))
+			os.Remove(filepath.Join(dir, tc.rgFile))
 
-			objs, inv, err := Load(tf, "-", tc.rgFile, &buf)
+			objs, inv, err := Load(tf, "-", &buf)
 
 			if tc.expectedErrMsg != "" {
 				if !assert.Error(t, err) {
@@ -565,7 +511,7 @@ func TestValidateInventory(t *testing.T) {
 				Name: "foo",
 			},
 			expectErr:           true,
-			expectedErrorFields: []string{"namespace"},
+			expectedErrorFields: []string{"namespace", "inventoryID"},
 		},
 	}
 
diff --git a/thirdparty/cli-utils/status/cmdstatus_test.go b/thirdparty/cli-utils/status/cmdstatus_test.go
index ab43c32f..73781914 100644
--- a/thirdparty/cli-utils/status/cmdstatus_test.go
+++ b/thirdparty/cli-utils/status/cmdstatus_test.go
@@ -60,7 +60,7 @@ func TestStatusCommand(t *testing.T) {
 	}{
 		"no inventory template": {
 			kptfileInv:     nil,
-			expectedErrMsg: "inventory failed validation",
+			expectedErrMsg: "no ResourceGroup object was provided within the stream or package",
 		},
 		"invalid value for pollUntil": {
 			pollUntil:      "doesNotExist",