// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package getter import ( "errors" "fmt" "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/mitchellh/go-homedir" "github.com/shoenig/test/must" ) func TestUtil_getURL(t *testing.T) { ci.Parallel(t) cases := []struct { name string artifact *structs.TaskArtifact expURL string expErr *Error }{{ name: "basic http", artifact: &structs.TaskArtifact{GetterSource: "example.com"}, expURL: "example.com", expErr: nil, }, { name: "bad url", artifact: &structs.TaskArtifact{GetterSource: "::example.com"}, expURL: "", expErr: &Error{ URL: "::example.com", Err: errors.New(`failed to parse source URL "::example.com": parse "::example.com": missing protocol scheme`), Recoverable: false, }, }, { name: "option", artifact: &structs.TaskArtifact{ GetterSource: "git::github.com/hashicorp/nomad", GetterOptions: map[string]string{"sshkey": "abc123"}, }, expURL: "git::github.com/hashicorp/nomad?sshkey=abc123", expErr: nil, }, { name: "github case", artifact: &structs.TaskArtifact{ GetterSource: "git@github.com:hashicorp/nomad.git", GetterOptions: map[string]string{"sshkey": "abc123"}, }, expURL: "git@github.com:hashicorp/nomad.git?sshkey=abc123", expErr: nil, }} env := noopTaskEnv("/path/to/task") for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { result, err := getURL(env, tc.artifact) err2, _ := err.(*Error) must.Equal(t, tc.expErr, err2) must.Eq(t, tc.expURL, result) }) } } 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{ RelativeDest: "local/downloads", }) must.NoError(t, err) must.Eq(t, "/path/to/task/local/downloads", result) }) t.Run("escapes", func(t *testing.T) { result, err := getDestination(env, &structs.TaskArtifact{ RelativeDest: "../../../../../../../etc", }) must.EqError(t, err, "artifact destination path escapes alloc directory") must.Eq(t, "", result) }) } func TestUtil_getMode(t *testing.T) { ci.Parallel(t) cases := []struct { mode string exp getter.ClientMode }{ {mode: structs.GetterModeFile, exp: getter.ClientModeFile}, {mode: structs.GetterModeDir, exp: getter.ClientModeDir}, {mode: structs.GetterModeAny, exp: getter.ClientModeAny}, } for _, tc := range cases { t.Run(tc.mode, func(t *testing.T) { artifact := &structs.TaskArtifact{GetterMode: tc.mode} result := getMode(artifact) must.Eq(t, tc.exp, result) }) } } func TestUtil_getHeaders(t *testing.T) { ci.Parallel(t) env := upTaskEnv("/path/to/task") t.Run("empty", func(t *testing.T) { result := getHeaders(env, &structs.TaskArtifact{ GetterHeaders: nil, }) must.Nil(t, result) }) t.Run("replacements", func(t *testing.T) { result := getHeaders(env, &structs.TaskArtifact{ GetterHeaders: map[string]string{ "color": "red", "number": "six", }, }) must.MapEq(t, map[string][]string{ "Color": {"RED"}, "Number": {"SIX"}, }, result) }) } func TestUtil_getTaskDir(t *testing.T) { ci.Parallel(t) env := noopTaskEnv("/path/to/alloc/task") allocDir, taskDir := getWritableDirs(env) must.Eq(t, "/path/to/alloc", allocDir) must.Eq(t, "/path/to/alloc/task", taskDir) } func TestUtil_environment(t *testing.T) { // not parallel testutil.RequireLinux(t) homedir.DisableCache = true t.Cleanup(func() { homedir.DisableCache = false }) t.Run("default", func(t *testing.T) { t.Setenv("HOME", "/test") result := environment("/a/b/c", "") must.Eq(t, []string{ "HOME=/test", "PATH=/usr/local/bin:/usr/bin:/bin", "TMPDIR=/a/b/c/tmp", }, result) }) t.Run("append", func(t *testing.T) { t.Setenv("HOME", "/test") t.Setenv("ONE", "1") t.Setenv("TWO", "2") result := environment("/a/b/c", "ONE,TWO") must.Eq(t, []string{ "HOME=/test", "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("HOME", "/test") t.Setenv("PATH", "/opt/bin") t.Setenv("TMPDIR", "/scratch") result := environment("/a/b/c", "PATH,TMPDIR") must.Eq(t, []string{ "HOME=/test", "PATH=/opt/bin", "TMPDIR=/scratch", }, result) }) t.Run("missing", func(t *testing.T) { t.Setenv("HOME", "/test") result := environment("/a/b/c", "DOES_NOT_EXIST") must.Eq(t, []string{ "DOES_NOT_EXIST=", "HOME=/test", "PATH=/usr/local/bin:/usr/bin:/bin", "TMPDIR=/a/b/c/tmp", }, result) }) t.Run("homeless non-root", func(t *testing.T) { testutil.RequireNonRoot(t) // assert we fallback via go-homdir ... userHome, err := homedir.Dir() must.NoError(t, err) // ... when HOME env var is not set, as is the case in some systemd setups t.Setenv("HOME", "") result := environment("/a/b/c", "") must.Eq(t, []string{ fmt.Sprintf("HOME=%s", userHome), "PATH=/usr/local/bin:/usr/bin:/bin", "TMPDIR=/a/b/c/tmp", }, result) }) t.Run("homeless root", func(t *testing.T) { testutil.RequireRoot(t) t.Setenv("HOME", "/root") // fake running as full root // assert we fallback via go-homdir ... userHome, err := homedir.Dir() must.NoError(t, err) // ... when HOME env var is not set, as is the case in some systemd setups t.Setenv("HOME", "") result := environment("/a/b/c", "") must.Eq(t, []string{ fmt.Sprintf("HOME=%s", userHome), "PATH=/usr/local/bin:/usr/bin:/bin", "TMPDIR=/a/b/c/tmp", }, result) }) }