Repository: vmware-tanzu/tanzu-framework (language: go)

Issue:
Provide an option in package tooling to generate package bundle thick tarball
(This is used to request new product features)

**Describe the feature request**

Add a boolean flag called `thick` to `package-bundle generate` command to generate a thick tarball of the package bundle.

imgpkg command to generate thick tarball https://carvel.dev/imgpkg/docs/v0.27.0/air-gapped-workflow/#option-2-with-intermediate-tarball

**Describe alternatives you've considered**

**Affected product area (please put an X in all that apply)**

- [ ] APIs
- [x] Addons
- [ ] CLI
- [ ] Docs
- [ ] IAM
- [ ] Installation
- [ ] Plugin
- [ ] Security
- [ ] Test and Release
- [ ] User Experience

**Additional context**



Discussion / hints:

Fix patch:
diff --git a/Makefile b/Makefile
index 9d951ea88e..9ab6dd3e1a 100644
--- a/Makefile
+++ b/Makefile
@@ -727,10 +727,18 @@ prep-package-tools:
 
 .PHONY: package-bundle
 package-bundle: tools prep-package-tools ## Build one specific tar bundle package, needs PACKAGE_NAME VERSION
-	cd hack/packages/package-tools && $(GO) run main.go package-bundle generate $(PACKAGE_NAME) --repository=$(PACKAGE_REPOSITORY) --version=$(PACKAGE_VERSION) --sub-version=$(PACKAGE_SUB_VERSION)
+	cd hack/packages/package-tools && $(GO) run main.go package-bundle generate --thick $(PACKAGE_NAME) --repository=$(PACKAGE_REPOSITORY) --version=$(PACKAGE_VERSION) --sub-version=$(PACKAGE_SUB_VERSION)
 
+.PHONY: package-bundle-thin
+package-bundle-thin: tools prep-package-tools ## Build one specific tar bundle package, needs PACKAGE_NAME VERSION
+	cd hack/packages/package-tools && $(GO) run main.go package-bundle generate $(PACKAGE_NAME) --repository=$(PACKAGE_REPOSITORY) --version=$(PACKAGE_VERSION) --sub-version=$(PACKAGE_SUB_VERSION)
+	
 .PHONY: package-bundles
 package-bundles: tools prep-package-tools ## Build tar bundles for multiple packages
+	cd hack/packages/package-tools && $(GO) run main.go package-bundle generate --all --thick --repository=$(PACKAGE_REPOSITORY) --version=$(PACKAGE_VERSION) --sub-version=$(PACKAGE_SUB_VERSION)
+
+.PHONY: package-bundles-thin
+package-bundles-thin: tools prep-package-tools ## Build tar bundles for multiple packages
 	cd hack/packages/package-tools && $(GO) run main.go package-bundle generate --all --repository=$(PACKAGE_REPOSITORY) --version=$(PACKAGE_VERSION) --sub-version=$(PACKAGE_SUB_VERSION)
 
 .PHONY: package-repo-bundle
diff --git a/docs/packages/definitions.md b/docs/packages/definitions.md
index 69e3ad7312..88e728cea3 100644
--- a/docs/packages/definitions.md
+++ b/docs/packages/definitions.md
@@ -20,3 +20,8 @@ management cluster.
 A Package that contains PackageInstall CRs, Secrets and ServiceAccounts, RBAC rules that are needed to install other
 packages and when the meta package is installed, it installs the other packages. A meta Package may describe
 a specific version of a Distribution.
