From 493389e861d50690f2c26029f3286d963d7b52cd Mon Sep 17 00:00:00 2001 From: Seth Hoenig Date: Fri, 9 Dec 2022 15:46:07 -0600 Subject: [PATCH] artifact: enable inheriting environment variables from client (#15514) * artifact: enable inheriting environment variables from client This PR adds client configuration for specifying environment variables that should be inherited by the artifact sandbox process from the Nomad Client agent. Most users should not need to set these values but the configuration is provided to ensure backwards compatability. Configuration of go-getter should ideally be done through the artifact block in a jobspec task. e.g. ```hcl client { artifact { set_environment_variables = "TMPDIR,GIT_SSH_OPTS" } } ``` Closes #15498 * website: update set_environment_variables text to mention PATH --- .../allocrunner/taskrunner/getter/params.go | 3 + .../taskrunner/getter/params_test.go | 1 + .../allocrunner/taskrunner/getter/sandbox.go | 1 + client/allocrunner/taskrunner/getter/util.go | 25 ++++++- .../taskrunner/getter/util_default.go | 13 ++-- .../taskrunner/getter/util_linux.go | 12 ++- .../taskrunner/getter/util_test.go | 70 +++++++++++++----- .../taskrunner/getter/util_windows.go | 18 ++--- client/config/artifact.go | 2 + client/config/artifact_test.go | 74 ++++++++++--------- nomad/structs/config/artifact.go | 16 ++++ nomad/structs/config/artifact_test.go | 21 ++++++ website/content/docs/configuration/client.mdx | 5 ++ .../content/docs/upgrade/upgrade-specific.mdx | 9 ++- 14 files changed, 192 insertions(+), 78 deletions(-) diff --git a/client/allocrunner/taskrunner/getter/params.go b/client/allocrunner/taskrunner/getter/params.go index e522f04ac..ba2c47db3 100644 --- a/client/allocrunner/taskrunner/getter/params.go +++ b/client/allocrunner/taskrunner/getter/params.go @@ -28,6 +28,7 @@ type parameters struct { HgTimeout time.Duration `json:"hg_timeout"` S3Timeout time.Duration `json:"s3_timeout"` DisableFilesystemIsolation bool `json:"disable_filesystem_isolation"` + SetEnvironmentVariables string `json:"set_environment_variables"` // Artifact Mode getter.ClientMode `json:"artifact_mode"` @@ -88,6 +89,8 @@ func (p *parameters) Equal(o *parameters) bool { return false case p.DisableFilesystemIsolation != o.DisableFilesystemIsolation: return false + case p.SetEnvironmentVariables != o.SetEnvironmentVariables: + return false case p.Mode != o.Mode: return false case p.Source != o.Source: diff --git a/client/allocrunner/taskrunner/getter/params_test.go b/client/allocrunner/taskrunner/getter/params_test.go index b86c34239..6a92d25de 100644 --- a/client/allocrunner/taskrunner/getter/params_test.go +++ b/client/allocrunner/taskrunner/getter/params_test.go @@ -20,6 +20,7 @@ const paramsAsJSON = ` "hg_timeout": 4000000000, "s3_timeout": 5000000000, "disable_filesystem_isolation": true, + "set_environment_variables": "", "artifact_mode": 2, "artifact_source": "https://example.com/file.txt", "artifact_destination": "local/out.txt", diff --git a/client/allocrunner/taskrunner/getter/sandbox.go b/client/allocrunner/taskrunner/getter/sandbox.go index 4f1f40423..46f093834 100644 --- a/client/allocrunner/taskrunner/getter/sandbox.go +++ b/client/allocrunner/taskrunner/getter/sandbox.go @@ -47,6 +47,7 @@ func (s *Sandbox) Get(env interfaces.EnvReplacer, artifact *structs.TaskArtifact HgTimeout: s.ac.HgTimeout, S3Timeout: s.ac.S3Timeout, DisableFilesystemIsolation: s.ac.DisableFilesystemIsolation, + SetEnvironmentVariables: s.ac.SetEnvironmentVariables, // artifact configuration Mode: mode, diff --git a/client/allocrunner/taskrunner/getter/util.go b/client/allocrunner/taskrunner/getter/util.go index df7318b55..e83dbdbb5 100644 --- a/client/allocrunner/taskrunner/getter/util.go +++ b/client/allocrunner/taskrunner/getter/util.go @@ -5,9 +5,12 @@ import ( "fmt" "net/http" "net/url" + "os" "os/exec" "path/filepath" + "sort" "strings" + "unicode" "github.com/hashicorp/go-getter" "github.com/hashicorp/nomad/client/interfaces" @@ -95,6 +98,26 @@ func getTaskDir(env interfaces.EnvReplacer) string { return filepath.Dir(p) } +// environment merges the default minimal environment per-OS with the set of +// environment variables configured to be inherited from the Client +func environment(taskDir string, inherit string) []string { + chomp := func(s string) []string { + return strings.FieldsFunc(s, func(c rune) bool { + return c == ',' || unicode.IsSpace(c) + }) + } + env := defaultEnvironment(taskDir) + for _, name := range chomp(inherit) { + env[name] = os.Getenv(name) + } + result := make([]string, 0, len(env)) + for k, v := range env { + result = append(result, fmt.Sprintf("%s=%s", k, v)) + } + sort.Strings(result) + return result +} + func (s *Sandbox) runCmd(env *parameters) error { // find the nomad process bin := subproc.Self() @@ -106,7 +129,7 @@ func (s *Sandbox) runCmd(env *parameters) error { // start the subprocess, passing in parameters via stdin output := new(bytes.Buffer) cmd := exec.CommandContext(ctx, bin, SubCommand) - cmd.Env = minimalVars(env.TaskDir) + cmd.Env = environment(env.TaskDir, env.SetEnvironmentVariables) cmd.Stdin = env.reader() cmd.Stdout = output cmd.Stderr = output diff --git a/client/allocrunner/taskrunner/getter/util_default.go b/client/allocrunner/taskrunner/getter/util_default.go index d025c09d1..9cb099681 100644 --- a/client/allocrunner/taskrunner/getter/util_default.go +++ b/client/allocrunner/taskrunner/getter/util_default.go @@ -3,7 +3,6 @@ package getter import ( - "fmt" "path/filepath" "syscall" ) @@ -27,13 +26,13 @@ func credentials() (uint32, uint32) { return uint32(uid), uint32(gid) } -// minimalVars returns the minimal environment set for artifact -// downloader sandbox -func minimalVars(taskDir string) []string { +// defaultEnvironment is the default minimal environment variables for Unix-like +// operating systems. +func defaultEnvironment(taskDir string) map[string]string { tmpDir := filepath.Join(taskDir, "tmp") - return []string{ - fmt.Sprintf("PATH=/usr/local/bin:/usr/bin:/bin"), - fmt.Sprintf("TMPDIR=%s", tmpDir), + return map[string]string{ + "PATH": "/usr/local/bin:/usr/bin:/bin", + "TMPDIR": tmpDir, } } diff --git a/client/allocrunner/taskrunner/getter/util_linux.go b/client/allocrunner/taskrunner/getter/util_linux.go index b02841ec1..d157b6166 100644 --- a/client/allocrunner/taskrunner/getter/util_linux.go +++ b/client/allocrunner/taskrunner/getter/util_linux.go @@ -3,7 +3,6 @@ package getter import ( - "fmt" "path/filepath" "syscall" @@ -49,13 +48,12 @@ func credentials() (uint32, uint32) { } } -// minimalVars returns the minimal environment set for artifact -// downloader sandbox -func minimalVars(taskDir string) []string { +// defaultEnvironment is the default minimal environment variables for Linux. +func defaultEnvironment(taskDir string) map[string]string { tmpDir := filepath.Join(taskDir, "tmp") - return []string{ - "PATH=/usr/local/bin:/usr/bin:/bin", - fmt.Sprintf("TMPDIR=%s", tmpDir), + return map[string]string{ + "PATH": "/usr/local/bin:/usr/bin:/bin", + "TMPDIR": tmpDir, } } diff --git a/client/allocrunner/taskrunner/getter/util_test.go b/client/allocrunner/taskrunner/getter/util_test.go index b637c3f8b..f201994de 100644 --- a/client/allocrunner/taskrunner/getter/util_test.go +++ b/client/allocrunner/taskrunner/getter/util_test.go @@ -2,15 +2,18 @@ package getter import ( "errors" - "runtime" "testing" "github.com/hashicorp/go-getter" + "github.com/hashicorp/nomad/ci" + "github.com/hashicorp/nomad/client/testutil" "github.com/hashicorp/nomad/nomad/structs" "github.com/shoenig/test/must" ) func TestUtil_getURL(t *testing.T) { + ci.Parallel(t) + cases := []struct { name string artifact *structs.TaskArtifact @@ -61,6 +64,8 @@ func TestUtil_getURL(t *testing.T) { } func TestUtil_getDestination(t *testing.T) { + ci.Parallel(t) + env := noopTaskEnv("/path/to/task") t.Run("ok", func(t *testing.T) { result, err := getDestination(env, &structs.TaskArtifact{ @@ -80,6 +85,8 @@ func TestUtil_getDestination(t *testing.T) { } func TestUtil_getMode(t *testing.T) { + ci.Parallel(t) + cases := []struct { mode string exp getter.ClientMode @@ -99,6 +106,8 @@ func TestUtil_getMode(t *testing.T) { } func TestUtil_getHeaders(t *testing.T) { + ci.Parallel(t) + env := upTaskEnv("/path/to/task") t.Run("empty", func(t *testing.T) { @@ -123,26 +132,53 @@ func TestUtil_getHeaders(t *testing.T) { } func TestUtil_getTaskDir(t *testing.T) { + ci.Parallel(t) + env := noopTaskEnv("/path/to/task") result := getTaskDir(env) must.Eq(t, "/path/to/task", result) } -func TestUtil_minimalVars(t *testing.T) { - var exp []string - switch runtime.GOOS { - case "windows": - exp = []string{ - `PATH=C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;`, - `TMP=C:\path\to\task\tmp`, - `TEMP=C:\path\to\task\tmp`, - } - default: - exp = []string{ +func TestUtil_environment(t *testing.T) { + // not parallel + testutil.RequireLinux(t) + + t.Run("default", func(t *testing.T) { + result := environment("/a/b/c", "") + must.Eq(t, []string{ "PATH=/usr/local/bin:/usr/bin:/bin", - "TMPDIR=/path/to/task/tmp", - } - } - result := minimalVars("/path/to/task") - must.Eq(t, exp, result) + "TMPDIR=/a/b/c/tmp", + }, result) + }) + + t.Run("append", func(t *testing.T) { + t.Setenv("ONE", "1") + t.Setenv("TWO", "2") + result := environment("/a/b/c", "ONE,TWO") + must.Eq(t, []string{ + "ONE=1", + "PATH=/usr/local/bin:/usr/bin:/bin", + "TMPDIR=/a/b/c/tmp", + "TWO=2", + }, result) + }) + + t.Run("override", func(t *testing.T) { + t.Setenv("PATH", "/opt/bin") + t.Setenv("TMPDIR", "/scratch") + result := environment("/a/b/c", "PATH,TMPDIR") + must.Eq(t, []string{ + "PATH=/opt/bin", + "TMPDIR=/scratch", + }, result) + }) + + t.Run("missing", func(t *testing.T) { + result := environment("/a/b/c", "DOES_NOT_EXIST") + must.Eq(t, []string{ + "DOES_NOT_EXIST=", + "PATH=/usr/local/bin:/usr/bin:/bin", + "TMPDIR=/a/b/c/tmp", + }, result) + }) } diff --git a/client/allocrunner/taskrunner/getter/util_windows.go b/client/allocrunner/taskrunner/getter/util_windows.go index 67c38b7ab..d1cc351ac 100644 --- a/client/allocrunner/taskrunner/getter/util_windows.go +++ b/client/allocrunner/taskrunner/getter/util_windows.go @@ -3,7 +3,6 @@ package getter import ( - "fmt" "os" "path/filepath" "syscall" @@ -24,14 +23,15 @@ func lockdown(string) error { return nil } -func minimalVars(taskDir string) []string { +// defaultEnvironment is the default minimal environment variables for Windows. +func defaultEnvironment(taskDir string) map[string]string { tmpDir := filepath.Join(taskDir, "tmp") - return []string{ - fmt.Sprintf("HOMEPATH=%s", os.Getenv("HOMEPATH")), - fmt.Sprintf("HOMEDRIVE=%s", os.Getenv("HOMEDRIVE")), - fmt.Sprintf("USERPROFILE=%s", os.Getenv("USERPROFILE")), - fmt.Sprintf("PATH=%s", os.Getenv("PATH")), - fmt.Sprintf("TMP=%s", tmpDir), - fmt.Sprintf("TEMP=%s", tmpDir), + return map[string]string{ + "HOMEPATH": os.Getenv("HOMEPATH"), + "HOMEDRIVE": os.Getenv("HOMEDRIVE"), + "USERPROFILE": os.Getenv("USERPROFILE"), + "PATH": os.Getenv("PATH"), + "TMP": tmpDir, + "TEMP": tmpDir, } } diff --git a/client/config/artifact.go b/client/config/artifact.go index b567d72e2..b8019eb07 100644 --- a/client/config/artifact.go +++ b/client/config/artifact.go @@ -20,6 +20,7 @@ type ArtifactConfig struct { S3Timeout time.Duration DisableFilesystemIsolation bool + SetEnvironmentVariables string } // ArtifactConfigFromAgent creates a new internal readonly copy of the client @@ -63,6 +64,7 @@ func ArtifactConfigFromAgent(c *config.ArtifactConfig) (*ArtifactConfig, error) HgTimeout: hgTimeout, S3Timeout: s3Timeout, DisableFilesystemIsolation: *c.DisableFilesystemIsolation, + SetEnvironmentVariables: *c.SetEnvironmentVariables, }, nil } diff --git a/client/config/artifact_test.go b/client/config/artifact_test.go index a79b4b2b7..f3ccbaa06 100644 --- a/client/config/artifact_test.go +++ b/client/config/artifact_test.go @@ -7,22 +7,22 @@ import ( "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/helper/pointer" "github.com/hashicorp/nomad/nomad/structs/config" - "github.com/stretchr/testify/require" + "github.com/shoenig/test/must" ) func TestArtifactConfigFromAgent(t *testing.T) { ci.Parallel(t) testCases := []struct { - name string - config *config.ArtifactConfig - expected *ArtifactConfig - expectedError string + name string + config *config.ArtifactConfig + exp *ArtifactConfig + expErr string }{ { name: "from default", config: config.DefaultArtifactConfig(), - expected: &ArtifactConfig{ + exp: &ArtifactConfig{ HTTPReadTimeout: 30 * time.Minute, HTTPMaxBytes: 100_000_000_000, GCSTimeout: 30 * time.Minute, @@ -41,7 +41,7 @@ func TestArtifactConfigFromAgent(t *testing.T) { HgTimeout: pointer.Of("30m"), S3Timeout: pointer.Of("30m"), }, - expectedError: "error parsing HTTPReadTimeout", + expErr: "error parsing HTTPReadTimeout", }, { name: "invalid http max size", @@ -53,7 +53,7 @@ func TestArtifactConfigFromAgent(t *testing.T) { HgTimeout: pointer.Of("30m"), S3Timeout: pointer.Of("30m"), }, - expectedError: "error parsing HTTPMaxSize", + expErr: "error parsing HTTPMaxSize", }, { name: "invalid gcs timeout", @@ -65,7 +65,7 @@ func TestArtifactConfigFromAgent(t *testing.T) { HgTimeout: pointer.Of("30m"), S3Timeout: pointer.Of("30m"), }, - expectedError: "error parsing GCSTimeout", + expErr: "error parsing GCSTimeout", }, { name: "invalid git timeout", @@ -77,7 +77,7 @@ func TestArtifactConfigFromAgent(t *testing.T) { HgTimeout: pointer.Of("30m"), S3Timeout: pointer.Of("30m"), }, - expectedError: "error parsing GitTimeout", + expErr: "error parsing GitTimeout", }, { name: "invalid hg timeout", @@ -89,7 +89,7 @@ func TestArtifactConfigFromAgent(t *testing.T) { HgTimeout: pointer.Of("invalid"), S3Timeout: pointer.Of("30m"), }, - expectedError: "error parsing HgTimeout", + expErr: "error parsing HgTimeout", }, { name: "invalid s3 timeout", @@ -101,7 +101,7 @@ func TestArtifactConfigFromAgent(t *testing.T) { HgTimeout: pointer.Of("30m"), S3Timeout: pointer.Of("invalid"), }, - expectedError: "error parsing S3Timeout", + expErr: "error parsing S3Timeout", }, } @@ -109,12 +109,12 @@ func TestArtifactConfigFromAgent(t *testing.T) { t.Run(tc.name, func(t *testing.T) { got, err := ArtifactConfigFromAgent(tc.config) - if tc.expectedError != "" { - require.Error(t, err) - require.Contains(t, err.Error(), tc.expectedError) + if tc.expErr != "" { + must.Error(t, err) + must.StrContains(t, err.Error(), tc.expErr) } else { - require.NoError(t, err) - require.Equal(t, tc.expected, got) + must.NoError(t, err) + must.Eq(t, tc.exp, got) } }) } @@ -123,18 +123,20 @@ func TestArtifactConfigFromAgent(t *testing.T) { func TestArtifactConfig_Copy(t *testing.T) { ci.Parallel(t) - config := &ArtifactConfig{ - HTTPReadTimeout: time.Minute, - HTTPMaxBytes: 1000, - GCSTimeout: 2 * time.Minute, - GitTimeout: time.Second, - HgTimeout: time.Hour, - S3Timeout: 5 * time.Minute, + ac := &ArtifactConfig{ + HTTPReadTimeout: time.Minute, + HTTPMaxBytes: 1000, + GCSTimeout: 2 * time.Minute, + GitTimeout: time.Second, + HgTimeout: time.Hour, + S3Timeout: 5 * time.Minute, + DisableFilesystemIsolation: true, + SetEnvironmentVariables: "FOO,BAR", } // make sure values are copied. - configCopy := config.Copy() - require.Equal(t, config, configCopy) + configCopy := ac.Copy() + must.Eq(t, ac, configCopy) // modify copy and make sure original doesn't change. configCopy.HTTPReadTimeout = 5 * time.Minute @@ -143,13 +145,17 @@ func TestArtifactConfig_Copy(t *testing.T) { configCopy.GitTimeout = 3 * time.Second configCopy.HgTimeout = 2 * time.Hour configCopy.S3Timeout = 10 * time.Minute + configCopy.DisableFilesystemIsolation = false + configCopy.SetEnvironmentVariables = "BAZ" - require.Equal(t, &ArtifactConfig{ - HTTPReadTimeout: time.Minute, - HTTPMaxBytes: 1000, - GCSTimeout: 2 * time.Minute, - GitTimeout: time.Second, - HgTimeout: time.Hour, - S3Timeout: 5 * time.Minute, - }, config) + must.Eq(t, &ArtifactConfig{ + HTTPReadTimeout: time.Minute, + HTTPMaxBytes: 1000, + GCSTimeout: 2 * time.Minute, + GitTimeout: time.Second, + HgTimeout: time.Hour, + S3Timeout: 5 * time.Minute, + DisableFilesystemIsolation: true, + SetEnvironmentVariables: "FOO,BAR", + }, ac) } diff --git a/nomad/structs/config/artifact.go b/nomad/structs/config/artifact.go index aa4ae9948..f21e3fee8 100644 --- a/nomad/structs/config/artifact.go +++ b/nomad/structs/config/artifact.go @@ -39,6 +39,11 @@ type ArtifactConfig struct { // artifact downloader can write only to the task sandbox directory, and can // read only from specific locations on the host filesystem. DisableFilesystemIsolation *bool `hcl:"disable_filesystem_isolation"` + + // SetEnvironmentVariables is a comma-separated list of environment + // variable names to inherit from the Nomad Client and set in the artifact + // download sandbox process. + SetEnvironmentVariables *string `hcl:"set_environment_variables"` } func (a *ArtifactConfig) Copy() *ArtifactConfig { @@ -53,6 +58,7 @@ func (a *ArtifactConfig) Copy() *ArtifactConfig { HgTimeout: pointer.Copy(a.HgTimeout), S3Timeout: pointer.Copy(a.S3Timeout), DisableFilesystemIsolation: pointer.Copy(a.DisableFilesystemIsolation), + SetEnvironmentVariables: pointer.Copy(a.SetEnvironmentVariables), } } @@ -71,6 +77,7 @@ func (a *ArtifactConfig) Merge(o *ArtifactConfig) *ArtifactConfig { HgTimeout: pointer.Merge(a.HgTimeout, o.HgTimeout), S3Timeout: pointer.Merge(a.S3Timeout, o.S3Timeout), DisableFilesystemIsolation: pointer.Merge(a.DisableFilesystemIsolation, o.DisableFilesystemIsolation), + SetEnvironmentVariables: pointer.Merge(a.SetEnvironmentVariables, o.SetEnvironmentVariables), } } } @@ -94,6 +101,8 @@ func (a *ArtifactConfig) Equal(o *ArtifactConfig) bool { return false case !pointer.Eq(a.DisableFilesystemIsolation, o.DisableFilesystemIsolation): return false + case !pointer.Eq(a.SetEnvironmentVariables, o.SetEnvironmentVariables): + return false } return true } @@ -161,6 +170,10 @@ func (a *ArtifactConfig) Validate() error { return fmt.Errorf("disable_filesystem_isolation must be set") } + if a.SetEnvironmentVariables == nil { + return fmt.Errorf("set_environment_variables must be set") + } + return nil } @@ -192,5 +205,8 @@ func DefaultArtifactConfig() *ArtifactConfig { // Toggle for disabling filesystem isolation, where available. DisableFilesystemIsolation: pointer.Of(false), + + // No environment variables are inherited from Client by default. + SetEnvironmentVariables: pointer.Of(""), } } diff --git a/nomad/structs/config/artifact_test.go b/nomad/structs/config/artifact_test.go index 41f839650..f0fb97f0d 100644 --- a/nomad/structs/config/artifact_test.go +++ b/nomad/structs/config/artifact_test.go @@ -42,6 +42,7 @@ func TestArtifactConfig_Merge(t *testing.T) { HgTimeout: pointer.Of("30m"), S3Timeout: pointer.Of("30m"), DisableFilesystemIsolation: pointer.Of(false), + SetEnvironmentVariables: pointer.Of(""), }, other: &ArtifactConfig{ HTTPReadTimeout: pointer.Of("5m"), @@ -51,6 +52,7 @@ func TestArtifactConfig_Merge(t *testing.T) { HgTimeout: pointer.Of("3m"), S3Timeout: pointer.Of("4m"), DisableFilesystemIsolation: pointer.Of(true), + SetEnvironmentVariables: pointer.Of("FOO,BAR"), }, expected: &ArtifactConfig{ HTTPReadTimeout: pointer.Of("5m"), @@ -60,6 +62,7 @@ func TestArtifactConfig_Merge(t *testing.T) { HgTimeout: pointer.Of("3m"), S3Timeout: pointer.Of("4m"), DisableFilesystemIsolation: pointer.Of(true), + SetEnvironmentVariables: pointer.Of("FOO,BAR"), }, }, { @@ -73,6 +76,7 @@ func TestArtifactConfig_Merge(t *testing.T) { HgTimeout: pointer.Of("3m"), S3Timeout: pointer.Of("4m"), DisableFilesystemIsolation: pointer.Of(true), + SetEnvironmentVariables: pointer.Of("FOO,BAR"), }, expected: &ArtifactConfig{ HTTPReadTimeout: pointer.Of("5m"), @@ -82,6 +86,7 @@ func TestArtifactConfig_Merge(t *testing.T) { HgTimeout: pointer.Of("3m"), S3Timeout: pointer.Of("4m"), DisableFilesystemIsolation: pointer.Of(true), + SetEnvironmentVariables: pointer.Of("FOO,BAR"), }, }, { @@ -94,6 +99,7 @@ func TestArtifactConfig_Merge(t *testing.T) { HgTimeout: pointer.Of("30m"), S3Timeout: pointer.Of("30m"), DisableFilesystemIsolation: pointer.Of(true), + SetEnvironmentVariables: pointer.Of("FOO,BAR"), }, other: nil, expected: &ArtifactConfig{ @@ -104,6 +110,7 @@ func TestArtifactConfig_Merge(t *testing.T) { HgTimeout: pointer.Of("30m"), S3Timeout: pointer.Of("30m"), DisableFilesystemIsolation: pointer.Of(true), + SetEnvironmentVariables: pointer.Of("FOO,BAR"), }, }, } @@ -339,6 +346,20 @@ func TestArtifactConfig_Validate(t *testing.T) { }, expErr: "s3_timeout not a valid duration", }, + { + name: "fs isolation not set", + config: func(a *ArtifactConfig) { + a.DisableFilesystemIsolation = nil + }, + expErr: "disable_filesystem_isolation must be set", + }, + { + name: "env not set", + config: func(a *ArtifactConfig) { + a.SetEnvironmentVariables = nil + }, + expErr: "set_environment_variables must be set", + }, } for _, tc := range testCases { diff --git a/website/content/docs/configuration/client.mdx b/website/content/docs/configuration/client.mdx index ea31dcb8b..827e3361c 100644 --- a/website/content/docs/configuration/client.mdx +++ b/website/content/docs/configuration/client.mdx @@ -382,6 +382,11 @@ see the [drivers documentation](/docs/drivers). isolation should be disabled for artifact downloads. Applies only to systems where filesystem isolation via [landlock] is possible (Linux kernel 5.13+). +- `set_environment_variables` `(string:"")` - Specifies a comma separated list + of environment variables that should be inherited by the artifact sandbox from + the Nomad client's environment. By default a minimal environment is set including + a `PATH` appropriate for the operating system. + ### `template` Parameters - `function_denylist` `([]string: ["plugin", "writeToFile"])` - Specifies a diff --git a/website/content/docs/upgrade/upgrade-specific.mdx b/website/content/docs/upgrade/upgrade-specific.mdx index 4eebf7ae1..4d18594d8 100644 --- a/website/content/docs/upgrade/upgrade-specific.mdx +++ b/website/content/docs/upgrade/upgrade-specific.mdx @@ -55,10 +55,12 @@ USERPROFILE= ``` Configuration of the artifact downloader should happen through the [`options`][artifact_params] -and [`headers`][artifact_params] fields of the `artifact` block. +and [`headers`][artifact_params] fields of the `artifact` block. For backwards +compatibility, the sandbox can be configured to inherit specified environment variables +from the Nomad client by setting [`set_environment_variables`][artifact_env]. The use of filesystem isolation can be disabled in Client configuration by -setting [`disable_filesystem_isolation`][fs_isolation]. +setting [`disable_filesystem_isolation`][artifact_fs_isolation]. ## Nomad 1.4.0 @@ -1580,4 +1582,5 @@ deleted and then Nomad 0.3.0 can be launched. [gh_issue]: https://github.com/hashicorp/nomad/issues/new/choose [upgrade process]: /docs/upgrade#upgrade-process [landlock]: https://docs.kernel.org/userspace-api/landlock.html -[fs_isolation]: /docs/configuration/client#disable_filesystem_isolation +[artifact_fs_isolation]: /docs/configuration/client#disable_filesystem_isolation +[artifact_env]: /docs/configuration/client#set_environment_variables