caba88128e
This PR adjusts the artifact sandbox on Linux to enable reading from known system-wide git or mercurial configuration, if they exist. Folks doing something odd like specifying custom paths for global config will need to use the standard locations, or disable artifact filesystem isolation.
217 lines
5.1 KiB
Go
217 lines
5.1 KiB
Go
package getter
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"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/go-landlock"
|
|
"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("replacments", 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)
|
|
|
|
t.Run("default", func(t *testing.T) {
|
|
result := environment("/a/b/c", "")
|
|
must.Eq(t, []string{
|
|
"PATH=/usr/local/bin:/usr/bin:/bin",
|
|
"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)
|
|
})
|
|
}
|
|
|
|
func TestUtil_loadVersionControlGlobalConfigs(t *testing.T) {
|
|
const filePerm = 0o644
|
|
const dirPerm = 0o755
|
|
fakeEtc := t.TempDir()
|
|
|
|
var (
|
|
gitFile = filepath.Join(fakeEtc, "gitconfig")
|
|
hgFile = filepath.Join(fakeEtc, "hgrc")
|
|
hgDir = filepath.Join(fakeEtc, "hgrc.d")
|
|
)
|
|
|
|
err := os.WriteFile(gitFile, []byte("git"), filePerm)
|
|
must.NoError(t, err)
|
|
|
|
err = os.WriteFile(hgFile, []byte("hg"), filePerm)
|
|
must.NoError(t, err)
|
|
|
|
err = os.Mkdir(hgDir, dirPerm)
|
|
must.NoError(t, err)
|
|
|
|
paths := loadVersionControlGlobalConfigs(gitFile, hgFile, hgDir)
|
|
must.SliceEqual(t, []*landlock.Path{
|
|
landlock.File(gitFile, "r"),
|
|
landlock.File(hgFile, "r"),
|
|
landlock.Dir(hgDir, "r"),
|
|
}, paths)
|
|
}
|