Skip to content
This repository has been archived by the owner on May 8, 2024. It is now read-only.

CIRRUS_CLONE_DIR #130

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ task:
task:
name: Test (Linux)
alias: test-linux
env:
CIRRUS_WORKING_DIR: /tmp/custom-working-dir
modules_cache:
fingerprint_script: cat go.sum
folder: $GOPATH/pkg/mod
Expand All @@ -40,6 +42,8 @@ docker_builder:
task:
name: Test (macOS)
alias: test-macos
env:
CIRRUS_WORKING_DIR: /tmp/custom-working-dir
macos_instance:
image: big-sur-base
test_script:
Expand Down
73 changes: 51 additions & 22 deletions internal/executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ func NewExecutor(
}
}

func (executor *Executor) SetPreCreatedWorkingDir(dir string) {
executor.preCreatedWorkingDir = dir
}

func (executor *Executor) RunBuild() {
log.Println("Getting initial commands...")
response, err := client.CirrusClient.InitialCommands(context.Background(), &api.InitialCommandsRequest{
Expand All @@ -99,7 +103,7 @@ func (executor *Executor) RunBuild() {
return
}

environment := getExpandedScriptEnvironment(executor, response.Environment)
environment := executor.GetExpandedScriptEnvironment(response.Environment)

workingDir, ok := environment["CIRRUS_WORKING_DIR"]
if ok {
Expand Down Expand Up @@ -178,10 +182,8 @@ func BoundedCommands(commands []*api.Command, fromName, toName string) []*api.Co
return commands[left:right]
}

func getExpandedScriptEnvironment(executor *Executor, responseEnvironment map[string]string) map[string]string {
if responseEnvironment == nil {
responseEnvironment = make(map[string]string)
}
func (executor *Executor) GetExpandedScriptEnvironment(responseEnvironment map[string]string) map[string]string {
responseEnvironment = executor.PopulateCloneAndWorkingDirEnvironmentVariables(responseEnvironment)

if _, ok := responseEnvironment["OS"]; !ok {
if _, ok := os.LookupEnv("OS"); !ok {
Expand All @@ -190,28 +192,54 @@ func getExpandedScriptEnvironment(executor *Executor, responseEnvironment map[st
}
responseEnvironment["CIRRUS_OS"] = runtime.GOOS

// Use directory created by the persistent worker if CIRRUS_WORKING_DIR
// was not overridden in the task specification by the user
_, hasWorkingDir := responseEnvironment["CIRRUS_WORKING_DIR"]
if !hasWorkingDir && executor.preCreatedWorkingDir != "" {
responseEnvironment["CIRRUS_WORKING_DIR"] = executor.preCreatedWorkingDir
return expandEnvironmentRecursively(responseEnvironment)
}

func (executor *Executor) PopulateCloneAndWorkingDirEnvironmentVariables(environment map[string]string) map[string]string {
result := make(map[string]string)
for key, value := range environment {
result[key] = value
}

if _, ok := responseEnvironment["CIRRUS_WORKING_DIR"]; !ok {
_, hasCloneDir := result["CIRRUS_CLONE_DIR"]
_, hasWorkingDir := result["CIRRUS_WORKING_DIR"]

if hasCloneDir && !hasWorkingDir {
// Only clone was overridden. Make sure $CIRRUS_WORKING_DIR is set
result["CIRRUS_WORKING_DIR"] = "$CIRRUS_CLONE_DIR"
}
if !hasCloneDir && hasWorkingDir {
// User did override working dir
if !strings.Contains(result["CIRRUS_WORKING_DIR"], "CIRRUS_CLONE_DIR") {
// If working dir doesn't depend on clone dir then we can default clone dir to the one provided by user
result["CIRRUS_CLONE_DIR"] = "$CIRRUS_WORKING_DIR"
hasCloneDir = true
}
}

if !hasCloneDir && !hasWorkingDir {
// none of the dirs are explicitly set. Make sure they'll be the same
result["CIRRUS_WORKING_DIR"] = "$CIRRUS_CLONE_DIR"
}

if !hasCloneDir && executor.preCreatedWorkingDir != "" {
// none of the dirs are explicitly set. Make sure they'll be the same
result["CIRRUS_CLONE_DIR"] = executor.preCreatedWorkingDir
}

if _, ok := result["CIRRUS_CLONE_DIR"]; !ok {
defaultTempDirPath := filepath.Join(os.TempDir(), "cirrus-ci-build")
if _, err := os.Stat(defaultTempDirPath); os.IsNotExist(err) {
responseEnvironment["CIRRUS_WORKING_DIR"] = filepath.ToSlash(defaultTempDirPath)
result["CIRRUS_CLONE_DIR"] = filepath.ToSlash(defaultTempDirPath)
} else if executor.commandFrom != "" {
// Default folder exists and we continue execution. Therefore we need to use it.
responseEnvironment["CIRRUS_WORKING_DIR"] = filepath.ToSlash(defaultTempDirPath)
result["CIRRUS_CLONE_DIR"] = filepath.ToSlash(defaultTempDirPath)
} else {
uniqueTempDirPath, _ := ioutil.TempDir(os.TempDir(), fmt.Sprintf("cirrus-task-%d", executor.taskIdentification.TaskId))
responseEnvironment["CIRRUS_WORKING_DIR"] = filepath.ToSlash(uniqueTempDirPath)
result["CIRRUS_CLONE_DIR"] = filepath.ToSlash(uniqueTempDirPath)
}
}

result := expandEnvironmentRecursively(responseEnvironment)

return result
}

Expand Down Expand Up @@ -386,7 +414,7 @@ func (executor *Executor) CloneRepository(env map[string]string) bool {

logUploader.Write([]byte("Using built-in Git...\n"))

working_dir := env["CIRRUS_WORKING_DIR"]
cloneDir := env["CIRRUS_CLONE_DIR"]
change := env["CIRRUS_CHANGE_IN_REPO"]
branch := env["CIRRUS_BRANCH"]
pr_number, is_pr := env["CIRRUS_PR"]
Expand Down Expand Up @@ -426,7 +454,7 @@ func (executor *Executor) CloneRepository(env map[string]string) bool {
var repo *git.Repository

if is_pr {
repo, err = git.PlainInit(working_dir, false)
repo, err = git.PlainInit(cloneDir, false)
if err != nil {
logUploader.Write([]byte(fmt.Sprintf("\nFailed to init repository: %s!", err)))
return false
Expand Down Expand Up @@ -498,13 +526,14 @@ func (executor *Executor) CloneRepository(env map[string]string) bool {
}
logUploader.Write([]byte(fmt.Sprintf("\nCloning %s...\n", cloneOptions.ReferenceName)))

repo, err = git.PlainClone(working_dir, false, &cloneOptions)
EnsureFolderExists(cloneDir)
repo, err = git.PlainClone(cloneDir, false, &cloneOptions)

if err != nil && retryableCloneError(err) {
logUploader.Write([]byte(fmt.Sprintf("\nRetryable error '%s' while cloning! Trying again...", err)))
os.RemoveAll(working_dir)
EnsureFolderExists(working_dir)
repo, err = git.PlainClone(working_dir, false, &cloneOptions)
os.RemoveAll(cloneDir)
EnsureFolderExists(cloneDir)
repo, err = git.PlainClone(cloneDir, false, &cloneOptions)
}

if err != nil {
Expand Down
164 changes: 161 additions & 3 deletions internal/executor/executor_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package executor_test
package executor

import (
"github.com/cirruslabs/cirrus-ci-agent/api"
"github.com/cirruslabs/cirrus-ci-agent/internal/executor"
"github.com/stretchr/testify/require"
"os"
"runtime"
"testing"
)

Expand Down Expand Up @@ -51,7 +52,164 @@ func TestLimitCommands(t *testing.T) {

for _, example := range examples {
t.Run(example.Description, func(t *testing.T) {
require.Equal(t, example.Expected, executor.BoundedCommands(commands, example.FromName, example.ToName))
require.Equal(t, example.Expected, BoundedCommands(commands, example.FromName, example.ToName))
})
}
}

func TestPopulateCloneAndWorkingDirEnvironmentVariables(t *testing.T) {
if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
t.Skip()
return
}
tmpDirToRestore := os.Getenv("TMPDIR")
_ = os.Setenv("TMPDIR", "/tmp")
executorDefault := &Executor{}
executorPreCreated := &Executor{}
executorPreCreated.SetPreCreatedWorkingDir("/tmp/precreated-build")
examples := []struct {
Executor *Executor
Description string
Given, Expected map[string]string
}{
{
executorDefault,
"empty",
map[string]string{},
map[string]string{
"CIRRUS_CLONE_DIR": "/tmp/cirrus-ci-build",
"CIRRUS_WORKING_DIR": "$CIRRUS_CLONE_DIR",
},
},
{
executorPreCreated,
"empty (precreated)",
map[string]string{},
map[string]string{
"CIRRUS_CLONE_DIR": "/tmp/precreated-build",
"CIRRUS_WORKING_DIR": "$CIRRUS_CLONE_DIR",
},
},
{
executorDefault,
"only working",
map[string]string{
"CIRRUS_WORKING_DIR": "/tmp/foo",
},
map[string]string{
"CIRRUS_CLONE_DIR": "$CIRRUS_WORKING_DIR",
"CIRRUS_WORKING_DIR": "/tmp/foo",
},
},
{
executorPreCreated,
"only working (precreated)",
map[string]string{
"CIRRUS_WORKING_DIR": "/tmp/foo",
},
map[string]string{
"CIRRUS_CLONE_DIR": "$CIRRUS_WORKING_DIR",
"CIRRUS_WORKING_DIR": "/tmp/foo",
},
},
{
executorDefault,
"only working (monorepo)",
map[string]string{
"CIRRUS_WORKING_DIR": "$CIRRUS_CLONE_DIR/foo",
},
map[string]string{
"CIRRUS_CLONE_DIR": "/tmp/cirrus-ci-build",
"CIRRUS_WORKING_DIR": "$CIRRUS_CLONE_DIR/foo",
},
},
{
executorPreCreated,
"only working (monorepo + precreated)",
map[string]string{
"CIRRUS_WORKING_DIR": "$CIRRUS_CLONE_DIR/foo",
},
map[string]string{
"CIRRUS_CLONE_DIR": "/tmp/precreated-build",
"CIRRUS_WORKING_DIR": "$CIRRUS_CLONE_DIR/foo",
},
},
{
executorDefault,
"only clone",
map[string]string{
"CIRRUS_CLONE_DIR": "/tmp/foo",
},
map[string]string{
"CIRRUS_CLONE_DIR": "/tmp/foo",
"CIRRUS_WORKING_DIR": "$CIRRUS_CLONE_DIR",
},
},
{
executorPreCreated,
"only clone (precreated)",
map[string]string{
"CIRRUS_CLONE_DIR": "/tmp/foo",
},
map[string]string{
"CIRRUS_CLONE_DIR": "/tmp/foo",
"CIRRUS_WORKING_DIR": "$CIRRUS_CLONE_DIR",
},
},
{
executorDefault,
"both",
map[string]string{
"CIRRUS_CLONE_DIR": "/tmp/foo",
"CIRRUS_WORKING_DIR": "/tmp/foo",
},
map[string]string{
"CIRRUS_CLONE_DIR": "/tmp/foo",
"CIRRUS_WORKING_DIR": "/tmp/foo",
},
},
{
executorPreCreated,
"both (precreated)",
map[string]string{
"CIRRUS_CLONE_DIR": "/tmp/foo",
"CIRRUS_WORKING_DIR": "/tmp/foo",
},
map[string]string{
"CIRRUS_CLONE_DIR": "/tmp/foo",
"CIRRUS_WORKING_DIR": "/tmp/foo",
},
},
{
executorDefault,
"both (monorepo)",
map[string]string{
"CIRRUS_CLONE_DIR": "/tmp/foo",
"CIRRUS_WORKING_DIR": "$CIRRUS_CLONE_DIR/bar",
},
map[string]string{
"CIRRUS_CLONE_DIR": "/tmp/foo",
"CIRRUS_WORKING_DIR": "$CIRRUS_CLONE_DIR/bar",
},
},
{
executorPreCreated,
"both (monorepo + precreated)",
map[string]string{
"CIRRUS_CLONE_DIR": "/tmp/foo",
"CIRRUS_WORKING_DIR": "$CIRRUS_CLONE_DIR/bar",
},
map[string]string{
"CIRRUS_CLONE_DIR": "/tmp/foo",
"CIRRUS_WORKING_DIR": "$CIRRUS_CLONE_DIR/bar",
},
},
}

for _, example := range examples {
t.Run(example.Description, func(t *testing.T) {
require.Equal(t, example.Expected, example.Executor.PopulateCloneAndWorkingDirEnvironmentVariables(example.Given))
})
}
_ = os.Setenv("TMPDIR", tmpDirToRestore)
}