Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions docs/reference/CLI/commands/uds_dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ description: UDS CLI command reference for <code>uds dev</code>.
* [uds](/reference/cli/commands/uds/) - CLI for UDS Bundles
* [uds dev deploy](/reference/cli/commands/uds_dev_deploy/) - [beta] Creates and deploys a UDS bundle in dev mode
* [uds dev extract](/reference/cli/commands/uds_dev_extract/) - [alpha] Extract the Zarf Package tarballs from a Bundle
* [uds dev tofu-create](/reference/cli/commands/uds_dev_tofu-create/) - create bundle from a uds-bundle.tf config

36 changes: 36 additions & 0 deletions docs/reference/CLI/commands/uds_dev_tofu-create.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: uds dev tofu-create
description: UDS CLI command reference for <code>uds dev tofu-create</code>.
---
## uds dev tofu-create

create bundle from a uds-bundle.tf config

```
uds dev tofu-create [DIRECTORY] [flags]
```

### Options

```
-h, --help help for tofu-create
```

### Options inherited from parent commands

```
-a, --architecture string Architecture for UDS bundles and Zarf packages
--insecure Allow access to insecure registries and disable other recommended security enforcements such as package checksum and signature validation. This flag should only be used if you have a specific reason and accept the reduced security posture.
-l, --log-level string Log level when running UDS-CLI. Valid options are: warn, info, debug, trace (default "info")
--no-color Disable color output
--no-log-file Disable log file creation
--no-progress Disable fancy UI progress bars, spinners, logos, etc
--oci-concurrency int Number of concurrent layer operations to perform when interacting with a remote bundle. (default 3)
--tmpdir string Specify the temporary directory to use for intermediate files
--uds-cache string Specify the location of the UDS cache directory (default "~/.uds-cache")
```

### SEE ALSO

* [uds dev](/reference/cli/commands/uds_dev/) - [beta] Commands useful for developing bundles

17 changes: 17 additions & 0 deletions src/cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,23 @@ func configureZarf() {
}
}

func setTofuFile(args []string) error {
pathToTofuDir := ""
if len(args) > 0 {
if !helpers.IsDir(args[0]) {
return fmt.Errorf("(%q) is not a valid path to a directory", args[0])
}
pathToTofuDir = filepath.Join(args[0])
}

tofuFilePath := filepath.Join(pathToTofuDir, config.BundleTF)
if _, err := os.Stat(tofuFilePath); err != nil {
return fmt.Errorf("%s not found", config.BundleTF)
}
bundleCfg.CreateOpts.BundleFile = tofuFilePath
return nil
}

