Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: dev deploy --ref and --flavor flags #638

Merged
merged 37 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4c18547
flavor and ref updates
decleaver May 21, 2024
31e6d7c
Merge branch 'main' into 537-dev-deploy-refs
decleaver May 22, 2024
8d36284
check refs and flavors on create, for local bundles
decleaver May 26, 2024
c2ff9b1
add test yamls
decleaver May 26, 2024
b0e7afe
schema update and fix flavor tag typo in test
decleaver May 26, 2024
7b56d29
delete debug ci yml
decleaver May 27, 2024
1ed528d
Merge branch 'main' into 537-dev-deploy-refs
decleaver May 29, 2024
f5aa28b
Merge branch 'main' into 537-dev-deploy-refs
decleaver May 29, 2024
ae513bb
moves ref check from create to deploy
decleaver May 29, 2024
f8e4d23
Merge branch 'main' into 537-dev-deploy-refs
decleaver May 29, 2024
65bfb5a
testing updates
decleaver May 30, 2024
fed1aec
fix flavor test
decleaver May 30, 2024
7089551
update documentation
decleaver May 31, 2024
13cbccc
Merge branch 'main' into 537-dev-deploy-refs
decleaver Jun 3, 2024
672df73
fix error messaging
decleaver Jun 3, 2024
5273a77
addressing pr comments
decleaver Jun 3, 2024
6951177
consolidate flavor and flavor-all flags
decleaver Jun 4, 2024
fdfa4cb
fix flavor bug
decleaver Jun 4, 2024
17b7724
refactor to remove DevDeployRefs from config
decleaver Jun 4, 2024
a365356
fix bug regarding RemoveOpts.source
decleaver Jun 4, 2024
50c4c24
adding unit tests and confirmation on all bundle types
decleaver Jun 4, 2024
4f05eff
review updates
decleaver Jun 4, 2024
858463c
add test fixes this time
decleaver Jun 5, 2024
7982151
test fix
decleaver Jun 5, 2024
ebedfac
remove extra space causing test to fail... smh
decleaver Jun 5, 2024
68ba0f1
Merge branch 'main' into 537-dev-deploy-refs
decleaver Jun 5, 2024
ee0b2e9
clarity is kindness
decleaver Jun 5, 2024
35e5b9f
fullsend no more confirm, update hack/push-test-artifacts.sh
decleaver Jun 6, 2024
5c9b865
fullersend
decleaver Jun 6, 2024
9d72026
add error messaging for ref and flavor flags on incompatible sources
decleaver Jun 6, 2024
102e099
split up bundles and packages for flavor tests
decleaver Jun 6, 2024
72a0d38
Merge branch 'main' into 537-dev-deploy-refs
decleaver Jun 6, 2024
c7564a6
better docs?
decleaver Jun 7, 2024
e2db54e
Update README.md
decleaver Jun 7, 2024
f56e628
Update README.md
decleaver Jun 7, 2024
d7abcf6
fix docs
decleaver Jun 7, 2024
97b44e0
Update README.md
decleaver Jun 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,23 +293,26 @@ 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
<b>NOTE: Dev mode is a BETA feature</b>

Dev mode facilitates faster dev cycles when developing and testing bundles

```
uds dev deploy <path-to-bundle-yaml-dir> | <oci-ref>
```

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

Expand Down
8 changes: 7 additions & 1 deletion hack/push-test-artifacts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
69 changes: 60 additions & 9 deletions src/cmd/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 := ""
Expand All @@ -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
}

Expand All @@ -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
Expand All @@ -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)
}
142 changes: 142 additions & 0 deletions src/cmd/dev_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
9 changes: 6 additions & 3 deletions src/config/lang/lang.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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."
)
34 changes: 34 additions & 0 deletions src/pkg/bundle/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading
Loading