Skip to content

Commit

Permalink
Bug fix: Generic Worker: Process absolute paths in task payload corre…
Browse files Browse the repository at this point in the history
…ctly

Generic Worker now evaluates absolute paths inside `mounts` (properties
`directory` and `file`) and artifacts (property `path`) correctly.  Previously
Generic Worker would effectively strip leading path separators and treat them
as relative paths inside the task directory. For example, `/tmp` would be
resolved as the relative path `tmp` from inside the task directory.  This has
now been fixed, so that both absolute and relative paths are evaluated
correctly.
  • Loading branch information
petemoore committed Nov 16, 2023
1 parent 13018a6 commit b351daf
Show file tree
Hide file tree
Showing 13 changed files with 94 additions and 33 deletions.
11 changes: 11 additions & 0 deletions changelog/issue-6689.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
audience: users
level: major
reference: issue 6689
---
Bug fix: Generic Worker now evaluates absolute paths inside `mounts`
(properties `directory` and `file`) and artifacts (property `path`) correctly.
Previously Generic Worker would effectively strip leading path separators and
treat them as relative paths inside the task directory. For example, `/tmp`
would be resolved as the relative path `tmp` from inside the task directory.
This has now been fixed, so that both absolute and relative paths are evaluated
correctly.
7 changes: 4 additions & 3 deletions workers/generic-worker/artifacts.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
tcclient "github.com/taskcluster/taskcluster/v57/clients/client-go"
"github.com/taskcluster/taskcluster/v57/clients/client-go/tcqueue"
"github.com/taskcluster/taskcluster/v57/workers/generic-worker/artifacts"
"github.com/taskcluster/taskcluster/v57/workers/generic-worker/fileutil"
)

