diff --git a/docs/4-user-guide/3-zarf-schema.md b/docs/4-user-guide/3-zarf-schema.md
index f9ad04f416..4577812f25 100644
--- a/docs/4-user-guide/3-zarf-schema.md
+++ b/docs/4-user-guide/3-zarf-schema.md
@@ -852,6 +852,89 @@ Must be one of:
+
+
+ shell
+
+
+
+
+ ## components > actions > onCreate > defaults > shell
+
+**Description:** (cmd only) Indicates a preference for a shell for the provided cmd to be executed in on supported operating systems
+
+| | |
+| ------------------------- | -------------------------------------------------------------------------------------------------------- |
+| **Type** | `object` |
+| **Additional properties** | [![Not allowed](https://img.shields.io/badge/Not%20allowed-red)](# "Additional Properties not allowed.") |
+| **Defined in** | #/definitions/ZarfComponentActionShell |
+
+
+
+ windows
+
+
+
+
+**Description:** (default 'powershell') Indicates a preference for the shell to use on Windows systems (note that choosing 'cmd' will turn off migrations like touch -> New-Item)
+
+| | |
+| -------- | -------- |
+| **Type** | `string` |
+
+**Examples:**
+
+
+"powershell", "cmd", "pwsh", "sh", "bash", "gsh"
+
+
+
+
+
+
+ linux
+
+
+
+
+**Description:** (default 'sh') Indicates a preference for the shell to use on Linux systems
+
+| | |
+| -------- | -------- |
+| **Type** | `string` |
+
+**Examples:**
+
+
+"sh", "bash", "fish", "zsh", "pwsh"
+
+
+
+
+
+
+ darwin
+
+
+
+
+**Description:** (default 'sh') Indicates a preference for the shell to use on macOS systems
+
+| | |
+| -------- | -------- |
+| **Type** | `string` |
+
+**Examples:**
+
+
+"sh", "bash", "fish", "zsh", "pwsh"
+
+
+
+
+
+
+
@@ -990,6 +1073,26 @@ Must be one of:
+
+
+ shell
+
+
+
+
+ ## components > actions > onCreate > before > shell
+
+**Description:** (cmd only) Indicates a preference for a shell for the provided cmd to be executed in on supported operating systems
+
+| | |
+| ------------------------- | -------------------------------------------------------------------------------------------------------- |
+| **Type** | `object` |
+| **Additional properties** | [![Not allowed](https://img.shields.io/badge/Not%20allowed-red)](# "Additional Properties not allowed.") |
+| **Same definition as** | [shell](#components_items_actions_onCreate_defaults_shell) |
+
+
+
+
setVariables
diff --git a/examples/component-actions/zarf.yaml b/examples/component-actions/zarf.yaml
index 8ec106b02e..f84b407d41 100644
--- a/examples/component-actions/zarf.yaml
+++ b/examples/component-actions/zarf.yaml
@@ -39,6 +39,9 @@ components:
- cmd: touch test-create-after.txt
- cmd: sleep 1
- cmd: echo "I can print!"
+ # prefer to run the above command in "cmd" instead of "powershell" on Windows
+ shell:
+ windows: cmd
- cmd: sleep 1
- cmd: |
echo "multiline!"
diff --git a/src/extensions/bigbang/bigbang.go b/src/extensions/bigbang/bigbang.go
index eeab77575f..e93c2e92af 100644
--- a/src/extensions/bigbang/bigbang.go
+++ b/src/extensions/bigbang/bigbang.go
@@ -43,7 +43,7 @@ func Run(tmpPaths types.ComponentPaths, c types.ZarfComponent) (types.ZarfCompon
cfg := c.Extensions.BigBang
manifests := []types.ZarfManifest{}
- err, validVersionResponse := isValidVersion(cfg.Version)
+ validVersionResponse, err := isValidVersion(cfg.Version)
if err != nil {
return c, fmt.Errorf("invalid Big Bang version: %s, parsing issue %s", cfg.Version, err)
@@ -233,11 +233,11 @@ func Run(tmpPaths types.ComponentPaths, c types.ZarfComponent) (types.ZarfCompon
}
// isValidVersion check if the version is 1.54.0 or greater.
-func isValidVersion(version string) (error, bool) {
+func isValidVersion(version string) (bool, error) {
specifiedVersion, err := semver.NewVersion(version)
if err != nil {
- return err, false
+ return false, err
}
minRequiredVersion, _ := semver.NewVersion(bbMinRequiredVersion)
@@ -246,7 +246,7 @@ func isValidVersion(version string) (error, bool) {
c, _ := semver.NewConstraint(fmt.Sprintf(">= %s-0", minRequiredVersion))
// This extension requires BB 1.54.0 or greater.
- return nil, c.Check(specifiedVersion)
+ return c.Check(specifiedVersion), nil
}
// findBBResources takes a list of yaml objects (as a string) and
diff --git a/src/extensions/bigbang/bigbang_test.go b/src/extensions/bigbang/bigbang_test.go
index e6d1167089..6f8ec30f9c 100644
--- a/src/extensions/bigbang/bigbang_test.go
+++ b/src/extensions/bigbang/bigbang_test.go
@@ -1,33 +1,34 @@
package bigbang
import (
- "github.com/stretchr/testify/assert"
"testing"
+
+ "github.com/stretchr/testify/assert"
)
func TestRequiredBigBangVersions(t *testing.T) {
// Support 1.54.0 and beyond
- err, vv := isValidVersion("1.54.0")
+ vv, err := isValidVersion("1.54.0")
assert.Equal(t, err, nil)
assert.Equal(t, vv, true)
// Do not support earlier than 1.54.0
- err, vv = isValidVersion("1.53.0")
+ vv, err = isValidVersion("1.53.0")
assert.Equal(t, err, nil)
assert.Equal(t, vv, false)
// Support for Big Bang release candidates
- err, vv = isValidVersion("1.57.0-rc.0")
+ vv, err = isValidVersion("1.57.0-rc.0")
assert.Equal(t, err, nil)
assert.Equal(t, vv, true)
// Support for Big Bang 2.0.0
- err, vv = isValidVersion("2.0.0")
+ vv, err = isValidVersion("2.0.0")
assert.Equal(t, err, nil)
assert.Equal(t, vv, true)
// Fail on non-semantic versions
- err, vv = isValidVersion("1.57b")
+ vv, err = isValidVersion("1.57b")
Expected := "Invalid Semantic Version"
if err.Error() != Expected {
t.Errorf("Error actual = %v, and Expected = %v.", err, Expected)
diff --git a/src/internal/cluster/data.go b/src/internal/cluster/data.go
index af09a79efa..d1767343c2 100644
--- a/src/internal/cluster/data.go
+++ b/src/internal/cluster/data.go
@@ -44,7 +44,7 @@ func (c *Cluster) HandleDataInjection(wg *sync.WaitGroup, data types.ZarfDataInj
}
// Get the OS shell to execute commands in
- shell, shellArgs := exec.GetOSShell()
+ shell, shellArgs := exec.GetOSShell(types.ZarfComponentActionShell{Windows: "cmd"})
if _, _, err := exec.Cmd(shell, shellArgs, "tar --version"); err != nil {
message.Error(err, "Unable to execute tar on this system. Please ensure it is installed and on your $PATH.")
diff --git a/src/pkg/packager/actions.go b/src/pkg/packager/actions.go
index f3e469b93f..66887890b0 100644
--- a/src/pkg/packager/actions.go
+++ b/src/pkg/packager/actions.go
@@ -89,7 +89,7 @@ func (p *Packager) runAction(defaultCfg types.ZarfComponentActionDefaults, actio
cfg := actionGetCfg(defaultCfg, action, vars)
- if cmd, err = actionCmdMutation(cmd); err != nil {
+ if cmd, err = actionCmdMutation(cmd, cfg.Shell); err != nil {
spinner.Errorf(err, "Error mutating command: %s", cmdEscaped)
}
@@ -102,7 +102,7 @@ func (p *Packager) runAction(defaultCfg types.ZarfComponentActionDefaults, actio
// Perform the action run.
tryCmd := func(ctx context.Context) error {
// Try running the command and continue the retry loop if it fails.
- if out, err = actionRun(ctx, cfg, cmd, spinner); err != nil {
+ if out, err = actionRun(ctx, cfg, cmd, cfg.Shell, spinner); err != nil {
return err
}
@@ -193,7 +193,7 @@ func convertWaitToCmd(wait types.ZarfComponentActionWait, timeout *int) (string,
}
// Perform some basic string mutations to make commands more useful.
-func actionCmdMutation(cmd string) (string, error) {
+func actionCmdMutation(cmd string, shellPref types.ZarfComponentActionShell) (string, error) {
binaryPath, err := os.Executable()
if err != nil {
return cmd, err
@@ -203,7 +203,7 @@ func actionCmdMutation(cmd string) (string, error) {
cmd = strings.ReplaceAll(cmd, "./zarf ", binaryPath+" ")
// Make commands 'more' compatible with Windows OS PowerShell
- if runtime.GOOS == "windows" {
+ if runtime.GOOS == "windows" && (exec.IsPowershell(shellPref.Windows) || shellPref.Windows == "") {
// Replace "touch" with "New-Item" on Windows as it's a common command, but not POSIX so not aliased by M$.
// See https://mathieubuisson.github.io/powershell-linux-bash/ &
// http://web.cs.ucla.edu/~miryung/teaching/EE461L-Spring2012/labs/posix.html for more details.
@@ -246,6 +246,10 @@ func actionGetCfg(cfg types.ZarfComponentActionDefaults, a types.ZarfComponentAc
cfg.Env = append(cfg.Env, a.Env...)
}
+ if a.Shell != nil {
+ cfg.Shell = *a.Shell
+ }
+
// Add variables to the environment.
for k, v := range vars {
// Remove # from env variable name.
@@ -259,10 +263,10 @@ func actionGetCfg(cfg types.ZarfComponentActionDefaults, a types.ZarfComponentAc
return cfg
}
-func actionRun(ctx context.Context, cfg types.ZarfComponentActionDefaults, cmd string, spinner *message.Spinner) (string, error) {
- shell, shellArgs := exec.GetOSShell()
+func actionRun(ctx context.Context, cfg types.ZarfComponentActionDefaults, cmd string, shellPref types.ZarfComponentActionShell, spinner *message.Spinner) (string, error) {
+ shell, shellArgs := exec.GetOSShell(shellPref)
- message.Debug("Running command in %s: %s", shell, cmd)
+ message.Debugf("Running command in %s: %s", shell, cmd)
execCfg := exec.Config{
Env: cfg.Env,
diff --git a/src/pkg/utils/exec/exec.go b/src/pkg/utils/exec/exec.go
index c1c000a8e8..09ca2a4794 100644
--- a/src/pkg/utils/exec/exec.go
+++ b/src/pkg/utils/exec/exec.go
@@ -14,6 +14,8 @@ import (
"os/exec"
"runtime"
"sync"
+
+ "github.com/defenseunicorns/zarf/src/types"
)
// Change terminal colors.
@@ -152,17 +154,56 @@ func LaunchURL(url string) error {
}
// GetOSShell returns the shell and shellArgs based on the current OS
-func GetOSShell() (string, string) {
+func GetOSShell(shellPref types.ZarfComponentActionShell) (string, string) {
var shell string
var shellArgs string
- if runtime.GOOS == "windows" {
+ switch runtime.GOOS {
+ case "windows":
shell = "powershell"
+ if shellPref.Windows != "" {
+ shell = shellPref.Windows
+ }
+
shellArgs = "-Command"
- } else {
+ if shell == "cmd" {
+ // Change shellArgs to /c if cmd is chosen
+ shellArgs = "/c"
+ } else if !IsPowershell(shell) {
+ // Change shellArgs to -c if a real shell is chosen
+ shellArgs = "-c"
+ }
+ case "darwin":
+ shell = "sh"
+ if shellPref.Darwin != "" {
+ shell = shellPref.Darwin
+ }
+
+ shellArgs = "-c"
+ if IsPowershell(shell) {
+ // Change shellArgs to -Command if pwsh is chosen
+ shellArgs = "-Command"
+ }
+ case "linux":
+ shell = "sh"
+ if shellPref.Linux != "" {
+ shell = shellPref.Linux
+ }
+
+ shellArgs = "-c"
+ if IsPowershell(shell) {
+ // Change shellArgs to -Command if pwsh is chosen
+ shellArgs = "-Command"
+ }
+ default:
shell = "sh"
shellArgs = "-c"
}
return shell, shellArgs
}
+
+// IsPowershell returns whether a shell name is powershell
+func IsPowershell(shellName string) bool {
+ return shellName == "powershell" || shellName == "pwsh"
+}
diff --git a/src/types/component.go b/src/types/component.go
index 5f1faaa425..53cf7d7813 100644
--- a/src/types/component.go
+++ b/src/types/component.go
@@ -133,11 +133,19 @@ type ZarfComponentActionSet struct {
// ZarfComponentActionDefaults sets the default configs for child actions
type ZarfComponentActionDefaults struct {
- Mute bool `json:"mute,omitempty" jsonschema:"description=Hide the output of commands during execution (default false)"`
- MaxTotalSeconds int `json:"maxTotalSeconds,omitempty" jsonschema:"description=Default timeout in seconds for commands (default to 0, no timeout)"`
- MaxRetries int `json:"maxRetries,omitempty" jsonschema:"description=Retry commands given number of times if they fail (default 0)"`
- Dir string `json:"dir,omitempty" jsonschema:"description=Working directory for commands (default CWD)"`
- Env []string `json:"env,omitempty" jsonschema:"description=Additional environment variables for commands"`
+ Mute bool `json:"mute,omitempty" jsonschema:"description=Hide the output of commands during execution (default false)"`
+ MaxTotalSeconds int `json:"maxTotalSeconds,omitempty" jsonschema:"description=Default timeout in seconds for commands (default to 0, no timeout)"`
+ MaxRetries int `json:"maxRetries,omitempty" jsonschema:"description=Retry commands given number of times if they fail (default 0)"`
+ Dir string `json:"dir,omitempty" jsonschema:"description=Working directory for commands (default CWD)"`
+ Env []string `json:"env,omitempty" jsonschema:"description=Additional environment variables for commands"`
+ Shell ZarfComponentActionShell `json:"shell,omitempty" jsonschema:"description=(cmd only) Indicates a preference for a shell for the provided cmd to be executed in on supported operating systems"`
+}
+
+// ZarfComponentActionShell represents the desired shell to use for a given command
+type ZarfComponentActionShell struct {
+ Windows string `json:"windows,omitempty" jsonschema:"description=(default 'powershell') Indicates a preference for the shell to use on Windows systems (note that choosing 'cmd' will turn off migrations like touch -> New-Item),example=powershell,example=cmd,example=pwsh,example=sh,example=bash,example=gsh"`
+ Linux string `json:"linux,omitempty" jsonschema:"description=(default 'sh') Indicates a preference for the shell to use on Linux systems,example=sh,example=bash,example=fish,example=zsh,example=pwsh"`
+ Darwin string `json:"darwin,omitempty" jsonschema:"description=(default 'sh') Indicates a preference for the shell to use on macOS systems,example=sh,example=bash,example=fish,example=zsh,example=pwsh"`
}
// ZarfComponentAction represents a single action to run during a zarf package operation
@@ -148,6 +156,7 @@ type ZarfComponentAction struct {
Dir *string `json:"dir,omitempty" jsonschema:"description=The working directory to run the command in (default is CWD)"`
Env []string `json:"env,omitempty" jsonschema:"description=Additional environment variables to set for the command"`
Cmd string `json:"cmd,omitempty" jsonschema:"description=The command to run. Must specify either cmd or wait for the action to do anything."`
+ Shell *ZarfComponentActionShell `json:"shell,omitempty" jsonschema:"description=(cmd only) Indicates a preference for a shell for the provided cmd to be executed in on supported operating systems"`
DeprecatedSetVariable string `json:"setVariable,omitempty" jsonschema:"description=[Deprecated] (replaced by setVariables) (onDeploy/cmd only) The name of a variable to update with the output of the command. This variable will be available to all remaining actions and components in the package.,pattern=^[A-Z0-9_]+$"`
SetVariables []ZarfComponentActionSetVariable `json:"setVariables,omitempty" jsonschema:"description=(onDeploy/cmd only) An array of variables to update with the output of the command. These variables will be available to all remaining actions and components in the package."`
Description string `json:"description,omitempty" jsonschema:"description=Description of the action to be displayed during package execution instead of the command"`
diff --git a/src/ui/lib/api-types.ts b/src/ui/lib/api-types.ts
index 15bdf0041f..89c2b3ccbb 100644
--- a/src/ui/lib/api-types.ts
+++ b/src/ui/lib/api-types.ts
@@ -400,6 +400,11 @@ export interface ZarfComponentAction {
* variables will be available to all remaining actions and components in the package.
*/
setVariables?: ZarfComponentActionSetVariable[];
+ /**
+ * (cmd only) Indicates a preference for a shell for the provided cmd to be executed in on
+ * supported operating systems
+ */
+ shell?: ZarfComponentActionShell;
/**
* Wait for a condition to be met before continuing. Must specify either cmd or wait for the
* action. See the 'zarf tools wait-for' command for more info.
@@ -423,6 +428,26 @@ export interface ZarfComponentActionSetVariable {
sensitive?: boolean;
}
+/**
+ * (cmd only) Indicates a preference for a shell for the provided cmd to be executed in on
+ * supported operating systems
+ */
+export interface ZarfComponentActionShell {
+ /**
+ * (default 'sh') Indicates a preference for the shell to use on macOS systems
+ */
+ darwin?: string;
+ /**
+ * (default 'sh') Indicates a preference for the shell to use on Linux systems
+ */
+ linux?: string;
+ /**
+ * (default 'powershell') Indicates a preference for the shell to use on Windows systems
+ * (note that choosing 'cmd' will turn off migrations like touch -> New-Item)
+ */
+ windows?: string;
+}
+
/**
* Wait for a condition to be met before continuing. Must specify either cmd or wait for the
* action. See the 'zarf tools wait-for' command for more info.
@@ -515,6 +540,11 @@ export interface ZarfComponentActionDefaults {
* Hide the output of commands during execution (default false)
*/
mute?: boolean;
+ /**
+ * (cmd only) Indicates a preference for a shell for the provided cmd to be executed in on
+ * supported operating systems
+ */
+ shell?: ZarfComponentActionShell;
}
export interface ZarfChart {
@@ -1300,6 +1330,7 @@ const typeMap: any = {
{ json: "mute", js: "mute", typ: u(undefined, true) },
{ json: "setVariable", js: "setVariable", typ: u(undefined, "") },
{ json: "setVariables", js: "setVariables", typ: u(undefined, a(r("ZarfComponentActionSetVariable"))) },
+ { json: "shell", js: "shell", typ: u(undefined, r("ZarfComponentActionShell")) },
{ json: "wait", js: "wait", typ: u(undefined, r("ZarfComponentActionWait")) },
], false),
"ZarfComponentActionSetVariable": o([
@@ -1307,6 +1338,11 @@ const typeMap: any = {
{ json: "name", js: "name", typ: "" },
{ json: "sensitive", js: "sensitive", typ: u(undefined, true) },
], false),
+ "ZarfComponentActionShell": o([
+ { json: "darwin", js: "darwin", typ: u(undefined, "") },
+ { json: "linux", js: "linux", typ: u(undefined, "") },
+ { json: "windows", js: "windows", typ: u(undefined, "") },
+ ], false),
"ZarfComponentActionWait": o([
{ json: "cluster", js: "cluster", typ: u(undefined, r("ZarfComponentActionWaitCluster")) },
{ json: "network", js: "network", typ: u(undefined, r("ZarfComponentActionWaitNetwork")) },
@@ -1328,6 +1364,7 @@ const typeMap: any = {
{ json: "maxRetries", js: "maxRetries", typ: u(undefined, 0) },
{ json: "maxTotalSeconds", js: "maxTotalSeconds", typ: u(undefined, 0) },
{ json: "mute", js: "mute", typ: u(undefined, true) },
+ { json: "shell", js: "shell", typ: u(undefined, r("ZarfComponentActionShell")) },
], false),
"ZarfChart": o([
{ json: "gitPath", js: "gitPath", typ: u(undefined, "") },
diff --git a/zarf.schema.json b/zarf.schema.json
index f786474c9a..66aa0a2474 100644
--- a/zarf.schema.json
+++ b/zarf.schema.json
@@ -316,6 +316,10 @@
"type": "string",
"description": "The command to run. Must specify either cmd or wait for the action to do anything."
},
+ "shell": {
+ "$ref": "#/definitions/ZarfComponentActionShell",
+ "description": "(cmd only) Indicates a preference for a shell for the provided cmd to be executed in on supported operating systems"
+ },
"setVariable": {
"pattern": "^[A-Z0-9_]+$",
"type": "string",
@@ -366,6 +370,11 @@
},
"type": "array",
"description": "Additional environment variables for commands"
+ },
+ "shell": {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "$ref": "#/definitions/ZarfComponentActionShell",
+ "description": "(cmd only) Indicates a preference for a shell for the provided cmd to be executed in on supported operating systems"
}
},
"additionalProperties": false,
@@ -433,6 +442,46 @@
"additionalProperties": false,
"type": "object"
},
+ "ZarfComponentActionShell": {
+ "properties": {
+ "windows": {
+ "type": "string",
+ "description": "(default 'powershell') Indicates a preference for the shell to use on Windows systems (note that choosing 'cmd' will turn off migrations like touch -\u003e New-Item)",
+ "examples": [
+ "powershell",
+ "cmd",
+ "pwsh",
+ "sh",
+ "bash",
+ "gsh"
+ ]
+ },
+ "linux": {
+ "type": "string",
+ "description": "(default 'sh') Indicates a preference for the shell to use on Linux systems",
+ "examples": [
+ "sh",
+ "bash",
+ "fish",
+ "zsh",
+ "pwsh"
+ ]
+ },
+ "darwin": {
+ "type": "string",
+ "description": "(default 'sh') Indicates a preference for the shell to use on macOS systems",
+ "examples": [
+ "sh",
+ "bash",
+ "fish",
+ "zsh",
+ "pwsh"
+ ]
+ }
+ },
+ "additionalProperties": false,
+ "type": "object"
+ },
"ZarfComponentActionWait": {
"properties": {
"cluster": {