diff --git a/README.md b/README.md
index 1b8721d3..84cf6536 100644
--- a/README.md
+++ b/README.md
@@ -293,8 +293,7 @@ UDS CLI includes a vendored version of Zarf inside of its binary. To use Zarf, s
## Dev Mode
-> [!NOTE]
-> Dev mode is a BETA feature
+NOTE: Dev mode is a BETA feature
Dev mode facilitates faster dev cycles when developing and testing bundles
@@ -302,14 +301,18 @@ Dev mode facilitates faster dev cycles when developing and testing bundles
uds dev deploy |
```
-The `dev deploy` command performs the following operations
+The `dev deploy` command performs the following operations:
-- If local bundle: Creates Zarf packages for all local packages in a bundle
- - Creates the Zarf tarball in the same directory as the `zarf.yaml`
- - Will only create the Zarf tarball if one does not already exist
- - Ignores any `kind: ZarfInitConfig` packages in the bundle
- - Creates a bundle from the newly created Zarf packages
- Deploys the bundle in [YOLO](https://docs.zarf.dev/faq/#what-is-yolo-mode-and-why-would-i-use-it) mode, eliminating the need to do a `zarf init`
+ - Any `kind: ZarfInitConfig` packages in the bundle will be ignored
+- For local bundles:
+ - For local packages:
+ - Creates the Zarf tarball if one does not already exist or the `--force-create` flag can be used to force the creation of a new Zarf package
+ - The Zarf tarball is created in the same directory as the `zarf.yaml`
+ - The `--flavor` flag can be used to specify what flavor of a package you want to create (example: `--flavor podinfo=upstream` to specify the flavor for the `podinfo` package or `--flavor upstream` to specify the flavor for all the packages in the bundle)
+ - For remote packages:
+ - The `--ref` flag can be used to specify what package ref you want to deploy (example: `--ref podinfo=0.2.0`)
+ - Creates a bundle from the newly created Zarf packages
## Scan
diff --git a/hack/push-test-artifacts.sh b/hack/push-test-artifacts.sh
index 23c465ac..731875b1 100755
--- a/hack/push-test-artifacts.sh
+++ b/hack/push-test-artifacts.sh
@@ -12,8 +12,14 @@ set -e
cd ./../src/test/packages/nginx
zarf package create -o oci://ghcr.io/defenseunicorns/uds-cli --confirm -a amd64
zarf package create -o oci://ghcr.io/defenseunicorns/uds-cli --confirm -a arm64
+cd ./refs
+zarf package create -o oci://ghcr.io/defenseunicorns/uds-cli --confirm -a amd64
+zarf package create -o oci://ghcr.io/defenseunicorns/uds-cli --confirm -a arm64
-cd ../podinfo
+cd ../../podinfo
+zarf package create -o oci://ghcr.io/defenseunicorns/uds-cli --confirm -a amd64
+zarf package create -o oci://ghcr.io/defenseunicorns/uds-cli --confirm -a arm64
+cd ./refs
zarf package create -o oci://ghcr.io/defenseunicorns/uds-cli --confirm -a amd64
zarf package create -o oci://ghcr.io/defenseunicorns/uds-cli --confirm -a arm64
diff --git a/src/cmd/dev.go b/src/cmd/dev.go
index 4425791d..3fd5fdc3 100644
--- a/src/cmd/dev.go
+++ b/src/cmd/dev.go
@@ -5,6 +5,9 @@
package cmd
import (
+ "fmt"
+ "strings"
+
"github.com/defenseunicorns/pkg/helpers/v2"
"github.com/defenseunicorns/uds-cli/src/config"
"github.com/defenseunicorns/uds-cli/src/config/lang"
@@ -26,6 +29,7 @@ var devDeployCmd = &cobra.Command{
Long: lang.CmdDevDeployLong,
Run: func(_ *cobra.Command, args []string) {
config.Dev = true
+ config.CommonOptions.Confirm = true
// Get bundle source
src := ""
@@ -34,13 +38,24 @@ var devDeployCmd = &cobra.Command{
}
// Check if source is a local bundle
- localBundle := helpers.IsDir(src)
+ isLocalBundle := isLocalBundle(src)
+
+ // Validate flags
+ err := validateDevDeployFlags(isLocalBundle)
+ if err != nil {
+ message.Fatalf(err, "Failed to validate flags: %s", err.Error())
+ }
+
+ if isLocalBundle {
+ // Populate flavor map
+ err = populateFlavorMap()
+ if err != nil {
+ message.Fatalf(err, "Failed to populate flavor map: %s", err.Error())
+ }
- if localBundle {
// Create Bundle
setBundleFile(args)
- config.CommonOptions.Confirm = true
bundleCfg.CreateOpts.SourceDirectory = src
}
@@ -58,17 +73,13 @@ var devDeployCmd = &cobra.Command{
defer bndlClient.ClearPaths()
// Create dev bundle
- if localBundle {
+ if isLocalBundle {
// Check if local zarf packages need to be created
bndlClient.CreateZarfPkgs()
if err := bndlClient.Create(); err != nil {
message.Fatalf(err, "Failed to create bundle: %s", err.Error())
}
- }
-
- // Set dev source
- if localBundle {
bndlClient.SetDeploySource(src)
} else {
bundleCfg.DeployOpts.Source = src
@@ -79,11 +90,51 @@ var devDeployCmd = &cobra.Command{
},
}
+// isLocalBundle checks if the bundle source is a local bundle
+func isLocalBundle(src string) bool {
+ return helpers.IsDir(src) || strings.Contains(src, ".tar.zst")
+}
+
+// validateDevDeployFlags validates the flags for dev deploy
+func validateDevDeployFlags(isLocalBundle bool) error {
+ if !isLocalBundle {
+ //Throw error if trying to run with --flavor or --force-create flag with remote bundle
+ if len(bundleCfg.DevDeployOpts.Flavor) > 0 || bundleCfg.DevDeployOpts.ForceCreate {
+ return fmt.Errorf("Cannot use --flavor or --force-create flags with remote bundle")
+ }
+ }
+ return nil
+}
+
+// populateFlavorMap populates the flavor map based on the string input to the --flavor flag
+func populateFlavorMap() error {
+ if bundleCfg.DevDeployOpts.FlavorInput != "" {
+ bundleCfg.DevDeployOpts.Flavor = make(map[string]string)
+ flavorEntries := strings.Split(bundleCfg.DevDeployOpts.FlavorInput, ",")
+ for i, entry := range flavorEntries {
+ entrySplit := strings.Split(entry, "=")
+ if len(entrySplit) != 2 {
+ // check i==0 to check for invalid input (ex. key=value1,value2)
+ if len(entrySplit) == 1 && i == 0 {
+ bundleCfg.DevDeployOpts.Flavor = map[string]string{"": bundleCfg.DevDeployOpts.FlavorInput}
+ } else {
+ return fmt.Errorf("Invalid flavor entry: %s", entry)
+ }
+ } else {
+ bundleCfg.DevDeployOpts.Flavor[entrySplit[0]] = entrySplit[1]
+ }
+ }
+ }
+ return nil
+}
+
func init() {
initViper()
rootCmd.AddCommand(devCmd)
devCmd.AddCommand(devDeployCmd)
devDeployCmd.Flags().StringArrayVarP(&bundleCfg.DeployOpts.Packages, "packages", "p", []string{}, lang.CmdBundleDeployFlagPackages)
- devDeployCmd.Flags().BoolVarP(&config.CommonOptions.Confirm, "confirm", "c", false, lang.CmdBundleDeployFlagConfirm)
+ devDeployCmd.Flags().StringToStringVarP(&bundleCfg.DevDeployOpts.Ref, "ref", "r", map[string]string{}, lang.CmdBundleDeployFlagRef)
+ devDeployCmd.Flags().StringVarP(&bundleCfg.DevDeployOpts.FlavorInput, "flavor", "f", "", lang.CmdBundleCreateFlagFlavor)
+ devDeployCmd.Flags().BoolVar(&bundleCfg.DevDeployOpts.ForceCreate, "force-create", false, lang.CmdBundleCreateForceCreate)
devDeployCmd.Flags().StringToStringVar(&bundleCfg.DeployOpts.SetVariables, "set", nil, lang.CmdBundleDeployFlagSet)
}
diff --git a/src/cmd/dev_test.go b/src/cmd/dev_test.go
new file mode 100644
index 00000000..0a60ae99
--- /dev/null
+++ b/src/cmd/dev_test.go
@@ -0,0 +1,142 @@
+package cmd
+
+import (
+ "testing"
+
+ "github.com/defenseunicorns/uds-cli/src/types"
+ "github.com/stretchr/testify/require"
+)
+
+func TestValidateDevDeployFlags(t *testing.T) {
+ testCases := []struct {
+ name string
+ localBundle bool
+ DevDeployOpts types.BundleDevDeployOptions
+ expectError bool
+ }{
+ {
+ name: "Local bundle with --ref flag",
+ localBundle: true,
+ DevDeployOpts: types.BundleDevDeployOptions{
+ Ref: map[string]string{"some-key": "some-ref"},
+ },
+ expectError: true,
+ },
+ {
+ name: "Remote bundle with --ref flag",
+ localBundle: false,
+ DevDeployOpts: types.BundleDevDeployOptions{
+ Ref: map[string]string{"some-key": "some-ref"},
+ },
+ expectError: false,
+ },
+ {
+ name: "Local bundle with --flavor flag",
+ localBundle: true,
+ DevDeployOpts: types.BundleDevDeployOptions{
+ Flavor: map[string]string{"some-key": "some-flavor"},
+ },
+ expectError: false,
+ },
+ {
+ name: "Remote bundle with --flavor flag",
+ localBundle: false,
+ DevDeployOpts: types.BundleDevDeployOptions{
+ Flavor: map[string]string{"some-key": "some-flavor"},
+ },
+ expectError: true,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ bundleCfg.DevDeployOpts = tc.DevDeployOpts
+
+ err := validateDevDeployFlags(tc.localBundle)
+ if tc.expectError {
+ require.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ }
+ })
+ }
+}
+
+func TestIsLocalBundle(t *testing.T) {
+ testCases := []struct {
+ name string
+ src string
+ want bool
+ }{
+ {
+ name: "Test with directory",
+ src: "../cmd/",
+ want: true,
+ },
+ {
+ name: "Test with .tar.zst file",
+ src: "/path/to/file.tar.zst",
+ want: true,
+ },
+ {
+ name: "Test with other file",
+ src: "/path/to/file.txt",
+ want: false,
+ },
+ {
+ name: "Test with registry",
+ src: "ghcr.io/defenseunicorns/uds-cli/nginx",
+ want: false,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ got := isLocalBundle(tc.src)
+ require.Equal(t, tc.want, got)
+ })
+ }
+}
+
+func TestPopulateFlavorMap(t *testing.T) {
+ testCases := []struct {
+ name string
+ FlavorInput string
+ expect map[string]string
+ expectError bool
+ }{
+ {
+ name: "Test with valid flavor input",
+ FlavorInput: "key1=value1,key2=value2",
+ expect: map[string]string{"key1": "value1", "key2": "value2"},
+ },
+ {
+ name: "Test with single value",
+ FlavorInput: "value1",
+ expect: map[string]string{"": "value1"},
+ },
+ {
+ name: "Test with invalid flavor input",
+ FlavorInput: "key1=value1,key2",
+ expectError: true,
+ },
+ {
+ name: "Test with empty flavor input",
+ FlavorInput: "",
+ expect: nil,
+ },
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ bundleCfg.DevDeployOpts.FlavorInput = tc.FlavorInput
+ bundleCfg.DevDeployOpts.Flavor = nil
+ err := populateFlavorMap()
+ if tc.expectError {
+ require.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ require.Equal(t, tc.expect, bundleCfg.DevDeployOpts.Flavor)
+ }
+ })
+ }
+}
diff --git a/src/config/lang/lang.go b/src/config/lang/lang.go
index 904bbfad..77196558 100644
--- a/src/config/lang/lang.go
+++ b/src/config/lang/lang.go
@@ -29,6 +29,7 @@ const (
CmdBundleCreateFlagOutput = "Specify the output (an oci:// URL) for the created bundle"
CmdBundleCreateFlagSigningKey = "Path to private key file for signing bundles"
CmdBundleCreateFlagSigningKeyPassword = "Password to the private key file used for signing bundles"
+ CmdBundleCreateFlagFlavor = "Specify which zarf package flavor you want to use."
// bundle deploy
CmdBundleDeployShort = "Deploy a bundle from a local tarball or oci:// URL"
@@ -37,6 +38,7 @@ const (
CmdBundleDeployFlagResume = "Only deploys packages from the bundle which haven't already been deployed"
CmdBundleDeployFlagSet = "Specify deployment variables to set on the command line (KEY=value)"
CmdBundleDeployFlagRetries = "Specify the number of retries for package deployments (applies to all pkgs in a bundle)"
+ CmdBundleDeployFlagRef = "Specify which zarf package ref you want to deploy. By default the ref set in the bundle yaml is used."
// bundle inspect
CmdBundleInspectShort = "Display the metadata of a bundle"
@@ -82,7 +84,8 @@ const (
CmdZarfShort = "Run a zarf command"
// uds dev
- CmdDevShort = "Commands useful for developing bundles"
- CmdDevDeployShort = "[beta] Creates and deploys a UDS bundle from a given directory in dev mode"
- CmdDevDeployLong = "[beta] Creates and deploys a UDS bundle from a given directory in dev mode, setting package options like YOLO mode for faster iteration."
+ CmdDevShort = "[beta] Commands useful for developing bundles"
+ CmdDevDeployShort = "[beta] Creates and deploys a UDS bundle from a given directory in dev mode"
+ CmdDevDeployLong = "[beta] Creates and deploys a UDS bundle from a given directory in dev mode, setting package options like YOLO mode for faster iteration."
+ CmdBundleCreateForceCreate = "For local bundles with local packages, specify whether to create a zarf package even if it already exists."
)
diff --git a/src/pkg/bundle/common.go b/src/pkg/bundle/common.go
index c1f31791..9467ea5e 100644
--- a/src/pkg/bundle/common.go
+++ b/src/pkg/bundle/common.go
@@ -344,3 +344,37 @@ func validateBundleVars(packages []types.Package) error {
}
return nil
}
+
+// setPackageRef sets the package reference
+func (b *Bundle) setPackageRef(pkg types.Package) types.Package {
+ if ref, ok := b.cfg.DevDeployOpts.Ref[pkg.Name]; ok {
+ // Can only set refs for remote packages
+ if pkg.Repository == "" {
+ message.Fatalf(errors.New("Invalid input"), "Cannot set ref for local packages: %s", pkg.Name)
+ }
+
+ errMsg := fmt.Sprintf("Unable to access %s:%s", pkg.Repository, ref)
+
+ // Get SHA from registry
+ url := fmt.Sprintf("%s:%s", pkg.Repository, ref)
+
+ platform := ocispec.Platform{
+ Architecture: config.GetArch(),
+ OS: oci.MultiOS,
+ }
+ remote, err := zoci.NewRemote(url, platform)
+ if err != nil {
+ message.Fatalf(err, errMsg)
+ }
+ if err := remote.Repo().Reference.ValidateReferenceAsDigest(); err != nil {
+ manifestDesc, err := remote.ResolveRoot(context.TODO())
+ if err != nil {
+ message.Fatalf(err, errMsg)
+ }
+ pkg.Ref = ref + "@sha256:" + manifestDesc.Digest.Encoded()
+ } else {
+ message.Fatalf(err, errMsg)
+ }
+ }
+ return pkg
+}
diff --git a/src/pkg/bundle/create.go b/src/pkg/bundle/create.go
index 3c691235..1f506750 100644
--- a/src/pkg/bundle/create.go
+++ b/src/pkg/bundle/create.go
@@ -34,11 +34,6 @@ func (b *Bundle) Create() error {
return err
}
- // Populate values from valuesFiles if provided
- if err := b.processValuesFiles(); err != nil {
- return err
- }
-
// confirm creation
if ok := b.confirmBundleCreation(); !ok {
return fmt.Errorf("bundle creation cancelled")
@@ -86,6 +81,14 @@ func (b *Bundle) Create() error {
}
}
+ // for dev mode update package ref for local bundles, refs for remote bundles updated on deploy
+ if config.Dev && len(b.cfg.DevDeployOpts.Ref) != 0 {
+ for i, pkg := range b.bundle.Packages {
+ pkg = b.setPackageRef(pkg)
+ b.bundle.Packages[i] = pkg
+ }
+ }
+
opts := bundler.Options{
Bundle: &b.bundle,
Output: b.cfg.CreateOpts.Output,
@@ -93,6 +96,7 @@ func (b *Bundle) Create() error {
SourceDir: b.cfg.CreateOpts.SourceDirectory,
}
bundlerClient := bundler.NewBundler(&opts)
+
return bundlerClient.Create()
}
diff --git a/src/pkg/bundle/deploy.go b/src/pkg/bundle/deploy.go
index d75ff7e1..6f2997e4 100644
--- a/src/pkg/bundle/deploy.go
+++ b/src/pkg/bundle/deploy.go
@@ -57,7 +57,6 @@ func (b *Bundle) Deploy() error {
} else {
packagesToDeploy = b.bundle.Packages
}
-
return deployPackages(packagesToDeploy, resume, b)
}
@@ -79,7 +78,12 @@ func deployPackages(packages []types.Package, resume bool, b *Bundle) error {
}
// deploy each package
- for _, pkg := range packagesToDeploy {
+ for i, pkg := range packagesToDeploy {
+ // for dev mode update package ref for remote bundles, refs for local bundles updated on create
+ if config.Dev && !strings.Contains(b.cfg.DeployOpts.Source, "tar.zst") {
+ pkg = b.setPackageRef(pkg)
+ b.bundle.Packages[i] = pkg
+ }
sha := strings.Split(pkg.Ref, "@sha256:")[1] // using appended SHA from create!
pkgTmp, err := utils.MakeTempDir(config.CommonOptions.TempDirectory)
if err != nil {
@@ -126,7 +130,7 @@ func deployPackages(packages []types.Package, resume bool, b *Bundle) error {
// Automatically confirm the package deployment
zarfConfig.CommonOptions.Confirm = true
- source, err := sources.New(b.cfg.DeployOpts.Source, pkg, opts, sha, nsOverrides)
+ source, err := sources.New(*b.cfg, pkg, opts, sha, nsOverrides)
if err != nil {
return err
}
diff --git a/src/pkg/bundle/dev.go b/src/pkg/bundle/dev.go
index 2ab10ebe..0b969fd9 100644
--- a/src/pkg/bundle/dev.go
+++ b/src/pkg/bundle/dev.go
@@ -5,6 +5,7 @@
package bundle
import (
+ "errors"
"fmt"
"os"
"path/filepath"
@@ -12,6 +13,7 @@ import (
"github.com/defenseunicorns/uds-cli/src/config"
"github.com/defenseunicorns/uds-cli/src/pkg/utils"
+ "github.com/defenseunicorns/uds-cli/src/types"
zarfCLI "github.com/defenseunicorns/zarf/src/cmd"
"github.com/defenseunicorns/zarf/src/pkg/message"
@@ -27,6 +29,15 @@ func (b *Bundle) CreateZarfPkgs() {
zarfPackagePattern := `^zarf-.*\.tar\.zst$`
for _, pkg := range b.bundle.Packages {
+ // Can only set flavors for local packages
+ if pkg.Path == "" {
+ // check if attempting to apply flavor to remote package
+ if (len(b.cfg.DevDeployOpts.Flavor) == 1 && b.cfg.DevDeployOpts.Flavor[""] != "") ||
+ (b.cfg.DevDeployOpts.Flavor[pkg.Name] != "") {
+ message.Fatalf(errors.New("Invalid input"), "Cannot set flavor for remote packages: %s", pkg.Name)
+ }
+ }
+
// if pkg is a local zarf package, attempt to create it if it doesn't exist
if pkg.Path != "" {
path := getPkgPath(pkg, config.GetArch(b.bundle.Metadata.Architecture), srcDir)
@@ -47,8 +58,13 @@ func (b *Bundle) CreateZarfPkgs() {
}
}
// create local zarf package if it doesn't exist
- if !packageFound {
- os.Args = []string{"zarf", "package", "create", pkgDir, "--confirm", "-o", pkgDir, "--skip-sbom"}
+ if !packageFound || b.cfg.DevDeployOpts.ForceCreate {
+ if len(b.cfg.DevDeployOpts.Flavor) != 0 {
+ pkg = b.setPackageFlavor(pkg)
+ os.Args = []string{"zarf", "package", "create", pkgDir, "--confirm", "-o", pkgDir, "--skip-sbom", "--flavor", pkg.Flavor}
+ } else {
+ os.Args = []string{"zarf", "package", "create", pkgDir, "--confirm", "-o", pkgDir, "--skip-sbom"}
+ }
zarfCLI.Execute()
if err != nil {
message.Fatalf(err, "Failed to create package %s: %s", pkg.Name, err.Error())
@@ -58,6 +74,17 @@ func (b *Bundle) CreateZarfPkgs() {
}
}
+func (b *Bundle) setPackageFlavor(pkg types.Package) types.Package {
+ // handle case when --flavor flag applies to all packages
+ // empty key references a value that is applied to all package flavors
+ if len(b.cfg.DevDeployOpts.Flavor) == 1 && b.cfg.DevDeployOpts.Flavor[""] != "" {
+ pkg.Flavor = b.cfg.DevDeployOpts.Flavor[""]
+ } else if flavor, ok := b.cfg.DevDeployOpts.Flavor[pkg.Name]; ok {
+ pkg.Flavor = flavor
+ }
+ return pkg
+}
+
// SetDeploySource sets the source for the bundle when in dev mode
func (b *Bundle) SetDeploySource(srcDir string) {
filename := fmt.Sprintf("%s%s-%s-%s.tar.zst", config.BundlePrefix, b.bundle.Metadata.Name, b.bundle.Metadata.Architecture, b.bundle.Metadata.Version)
diff --git a/src/pkg/bundle/remove.go b/src/pkg/bundle/remove.go
index a6768064..8a7f0f52 100644
--- a/src/pkg/bundle/remove.go
+++ b/src/pkg/bundle/remove.go
@@ -94,7 +94,7 @@ func removePackages(packagesToRemove []types.Package, b *Bundle) error {
}
sha := strings.Split(pkg.Ref, "sha256:")[1]
- source, err := sources.New(b.cfg.RemoveOpts.Source, pkg, opts, sha, nil)
+ source, err := sources.New(*b.cfg, pkg, opts, sha, nil)
if err != nil {
return err
}
diff --git a/src/pkg/bundler/localbundle.go b/src/pkg/bundler/localbundle.go
index 711f55a5..30031cc2 100644
--- a/src/pkg/bundler/localbundle.go
+++ b/src/pkg/bundler/localbundle.go
@@ -66,7 +66,7 @@ func (lo *LocalBundle) create(signature []byte) error {
message.HeaderInfof("🐕 Fetching Packages")
- // create root manifest for bundle, will populate with refs to uds-bundle.yaml and zarf image manifests
+ // create root manifest for bundle, will populate with ref to uds-bundle.yaml and zarf image manifests
rootManifest := ocispec.Manifest{
MediaType: ocispec.MediaTypeImageManifest,
}
diff --git a/src/pkg/sources/new.go b/src/pkg/sources/new.go
index 8c902772..56681e49 100644
--- a/src/pkg/sources/new.go
+++ b/src/pkg/sources/new.go
@@ -5,6 +5,7 @@
package sources
import (
+ "fmt"
"strings"
"github.com/defenseunicorns/pkg/oci"
@@ -17,8 +18,17 @@ import (
)
// New creates a new package source based on pkgLocation
-func New(pkgLocation string, pkg types.Package, opts zarfTypes.ZarfPackageOptions, sha string, nsOverrides NamespaceOverrideMap) (zarfSources.PackageSource, error) {
+func New(bundleCfg types.BundleConfig, pkg types.Package, opts zarfTypes.ZarfPackageOptions, sha string, nsOverrides NamespaceOverrideMap) (zarfSources.PackageSource, error) {
var source zarfSources.PackageSource
+ var pkgLocation string
+ if bundleCfg.DeployOpts.Source != "" {
+ pkgLocation = bundleCfg.DeployOpts.Source
+ } else if bundleCfg.RemoveOpts.Source != "" {
+ pkgLocation = bundleCfg.RemoveOpts.Source
+ } else {
+ return nil, fmt.Errorf("no source provided for package %s", pkg.Name)
+ }
+
if strings.Contains(pkgLocation, "tar.zst") {
source = &TarballBundle{
Pkg: pkg,
@@ -44,6 +54,7 @@ func New(pkgLocation string, pkg types.Package, opts zarfTypes.ZarfPackageOption
TmpDir: opts.PackageSource,
Remote: remote.OrasRemote,
nsOverrides: nsOverrides,
+ bundleCfg: bundleCfg,
}
}
return source, nil
diff --git a/src/pkg/sources/remote.go b/src/pkg/sources/remote.go
index 7dc30537..8cfc0755 100644
--- a/src/pkg/sources/remote.go
+++ b/src/pkg/sources/remote.go
@@ -37,12 +37,33 @@ type RemoteBundle struct {
TmpDir string
Remote *oci.OrasRemote
nsOverrides NamespaceOverrideMap
+ bundleCfg types.BundleConfig
}
// LoadPackage loads a Zarf package from a remote bundle
func (r *RemoteBundle) LoadPackage(dst *layout.PackagePaths, filter filters.ComponentFilterStrategy, unarchiveAll bool) (zarfTypes.ZarfPackage, []string, error) {
// todo: progress bar??
- layers, err := r.downloadPkgFromRemoteBundle()
+ var layers []ocispec.Descriptor
+ var err error
+
+ if config.Dev {
+ if _, ok := r.bundleCfg.DevDeployOpts.Ref[r.Pkg.Name]; ok {
+ // create new oras remote for package
+ platform := ocispec.Platform{
+ Architecture: config.GetArch(),
+ OS: oci.MultiOS,
+ }
+ // get remote client
+ repoUrl := fmt.Sprintf("%s:%s", r.Pkg.Repository, r.Pkg.Ref)
+ remote, _ := zoci.NewRemote(repoUrl, platform)
+ layers, err = remote.PullPackage(context.TODO(), r.TmpDir, config.CommonOptions.OCIConcurrency)
+ } else {
+ layers, err = r.downloadPkgFromRemoteBundle()
+ }
+ } else {
+ layers, err = r.downloadPkgFromRemoteBundle()
+ }
+
if err != nil {
return zarfTypes.ZarfPackage{}, nil, err
}
diff --git a/src/test/bundles/15-dev-deploy/uds-bundle.yaml b/src/test/bundles/15-dev-deploy/uds-bundle.yaml
new file mode 100644
index 00000000..5d980e3e
--- /dev/null
+++ b/src/test/bundles/15-dev-deploy/uds-bundle.yaml
@@ -0,0 +1,10 @@
+kind: UDSBundle
+metadata:
+ name: dev-deploy-flavors
+ description: building from a local Zarf pkg with flavors
+ version: 0.0.1
+
+packages:
+ - name: podinfo
+ path: "../../packages/podinfo/flavors"
+ ref: 0.0.1
diff --git a/src/test/e2e/commands_test.go b/src/test/e2e/commands_test.go
index cda25a57..d93564e8 100644
--- a/src/test/e2e/commands_test.go
+++ b/src/test/e2e/commands_test.go
@@ -119,7 +119,7 @@ func deploy(t *testing.T, tarballPath string) (stdout string, stderr string) {
}
func devDeploy(t *testing.T, bundlePath string) (stdout string, stderr string) {
- cmd := strings.Split(fmt.Sprintf("dev deploy %s --confirm", bundlePath), " ")
+ cmd := strings.Split(fmt.Sprintf("dev deploy %s", bundlePath), " ")
stdout, stderr, err := e2e.UDS(cmd...)
require.NoError(t, err)
return stdout, stderr
diff --git a/src/test/e2e/dev_test.go b/src/test/e2e/dev_test.go
index b0dbf759..e9a14798 100644
--- a/src/test/e2e/dev_test.go
+++ b/src/test/e2e/dev_test.go
@@ -16,7 +16,6 @@ import (
func TestDevDeploy(t *testing.T) {
removeZarfInit()
- cmd := strings.Split("zarf tools kubectl get deployments -A -o=jsonpath='{.items[*].metadata.name}'", " ")
t.Run("Test dev deploy with local and remote pkgs", func(t *testing.T) {
@@ -27,6 +26,7 @@ func TestDevDeploy(t *testing.T) {
devDeploy(t, bundleDir)
+ cmd := strings.Split("zarf tools kubectl get deployments -A -o=jsonpath='{.items[*].metadata.name}'", " ")
deployments, _, _ := e2e.UDS(cmd...)
require.Contains(t, deployments, "podinfo")
require.Contains(t, deployments, "nginx")
@@ -43,6 +43,7 @@ func TestDevDeploy(t *testing.T) {
devDeployPackages(t, bundleDir, "podinfo")
+ cmd := strings.Split("zarf tools kubectl get deployments -A -o=jsonpath='{.items[*].metadata.name}'", " ")
deployments, _, _ := e2e.UDS(cmd...)
require.Contains(t, deployments, "podinfo")
require.NotContains(t, deployments, "nginx")
@@ -50,12 +51,90 @@ func TestDevDeploy(t *testing.T) {
remove(t, bundlePath)
})
+ t.Run("Test dev deploy with ref flag", func(t *testing.T) {
+ e2e.DeleteZarfPkg(t, "src/test/packages/podinfo")
+ bundleDir := "src/test/bundles/03-local-and-remote"
+
+ cmd := strings.Split(fmt.Sprintf("dev deploy %s --ref %s", bundleDir, "nginx=0.0.2"), " ")
+ _, _, err := e2e.UDS(cmd...)
+ require.NoError(t, err)
+
+ cmd = strings.Split("zarf tools kubectl get deployment -n nginx nginx-deployment -o=jsonpath='{.spec.template.spec.containers[0].image}'", " ")
+ ref, _, err := e2e.UDS(cmd...)
+ require.Contains(t, ref, "nginx:1.26.0")
+ require.NoError(t, err)
+
+ cmd = strings.Split("zarf tools kubectl delete ns podinfo nginx zarf", " ")
+ _, _, err = e2e.UDS(cmd...)
+ require.NoError(t, err)
+ })
+
+ t.Run("Test dev deploy with flavor flag", func(t *testing.T) {
+ e2e.DeleteZarfPkg(t, "src/test/packages/podinfo/flavors")
+ bundleDir := "src/test/bundles/15-dev-deploy"
+
+ cmd := strings.Split(fmt.Sprintf("dev deploy %s --flavor %s", bundleDir, "podinfo=patchVersion3"), " ")
+ _, _, err := e2e.UDS(cmd...)
+ require.NoError(t, err)
+
+ cmd = strings.Split("zarf tools kubectl get deployment -n podinfo-flavor podinfo -o=jsonpath='{.spec.template.spec.containers[0].image}'", " ")
+ ref, _, err := e2e.UDS(cmd...)
+ require.Contains(t, ref, "ghcr.io/stefanprodan/podinfo:6.6.3")
+ require.NoError(t, err)
+
+ cmd = strings.Split("zarf tools kubectl delete ns zarf podinfo-flavor", " ")
+ _, _, err = e2e.UDS(cmd...)
+ require.NoError(t, err)
+ })
+ t.Run("Test dev deploy with global flavor", func(t *testing.T) {
+ bundleDir := "src/test/bundles/15-dev-deploy"
+
+ cmd := strings.Split(fmt.Sprintf("dev deploy %s --flavor %s --force-create", bundleDir, "patchVersion3"), " ")
+ _, _, err := e2e.UDS(cmd...)
+ require.NoError(t, err)
+
+ cmd = strings.Split("zarf tools kubectl get deployment -n podinfo-flavor podinfo -o=jsonpath='{.spec.template.spec.containers[0].image}'", " ")
+ ref, _, err := e2e.UDS(cmd...)
+ require.Contains(t, ref, "ghcr.io/stefanprodan/podinfo:6.6.3")
+ require.NoError(t, err)
+
+ cmd = strings.Split("zarf tools kubectl delete ns zarf podinfo-flavor", " ")
+ _, _, err = e2e.UDS(cmd...)
+ require.NoError(t, err)
+ })
+
+ t.Run("Test dev deploy with flavor and force create", func(t *testing.T) {
+
+ bundleDir := "src/test/bundles/15-dev-deploy"
+
+ // create flavor patchVersion3 podinfo-flavor package
+ pkgDir := "src/test/packages/podinfo"
+ cmd := strings.Split(fmt.Sprintf("zarf package create %s --flavor %s --confirm -o %s", pkgDir, "patchVersion3", pkgDir), " ")
+ _, _, err := e2e.UDS(cmd...)
+ require.NoError(t, err)
+
+ // dev deploy with flavor patchVersion2 and --force-create
+ cmd = strings.Split(fmt.Sprintf("dev deploy %s --flavor %s --force-create", bundleDir, "podinfo=patchVersion2"), " ")
+ _, _, err = e2e.UDS(cmd...)
+ require.NoError(t, err)
+
+ cmd = strings.Split("zarf tools kubectl get deployment -n podinfo-flavor podinfo -o=jsonpath='{.spec.template.spec.containers[0].image}'", " ")
+ ref, _, err := e2e.UDS(cmd...)
+ // assert that podinfo package with flavor patchVersion2 was deployed.
+ require.Contains(t, ref, "ghcr.io/stefanprodan/podinfo:6.6.2")
+ require.NoError(t, err)
+
+ cmd = strings.Split("zarf tools kubectl delete ns zarf podinfo-flavor", " ")
+ _, _, err = e2e.UDS(cmd...)
+ require.NoError(t, err)
+ })
t.Run("Test dev deploy with remote bundle", func(t *testing.T) {
bundle := "oci://ghcr.io/defenseunicorns/packages/uds-cli/test/publish/ghcr-test:0.0.1"
devDeploy(t, bundle)
+ cmd := strings.Split("zarf tools kubectl get deployments -A -o=jsonpath='{.items[*].metadata.name}'", " ")
deployments, _, _ := e2e.UDS(cmd...)
require.Contains(t, deployments, "podinfo")
require.Contains(t, deployments, "nginx")
@@ -66,7 +145,7 @@ func TestDevDeploy(t *testing.T) {
t.Run("Test dev deploy with --set flag", func(t *testing.T) {
bundleDir := "src/test/bundles/02-variables"
bundleTarballPath := filepath.Join(bundleDir, fmt.Sprintf("uds-bundle-variables-%s-0.0.1.tar.zst", e2e.Arch))
- _, stderr := runCmd(t, "dev deploy "+bundleDir+" --set ANIMAL=Longhorns --set COUNTRY=Texas --confirm -l=debug")
+ _, stderr := runCmd(t, "dev deploy "+bundleDir+" --set ANIMAL=Longhorns --set COUNTRY=Texas -l=debug")
require.Contains(t, stderr, "This fun-fact was imported: Longhorns are the national animal of Texas")
require.NotContains(t, stderr, "This fun-fact was imported: Unicorns are the national animal of Scotland")
remove(t, bundleTarballPath)
diff --git a/src/test/packages/nginx/refs/deployment.yaml b/src/test/packages/nginx/refs/deployment.yaml
new file mode 100644
index 00000000..30bd1048
--- /dev/null
+++ b/src/test/packages/nginx/refs/deployment.yaml
@@ -0,0 +1,19 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: nginx-deployment
+spec:
+ selector:
+ matchLabels:
+ app: nginx
+ replicas: 2 # tells deployment to run 2 pods matching the template
+ template:
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ containers:
+ - name: nginx
+ image: nginx:1.26.0
+ ports:
+ - containerPort: 80
diff --git a/src/test/packages/nginx/refs/zarf.yaml b/src/test/packages/nginx/refs/zarf.yaml
new file mode 100644
index 00000000..6982cd7f
--- /dev/null
+++ b/src/test/packages/nginx/refs/zarf.yaml
@@ -0,0 +1,29 @@
+kind: ZarfPackageConfig
+metadata:
+ name: nginx
+ version: 0.0.2
+ description: nginx deployment using image docker.io/library/nginx:1.26.0 for testing dev deploy --refs flag
+
+components:
+ - name: nginx-remote
+ required: true
+ manifests:
+ - name: simple-nginx-deployment
+ namespace: nginx
+ files:
+ - deployment.yaml
+ actions:
+ onDeploy:
+ # the following checks were computed by viewing the success state of the package deployment
+ # and creating `wait` actions that match
+ after:
+ - wait:
+ cluster:
+ kind: deployment
+ name: nginx-deployment
+ namespace: nginx
+ condition: available
+ # image discovery is supported in all manifests and charts using:
+ # zarf prepare find-images
+ images:
+ - docker.io/library/nginx:1.26.0
diff --git a/src/test/packages/podinfo/flavors/zarf.yaml b/src/test/packages/podinfo/flavors/zarf.yaml
new file mode 100644
index 00000000..26a4de05
--- /dev/null
+++ b/src/test/packages/podinfo/flavors/zarf.yaml
@@ -0,0 +1,49 @@
+kind: ZarfPackageConfig
+metadata:
+ name: podinfo
+ version: 0.0.1
+
+components:
+ - name: podinfo-flavor
+ required: true
+ only:
+ flavor: patchVersion2
+ charts:
+ - name: podinfo
+ version: 6.6.2
+ namespace: podinfo-flavor
+ url: https://github.com/stefanprodan/podinfo.git
+ gitPath: charts/podinfo
+ images:
+ - ghcr.io/stefanprodan/podinfo:6.6.2
+ actions:
+ onDeploy:
+ after:
+ - wait:
+ cluster:
+ kind: deployment
+ name: podinfo
+ namespace: podinfo-flavor
+ condition: available
+
+ - name: podinfo-flavor
+ required: true
+ only:
+ flavor: patchVersion3
+ charts:
+ - name: podinfo
+ version: 6.6.3
+ namespace: podinfo-flavor
+ url: https://github.com/stefanprodan/podinfo.git
+ gitPath: charts/podinfo
+ images:
+ - ghcr.io/stefanprodan/podinfo:6.6.3
+ actions:
+ onDeploy:
+ after:
+ - wait:
+ cluster:
+ kind: deployment
+ name: podinfo
+ namespace: podinfo-flavor
+ condition: available
diff --git a/src/test/packages/podinfo/refs/zarf.yaml b/src/test/packages/podinfo/refs/zarf.yaml
new file mode 100644
index 00000000..691a3647
--- /dev/null
+++ b/src/test/packages/podinfo/refs/zarf.yaml
@@ -0,0 +1,27 @@
+kind: ZarfPackageConfig
+metadata:
+ name: podinfo
+ version: 0.0.2
+ description: podinfo deployment using image ghcr.io/stefanprodan/podinfo:6.6.2 for testing dev deploy --refs flag
+
+
+components:
+ - name: podinfo
+ required: true
+ charts:
+ - name: podinfo
+ version: 6.6.2
+ namespace: podinfo
+ url: https://github.com/stefanprodan/podinfo.git
+ gitPath: charts/podinfo
+ images:
+ - ghcr.io/stefanprodan/podinfo:6.6.2
+ actions:
+ onDeploy:
+ after:
+ - wait:
+ cluster:
+ kind: deployment
+ name: podinfo
+ namespace: podinfo
+ condition: available
diff --git a/src/types/bundle.go b/src/types/bundle.go
index 358ba78e..7d3283be 100644
--- a/src/types/bundle.go
+++ b/src/types/bundle.go
@@ -36,6 +36,7 @@ type Package struct {
Repository string `json:"repository,omitempty" jsonschema:"description=The repository to import the package from"`
Path string `json:"path,omitempty" jsonschema:"description=The local path to import the package from"`
Ref string `json:"ref" jsonschema:"description=Ref (tag) of the Zarf package"`
+ Flavor string `json:"flavor,omitempty" jsonschema:"description=Flavor of the Zarf package"`
OptionalComponents []string `json:"optionalComponents,omitempty" jsonschema:"description=List of optional components to include from the package (required components are always included)"`
PublicKey string `json:"publicKey,omitempty" jsonschema:"description=The public key to use to verify the package"`
Imports []BundleVariableImport `json:"imports,omitempty" jsonschema:"description=List of Zarf variables to import from another Zarf package"`
diff --git a/src/types/options.go b/src/types/options.go
index e060bff3..bd1b5591 100644
--- a/src/types/options.go
+++ b/src/types/options.go
@@ -6,12 +6,13 @@ package types
// BundleConfig is the main struct that the bundler uses to hold high-level options.
type BundleConfig struct {
- CreateOpts BundleCreateOptions
- DeployOpts BundleDeployOptions
- PublishOpts BundlePublishOptions
- PullOpts BundlePullOptions
- InspectOpts BundleInspectOptions
- RemoveOpts BundleRemoveOptions
+ CreateOpts BundleCreateOptions
+ DeployOpts BundleDeployOptions
+ PublishOpts BundlePublishOptions
+ PullOpts BundlePullOptions
+ InspectOpts BundleInspectOptions
+ RemoveOpts BundleRemoveOptions
+ DevDeployOpts BundleDevDeployOptions
}
// BundleCreateOptions is the options for the bundler.Create() function
@@ -75,6 +76,14 @@ type BundleCommonOptions struct {
OCIConcurrency int `jsonschema:"description=Number of concurrent layer operations to perform when interacting with a remote package"`
}
+// BundleDevDeployOptions are the options for when doing a dev deploy
+type BundleDevDeployOptions struct {
+ FlavorInput string
+ Flavor map[string]string
+ ForceCreate bool
+ Ref map[string]string
+}
+
// PathMap is a map of either absolute paths to relative paths or relative paths to absolute paths
// used to map filenames during local bundle tarball creation
type PathMap map[string]string
diff --git a/uds.schema.json b/uds.schema.json
index 8ca65ba7..0ed9c07f 100644
--- a/uds.schema.json
+++ b/uds.schema.json
@@ -151,6 +151,10 @@
"type": "string",
"description": "Ref (tag) of the Zarf package"
},
+ "flavor": {
+ "type": "string",
+ "description": "Flavor of the Zarf package"
+ },
"optionalComponents": {
"items": {
"type": "string"