func setBundleFile(args []string) error {
pathToBundleFile := ""
if len(args) > 0 {
Expand Down
40 changes: 40 additions & 0 deletions src/cmd/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package cmd

import (
"fmt"
"os"
"strings"

"github.com/defenseunicorns/pkg/helpers/v2"
Expand All @@ -21,6 +22,44 @@ var devCmd = &cobra.Command{
Short: lang.CmdDevShort,
}

var tofuCreateCmd = &cobra.Command{
Use: "tofu-create [DIRECTORY]",
Aliases: []string{"c"},
Args: cobra.MaximumNArgs(1),
Short: "create bundle from a uds-bundle.tf config",
PreRunE: func(_ *cobra.Command, args []string) error {
err := setTofuFile(args)
if err != nil {
return err
}
return nil
},
RunE: func(_ *cobra.Command, args []string) error {
configureZarf()
srcDir, err := os.Getwd()
if err != nil {
return fmt.Errorf("error reading the current working directory")
}
if len(args) > 0 {
srcDir = args[0]
}
bundleCfg.CreateOpts.SourceDirectory = srcDir
bundleCfg.IsTofu = true

bndlClient, err := bundle.New(&bundleCfg)
if err != nil {
return err
}
defer bndlClient.ClearPaths()

if err := bndlClient.Create(); err != nil {
bndlClient.ClearPaths()
return fmt.Errorf("failed to create bundle: %s", err.Error())
}
return nil
},
}

var devDeployCmd = &cobra.Command{
Use: "deploy [BUNDLE_DIR|OCI_REF]",
Args: cobra.MaximumNArgs(1),
Expand Down Expand Up @@ -189,4 +228,5 @@ func init() {
devDeployCmd.Flags().StringToStringVar(&bundleCfg.DeployOpts.SetVariables, "set", nil, lang.CmdBundleDeployFlagSet)

devCmd.AddCommand(extractCmd)
devCmd.AddCommand(tofuCreateCmd)
}
4 changes: 4 additions & 0 deletions src/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const (
// BundleYAML is the string for uds-bundle.yaml
BundleYAML = "uds-bundle.yaml"

// BundleTF is the string for uds-bundle.tf
BundleTF = "uds-bundle.tf"
BundleTFConfig = "uds-tf-config.yaml"

// BundlePrefix is the prefix for compiled uds bundles
BundlePrefix = "uds-bundle-"

Expand Down
39 changes: 36 additions & 3 deletions src/pkg/bundle/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/AlecAivazis/survey/v2"
"github.com/defenseunicorns/uds-cli/src/config"
"github.com/defenseunicorns/uds-cli/src/pkg/bundler"
"github.com/defenseunicorns/uds-cli/src/pkg/tfparser"
"github.com/defenseunicorns/uds-cli/src/pkg/utils"
"github.com/defenseunicorns/uds-cli/src/types"
"github.com/pterm/pterm"
Expand All @@ -23,9 +24,37 @@ import (

// Create creates a bundle
func (b *Bundle) Create() error {
// read the bundle's metadata into memory
if err := utils.ReadYAMLStrict(filepath.Join(b.cfg.CreateOpts.SourceDirectory, b.cfg.CreateOpts.BundleFile), &b.bundle); err != nil {
return err
// populate the b.bundle struct with information on the packages we are creating
if b.cfg.IsTofu {
// read the .tf data to determine which resources (packages) we are creating
// TODO: @JEPRRY consider making a helper function for this
tfConfig, err := tfparser.ParseFile(b.cfg.CreateOpts.BundleFile)
if err != nil {
return err
}

b.bundle.Kind = "UDSBundle"
b.bundle.Metadata = types.UDSMetadata{
Name: tfConfig.Metadata.Name,
Version: tfConfig.Metadata.Version,
Architecture: *tfConfig.Metadata.Architecture,
Description: *tfConfig.Metadata.Description,
}

// Parse each tfparser.Packages resource and convert it to types.Package type
for _, pkg := range tfConfig.Packages {
newPackage := types.Package{
Name: pkg.Name,
Repository: pkg.OCIUrl,
Ref: pkg.Ref,
}
b.bundle.Packages = append(b.bundle.Packages, newPackage)
}
} else {
// read the bundle's metadata into memory
if err := utils.ReadYAMLStrict(filepath.Join(b.cfg.CreateOpts.SourceDirectory, b.cfg.CreateOpts.BundleFile), &b.bundle); err != nil {
return err
}
}

// set the bundle's name and version if provided via flag
Expand All @@ -36,6 +65,7 @@ func (b *Bundle) Create() error {
b.bundle.Metadata.Version = b.cfg.CreateOpts.Version
}

// TODO: @JPERRY this valuesFile block has not been modified to work with tofu based bundles yet
// Populate values from valuesFiles if provided
if err := b.processValuesFiles(); err != nil {
return err
Expand Down Expand Up @@ -66,6 +96,7 @@ func (b *Bundle) Create() error {
validateSpinner.Successf("Bundle Validated")
pterm.Print()

// TODO: @JPERRY This signingkey block has not been modified to work with tofu based bundles yet
// sign the bundle if a signing key was provided
if b.cfg.CreateOpts.SigningKeyPath != "" {
// write the bundle to disk so we can sign it
Expand All @@ -88,6 +119,7 @@ func (b *Bundle) Create() error {
}
}

// TODO: @JPERRY this dev deploy block has not been validated to work with tofu based bundles yet
// 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 {
Expand All @@ -101,6 +133,7 @@ func (b *Bundle) Create() error {
Output: b.cfg.CreateOpts.Output,
TmpDstDir: b.tmp,
SourceDir: b.cfg.CreateOpts.SourceDirectory,
IsTofu: b.cfg.IsTofu,
}
bundlerClient := bundler.NewBundler(&opts)

Expand Down
6 changes: 5 additions & 1 deletion src/pkg/bundler/bundler.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Bundler struct {
output string
tmpDstDir string
sourceDir string
isTofu bool
}

// Pusher is the interface for pushing bundles
Expand All @@ -27,6 +28,7 @@ type Options struct {
Output string
TmpDstDir string
SourceDir string
IsTofu bool
}

// NewBundler creates a new bundler
Expand All @@ -36,20 +38,22 @@ func NewBundler(opts *Options) *Bundler {
output: opts.Output,
tmpDstDir: opts.TmpDstDir,
sourceDir: opts.SourceDir,
isTofu: opts.IsTofu,
}
return &b
}

// Create creates a bundle
func (b *Bundler) Create() error {
if utils.IsRegistryURL(b.output) {
// TODO: @JPERRY Remote bundles have not been implemented to work with tofu based bundles yet
remoteBundle := NewRemoteBundle(&RemoteBundleOpts{Bundle: b.bundle, Output: b.output})
err := remoteBundle.create(nil)
if err != nil {
return err
}
} else {
localBundle := NewLocalBundle(&LocalBundleOpts{Bundle: b.bundle, TmpDstDir: b.tmpDstDir, SourceDir: b.sourceDir, OutputDir: b.output})
localBundle := NewLocalBundle(&LocalBundleOpts{Bundle: b.bundle, TmpDstDir: b.tmpDstDir, SourceDir: b.sourceDir, OutputDir: b.output, IsTofu: b.isTofu})
err := localBundle.create(nil)
if err != nil {
return err
Expand Down
56 changes: 52 additions & 4 deletions src/pkg/bundler/localbundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type LocalBundleOpts struct {
TmpDstDir string
SourceDir string
OutputDir string
IsTofu bool
}

// LocalBundle enables create ops with local bundles
Expand All @@ -43,6 +44,7 @@ type LocalBundle struct {
tmpDstDir string
sourceDir string
outputDir string
isTofu bool
}

// NewLocalBundle creates a new local bundle
Expand All @@ -52,6 +54,7 @@ func NewLocalBundle(opts *LocalBundleOpts) *LocalBundle {
tmpDstDir: opts.TmpDstDir,
sourceDir: opts.SourceDir,
outputDir: opts.OutputDir,
isTofu: opts.IsTofu,
}
}

Expand Down Expand Up @@ -107,17 +110,34 @@ func (lo *LocalBundle) create(signature []byte) error {

message.HeaderInfof("🚧 Building Bundle")

// push uds-bundle.yaml to OCI store
bundleYAMLDesc, err := pushBundleYAMLToStore(store, bundle)
// push bundle manifest to OCI stoer
var bundleManifestDesc ocispec.Descriptor
if lo.isTofu {
bundleManifestDesc, err = pushBundleTFToStore(store, filepath.Join(lo.sourceDir, config.BundleTF))
} else {
bundleManifestDesc, err = pushBundleYAMLToStore(store, bundle)
}
if err != nil {
return err
}

// append uds-bundle.yaml layer to rootManifest and grab path for archiving
rootManifest.Layers = append(rootManifest.Layers, bundleYAMLDesc)
digest := bundleYAMLDesc.Digest.Encoded()
rootManifest.Layers = append(rootManifest.Layers, bundleManifestDesc)
digest := bundleManifestDesc.Digest.Encoded()
artifactPathMap[filepath.Join(lo.tmpDstDir, config.BlobsDir, digest)] = filepath.Join(config.BlobsDir, digest)

if lo.isTofu {
bundleConfigManifestDesc, err := pushBundleTFConfigToStore(store, bundle)
if err != nil {
return err
}

// append uds-bundle.yaml layer to rootManifest and grab path for archiving
rootManifest.Layers = append(rootManifest.Layers, bundleConfigManifestDesc)
digest := bundleConfigManifestDesc.Digest.Encoded()
artifactPathMap[filepath.Join(lo.tmpDstDir, config.BlobsDir, digest)] = filepath.Join(config.BlobsDir, digest)
}

// create and push bundle manifest config
manifestConfigDesc, err := pushManifestConfig(store, bundle.Metadata, bundle.Build)
if err != nil {
Expand Down Expand Up @@ -180,6 +200,34 @@ func (lo *LocalBundle) create(signature []byte) error {
return nil
}

func pushBundleTFConfigToStore(store *ocistore.Store, bundle *types.UDSBundle) (ocispec.Descriptor, error) {
tfConfigHelper := types.TFConfigHelper{Packages: bundle.Packages}
configYAMLBytes, err := goyaml.Marshal(tfConfigHelper)
if err != nil {
return ocispec.Descriptor{}, err
}
configYAMLDesc := content.NewDescriptorFromBytes(zoci.ZarfLayerMediaTypeBlob, configYAMLBytes)
configYAMLDesc.Annotations = map[string]string{
ocispec.AnnotationTitle: config.BundleTFConfig,
}
err = store.Push(context.TODO(), configYAMLDesc, bytes.NewReader(configYAMLBytes))
return configYAMLDesc, err
}

func pushBundleTFToStore(store *ocistore.Store, bundleSourcePath string) (ocispec.Descriptor, error) {
bundleTFBytes, err := os.ReadFile(bundleSourcePath)
if err != nil {
return ocispec.Descriptor{}, err
}

bundleTFDesc := content.NewDescriptorFromBytes(zoci.ZarfLayerMediaTypeBlob, bundleTFBytes)
bundleTFDesc.Annotations = map[string]string{
ocispec.AnnotationTitle: config.BundleTF,
}
err = store.Push(context.TODO(), bundleTFDesc, bytes.NewReader(bundleTFBytes))
return bundleTFDesc, err
}

// pushBundleYAMLToStore pushes the uds-bundle.yaml to a provided OCI store
func pushBundleYAMLToStore(store *ocistore.Store, bundle *types.UDSBundle) (ocispec.Descriptor, error) {
ctx := context.TODO()
Expand Down
7 changes: 7 additions & 0 deletions src/types/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type BundleConfig struct {
InspectOpts BundleInspectOptions
RemoveOpts BundleRemoveOptions
DevDeployOpts BundleDevDeployOptions
IsTofu bool
}

// BundleCreateOptions is the options for the bundler.Create() function
Expand Down Expand Up @@ -92,3 +93,9 @@ type BundleDevDeployOptions struct {
// 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

// TFConfigHelper is a subset of the Bundle struct, this is where configs will go that the TF bundle will need when deploying (such as shasum of the package manifest within the OCI tarball)
// I appologize for such a long comment - I didn't have the ~time~ understanding to write a short one
type TFConfigHelper struct {
Packages []Package `json:"packages" jsonschema:"description=List of Zarf packages"`
}