Skip to content
Open
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
24 changes: 24 additions & 0 deletions .github/workflows/runner_input_validation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Runner input validation

on:
pull_request:
paths:
- action.sh
- action.yml
- tests/runner_input_validation_test.sh
- .github/workflows/runner_input_validation.yml
push:
branches:
- main
paths:
- action.sh
- action.yml
- tests/runner_input_validation_test.sh
- .github/workflows/runner_input_validation.yml

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: tests/runner_input_validation_test.sh
36 changes: 27 additions & 9 deletions action.sh
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,22 @@ do
esac
done

function is_exact_runner_version {
[[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
}

function validate_inputs {
if [[ "${runner_ver}" != "latest" ]] && ! is_exact_runner_version "${runner_ver}"; then
echo "Invalid runner_ver: expected 'latest' or a version in X.Y.Z format" >&2
exit 1
fi

if [[ ! "${shutdown_timeout}" =~ ^[0-9]+$ || ${#shutdown_timeout} -gt 5 ]] || (( 10#${shutdown_timeout} > 86400 )); then
echo "Invalid shutdown_timeout: expected an integer from 0 through 86400" >&2
exit 1
fi
}

function gcloud_auth {
# NOTE: when --project is specified, it updates the config
echo ${service_account_key} | gcloud --project ${project_id} --quiet auth activate-service-account --key-file - &>/dev/null
Expand All @@ -179,6 +195,16 @@ function start_vm {
gcloud_auth
fi

if [[ "${actions_preinstalled}" != "true" && "${runner_ver}" == "latest" ]]; then
latest_ver=$(curl -sL https://api.github.com/repos/actions/runner/releases/latest | jq -r '.tag_name' | sed -e 's/^v//')
if ! is_exact_runner_version "$latest_ver"; then
echo "Invalid resolved runner version: expected a version in X.Y.Z format" >&2
exit 2
fi
runner_ver="$latest_ver"
echo "✅ runner_ver=latest is specified. v$latest_ver is detected as the latest version."
fi

RUNNER_TOKEN=$(curl -S -s -XPOST \
-H "authorization: Bearer ${token}" \
https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/runners/registration-token |\
Expand Down Expand Up @@ -258,15 +284,6 @@ function start_vm {
cd /actions-runner
$startup_script"
else
if [[ "$runner_ver" = "latest" ]]; then
latest_ver=$(curl -sL https://api.github.com/repos/actions/runner/releases/latest | jq -r '.tag_name' | sed -e 's/^v//')
runner_ver="$latest_ver"
echo "✅ runner_ver=latest is specified. v$latest_ver is detected as the latest version."
if [[ -z "$latest_ver" || "null" == "$latest_ver" ]]; then
echo "❌ could not retrieve the latest version of a runner"
exit 2
fi
fi
echo "✅ Startup script will install GitHub Actions v$runner_ver"
if $arm ; then
startup_script="${startup_prelude}
Expand Down Expand Up @@ -352,6 +369,7 @@ function start_vm {
}

safety_on
validate_inputs
case "$command" in
start)
start_vm
Expand Down
11 changes: 7 additions & 4 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ inputs:
This key should be created and stored as a secret. Should be JSON key.
required: false
runner_ver:
description: Version of the GitHub Runner. "latest" will resolve the latest version.
description: Version of the GitHub Runner in X.Y.Z format. "latest" will resolve the latest version.
default: "latest"
required: true
vm_name_prefix:
Expand Down Expand Up @@ -87,7 +87,7 @@ inputs:
default: cloud-platform
required: true
shutdown_timeout:
description: "Shutdown grace period (in seconds)."
description: "Shutdown grace period in seconds, from 0 through 86400."
default: 30
required: true
actions_preinstalled:
Expand Down Expand Up @@ -117,13 +117,16 @@ runs:
using: "composite"
steps:
- id: gce-github-runner-script
env:
INPUT_RUNNER_VER: ${{ inputs.runner_ver }}
INPUT_SHUTDOWN_TIMEOUT: ${{ inputs.shutdown_timeout }}
run: >
${{ github.action_path }}/action.sh
--command=start
--token=${{ inputs.token }}
--project_id=${{ inputs.project_id }}
--service_account_key='${{ inputs.service_account_key }}'
--runner_ver=${{ inputs.runner_ver }}
--runner_ver="${INPUT_RUNNER_VER}"
--vm_name_prefix=${{ inputs.vm_name_prefix }}
--machine_zone=${{ inputs.machine_zone }}
--machine_type=${{ inputs.machine_type }}
Expand All @@ -132,7 +135,7 @@ runs:
--accelerator=${{ inputs.accelerator }}
--disk_size=${{ inputs.disk_size }}
--scopes=${{ inputs.scopes }}
--shutdown_timeout=${{ inputs.shutdown_timeout }}
--shutdown_timeout="${INPUT_SHUTDOWN_TIMEOUT}"
--runner_service_account=${{ inputs.runner_service_account }}
--image_project=${{ inputs.image_project }}
--image=${{ inputs.image }}
Expand Down
111 changes: 111 additions & 0 deletions tests/runner_input_validation_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env bash
set -euo pipefail

repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
tmp_dir=$(mktemp -d)
trap 'rm -rf "$tmp_dir"' EXIT
mkdir -p "$tmp_dir/bin"

cat > "$tmp_dir/bin/curl" <<'EOF'
#!/usr/bin/env bash
if [[ "$*" == *"/actions/runner/releases/latest"* ]]; then
printf '{"tag_name":"%s"}\n' "${MOCK_LATEST_TAG:-v2.321.0}"
else
printf '{"token":"runner-token"}\n'
fi
EOF

cat > "$tmp_dir/bin/gcloud" <<'EOF'
#!/usr/bin/env bash
printf '%s\n' "$*" >> "${GCLOUD_LOG}"
if [[ "$*" == *"compute instances describe"* ]]; then
printf '{"labels":{"gh_ready":"1"}}\n'
fi
EOF
chmod +x "$tmp_dir/bin/curl" "$tmp_dir/bin/gcloud"

run_action() {
local runner_ver=$1
local shutdown_timeout=$2
local latest_tag=${3:-v2.321.0}
: > "$tmp_dir/gcloud.log"
set +e
output=$(
PATH="$tmp_dir/bin:$PATH" \
GCLOUD_LOG="$tmp_dir/gcloud.log" \
MOCK_LATEST_TAG="$latest_tag" \
GITHUB_REPOSITORY=example/repo \
GITHUB_REPOSITORY_OWNER=example \
GITHUB_RUN_ID=123 \
GITHUB_RUN_ATTEMPT=1 \
GITHUB_OUTPUT="$tmp_dir/output" \
"$repo_root/action.sh" \
--command=start \
--token=test-token \
--project_id= \
--service_account_key= \
--runner_ver="$runner_ver" \
--vm_name_prefix=test-runner \
--machine_zone=us-east1-c \
--machine_type=n1-standard-1 \
--scopes=cloud-platform \
--shutdown_timeout="$shutdown_timeout" \
--preemptible=false \
--ephemeral=false \
--no_external_address=false \
--actions_preinstalled=false \
--arm=false \
2>&1
)
status=$?
set -e
}

assert_rejected_before_gcloud() {
local expected_message=$1
if [[ $status -eq 0 ]]; then
printf 'expected failure, got success\n%s\n' "$output" >&2
exit 1
fi
if [[ "$output" != *"$expected_message"* ]]; then
printf 'missing error %q in output:\n%s\n' "$expected_message" "$output" >&2
exit 1
fi
if [[ -s "$tmp_dir/gcloud.log" ]]; then
printf 'gcloud ran for rejected input:\n' >&2
cat "$tmp_dir/gcloud.log" >&2
exit 1
fi
}

run_action '2.321.0; touch /tmp/runner-version-marker' 30
assert_rejected_before_gcloud 'Invalid runner_ver'

run_action '2.321' 30
assert_rejected_before_gcloud 'Invalid runner_ver'

run_action '2.321.0' '30; touch /tmp/shutdown-timeout-marker'
assert_rejected_before_gcloud 'Invalid shutdown_timeout'

run_action '2.321.0' 86401
assert_rejected_before_gcloud 'Invalid shutdown_timeout'

run_action '2.321.0' 999999999999999999999999
assert_rejected_before_gcloud 'Invalid shutdown_timeout'

run_action latest 30 'v2.321.0; touch /tmp/resolved-version-marker'
assert_rejected_before_gcloud 'Invalid resolved runner version'

run_action '2.321.0' 0
if [[ $status -ne 0 ]]; then
printf 'valid explicit version failed:\n%s\n' "$output" >&2
exit 1
fi

run_action latest 86400
if [[ $status -ne 0 ]]; then
printf 'valid latest version failed:\n%s\n' "$output" >&2
exit 1
fi

printf 'runner input validation tests passed\n'