diff --git a/Dockerfile b/Dockerfile index d5f49cd..4a557ef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # Dockerfile used in execution of Github Action FROM gruntwork/terragrunt:0.2.0 -LABEL maintainer "Gruntwork " +LABEL maintainer="Gruntwork " ENV MISE_CONFIG_DIR=~/.config/mise ENV MISE_STATE_DIR=~/.local/state/mise diff --git a/src/main.sh b/src/main.sh index 1cfd9a6..d830111 100755 --- a/src/main.sh +++ b/src/main.sh @@ -103,18 +103,13 @@ function setup_git { function setup_permissions { local -r dir="${1}" - sudo chown -R $(whoami) /github/workspace - # Set permissions for the working directory - if [[ -f "${dir}" ]]; then - sudo chown -R $(whoami) "${dir}" - sudo chmod -R o+rw "${dir}" + # fetch the user id and group id under which the github action is running + local -r uid=$(stat -c "%u" "/github/workspace") + local -r gid=$(stat -c "%g" "/github/workspace") + if [[ -e "${dir}" ]]; then + sudo chown -R "$uid:$gid" "${dir}" + sudo chmod -R o+rw "${dir}" fi - # Set permissions for the output file - if [[ -f "${GITHUB_OUTPUT}" ]]; then - sudo chown -R $(whoami) "${GITHUB_OUTPUT}" - fi - # set permissions for .terraform directories, if any - sudo find /github/workspace -name ".terraform*" -exec chmod -R 777 {} \; } # Run INPUT_PRE_EXEC_* environment variables as Bash code @@ -219,6 +214,8 @@ function main { fi run_terragrunt "${tg_dir}" "${tg_arg_and_commands}" setup_permissions "${tg_dir}" + setup_permissions "${terragrunt_log_file}" + setup_permissions "${GITHUB_OUTPUT}" # setup permissions for the output files setup_post_exec diff --git a/terragrunt/Dockerfile b/terragrunt/Dockerfile index 1e3acf4..8584907 100644 --- a/terragrunt/Dockerfile +++ b/terragrunt/Dockerfile @@ -1,7 +1,7 @@ # Container to run Terragrunt and Terraform # Contains inside mise to allow users to install custom Terraform and Terragrunt versions FROM ubuntu:22.04 -LABEL maintainer "Gruntwork " +LABEL maintainer="Gruntwork " ARG MISE_VERSION_INSTALL=v2024.4.0 @@ -16,9 +16,9 @@ RUN apt-get update \ sudo \ && rm -rf /var/lib/apt/lists/* -RUN mkdir -p /home/runner/.ssh -COPY ./known_hosts /home/runner/.ssh/known_hosts - +RUN mkdir -p /root/.ssh +COPY ./known_hosts /root/.ssh/known_hosts +RUN chown -R root:root /root/.ssh # install mise RUN wget -q "https://github.com/jdx/mise/releases/download/${MISE_VERSION_INSTALL}/mise-${MISE_VERSION_INSTALL}-linux-x64" -O "/usr/bin/mise" \ && chmod +x "/usr/bin/mise" diff --git a/terragrunt/known_hosts b/terragrunt/known_hosts index 77a3f00..9f63205 100644 --- a/terragrunt/known_hosts +++ b/terragrunt/known_hosts @@ -1,3 +1,5 @@ gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9 github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk= bitbucket.com,bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAubiN81eDcafrgMeLzaFPsw2kNvEcqTKl/VqLat/MaB33pZy0y3rJZtnqwR2qOOvbwKZYKiEO1O6VqNEBxKvJJelCq0dTXWT5pbO2gDXC6h6QDXCaHo6pOHGPUy+YBaGQRGuSusMEASYiWunYN0vCAI8QaXnWMXNMdFP3jHAJH0eDsoiGnLPBlBp4TNm6rYI74nMzgz3B9IikW4WVK+dc8KZJZWYjAuORU3jc1c/NPskD2ASinf8v3xnfXeukU0sJ5N6m5E8VLjObPEO+mN2t/FZTMZLiFqPWc/ALSqnMnnhwrNi2rbfg/rd/IpL8Le3pSBne8+seeFVBoGqzHM9yXw== +github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl +github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg= diff --git a/test/action.go b/test/action.go index f1c6fa0..4a24064 100644 --- a/test/action.go +++ b/test/action.go @@ -7,11 +7,15 @@ import ( "github.com/gruntwork-io/terratest/modules/random" ) -func buildActionImage(t *testing.T) string { - tag := "terragrunt-action:" + random.UniqueId() +func buildImage(t *testing.T, tag, path string) { buildOptions := &docker.BuildOptions{ Tags: []string{tag}, } - docker.Build(t, "..", buildOptions) + docker.Build(t, path, buildOptions) +} + +func buildActionImage(t *testing.T) string { + tag := "terragrunt-action:" + random.UniqueId() + buildImage(t, tag, "..") return tag } diff --git a/test/action_run_test.go b/test/action_run_test.go index 87a27eb..5aa592b 100644 --- a/test/action_run_test.go +++ b/test/action_run_test.go @@ -1,10 +1,13 @@ package test import ( + "fmt" "os" "path/filepath" "testing" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/gruntwork-io/terratest/modules/files" "github.com/stretchr/testify/require" @@ -12,16 +15,19 @@ import ( "github.com/stretchr/testify/assert" ) +type ActionConfig struct { + iacName string + iacType string + iacVersion string + tgVersion string +} + func TestTerragruntAction(t *testing.T) { t.Parallel() tag := buildActionImage(t) + buildImage(t, "ssh-agent:local", "ssh-agent") - testCases := []struct { - iac_name string - iac_type string - iac_version string - tg_version string - }{ + testCases := []ActionConfig{ {"Terraform", "TF", "1.4.6", "0.46.3"}, {"OpenTofu", "TOFU", "1.6.0", "0.53.3"}, } @@ -29,86 +35,129 @@ func TestTerragruntAction(t *testing.T) { for _, tc := range testCases { tc := tc - t.Run(tc.iac_name, func(t *testing.T) { + t.Run(tc.iacName, func(t *testing.T) { t.Parallel() t.Run("testActionIsExecuted", func(t *testing.T) { t.Parallel() - testActionIsExecuted(t, tc.iac_type, tc.iac_name, tc.iac_version, tc.tg_version, tag) + testActionIsExecuted(t, tc, tag) + }) + t.Run("testActionIsExecutedSSHProject", func(t *testing.T) { + t.Parallel() + testActionIsExecutedSSHProject(t, tc, tag) }) t.Run("testOutputPlanIsUsedInApply", func(t *testing.T) { t.Parallel() - testOutputPlanIsUsedInApply(t, tc.iac_type, tc.iac_name, tc.iac_version, tc.tg_version, tag) + testOutputPlanIsUsedInApply(t, tc, tag) }) t.Run("testRunAllIsExecute", func(t *testing.T) { t.Parallel() - testRunAllIsExecuted(t, tc.iac_type, tc.iac_name, tc.iac_version, tc.tg_version, tag) + testRunAllIsExecuted(t, tc, tag) }) t.Run("testAutoApproveDelete", func(t *testing.T) { t.Parallel() - testAutoApproveDelete(t, tc.iac_type, tc.iac_name, tc.iac_version, tc.tg_version, tag) + testAutoApproveDelete(t, tc, tag) }) }) } } -func testActionIsExecuted(t *testing.T, iac_type string, iac_name string, iac_version string, tg_version string, tag string) { +func testActionIsExecuted(t *testing.T, actionConfig ActionConfig, tag string) { fixturePath := prepareFixture(t, "fixture-action-execution") - outputTF := runAction(t, tag, fixturePath, iac_type, iac_version, tg_version, "plan") - assert.Contains(t, outputTF, "You can apply this plan to save these new output values to the "+iac_name) + outputTF := runAction(t, actionConfig, false, tag, fixturePath, "plan") + assert.Contains(t, outputTF, "You can apply this plan to save these new output values to the "+actionConfig.iacName) } -func testOutputPlanIsUsedInApply(t *testing.T, iac_type string, iac_name string, iac_version string, tg_version string, tag string) { +func testActionIsExecutedSSHProject(t *testing.T, actionConfig ActionConfig, tag string) { + fixturePath := prepareFixture(t, "fixture-action-execution-ssh") + + outputTF := runAction(t, actionConfig, true, tag, fixturePath, "plan") + assert.Contains(t, outputTF, "You can apply this plan to save these new output values to the "+actionConfig.iacName) +} + +func testOutputPlanIsUsedInApply(t *testing.T, actionConfig ActionConfig, tag string) { fixturePath := prepareFixture(t, "fixture-dependencies-project") - output := runAction(t, tag, fixturePath, iac_type, iac_version, tg_version, "run-all plan -out=plan.out") - assert.Contains(t, output, "1 to add, 0 to change, 0 to destroy") + output := runAction(t, actionConfig, false, tag, fixturePath, "run-all plan -out=plan.out") + assert.Contains(t, output, "1 to add, 0 to change, 0 to destroy", actionConfig.iacName) - output = runAction(t, tag, fixturePath, iac_type, iac_version, tg_version, "run-all apply plan.out") - assert.Contains(t, output, "1 added, 0 changed, 0 destroyed") + output = runAction(t, actionConfig, false, tag, fixturePath, "run-all apply plan.out") + assert.Contains(t, output, "1 added, 0 changed, 0 destroyed", actionConfig.iacName) } -func testRunAllIsExecuted(t *testing.T, iac_type string, iac_name string, iac_version string, tg_version string, tag string) { +func testRunAllIsExecuted(t *testing.T, actionConfig ActionConfig, tag string) { fixturePath := prepareFixture(t, "fixture-dependencies-project") - output := runAction(t, tag, fixturePath, iac_type, iac_version, tg_version, "run-all plan") - assert.Contains(t, output, "1 to add, 0 to change, 0 to destroy") + output := runAction(t, actionConfig, false, tag, fixturePath, "run-all plan") + assert.Contains(t, output, "1 to add, 0 to change, 0 to destroy", actionConfig.iacName) - output = runAction(t, tag, fixturePath, iac_type, iac_version, tg_version, "run-all apply") - assert.Contains(t, output, "1 to add, 0 to change, 0 to destroy") + output = runAction(t, actionConfig, false, tag, fixturePath, "run-all apply") + assert.Contains(t, output, "1 to add, 0 to change, 0 to destroy", actionConfig.iacName) - output = runAction(t, tag, fixturePath, iac_type, iac_version, tg_version, "run-all destroy") - assert.Contains(t, output, "0 to add, 0 to change, 1 to destroy") - assert.Contains(t, output, "Destroy complete! Resources: 1 destroyed") + output = runAction(t, actionConfig, false, tag, fixturePath, "run-all destroy") + assert.Contains(t, output, "0 to add, 0 to change, 1 to destroy", actionConfig.iacName) + assert.Contains(t, output, "Destroy complete! Resources: 1 destroyed", actionConfig.iacName) } -func testAutoApproveDelete(t *testing.T, iac_type string, iac_name string, iac_version string, tg_version string, tag string) { +func testAutoApproveDelete(t *testing.T, actionConfig ActionConfig, tag string) { fixturePath := prepareFixture(t, "fixture-dependencies-project") - output := runAction(t, tag, fixturePath, iac_type, iac_version, tg_version, "run-all plan -out=plan.out") + output := runAction(t, actionConfig, false, tag, fixturePath, "run-all plan -out=plan.out") assert.Contains(t, output, "1 to add, 0 to change, 0 to destroy") - output = runAction(t, tag, fixturePath, iac_type, iac_version, tg_version, "run-all apply plan.out") - assert.Contains(t, output, "1 added, 0 changed, 0 destroyed") + output = runAction(t, actionConfig, false, tag, fixturePath, "run-all apply plan.out") + assert.Contains(t, output, "1 added, 0 changed, 0 destroyed", actionConfig.iacName) // run destroy with auto-approve - output = runAction(t, tag, fixturePath, iac_type, iac_version, tg_version, "run-all plan -destroy -out=destroy.out") - assert.Contains(t, output, "0 to add, 0 to change, 1 to destroy") + output = runAction(t, actionConfig, false, tag, fixturePath, "run-all plan -destroy -out=destroy.out") + assert.Contains(t, output, "0 to add, 0 to change, 1 to destroy", actionConfig.iacName) + + output = runAction(t, actionConfig, false, tag, fixturePath, "run-all apply -destroy destroy.out") + assert.Contains(t, output, "Resources: 0 added, 0 changed, 1 destroyed", actionConfig.iacName) - output = runAction(t, tag, fixturePath, iac_type, iac_version, tg_version, "run-all apply -destroy destroy.out") - assert.Contains(t, output, "Resources: 0 added, 0 changed, 1 destroyed") + // check that fixturePath can removed recursively + err := os.RemoveAll(fixturePath) + assert.NoError(t, err) } -func runAction(t *testing.T, tag, fixturePath, iac_type string, iac_version string, tg_version string, command string) string { +func runAction(t *testing.T, actionConfig ActionConfig, sshAgent bool, tag, fixturePath string, command string) string { + + logId := random.Random(1, 5000) opts := &docker.RunOptions{ EnvironmentVariables: []string{ - "INPUT_" + iac_type + "_VERSION=" + iac_version, - "INPUT_TG_VERSION=" + tg_version, + "INPUT_" + actionConfig.iacType + "_VERSION=" + actionConfig.iacVersion, + "INPUT_TG_VERSION=" + actionConfig.tgVersion, "INPUT_TG_COMMAND=" + command, - "INPUT_TG_DIR=/github/workspace/code", - "GITHUB_OUTPUT=/tmp/logs", + "INPUT_TG_DIR=/github/workspace", + fmt.Sprintf("GITHUB_OUTPUT=/tmp/github-action-logs.%d", logId), }, - Volumes: []string{fixturePath + ":/github/workspace/code"}, + Volumes: []string{ + fixturePath + ":/github/workspace", + }, + } + + // start ssh-agent container with SSH keys to allow clones over SSH + if sshAgent { + homeDir, err := os.UserHomeDir() + assert.NoError(t, err) + sshPath := filepath.Join(homeDir, ".ssh") + + socketId := random.Random(1, 5000) + socketPath := fmt.Sprintf("/tmp/ssh-agent.sock.%d", socketId) + sshAgentID := docker.RunAndGetID(t, "ssh-agent:local", &docker.RunOptions{ + Detach: true, + Remove: true, + EnvironmentVariables: []string{ + "SSH_AUTH_SOCK=" + socketPath, + }, + Volumes: []string{ + "/tmp:/tmp", + sshPath + ":/root/keys", + }, + }) + defer docker.Stop(t, []string{sshAgentID}, &docker.StopOptions{}) + opts.Volumes = append(opts.Volumes, "/tmp:/tmp") + opts.EnvironmentVariables = append(opts.EnvironmentVariables, "SSH_AUTH_SOCK="+socketPath) } return docker.Run(t, tag, opts) } @@ -116,17 +165,5 @@ func runAction(t *testing.T, tag, fixturePath, iac_type string, iac_version stri func prepareFixture(t *testing.T, fixtureDir string) string { path, err := files.CopyTerraformFolderToTemp(fixtureDir, "test") require.NoError(t, err) - // chmod recursive for docker run - - err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - return os.Chmod(path, 0777) - }) - require.NoError(t, err) - - err = os.Chmod(path, 0777) - require.NoError(t, err) return path } diff --git a/test/fixture-action-execution-ssh/terragrunt.hcl b/test/fixture-action-execution-ssh/terragrunt.hcl new file mode 100644 index 0000000..2e95587 --- /dev/null +++ b/test/fixture-action-execution-ssh/terragrunt.hcl @@ -0,0 +1,7 @@ +inputs = { + name = "World" +} + +terraform { + source = "git@github.com:gruntwork-io/terragrunt.git//test/fixture-download/hello-world?ref=v0.9.9" +} diff --git a/test/ssh-agent/Dockerfile b/test/ssh-agent/Dockerfile new file mode 100644 index 0000000..23a42bd --- /dev/null +++ b/test/ssh-agent/Dockerfile @@ -0,0 +1,11 @@ +FROM ubuntu:22.04 + +RUN apt-get update && \ + apt-get install -y openssh-client && \ + rm -rf /var/lib/apt/lists/* + +RUN mkdir /ssh-agent + +ENV SSH_AUTH_SOCK /tmp/ssh-agent.sock +ADD run.sh /run.sh +CMD ["/run.sh"] diff --git a/test/ssh-agent/run.sh b/test/ssh-agent/run.sh new file mode 100755 index 0000000..420fcc3 --- /dev/null +++ b/test/ssh-agent/run.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Script to run ssh-agent in the background and add ssh keys +set -x + +cleanup() { + echo "Caught SIGTERM signal! Cleaning up..." + ssh-agent -k + exit 0 +} + +trap cleanup SIGTERM SIGINT + +mkdir -p ~/.ssh +cp -rfv ~/keys/* ~/.ssh/ +chown -R $(whoami) ~/.ssh + +# delete socket if exists +if [[ -S "$SSH_AUTH_SOCK" ]]; then + rm -f "$SSH_AUTH_SOCK" +fi + +eval "$(ssh-agent -s -a "$SSH_AUTH_SOCK")" +ssh-add ~/.ssh/* + +echo "SSH agent running on: $SSH_AUTH_SOCK" +tail -f /dev/null +