diff --git a/docs/3-create-a-zarf-package/4-zarf-schema.md b/docs/3-create-a-zarf-package/4-zarf-schema.md
index 80a989a03b..050539669f 100644
--- a/docs/3-create-a-zarf-package/4-zarf-schema.md
+++ b/docs/3-create-a-zarf-package/4-zarf-schema.md
@@ -409,6 +409,25 @@ Must be one of:
+
+
+ registryOverrides
+
+
+
+
+ ## build > registryOverrides
+
+**Description:** Any registry domains that were overridden on package create when pulling images
+
+| | |
+| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
+| **Type** | `object` |
+| **Additional properties** | [![Any type: allowed](https://img.shields.io/badge/Any%20type-allowed-green)](# "Additional Properties of any type are allowed.") |
+
+
+
+
differential
@@ -425,21 +444,18 @@ Must be one of:
-
+
- registryOverrides
+ differentialPackageVersion
- ## build > registryOverrides
-
-**Description:** Any registry domains that were overridden on package create when pulling images
+**Description:** Version of a previously built package used as the basis for creating this differential package
-| | |
-| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
-| **Type** | `object` |
-| **Additional properties** | [![Any type: allowed](https://img.shields.io/badge/Any%20type-allowed-green)](# "Additional Properties of any type are allowed.") |
+| | |
+| -------- | -------- |
+| **Type** | `string` |
diff --git a/docs/3-create-a-zarf-package/5-package-create-lifecycle.md b/docs/3-create-a-zarf-package/5-package-create-lifecycle.md
index 58da969df8..fa1a0a8d6d 100644
--- a/docs/3-create-a-zarf-package/5-package-create-lifecycle.md
+++ b/docs/3-create-a-zarf-package/5-package-create-lifecycle.md
@@ -6,50 +6,51 @@ The following diagram shows the order of operations for the `zarf package create
```mermaid
graph TD
- A1(set working directory)-->A2
- A2(parse zarf.yaml)-->A3
- A3(filter components by architecture)-->A4
- A4(detect init package)-->A5
- A5(handle deprecations)-->A6
+ A1(cd to directory with zarf.yaml)-->A2
+ A2(load zarf.yaml into memory)-->A3
+ A3(set package architecture if not provided)-->A4
+ A4(filter components by architecture and flavor)-->A5
+ A5(migrate deprecated component configs)-->A6
A6(parse component imports)-->A7
A7(process create-time variables)-->A8
- A8(write build data and zarf.yaml)-->A9
-
- A9(run validations)-->A10
- A10(confirm package create):::prompt-->A11
- A11{Init package?}
- A11 -->|Yes| A12(add seed image)-->A13
- A11 -->|No| A13
+ A8(process extensions)-->A9
+ A9(remove duplicate images/repos if --differential flag used)-->A10
+ A10(run validations)-->A11
+ A11(confirm package create):::prompt-->A12
subgraph
- A13(add each component)-->A13
- A13 --> A14(run each '.actions.onCreate.before'):::action-->A14
- A14 --> A15(load '.charts')-->A16
- A16(load '.files')-->A17
- A17(load '.dataInjections')-->A18
- A18(load '.manifests')-->A19
- A19(load '.repos')-->A20
- A20(run each '.actions.onCreate.after'):::action-->A20
- A20-->A21{Success?}
- A21-->|Yes|A22(run each\n'.actions.onCreate.success'):::action-->A22
- A21-->|No|A23(run each\n'.actions.onCreate.failure'):::action-->A23-->A999
+ A12(run each '.actions.onCreate.before'):::action-->A13(load '.charts')
+ A13-->A14(load '.files')
+ A14-->A15(load '.dataInjections')
+ A15-->A16(load '.manifests')
+ A16-->A17(load '.repos')
+ A17-->A18(run each '.actions.onCreate.after'):::action
+ A18-->A19{Success?}
+ A19-->|Yes|A20(run each\n'.actions.onCreate.success'):::action
+ A19-->|No|A999
end
- A22-->A24(load all '.images')
- A24-->A25{Skip SBOM?}
- A25-->|Yes|A27
- A25-->|No|A26
- A26(generate SBOM)-->A27
- A27(reset working directory)-->A28
- A28(create package archive)-->A29
- A29{Is multipart?}
- A29-->|Yes|A30(split package archive)-->A31
- A29-->|No|A31
- A31(handle sbom view/out flags)
+ A20-->A21(load all '.images')
+ A21-->A22(generate SBOMs unless --skip-sbom flag was used)
+ A22-->A23(cd back to original working directory)
+ A23-->A24(archive components into tarballs)
+ A24-->A25(generate checksums for all package files)
+ A25-->A26(record package build metadata)
+ A26-->A27(write the zarf.yaml to disk)
+ A27-->A28(sign the package if a key was provided)
+ A28-->A29{Output to OCI?}
+ A29-->|Yes|A30(publish package to OCI registry)
+ A29-->|No|A31(archive package into a tarball and write to disk)
+ A30-->A32
+ A31-->A32
+ A32(write SBOM files to disk if --sbom or --sbom-out flags used)-->A33
+ A33(view SBOMs if --sbom flag used)-->A34
+ A34[Zarf Package Create Successful]:::success
A999[Abort]:::fail
classDef prompt fill:#4adede,color:#000000
classDef action fill:#bd93f9,color:#000000
classDef fail fill:#aa0000
+ classDef success fill:#008000,color:#fff;
```
diff --git a/src/cmd/common/utils.go b/src/cmd/common/utils.go
index 01a6b104d6..1da7e456ee 100644
--- a/src/cmd/common/utils.go
+++ b/src/cmd/common/utils.go
@@ -4,15 +4,10 @@
// Package common handles command configuration across all commands
package common
-import (
- "github.com/defenseunicorns/zarf/src/types"
-)
-
-// SetBaseDirectory sets base directory on package config when given in args
-func SetBaseDirectory(args []string, pkgConfig *types.PackagerConfig) {
+// SetBaseDirectory sets the base directory. This is a directory with a zarf.yaml.
+func SetBaseDirectory(args []string) string {
if len(args) > 0 {
- pkgConfig.CreateOpts.BaseDir = args[0]
- } else {
- pkgConfig.CreateOpts.BaseDir = "."
+ return args[0]
}
+ return "."
}
diff --git a/src/cmd/dev.go b/src/cmd/dev.go
index 0ded3ac401..9bc12a2b1c 100644
--- a/src/cmd/dev.go
+++ b/src/cmd/dev.go
@@ -41,7 +41,7 @@ var devDeployCmd = &cobra.Command{
Short: lang.CmdDevDeployShort,
Long: lang.CmdDevDeployLong,
Run: func(_ *cobra.Command, args []string) {
- common.SetBaseDirectory(args, &pkgConfig)
+ pkgConfig.CreateOpts.BaseDir = common.SetBaseDirectory(args)
v := common.GetViper()
pkgConfig.CreateOpts.SetVariables = helpers.TransformAndMergeMap(
@@ -116,7 +116,7 @@ var devTransformGitLinksCmd = &cobra.Command{
if confirm {
// Overwrite the file
- err = os.WriteFile(fileName, []byte(processedText), 0640)
+ err = os.WriteFile(fileName, []byte(processedText), helpers.ReadAllWriteUser)
if err != nil {
message.Fatal(err, lang.CmdDevPatchGitFileWriteErr)
}
@@ -207,7 +207,7 @@ var devFindImagesCmd = &cobra.Command{
Short: lang.CmdDevFindImagesShort,
Long: lang.CmdDevFindImagesLong,
Run: func(_ *cobra.Command, args []string) {
- common.SetBaseDirectory(args, &pkgConfig)
+ pkgConfig.CreateOpts.BaseDir = common.SetBaseDirectory(args)
// Ensure uppercase keys from viper
v := common.GetViper()
@@ -256,7 +256,7 @@ var devLintCmd = &cobra.Command{
Short: lang.CmdDevLintShort,
Long: lang.CmdDevLintLong,
Run: func(_ *cobra.Command, args []string) {
- common.SetBaseDirectory(args, &pkgConfig)
+ pkgConfig.CreateOpts.BaseDir = common.SetBaseDirectory(args)
v := common.GetViper()
pkgConfig.CreateOpts.SetVariables = helpers.TransformAndMergeMap(
v.GetStringMapString(common.VPkgCreateSet), pkgConfig.CreateOpts.SetVariables, strings.ToUpper)
diff --git a/src/cmd/initialize.go b/src/cmd/initialize.go
index e12eccd665..40f633ed41 100644
--- a/src/cmd/initialize.go
+++ b/src/cmd/initialize.go
@@ -44,7 +44,7 @@ var initCmd = &cobra.Command{
}
// Continue running package deploy for all components like any other package
- initPackageName := packager.GetInitPackageName("")
+ initPackageName := sources.GetInitPackageName()
pkgConfig.PkgOpts.PackageSource = initPackageName
// Try to use an init-package in the executable directory if none exist in current working directory
diff --git a/src/cmd/package.go b/src/cmd/package.go
index f5a1c57804..f296684e7a 100644
--- a/src/cmd/package.go
+++ b/src/cmd/package.go
@@ -41,7 +41,7 @@ var packageCreateCmd = &cobra.Command{
Short: lang.CmdPackageCreateShort,
Long: lang.CmdPackageCreateLong,
Run: func(_ *cobra.Command, args []string) {
- common.SetBaseDirectory(args, &pkgConfig)
+ pkgConfig.CreateOpts.BaseDir = common.SetBaseDirectory(args)
var isCleanPathRegex = regexp.MustCompile(`^[a-zA-Z0-9\_\-\/\.\~\\:]+$`)
if !isCleanPathRegex.MatchString(config.CommonOptions.CachePath) {
@@ -350,7 +350,7 @@ func bindCreateFlags(v *viper.Viper) {
createFlags.StringVar(&pkgConfig.CreateOpts.Output, "output-directory", v.GetString("package.create.output_directory"), lang.CmdPackageCreateFlagOutput)
createFlags.StringVarP(&pkgConfig.CreateOpts.Output, "output", "o", v.GetString(common.VPkgCreateOutput), lang.CmdPackageCreateFlagOutput)
- createFlags.StringVar(&pkgConfig.CreateOpts.DifferentialData.DifferentialPackagePath, "differential", v.GetString(common.VPkgCreateDifferential), lang.CmdPackageCreateFlagDifferential)
+ createFlags.StringVar(&pkgConfig.CreateOpts.DifferentialPackagePath, "differential", v.GetString(common.VPkgCreateDifferential), lang.CmdPackageCreateFlagDifferential)
createFlags.StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdPackageCreateFlagSet)
createFlags.BoolVarP(&pkgConfig.CreateOpts.ViewSBOM, "sbom", "s", v.GetBool(common.VPkgCreateSbom), lang.CmdPackageCreateFlagSbom)
createFlags.StringVar(&pkgConfig.CreateOpts.SBOMOutputDir, "sbom-out", v.GetString(common.VPkgCreateSbomOutput), lang.CmdPackageCreateFlagSbomOut)
diff --git a/src/config/lang/english.go b/src/config/lang/english.go
index 9ff198e8e1..d919da0f9b 100644
--- a/src/config/lang/english.go
+++ b/src/config/lang/english.go
@@ -633,12 +633,13 @@ const (
AgentErrUnableTransform = "unable to transform the provided request; see zarf http proxy logs for more details"
)
-// src/internal/packager/create
+// Package create
const (
- PkgCreateErrDifferentialSameVersion = "unable to create a differential package with the same version as the package you are using as a reference; the package version must be incremented"
+ PkgCreateErrDifferentialSameVersion = "unable to create differential package. Please ensure the differential package version and reference package version are not the same. The package version must be incremented"
+ PkgCreateErrDifferentialNoVersion = "unable to create differential package. Please ensure both package versions are set"
)
-// src/internal/packager/deploy.
+// Package deploy
const (
PkgDeployErrMultipleComponentsSameGroup = "You cannot specify multiple components (%q, %q) within the same group (%q) when using the --components flag."
PkgDeployErrNoDefaultOrSelection = "You must make a selection from %q with the --components flag as there is no default in their group."
@@ -646,7 +647,7 @@ const (
PkgDeployErrComponentSelectionCanceled = "Component selection canceled: %s"
)
-// src/internal/packager/validate.
+// Package validate
const (
PkgValidateTemplateDeprecation = "Package template %q is using the deprecated syntax ###ZARF_PKG_VAR_%s###. This will be removed in Zarf v1.0.0. Please update to ###ZARF_PKG_TMPL_%s###."
PkgValidateMustBeUppercase = "variable name %q must be all uppercase and contain no special characters except _"
diff --git a/src/internal/packager/sbom/tools.go b/src/internal/packager/sbom/tools.go
index e964f85ece..860b50e879 100644
--- a/src/internal/packager/sbom/tools.go
+++ b/src/internal/packager/sbom/tools.go
@@ -6,15 +6,11 @@ package sbom
import (
"fmt"
- "os"
"path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/defenseunicorns/zarf/src/pkg/message"
- "github.com/defenseunicorns/zarf/src/pkg/utils"
"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
- "github.com/defenseunicorns/zarf/src/pkg/utils/helpers"
- "github.com/defenseunicorns/zarf/src/types"
)
// ViewSBOMFiles opens a browser to view the SBOM files and pauses for user input.
@@ -41,29 +37,3 @@ func ViewSBOMFiles(directory string) {
message.Note("There were no images with software bill-of-materials (SBOM) included.")
}
}
-
-// OutputSBOMFiles outputs the sbom files into a specified directory.
-func OutputSBOMFiles(sourceDir, outputDir, packageName string) (string, error) {
- packagePath := filepath.Join(outputDir, packageName)
-
- if err := os.RemoveAll(packagePath); err != nil {
- return "", err
- }
-
- if err := utils.CreateDirectory(packagePath, helpers.ReadWriteExecuteUser); err != nil {
- return "", err
- }
-
- return packagePath, utils.CreatePathAndCopy(sourceDir, packagePath)
-}
-
-// IsSBOMAble checks if a package has contents that an SBOM can be created on (i.e. images, files, or data injections)
-func IsSBOMAble(pkg types.ZarfPackage) bool {
- for _, c := range pkg.Components {
- if len(c.Images) > 0 || len(c.Files) > 0 || len(c.DataInjections) > 0 {
- return true
- }
- }
-
- return false
-}
diff --git a/src/pkg/layout/package.go b/src/pkg/layout/package.go
index 4e7bcff63a..2683ebf603 100644
--- a/src/pkg/layout/package.go
+++ b/src/pkg/layout/package.go
@@ -5,15 +5,21 @@
package layout
import (
+ "fmt"
"os"
"path/filepath"
+ "slices"
"strings"
"github.com/Masterminds/semver/v3"
+ "github.com/defenseunicorns/zarf/src/pkg/interactive"
"github.com/defenseunicorns/zarf/src/pkg/message"
+ "github.com/defenseunicorns/zarf/src/pkg/packager/deprecated"
"github.com/defenseunicorns/zarf/src/pkg/utils"
+ "github.com/defenseunicorns/zarf/src/pkg/utils/helpers"
"github.com/defenseunicorns/zarf/src/types"
"github.com/google/go-containerregistry/pkg/crane"
+ "github.com/mholt/archiver/v3"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
@@ -51,6 +57,29 @@ func New(baseDir string) *PackagePaths {
}
}
+// ReadZarfYAML reads a zarf.yaml file into memory,
+// checks if it's using the legacy layout, and migrates deprecated component configs.
+func (pp *PackagePaths) ReadZarfYAML(path string) (pkg types.ZarfPackage, warnings []string, err error) {
+ if err := utils.ReadYaml(path, &pkg); err != nil {
+ return types.ZarfPackage{}, nil, fmt.Errorf("unable to read zarf.yaml file")
+ }
+
+ if pp.IsLegacyLayout() {
+ warnings = append(warnings, "Detected deprecated package layout, migrating to new layout - support for this package will be dropped in v1.0.0")
+ }
+
+ if len(pkg.Build.Migrations) > 0 {
+ var componentWarnings []string
+ for idx, component := range pkg.Components {
+ // Handle component configuration deprecations
+ pkg.Components[idx], componentWarnings = deprecated.MigrateComponent(pkg.Build, component)
+ warnings = append(warnings, componentWarnings...)
+ }
+ }
+
+ return pkg, warnings, nil
+}
+
// MigrateLegacy migrates a legacy package layout to the new layout.
func (pp *PackagePaths) MigrateLegacy() (err error) {
var pkg types.ZarfPackage
@@ -139,12 +168,87 @@ func (pp *PackagePaths) IsLegacyLayout() bool {
return pp.isLegacyLayout
}
-// AddSignature sets the signature path if the keyPath is not empty.
-func (pp *PackagePaths) AddSignature(keyPath string) *PackagePaths {
- if keyPath != "" {
- pp.Signature = filepath.Join(pp.Base, Signature)
+// SignPackage signs the zarf.yaml in a Zarf package.
+func (pp *PackagePaths) SignPackage(signingKeyPath, signingKeyPassword string) error {
+ pp.Signature = filepath.Join(pp.Base, Signature)
+
+ passwordFunc := func(_ bool) ([]byte, error) {
+ if signingKeyPassword != "" {
+ return []byte(signingKeyPassword), nil
+ }
+ return interactive.PromptSigPassword()
}
- return pp
+ _, err := utils.CosignSignBlob(pp.ZarfYAML, pp.Signature, signingKeyPath, passwordFunc)
+ if err != nil {
+ return fmt.Errorf("unable to sign the package: %w", err)
+ }
+
+ return nil
+}
+
+// GenerateChecksums walks through all of the files starting at the base path and generates a checksum file.
+//
+// Each file within the basePath represents a layer within the Zarf package.
+//
+// Returns a SHA256 checksum of the checksums.txt file.
+func (pp *PackagePaths) GenerateChecksums() (string, error) {
+ var checksumsData = []string{}
+
+ for rel, abs := range pp.Files() {
+ if rel == ZarfYAML || rel == Checksums {
+ continue
+ }
+
+ sum, err := utils.GetSHA256OfFile(abs)
+ if err != nil {
+ return "", err
+ }
+ checksumsData = append(checksumsData, fmt.Sprintf("%s %s", sum, rel))
+ }
+ slices.Sort(checksumsData)
+
+ // Create the checksums file
+ if err := os.WriteFile(pp.Checksums, []byte(strings.Join(checksumsData, "\n")+"\n"), helpers.ReadWriteUser); err != nil {
+ return "", err
+ }
+
+ // Calculate the checksum of the checksum file
+ return utils.GetSHA256OfFile(pp.Checksums)
+}
+
+// ArchivePackage creates an archive for a Zarf package.
+func (pp *PackagePaths) ArchivePackage(destinationTarball string, maxPackageSizeMB int) error {
+ spinner := message.NewProgressSpinner("Writing %s to %s", pp.Base, destinationTarball)
+ defer spinner.Stop()
+
+ // Make the archive
+ archiveSrc := []string{pp.Base + string(os.PathSeparator)}
+ if err := archiver.Archive(archiveSrc, destinationTarball); err != nil {
+ return fmt.Errorf("unable to create package: %w", err)
+ }
+ spinner.Updatef("Wrote %s to %s", pp.Base, destinationTarball)
+
+ fi, err := os.Stat(destinationTarball)
+ if err != nil {
+ return fmt.Errorf("unable to read the package archive: %w", err)
+ }
+ spinner.Successf("Package saved to %q", destinationTarball)
+
+ // Convert Megabytes to bytes.
+ chunkSize := maxPackageSizeMB * 1000 * 1000
+
+ // If a chunk size was specified and the package is larger than the chunk size, split it into chunks.
+ if maxPackageSizeMB > 0 && fi.Size() > int64(chunkSize) {
+ if fi.Size()/int64(chunkSize) > 999 {
+ return fmt.Errorf("unable to split the package archive into multiple files: must be less than 1,000 files")
+ }
+ message.Notef("Package is larger than %dMB, splitting into multiple files", maxPackageSizeMB)
+ err := utils.SplitFile(destinationTarball, chunkSize)
+ if err != nil {
+ return fmt.Errorf("unable to split the package archive into multiple files: %w", err)
+ }
+ }
+ return nil
}
// AddImages sets the default image paths.
@@ -242,7 +346,7 @@ func (pp *PackagePaths) Files() map[string]string {
add(tarball)
}
- if filepath.Ext(pp.SBOMs.Path) == ".tar" {
+ if pp.SBOMs.IsTarball() {
add(pp.SBOMs.Path)
}
return pathMap
diff --git a/src/pkg/layout/package_test.go b/src/pkg/layout/package_test.go
index 55c3aeb695..8e7036f066 100644
--- a/src/pkg/layout/package_test.go
+++ b/src/pkg/layout/package_test.go
@@ -5,6 +5,7 @@
package layout
import (
+ "path/filepath"
"runtime"
"strings"
"testing"
@@ -14,128 +15,154 @@ import (
)
func TestPackageFiles(t *testing.T) {
- pp := New("test")
-
- raw := &PackagePaths{
- Base: "test",
- ZarfYAML: normalizePath("test/zarf.yaml"),
- Checksums: normalizePath("test/checksums.txt"),
- Components: Components{
- Base: normalizePath("test/components"),
- },
- }
-
- require.Equal(t, raw, pp)
-
- files := pp.Files()
-
- expected := map[string]string{
- "zarf.yaml": normalizePath("test/zarf.yaml"),
- "checksums.txt": normalizePath("test/checksums.txt"),
- }
-
- require.Equal(t, expected, files)
-
- pp = pp.AddSignature("")
-
- files = pp.Files()
-
- // AddSignature will only add the signature if it is not empty
- require.Equal(t, expected, files)
-
- pp = pp.AddSignature("key.priv")
-
- files = pp.Files()
-
- expected = map[string]string{
- "zarf.yaml": normalizePath("test/zarf.yaml"),
- "checksums.txt": normalizePath("test/checksums.txt"),
- "zarf.yaml.sig": normalizePath("test/zarf.yaml.sig"),
- }
-
- require.Equal(t, expected, files)
- pp = pp.AddImages()
-
- files = pp.Files()
-
- // Note that the map key will always be the forward "Slash" (/) version of the file path (never \)
- expected = map[string]string{
- "zarf.yaml": normalizePath("test/zarf.yaml"),
- "checksums.txt": normalizePath("test/checksums.txt"),
- "zarf.yaml.sig": normalizePath("test/zarf.yaml.sig"),
- "images/index.json": normalizePath("test/images/index.json"),
- "images/oci-layout": normalizePath("test/images/oci-layout"),
- }
-
- require.Equal(t, expected, files)
+ t.Parallel()
- pp = pp.AddSBOMs()
+ t.Run("Verify New()", func(t *testing.T) {
+ t.Parallel()
- files = pp.Files()
+ pp := New("test")
- // AddSBOMs adds the SBOMs directory, and files will only cares about files
- require.Equal(t, expected, files)
-
- paths := []string{
- "zarf.yaml",
- "checksums.txt",
- "sboms.tar",
- normalizePath("components/c1.tar"),
- normalizePath("images/index.json"),
- normalizePath("images/oci-layout"),
- normalizePath("images/blobs/sha256/" + strings.Repeat("1", 64)),
- }
-
- pp = New("test")
-
- pp.SetFromPaths(paths)
-
- files = pp.Files()
-
- expected = map[string]string{
- "zarf.yaml": normalizePath("test/zarf.yaml"),
- "checksums.txt": normalizePath("test/checksums.txt"),
- "sboms.tar": normalizePath("test/sboms.tar"),
- "components/c1.tar": normalizePath("test/components/c1.tar"),
- "images/index.json": normalizePath("test/images/index.json"),
- "images/oci-layout": normalizePath("test/images/oci-layout"),
- "images/blobs/sha256/" + strings.Repeat("1", 64): normalizePath("test/images/blobs/sha256/" + strings.Repeat("1", 64)),
- }
-
- require.Len(t, pp.Images.Blobs, 1)
-
- require.Equal(t, expected, files)
-
- descs := []ocispec.Descriptor{
- {
- Annotations: map[string]string{
- ocispec.AnnotationTitle: "components/c2.tar",
+ raw := &PackagePaths{
+ Base: "test",
+ ZarfYAML: normalizePath("test/zarf.yaml"),
+ Checksums: normalizePath("test/checksums.txt"),
+ Components: Components{
+ Base: normalizePath("test/components"),
},
- },
- {
- Annotations: map[string]string{
- ocispec.AnnotationTitle: "images/blobs/sha256/" + strings.Repeat("2", 64),
+ }
+ require.Equal(t, raw, pp)
+ })
+
+ t.Run("Verify Files()", func(t *testing.T) {
+ t.Parallel()
+
+ pp := New("test")
+
+ files := pp.Files()
+ expected := map[string]string{
+ "zarf.yaml": normalizePath("test/zarf.yaml"),
+ "checksums.txt": normalizePath("test/checksums.txt"),
+ }
+ require.Equal(t, expected, files)
+ })
+
+ t.Run("Verify Files() with signature", func(t *testing.T) {
+ t.Parallel()
+
+ pp := New("test")
+ pp.Signature = filepath.Join(pp.Base, Signature)
+
+ files := pp.Files()
+ expected := map[string]string{
+ "zarf.yaml": normalizePath("test/zarf.yaml"),
+ "checksums.txt": normalizePath("test/checksums.txt"),
+ "zarf.yaml.sig": normalizePath("test/zarf.yaml.sig"),
+ }
+ require.Equal(t, expected, files)
+ })
+
+ t.Run("Verify Files() with images", func(t *testing.T) {
+ t.Parallel()
+
+ pp := New("test")
+ pp = pp.AddImages()
+
+ files := pp.Files()
+ expected := map[string]string{
+ "zarf.yaml": normalizePath("test/zarf.yaml"),
+ "checksums.txt": normalizePath("test/checksums.txt"),
+ "images/index.json": normalizePath("test/images/index.json"),
+ "images/oci-layout": normalizePath("test/images/oci-layout"),
+ }
+ require.Equal(t, expected, files)
+ })
+
+ // AddSBOMs sets the SBOMs path, so Files() should not return new files.
+ t.Run("Verify Files() with SBOMs", func(t *testing.T) {
+ t.Parallel()
+
+ pp := New("test")
+ pp = pp.AddSBOMs()
+
+ files := pp.Files()
+ expected := map[string]string{
+ "zarf.yaml": normalizePath("test/zarf.yaml"),
+ "checksums.txt": normalizePath("test/checksums.txt"),
+ }
+ require.Equal(t, expected, files)
+
+ pp.SBOMs.Path = normalizePath("test/sboms.tar")
+ files = pp.Files()
+ expected = map[string]string{
+ "zarf.yaml": normalizePath("test/zarf.yaml"),
+ "checksums.txt": normalizePath("test/checksums.txt"),
+ "sboms.tar": normalizePath("test/sboms.tar"),
+ }
+ require.Equal(t, expected, files)
+ })
+
+ t.Run("Verify Files() with paths mapped to package paths", func(t *testing.T) {
+ t.Parallel()
+
+ pp := New("test")
+
+ paths := []string{
+ "zarf.yaml",
+ "checksums.txt",
+ "sboms.tar",
+ normalizePath("components/c1.tar"),
+ normalizePath("images/index.json"),
+ normalizePath("images/oci-layout"),
+ normalizePath("images/blobs/sha256/" + strings.Repeat("1", 64)),
+ }
+ pp.SetFromPaths(paths)
+
+ files := pp.Files()
+ expected := map[string]string{
+ "zarf.yaml": normalizePath("test/zarf.yaml"),
+ "checksums.txt": normalizePath("test/checksums.txt"),
+ "sboms.tar": normalizePath("test/sboms.tar"),
+ "components/c1.tar": normalizePath("test/components/c1.tar"),
+ "images/index.json": normalizePath("test/images/index.json"),
+ "images/oci-layout": normalizePath("test/images/oci-layout"),
+ "images/blobs/sha256/" + strings.Repeat("1", 64): normalizePath("test/images/blobs/sha256/" + strings.Repeat("1", 64)),
+ }
+
+ require.Len(t, pp.Images.Blobs, 1)
+ require.Equal(t, expected, files)
+ })
+
+ t.Run("Verify Files() with image layers mapped to package paths", func(t *testing.T) {
+ t.Parallel()
+
+ pp := New("test")
+
+ descs := []ocispec.Descriptor{
+ {
+ Annotations: map[string]string{
+ ocispec.AnnotationTitle: "components/c2.tar",
+ },
},
- },
- }
-
- pp.SetFromLayers(descs)
-
- files = pp.Files()
-
- expected = map[string]string{
- "zarf.yaml": normalizePath("test/zarf.yaml"),
- "checksums.txt": normalizePath("test/checksums.txt"),
- "sboms.tar": normalizePath("test/sboms.tar"),
- "components/c1.tar": normalizePath("test/components/c1.tar"),
- "components/c2.tar": normalizePath("test/components/c2.tar"),
- "images/index.json": normalizePath("test/images/index.json"),
- "images/oci-layout": normalizePath("test/images/oci-layout"),
- "images/blobs/sha256/" + strings.Repeat("1", 64): normalizePath("test/images/blobs/sha256/" + strings.Repeat("1", 64)),
- "images/blobs/sha256/" + strings.Repeat("2", 64): normalizePath("test/images/blobs/sha256/" + strings.Repeat("2", 64)),
- }
-
- require.Equal(t, expected, files)
+ {
+ Annotations: map[string]string{
+ ocispec.AnnotationTitle: "images/blobs/sha256/" + strings.Repeat("1", 64),
+ },
+ },
+ }
+ pp.AddImages()
+ pp.SetFromLayers(descs)
+
+ files := pp.Files()
+ expected := map[string]string{
+ "zarf.yaml": normalizePath("test/zarf.yaml"),
+ "checksums.txt": normalizePath("test/checksums.txt"),
+ "components/c2.tar": normalizePath("test/components/c2.tar"),
+ "images/index.json": normalizePath("test/images/index.json"),
+ "images/oci-layout": normalizePath("test/images/oci-layout"),
+ "images/blobs/sha256/" + strings.Repeat("1", 64): normalizePath("test/images/blobs/sha256/" + strings.Repeat("1", 64)),
+ }
+ require.Equal(t, expected, files)
+ })
}
// normalizePath ensures that the filepaths being generated are normalized to the host OS.
diff --git a/src/pkg/layout/sbom.go b/src/pkg/layout/sbom.go
index 13f7ee0fc1..fe7dcefd02 100644
--- a/src/pkg/layout/sbom.go
+++ b/src/pkg/layout/sbom.go
@@ -5,6 +5,7 @@
package layout
import (
+ "fmt"
"io/fs"
"os"
"path/filepath"
@@ -67,12 +68,44 @@ func (s *SBOMs) Archive() (err error) {
return os.RemoveAll(dir)
}
-// IsDir returns true if the SBOMs are a directory.
-func (s SBOMs) IsDir() bool {
- return utils.IsDir(s.Path)
+// StageSBOMViewFiles copies SBOM viewer HTML files to the Zarf SBOM directory.
+func (s *SBOMs) StageSBOMViewFiles() (warnings []string, err error) {
+ if s.IsTarball() {
+ return nil, fmt.Errorf("unable to process the SBOM files for this package: %s is a tarball", s.Path)
+ }
+
+ // If SBOMs were loaded, temporarily place them in the deploy directory
+ if !utils.InvalidPath(s.Path) {
+ if _, err := filepath.Glob(filepath.Join(s.Path, "sbom-viewer-*")); err != nil {
+ return nil, err
+ }
+
+ if _, err := s.OutputSBOMFiles(SBOMDir, ""); err != nil {
+ // Don't stop the deployment, let the user decide if they want to continue the deployment
+ warning := fmt.Sprintf("Unable to process the SBOM files for this package: %s", err.Error())
+ warnings = append(warnings, warning)
+ }
+ }
+
+ return warnings, nil
+}
+
+// OutputSBOMFiles outputs SBOM files into outputDir.
+func (s *SBOMs) OutputSBOMFiles(outputDir, packageName string) (string, error) {
+ packagePath := filepath.Join(outputDir, packageName)
+
+ if err := os.RemoveAll(packagePath); err != nil {
+ return "", err
+ }
+
+ if err := utils.CreateDirectory(packagePath, 0700); err != nil {
+ return "", err
+ }
+
+ return packagePath, utils.CreatePathAndCopy(s.Path, packagePath)
}
// IsTarball returns true if the SBOMs are a tarball.
func (s SBOMs) IsTarball() bool {
- return !s.IsDir() && filepath.Ext(s.Path) == ".tar"
+ return !utils.IsDir(s.Path) && filepath.Ext(s.Path) == ".tar"
}
diff --git a/src/pkg/packager/actions.go b/src/pkg/packager/actions/actions.go
similarity index 86%
rename from src/pkg/packager/actions.go
rename to src/pkg/packager/actions/actions.go
index 1a49af931d..c6df1abc8d 100644
--- a/src/pkg/packager/actions.go
+++ b/src/pkg/packager/actions/actions.go
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
-// Package packager contains functions for interacting with, managing and deploying zarf packages.
-package packager
+// Package actions contains functions for running component actions within Zarf packages.
+package actions
import (
"context"
@@ -20,9 +20,10 @@ import (
"github.com/defenseunicorns/zarf/src/types"
)
-func (p *Packager) runActions(defaultCfg types.ZarfComponentActionDefaults, actions []types.ZarfComponentAction, valueTemplate *template.Values) error {
+// Run runs all provided actions.
+func Run(cfg *types.PackagerConfig, defaultCfg types.ZarfComponentActionDefaults, actions []types.ZarfComponentAction, valueTemplate *template.Values) error {
for _, a := range actions {
- if err := p.runAction(defaultCfg, a, valueTemplate); err != nil {
+ if err := runAction(cfg, defaultCfg, a, valueTemplate); err != nil {
return err
}
}
@@ -30,7 +31,7 @@ func (p *Packager) runActions(defaultCfg types.ZarfComponentActionDefaults, acti
}
// Run commands that a component has provided.
-func (p *Packager) runAction(defaultCfg types.ZarfComponentActionDefaults, action types.ZarfComponentAction, valueTemplate *template.Values) error {
+func runAction(cfg *types.PackagerConfig, defaultCfg types.ZarfComponentActionDefaults, action types.ZarfComponentAction, valueTemplate *template.Values) error {
var (
ctx context.Context
cancel context.CancelFunc
@@ -87,23 +88,23 @@ func (p *Packager) runAction(defaultCfg types.ZarfComponentActionDefaults, actio
vars, _ = valueTemplate.GetVariables(types.ZarfComponent{})
}
- cfg := actionGetCfg(defaultCfg, action, vars)
+ actionDefaults := actionGetCfg(defaultCfg, action, vars)
- if cmd, err = actionCmdMutation(cmd, cfg.Shell); err != nil {
+ if cmd, err = actionCmdMutation(cmd, actionDefaults.Shell); err != nil {
spinner.Errorf(err, "Error mutating command: %s", cmdEscaped)
}
- duration := time.Duration(cfg.MaxTotalSeconds) * time.Second
+ duration := time.Duration(actionDefaults.MaxTotalSeconds) * time.Second
timeout := time.After(duration)
// Keep trying until the max retries is reached.
retryCmd:
- for remaining := cfg.MaxRetries + 1; remaining > 0; remaining-- {
+ for remaining := actionDefaults.MaxRetries + 1; remaining > 0; remaining-- {
// Perform the action run.
tryCmd := func(ctx context.Context) error {
// Try running the command and continue the retry loop if it fails.
- if out, err = actionRun(ctx, cfg, cmd, cfg.Shell, spinner); err != nil {
+ if out, err = actionRun(ctx, actionDefaults, cmd, actionDefaults.Shell, spinner); err != nil {
return err
}
@@ -111,8 +112,8 @@ retryCmd:
// If an output variable is defined, set it.
for _, v := range action.SetVariables {
- p.setVariableInConfig(v.Name, out, v.Sensitive, v.AutoIndent, v.Type)
- if err := p.checkVariablePattern(v.Name, v.Pattern); err != nil {
+ cfg.SetVariable(v.Name, out, v.Sensitive, v.AutoIndent, v.Type)
+ if err := cfg.CheckVariablePattern(v.Name, v.Pattern); err != nil {
message.WarnErr(err, err.Error())
return err
}
@@ -130,7 +131,7 @@ retryCmd:
}
// If no timeout is set, run the command and return or continue retrying.
- if cfg.MaxTotalSeconds < 1 {
+ if actionDefaults.MaxTotalSeconds < 1 {
spinner.Updatef("Waiting for \"%s\" (no timeout)", cmdEscaped)
if err := tryCmd(context.TODO()); err != nil {
continue retryCmd
@@ -140,7 +141,7 @@ retryCmd:
}
// Run the command on repeat until success or timeout.
- spinner.Updatef("Waiting for \"%s\" (timeout: %ds)", cmdEscaped, cfg.MaxTotalSeconds)
+ spinner.Updatef("Waiting for \"%s\" (timeout: %ds)", cmdEscaped, actionDefaults.MaxTotalSeconds)
select {
// On timeout break the loop to abort.
case <-timeout:
@@ -161,14 +162,14 @@ retryCmd:
select {
case <-timeout:
// If we reached this point, the timeout was reached or command failed with no retries.
- if cfg.MaxTotalSeconds < 1 {
- return fmt.Errorf("command %q failed after %d retries", cmdEscaped, cfg.MaxRetries)
+ if actionDefaults.MaxTotalSeconds < 1 {
+ return fmt.Errorf("command %q failed after %d retries", cmdEscaped, actionDefaults.MaxRetries)
} else {
- return fmt.Errorf("command %q timed out after %d seconds", cmdEscaped, cfg.MaxTotalSeconds)
+ return fmt.Errorf("command %q timed out after %d seconds", cmdEscaped, actionDefaults.MaxTotalSeconds)
}
default:
// If we reached this point, the retry limit was reached.
- return fmt.Errorf("command %q failed after %d retries", cmdEscaped, cfg.MaxRetries)
+ return fmt.Errorf("command %q failed after %d retries", cmdEscaped, actionDefaults.MaxRetries)
}
}
diff --git a/src/pkg/packager/common.go b/src/pkg/packager/common.go
index b2286f7fd4..11101ecec2 100644
--- a/src/pkg/packager/common.go
+++ b/src/pkg/packager/common.go
@@ -8,8 +8,6 @@ import (
"errors"
"fmt"
"os"
- "path/filepath"
- "regexp"
"strings"
"time"
@@ -17,14 +15,11 @@ import (
"github.com/Masterminds/semver/v3"
"github.com/defenseunicorns/zarf/src/config/lang"
- "github.com/defenseunicorns/zarf/src/internal/packager/sbom"
"github.com/defenseunicorns/zarf/src/internal/packager/template"
"github.com/defenseunicorns/zarf/src/pkg/cluster"
"github.com/defenseunicorns/zarf/src/types"
- "github.com/mholt/archiver/v3"
"github.com/defenseunicorns/zarf/src/config"
- "github.com/defenseunicorns/zarf/src/pkg/interactive"
"github.com/defenseunicorns/zarf/src/pkg/layout"
"github.com/defenseunicorns/zarf/src/pkg/message"
"github.com/defenseunicorns/zarf/src/pkg/packager/deprecated"
@@ -37,7 +32,6 @@ type Packager struct {
cfg *types.PackagerConfig
cluster *cluster.Cluster
layout *layout.PackagePaths
- arch string
warnings []string
valueTemplate *template.Values
hpaModified bool
@@ -47,15 +41,6 @@ type Packager struct {
generation int
}
-// Zarf Packager Variables.
-var (
- // Find zarf-packages on the local system (https://regex101.com/r/TUUftK/1)
- ZarfPackagePattern = regexp.MustCompile(`zarf-package[^\s\\\/]*\.tar(\.zst)?$`)
-
- // Find zarf-init packages on the local system
- ZarfInitPattern = regexp.MustCompile(GetInitPackageName("") + "$")
-)
-
// Modifier is a function that modifies the packager.
type Modifier func(*Packager)
@@ -161,37 +146,6 @@ func (p *Packager) setTempDirectory(path string) error {
return nil
}
-// GetInitPackageName returns the formatted name of the init package.
-func GetInitPackageName(arch string) string {
- if arch == "" {
- // No package has been loaded yet so lookup GetArch() with no package info
- arch = config.GetArch()
- }
- return fmt.Sprintf("zarf-init-%s-%s.tar.zst", arch, config.CLIVersion)
-}
-
-// GetPackageName returns the formatted name of the package.
-func (p *Packager) GetPackageName() string {
- if p.isInitConfig() {
- return GetInitPackageName(p.arch)
- }
-
- packageName := p.cfg.Pkg.Metadata.Name
- suffix := "tar.zst"
- if p.cfg.Pkg.Metadata.Uncompressed {
- suffix = "tar"
- }
-
- packageFileName := fmt.Sprintf("%s%s-%s", config.ZarfPackagePrefix, packageName, p.arch)
- if p.cfg.Pkg.Build.Differential {
- packageFileName = fmt.Sprintf("%s-%s-differential-%s", packageFileName, p.cfg.CreateOpts.DifferentialData.DifferentialPackageVersion, p.cfg.Pkg.Metadata.Version)
- } else if p.cfg.Pkg.Metadata.Version != "" {
- packageFileName = fmt.Sprintf("%s-%s", packageFileName, p.cfg.Pkg.Metadata.Version)
- }
-
- return fmt.Sprintf("%s.%s", packageFileName, suffix)
-}
-
// ClearTempPaths removes the temp directory and any files within it.
func (p *Packager) ClearTempPaths() {
// Remove the temp directory, but don't throw an error if it fails
@@ -218,11 +172,6 @@ func (p *Packager) isConnectedToCluster() bool {
return p.cluster != nil
}
-// isInitConfig returns whether the current packager instance is deploying an init config
-func (p *Packager) isInitConfig() bool {
- return p.cfg.Pkg.Kind == types.ZarfInitConfig
-}
-
// hasImages returns whether the current package contains images
func (p *Packager) hasImages() bool {
for _, component := range p.cfg.Pkg.Components {
@@ -269,7 +218,7 @@ func (p *Packager) attemptClusterChecks() (err error) {
// validatePackageArchitecture validates that the package architecture matches the target cluster architecture.
func (p *Packager) validatePackageArchitecture() error {
// Ignore this check if the architecture is explicitly "multi", we don't have a cluster connection, or the package contains no images
- if p.arch == "multi" || !p.isConnectedToCluster() || !p.hasImages() {
+ if p.cfg.Pkg.Metadata.Architecture == "multi" || !p.isConnectedToCluster() || !p.hasImages() {
return nil
}
@@ -279,8 +228,8 @@ func (p *Packager) validatePackageArchitecture() error {
}
// Check if the package architecture and the cluster architecture are the same.
- if !slices.Contains(clusterArchitectures, p.arch) {
- return fmt.Errorf(lang.CmdPackageDeployValidateArchitectureErr, p.arch, strings.Join(clusterArchitectures, ", "))
+ if !slices.Contains(clusterArchitectures, p.cfg.Pkg.Metadata.Architecture) {
+ return fmt.Errorf(lang.CmdPackageDeployValidateArchitectureErr, p.cfg.Pkg.Metadata.Architecture, strings.Join(clusterArchitectures, ", "))
}
return nil
@@ -319,70 +268,3 @@ func (p *Packager) validateLastNonBreakingVersion() (err error) {
return nil
}
-
-func (p *Packager) archivePackage(destinationTarball string) error {
- spinner := message.NewProgressSpinner("Writing %s to %s", p.layout.Base, destinationTarball)
- defer spinner.Stop()
-
- // Make the archive
- archiveSrc := []string{p.layout.Base + string(os.PathSeparator)}
- if err := archiver.Archive(archiveSrc, destinationTarball); err != nil {
- return fmt.Errorf("unable to create package: %w", err)
- }
- spinner.Updatef("Wrote %s to %s", p.layout.Base, destinationTarball)
-
- fi, err := os.Stat(destinationTarball)
- if err != nil {
- return fmt.Errorf("unable to read the package archive: %w", err)
- }
- spinner.Successf("Package saved to %q", destinationTarball)
-
- // Convert Megabytes to bytes.
- chunkSize := p.cfg.CreateOpts.MaxPackageSizeMB * 1000 * 1000
-
- // If a chunk size was specified and the package is larger than the chunk size, split it into chunks.
- if p.cfg.CreateOpts.MaxPackageSizeMB > 0 && fi.Size() > int64(chunkSize) {
- if fi.Size()/int64(chunkSize) > 999 {
- return fmt.Errorf("unable to split the package archive into multiple files: must be less than 1,000 files")
- }
- message.Notef("Package is larger than %dMB, splitting into multiple files", p.cfg.CreateOpts.MaxPackageSizeMB)
- err := utils.SplitFile(destinationTarball, chunkSize)
- if err != nil {
- return fmt.Errorf("unable to split the package archive into multiple files: %w", err)
- }
- }
- return nil
-}
-
-func (p *Packager) signPackage(signingKeyPath, signingKeyPassword string) error {
- p.layout = p.layout.AddSignature(signingKeyPath)
- passwordFunc := func(_ bool) ([]byte, error) {
- if signingKeyPassword != "" {
- return []byte(signingKeyPassword), nil
- }
- return interactive.PromptSigPassword()
- }
- _, err := utils.CosignSignBlob(p.layout.ZarfYAML, p.layout.Signature, signingKeyPath, passwordFunc)
- if err != nil {
- return fmt.Errorf("unable to sign the package: %w", err)
- }
- return nil
-}
-
-func (p *Packager) stageSBOMViewFiles() error {
- if p.layout.SBOMs.IsTarball() {
- return fmt.Errorf("unable to process the SBOM files for this package: %s is a tarball", p.layout.SBOMs.Path)
- }
- // If SBOMs were loaded, temporarily place them in the deploy directory
- sbomDir := p.layout.SBOMs.Path
- if !utils.InvalidPath(sbomDir) {
- p.sbomViewFiles, _ = filepath.Glob(filepath.Join(sbomDir, "sbom-viewer-*"))
- _, err := sbom.OutputSBOMFiles(sbomDir, layout.SBOMDir, "")
- if err != nil {
- // Don't stop the deployment, let the user decide if they want to continue the deployment
- warning := fmt.Sprintf("Unable to process the SBOM files for this package: %s", err.Error())
- p.warnings = append(p.warnings, warning)
- }
- }
- return nil
-}
diff --git a/src/pkg/packager/common_test.go b/src/pkg/packager/common_test.go
index 105daf0242..1f497415be 100644
--- a/src/pkg/packager/common_test.go
+++ b/src/pkg/packager/common_test.go
@@ -85,7 +85,6 @@ func TestValidatePackageArchitecture(t *testing.T) {
// Create a Packager instance with package architecture set and a mock Kubernetes client.
p := &Packager{
- arch: testCase.pkgArch,
cluster: &cluster.Cluster{
K8s: &k8s.K8s{
Clientset: mockClient,
@@ -94,6 +93,7 @@ func TestValidatePackageArchitecture(t *testing.T) {
},
cfg: &types.PackagerConfig{
Pkg: types.ZarfPackage{
+ Metadata: types.ZarfMetadata{Architecture: testCase.pkgArch},
Components: []types.ZarfComponent{
{
Images: testCase.images,
diff --git a/src/pkg/packager/components.go b/src/pkg/packager/components.go
index d393eb3e59..84e0b59d04 100644
--- a/src/pkg/packager/components.go
+++ b/src/pkg/packager/components.go
@@ -6,6 +6,7 @@ package packager
import (
"path"
+ "runtime"
"slices"
"strings"
@@ -24,6 +25,37 @@ const (
excluded
)
+// filterComponents removes components not matching the current OS if filterByOS is set.
+func (p *Packager) filterComponents() {
+ // Filter each component to only compatible platforms.
+ filteredComponents := []types.ZarfComponent{}
+ for _, component := range p.cfg.Pkg.Components {
+ // Ignore only filters that are empty
+ var validArch, validOS bool
+
+ // Test for valid architecture
+ if component.Only.Cluster.Architecture == "" || component.Only.Cluster.Architecture == p.cfg.Pkg.Metadata.Architecture {
+ validArch = true
+ } else {
+ message.Debugf("Skipping component %s, %s is not compatible with %s", component.Name, component.Only.Cluster.Architecture, p.cfg.Pkg.Metadata.Architecture)
+ }
+
+ // Test for a valid OS
+ if component.Only.LocalOS == "" || component.Only.LocalOS == runtime.GOOS {
+ validOS = true
+ } else {
+ message.Debugf("Skipping component %s, %s is not compatible with %s", component.Name, component.Only.LocalOS, runtime.GOOS)
+ }
+
+ // If both the OS and architecture are valid, add the component to the filtered list
+ if validArch && validOS {
+ filteredComponents = append(filteredComponents, component)
+ }
+ }
+ // Update the active package with the filtered components.
+ p.cfg.Pkg.Components = filteredComponents
+}
+
func (p *Packager) getSelectedComponents() []types.ZarfComponent {
var selectedComponents []types.ZarfComponent
groupedComponents := map[string][]types.ZarfComponent{}
diff --git a/src/pkg/packager/composer/list.go b/src/pkg/packager/composer/list.go
index a037812101..9ab104b8b1 100644
--- a/src/pkg/packager/composer/list.go
+++ b/src/pkg/packager/composer/list.go
@@ -347,6 +347,9 @@ func (ic *ImportChain) MergeConstants(existing []types.ZarfPackageConstant) (mer
// CompatibleComponent determines if this component is compatible with the given create options
func CompatibleComponent(c types.ZarfComponent, arch, flavor string) bool {
+ if arch == zoci.SkeletonArch {
+ return true
+ }
satisfiesArch := c.Only.Cluster.Architecture == "" || c.Only.Cluster.Architecture == arch
satisfiesFlavor := c.Only.Flavor == "" || c.Only.Flavor == flavor
return satisfiesArch && satisfiesFlavor
diff --git a/src/pkg/packager/create.go b/src/pkg/packager/create.go
index 02f13b240b..9b01c6fefe 100755
--- a/src/pkg/packager/create.go
+++ b/src/pkg/packager/create.go
@@ -10,21 +10,27 @@ import (
"github.com/defenseunicorns/zarf/src/config"
"github.com/defenseunicorns/zarf/src/internal/packager/validate"
+ "github.com/defenseunicorns/zarf/src/pkg/message"
+ "github.com/defenseunicorns/zarf/src/pkg/packager/creator"
)
// Create generates a Zarf package tarball for a given PackageConfig and optional base directory.
func (p *Packager) Create() (err error) {
-
cwd, err := os.Getwd()
if err != nil {
return err
}
- if err := p.cdToBaseDir(p.cfg.CreateOpts.BaseDir, cwd); err != nil {
- return err
+ if err := os.Chdir(p.cfg.CreateOpts.BaseDir); err != nil {
+ return fmt.Errorf("unable to access directory %q: %w", p.cfg.CreateOpts.BaseDir, err)
}
- if err := p.load(); err != nil {
+ message.Note(fmt.Sprintf("Using build directory %s", p.cfg.CreateOpts.BaseDir))
+
+ pc := creator.NewPackageCreator(p.cfg.CreateOpts, p.cfg, cwd)
+
+ p.cfg.Pkg, p.warnings, err = pc.LoadPackageDefinition(p.layout)
+ if err != nil {
return err
}
@@ -37,7 +43,7 @@ func (p *Packager) Create() (err error) {
return fmt.Errorf("package creation canceled")
}
- if err := p.assemble(); err != nil {
+ if err := pc.Assemble(p.layout, p.cfg.Pkg.Components, p.cfg.Pkg.Metadata.Architecture); err != nil {
return err
}
@@ -46,5 +52,5 @@ func (p *Packager) Create() (err error) {
return err
}
- return p.output()
+ return pc.Output(p.layout, &p.cfg.Pkg)
}
diff --git a/src/pkg/packager/create_stages.go b/src/pkg/packager/create_stages.go
deleted file mode 100644
index a4c0aa7e58..0000000000
--- a/src/pkg/packager/create_stages.go
+++ /dev/null
@@ -1,782 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
-
-// Package packager contains functions for interacting with, managing and deploying Zarf packages.
-package packager
-
-import (
- "context"
- "errors"
- "fmt"
- "os"
- "path/filepath"
- "slices"
- "strconv"
- "strings"
- "time"
-
- "github.com/defenseunicorns/zarf/src/config"
- "github.com/defenseunicorns/zarf/src/config/lang"
- "github.com/defenseunicorns/zarf/src/internal/packager/git"
- "github.com/defenseunicorns/zarf/src/internal/packager/helm"
- "github.com/defenseunicorns/zarf/src/internal/packager/images"
- "github.com/defenseunicorns/zarf/src/internal/packager/kustomize"
- "github.com/defenseunicorns/zarf/src/internal/packager/sbom"
- "github.com/defenseunicorns/zarf/src/pkg/layout"
- "github.com/defenseunicorns/zarf/src/pkg/message"
- "github.com/defenseunicorns/zarf/src/pkg/oci"
- "github.com/defenseunicorns/zarf/src/pkg/transform"
- "github.com/defenseunicorns/zarf/src/pkg/utils"
- "github.com/defenseunicorns/zarf/src/pkg/utils/helpers"
- "github.com/defenseunicorns/zarf/src/pkg/zoci"
- "github.com/defenseunicorns/zarf/src/types"
- "github.com/go-git/go-git/v5/plumbing"
- "github.com/mholt/archiver/v3"
-)
-
-func (p *Packager) cdToBaseDir(base string, cwd string) error {
- if err := os.Chdir(base); err != nil {
- return fmt.Errorf("unable to access directory %q: %w", base, err)
- }
- message.Note(fmt.Sprintf("Using build directory %s", base))
-
- // differentials are relative to the current working directory
- if p.cfg.CreateOpts.DifferentialData.DifferentialPackagePath != "" {
- p.cfg.CreateOpts.DifferentialData.DifferentialPackagePath = filepath.Join(cwd, p.cfg.CreateOpts.DifferentialData.DifferentialPackagePath)
- }
- return nil
-}
-
-func (p *Packager) load() error {
- if err := p.readZarfYAML(layout.ZarfYAML); err != nil {
- return fmt.Errorf("unable to read the zarf.yaml file: %s", err.Error())
- }
- if p.isInitConfig() {
- p.cfg.Pkg.Metadata.Version = config.CLIVersion
- }
-
- // Compose components into a single zarf.yaml file
- if err := p.composeComponents(); err != nil {
- return err
- }
-
- if p.cfg.CreateOpts.IsSkeleton {
- return nil
- }
-
- // After components are composed, template the active package.
- if err := p.fillActiveTemplate(); err != nil {
- return fmt.Errorf("unable to fill values in template: %s", err.Error())
- }
-
- // After templates are filled process any create extensions
- if err := p.processExtensions(); err != nil {
- return err
- }
-
- // After we have a full zarf.yaml remove unnecessary repos and images if we are building a differential package
- if p.cfg.CreateOpts.DifferentialData.DifferentialPackagePath != "" {
- // Load the images and repos from the 'reference' package
- if err := p.loadDifferentialData(); err != nil {
- return err
- }
- // Verify the package version of the package we're using as a 'reference' for the differential build is different than the package we're building
- // If the package versions are the same return an error
- if p.cfg.CreateOpts.DifferentialData.DifferentialPackageVersion == p.cfg.Pkg.Metadata.Version {
- return errors.New(lang.PkgCreateErrDifferentialSameVersion)
- }
- if p.cfg.CreateOpts.DifferentialData.DifferentialPackageVersion == "" || p.cfg.Pkg.Metadata.Version == "" {
- return fmt.Errorf("unable to build differential package when either the differential package version or the referenced package version is not set")
- }
-
- // Handle any potential differential images/repos before going forward
- if err := p.removeCopiesFromDifferentialPackage(); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (p *Packager) assemble() error {
- componentSBOMs := map[string]*layout.ComponentSBOM{}
- var imageList []transform.Image
- for idx, component := range p.cfg.Pkg.Components {
- onCreate := component.Actions.OnCreate
- onFailure := func() {
- if err := p.runActions(onCreate.Defaults, onCreate.OnFailure, nil); err != nil {
- message.Debugf("unable to run component failure action: %s", err.Error())
- }
- }
- if err := p.addComponent(idx, component); err != nil {
- onFailure()
- return fmt.Errorf("unable to add component %q: %w", component.Name, err)
- }
-
- if err := p.runActions(onCreate.Defaults, onCreate.OnSuccess, nil); err != nil {
- onFailure()
- return fmt.Errorf("unable to run component success action: %w", err)
- }
-
- if !p.cfg.CreateOpts.SkipSBOM {
- componentSBOM, err := p.getFilesToSBOM(component)
- if err != nil {
- return fmt.Errorf("unable to create component SBOM: %w", err)
- }
- if componentSBOM != nil && len(componentSBOM.Files) > 0 {
- componentSBOMs[component.Name] = componentSBOM
- }
- }
-
- // Combine all component images into a single entry for efficient layer reuse.
- for _, src := range component.Images {
- refInfo, err := transform.ParseImageRef(src)
- if err != nil {
- return fmt.Errorf("failed to create ref for image %s: %w", src, err)
- }
- imageList = append(imageList, refInfo)
- }
- }
-
- imageList = helpers.Unique(imageList)
- var sbomImageList []transform.Image
-
- // Images are handled separately from other component assets.
- if len(imageList) > 0 {
- message.HeaderInfof("📦 PACKAGE IMAGES")
-
- p.layout = p.layout.AddImages()
-
- var pulled []images.ImgInfo
- var err error
-
- doPull := func() error {
- imgConfig := images.ImageConfig{
- ImagesPath: p.layout.Images.Base,
- ImageList: imageList,
- Insecure: config.CommonOptions.Insecure,
- Architectures: []string{p.cfg.Pkg.Metadata.Architecture, p.cfg.Pkg.Build.Architecture},
- RegistryOverrides: p.cfg.CreateOpts.RegistryOverrides,
- }
-
- pulled, err = imgConfig.PullAll()
- return err
- }
-
- if err := helpers.Retry(doPull, p.cfg.PkgOpts.Retries, 5*time.Second, message.Warnf); err != nil {
- return fmt.Errorf("unable to pull images after %d attempts: %w", p.cfg.PkgOpts.Retries, err)
- }
-
- for _, imgInfo := range pulled {
- if err := p.layout.Images.AddV1Image(imgInfo.Img); err != nil {
- return err
- }
- if imgInfo.HasImageLayers {
- sbomImageList = append(sbomImageList, imgInfo.RefInfo)
- }
- }
- }
-
- // Ignore SBOM creation if the flag is set.
- if p.cfg.CreateOpts.SkipSBOM {
- message.Debug("Skipping image SBOM processing per --skip-sbom flag")
- } else {
- p.layout = p.layout.AddSBOMs()
- if err := sbom.Catalog(componentSBOMs, sbomImageList, p.layout); err != nil {
- return fmt.Errorf("unable to create an SBOM catalog for the package: %w", err)
- }
- }
-
- return nil
-}
-
-func (p *Packager) assembleSkeleton() error {
- if err := p.skeletonizeExtensions(); err != nil {
- return err
- }
- for _, warning := range p.warnings {
- message.Warn(warning)
- }
- for idx, component := range p.cfg.Pkg.Components {
- if err := p.addComponent(idx, component); err != nil {
- return err
- }
-
- if err := p.layout.Components.Archive(component, false); err != nil {
- return err
- }
- }
- checksumChecksum, err := p.generatePackageChecksums()
- if err != nil {
- return fmt.Errorf("unable to generate checksums for skeleton package: %w", err)
- }
- p.cfg.Pkg.Metadata.AggregateChecksum = checksumChecksum
-
- return p.writeYaml()
-}
-
-// output assumes it is running from cwd, not the build directory
-func (p *Packager) output() error {
- // Process the component directories into compressed tarballs
- // NOTE: This is purposefully being done after the SBOM cataloging
- for _, component := range p.cfg.Pkg.Components {
- // Make the component a tar archive
- if err := p.layout.Components.Archive(component, true); err != nil {
- return fmt.Errorf("unable to archive component: %s", err.Error())
- }
- }
-
- // Calculate all the checksums
- checksumChecksum, err := p.generatePackageChecksums()
- if err != nil {
- return fmt.Errorf("unable to generate checksums for the package: %w", err)
- }
- p.cfg.Pkg.Metadata.AggregateChecksum = checksumChecksum
-
- // Save the transformed config.
- if err := p.writeYaml(); err != nil {
- return fmt.Errorf("unable to write zarf.yaml: %w", err)
- }
-
- // Sign the config file if a key was provided
- if p.cfg.CreateOpts.SigningKeyPath != "" {
- if err := p.signPackage(p.cfg.CreateOpts.SigningKeyPath, p.cfg.CreateOpts.SigningKeyPassword); err != nil {
- return err
- }
- }
-
- // Create a remote ref + client for the package (if output is OCI)
- // then publish the package to the remote.
- if helpers.IsOCIURL(p.cfg.CreateOpts.Output) {
- ref, err := zoci.ReferenceFromMetadata(p.cfg.CreateOpts.Output, &p.cfg.Pkg.Metadata, &p.cfg.Pkg.Build)
- if err != nil {
- return err
- }
- remote, err := zoci.NewRemote(ref, oci.PlatformForArch(config.GetArch()))
- if err != nil {
- return err
- }
-
- ctx := context.TODO()
- err = remote.PublishPackage(ctx, &p.cfg.Pkg, p.layout, config.CommonOptions.OCIConcurrency)
- if err != nil {
- return fmt.Errorf("unable to publish package: %w", err)
- }
- message.HorizontalRule()
- flags := ""
- if config.CommonOptions.Insecure {
- flags = "--insecure"
- }
- message.Title("To inspect/deploy/pull:", "")
- message.ZarfCommand("package inspect %s %s", helpers.OCIURLPrefix+remote.Repo().Reference.String(), flags)
- message.ZarfCommand("package deploy %s %s", helpers.OCIURLPrefix+remote.Repo().Reference.String(), flags)
- message.ZarfCommand("package pull %s %s", helpers.OCIURLPrefix+remote.Repo().Reference.String(), flags)
- } else {
- // Use the output path if the user specified it.
- packageName := filepath.Join(p.cfg.CreateOpts.Output, p.GetPackageName())
-
- // Try to remove the package if it already exists.
- _ = os.Remove(packageName)
-
- // Create the package tarball.
- if err := p.archivePackage(packageName); err != nil {
- return fmt.Errorf("unable to archive package: %w", err)
- }
- }
-
- // Output the SBOM files into a directory if specified.
- if p.cfg.CreateOpts.ViewSBOM || p.cfg.CreateOpts.SBOMOutputDir != "" {
- outputSBOM := p.cfg.CreateOpts.SBOMOutputDir
- var sbomDir string
- if err := p.layout.SBOMs.Unarchive(); err != nil {
- return fmt.Errorf("unable to unarchive SBOMs: %w", err)
- }
- sbomDir = p.layout.SBOMs.Path
-
- if outputSBOM != "" {
- out, err := sbom.OutputSBOMFiles(sbomDir, outputSBOM, p.cfg.Pkg.Metadata.Name)
- if err != nil {
- return err
- }
- sbomDir = out
- }
-
- if p.cfg.CreateOpts.ViewSBOM {
- sbom.ViewSBOMFiles(sbomDir)
- }
- }
- return nil
-}
-
-func (p *Packager) getFilesToSBOM(component types.ZarfComponent) (*layout.ComponentSBOM, error) {
- componentPaths, err := p.layout.Components.Create(component)
- if err != nil {
- return nil, err
- }
- // Create an struct to hold the SBOM information for this component.
- componentSBOM := &layout.ComponentSBOM{
- Files: []string{},
- Component: componentPaths,
- }
-
- appendSBOMFiles := func(path string) error {
- if utils.IsDir(path) {
- files, err := utils.RecursiveFileList(path, nil, false)
- if err != nil {
- return err
- }
- componentSBOM.Files = append(componentSBOM.Files, files...)
- } else {
- info, err := os.Lstat(path)
- if err != nil {
- return err
- }
- if info.Mode().IsRegular() {
- componentSBOM.Files = append(componentSBOM.Files, path)
- }
- }
-
- return nil
- }
-
- for filesIdx, file := range component.Files {
- path := filepath.Join(componentPaths.Files, strconv.Itoa(filesIdx), filepath.Base(file.Target))
- err := appendSBOMFiles(path)
- if err != nil {
- return nil, err
- }
- }
-
- for dataIdx, data := range component.DataInjections {
- path := filepath.Join(componentPaths.DataInjections, strconv.Itoa(dataIdx), filepath.Base(data.Target.Path))
-
- err := appendSBOMFiles(path)
- if err != nil {
- return nil, err
- }
- }
-
- return componentSBOM, nil
-}
-
-func (p *Packager) addComponent(index int, component types.ZarfComponent) error {
- message.HeaderInfof("📦 %s COMPONENT", strings.ToUpper(component.Name))
-
- isSkeleton := p.cfg.CreateOpts.IsSkeleton
-
- componentPaths, err := p.layout.Components.Create(component)
- if err != nil {
- return err
- }
-
- if isSkeleton && component.DeprecatedCosignKeyPath != "" {
- dst := filepath.Join(componentPaths.Base, "cosign.pub")
- err := utils.CreatePathAndCopy(component.DeprecatedCosignKeyPath, dst)
- if err != nil {
- return err
- }
- p.cfg.Pkg.Components[index].DeprecatedCosignKeyPath = "cosign.pub"
- }
-
- // TODO: (@WSTARR) Shim the skeleton component's create action dirs to be empty. This prevents actions from failing by cd'ing into directories that will be flattened.
- if isSkeleton {
- component.Actions.OnCreate.Defaults.Dir = ""
- resetActions := func(actions []types.ZarfComponentAction) []types.ZarfComponentAction {
- for idx := range actions {
- actions[idx].Dir = nil
- }
- return actions
- }
- component.Actions.OnCreate.Before = resetActions(component.Actions.OnCreate.Before)
- component.Actions.OnCreate.After = resetActions(component.Actions.OnCreate.After)
- component.Actions.OnCreate.OnSuccess = resetActions(component.Actions.OnCreate.OnSuccess)
- component.Actions.OnCreate.OnFailure = resetActions(component.Actions.OnCreate.OnFailure)
- }
-
- onCreate := component.Actions.OnCreate
- if !isSkeleton {
- if err := p.runActions(onCreate.Defaults, onCreate.Before, nil); err != nil {
- return fmt.Errorf("unable to run component before action: %w", err)
- }
- }
-
- // If any helm charts are defined, process them.
- for chartIdx, chart := range component.Charts {
-
- helmCfg := helm.New(chart, componentPaths.Charts, componentPaths.Values)
-
- if isSkeleton {
- if chart.LocalPath != "" {
- rel := filepath.Join(layout.ChartsDir, fmt.Sprintf("%s-%d", chart.Name, chartIdx))
- dst := filepath.Join(componentPaths.Base, rel)
-
- err := utils.CreatePathAndCopy(chart.LocalPath, dst)
- if err != nil {
- return err
- }
-
- p.cfg.Pkg.Components[index].Charts[chartIdx].LocalPath = rel
- }
-
- for valuesIdx, path := range chart.ValuesFiles {
- if helpers.IsURL(path) {
- continue
- }
-
- rel := helm.StandardValuesName(layout.ValuesDir, chart, valuesIdx)
- p.cfg.Pkg.Components[index].Charts[chartIdx].ValuesFiles[valuesIdx] = rel
-
- if err := utils.CreatePathAndCopy(path, filepath.Join(componentPaths.Base, rel)); err != nil {
- return fmt.Errorf("unable to copy chart values file %s: %w", path, err)
- }
- }
- } else {
- err := helmCfg.PackageChart(componentPaths.Charts)
- if err != nil {
- return err
- }
- }
- }
-
- for filesIdx, file := range component.Files {
- message.Debugf("Loading %#v", file)
-
- rel := filepath.Join(layout.FilesDir, strconv.Itoa(filesIdx), filepath.Base(file.Target))
- dst := filepath.Join(componentPaths.Base, rel)
- destinationDir := filepath.Dir(dst)
-
- if helpers.IsURL(file.Source) {
- if isSkeleton {
- continue
- }
-
- if file.ExtractPath != "" {
-
- // get the compressedFileName from the source
- compressedFileName, err := helpers.ExtractBasePathFromURL(file.Source)
- if err != nil {
- return fmt.Errorf(lang.ErrFileNameExtract, file.Source, err.Error())
- }
-
- compressedFile := filepath.Join(componentPaths.Temp, compressedFileName)
-
- // If the file is an archive, download it to the componentPath.Temp
- if err := utils.DownloadToFile(file.Source, compressedFile, component.DeprecatedCosignKeyPath); err != nil {
- return fmt.Errorf(lang.ErrDownloading, file.Source, err.Error())
- }
-
- err = archiver.Extract(compressedFile, file.ExtractPath, destinationDir)
- if err != nil {
- return fmt.Errorf(lang.ErrFileExtract, file.ExtractPath, compressedFileName, err.Error())
- }
-
- } else {
- if err := utils.DownloadToFile(file.Source, dst, component.DeprecatedCosignKeyPath); err != nil {
- return fmt.Errorf(lang.ErrDownloading, file.Source, err.Error())
- }
- }
-
- } else {
- if file.ExtractPath != "" {
- if err := archiver.Extract(file.Source, file.ExtractPath, destinationDir); err != nil {
- return fmt.Errorf(lang.ErrFileExtract, file.ExtractPath, file.Source, err.Error())
- }
- } else {
- if err := utils.CreatePathAndCopy(file.Source, dst); err != nil {
- return fmt.Errorf("unable to copy file %s: %w", file.Source, err)
- }
- }
-
- }
-
- if file.ExtractPath != "" {
- // Make sure dst reflects the actual file or directory.
- updatedExtractedFileOrDir := filepath.Join(destinationDir, file.ExtractPath)
- if updatedExtractedFileOrDir != dst {
- if err := os.Rename(updatedExtractedFileOrDir, dst); err != nil {
- return fmt.Errorf(lang.ErrWritingFile, dst, err)
- }
- }
- }
-
- if isSkeleton {
- // Change the source to the new relative source directory (any remote files will have been skipped above)
- p.cfg.Pkg.Components[index].Files[filesIdx].Source = rel
- // Remove the extractPath from a skeleton since it will already extract it
- p.cfg.Pkg.Components[index].Files[filesIdx].ExtractPath = ""
- }
-
- // Abort packaging on invalid shasum (if one is specified).
- if file.Shasum != "" {
- if err := utils.SHAsMatch(dst, file.Shasum); err != nil {
- return err
- }
- }
-
- if file.Executable || utils.IsDir(dst) {
- _ = os.Chmod(dst, helpers.ReadWriteExecuteUser)
- } else {
- _ = os.Chmod(dst, helpers.ReadWriteUser)
- }
- }
-
- if len(component.DataInjections) > 0 {
- spinner := message.NewProgressSpinner("Loading data injections")
- defer spinner.Stop()
-
- for dataIdx, data := range component.DataInjections {
- spinner.Updatef("Copying data injection %s for %s", data.Target.Path, data.Target.Selector)
-
- rel := filepath.Join(layout.DataInjectionsDir, strconv.Itoa(dataIdx), filepath.Base(data.Target.Path))
- dst := filepath.Join(componentPaths.Base, rel)
-
- if helpers.IsURL(data.Source) {
- if isSkeleton {
- continue
- }
- if err := utils.DownloadToFile(data.Source, dst, component.DeprecatedCosignKeyPath); err != nil {
- return fmt.Errorf(lang.ErrDownloading, data.Source, err.Error())
- }
- } else {
- if err := utils.CreatePathAndCopy(data.Source, dst); err != nil {
- return fmt.Errorf("unable to copy data injection %s: %s", data.Source, err.Error())
- }
- if isSkeleton {
- p.cfg.Pkg.Components[index].DataInjections[dataIdx].Source = rel
- }
- }
- }
- spinner.Success()
- }
-
- if len(component.Manifests) > 0 {
- // Get the proper count of total manifests to add.
- manifestCount := 0
-
- for _, manifest := range component.Manifests {
- manifestCount += len(manifest.Files)
- manifestCount += len(manifest.Kustomizations)
- }
-
- spinner := message.NewProgressSpinner("Loading %d K8s manifests", manifestCount)
- defer spinner.Stop()
-
- // Iterate over all manifests.
- for manifestIdx, manifest := range component.Manifests {
- for fileIdx, path := range manifest.Files {
- rel := filepath.Join(layout.ManifestsDir, fmt.Sprintf("%s-%d.yaml", manifest.Name, fileIdx))
- dst := filepath.Join(componentPaths.Base, rel)
-
- // Copy manifests without any processing.
- spinner.Updatef("Copying manifest %s", path)
- if helpers.IsURL(path) {
- if isSkeleton {
- continue
- }
- if err := utils.DownloadToFile(path, dst, component.DeprecatedCosignKeyPath); err != nil {
- return fmt.Errorf(lang.ErrDownloading, path, err.Error())
- }
- } else {
- if err := utils.CreatePathAndCopy(path, dst); err != nil {
- return fmt.Errorf("unable to copy manifest %s: %w", path, err)
- }
- if isSkeleton {
- p.cfg.Pkg.Components[index].Manifests[manifestIdx].Files[fileIdx] = rel
- }
- }
- }
-
- for kustomizeIdx, path := range manifest.Kustomizations {
- // Generate manifests from kustomizations and place in the package.
- spinner.Updatef("Building kustomization for %s", path)
-
- kname := fmt.Sprintf("kustomization-%s-%d.yaml", manifest.Name, kustomizeIdx)
- rel := filepath.Join(layout.ManifestsDir, kname)
- dst := filepath.Join(componentPaths.Base, rel)
-
- if err := kustomize.Build(path, dst, manifest.KustomizeAllowAnyDirectory); err != nil {
- return fmt.Errorf("unable to build kustomization %s: %w", path, err)
- }
- if isSkeleton {
- p.cfg.Pkg.Components[index].Manifests[manifestIdx].Files = append(p.cfg.Pkg.Components[index].Manifests[manifestIdx].Files, rel)
- }
- }
- if isSkeleton {
- // remove kustomizations
- p.cfg.Pkg.Components[index].Manifests[manifestIdx].Kustomizations = nil
- }
- }
- spinner.Success()
- }
-
- // Load all specified git repos.
- if len(component.Repos) > 0 && !isSkeleton {
- spinner := message.NewProgressSpinner("Loading %d git repos", len(component.Repos))
- defer spinner.Stop()
-
- for _, url := range component.Repos {
- // Pull all the references if there is no `@` in the string.
- gitCfg := git.NewWithSpinner(types.GitServerInfo{}, spinner)
- if err := gitCfg.Pull(url, componentPaths.Repos, false); err != nil {
- return fmt.Errorf("unable to pull git repo %s: %w", url, err)
- }
- }
- spinner.Success()
- }
-
- if !isSkeleton {
- if err := p.runActions(onCreate.Defaults, onCreate.After, nil); err != nil {
- return fmt.Errorf("unable to run component after action: %w", err)
- }
- }
-
- return nil
-}
-
-// generateChecksum walks through all of the files starting at the base path and generates a checksum file.
-// Each file within the basePath represents a layer within the Zarf package.
-// generateChecksum returns a SHA256 checksum of the checksums.txt file.
-func (p *Packager) generatePackageChecksums() (string, error) {
- // Loop over the "loaded" files
- var checksumsData = []string{}
- for rel, abs := range p.layout.Files() {
- if rel == layout.ZarfYAML || rel == layout.Checksums {
- continue
- }
-
- sum, err := utils.GetSHA256OfFile(abs)
- if err != nil {
- return "", err
- }
- checksumsData = append(checksumsData, fmt.Sprintf("%s %s", sum, rel))
- }
- slices.Sort(checksumsData)
-
- // Create the checksums file
- checksumsFilePath := p.layout.Checksums
- if err := os.WriteFile(checksumsFilePath, []byte(strings.Join(checksumsData, "\n")+"\n"), helpers.ReadWriteUser); err != nil {
- return "", err
- }
-
- // Calculate the checksum of the checksum file
- return utils.GetSHA256OfFile(checksumsFilePath)
-}
-
-// loadDifferentialData extracts the zarf config of a designated 'reference' package that we are building a differential over and creates a list of all images and repos that are in the reference package
-func (p *Packager) loadDifferentialData() error {
- // Save the fact that this is a differential build into the build data of the package
- p.cfg.Pkg.Build.Differential = true
-
- tmpDir, _ := utils.MakeTempDir(config.CommonOptions.TempDirectory)
- defer os.RemoveAll(tmpDir)
-
- // Load the package spec of the package we're using as a 'reference' for the differential build
- if helpers.IsOCIURL(p.cfg.CreateOpts.DifferentialData.DifferentialPackagePath) {
- remote, err := zoci.NewRemote(p.cfg.CreateOpts.DifferentialData.DifferentialPackagePath, oci.PlatformForArch(config.GetArch()))
- if err != nil {
- return err
- }
- pkg, err := remote.FetchZarfYAML(context.TODO())
- if err != nil {
- return err
- }
- err = utils.WriteYaml(filepath.Join(tmpDir, layout.ZarfYAML), pkg, helpers.ReadWriteUser)
- if err != nil {
- return err
- }
- } else {
- if err := archiver.Extract(p.cfg.CreateOpts.DifferentialData.DifferentialPackagePath, layout.ZarfYAML, tmpDir); err != nil {
- return fmt.Errorf("unable to extract the differential zarf package spec: %s", err.Error())
- }
- }
-
- var differentialZarfConfig types.ZarfPackage
- if err := utils.ReadYaml(filepath.Join(tmpDir, layout.ZarfYAML), &differentialZarfConfig); err != nil {
- return fmt.Errorf("unable to load the differential zarf package spec: %s", err.Error())
- }
-
- // Generate a map of all the images and repos that are included in the provided package
- allIncludedImagesMap := map[string]bool{}
- allIncludedReposMap := map[string]bool{}
- for _, component := range differentialZarfConfig.Components {
- for _, image := range component.Images {
- allIncludedImagesMap[image] = true
- }
- for _, repo := range component.Repos {
- allIncludedReposMap[repo] = true
- }
- }
-
- p.cfg.CreateOpts.DifferentialData.DifferentialImages = allIncludedImagesMap
- p.cfg.CreateOpts.DifferentialData.DifferentialRepos = allIncludedReposMap
- p.cfg.CreateOpts.DifferentialData.DifferentialPackageVersion = differentialZarfConfig.Metadata.Version
-
- return nil
-}
-
-// removeCopiesFromDifferentialPackage will remove any images and repos that are already included in the reference package from the new package
-func (p *Packager) removeCopiesFromDifferentialPackage() error {
- // If a differential build was not requested, continue on as normal
- if p.cfg.CreateOpts.DifferentialData.DifferentialPackagePath == "" {
- return nil
- }
-
- // Loop through all of the components to determine if any of them are using already included images or repos
- componentMap := make(map[int]types.ZarfComponent)
- for idx, component := range p.cfg.Pkg.Components {
- newImageList := []string{}
- newRepoList := []string{}
- // Generate a list of all unique images for this component
- for _, img := range component.Images {
- // If a image doesn't have a ref (or is a commonly reused ref), we will include this image in the differential package
- imgRef, err := transform.ParseImageRef(img)
- if err != nil {
- return fmt.Errorf("unable to parse image ref %s: %s", img, err.Error())
- }
-
- // Only include new images or images that have a commonly overwritten tag
- imgTag := imgRef.TagOrDigest
- useImgAnyways := imgTag == ":latest" || imgTag == ":stable" || imgTag == ":nightly"
- if useImgAnyways || !p.cfg.CreateOpts.DifferentialData.DifferentialImages[img] {
- newImageList = append(newImageList, img)
- } else {
- message.Debugf("Image %s is already included in the differential package", img)
- }
- }
-
- // Generate a list of all unique repos for this component
- for _, repoURL := range component.Repos {
- // Split the remote url and the zarf reference
- _, refPlain, err := transform.GitURLSplitRef(repoURL)
- if err != nil {
- return err
- }
-
- var ref plumbing.ReferenceName
- // Parse the ref from the git URL.
- if refPlain != "" {
- ref = git.ParseRef(refPlain)
- }
-
- // Only include new repos or repos that were not referenced by a specific commit sha or tag
- useRepoAnyways := ref == "" || (!ref.IsTag() && !plumbing.IsHash(refPlain))
- if useRepoAnyways || !p.cfg.CreateOpts.DifferentialData.DifferentialRepos[repoURL] {
- newRepoList = append(newRepoList, repoURL)
- } else {
- message.Debugf("Repo %s is already included in the differential package", repoURL)
- }
- }
-
- // Update the component with the unique lists of repos and images
- component.Images = newImageList
- component.Repos = newRepoList
- componentMap[idx] = component
- }
-
- // Update the package with the new component list
- for idx, component := range componentMap {
- p.cfg.Pkg.Components[idx] = component
- }
-
- return nil
-}
diff --git a/src/pkg/packager/compose.go b/src/pkg/packager/creator/compose.go
similarity index 50%
rename from src/pkg/packager/compose.go
rename to src/pkg/packager/creator/compose.go
index 6bbbf22fff..dd2e429731 100644
--- a/src/pkg/packager/compose.go
+++ b/src/pkg/packager/creator/compose.go
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
-// Package packager contains functions for interacting with, managing and deploying Zarf packages.
-package packager
+// Package creator contains functions for creating Zarf packages.
+package creator
import (
"github.com/defenseunicorns/zarf/src/pkg/message"
@@ -10,17 +10,19 @@ import (
"github.com/defenseunicorns/zarf/src/types"
)
-// composeComponents builds the composed components list for the current config.
-func (p *Packager) composeComponents() error {
+// ComposeComponents composes components and their dependencies into a single Zarf package using an import chain.
+func ComposeComponents(pkg types.ZarfPackage, flavor string) (types.ZarfPackage, []string, error) {
components := []types.ZarfComponent{}
+ warnings := []string{}
- pkgVars := p.cfg.Pkg.Variables
- pkgConsts := p.cfg.Pkg.Constants
+ pkgVars := pkg.Variables
+ pkgConsts := pkg.Constants
- for i, component := range p.cfg.Pkg.Components {
- arch := p.arch
- // filter by architecture
- if !composer.CompatibleComponent(component, arch, p.cfg.CreateOpts.Flavor) {
+ arch := pkg.Metadata.Architecture
+
+ for i, component := range pkg.Components {
+ // filter by architecture and flavor
+ if !composer.CompatibleComponent(component, arch, flavor) {
continue
}
@@ -29,20 +31,20 @@ func (p *Packager) composeComponents() error {
component.Only.Flavor = ""
// build the import chain
- chain, err := composer.NewImportChain(component, i, p.cfg.Pkg.Metadata.Name, arch, p.cfg.CreateOpts.Flavor)
+ chain, err := composer.NewImportChain(component, i, pkg.Metadata.Name, arch, flavor)
if err != nil {
- return err
+ return types.ZarfPackage{}, nil, err
}
message.Debugf("%s", chain)
// migrate any deprecated component configurations now
- warnings := chain.Migrate(p.cfg.Pkg.Build)
- p.warnings = append(p.warnings, warnings...)
+ warning := chain.Migrate(pkg.Build)
+ warnings = append(warnings, warning...)
// get the composed component
composed, err := chain.Compose()
if err != nil {
- return err
+ return types.ZarfPackage{}, nil, err
}
components = append(components, *composed)
@@ -52,10 +54,10 @@ func (p *Packager) composeComponents() error {
}
// set the filtered + composed components
- p.cfg.Pkg.Components = components
+ pkg.Components = components
- p.cfg.Pkg.Variables = pkgVars
- p.cfg.Pkg.Constants = pkgConsts
+ pkg.Variables = pkgVars
+ pkg.Constants = pkgConsts
- return nil
+ return pkg, warnings, nil
}
diff --git a/src/pkg/packager/creator/creator.go b/src/pkg/packager/creator/creator.go
new file mode 100644
index 0000000000..5b0f3c27b1
--- /dev/null
+++ b/src/pkg/packager/creator/creator.go
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
+
+// Package creator contains functions for creating Zarf packages.
+package creator
+
+import (
+ "github.com/defenseunicorns/zarf/src/pkg/layout"
+ "github.com/defenseunicorns/zarf/src/types"
+)
+
+// Creator is an interface for creating Zarf packages.
+type Creator interface {
+ LoadPackageDefinition(dst *layout.PackagePaths) (pkg types.ZarfPackage, warnings []string, err error)
+ Assemble(dst *layout.PackagePaths, components []types.ZarfComponent, arch string) error
+ Output(dst *layout.PackagePaths, pkg *types.ZarfPackage) error
+}
diff --git a/src/pkg/packager/creator/differential.go b/src/pkg/packager/creator/differential.go
new file mode 100644
index 0000000000..4533531e03
--- /dev/null
+++ b/src/pkg/packager/creator/differential.go
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
+
+// Package creator contains functions for creating Zarf packages.
+package creator
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/defenseunicorns/zarf/src/config"
+ "github.com/defenseunicorns/zarf/src/internal/packager/git"
+ "github.com/defenseunicorns/zarf/src/pkg/layout"
+ "github.com/defenseunicorns/zarf/src/pkg/packager/sources"
+ "github.com/defenseunicorns/zarf/src/pkg/transform"
+ "github.com/defenseunicorns/zarf/src/pkg/utils"
+ "github.com/defenseunicorns/zarf/src/types"
+ "github.com/go-git/go-git/v5/plumbing"
+)
+
+// loadDifferentialData sets any images and repos from the existing reference package in the DifferentialData and returns it.
+func loadDifferentialData(diffPkgPath string) (diffData *types.DifferentialData, err error) {
+ tmpdir, err := utils.MakeTempDir(config.CommonOptions.TempDirectory)
+ if err != nil {
+ return nil, err
+ }
+
+ diffLayout := layout.New(tmpdir)
+ defer os.RemoveAll(diffLayout.Base)
+
+ src, err := sources.New(&types.ZarfPackageOptions{
+ PackageSource: diffPkgPath,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ if err := src.LoadPackageMetadata(diffLayout, false, false); err != nil {
+ return nil, err
+ }
+
+ var diffPkg types.ZarfPackage
+ if err := utils.ReadYaml(diffLayout.ZarfYAML, &diffPkg); err != nil {
+ return nil, fmt.Errorf("error reading the differential Zarf package: %w", err)
+ }
+
+ allIncludedImagesMap := map[string]bool{}
+ allIncludedReposMap := map[string]bool{}
+
+ for _, component := range diffPkg.Components {
+ for _, image := range component.Images {
+ allIncludedImagesMap[image] = true
+ }
+ for _, repo := range component.Repos {
+ allIncludedReposMap[repo] = true
+ }
+ }
+
+ return &types.DifferentialData{
+ DifferentialImages: allIncludedImagesMap,
+ DifferentialRepos: allIncludedReposMap,
+ DifferentialPackageVersion: diffPkg.Metadata.Version,
+ }, nil
+}
+
+// removeCopiesFromComponents removes any images and repos already present in the reference package components.
+func removeCopiesFromComponents(components []types.ZarfComponent, loadedDiffData *types.DifferentialData) (diffComponents []types.ZarfComponent, err error) {
+ for _, component := range components {
+ newImageList := []string{}
+ newRepoList := []string{}
+
+ for _, img := range component.Images {
+ imgRef, err := transform.ParseImageRef(img)
+ if err != nil {
+ return nil, fmt.Errorf("unable to parse image ref %s: %s", img, err.Error())
+ }
+
+ imgTag := imgRef.TagOrDigest
+ includeImage := imgTag == ":latest" || imgTag == ":stable" || imgTag == ":nightly"
+ if includeImage || !loadedDiffData.DifferentialImages[img] {
+ newImageList = append(newImageList, img)
+ }
+ }
+
+ for _, repoURL := range component.Repos {
+ _, refPlain, err := transform.GitURLSplitRef(repoURL)
+ if err != nil {
+ return nil, err
+ }
+
+ var ref plumbing.ReferenceName
+ if refPlain != "" {
+ ref = git.ParseRef(refPlain)
+ }
+
+ includeRepo := ref == "" || (!ref.IsTag() && !plumbing.IsHash(refPlain))
+ if includeRepo || !loadedDiffData.DifferentialRepos[repoURL] {
+ newRepoList = append(newRepoList, repoURL)
+ }
+ }
+
+ component.Images = newImageList
+ component.Repos = newRepoList
+ diffComponents = append(diffComponents, component)
+ }
+
+ return diffComponents, nil
+}
diff --git a/src/pkg/packager/creator/normal.go b/src/pkg/packager/creator/normal.go
new file mode 100644
index 0000000000..36a86940ae
--- /dev/null
+++ b/src/pkg/packager/creator/normal.go
@@ -0,0 +1,560 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
+
+// Package creator contains functions for creating Zarf packages.
+package creator
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/defenseunicorns/zarf/src/config"
+ "github.com/defenseunicorns/zarf/src/config/lang"
+ "github.com/defenseunicorns/zarf/src/extensions/bigbang"
+ "github.com/defenseunicorns/zarf/src/internal/packager/git"
+ "github.com/defenseunicorns/zarf/src/internal/packager/helm"
+ "github.com/defenseunicorns/zarf/src/internal/packager/images"
+ "github.com/defenseunicorns/zarf/src/internal/packager/kustomize"
+ "github.com/defenseunicorns/zarf/src/internal/packager/sbom"
+ "github.com/defenseunicorns/zarf/src/pkg/layout"
+ "github.com/defenseunicorns/zarf/src/pkg/message"
+ "github.com/defenseunicorns/zarf/src/pkg/oci"
+ "github.com/defenseunicorns/zarf/src/pkg/packager/actions"
+ "github.com/defenseunicorns/zarf/src/pkg/packager/sources"
+ "github.com/defenseunicorns/zarf/src/pkg/transform"
+ "github.com/defenseunicorns/zarf/src/pkg/utils"
+ "github.com/defenseunicorns/zarf/src/pkg/utils/helpers"
+ "github.com/defenseunicorns/zarf/src/pkg/zoci"
+ "github.com/defenseunicorns/zarf/src/types"
+ "github.com/mholt/archiver/v3"
+)
+
+var (
+ // verify that PackageCreator implements Creator
+ _ Creator = (*PackageCreator)(nil)
+)
+
+// PackageCreator provides methods for creating normal (not skeleton) Zarf packages.
+type PackageCreator struct {
+ createOpts types.ZarfCreateOptions
+
+ // TODO: (@lucasrod16) remove PackagerConfig once actions do not depend on it: https://github.com/defenseunicorns/zarf/pull/2276
+ cfg *types.PackagerConfig
+}
+
+// NewPackageCreator returns a new PackageCreator.
+func NewPackageCreator(createOpts types.ZarfCreateOptions, cfg *types.PackagerConfig, cwd string) *PackageCreator {
+ // differentials are relative to the current working directory
+ if createOpts.DifferentialPackagePath != "" {
+ createOpts.DifferentialPackagePath = filepath.Join(cwd, createOpts.DifferentialPackagePath)
+ }
+
+ return &PackageCreator{createOpts, cfg}
+}
+
+// LoadPackageDefinition loads and configures a zarf.yaml file during package create.
+func (pc *PackageCreator) LoadPackageDefinition(dst *layout.PackagePaths) (pkg types.ZarfPackage, warnings []string, err error) {
+ pkg, warnings, err = dst.ReadZarfYAML(layout.ZarfYAML)
+ if err != nil {
+ return types.ZarfPackage{}, nil, err
+ }
+
+ pkg.Metadata.Architecture = config.GetArch(pkg.Metadata.Architecture)
+
+ // Compose components into a single zarf.yaml file
+ pkg, composeWarnings, err := ComposeComponents(pkg, pc.createOpts.Flavor)
+ if err != nil {
+ return types.ZarfPackage{}, nil, err
+ }
+
+ warnings = append(warnings, composeWarnings...)
+
+ // After components are composed, template the active package.
+ pkg, templateWarnings, err := FillActiveTemplate(pkg, pc.createOpts.SetVariables)
+ if err != nil {
+ return types.ZarfPackage{}, nil, fmt.Errorf("unable to fill values in template: %w", err)
+ }
+
+ warnings = append(warnings, templateWarnings...)
+
+ // After templates are filled process any create extensions
+ pkg.Components, err = pc.processExtensions(pkg.Components, dst, pkg.Metadata.YOLO)
+ if err != nil {
+ return types.ZarfPackage{}, nil, err
+ }
+
+ // If we are creating a differential package, remove duplicate images and repos.
+ if pc.createOpts.DifferentialPackagePath != "" {
+ pkg.Build.Differential = true
+
+ diffData, err := loadDifferentialData(pc.createOpts.DifferentialPackagePath)
+ if err != nil {
+ return types.ZarfPackage{}, nil, err
+ }
+
+ pkg.Build.DifferentialPackageVersion = diffData.DifferentialPackageVersion
+
+ versionsMatch := diffData.DifferentialPackageVersion == pkg.Metadata.Version
+ if versionsMatch {
+ return types.ZarfPackage{}, nil, errors.New(lang.PkgCreateErrDifferentialSameVersion)
+ }
+
+ noVersionSet := diffData.DifferentialPackageVersion == "" || pkg.Metadata.Version == ""
+ if noVersionSet {
+ return types.ZarfPackage{}, nil, errors.New(lang.PkgCreateErrDifferentialNoVersion)
+ }
+
+ pkg.Components, err = removeCopiesFromComponents(pkg.Components, diffData)
+ if err != nil {
+ return types.ZarfPackage{}, nil, err
+ }
+ }
+
+ return pkg, warnings, nil
+}
+
+// Assemble assembles all of the package assets into Zarf's tmp directory layout.
+func (pc *PackageCreator) Assemble(dst *layout.PackagePaths, components []types.ZarfComponent, arch string) error {
+ var imageList []transform.Image
+
+ skipSBOMFlagUsed := pc.createOpts.SkipSBOM
+ componentSBOMs := map[string]*layout.ComponentSBOM{}
+
+ for _, component := range components {
+ onCreate := component.Actions.OnCreate
+
+ onFailure := func() {
+ if err := actions.Run(pc.cfg, onCreate.Defaults, onCreate.OnFailure, nil); err != nil {
+ message.Debugf("unable to run component failure action: %s", err.Error())
+ }
+ }
+
+ if err := pc.addComponent(component, dst); err != nil {
+ onFailure()
+ return fmt.Errorf("unable to add component %q: %w", component.Name, err)
+ }
+
+ if err := actions.Run(pc.cfg, onCreate.Defaults, onCreate.OnSuccess, nil); err != nil {
+ onFailure()
+ return fmt.Errorf("unable to run component success action: %w", err)
+ }
+
+ if !skipSBOMFlagUsed {
+ componentSBOM, err := pc.getFilesToSBOM(component, dst)
+ if err != nil {
+ return fmt.Errorf("unable to create component SBOM: %w", err)
+ }
+ if componentSBOM != nil && len(componentSBOM.Files) > 0 {
+ componentSBOMs[component.Name] = componentSBOM
+ }
+ }
+
+ // Combine all component images into a single entry for efficient layer reuse.
+ for _, src := range component.Images {
+ refInfo, err := transform.ParseImageRef(src)
+ if err != nil {
+ return fmt.Errorf("failed to create ref for image %s: %w", src, err)
+ }
+ imageList = append(imageList, refInfo)
+ }
+ }
+
+ imageList = helpers.Unique(imageList)
+ var sbomImageList []transform.Image
+
+ // Images are handled separately from other component assets.
+ if len(imageList) > 0 {
+ message.HeaderInfof("📦 PACKAGE IMAGES")
+
+ dst.AddImages()
+
+ var pulled []images.ImgInfo
+ var err error
+
+ doPull := func() error {
+ imgConfig := images.ImageConfig{
+ ImagesPath: dst.Images.Base,
+ ImageList: imageList,
+ Insecure: config.CommonOptions.Insecure,
+ Architectures: []string{arch},
+ RegistryOverrides: pc.createOpts.RegistryOverrides,
+ }
+
+ pulled, err = imgConfig.PullAll()
+ return err
+ }
+
+ if err := helpers.Retry(doPull, 3, 5*time.Second, message.Warnf); err != nil {
+ return fmt.Errorf("unable to pull images after 3 attempts: %w", err)
+ }
+
+ for _, imgInfo := range pulled {
+ if err := dst.Images.AddV1Image(imgInfo.Img); err != nil {
+ return err
+ }
+ if imgInfo.HasImageLayers {
+ sbomImageList = append(sbomImageList, imgInfo.RefInfo)
+ }
+ }
+ }
+
+ // Ignore SBOM creation if the flag is set.
+ if skipSBOMFlagUsed {
+ message.Debug("Skipping image SBOM processing per --skip-sbom flag")
+ } else {
+ dst.AddSBOMs()
+ if err := sbom.Catalog(componentSBOMs, sbomImageList, dst); err != nil {
+ return fmt.Errorf("unable to create an SBOM catalog for the package: %w", err)
+ }
+ }
+
+ return nil
+}
+
+// Output does the following:
+//
+// - archives components
+//
+// - generates checksums for all package files
+//
+// - writes the loaded zarf.yaml to disk
+//
+// - signs the package
+//
+// - writes the Zarf package as a tarball to a local directory,
+// or an OCI registry based on the --output flag
+func (pc *PackageCreator) Output(dst *layout.PackagePaths, pkg *types.ZarfPackage) (err error) {
+ // Process the component directories into compressed tarballs
+ // NOTE: This is purposefully being done after the SBOM cataloging
+ for _, component := range pkg.Components {
+ // Make the component a tar archive
+ if err := dst.Components.Archive(component, true); err != nil {
+ return fmt.Errorf("unable to archive component: %s", err.Error())
+ }
+ }
+
+ // Calculate all the checksums
+ pkg.Metadata.AggregateChecksum, err = dst.GenerateChecksums()
+ if err != nil {
+ return fmt.Errorf("unable to generate checksums for the package: %w", err)
+ }
+
+ if err := recordPackageMetadata(pkg, pc.createOpts); err != nil {
+ return err
+ }
+
+ if err := utils.WriteYaml(dst.ZarfYAML, pkg, helpers.ReadUser); err != nil {
+ return fmt.Errorf("unable to write zarf.yaml: %w", err)
+ }
+
+ // Sign the package if a key has been provided
+ if pc.createOpts.SigningKeyPath != "" {
+ if err := dst.SignPackage(pc.createOpts.SigningKeyPath, pc.createOpts.SigningKeyPassword); err != nil {
+ return err
+ }
+ }
+
+ // Create a remote ref + client for the package (if output is OCI)
+ // then publish the package to the remote.
+ if helpers.IsOCIURL(pc.createOpts.Output) {
+ ref, err := zoci.ReferenceFromMetadata(pc.createOpts.Output, &pkg.Metadata, &pkg.Build)
+ if err != nil {
+ return err
+ }
+ remote, err := zoci.NewRemote(ref, oci.PlatformForArch(config.GetArch()))
+ if err != nil {
+ return err
+ }
+
+ ctx := context.TODO()
+ err = remote.PublishPackage(ctx, pkg, dst, config.CommonOptions.OCIConcurrency)
+ if err != nil {
+ return fmt.Errorf("unable to publish package: %w", err)
+ }
+ message.HorizontalRule()
+ flags := ""
+ if config.CommonOptions.Insecure {
+ flags = "--insecure"
+ }
+ message.Title("To inspect/deploy/pull:", "")
+ message.ZarfCommand("package inspect %s %s", helpers.OCIURLPrefix+remote.Repo().Reference.String(), flags)
+ message.ZarfCommand("package deploy %s %s", helpers.OCIURLPrefix+remote.Repo().Reference.String(), flags)
+ message.ZarfCommand("package pull %s %s", helpers.OCIURLPrefix+remote.Repo().Reference.String(), flags)
+ } else {
+ // Use the output path if the user specified it.
+ packageName := fmt.Sprintf("%s%s", sources.NameFromMetadata(pkg, pc.createOpts.IsSkeleton), sources.PkgSuffix(pkg.Metadata.Uncompressed))
+ tarballPath := filepath.Join(pc.createOpts.Output, packageName)
+
+ // Try to remove the package if it already exists.
+ _ = os.Remove(tarballPath)
+
+ // Create the package tarball.
+ if err := dst.ArchivePackage(tarballPath, pc.createOpts.MaxPackageSizeMB); err != nil {
+ return fmt.Errorf("unable to archive package: %w", err)
+ }
+ }
+
+ // Output the SBOM files into a directory if specified.
+ if pc.createOpts.ViewSBOM || pc.createOpts.SBOMOutputDir != "" {
+ outputSBOM := pc.createOpts.SBOMOutputDir
+ var sbomDir string
+ if err := dst.SBOMs.Unarchive(); err != nil {
+ return fmt.Errorf("unable to unarchive SBOMs: %w", err)
+ }
+ sbomDir = dst.SBOMs.Path
+
+ if outputSBOM != "" {
+ out, err := dst.SBOMs.OutputSBOMFiles(outputSBOM, pkg.Metadata.Name)
+ if err != nil {
+ return err
+ }
+ sbomDir = out
+ }
+
+ if pc.createOpts.ViewSBOM {
+ sbom.ViewSBOMFiles(sbomDir)
+ }
+ }
+ return nil
+}
+
+func (pc *PackageCreator) processExtensions(components []types.ZarfComponent, layout *layout.PackagePaths, isYOLO bool) (processedComponents []types.ZarfComponent, err error) {
+ // Create component paths and process extensions for each component.
+ for _, c := range components {
+ componentPaths, err := layout.Components.Create(c)
+ if err != nil {
+ return nil, err
+ }
+
+ // Big Bang
+ if c.Extensions.BigBang != nil {
+ if c, err = bigbang.Run(isYOLO, componentPaths, c); err != nil {
+ return nil, fmt.Errorf("unable to process bigbang extension: %w", err)
+ }
+ }
+
+ processedComponents = append(processedComponents, c)
+ }
+
+ return processedComponents, nil
+}
+
+func (pc *PackageCreator) addComponent(component types.ZarfComponent, dst *layout.PackagePaths) error {
+ message.HeaderInfof("📦 %s COMPONENT", strings.ToUpper(component.Name))
+
+ componentPaths, err := dst.Components.Create(component)
+ if err != nil {
+ return err
+ }
+
+ onCreate := component.Actions.OnCreate
+ if err := actions.Run(pc.cfg, onCreate.Defaults, onCreate.Before, nil); err != nil {
+ return fmt.Errorf("unable to run component before action: %w", err)
+ }
+
+ // If any helm charts are defined, process them.
+ for _, chart := range component.Charts {
+ helmCfg := helm.New(chart, componentPaths.Charts, componentPaths.Values)
+ if err := helmCfg.PackageChart(componentPaths.Charts); err != nil {
+ return err
+ }
+ }
+
+ for filesIdx, file := range component.Files {
+ message.Debugf("Loading %#v", file)
+
+ rel := filepath.Join(layout.FilesDir, strconv.Itoa(filesIdx), filepath.Base(file.Target))
+ dst := filepath.Join(componentPaths.Base, rel)
+ destinationDir := filepath.Dir(dst)
+
+ if helpers.IsURL(file.Source) {
+ if file.ExtractPath != "" {
+ // get the compressedFileName from the source
+ compressedFileName, err := helpers.ExtractBasePathFromURL(file.Source)
+ if err != nil {
+ return fmt.Errorf(lang.ErrFileNameExtract, file.Source, err.Error())
+ }
+
+ compressedFile := filepath.Join(componentPaths.Temp, compressedFileName)
+
+ // If the file is an archive, download it to the componentPath.Temp
+ if err := utils.DownloadToFile(file.Source, compressedFile, component.DeprecatedCosignKeyPath); err != nil {
+ return fmt.Errorf(lang.ErrDownloading, file.Source, err.Error())
+ }
+
+ err = archiver.Extract(compressedFile, file.ExtractPath, destinationDir)
+ if err != nil {
+ return fmt.Errorf(lang.ErrFileExtract, file.ExtractPath, compressedFileName, err.Error())
+ }
+ } else {
+ if err := utils.DownloadToFile(file.Source, dst, component.DeprecatedCosignKeyPath); err != nil {
+ return fmt.Errorf(lang.ErrDownloading, file.Source, err.Error())
+ }
+ }
+ } else {
+ if file.ExtractPath != "" {
+ if err := archiver.Extract(file.Source, file.ExtractPath, destinationDir); err != nil {
+ return fmt.Errorf(lang.ErrFileExtract, file.ExtractPath, file.Source, err.Error())
+ }
+ } else {
+ if err := utils.CreatePathAndCopy(file.Source, dst); err != nil {
+ return fmt.Errorf("unable to copy file %s: %w", file.Source, err)
+ }
+ }
+ }
+
+ if file.ExtractPath != "" {
+ // Make sure dst reflects the actual file or directory.
+ updatedExtractedFileOrDir := filepath.Join(destinationDir, file.ExtractPath)
+ if updatedExtractedFileOrDir != dst {
+ if err := os.Rename(updatedExtractedFileOrDir, dst); err != nil {
+ return fmt.Errorf(lang.ErrWritingFile, dst, err)
+ }
+ }
+ }
+
+ // Abort packaging on invalid shasum (if one is specified).
+ if file.Shasum != "" {
+ if err := utils.SHAsMatch(dst, file.Shasum); err != nil {
+ return err
+ }
+ }
+
+ if file.Executable || utils.IsDir(dst) {
+ _ = os.Chmod(dst, helpers.ReadWriteExecuteUser)
+ } else {
+ _ = os.Chmod(dst, helpers.ReadWriteUser)
+ }
+ }
+
+ if len(component.DataInjections) > 0 {
+ spinner := message.NewProgressSpinner("Loading data injections")
+ defer spinner.Stop()
+
+ for dataIdx, data := range component.DataInjections {
+ spinner.Updatef("Copying data injection %s for %s", data.Target.Path, data.Target.Selector)
+
+ rel := filepath.Join(layout.DataInjectionsDir, strconv.Itoa(dataIdx), filepath.Base(data.Target.Path))
+ dst := filepath.Join(componentPaths.Base, rel)
+
+ if helpers.IsURL(data.Source) {
+ if err := utils.DownloadToFile(data.Source, dst, component.DeprecatedCosignKeyPath); err != nil {
+ return fmt.Errorf(lang.ErrDownloading, data.Source, err.Error())
+ }
+ } else {
+ if err := utils.CreatePathAndCopy(data.Source, dst); err != nil {
+ return fmt.Errorf("unable to copy data injection %s: %s", data.Source, err.Error())
+ }
+ }
+ }
+ spinner.Success()
+ }
+
+ if len(component.Manifests) > 0 {
+ // Get the proper count of total manifests to add.
+ manifestCount := 0
+
+ for _, manifest := range component.Manifests {
+ manifestCount += len(manifest.Files)
+ manifestCount += len(manifest.Kustomizations)
+ }
+
+ spinner := message.NewProgressSpinner("Loading %d K8s manifests", manifestCount)
+ defer spinner.Stop()
+
+ // Iterate over all manifests.
+ for _, manifest := range component.Manifests {
+ for fileIdx, path := range manifest.Files {
+ rel := filepath.Join(layout.ManifestsDir, fmt.Sprintf("%s-%d.yaml", manifest.Name, fileIdx))
+ dst := filepath.Join(componentPaths.Base, rel)
+
+ // Copy manifests without any processing.
+ spinner.Updatef("Copying manifest %s", path)
+ if helpers.IsURL(path) {
+ if err := utils.DownloadToFile(path, dst, component.DeprecatedCosignKeyPath); err != nil {
+ return fmt.Errorf(lang.ErrDownloading, path, err.Error())
+ }
+ } else {
+ if err := utils.CreatePathAndCopy(path, dst); err != nil {
+ return fmt.Errorf("unable to copy manifest %s: %w", path, err)
+ }
+ }
+ }
+
+ for kustomizeIdx, path := range manifest.Kustomizations {
+ // Generate manifests from kustomizations and place in the package.
+ spinner.Updatef("Building kustomization for %s", path)
+
+ kname := fmt.Sprintf("kustomization-%s-%d.yaml", manifest.Name, kustomizeIdx)
+ rel := filepath.Join(layout.ManifestsDir, kname)
+ dst := filepath.Join(componentPaths.Base, rel)
+
+ if err := kustomize.Build(path, dst, manifest.KustomizeAllowAnyDirectory); err != nil {
+ return fmt.Errorf("unable to build kustomization %s: %w", path, err)
+ }
+ }
+ }
+ spinner.Success()
+ }
+
+ // Load all specified git repos.
+ if len(component.Repos) > 0 {
+ spinner := message.NewProgressSpinner("Loading %d git repos", len(component.Repos))
+ defer spinner.Stop()
+
+ for _, url := range component.Repos {
+ // Pull all the references if there is no `@` in the string.
+ gitCfg := git.NewWithSpinner(types.GitServerInfo{}, spinner)
+ if err := gitCfg.Pull(url, componentPaths.Repos, false); err != nil {
+ return fmt.Errorf("unable to pull git repo %s: %w", url, err)
+ }
+ }
+ spinner.Success()
+ }
+
+ if err := actions.Run(pc.cfg, onCreate.Defaults, onCreate.After, nil); err != nil {
+ return fmt.Errorf("unable to run component after action: %w", err)
+ }
+
+ return nil
+}
+
+func (pc *PackageCreator) getFilesToSBOM(component types.ZarfComponent, dst *layout.PackagePaths) (*layout.ComponentSBOM, error) {
+ componentPaths, err := dst.Components.Create(component)
+ if err != nil {
+ return nil, err
+ }
+ // Create an struct to hold the SBOM information for this component.
+ componentSBOM := &layout.ComponentSBOM{
+ Files: []string{},
+ Component: componentPaths,
+ }
+
+ appendSBOMFiles := func(path string) {
+ if utils.IsDir(path) {
+ files, _ := utils.RecursiveFileList(path, nil, false)
+ componentSBOM.Files = append(componentSBOM.Files, files...)
+ } else {
+ componentSBOM.Files = append(componentSBOM.Files, path)
+ }
+ }
+
+ for filesIdx, file := range component.Files {
+ path := filepath.Join(componentPaths.Files, strconv.Itoa(filesIdx), filepath.Base(file.Target))
+ appendSBOMFiles(path)
+ }
+
+ for dataIdx, data := range component.DataInjections {
+ path := filepath.Join(componentPaths.DataInjections, strconv.Itoa(dataIdx), filepath.Base(data.Target.Path))
+
+ appendSBOMFiles(path)
+ }
+
+ return componentSBOM, nil
+}
diff --git a/src/pkg/packager/creator/skeleton.go b/src/pkg/packager/creator/skeleton.go
new file mode 100644
index 0000000000..d23081f1a7
--- /dev/null
+++ b/src/pkg/packager/creator/skeleton.go
@@ -0,0 +1,329 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
+
+// Package creator contains functions for creating Zarf packages.
+package creator
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/defenseunicorns/zarf/src/config/lang"
+ "github.com/defenseunicorns/zarf/src/extensions/bigbang"
+ "github.com/defenseunicorns/zarf/src/internal/packager/helm"
+ "github.com/defenseunicorns/zarf/src/internal/packager/kustomize"
+ "github.com/defenseunicorns/zarf/src/pkg/layout"
+ "github.com/defenseunicorns/zarf/src/pkg/message"
+ "github.com/defenseunicorns/zarf/src/pkg/utils"
+ "github.com/defenseunicorns/zarf/src/pkg/utils/helpers"
+ "github.com/defenseunicorns/zarf/src/pkg/zoci"
+ "github.com/defenseunicorns/zarf/src/types"
+ "github.com/mholt/archiver/v3"
+)
+
+var (
+ // verify that SkeletonCreator implements Creator
+ _ Creator = (*SkeletonCreator)(nil)
+)
+
+// SkeletonCreator provides methods for creating skeleton Zarf packages.
+type SkeletonCreator struct {
+ createOpts types.ZarfCreateOptions
+ publishOpts types.ZarfPublishOptions
+}
+
+// NewSkeletonCreator returns a new SkeletonCreator.
+func NewSkeletonCreator(createOpts types.ZarfCreateOptions, publishOpts types.ZarfPublishOptions) *SkeletonCreator {
+ return &SkeletonCreator{createOpts, publishOpts}
+}
+
+// LoadPackageDefinition loads and configure a zarf.yaml file when creating and publishing a skeleton package.
+func (sc *SkeletonCreator) LoadPackageDefinition(dst *layout.PackagePaths) (pkg types.ZarfPackage, warnings []string, err error) {
+ pkg, warnings, err = dst.ReadZarfYAML(layout.ZarfYAML)
+ if err != nil {
+ return types.ZarfPackage{}, nil, err
+ }
+
+ pkg.Metadata.Architecture = zoci.SkeletonArch
+
+ // Compose components into a single zarf.yaml file
+ pkg, composeWarnings, err := ComposeComponents(pkg, sc.createOpts.Flavor)
+ if err != nil {
+ return types.ZarfPackage{}, nil, err
+ }
+
+ warnings = append(warnings, composeWarnings...)
+
+ pkg.Components, err = sc.processExtensions(pkg.Components, dst)
+ if err != nil {
+ return types.ZarfPackage{}, nil, err
+ }
+
+ for _, warning := range warnings {
+ message.Warn(warning)
+ }
+
+ return pkg, warnings, nil
+}
+
+// Assemble updates all components of the loaded Zarf package with necessary modifications for package assembly.
+//
+// It processes each component to ensure correct structure and resource locations.
+func (sc *SkeletonCreator) Assemble(dst *layout.PackagePaths, components []types.ZarfComponent, _ string) error {
+ for _, component := range components {
+ c, err := sc.addComponent(component, dst)
+ if err != nil {
+ return err
+ }
+ components = append(components, *c)
+ }
+
+ return nil
+}
+
+// Output does the following:
+//
+// - archives components
+//
+// - generates checksums for all package files
+//
+// - writes the loaded zarf.yaml to disk
+//
+// - signs the package
+func (sc *SkeletonCreator) Output(dst *layout.PackagePaths, pkg *types.ZarfPackage) (err error) {
+ for _, component := range pkg.Components {
+ if err := dst.Components.Archive(component, false); err != nil {
+ return err
+ }
+ }
+
+ // Calculate all the checksums
+ pkg.Metadata.AggregateChecksum, err = dst.GenerateChecksums()
+ if err != nil {
+ return fmt.Errorf("unable to generate checksums for the package: %w", err)
+ }
+
+ if err := recordPackageMetadata(pkg, sc.createOpts); err != nil {
+ return err
+ }
+
+ if err := utils.WriteYaml(dst.ZarfYAML, pkg, helpers.ReadUser); err != nil {
+ return fmt.Errorf("unable to write zarf.yaml: %w", err)
+ }
+
+ // Sign the package if a key has been provided
+ if sc.publishOpts.SigningKeyPath != "" {
+ if err := dst.SignPackage(sc.publishOpts.SigningKeyPath, sc.publishOpts.SigningKeyPassword); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (sc *SkeletonCreator) processExtensions(components []types.ZarfComponent, layout *layout.PackagePaths) (processedComponents []types.ZarfComponent, err error) {
+ // Create component paths and process extensions for each component.
+ for _, c := range components {
+ componentPaths, err := layout.Components.Create(c)
+ if err != nil {
+ return nil, err
+ }
+
+ // Big Bang
+ if c.Extensions.BigBang != nil {
+ if c, err = bigbang.Skeletonize(componentPaths, c); err != nil {
+ return nil, fmt.Errorf("unable to process bigbang extension: %w", err)
+ }
+ }
+
+ processedComponents = append(processedComponents, c)
+ }
+
+ return processedComponents, nil
+}
+
+func (sc *SkeletonCreator) addComponent(component types.ZarfComponent, dst *layout.PackagePaths) (updatedComponent *types.ZarfComponent, err error) {
+ message.HeaderInfof("📦 %s COMPONENT", strings.ToUpper(component.Name))
+
+ updatedComponent = &component
+
+ componentPaths, err := dst.Components.Create(component)
+ if err != nil {
+ return nil, err
+ }
+
+ if component.DeprecatedCosignKeyPath != "" {
+ dst := filepath.Join(componentPaths.Base, "cosign.pub")
+ err := utils.CreatePathAndCopy(component.DeprecatedCosignKeyPath, dst)
+ if err != nil {
+ return nil, err
+ }
+ updatedComponent.DeprecatedCosignKeyPath = "cosign.pub"
+ }
+
+ // TODO: (@WSTARR) Shim the skeleton component's create action dirs to be empty. This prevents actions from failing by cd'ing into directories that will be flattened.
+ updatedComponent.Actions.OnCreate.Defaults.Dir = ""
+
+ resetActions := func(actions []types.ZarfComponentAction) []types.ZarfComponentAction {
+ for idx := range actions {
+ actions[idx].Dir = nil
+ }
+ return actions
+ }
+
+ updatedComponent.Actions.OnCreate.Before = resetActions(component.Actions.OnCreate.Before)
+ updatedComponent.Actions.OnCreate.After = resetActions(component.Actions.OnCreate.After)
+ updatedComponent.Actions.OnCreate.OnSuccess = resetActions(component.Actions.OnCreate.OnSuccess)
+ updatedComponent.Actions.OnCreate.OnFailure = resetActions(component.Actions.OnCreate.OnFailure)
+
+ // If any helm charts are defined, process them.
+ for chartIdx, chart := range component.Charts {
+
+ if chart.LocalPath != "" {
+ rel := filepath.Join(layout.ChartsDir, fmt.Sprintf("%s-%d", chart.Name, chartIdx))
+ dst := filepath.Join(componentPaths.Base, rel)
+
+ err := utils.CreatePathAndCopy(chart.LocalPath, dst)
+ if err != nil {
+ return nil, err
+ }
+
+ updatedComponent.Charts[chartIdx].LocalPath = rel
+ }
+
+ for valuesIdx, path := range chart.ValuesFiles {
+ if helpers.IsURL(path) {
+ continue
+ }
+
+ rel := fmt.Sprintf("%s-%d", helm.StandardName(layout.ValuesDir, chart), valuesIdx)
+ updatedComponent.Charts[chartIdx].ValuesFiles[valuesIdx] = rel
+
+ if err := utils.CreatePathAndCopy(path, filepath.Join(componentPaths.Base, rel)); err != nil {
+ return nil, fmt.Errorf("unable to copy chart values file %s: %w", path, err)
+ }
+ }
+ }
+
+ for filesIdx, file := range component.Files {
+ message.Debugf("Loading %#v", file)
+
+ if helpers.IsURL(file.Source) {
+ continue
+ }
+
+ rel := filepath.Join(layout.FilesDir, strconv.Itoa(filesIdx), filepath.Base(file.Target))
+ dst := filepath.Join(componentPaths.Base, rel)
+ destinationDir := filepath.Dir(dst)
+
+ if file.ExtractPath != "" {
+ if err := archiver.Extract(file.Source, file.ExtractPath, destinationDir); err != nil {
+ return nil, fmt.Errorf(lang.ErrFileExtract, file.ExtractPath, file.Source, err.Error())
+ }
+
+ // Make sure dst reflects the actual file or directory.
+ updatedExtractedFileOrDir := filepath.Join(destinationDir, file.ExtractPath)
+ if updatedExtractedFileOrDir != dst {
+ if err := os.Rename(updatedExtractedFileOrDir, dst); err != nil {
+ return nil, fmt.Errorf(lang.ErrWritingFile, dst, err)
+ }
+ }
+ } else {
+ if err := utils.CreatePathAndCopy(file.Source, dst); err != nil {
+ return nil, fmt.Errorf("unable to copy file %s: %w", file.Source, err)
+ }
+ }
+
+ // Change the source to the new relative source directory (any remote files will have been skipped above)
+ updatedComponent.Files[filesIdx].Source = rel
+
+ // Remove the extractPath from a skeleton since it will already extract it
+ updatedComponent.Files[filesIdx].ExtractPath = ""
+
+ // Abort packaging on invalid shasum (if one is specified).
+ if file.Shasum != "" {
+ if err := utils.SHAsMatch(dst, file.Shasum); err != nil {
+ return nil, err
+ }
+ }
+
+ if file.Executable || utils.IsDir(dst) {
+ _ = os.Chmod(dst, helpers.ReadWriteExecuteUser)
+ } else {
+ _ = os.Chmod(dst, helpers.ReadWriteUser)
+ }
+ }
+
+ if len(component.DataInjections) > 0 {
+ spinner := message.NewProgressSpinner("Loading data injections")
+ defer spinner.Stop()
+
+ for dataIdx, data := range component.DataInjections {
+ spinner.Updatef("Copying data injection %s for %s", data.Target.Path, data.Target.Selector)
+
+ rel := filepath.Join(layout.DataInjectionsDir, strconv.Itoa(dataIdx), filepath.Base(data.Target.Path))
+ dst := filepath.Join(componentPaths.Base, rel)
+
+ if err := utils.CreatePathAndCopy(data.Source, dst); err != nil {
+ return nil, fmt.Errorf("unable to copy data injection %s: %s", data.Source, err.Error())
+ }
+
+ updatedComponent.DataInjections[dataIdx].Source = rel
+ }
+
+ spinner.Success()
+ }
+
+ if len(component.Manifests) > 0 {
+ // Get the proper count of total manifests to add.
+ manifestCount := 0
+
+ for _, manifest := range component.Manifests {
+ manifestCount += len(manifest.Files)
+ manifestCount += len(manifest.Kustomizations)
+ }
+
+ spinner := message.NewProgressSpinner("Loading %d K8s manifests", manifestCount)
+ defer spinner.Stop()
+
+ // Iterate over all manifests.
+ for manifestIdx, manifest := range component.Manifests {
+ for fileIdx, path := range manifest.Files {
+ rel := filepath.Join(layout.ManifestsDir, fmt.Sprintf("%s-%d.yaml", manifest.Name, fileIdx))
+ dst := filepath.Join(componentPaths.Base, rel)
+
+ // Copy manifests without any processing.
+ spinner.Updatef("Copying manifest %s", path)
+
+ if err := utils.CreatePathAndCopy(path, dst); err != nil {
+ return nil, fmt.Errorf("unable to copy manifest %s: %w", path, err)
+ }
+
+ updatedComponent.Manifests[manifestIdx].Files[fileIdx] = rel
+ }
+
+ for kustomizeIdx, path := range manifest.Kustomizations {
+ // Generate manifests from kustomizations and place in the package.
+ spinner.Updatef("Building kustomization for %s", path)
+
+ kname := fmt.Sprintf("kustomization-%s-%d.yaml", manifest.Name, kustomizeIdx)
+ rel := filepath.Join(layout.ManifestsDir, kname)
+ dst := filepath.Join(componentPaths.Base, rel)
+
+ if err := kustomize.Build(path, dst, manifest.KustomizeAllowAnyDirectory); err != nil {
+ return nil, fmt.Errorf("unable to build kustomization %s: %w", path, err)
+ }
+ }
+
+ // remove kustomizations
+ updatedComponent.Manifests[manifestIdx].Kustomizations = nil
+ }
+
+ spinner.Success()
+ }
+
+ return updatedComponent, nil
+}
diff --git a/src/pkg/packager/creator/template.go b/src/pkg/packager/creator/template.go
new file mode 100644
index 0000000000..53b22ba206
--- /dev/null
+++ b/src/pkg/packager/creator/template.go
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
+
+// Package creator contains functions for creating Zarf packages.
+package creator
+
+import (
+ "fmt"
+
+ "github.com/defenseunicorns/zarf/src/config"
+ "github.com/defenseunicorns/zarf/src/config/lang"
+ "github.com/defenseunicorns/zarf/src/pkg/interactive"
+ "github.com/defenseunicorns/zarf/src/pkg/utils"
+ "github.com/defenseunicorns/zarf/src/types"
+)
+
+// FillActiveTemplate merges user-specified variables into the configuration templates of a zarf.yaml.
+func FillActiveTemplate(pkg types.ZarfPackage, setVariables map[string]string) (types.ZarfPackage, []string, error) {
+ templateMap := map[string]string{}
+ warnings := []string{}
+
+ promptAndSetTemplate := func(templatePrefix string, deprecated bool) error {
+ yamlTemplates, err := utils.FindYamlTemplates(&pkg, templatePrefix, "###")
+ if err != nil {
+ return err
+ }
+
+ for key := range yamlTemplates {
+ if deprecated {
+ warnings = append(warnings, fmt.Sprintf(lang.PkgValidateTemplateDeprecation, key, key, key))
+ }
+
+ _, present := setVariables[key]
+ if !present && !config.CommonOptions.Confirm {
+ setVal, err := interactive.PromptVariable(types.ZarfPackageVariable{
+ Name: key,
+ })
+ if err != nil {
+ return err
+ }
+ setVariables[key] = setVal
+ } else if !present {
+ return fmt.Errorf("template %q must be '--set' when using the '--confirm' flag", key)
+ }
+ }
+
+ for key, value := range setVariables {
+ templateMap[fmt.Sprintf("%s%s###", templatePrefix, key)] = value
+ }
+
+ return nil
+ }
+
+ // update the component templates on the package
+ if err := ReloadComponentTemplatesInPackage(&pkg); err != nil {
+ return types.ZarfPackage{}, nil, err
+ }
+
+ if err := promptAndSetTemplate(types.ZarfPackageTemplatePrefix, false); err != nil {
+ return types.ZarfPackage{}, nil, err
+ }
+ // [DEPRECATION] Set the Package Variable syntax as well for backward compatibility
+ if err := promptAndSetTemplate(types.ZarfPackageVariablePrefix, true); err != nil {
+ return types.ZarfPackage{}, nil, err
+ }
+
+ // Add special variable for the current package architecture
+ templateMap[types.ZarfPackageArch] = pkg.Metadata.Architecture
+
+ if err := utils.ReloadYamlTemplate(&pkg, templateMap); err != nil {
+ return types.ZarfPackage{}, nil, err
+ }
+
+ return pkg, warnings, nil
+}
+
+// ReloadComponentTemplate appends ###ZARF_COMPONENT_NAME### for the component, assigns value, and reloads
+// Any instance of ###ZARF_COMPONENT_NAME### within a component will be replaced with that components name
+func ReloadComponentTemplate(component *types.ZarfComponent) error {
+ mappings := map[string]string{}
+ mappings[types.ZarfComponentName] = component.Name
+ err := utils.ReloadYamlTemplate(component, mappings)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// ReloadComponentTemplatesInPackage appends ###ZARF_COMPONENT_NAME### for each component, assigns value, and reloads
+func ReloadComponentTemplatesInPackage(zarfPackage *types.ZarfPackage) error {
+ // iterate through components to and find all ###ZARF_COMPONENT_NAME, assign to component Name and value
+ for i := range zarfPackage.Components {
+ if err := ReloadComponentTemplate(&zarfPackage.Components[i]); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/src/pkg/packager/creator/utils.go b/src/pkg/packager/creator/utils.go
new file mode 100644
index 0000000000..43ab469565
--- /dev/null
+++ b/src/pkg/packager/creator/utils.go
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
+
+// Package creator contains functions for creating Zarf packages.
+package creator
+
+import (
+ "os"
+ "runtime"
+ "time"
+
+ "github.com/defenseunicorns/zarf/src/config"
+ "github.com/defenseunicorns/zarf/src/pkg/packager/deprecated"
+ "github.com/defenseunicorns/zarf/src/types"
+)
+
+// recordPackageMetadata records various package metadata during package create.
+func recordPackageMetadata(pkg *types.ZarfPackage, createOpts types.ZarfCreateOptions) error {
+ now := time.Now()
+ // Just use $USER env variable to avoid CGO issue.
+ // https://groups.google.com/g/golang-dev/c/ZFDDX3ZiJ84.
+ // Record the name of the user creating the package.
+ if runtime.GOOS == "windows" {
+ pkg.Build.User = os.Getenv("USERNAME")
+ } else {
+ pkg.Build.User = os.Getenv("USER")
+ }
+
+ // Record the hostname of the package creation terminal.
+ // The error here is ignored because the hostname is not critical to the package creation.
+ hostname, _ := os.Hostname()
+ pkg.Build.Terminal = hostname
+
+ if pkg.IsInitConfig() {
+ pkg.Metadata.Version = config.CLIVersion
+ }
+
+ pkg.Build.Architecture = pkg.Metadata.Architecture
+
+ // Record the Zarf Version the CLI was built with.
+ pkg.Build.Version = config.CLIVersion
+
+ // Record the time of package creation.
+ pkg.Build.Timestamp = now.Format(time.RFC1123Z)
+
+ // Record the migrations that will be ran on the package.
+ pkg.Build.Migrations = []string{
+ deprecated.ScriptsToActionsMigrated,
+ deprecated.PluralizeSetVariable,
+ }
+
+ // Record the flavor of Zarf used to build this package (if any).
+ pkg.Build.Flavor = createOpts.Flavor
+
+ pkg.Build.RegistryOverrides = createOpts.RegistryOverrides
+
+ // Record the latest version of Zarf without breaking changes to the package structure.
+ pkg.Build.LastNonBreakingVersion = deprecated.LastNonBreakingVersion
+
+ return nil
+}
diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go
index 8974c71612..7a1cece304 100644
--- a/src/pkg/packager/deploy.go
+++ b/src/pkg/packager/deploy.go
@@ -23,6 +23,8 @@ import (
"github.com/defenseunicorns/zarf/src/pkg/k8s"
"github.com/defenseunicorns/zarf/src/pkg/layout"
"github.com/defenseunicorns/zarf/src/pkg/message"
+ "github.com/defenseunicorns/zarf/src/pkg/packager/actions"
+ "github.com/defenseunicorns/zarf/src/pkg/packager/variables"
"github.com/defenseunicorns/zarf/src/pkg/transform"
"github.com/defenseunicorns/zarf/src/pkg/utils"
"github.com/defenseunicorns/zarf/src/pkg/utils/helpers"
@@ -44,7 +46,8 @@ func (p *Packager) Deploy() (err error) {
return fmt.Errorf("unable to load the package: %w", err)
}
- if err = p.readZarfYAML(p.layout.ZarfYAML); err != nil {
+ p.cfg.Pkg, p.warnings, err = p.layout.ReadZarfYAML(p.layout.ZarfYAML)
+ if err != nil {
return err
}
@@ -52,17 +55,20 @@ func (p *Packager) Deploy() (err error) {
return err
}
- if err := p.stageSBOMViewFiles(); err != nil {
+ sbomWarnings, err := p.layout.SBOMs.StageSBOMViewFiles()
+ if err != nil {
return err
}
+ p.warnings = append(p.warnings, sbomWarnings...)
+
// Confirm the overall package deployment
if !p.confirmAction(config.ZarfDeployStage) {
return fmt.Errorf("deployment cancelled")
}
// Set variables and prompt if --confirm is not set
- if err := p.setVariableMapInConfig(); err != nil {
+ if err := variables.SetVariableMapInConfig(p.cfg); err != nil {
return err
}
@@ -117,7 +123,7 @@ func (p *Packager) deployComponents() (deployedComponents []types.DeployedCompon
// If this component requires a cluster, connect to one
if requiresCluster(component) {
timeout := cluster.DefaultTimeout
- if p.isInitConfig() {
+ if p.cfg.Pkg.IsInitConfig() {
timeout = 5 * time.Minute
}
@@ -147,7 +153,7 @@ func (p *Packager) deployComponents() (deployedComponents []types.DeployedCompon
// Deploy the component
var charts []types.InstalledChart
var deployErr error
- if p.isInitConfig() {
+ if p.cfg.Pkg.IsInitConfig() {
charts, deployErr = p.deployInitComponent(component)
} else {
charts, deployErr = p.deployComponent(component, false /* keep img checksum */, false /* always push images */)
@@ -156,7 +162,7 @@ func (p *Packager) deployComponents() (deployedComponents []types.DeployedCompon
onDeploy := component.Actions.OnDeploy
onFailure := func() {
- if err := p.runActions(onDeploy.Defaults, onDeploy.OnFailure, p.valueTemplate); err != nil {
+ if err := actions.Run(p.cfg, onDeploy.Defaults, onDeploy.OnFailure, p.valueTemplate); err != nil {
message.Debugf("unable to run component failure action: %s", err.Error())
}
}
@@ -184,7 +190,7 @@ func (p *Packager) deployComponents() (deployedComponents []types.DeployedCompon
}
}
- if err := p.runActions(onDeploy.Defaults, onDeploy.OnSuccess, p.valueTemplate); err != nil {
+ if err := actions.Run(p.cfg, onDeploy.Defaults, onDeploy.OnSuccess, p.valueTemplate); err != nil {
onFailure()
return deployedComponents, fmt.Errorf("unable to run component success action: %w", err)
}
@@ -277,7 +283,7 @@ func (p *Packager) deployComponent(component types.ZarfComponent, noImgChecksum
}
}
- if err = p.runActions(onDeploy.Defaults, onDeploy.Before, p.valueTemplate); err != nil {
+ if err = actions.Run(p.cfg, onDeploy.Defaults, onDeploy.Before, p.valueTemplate); err != nil {
return charts, fmt.Errorf("unable to run component before action: %w", err)
}
@@ -315,7 +321,7 @@ func (p *Packager) deployComponent(component types.ZarfComponent, noImgChecksum
}
}
- if err = p.runActions(onDeploy.Defaults, onDeploy.After, p.valueTemplate); err != nil {
+ if err = actions.Run(p.cfg, onDeploy.Defaults, onDeploy.After, p.valueTemplate); err != nil {
return charts, fmt.Errorf("unable to run component after action: %w", err)
}
@@ -465,7 +471,7 @@ func (p *Packager) pushImagesToRegistry(componentImages []string, noImgChecksum
NoChecksum: noImgChecksum,
RegInfo: p.cfg.State.RegistryInfo,
Insecure: config.CommonOptions.Insecure,
- Architectures: []string{p.cfg.Pkg.Metadata.Architecture, p.cfg.Pkg.Build.Architecture},
+ Architectures: []string{p.cfg.Pkg.Build.Architecture},
}
return helpers.Retry(func() error {
@@ -624,7 +630,7 @@ func (p *Packager) installChartAndManifests(componentPaths *layout.ComponentPath
func (p *Packager) printTablesForDeployment(componentsToDeploy []types.DeployedComponent) {
// If not init config, print the application connection table
- if !p.isInitConfig() {
+ if !p.cfg.Pkg.IsInitConfig() {
message.PrintConnectStringTable(p.connectStrings)
} else {
if p.cluster != nil {
diff --git a/src/pkg/packager/dev.go b/src/pkg/packager/dev.go
index 675ee540ee..2b978ec479 100644
--- a/src/pkg/packager/dev.go
+++ b/src/pkg/packager/dev.go
@@ -11,6 +11,8 @@ import (
"github.com/defenseunicorns/zarf/src/config"
"github.com/defenseunicorns/zarf/src/internal/packager/validate"
"github.com/defenseunicorns/zarf/src/pkg/message"
+ "github.com/defenseunicorns/zarf/src/pkg/packager/creator"
+ "github.com/defenseunicorns/zarf/src/pkg/packager/variables"
"github.com/defenseunicorns/zarf/src/types"
)
@@ -24,11 +26,14 @@ func (p *Packager) DevDeploy() error {
return err
}
- if err := p.cdToBaseDir(p.cfg.CreateOpts.BaseDir, cwd); err != nil {
- return err
+ if err := os.Chdir(p.cfg.CreateOpts.BaseDir); err != nil {
+ return fmt.Errorf("unable to access directory %q: %w", p.cfg.CreateOpts.BaseDir, err)
}
- if err := p.load(); err != nil {
+ pc := creator.NewPackageCreator(p.cfg.CreateOpts, p.cfg, cwd)
+
+ p.cfg.Pkg, p.warnings, err = pc.LoadPackageDefinition(p.layout)
+ if err != nil {
return err
}
@@ -54,14 +59,14 @@ func (p *Packager) DevDeploy() error {
}
}
- if err := p.assemble(); err != nil {
+ if err := pc.Assemble(p.layout, p.cfg.Pkg.Components, p.cfg.Pkg.Metadata.Architecture); err != nil {
return err
}
message.HeaderInfof("📦 PACKAGE DEPLOY %s", p.cfg.Pkg.Metadata.Name)
// Set variables and prompt if --confirm is not set
- if err := p.setVariableMapInConfig(); err != nil {
+ if err := variables.SetVariableMapInConfig(p.cfg); err != nil {
return err
}
diff --git a/src/pkg/packager/extensions.go b/src/pkg/packager/extensions.go
deleted file mode 100644
index b5cc0ab17d..0000000000
--- a/src/pkg/packager/extensions.go
+++ /dev/null
@@ -1,68 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
-
-// Package packager contains functions for interacting with, managing and deploying Zarf packages.
-package packager
-
-import (
- "fmt"
-
- "github.com/defenseunicorns/zarf/src/extensions/bigbang"
- "github.com/defenseunicorns/zarf/src/types"
-)
-
-// Check for any extensions in use and runs the appropriate functions.
-func (p *Packager) processExtensions() (err error) {
- components := []types.ZarfComponent{}
-
- // Create component paths and process extensions for each component.
- for _, c := range p.cfg.Pkg.Components {
- componentPaths, err := p.layout.Components.Create(c)
- if err != nil {
- return err
- }
-
- // Big Bang
- if c.Extensions.BigBang != nil {
- if c, err = bigbang.Run(p.cfg.Pkg.Metadata.YOLO, componentPaths, c); err != nil {
- return fmt.Errorf("unable to process bigbang extension: %w", err)
- }
- }
-
- components = append(components, c)
- }
-
- // Update the parent package config with the expanded sub components.
- // This is important when the deploy package is created.
- p.cfg.Pkg.Components = components
-
- return nil
-}
-
-// Check for any extensions in use and skeletonize their local files.
-func (p *Packager) skeletonizeExtensions() (err error) {
- components := []types.ZarfComponent{}
-
- // Create component paths and process extensions for each component.
- for _, c := range p.cfg.Pkg.Components {
- componentPaths, err := p.layout.Components.Create(c)
- if err != nil {
- return err
- }
-
- // Big Bang
- if c.Extensions.BigBang != nil {
- if c, err = bigbang.Skeletonize(componentPaths, c); err != nil {
- return fmt.Errorf("unable to process bigbang extension: %w", err)
- }
- }
-
- components = append(components, c)
- }
-
- // Update the parent package config with the expanded sub components.
- // This is important when the deploy package is created.
- p.cfg.Pkg.Components = components
-
- return nil
-}
diff --git a/src/pkg/packager/generate.go b/src/pkg/packager/generate.go
index a82fcdeca3..286807d186 100644
--- a/src/pkg/packager/generate.go
+++ b/src/pkg/packager/generate.go
@@ -62,7 +62,6 @@ func (p *Packager) Generate() (err error) {
generatedComponent,
},
}
- p.arch = config.GetArch()
images, err := p.findImages()
if err != nil {
diff --git a/src/pkg/packager/inspect.go b/src/pkg/packager/inspect.go
index 993c9ffeb0..bfc9c1aac8 100644
--- a/src/pkg/packager/inspect.go
+++ b/src/pkg/packager/inspect.go
@@ -17,7 +17,8 @@ func (p *Packager) Inspect() (err error) {
return err
}
- if err = p.readZarfYAML(p.layout.ZarfYAML); err != nil {
+ p.cfg.Pkg, p.warnings, err = p.layout.ReadZarfYAML(p.layout.ZarfYAML)
+ if err != nil {
return err
}
@@ -26,7 +27,7 @@ func (p *Packager) Inspect() (err error) {
sbomDir := p.layout.SBOMs.Path
if p.cfg.InspectOpts.SBOMOutputDir != "" {
- out, err := sbom.OutputSBOMFiles(sbomDir, p.cfg.InspectOpts.SBOMOutputDir, p.cfg.Pkg.Metadata.Name)
+ out, err := p.layout.SBOMs.OutputSBOMFiles(p.cfg.InspectOpts.SBOMOutputDir, p.cfg.Pkg.Metadata.Name)
if err != nil {
return err
}
diff --git a/src/pkg/packager/interactive.go b/src/pkg/packager/interactive.go
index f338b00b90..cdd46bf069 100644
--- a/src/pkg/packager/interactive.go
+++ b/src/pkg/packager/interactive.go
@@ -11,7 +11,6 @@ import (
"github.com/AlecAivazis/survey/v2"
"github.com/defenseunicorns/zarf/src/config"
- "github.com/defenseunicorns/zarf/src/internal/packager/sbom"
"github.com/defenseunicorns/zarf/src/pkg/layout"
"github.com/defenseunicorns/zarf/src/pkg/message"
"github.com/defenseunicorns/zarf/src/pkg/utils"
@@ -26,7 +25,7 @@ func (p *Packager) confirmAction(stage string) (confirm bool) {
// Print any potential breaking changes (if this is a Deploy confirm) between this CLI version and the deployed init package
if stage == config.ZarfDeployStage {
- if sbom.IsSBOMAble(p.cfg.Pkg) {
+ if p.cfg.Pkg.IsSBOMAble() {
// Print the location that the user can view the package SBOMs from
message.HorizontalRule()
message.Title("Software Bill of Materials", "an inventory of all software contained in this package")
diff --git a/src/pkg/packager/lint/lint.go b/src/pkg/packager/lint/lint.go
index bc8b5df336..8ac60076d1 100644
--- a/src/pkg/packager/lint/lint.go
+++ b/src/pkg/packager/lint/lint.go
@@ -15,8 +15,8 @@ import (
"github.com/defenseunicorns/zarf/src/config"
"github.com/defenseunicorns/zarf/src/config/lang"
"github.com/defenseunicorns/zarf/src/pkg/layout"
- "github.com/defenseunicorns/zarf/src/pkg/packager"
"github.com/defenseunicorns/zarf/src/pkg/packager/composer"
+ "github.com/defenseunicorns/zarf/src/pkg/packager/creator"
"github.com/defenseunicorns/zarf/src/pkg/transform"
"github.com/defenseunicorns/zarf/src/pkg/utils"
"github.com/defenseunicorns/zarf/src/pkg/utils/helpers"
@@ -104,8 +104,7 @@ func lintComponents(validator *Validator, createOpts *types.ZarfCreateOptions) {
}
func fillComponentTemplate(validator *Validator, node *composer.Node, createOpts *types.ZarfCreateOptions) {
-
- err := packager.ReloadComponentTemplate(&node.ZarfComponent)
+ err := creator.ReloadComponentTemplate(&node.ZarfComponent)
if err != nil {
validator.addWarning(validatorMessage{
description: err.Error(),
diff --git a/src/pkg/packager/mirror.go b/src/pkg/packager/mirror.go
index 689ddcfbaa..6dabec8a7f 100644
--- a/src/pkg/packager/mirror.go
+++ b/src/pkg/packager/mirror.go
@@ -21,14 +21,19 @@ func (p *Packager) Mirror() (err error) {
if err = p.source.LoadPackage(p.layout, true); err != nil {
return fmt.Errorf("unable to load the package: %w", err)
}
- if err = p.readZarfYAML(p.layout.ZarfYAML); err != nil {
+
+ p.cfg.Pkg, p.warnings, err = p.layout.ReadZarfYAML(p.layout.ZarfYAML)
+ if err != nil {
return err
}
- if err := p.stageSBOMViewFiles(); err != nil {
+ sbomWarnings, err := p.layout.SBOMs.StageSBOMViewFiles()
+ if err != nil {
return err
}
+ p.warnings = append(p.warnings, sbomWarnings...)
+
// Confirm the overall package mirror
if !p.confirmAction(config.ZarfMirrorStage) {
return fmt.Errorf("mirror cancelled")
diff --git a/src/pkg/packager/prepare.go b/src/pkg/packager/prepare.go
index 76d057e568..c4cfc95d3c 100644
--- a/src/pkg/packager/prepare.go
+++ b/src/pkg/packager/prepare.go
@@ -19,8 +19,9 @@ import (
"github.com/defenseunicorns/zarf/src/internal/packager/helm"
"github.com/defenseunicorns/zarf/src/internal/packager/kustomize"
"github.com/defenseunicorns/zarf/src/internal/packager/template"
- "github.com/defenseunicorns/zarf/src/pkg/layout"
"github.com/defenseunicorns/zarf/src/pkg/message"
+ "github.com/defenseunicorns/zarf/src/pkg/packager/creator"
+ "github.com/defenseunicorns/zarf/src/pkg/packager/variables"
"github.com/defenseunicorns/zarf/src/pkg/utils"
"github.com/defenseunicorns/zarf/src/pkg/utils/helpers"
"github.com/defenseunicorns/zarf/src/types"
@@ -53,8 +54,15 @@ func (p *Packager) FindImages() (imgMap map[string][]string, err error) {
}
message.Note(fmt.Sprintf("Using build directory %s", p.cfg.CreateOpts.BaseDir))
- if err = p.readZarfYAML(layout.ZarfYAML); err != nil {
- return nil, fmt.Errorf("unable to read the zarf.yaml file: %w", err)
+ c := creator.NewPackageCreator(p.cfg.CreateOpts, p.cfg, cwd)
+
+ p.cfg.Pkg, p.warnings, err = c.LoadPackageDefinition(p.layout)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, warning := range p.warnings {
+ message.Warn(warning)
}
return p.findImages()
@@ -70,19 +78,6 @@ func (p *Packager) findImages() (imgMap map[string][]string, err error) {
erroredCosignLookups := []string{}
whyResources := []string{}
- if err := p.composeComponents(); err != nil {
- return nil, err
- }
-
- for _, warning := range p.warnings {
- message.Warn(warning)
- }
-
- // After components are composed, template the active package
- if err := p.fillActiveTemplate(); err != nil {
- return nil, fmt.Errorf("unable to fill values in template: %w", err)
- }
-
for _, component := range p.cfg.Pkg.Components {
if len(component.Repos) > 0 && repoHelmChartPath == "" {
message.Note("This Zarf package contains git repositories, " +
@@ -94,7 +89,7 @@ func (p *Packager) findImages() (imgMap map[string][]string, err error) {
componentDefinition := "\ncomponents:\n"
- if err := p.setVariableMapInConfig(); err != nil {
+ if err := variables.SetVariableMapInConfig(p.cfg); err != nil {
return nil, err
}
diff --git a/src/pkg/packager/publish.go b/src/pkg/packager/publish.go
index 05ffe3bb4e..c438b83456 100644
--- a/src/pkg/packager/publish.go
+++ b/src/pkg/packager/publish.go
@@ -13,6 +13,7 @@ import (
"github.com/defenseunicorns/zarf/src/config"
"github.com/defenseunicorns/zarf/src/pkg/message"
"github.com/defenseunicorns/zarf/src/pkg/oci"
+ "github.com/defenseunicorns/zarf/src/pkg/packager/creator"
"github.com/defenseunicorns/zarf/src/pkg/packager/sources"
"github.com/defenseunicorns/zarf/src/pkg/utils"
"github.com/defenseunicorns/zarf/src/pkg/utils/helpers"
@@ -46,26 +47,40 @@ func (p *Packager) Publish() (err error) {
}
if p.cfg.CreateOpts.IsSkeleton {
- cwd, err := os.Getwd()
- if err != nil {
- return err
+ if err := os.Chdir(p.cfg.CreateOpts.BaseDir); err != nil {
+ return fmt.Errorf("unable to access directory %q: %w", p.cfg.CreateOpts.BaseDir, err)
}
- if err := p.cdToBaseDir(p.cfg.CreateOpts.BaseDir, cwd); err != nil {
+
+ sc := creator.NewSkeletonCreator(p.cfg.CreateOpts, p.cfg.PublishOpts)
+
+ p.cfg.Pkg, p.warnings, err = sc.LoadPackageDefinition(p.layout)
+ if err != nil {
return err
}
- if err := p.load(); err != nil {
+
+ if err := sc.Assemble(p.layout, p.cfg.Pkg.Components, ""); err != nil {
return err
}
- if err := p.assembleSkeleton(); err != nil {
+
+ if err := sc.Output(p.layout, &p.cfg.Pkg); err != nil {
return err
}
} else {
- if err = p.source.LoadPackage(p.layout, false); err != nil {
+ if err := p.source.LoadPackage(p.layout, false); err != nil {
return fmt.Errorf("unable to load the package: %w", err)
}
- if err = p.readZarfYAML(p.layout.ZarfYAML); err != nil {
+
+ p.cfg.Pkg, p.warnings, err = p.layout.ReadZarfYAML(p.layout.ZarfYAML)
+ if err != nil {
return err
}
+
+ // Sign the package if a key has been provided
+ if p.cfg.PublishOpts.SigningKeyPath != "" {
+ if err := p.layout.SignPackage(p.cfg.PublishOpts.SigningKeyPath, p.cfg.PublishOpts.SigningKeyPassword); err != nil {
+ return err
+ }
+ }
}
// Get a reference to the registry for this package
@@ -77,20 +92,13 @@ func (p *Packager) Publish() (err error) {
if p.cfg.CreateOpts.IsSkeleton {
platform = zoci.PlatformForSkeleton()
} else {
- platform = oci.PlatformForArch(p.arch)
+ platform = oci.PlatformForArch(p.cfg.Pkg.Build.Architecture)
}
remote, err := zoci.NewRemote(ref, platform)
if err != nil {
return err
}
- // Sign the package if a key has been provided
- if p.cfg.PublishOpts.SigningKeyPath != "" {
- if err := p.signPackage(p.cfg.PublishOpts.SigningKeyPath, p.cfg.PublishOpts.SigningKeyPassword); err != nil {
- return err
- }
- }
-
message.HeaderInfof("📦 PACKAGE PUBLISH %s:%s", p.cfg.Pkg.Metadata.Name, ref)
// Publish the package/skeleton to the registry
diff --git a/src/pkg/packager/remove.go b/src/pkg/packager/remove.go
index 2aaee0c3af..2f7bf05e01 100644
--- a/src/pkg/packager/remove.go
+++ b/src/pkg/packager/remove.go
@@ -15,6 +15,7 @@ import (
"github.com/defenseunicorns/zarf/src/internal/packager/helm"
"github.com/defenseunicorns/zarf/src/pkg/cluster"
"github.com/defenseunicorns/zarf/src/pkg/message"
+ "github.com/defenseunicorns/zarf/src/pkg/packager/actions"
"github.com/defenseunicorns/zarf/src/pkg/packager/sources"
"github.com/defenseunicorns/zarf/src/pkg/utils/helpers"
"github.com/defenseunicorns/zarf/src/types"
@@ -38,9 +39,12 @@ func (p *Packager) Remove() (err error) {
if err = p.source.LoadPackageMetadata(p.layout, false, false); err != nil {
return err
}
- if err = p.readZarfYAML(p.layout.ZarfYAML); err != nil {
+
+ p.cfg.Pkg, p.warnings, err = p.layout.ReadZarfYAML(p.layout.ZarfYAML)
+ if err != nil {
return err
}
+
p.filterComponents()
packageName = p.cfg.Pkg.Metadata.Name
@@ -124,12 +128,12 @@ func (p *Packager) removeComponent(deployedPackage *types.DeployedPackage, deplo
onRemove := c.Actions.OnRemove
onFailure := func() {
- if err := p.runActions(onRemove.Defaults, onRemove.OnFailure, nil); err != nil {
+ if err := actions.Run(p.cfg, onRemove.Defaults, onRemove.OnFailure, nil); err != nil {
message.Debugf("Unable to run the failure action: %s", err)
}
}
- if err := p.runActions(onRemove.Defaults, onRemove.Before, nil); err != nil {
+ if err := actions.Run(p.cfg, onRemove.Defaults, onRemove.Before, nil); err != nil {
onFailure()
return nil, fmt.Errorf("unable to run the before action for component (%s): %w", c.Name, err)
}
@@ -158,12 +162,12 @@ func (p *Packager) removeComponent(deployedPackage *types.DeployedPackage, deplo
p.updatePackageSecret(*deployedPackage)
}
- if err := p.runActions(onRemove.Defaults, onRemove.After, nil); err != nil {
+ if err := actions.Run(p.cfg, onRemove.Defaults, onRemove.After, nil); err != nil {
onFailure()
return deployedPackage, fmt.Errorf("unable to run the after action: %w", err)
}
- if err := p.runActions(onRemove.Defaults, onRemove.OnSuccess, nil); err != nil {
+ if err := actions.Run(p.cfg, onRemove.Defaults, onRemove.OnSuccess, nil); err != nil {
onFailure()
return deployedPackage, fmt.Errorf("unable to run the success action: %w", err)
}
diff --git a/src/pkg/packager/sources/oci.go b/src/pkg/packager/sources/oci.go
index a81e5c16f1..b9a0857c9d 100644
--- a/src/pkg/packager/sources/oci.go
+++ b/src/pkg/packager/sources/oci.go
@@ -201,18 +201,11 @@ func (s *OCISource) Collect(dir string) (string, error) {
spinner.Success()
// TODO (@Noxsios) remove the suffix check at v1.0.0
- isSkeleton := pkg.Build.Architecture == "skeleton" || strings.HasSuffix(s.Repo().Reference.Reference, zoci.SkeletonArch)
- name := NameFromMetadata(&pkg, isSkeleton)
+ isSkeleton := pkg.Build.Architecture == zoci.SkeletonArch || strings.HasSuffix(s.Repo().Reference.Reference, zoci.SkeletonArch)
+ name := fmt.Sprintf("%s%s", NameFromMetadata(&pkg, isSkeleton), PkgSuffix(pkg.Metadata.Uncompressed))
dstTarball := filepath.Join(dir, name)
- // honor uncompressed flag
- if pkg.Metadata.Uncompressed {
- dstTarball = dstTarball + ".tar"
- } else {
- dstTarball = dstTarball + ".tar.zst"
- }
-
allTheLayers, err := filepath.Glob(filepath.Join(tmp, "*"))
if err != nil {
return "", err
diff --git a/src/pkg/packager/sources/utils.go b/src/pkg/packager/sources/utils.go
index 383cc262cd..1347ee4127 100644
--- a/src/pkg/packager/sources/utils.go
+++ b/src/pkg/packager/sources/utils.go
@@ -14,6 +14,7 @@ import (
"github.com/defenseunicorns/zarf/src/config"
"github.com/defenseunicorns/zarf/src/pkg/layout"
"github.com/defenseunicorns/zarf/src/pkg/utils"
+ "github.com/defenseunicorns/zarf/src/pkg/zoci"
"github.com/defenseunicorns/zarf/src/types"
goyaml "github.com/goccy/go-yaml"
"github.com/mholt/archiver/v3"
@@ -112,7 +113,7 @@ func NameFromMetadata(pkg *types.ZarfPackage, isSkeleton bool) string {
arch := config.GetArch(pkg.Metadata.Architecture, pkg.Build.Architecture)
if isSkeleton {
- arch = "skeleton"
+ arch = zoci.SkeletonArch
}
switch pkg.Kind {
@@ -125,10 +126,26 @@ func NameFromMetadata(pkg *types.ZarfPackage, isSkeleton bool) string {
}
if pkg.Build.Differential {
- name = fmt.Sprintf("%s-differential-%s", name, pkg.Metadata.Version)
+ name = fmt.Sprintf("%s-%s-differential-%s", name, pkg.Build.DifferentialPackageVersion, pkg.Metadata.Version)
} else if pkg.Metadata.Version != "" {
name = fmt.Sprintf("%s-%s", name, pkg.Metadata.Version)
}
return name
}
+
+// GetInitPackageName returns the formatted name of the init package.
+func GetInitPackageName() string {
+ // No package has been loaded yet so lookup GetArch() with no package info
+ arch := config.GetArch()
+ return fmt.Sprintf("zarf-init-%s-%s.tar.zst", arch, config.CLIVersion)
+}
+
+// PkgSuffix returns a package suffix based on whether it is uncompressed or not.
+func PkgSuffix(uncompressed bool) (suffix string) {
+ suffix = ".tar.zst"
+ if uncompressed {
+ suffix = ".tar"
+ }
+ return suffix
+}
diff --git a/src/pkg/packager/variables.go b/src/pkg/packager/variables.go
deleted file mode 100644
index 53f4e05df3..0000000000
--- a/src/pkg/packager/variables.go
+++ /dev/null
@@ -1,160 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
-
-// Package packager contains functions for interacting with, managing and deploying Zarf packages.
-package packager
-
-import (
- "fmt"
- "regexp"
-
- "github.com/defenseunicorns/zarf/src/config"
- "github.com/defenseunicorns/zarf/src/config/lang"
- "github.com/defenseunicorns/zarf/src/pkg/interactive"
- "github.com/defenseunicorns/zarf/src/pkg/utils"
- "github.com/defenseunicorns/zarf/src/types"
-)
-
-// ReloadComponentTemplate appends ###ZARF_COMPONENT_NAME### for the component, assigns value, and reloads
-// Any instance of ###ZARF_COMPONENT_NAME### within a component will be replaced with that components name
-func ReloadComponentTemplate(component *types.ZarfComponent) error {
- mappings := map[string]string{}
- mappings[types.ZarfComponentName] = component.Name
- err := utils.ReloadYamlTemplate(component, mappings)
- if err != nil {
- return err
- }
- return nil
-}
-
-// ReloadComponentTemplatesInPackage appends ###ZARF_COMPONENT_NAME### for each component, assigns value, and reloads
-func ReloadComponentTemplatesInPackage(zarfPackage *types.ZarfPackage) error {
- // iterate through components to and find all ###ZARF_COMPONENT_NAME, assign to component Name and value
- for i := range zarfPackage.Components {
- if err := ReloadComponentTemplate(&zarfPackage.Components[i]); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-// fillActiveTemplate handles setting the active variables and reloading the base template.
-func (p *Packager) fillActiveTemplate() error {
- templateMap := map[string]string{}
-
- promptAndSetTemplate := func(templatePrefix string, deprecated bool) error {
- yamlTemplates, err := utils.FindYamlTemplates(&p.cfg.Pkg, templatePrefix, "###")
- if err != nil {
- return err
- }
-
- for key := range yamlTemplates {
- if deprecated {
- p.warnings = append(p.warnings, fmt.Sprintf(lang.PkgValidateTemplateDeprecation, key, key, key))
- }
-
- _, present := p.cfg.CreateOpts.SetVariables[key]
- if !present && !config.CommonOptions.Confirm {
- setVal, err := interactive.PromptVariable(types.ZarfPackageVariable{
- Name: key,
- })
-
- if err == nil {
- p.cfg.CreateOpts.SetVariables[key] = setVal
- } else {
- return err
- }
- } else if !present {
- return fmt.Errorf("template '%s' must be '--set' when using the '--confirm' flag", key)
- }
- }
-
- for key, value := range p.cfg.CreateOpts.SetVariables {
- templateMap[fmt.Sprintf("%s%s###", templatePrefix, key)] = value
- }
-
- return nil
- }
-
- // update the component templates on the package
- err := ReloadComponentTemplatesInPackage(&p.cfg.Pkg)
- if err != nil {
- return err
- }
-
- if err := promptAndSetTemplate(types.ZarfPackageTemplatePrefix, false); err != nil {
- return err
- }
- // [DEPRECATION] Set the Package Variable syntax as well for backward compatibility
- if err := promptAndSetTemplate(types.ZarfPackageVariablePrefix, true); err != nil {
- return err
- }
-
- // Add special variable for the current package architecture
- templateMap[types.ZarfPackageArch] = p.arch
-
- return utils.ReloadYamlTemplate(&p.cfg.Pkg, templateMap)
-}
-
-// setVariableMapInConfig handles setting the active variables used to template component files.
-func (p *Packager) setVariableMapInConfig() error {
- for name, value := range p.cfg.PkgOpts.SetVariables {
- p.setVariableInConfig(name, value, false, false, "")
- }
-
- for _, variable := range p.cfg.Pkg.Variables {
- _, present := p.cfg.SetVariableMap[variable.Name]
-
- // Variable is present, no need to continue checking
- if present {
- p.cfg.SetVariableMap[variable.Name].Sensitive = variable.Sensitive
- p.cfg.SetVariableMap[variable.Name].AutoIndent = variable.AutoIndent
- p.cfg.SetVariableMap[variable.Name].Type = variable.Type
- if err := p.checkVariablePattern(variable.Name, variable.Pattern); err != nil {
- return err
- }
- continue
- }
-
- // First set default (may be overridden by prompt)
- p.setVariableInConfig(variable.Name, variable.Default, variable.Sensitive, variable.AutoIndent, variable.Type)
-
- // Variable is set to prompt the user
- if variable.Prompt && !config.CommonOptions.Confirm {
- // Prompt the user for the variable
- val, err := interactive.PromptVariable(variable)
-
- if err != nil {
- return fmt.Errorf("unable to get value from prompt: %w", err)
- }
-
- p.setVariableInConfig(variable.Name, val, variable.Sensitive, variable.AutoIndent, variable.Type)
- }
-
- if err := p.checkVariablePattern(variable.Name, variable.Pattern); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (p *Packager) setVariableInConfig(name, value string, sensitive bool, autoIndent bool, varType types.VariableType) {
- p.cfg.SetVariableMap[name] = &types.ZarfSetVariable{
- Name: name,
- Value: value,
- Sensitive: sensitive,
- AutoIndent: autoIndent,
- Type: varType,
- }
-}
-
-// checkVariablePattern checks to see if a current variable is set to a value that matches its pattern
-func (p *Packager) checkVariablePattern(name, pattern string) error {
- if regexp.MustCompile(pattern).MatchString(p.cfg.SetVariableMap[name].Value) {
- return nil
- }
-
- return fmt.Errorf("provided value for variable %q does not match pattern \"%s\"", name, pattern)
-}
diff --git a/src/pkg/packager/variables/variables.go b/src/pkg/packager/variables/variables.go
new file mode 100644
index 0000000000..a2ab07a2e3
--- /dev/null
+++ b/src/pkg/packager/variables/variables.go
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
+
+// Package variables contains functions for working with variables within Zarf packages.
+package variables
+
+import (
+ "github.com/defenseunicorns/zarf/src/config"
+ "github.com/defenseunicorns/zarf/src/pkg/interactive"
+ "github.com/defenseunicorns/zarf/src/types"
+)
+
+// SetVariableMapInConfig handles setting the active variables used to template component files.
+func SetVariableMapInConfig(cfg *types.PackagerConfig) error {
+ for name, value := range cfg.PkgOpts.SetVariables {
+ cfg.SetVariable(name, value, false, false, "")
+ }
+
+ for _, variable := range cfg.Pkg.Variables {
+ _, present := cfg.SetVariableMap[variable.Name]
+
+ // Variable is present, no need to continue checking
+ if present {
+ cfg.SetVariableMap[variable.Name].Sensitive = variable.Sensitive
+ cfg.SetVariableMap[variable.Name].AutoIndent = variable.AutoIndent
+ cfg.SetVariableMap[variable.Name].Type = variable.Type
+ if err := cfg.CheckVariablePattern(variable.Name, variable.Pattern); err != nil {
+ return err
+ }
+ continue
+ }
+
+ // First set default (may be overridden by prompt)
+ cfg.SetVariable(variable.Name, variable.Default, variable.Sensitive, variable.AutoIndent, variable.Type)
+
+ // Variable is set to prompt the user
+ if variable.Prompt && !config.CommonOptions.Confirm {
+ // Prompt the user for the variable
+ val, err := interactive.PromptVariable(variable)
+
+ if err != nil {
+ return err
+ }
+
+ cfg.SetVariable(variable.Name, val, variable.Sensitive, variable.AutoIndent, variable.Type)
+ }
+
+ if err := cfg.CheckVariablePattern(variable.Name, variable.Pattern); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/src/pkg/packager/yaml.go b/src/pkg/packager/yaml.go
deleted file mode 100644
index ba38a31bf1..0000000000
--- a/src/pkg/packager/yaml.go
+++ /dev/null
@@ -1,123 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
-
-// Package packager contains functions for interacting with, managing and deploying Zarf packages.
-package packager
-
-import (
- "os"
- "runtime"
- "time"
-
- "github.com/defenseunicorns/zarf/src/config"
- "github.com/defenseunicorns/zarf/src/pkg/message"
- "github.com/defenseunicorns/zarf/src/pkg/packager/deprecated"
- "github.com/defenseunicorns/zarf/src/pkg/utils"
- "github.com/defenseunicorns/zarf/src/types"
-)
-
-// readZarfYAML reads a Zarf YAML file.
-func (p *Packager) readZarfYAML(path string) error {
- var warnings []string
-
- if err := utils.ReadYaml(path, &p.cfg.Pkg); err != nil {
- return err
- }
-
- if p.layout.IsLegacyLayout() {
- warning := "Detected deprecated package layout, migrating to new layout - support for this package will be dropped in v1.0.0"
- p.warnings = append(p.warnings, warning)
- }
-
- if len(p.cfg.Pkg.Build.Migrations) > 0 {
- for idx, component := range p.cfg.Pkg.Components {
- // Handle component configuration deprecations
- p.cfg.Pkg.Components[idx], warnings = deprecated.MigrateComponent(p.cfg.Pkg.Build, component)
- p.warnings = append(p.warnings, warnings...)
- }
- }
-
- p.arch = config.GetArch(p.cfg.Pkg.Metadata.Architecture, p.cfg.Pkg.Build.Architecture)
-
- return nil
-}
-
-// filterComponents removes components not matching the current OS if filterByOS is set.
-func (p *Packager) filterComponents() {
- // Filter each component to only compatible platforms.
- filteredComponents := []types.ZarfComponent{}
- for _, component := range p.cfg.Pkg.Components {
- // Ignore only filters that are empty
- var validArch, validOS bool
-
- // Test for valid architecture
- if component.Only.Cluster.Architecture == "" || component.Only.Cluster.Architecture == p.arch {
- validArch = true
- } else {
- message.Debugf("Skipping component %s, %s is not compatible with %s", component.Name, component.Only.Cluster.Architecture, p.arch)
- }
-
- // Test for a valid OS
- if component.Only.LocalOS == "" || component.Only.LocalOS == runtime.GOOS {
- validOS = true
- } else {
- message.Debugf("Skipping component %s, %s is not compatible with %s", component.Name, component.Only.LocalOS, runtime.GOOS)
- }
-
- // If both the OS and architecture are valid, add the component to the filtered list
- if validArch && validOS {
- filteredComponents = append(filteredComponents, component)
- }
- }
- // Update the active package with the filtered components.
- p.cfg.Pkg.Components = filteredComponents
-}
-
-// writeYaml adds build information and writes the config to the temp directory.
-func (p *Packager) writeYaml() error {
- now := time.Now()
- // Just use $USER env variable to avoid CGO issue.
- // https://groups.google.com/g/golang-dev/c/ZFDDX3ZiJ84.
- // Record the name of the user creating the package.
- if runtime.GOOS == "windows" {
- p.cfg.Pkg.Build.User = os.Getenv("USERNAME")
- } else {
- p.cfg.Pkg.Build.User = os.Getenv("USER")
- }
- hostname, hostErr := os.Hostname()
-
- // Normalize these for the package confirmation.
- p.cfg.Pkg.Metadata.Architecture = p.arch
- p.cfg.Pkg.Build.Architecture = p.arch
-
- if p.cfg.CreateOpts.IsSkeleton {
- p.cfg.Pkg.Build.Architecture = "skeleton"
- }
-
- // Record the time of package creation.
- p.cfg.Pkg.Build.Timestamp = now.Format(time.RFC1123Z)
-
- // Record the Zarf Version the CLI was built with.
- p.cfg.Pkg.Build.Version = config.CLIVersion
-
- if hostErr == nil {
- // Record the hostname of the package creation terminal.
- p.cfg.Pkg.Build.Terminal = hostname
- }
-
- // Record the migrations that will be run on the package.
- p.cfg.Pkg.Build.Migrations = []string{
- deprecated.ScriptsToActionsMigrated,
- deprecated.PluralizeSetVariable,
- }
-
- // Record the flavor of Zarf used to build this package (if any).
- p.cfg.Pkg.Build.Flavor = p.cfg.CreateOpts.Flavor
-
- p.cfg.Pkg.Build.RegistryOverrides = p.cfg.CreateOpts.RegistryOverrides
-
- // Record the latest version of Zarf without breaking changes to the package structure.
- p.cfg.Pkg.Build.LastNonBreakingVersion = deprecated.LastNonBreakingVersion
-
- return utils.WriteYaml(p.layout.ZarfYAML, p.cfg.Pkg, 0400)
-}
diff --git a/src/pkg/utils/helpers/io.go b/src/pkg/utils/helpers/io.go
index e0b1879f86..996d643e52 100644
--- a/src/pkg/utils/helpers/io.go
+++ b/src/pkg/utils/helpers/io.go
@@ -5,11 +5,12 @@
package helpers
const (
+ // ReadUser is used for any internal file to be read only
+ ReadUser = 0400
// ReadWriteUser is used for any internal file not normally used by the end user or containing sensitive data
ReadWriteUser = 0600
// ReadAllWriteUser is used for any non sensitive file intended to be consumed by the end user
ReadAllWriteUser = 0644
-
// ReadWriteExecuteUser is used for any directory or executable not normally used by the end user or containing sensitive data
ReadWriteExecuteUser = 0700
// ReadExecuteAllWriteUser is used for any non sensitive directory or executable intended to be consumed by the end user
diff --git a/src/pkg/utils/io.go b/src/pkg/utils/io.go
index 8dad0da6ba..0e158cf1e0 100755
--- a/src/pkg/utils/io.go
+++ b/src/pkg/utils/io.go
@@ -47,9 +47,15 @@ func MakeTempDir(basePath string) (string, error) {
return "", err
}
}
+
tmp, err := os.MkdirTemp(basePath, tmpPathPrefix)
+ if err != nil {
+ return "", err
+ }
+
message.Debug("Using temporary directory:", tmp)
- return tmp, err
+
+ return tmp, nil
}
// VerifyBinary returns true if binary is available.
diff --git a/src/pkg/utils/yaml.go b/src/pkg/utils/yaml.go
index fcf81c497c..f2c73d5f77 100644
--- a/src/pkg/utils/yaml.go
+++ b/src/pkg/utils/yaml.go
@@ -125,8 +125,8 @@ func AddRootHint(hints map[string]string, rootKey string, hintText string) map[s
// ReadYaml reads a yaml file and unmarshals it into a given config.
func ReadYaml(path string, destConfig any) error {
message.Debugf("Reading YAML at %s", path)
- file, err := os.ReadFile(path)
+ file, err := os.ReadFile(path)
if err != nil {
return err
}
@@ -148,7 +148,6 @@ func WriteYaml(path string, srcConfig any, perm fs.FileMode) error {
// ReloadYamlTemplate marshals a given config, replaces strings and unmarshals it back.
func ReloadYamlTemplate(config any, mappings map[string]string) error {
text, err := goyaml.Marshal(config)
-
if err != nil {
return err
}
@@ -172,9 +171,8 @@ func FindYamlTemplates(config any, prefix string, suffix string) (map[string]str
mappings := map[string]string{}
text, err := goyaml.Marshal(config)
-
if err != nil {
- return mappings, err
+ return nil, err
}
// Find all strings that are between the given prefix and suffix
diff --git a/src/test/e2e/03_deprecations_test.go b/src/test/e2e/03_deprecations_test.go
index 808a5b25af..9d1c96b0c9 100644
--- a/src/test/e2e/03_deprecations_test.go
+++ b/src/test/e2e/03_deprecations_test.go
@@ -81,7 +81,7 @@ func TestDeprecatedSetAndPackageVariables(t *testing.T) {
// Check that the command still errors out
stdOut, stdErr, err := e2e.Zarf("package", "create", testPackageDirPath, outputFlag, "--confirm")
require.Error(t, err, stdOut, stdErr)
- require.Contains(t, stdErr, "template 'ECHO' must be '--set'")
+ require.Contains(t, stdErr, "template \"ECHO\" must be '--set'")
// Check that the command displays a warning on create
stdOut, stdErr, err = e2e.Zarf("package", "create", testPackageDirPath, outputFlag, "--confirm", "--set", "ECHO=Zarf-The-Axolotl")
diff --git a/src/test/e2e/08_create_differential_test.go b/src/test/e2e/08_create_differential_test.go
index 78f4d0ed81..042ed2e185 100644
--- a/src/test/e2e/08_create_differential_test.go
+++ b/src/test/e2e/08_create_differential_test.go
@@ -9,6 +9,7 @@ import (
"path/filepath"
"testing"
+ "github.com/defenseunicorns/zarf/src/config/lang"
"github.com/defenseunicorns/zarf/src/pkg/layout"
"github.com/defenseunicorns/zarf/src/pkg/utils"
"github.com/defenseunicorns/zarf/src/types"
@@ -16,14 +17,14 @@ import (
"github.com/stretchr/testify/require"
)
-// TestCreateDifferential creates several differential packages and ensures the already built images and repos and not included in the new package
+// TestCreateDifferential creates several differential packages and ensures the reference package images and repos are not included in the new package.
func TestCreateDifferential(t *testing.T) {
t.Log("E2E: Test Differential Package Behavior")
tmpdir := t.TempDir()
packagePath := "src/test/packages/08-differential-package"
- packageName := "zarf-package-differential-package-amd64-v0.25.0.tar.zst"
- differentialPackageName := "zarf-package-differential-package-amd64-v0.25.0-differential-v0.26.0.tar.zst"
+ packageName := fmt.Sprintf("zarf-package-differential-package-%s-v0.25.0.tar.zst", e2e.Arch)
+ differentialPackageName := fmt.Sprintf("zarf-package-differential-package-%s-v0.25.0-differential-v0.26.0.tar.zst", e2e.Arch)
differentialFlag := fmt.Sprintf("--differential=%s", packageName)
// Build the package a first time
@@ -34,7 +35,7 @@ func TestCreateDifferential(t *testing.T) {
// Build the differential package without changing the version
_, stdErr, err = e2e.Zarf("package", "create", packagePath, "--set=PACKAGE_VERSION=v0.25.0", differentialFlag, "--confirm")
require.Error(t, err, "zarf package create should have errored when a differential package was being created without updating the package version number")
- require.Contains(t, stdErr, "unable to create a differential package with the same version")
+ require.Contains(t, e2e.StripMessageFormatting(stdErr), lang.PkgCreateErrDifferentialSameVersion)
// Build the differential package
stdOut, stdErr, err = e2e.Zarf("package", "create", packagePath, "--set=PACKAGE_VERSION=v0.26.0", differentialFlag, "--confirm")
diff --git a/src/test/e2e/20_zarf_init_test.go b/src/test/e2e/20_zarf_init_test.go
index fb1934561b..d8ae669721 100644
--- a/src/test/e2e/20_zarf_init_test.go
+++ b/src/test/e2e/20_zarf_init_test.go
@@ -70,7 +70,7 @@ func TestZarfInit(t *testing.T) {
require.NoError(t, err)
require.Contains(t, initStdErr, "an inventory of all software contained in this package")
- logText := e2e.GetLogFileContents(t, initStdErr)
+ logText := e2e.GetLogFileContents(t, e2e.StripMessageFormatting(initStdErr))
// Verify that any state secrets were not included in the log
state := types.ZarfState{}
diff --git a/src/test/e2e/24_variables_test.go b/src/test/e2e/24_variables_test.go
index eacd883f41..c0f546ad83 100644
--- a/src/test/e2e/24_variables_test.go
+++ b/src/test/e2e/24_variables_test.go
@@ -64,7 +64,7 @@ func TestVariables(t *testing.T) {
// Verify that the sensitive variable 'unicorn-land' was not printed to the screen
require.NotContains(t, stdErr, "unicorn-land")
- logText := e2e.GetLogFileContents(t, stdErr)
+ logText := e2e.GetLogFileContents(t, e2e.StripMessageFormatting(stdErr))
// Verify that the sensitive variable 'unicorn-land' was not included in the log
require.NotContains(t, logText, "unicorn-land")
diff --git a/src/test/e2e/51_oci_compose_test.go b/src/test/e2e/51_oci_compose_test.go
index 4ad8769d8e..4412e13226 100644
--- a/src/test/e2e/51_oci_compose_test.go
+++ b/src/test/e2e/51_oci_compose_test.go
@@ -19,6 +19,7 @@ import (
"github.com/defenseunicorns/zarf/src/types"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
+ corev1 "k8s.io/api/core/v1"
"oras.land/oras-go/v2/registry"
)
@@ -71,6 +72,11 @@ func (suite *SkeletonSuite) Test_0_Publish_Skeletons() {
suite.NoError(err)
suite.Contains(stdErr, "Published "+ref)
+ composable := filepath.Join("src", "test", "packages", "09-composable-packages")
+ _, stdErr, err = e2e.Zarf("package", "publish", composable, "oci://"+ref, "--insecure")
+ suite.NoError(err)
+ suite.Contains(stdErr, "Published "+ref)
+
_, stdErr, err = e2e.Zarf("package", "publish", importEverything, "oci://"+ref, "--insecure")
suite.NoError(err)
suite.Contains(stdErr, "Published "+ref)
@@ -86,6 +92,9 @@ func (suite *SkeletonSuite) Test_0_Publish_Skeletons() {
_, _, err = e2e.Zarf("package", "pull", "oci://"+ref+"/big-bang-min:2.10.0", "-o", "build", "--insecure", "-a", "skeleton")
suite.NoError(err)
+
+ _, _, err = e2e.Zarf("package", "pull", "oci://"+ref+"/test-compose-package:0.0.1", "-o", "build", "--insecure", "-a", "skeleton")
+ suite.NoError(err)
}
func (suite *SkeletonSuite) Test_1_Compose_Everything_Inception() {
@@ -122,6 +131,7 @@ func (suite *SkeletonSuite) Test_2_FilePaths() {
filepath.Join("build", fmt.Sprintf("zarf-package-importception-%s-0.0.1.tar.zst", e2e.Arch)),
filepath.Join("build", "zarf-package-helm-charts-skeleton-0.0.1.tar.zst"),
filepath.Join("build", "zarf-package-big-bang-min-skeleton-2.10.0.tar.zst"),
+ filepath.Join("build", "zarf-package-test-compose-package-skeleton-0.0.1.tar.zst"),
}
for _, pkgTar := range pkgTars {
@@ -134,6 +144,24 @@ func (suite *SkeletonSuite) Test_2_FilePaths() {
suite.NoError(err)
suite.DirExists(unpacked)
+ // Verify skeleton contains kustomize-generated manifests.
+ if strings.HasSuffix(pkgTar, "zarf-package-test-compose-package-skeleton-0.0.1.tar.zst") {
+ kustomizeGeneratedManifests := []string{
+ "kustomization-connect-service-0.yaml",
+ "kustomization-connect-service-1.yaml",
+ "kustomization-connect-service-two-0.yaml",
+ }
+ manifestDir := filepath.Join(unpacked, "components", "test-compose-package", "manifests")
+ for _, manifest := range kustomizeGeneratedManifests {
+ manifestPath := filepath.Join(manifestDir, manifest)
+ suite.FileExists(manifestPath, "expected to find kustomize-generated manifest: %q", manifestPath)
+ var configMap corev1.ConfigMap
+ err := utils.ReadYaml(manifestPath, &configMap)
+ suite.NoError(err)
+ suite.Equal("ConfigMap", configMap.Kind, "expected manifest %q to be of kind ConfigMap", manifestPath)
+ }
+ }
+
err = utils.ReadYaml(filepath.Join(unpacked, layout.ZarfYAML), &pkg)
suite.NoError(err)
suite.NotNil(pkg)
diff --git a/src/test/packages/09-composable-packages/zarf.yaml b/src/test/packages/09-composable-packages/zarf.yaml
index 21e40e5df2..cf121120e6 100644
--- a/src/test/packages/09-composable-packages/zarf.yaml
+++ b/src/test/packages/09-composable-packages/zarf.yaml
@@ -2,6 +2,7 @@ kind: ZarfPackageConfig
metadata:
name: test-compose-package
description: A contrived example for podinfo using many Zarf primitives for compose testing
+ version: 0.0.1
components:
- name: test-compose-package
diff --git a/src/types/package.go b/src/types/package.go
index 0ec8f58a23..7a4e91a503 100644
--- a/src/types/package.go
+++ b/src/types/package.go
@@ -24,6 +24,21 @@ type ZarfPackage struct {
Variables []ZarfPackageVariable `json:"variables,omitempty" jsonschema:"description=Variable template values applied on deploy for K8s resources"`
}
+// IsInitConfig returns whether a Zarf package is an init config.
+func (pkg ZarfPackage) IsInitConfig() bool {
+ return pkg.Kind == ZarfInitConfig
+}
+
+// IsSBOMAble checks if a package has contents that an SBOM can be created on (i.e. images, files, or data injections).
+func (pkg ZarfPackage) IsSBOMAble() bool {
+ for _, c := range pkg.Components {
+ if len(c.Images) > 0 || len(c.Files) > 0 || len(c.DataInjections) > 0 {
+ return true
+ }
+ }
+ return false
+}
+
// ZarfMetadata lists information about the current ZarfPackage.
type ZarfMetadata struct {
Name string `json:"name" jsonschema:"description=Name to identify this Zarf package,pattern=^[a-z0-9\\-]*[a-z0-9]$"`
@@ -43,17 +58,18 @@ type ZarfMetadata struct {
// ZarfBuildData is written during the packager.Create() operation to track details of the created package.
type ZarfBuildData struct {
- Terminal string `json:"terminal" jsonschema:"description=The machine name that created this package"`
- User string `json:"user" jsonschema:"description=The username who created this package"`
- Architecture string `json:"architecture" jsonschema:"description=The architecture this package was created on"`
- Timestamp string `json:"timestamp" jsonschema:"description=The timestamp when this package was created"`
- Version string `json:"version" jsonschema:"description=The version of Zarf used to build this package"`
- Migrations []string `json:"migrations,omitempty" jsonschema:"description=Any migrations that have been run on this package"`
- Differential bool `json:"differential,omitempty" jsonschema:"description=Whether this package was created with differential components"`
- RegistryOverrides map[string]string `json:"registryOverrides,omitempty" jsonschema:"description=Any registry domains that were overridden on package create when pulling images"`
- DifferentialMissing []string `json:"differentialMissing,omitempty" jsonschema:"description=List of components that were not included in this package due to differential packaging"`
- LastNonBreakingVersion string `json:"lastNonBreakingVersion,omitempty" jsonschema:"description=The minimum version of Zarf that does not have breaking package structure changes"`
- Flavor string `json:"flavor,omitempty" jsonschema:"description=The flavor of Zarf used to build this package"`
+ Terminal string `json:"terminal" jsonschema:"description=The machine name that created this package"`
+ User string `json:"user" jsonschema:"description=The username who created this package"`
+ Architecture string `json:"architecture" jsonschema:"description=The architecture this package was created on"`
+ Timestamp string `json:"timestamp" jsonschema:"description=The timestamp when this package was created"`
+ Version string `json:"version" jsonschema:"description=The version of Zarf used to build this package"`
+ Migrations []string `json:"migrations,omitempty" jsonschema:"description=Any migrations that have been run on this package"`
+ RegistryOverrides map[string]string `json:"registryOverrides,omitempty" jsonschema:"description=Any registry domains that were overridden on package create when pulling images"`
+ Differential bool `json:"differential,omitempty" jsonschema:"description=Whether this package was created with differential components"`
+ DifferentialPackageVersion string `json:"differentialPackageVersion,omitempty" jsonschema:"description=Version of a previously built package used as the basis for creating this differential package"`
+ DifferentialMissing []string `json:"differentialMissing,omitempty" jsonschema:"description=List of components that were not included in this package due to differential packaging"`
+ LastNonBreakingVersion string `json:"lastNonBreakingVersion,omitempty" jsonschema:"description=The minimum version of Zarf that does not have breaking package structure changes"`
+ Flavor string `json:"flavor,omitempty" jsonschema:"description=The flavor of Zarf used to build this package"`
}
// ZarfPackageVariable are variables that can be used to dynamically template K8s resources.
diff --git a/src/types/packager.go b/src/types/packager.go
index 9e945e1472..857683a529 100644
--- a/src/types/packager.go
+++ b/src/types/packager.go
@@ -4,6 +4,11 @@
// Package types contains all the types used by Zarf.
package types
+import (
+ "fmt"
+ "regexp"
+)
+
// PackagerConfig is the main struct that the packager uses to hold high-level options.
type PackagerConfig struct {
// CreateOpts tracks the user-defined options used to create the package
@@ -45,3 +50,22 @@ type PackagerConfig struct {
// Variables set by the user
SetVariableMap map[string]*ZarfSetVariable
}
+
+// SetVariable sets a value for a variable in PackagerConfig.SetVariableMap.
+func (cfg *PackagerConfig) SetVariable(name, value string, sensitive bool, autoIndent bool, varType VariableType) {
+ cfg.SetVariableMap[name] = &ZarfSetVariable{
+ Name: name,
+ Value: value,
+ Sensitive: sensitive,
+ AutoIndent: autoIndent,
+ Type: varType,
+ }
+}
+
+// CheckVariablePattern checks to see if a variable is set to a value that matches its pattern.
+func (cfg *PackagerConfig) CheckVariablePattern(name, pattern string) error {
+ if regexp.MustCompile(pattern).MatchString(cfg.SetVariableMap[name].Value) {
+ return nil
+ }
+ return fmt.Errorf("provided value for variable %q does not match pattern \"%s\"", name, pattern)
+}
diff --git a/src/types/runtime.go b/src/types/runtime.go
index 4b69491a08..1ee40f91a0 100644
--- a/src/types/runtime.go
+++ b/src/types/runtime.go
@@ -111,20 +111,20 @@ type ZarfInitOptions struct {
// ZarfCreateOptions tracks the user-defined options used to create the package.
type ZarfCreateOptions struct {
- SkipSBOM bool `json:"skipSBOM" jsonschema:"description=Disable the generation of SBOM materials during package creation"`
- BaseDir string `json:"baseDir" jsonschema:"description=Location where the Zarf package will be created from"`
- Output string `json:"output" jsonschema:"description=Location where the finalized Zarf package will be placed"`
- ViewSBOM bool `json:"sbom" jsonschema:"description=Whether to pause to allow for viewing the SBOM post-creation"`
- SBOMOutputDir string `json:"sbomOutput" jsonschema:"description=Location to output an SBOM into after package creation"`
- SetVariables map[string]string `json:"setVariables" jsonschema:"description=Key-Value map of variable names and their corresponding values that will be used to template against the Zarf package being used"`
- MaxPackageSizeMB int `json:"maxPackageSizeMB" jsonschema:"description=Size of chunks to use when splitting a zarf package into multiple files in megabytes"`
- SigningKeyPath string `json:"signingKeyPath" jsonschema:"description=Location where the private key component of a cosign key-pair can be found"`
- SigningKeyPassword string `json:"signingKeyPassword" jsonschema:"description=Password to the private key signature file that will be used to sigh the created package"`
- DifferentialData DifferentialData `json:"differential" jsonschema:"description=A package's differential images and git repositories from a referenced previously built package"`
- RegistryOverrides map[string]string `json:"registryOverrides" jsonschema:"description=A map of domains to override on package create when pulling images"`
- Flavor string `json:"flavor" jsonschema:"description=An optional variant that controls which components will be included in a package"`
- IsSkeleton bool `json:"isSkeleton" jsonschema:"description=Whether to create a skeleton package"`
- NoYOLO bool `json:"noYOLO" jsonschema:"description=Whether to create a YOLO package"`
+ SkipSBOM bool `json:"skipSBOM" jsonschema:"description=Disable the generation of SBOM materials during package creation"`
+ BaseDir string `json:"baseDir" jsonschema:"description=Location where the Zarf package will be created from"`
+ Output string `json:"output" jsonschema:"description=Location where the finalized Zarf package will be placed"`
+ ViewSBOM bool `json:"sbom" jsonschema:"description=Whether to pause to allow for viewing the SBOM post-creation"`
+ SBOMOutputDir string `json:"sbomOutput" jsonschema:"description=Location to output an SBOM into after package creation"`
+ SetVariables map[string]string `json:"setVariables" jsonschema:"description=Key-Value map of variable names and their corresponding values that will be used to template against the Zarf package being used"`
+ MaxPackageSizeMB int `json:"maxPackageSizeMB" jsonschema:"description=Size of chunks to use when splitting a zarf package into multiple files in megabytes"`
+ SigningKeyPath string `json:"signingKeyPath" jsonschema:"description=Location where the private key component of a cosign key-pair can be found"`
+ SigningKeyPassword string `json:"signingKeyPassword" jsonschema:"description=Password to the private key signature file that will be used to sigh the created package"`
+ DifferentialPackagePath string `json:"differentialPackagePath" jsonschema:"description=Path to a previously built package used as the basis for creating a differential package"`
+ RegistryOverrides map[string]string `json:"registryOverrides" jsonschema:"description=A map of domains to override on package create when pulling images"`
+ Flavor string `json:"flavor" jsonschema:"description=An optional variant that controls which components will be included in a package"`
+ IsSkeleton bool `json:"isSkeleton" jsonschema:"description=Whether to create a skeleton package"`
+ NoYOLO bool `json:"noYOLO" jsonschema:"description=Whether to create a YOLO package"`
}
// ZarfSplitPackageData contains info about a split package.
@@ -154,8 +154,7 @@ type ConnectStrings map[string]ConnectString
// DifferentialData contains image and repository information about the package a Differential Package is Based on.
type DifferentialData struct {
- DifferentialPackagePath string
- DifferentialPackageVersion string
DifferentialImages map[string]bool
DifferentialRepos map[string]bool
+ DifferentialPackageVersion string
}
diff --git a/zarf.schema.json b/zarf.schema.json
index f8e53e98e2..539081048a 100644
--- a/zarf.schema.json
+++ b/zarf.schema.json
@@ -118,10 +118,6 @@
"type": "array",
"description": "Any migrations that have been run on this package"
},
- "differential": {
- "type": "boolean",
- "description": "Whether this package was created with differential components"
- },
"registryOverrides": {
"patternProperties": {
".*": {
@@ -131,6 +127,14 @@
"type": "object",
"description": "Any registry domains that were overridden on package create when pulling images"
},
+ "differential": {
+ "type": "boolean",
+ "description": "Whether this package was created with differential components"
+ },
+ "differentialPackageVersion": {
+ "type": "string",
+ "description": "Version of a previously built package used as the basis for creating this differential package"
+ },
"differentialMissing": {
"items": {
"type": "string"