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: add scaffolding for deploying bundles built from a .tf source #1062

Merged
merged 7 commits into from
Feb 4, 2025
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 @@ -32,4 +32,5 @@ description: UDS CLI command reference for <code>uds dev</code>.
* [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
* [uds dev tofu-deploy](/reference/cli/commands/uds_dev_tofu-deploy/) - Deploy a bundle from a local tarball or oci:// URL

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

Deploy a bundle from a local tarball or oci:// URL

```
uds dev tofu-deploy [BUNDLE_TARBALL|OCI_REF] [flags]
```

### Options

```
-h, --help help for tofu-deploy
--tf-state string Path to TF statefile (default "terraform.tfstate")
```

### 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

38 changes: 34 additions & 4 deletions src/cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ func isValidConfigOption(str string) bool {

// deploy performs validation, confirmation and deployment of a bundle
func deploy(bndlClient *bundle.Bundle) error {
_, _, _, err := bndlClient.PreDeployValidation()
var err error
if bundleCfg.TofuOpts.IsTofu {
_, _, _, err = bndlClient.PreDeployValidationTF()
} else {
_, _, _, err = bndlClient.PreDeployValidation()
}
if err != nil {
return fmt.Errorf("failed to validate bundle: %s", err.Error())
}
Expand All @@ -62,9 +67,34 @@ func deploy(bndlClient *bundle.Bundle) error {
}

// deploy the bundle
if err := bndlClient.Deploy(); err != nil {
bndlClient.ClearPaths()
return fmt.Errorf("failed to deploy bundle: %s", err.Error())
if bundleCfg.TofuOpts.IsTofu {
// extract the tarballs of the Zarf Packages within the bundle
if err := bndlClient.Extract(bndlClient.GetDefaultExtractPath()); err != nil {
return fmt.Errorf("failed to extract packages from budnle: %s", err.Error())
}

// Determine the location of the local tfstate file
stateFilepath, err := filepath.Abs(bndlClient.GetTofuStateFilepath())
if err != nil {
return fmt.Errorf("failed to locate path to tfstate file: %s", err.Error())
}
stateFlag := fmt.Sprintf("-state=%s", stateFilepath)

// Determine the location of the extracted *.tf files
chdirFlag := fmt.Sprintf("-chdir=%s", filepath.Dir(bndlClient.GetDefaultExtractPath()))

// Run the `tofu apply` command
os.Args = []string{"tofu", chdirFlag, "apply", "-input=false", "-auto-approve", stateFlag}
err = useEmbeddedTofu()
if err != nil {
message.Warnf("unable to deploy bundle that was built from a .tf file: %s", err.Error())
return err
}
} else {
if err := bndlClient.Deploy(); err != nil {
bndlClient.ClearPaths()
return fmt.Errorf("failed to deploy bundle: %s", err.Error())
}
}

return nil
Expand Down
44 changes: 41 additions & 3 deletions src/cmd/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,42 @@ var devCmd = &cobra.Command{
Short: lang.CmdDevShort,
}

var tofuDeployCmd = &cobra.Command{
Use: "tofu-deploy [BUNDLE_TARBALL|OCI_REF]",
Aliases: []string{"d"},
Short: lang.CmdBundleDeployShort,
Args: cobra.MaximumNArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
config.CommonOptions.Confirm = true

var err error
bundleCfg.DeployOpts.Source, err = chooseBundle(args)
if err != nil {
return err
}
configureZarf()

// set DeployOptions.Config if exists
if config := v.ConfigFileUsed(); config != "" {
bundleCfg.DeployOpts.Config = config
}

bundleCfg.TofuOpts.IsTofu = true

// create new bundle client and deploy
bndlClient, err := bundle.New(&bundleCfg)
if err != nil {
return err
}
defer bndlClient.ClearPaths()
err = deploy(bndlClient)
if err != nil {
return err
}
return nil
},
}

var tofuCreateCmd = &cobra.Command{
Use: "tofu-create [DIRECTORY]",
Aliases: []string{"c"},
Expand All @@ -44,7 +80,7 @@ var tofuCreateCmd = &cobra.Command{
srcDir = args[0]
}
bundleCfg.CreateOpts.SourceDirectory = srcDir
bundleCfg.IsTofu = true
bundleCfg.TofuOpts.IsTofu = true

bndlClient, err := bundle.New(&bundleCfg)
if err != nil {
Expand Down Expand Up @@ -163,7 +199,7 @@ var extractCmd = &cobra.Command{
return err
}

if bundleCfg.IsTofu {
if bundleCfg.TofuOpts.IsTofu {
_, _, _, err = bndlClient.PreDeployValidationTF()
} else {
_, _, _, err = bndlClient.PreDeployValidation()
Expand Down Expand Up @@ -230,6 +266,8 @@ func init() {
devDeployCmd.Flags().StringToStringVar(&bundleCfg.DeployOpts.SetVariables, "set", nil, lang.CmdBundleDeployFlagSet)

devCmd.AddCommand(extractCmd)
extractCmd.Flags().BoolVar(&bundleCfg.IsTofu, "is-tofu", false, "indicates if the package was built from a uds-bundle.tf")
extractCmd.Flags().BoolVar(&bundleCfg.TofuOpts.IsTofu, "is-tofu", false, "indicates if the package was built from a uds-bundle.tf")
devCmd.AddCommand(tofuCreateCmd)
devCmd.AddCommand(tofuDeployCmd)
tofuDeployCmd.Flags().StringVar(&bundleCfg.TofuOpts.TFStateFilepath, "tf-state", "terraform.tfstate", "Path to TF statefile")
}
4 changes: 4 additions & 0 deletions src/pkg/bundle/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,3 +379,7 @@ func GetDeployedPackageNames() []string {
}
return deployedPackageNames
}

func (b *Bundle) GetTofuStateFilepath() string {
return b.cfg.TofuOpts.TFStateFilepath
}
8 changes: 4 additions & 4 deletions src/pkg/bundle/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import (
// Create creates a bundle
func (b *Bundle) Create() error {
// populate the b.bundle struct with information on the packages we are creating
if b.cfg.IsTofu {
if b.cfg.TofuOpts.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)
Expand All @@ -45,8 +45,8 @@ func (b *Bundle) Create() error {
for _, pkg := range tfConfig.Packages {
newPackage := types.Package{
Name: pkg.Name,
Repository: pkg.OCIUrl,
Ref: pkg.Ref,
Repository: pkg.Repository,
Ref: pkg.Version,
}
b.bundle.Packages = append(b.bundle.Packages, newPackage)
}
Expand Down Expand Up @@ -133,7 +133,7 @@ func (b *Bundle) Create() error {
Output: b.cfg.CreateOpts.Output,
TmpDstDir: b.tmp,
SourceDir: b.cfg.CreateOpts.SourceDirectory,
IsTofu: b.cfg.IsTofu,
IsTofu: b.cfg.TofuOpts.IsTofu,
}
bundlerClient := bundler.NewBundler(&opts)

Expand Down
8 changes: 8 additions & 0 deletions src/pkg/bundle/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,14 @@ func (b *Bundle) PreDeployValidationTF() (string, string, string, error) {
if err != nil {
return "", "", "", err
}

// Copy the .tf contents from the file at `/{tmpdir}/blobs/sha256/{SHASUM}` to `/{tmpdir}/main.tf`
// This makes it a lot easier for tofu to find and use the config file
err = os.WriteFile(filepath.Join(b.tmp, "main.tf"), bundleTF, 0600)
if err != nil {
return "", "", "", err
}

// parse the tf config
tfConfig, err := tfparser.ParseFile(filepaths[config.BundleTF])
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions src/pkg/bundle/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ func (b *Bundle) extractPackage(path string, pkg types.Package) error {
return nil
}

func (b *Bundle) GetDefaultExtractPath() string {
return filepath.Join(b.tmp, "extracted-packages")
}

func (b *Bundle) Extract(path string) error {
// Create the directory that we will store the extract tarballs into
if err := helpers.CreateDirectory(path, helpers.ReadWriteExecuteUser); err != nil {
Expand Down
40 changes: 30 additions & 10 deletions src/pkg/tfparser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@ type Provider struct {

// Packages represents a uds_package resource in Terraform
type Packages struct {
Name string `json:"name"`
Type string `json:"type"`
OCIUrl string `json:"oci_url"`
Ref string `json:"ref,omitempty"`
Name string `json:"name"`
Type string `json:"type"`
Repository string `json:"repository"`
Version string `json:"version,omitempty"`
Path string `json:"path,omitempty"`

Kind string `json:"kind"`
Components []string `json:"components"`
}

// TerraformConfig represents the root Terraform configuration
Expand All @@ -39,7 +43,10 @@ type BundleMetadata struct {
Version string `json:"version"`
// Kind reflects the type of package; typicaly always UDSBundle
Kind string `json:"kind"`

// these are optional
// TODO: Even if these are optional, these should probably be set to empty string so
// that panics don't happen if someone attempts to dereference them in the future?
Description *string `json:"description"`
URL *string `json:"url"`
Architecture *string `json:"architecture"`
Expand Down Expand Up @@ -95,6 +102,11 @@ func ParseFile(filename string) (*TerraformConfig, error) {
}
}

// After parsing the bundle config, we should expect to have parsed at least one 'uds_package' resource
if len(config.Packages) == 0 {
return config, fmt.Errorf("expected to parse a uds_package but none found")
}

return config, nil
}

Expand Down Expand Up @@ -168,20 +180,28 @@ func parseUDSPackageBlock(block *hcl.Block) (*Packages, error) {

ctx := &hcl.EvalContext{}

if attr, exists := attrs["oci_url"]; exists {
if attr, exists := attrs["repository"]; exists {
value, diags := attr.Expr.Value(ctx)
if diags.HasErrors() {
return nil, fmt.Errorf("oci_url error: %s", diags.Error())
return nil, fmt.Errorf("repository error: %s", diags.Error())
}
pkg.OCIUrl = value.AsString()
pkg.Repository = value.AsString()
}

if attr, exists := attrs["ref"]; exists {
if attr, exists := attrs["version"]; exists {
value, diags := attr.Expr.Value(ctx)
if diags.HasErrors() {
return nil, fmt.Errorf("ref error: %s", diags.Error())
return nil, fmt.Errorf("version error: %s", diags.Error())
}
pkg.Version = value.AsString()
}

if attr, exists := attrs["path"]; exists {
value, diags := attr.Expr.Value(ctx)
if diags.HasErrors() {
return nil, fmt.Errorf("path error: %s", diags.Error())
}
pkg.Ref = value.AsString()
pkg.Path = value.AsString()
}

return pkg, nil
Expand Down
Loading
Loading