var (
Expand Down Expand Up @@ -84,7 +85,7 @@ func (task *TaskRun) PayloadArtifacts() []artifacts.TaskArtifact {
// cause the task to fail, and the cause to be preserved in the
// error artifact.
case incomingErr != nil:
fullPath := filepath.Join(taskContext.TaskDir, subPath)
fullPath := fileutil.AbsFrom(taskContext.TaskDir, subPath)
payloadArtifacts = append(
payloadArtifacts,
&artifacts.ErrorArtifact{
Expand All @@ -105,7 +106,7 @@ func (task *TaskRun) PayloadArtifacts() []artifacts.TaskArtifact {
}
// Any error returned here should already have been handled by
// walkFn, so should be safe to ignore.
_ = filepath.Walk(filepath.Join(taskContext.TaskDir, basePath), walkFn)
_ = filepath.Walk(fileutil.AbsFrom(taskContext.TaskDir, basePath), walkFn)
}
}
return payloadArtifacts
Expand All @@ -121,7 +122,7 @@ func (task *TaskRun) PayloadArtifacts() []artifacts.TaskArtifact {
// "invalid-resource-on-worker" ErrorArtifact
// TODO: need to also handle "too-large-file-on-worker"
func resolve(base *artifacts.BaseArtifact, artifactType string, path string, contentType string, contentEncoding string) artifacts.TaskArtifact {
fullPath := filepath.Join(taskContext.TaskDir, path)
fullPath := fileutil.AbsFrom(taskContext.TaskDir, path)
fileReader, err := os.Open(fullPath)
if err != nil {
// cannot read file/dir, create an error artifact
Expand Down
29 changes: 29 additions & 0 deletions workers/generic-worker/artifacts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1032,3 +1032,32 @@ func TestObjectArtifact(t *testing.T) {
td := testTask(t)
_ = submitAndAssert(t, td, payload, "completed", "completed")
}

func TestFileArtifactWithAbsolutePath(t *testing.T) {

setup(t)
validateArtifacts(t,

// what appears in task payload
[]Artifact{
{
Expires: inAnHour,
Path: filepath.Join(cwd, filepath.Join("testdata", "SampleArtifacts", "b", "c", "d.jpg")),
Type: "file",
Name: "public/build/firefox.exe",
},
},

// what we expect to discover on file system
[]artifacts.TaskArtifact{
&artifacts.S3Artifact{
BaseArtifact: &artifacts.BaseArtifact{
Name: "public/build/firefox.exe",
Expires: inAnHour,
},
ContentType: "image/jpeg",
ContentEncoding: "identity",
Path: filepath.Join(cwd, filepath.Join("testdata", "SampleArtifacts", "b", "c", "d.jpg")),
},
})
}
16 changes: 8 additions & 8 deletions workers/generic-worker/chain_of_trust.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,15 @@ func (feature *ChainOfTrustTaskFeature) Stop(err *ExecutionErrors) {
if feature.disabled {
return
}
logFile := filepath.Join(taskContext.TaskDir, logPath)
certifiedLogFile := filepath.Join(taskContext.TaskDir, certifiedLogPath)
unsignedCert := filepath.Join(taskContext.TaskDir, unsignedCertPath)
ed25519SignedCert := filepath.Join(taskContext.TaskDir, ed25519SignedCertPath)
logFile := fileutil.AbsFrom(taskContext.TaskDir, logPath)
certifiedLogFile := fileutil.AbsFrom(taskContext.TaskDir, certifiedLogPath)
unsignedCert := fileutil.AbsFrom(taskContext.TaskDir, unsignedCertPath)
ed25519SignedCert := fileutil.AbsFrom(taskContext.TaskDir, ed25519SignedCertPath)
copyErr := copyFileContents(logFile, certifiedLogFile)
if copyErr != nil {
panic(copyErr)
}
err.add(feature.task.uploadLog(certifiedLogName, filepath.Join(taskContext.TaskDir, certifiedLogPath)))
err.add(feature.task.uploadLog(certifiedLogName, fileutil.AbsFrom(taskContext.TaskDir, certifiedLogPath)))
artifactHashes := map[string]ArtifactHash{}
for _, artifact := range feature.task.Artifacts {
// make sure SHA256 is calculated
Expand Down Expand Up @@ -188,7 +188,7 @@ func (feature *ChainOfTrustTaskFeature) Stop(err *ExecutionErrors) {
if e != nil {
panic(e)
}
err.add(feature.task.uploadLog(unsignedCertName, filepath.Join(taskContext.TaskDir, unsignedCertPath)))
err.add(feature.task.uploadLog(unsignedCertName, fileutil.AbsFrom(taskContext.TaskDir, unsignedCertPath)))

// create detached ed25519 chain-of-trust.json.sig
sig := ed25519.Sign(feature.ed25519PrivKey, certBytes)
Expand All @@ -202,8 +202,8 @@ func (feature *ChainOfTrustTaskFeature) Stop(err *ExecutionErrors) {
Name: ed25519SignedCertName,
Expires: feature.task.Definition.Expires,
},
filepath.Join(taskContext.TaskDir, ed25519SignedCertPath),
filepath.Join(taskContext.TaskDir, ed25519SignedCertPath),
fileutil.AbsFrom(taskContext.TaskDir, ed25519SignedCertPath),
fileutil.AbsFrom(taskContext.TaskDir, ed25519SignedCertPath),
"application/octet-stream",
"gzip",
),
Expand Down
15 changes: 15 additions & 0 deletions workers/generic-worker/fileutil/filepath.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package fileutil

import (
"path/filepath"
)

// AbsFrom returns path if an absolute path, otherwise the result of joining it
// to the parent path. This mimics filepath.Abs(path) without restricting
// parent to the current working directory.
func AbsFrom(parent string, path string) string {
if filepath.IsAbs(path) {
return path
}
return filepath.Join(parent, path)
}
2 changes: 1 addition & 1 deletion workers/generic-worker/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ func ensureDirContainsNFiles(t *testing.T, dir string, n int) {

func LogText(t *testing.T) string {
t.Helper()
bytes, err := os.ReadFile(filepath.Join(taskContext.TaskDir, logPath))
bytes, err := os.ReadFile(fileutil.AbsFrom(taskContext.TaskDir, logPath))
if err != nil {
t.Fatalf("Error when trying to read log file: %v", err)
}
Expand Down
8 changes: 4 additions & 4 deletions workers/generic-worker/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,7 @@ func (task *TaskRun) kill() {
}

func (task *TaskRun) createLogFile() *os.File {
absLogFile := filepath.Join(taskContext.TaskDir, logPath)
absLogFile := fileutil.AbsFrom(taskContext.TaskDir, logPath)
logFileHandle, err := os.Create(absLogFile)
if err != nil {
panic(err)
Expand Down Expand Up @@ -947,10 +947,10 @@ func (task *TaskRun) Run() (err *ExecutionErrors) {
}
task.closeLog(logHandle)
if task.Payload.Features.BackingLog {
err.add(task.uploadLog(task.Payload.Logs.Backing, filepath.Join(taskContext.TaskDir, logPath)))
err.add(task.uploadLog(task.Payload.Logs.Backing, fileutil.AbsFrom(taskContext.TaskDir, logPath)))
}
if config.CleanUpTaskDirs {
_ = os.Remove(filepath.Join(taskContext.TaskDir, logPath))
_ = os.Remove(fileutil.AbsFrom(taskContext.TaskDir, logPath))
}
}()

Expand Down Expand Up @@ -1151,7 +1151,7 @@ func PrepareTaskEnvironment() (reboot bool) {
if PlatformTaskEnvironmentSetup(taskDirName) {
return true
}
logDir := filepath.Join(taskContext.TaskDir, filepath.Dir(logPath))
logDir := fileutil.AbsFrom(taskContext.TaskDir, filepath.Dir(logPath))
err := os.MkdirAll(logDir, 0700)
if err != nil {
panic(err)
Expand Down
3 changes: 2 additions & 1 deletion workers/generic-worker/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/mcuadros/go-defaults"
"github.com/stretchr/testify/require"
"github.com/taskcluster/slugid-go/slugid"
"github.com/taskcluster/taskcluster/v57/workers/generic-worker/fileutil"
)

// Test failure should resolve as "failed"
Expand Down Expand Up @@ -152,7 +153,7 @@ func TestExecutionErrorsText(t *testing.T) {
func TestNonExecutableBinaryFailsTask(t *testing.T) {
setup(t)
commands := copyTestdataFile("ed25519_public_key")
commands = append(commands, singleCommandNoArgs(filepath.Join(taskContext.TaskDir, "ed25519_public_key"))...)
commands = append(commands, singleCommandNoArgs(fileutil.AbsFrom(taskContext.TaskDir, "ed25519_public_key"))...)
payload := GenericWorkerPayload{
Command: commands,
MaxRunTime: 10,
Expand Down
8 changes: 4 additions & 4 deletions workers/generic-worker/mounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ func (f *FileMount) FSContent() (FSContent, error) {
}

func (w *WritableDirectoryCache) Mount(taskMount *TaskMount) error {
target := filepath.Join(taskContext.TaskDir, w.Directory)
target := fileutil.AbsFrom(taskContext.TaskDir, w.Directory)
// cache already there?
if _, dirCacheExists := directoryCaches[w.CacheName]; dirCacheExists {
// bump counter
Expand Down Expand Up @@ -560,7 +560,7 @@ func (w *WritableDirectoryCache) Mount(taskMount *TaskMount) error {
func (w *WritableDirectoryCache) Unmount(taskMount *TaskMount) error {
cache := directoryCaches[w.CacheName]
cacheDir := cache.Location
taskCacheDir := filepath.Join(taskContext.TaskDir, w.Directory)
taskCacheDir := fileutil.AbsFrom(taskContext.TaskDir, w.Directory)
taskMount.Infof("Preserving cache: Moving %q to %q", taskCacheDir, cacheDir)
err := RenameCrossDevice(taskCacheDir, cacheDir)
if err != nil {
Expand Down Expand Up @@ -612,7 +612,7 @@ func (r *ReadOnlyDirectory) Mount(taskMount *TaskMount) error {
if err != nil {
return fmt.Errorf("Not able to retrieve FSContent: %v", err)
}
dir := filepath.Join(taskContext.TaskDir, r.Directory)
dir := fileutil.AbsFrom(taskContext.TaskDir, r.Directory)
err = extract(c, r.Format, dir, taskMount)
if err != nil {
return err
Expand All @@ -631,7 +631,7 @@ func (f *FileMount) Mount(taskMount *TaskMount) error {
return err
}

file := filepath.Join(taskContext.TaskDir, f.File)
file := fileutil.AbsFrom(taskContext.TaskDir, f.File)
err = decompress(fsContent, f.Format, file, taskMount)
if err != nil {
return err
Expand Down
7 changes: 4 additions & 3 deletions workers/generic-worker/mounts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,8 @@ func TestMounts(t *testing.T) {
}`),
},
&FileMount{
File: filepath.Join("preloaded", "shasums"),
// use absolute path of file
File: filepath.Join(taskContext.TaskDir, "preloaded", "shasums"),
Content: json.RawMessage(`{
"url": "https://raw.githubusercontent.com/taskcluster/testrepo/db12070fc7ea6e5d21797bf943c0b9466fb4d65e/generic-worker/shasums"
}`),
Expand Down Expand Up @@ -730,9 +731,9 @@ func TestMounts(t *testing.T) {
Format: "zip",
},

// read only directory from url
// read only directory from url with absolute path for directory
&ReadOnlyDirectory{
Directory: filepath.Join("my-task-caches", "package"),
Directory: filepath.Join(taskContext.TaskDir, "my-task-caches", "package"),
Content: json.RawMessage(`{
"url": "https://github.com/taskcluster/logserver/raw/53134a5b9cbece05752c0ecc1a6c6d7c2fbf6580/node_modules/express/node_modules/connect/node_modules/multiparty/test/fixture/file/binaryfile.tar.gz"
}`),
Expand Down
11 changes: 6 additions & 5 deletions workers/generic-worker/multiuser_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"strings"
"syscall"

"github.com/taskcluster/taskcluster/v57/workers/generic-worker/fileutil"
"github.com/taskcluster/taskcluster/v57/workers/generic-worker/host"
"github.com/taskcluster/taskcluster/v57/workers/generic-worker/process"
"github.com/taskcluster/taskcluster/v57/workers/generic-worker/runtime"
Expand Down Expand Up @@ -59,7 +60,7 @@ func deleteDir(path string) error {

func (task *TaskRun) generateCommand(index int) error {
commandName := fmt.Sprintf("command_%06d", index)
wrapper := filepath.Join(taskContext.TaskDir, commandName+"_wrapper.bat")
wrapper := fileutil.AbsFrom(taskContext.TaskDir, commandName+"_wrapper.bat")
log.Printf("Creating wrapper script: %v", wrapper)
command, err := process.NewCommand([]string{wrapper}, taskContext.TaskDir, nil, taskContext.pd)
if err != nil {
Expand All @@ -75,11 +76,11 @@ func (task *TaskRun) generateCommand(index int) error {
func (task *TaskRun) prepareCommand(index int) *CommandExecutionError {
// In order that capturing of log files works, create a custom .bat file
// for the task which redirects output to a log file...
env := filepath.Join(taskContext.TaskDir, "env.txt")
dir := filepath.Join(taskContext.TaskDir, "dir.txt")
env := fileutil.AbsFrom(taskContext.TaskDir, "env.txt")
dir := fileutil.AbsFrom(taskContext.TaskDir, "dir.txt")
commandName := fmt.Sprintf("command_%06d", index)
wrapper := filepath.Join(taskContext.TaskDir, commandName+"_wrapper.bat")
script := filepath.Join(taskContext.TaskDir, commandName+".bat")
wrapper := fileutil.AbsFrom(taskContext.TaskDir, commandName+"_wrapper.bat")
script := fileutil.AbsFrom(taskContext.TaskDir, commandName+".bat")
contents := ":: This script runs command " + strconv.Itoa(index) + " defined in TaskId " + task.TaskID + "..." + "\r\n"
contents += "@echo off\r\n"

Expand Down
6 changes: 3 additions & 3 deletions workers/generic-worker/rdp_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (l *RDPTask) createRDPArtifact() {
Username: taskContext.User.Name,
Password: taskContext.User.Password,
}
rdpInfoFile := filepath.Join(taskContext.TaskDir, rdpInfoPath)
rdpInfoFile := fileutil.AbsFrom(taskContext.TaskDir, rdpInfoPath)
err := fileutil.WriteToFileAsJSON(l.info, rdpInfoFile)
// if we can't write this, something seriously wrong, so cause worker to
// report an internal-error to sentry and crash!
Expand All @@ -100,8 +100,8 @@ func (l *RDPTask) uploadRDPArtifact() *CommandExecutionError {
// RDP info expires one day after task
Expires: tcclient.Time(time.Now().Add(time.Hour * 24)),
},
filepath.Join(taskContext.TaskDir, rdpInfoPath),
filepath.Join(taskContext.TaskDir, rdpInfoPath),
fileutil.AbsFrom(taskContext.TaskDir, rdpInfoPath),
fileutil.AbsFrom(taskContext.TaskDir, rdpInfoPath),
"application/json",
"gzip",
),
Expand Down
4 changes: 3 additions & 1 deletion workers/generic-worker/user_creation_multiuser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"runtime"
"strings"
"testing"

"github.com/taskcluster/taskcluster/v57/workers/generic-worker/fileutil"
)

func TestRunAfterUserCreation(t *testing.T) {
Expand All @@ -28,7 +30,7 @@ func TestRunAfterUserCreation(t *testing.T) {
t.Fatalf("Problem deleting old tasks: %v", err)
}
}()
fileContents, err := os.ReadFile(filepath.Join(taskContext.TaskDir, "run-after-user.txt"))
fileContents, err := os.ReadFile(fileutil.AbsFrom(taskContext.TaskDir, "run-after-user.txt"))
if err != nil {
t.Fatalf("Got error when looking for file run-after-user.txt: %v", err)
}
Expand Down

0 comments on commit b351daf

Please sign in to comment.