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

Add ability to deploy remote packages #143

Merged
merged 6 commits into from
Nov 4, 2021
Merged
Show file tree
Hide file tree
Changes from 5 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
11 changes: 8 additions & 3 deletions cli/cmd/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
var confirmCreate bool
var confirmDeploy bool
var deployComponents string
var insecureDeploy bool
var shasum string

var packageCmd = &cobra.Command{
Use: "package",
Expand All @@ -28,17 +30,18 @@ var packageCreateCmd = &cobra.Command{

var packageDeployCmd = &cobra.Command{
Use: "deploy PACKAGE",
Short: "deploys an update package file (runs offline)",
Short: "Deploys an update package from a local file or URL (runs offline)",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
packageName := choosePackage(args)
packager.Deploy(packageName, confirmDeploy, deployComponents)
localPackagePath := packager.HandleIfURL(packageName, shasum, insecureDeploy)
packager.Deploy(localPackagePath, confirmDeploy, deployComponents)
},
}

var packageInspectCmd = &cobra.Command{
Use: "inspect PACKAGE",
Short: "lists the paylod of an update package file (runs offline)",
Short: "lists the payload of an update package file (runs offline)",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
packageName := choosePackage(args)
Expand Down Expand Up @@ -71,4 +74,6 @@ func init() {
packageCreateCmd.Flags().BoolVar(&confirmCreate, "confirm", false, "Confirm package creation without prompting")
packageDeployCmd.Flags().BoolVar(&confirmDeploy, "confirm", false, "Confirm package deployment without prompting")
packageDeployCmd.Flags().StringVar(&deployComponents, "components", "", "Comma-separated list of components to install. Adding this flag will skip the init prompts for which components to install")
packageDeployCmd.Flags().BoolVar(&insecureDeploy, "insecure", false, "Skip shasum validation of remote package. Required if deploying a remote package and `--shasum` is not provided")
packageDeployCmd.Flags().StringVar(&shasum, "shasum", "", "Shasum of the package to deploy. Required if deploying a remote package and `--insecure` is not provided")
}
4 changes: 3 additions & 1 deletion cli/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ const PackagePrefix = "zarf-package-"
const ZarfLocalIP = "127.0.0.1"
const ZarfGitUser = "zarf-git-user"

var config ZarfConfig
var CLIVersion = "unset"
var ValidPackageExtensions = [...]string{".tar.zst", ".tar", ".zip"}

var config ZarfConfig

func IsZarfInitConfig() bool {
return strings.ToLower(config.Kind) == "zarfinitconfig"
Expand Down
83 changes: 77 additions & 6 deletions cli/internal/packager/deploy.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package packager

import (
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
Expand All @@ -17,19 +22,19 @@ import (
"github.com/sirupsen/logrus"
)

func Deploy(packageName string, confirm bool, componentRequest string) {
func Deploy(packagePath string, confirm bool, componentRequest string) {
// Prevent disk pressure on smaller systems due to leaking temp files
_ = os.RemoveAll("/tmp/zarf*")
tempPath := createPaths()

if utils.InvalidPath(packageName) {
logrus.WithField("archive", packageName).Fatal("The package archive seems to be missing or unreadable.")
// Make sure the user gave us a package we can work with
if utils.InvalidPath(packagePath) {
logrus.WithField("localPackagePath", packagePath).Fatal("Was not able to find the package on the local system")
}

logrus.Info("Extracting the package, this may take a few moments")

// Extract the archive
err := archiver.Unarchive(packageName, tempPath.base)
logrus.Info("Extracting the package, this may take a few moments")
err := archiver.Unarchive(packagePath, tempPath.base)
if err != nil {
logrus.Debug(err)
logrus.Fatal("Unable to extract the package contents")
Expand Down Expand Up @@ -178,3 +183,69 @@ func deployComponents(tempPath componentPaths, assets config.ZarfComponent) {
git.PushAllDirectories(tempPath.repos)
}
}

// If provided package is a URL download it to a temp directory
func HandleIfURL(packagePath string, shasum string, insecureDeploy bool) string {
// Check if the user gave us a remote package
providedURL, err := url.Parse(packagePath)
if err != nil || providedURL.Scheme == "" || providedURL.Host == "" {
logrus.WithField("archive", packagePath).Debug("The package provided is not a remote package.")
return packagePath
}

if !insecureDeploy && shasum == "" {
logrus.Fatal("When deploying a remote package you must provide either a `--shasum` or the `--insecure` flag. Neither were provided.")
}

// Check the extension on the package is what we expect
if !isValidFileExtension(providedURL.Path) {
logrus.Fatalf("Only %s file extensions are permitted.\n", config.ValidPackageExtensions)
}

// Download the package
resp, err := http.Get(packagePath)
if err != nil {
logrus.Fatal("Unable to download the package: ", err)
}
defer resp.Body.Close()

// Write the package to a local file
tempPath := createPaths()
localPackagePath := tempPath.base + providedURL.Path
logrus.Debug("Creating local package with the path: ", localPackagePath)
packageFile, _ := os.Create(localPackagePath)
_, err = io.Copy(packageFile, resp.Body)
if err != nil {
logrus.Debug(err)
logrus.Fatal("Unable to copy the contents of the provided URL into a local file.")
}

// Check the shasum if necessary
if !insecureDeploy {
hasher := sha256.New()
_, err = io.Copy(hasher, packageFile)
if err != nil {
logrus.Debug(err)
logrus.Fatal("Unable to calculate the sha256 of the provided remote package.")
}

value := hex.EncodeToString(hasher.Sum(nil))
if value != shasum {
os.Remove(localPackagePath)
logrus.Fatalf("Provided shasum (%s) of the package did not match what was downloaded (%s)\n", shasum, value)
}
}

return localPackagePath
}

func isValidFileExtension(filename string) bool {
for _, extension := range config.ValidPackageExtensions {
if strings.HasSuffix(filename, extension) {
logrus.WithField("packagePath", filename).Warn("Package extension is valid.")
return true
}
}

return false
}
13 changes: 12 additions & 1 deletion test/e2e/e2e_general_cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package test

import (
"fmt"
"testing"

"github.com/gruntwork-io/terratest/modules/aws"
"github.com/gruntwork-io/terratest/modules/ssh"
"github.com/gruntwork-io/terratest/modules/terraform"
teststructure "github.com/gruntwork-io/terratest/modules/test-structure"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)

func TestGeneralCli(t *testing.T) {
Expand Down Expand Up @@ -91,4 +92,14 @@ func testGeneralCliStuff(t *testing.T, terraformOptions *terraform.Options, keyP
require.Error(t, err, output)
output, err = ssh.CheckSshCommandE(t, publicHost, fmt.Sprintf("cd /home/%s/build && ./zarf pki regenerate --host some_unique_server", username))
require.Error(t, err, output)

// Test that `zarf package deploy` doesn't die when given a URL
output, err = ssh.CheckSshCommandE(t, publicHost, fmt.Sprintf("sudo bash -c 'cd /home/%s/build && ./zarf package deploy https://zarf-examples.s3.amazonaws.com/zarf-package-appliance-demo-doom.tar.zst --confirm --insecure'", username))
require.NoError(t, err, output)
output, err = ssh.CheckSshCommandE(t, publicHost, fmt.Sprintf("sudo bash -c 'cd /home/%s/build && ./zarf package deploy https://zarf-examples.s3.amazonaws.com/zarf-package-appliance-demo-doom.tar.zst --confirm --shasum e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'", username))
require.NoError(t, err, output)

// Test that `zarf package deploy` gives an error if deploying a remote package without the --insecure or --shasum flags
output, err = ssh.CheckSshCommandE(t, publicHost, fmt.Sprintf("sudo bash -c 'cd /home/%s/build && ./zarf package deploy https://zarf-examples.s3.amazonaws.com/zarf-package-appliance-demo-doom.tar.zst --confirm'", username))
YrrepNoj marked this conversation as resolved.
Show resolved Hide resolved
require.Error(t, err, output)
}