+
+## Thick tarball
+
+A tarball that contains a package bundle and its associated images.
+Thick tarballs are suitable for air-gapped environments.
diff --git a/docs/packages/dev-workflow.md b/docs/packages/dev-workflow.md
index 732ae498cc..ebd1c23be2 100644
--- a/docs/packages/dev-workflow.md
+++ b/docs/packages/dev-workflow.md
@@ -38,12 +38,19 @@ This document provides guidance on how to build and publish package and repo bun
       PACKAGE_REPOSITORY=management make push-package-bundles
    ```
 
-   To build a particular package bundle, run:
+   To build a particular package bundle, run the following command.
+   Note that included in the package bundle will be a thick tarball that is useful for air-gapped environments.
 
    ```shell
       PACKAGE_REPOSITORY=management PACKAGE_NAME=my-package make package-bundle
    ```
 
+   For environments where an external image registry is accessible, you may use the following command to build a package bundle:
+
+   ```shell
+      PACKAGE_NAME=my-package make package-bundle-thin
+   ```
+
 5. Build package repo bundle
 
    After building the package bundles, it is time to build package repository bundle, run the below make
diff --git a/hack/packages/package-tools/cmd/package-bundle-generate.go b/hack/packages/package-tools/cmd/package-bundle-generate.go
index 6c61a29529..b871081e1b 100644
--- a/hack/packages/package-tools/cmd/package-bundle-generate.go
+++ b/hack/packages/package-tools/cmd/package-bundle-generate.go
@@ -19,7 +19,7 @@ import (
 )
 
 var packageRepository, version, subVersion string
-var all bool
+var all, thick bool
 
 // packageBundleGenerateCmd is for generating package bundle
 var packageBundleGenerateCmd = &cobra.Command{
@@ -34,6 +34,7 @@ func init() {
 	packageBundleGenerateCmd.Flags().StringVar(&version, "version", "", "Package bundle version")
 	packageBundleGenerateCmd.Flags().StringVar(&subVersion, "sub-version", "", "Package bundle subversion")
 	packageBundleGenerateCmd.Flags().BoolVar(&all, "all", false, "Generate all package bundles in a repository")
+	packageBundleGenerateCmd.Flags().BoolVar(&thick, "thick", false, "Include thick tarball(s) in package bundle(s)")
 	packageBundleGenerateCmd.MarkFlagRequired("repository") //nolint: errcheck
 	packageBundleGenerateCmd.MarkFlagRequired("version")    //nolint: errcheck
 }
@@ -64,7 +65,7 @@ func runPackageBundleGenerate(cmd *cobra.Command, args []string) error {
 		if err := generateSingleImgpkgLockOutput(projectRootDir, toolsBinDir, packagePath); err != nil {
 			return fmt.Errorf("couldn't generate imgpkg lock output file: %w", err)
 		}
-		if err := generatePackageBundle(projectRootDir, packageName, packagePath); err != nil {
+		if err := generatePackageBundle(projectRootDir, toolsBinDir, packageName, packagePath); err != nil {
 			return fmt.Errorf("couldn't generate the package bundle: %w", err)
 		}
 	}
@@ -122,7 +123,7 @@ func generateSingleImgpkgLockOutput(projectRootDir, toolsBinDir, packagePath str
 	return nil
 }
 
-func generatePackageBundle(projectRootDir, packageName, packagePath string) error {
+func generatePackageBundle(projectRootDir, toolsBinDir, packageName, packagePath string) error {
 	if err := utils.RunMakeTarget(packagePath, "configure-package"); err != nil {
 		return err
 	}
@@ -140,6 +141,38 @@ func generatePackageBundle(projectRootDir, packageName, packagePath string) erro
 		return fmt.Errorf("couldn't generate package bundle: %w", err)
 	}
 
+	// create thick tarball
+	if thick {
+		fmt.Println("Including thick tarball...")
+		var cmdErr bytes.Buffer
+
+		packageURL := fmt.Sprintf("%s/%s:%s", constants.LocalRegistryURL, packageName, imagePackageVersion)
+		imgpkgPushCmd := exec.Command(
+			filepath.Join(toolsBinDir, "imgpkg"),
+			"push",
+			"-b", packageURL,
+			"--file", filepath.Join(packagePath, "bundle"),
+		) // #nosec G204
+		imgpkgPushCmd.Stderr = &cmdErr
+		if err := imgpkgPushCmd.Run(); err != nil {
+			fmt.Println("cmd:", imgpkgPushCmd.String())
+			fmt.Println("err:", err)
+			return fmt.Errorf("pushing package bundle to local registry: %s", cmdErr.String())
+		}
+
+		tarBallFileName = packageName + "-" + imagePackageVersion + "-thick.tar.gz"
+		imgpkgCopyCmd := exec.Command(
+			filepath.Join(toolsBinDir, "imgpkg"),
+			"copy",
+			"-b", packageURL,
+			"--to-tar", filepath.Join(tarBallPath, tarBallFileName),
+		) // #nosec G204
+		imgpkgCopyCmd.Stderr = &cmdErr
+		if err := imgpkgCopyCmd.Run(); err != nil {
+			return fmt.Errorf("generating thick tarball: %s", cmdErr.String())
+		}
+	}
+
 	if err := utils.RunMakeTarget(packagePath, "reset-package"); err != nil {
 		return err
 	}
@@ -215,7 +248,7 @@ func generatePackageBundles(projectRootDir, toolsBinDir string) error {
 			return err
 		}
 
-		if err := generatePackageBundle(projectRootDir, pkg.Name, packagePath); err != nil {
+		if err := generatePackageBundle(projectRootDir, toolsBinDir, pkg.Name, packagePath); err != nil {
 			return fmt.Errorf("couldn't generate package bundle: %w", err)
 		}
 


Test patch: