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/boolean_input_validation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Boolean input validation

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

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

function validate_boolean_input {
local input_name=$1
local input_value=$2
case "${input_value}" in
true|false)
;;
*)
echo "Invalid ${input_name}: expected 'true' or 'false'" >&2
exit 1
;;
esac
}

function validate_boolean_inputs {
maintenance_policy_terminate=${maintenance_policy_terminate:-false}
validate_boolean_input preemptible "${preemptible}"
validate_boolean_input ephemeral "${ephemeral}"
validate_boolean_input no_external_address "${no_external_address}"
validate_boolean_input actions_preinstalled "${actions_preinstalled}"
validate_boolean_input arm "${arm}"
validate_boolean_input maintenance_policy_terminate "${maintenance_policy_terminate}"
}

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 Down Expand Up @@ -198,7 +221,7 @@ function start_vm {
network_flag=$([[ ! -z "${network}" ]] && echo "--network=${network}" || echo "")
subnet_flag=$([[ ! -z "${subnet}" ]] && echo "--subnet=${subnet}" || echo "")
accelerator=$([[ ! -z "${accelerator}" ]] && echo "--accelerator=${accelerator} --maintenance-policy=TERMINATE" || echo "")
maintenance_policy_flag=$([[ -z "${maintenance_policy_terminate}" ]] || echo "--maintenance-policy=TERMINATE" )
maintenance_policy_flag=$([[ "${maintenance_policy_terminate}" == "true" ]] && echo "--maintenance-policy=TERMINATE" || echo "")

echo "The new GCE VM will be ${VM_ID}"

Expand Down Expand Up @@ -252,7 +275,7 @@ function start_vm {
nohup sh -c \"sleep 3d && ${shutdown_command}\" > /dev/null &
"

if $actions_preinstalled ; then
if [[ "${actions_preinstalled}" == "true" ]]; then
echo "✅ Startup script won't install GitHub Actions (pre-installed)"
startup_script="${startup_prelude}
cd /actions-runner
Expand All @@ -268,7 +291,7 @@ function start_vm {
fi
fi
echo "✅ Startup script will install GitHub Actions v$runner_ver"
if $arm ; then
if [[ "${arm}" == "true" ]]; then
startup_script="${startup_prelude}
mkdir /actions-runner
cd /actions-runner
Expand Down Expand Up @@ -352,6 +375,7 @@ function start_vm {
}

safety_on
validate_boolean_inputs
case "$command" in
start)
start_vm
Expand Down
23 changes: 16 additions & 7 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ inputs:
default: false
required: false
maintenance_policy_terminate:
description: "Set Google maintenance policy to TERMINATE. This is required for GPUs as they do ot support live-migration."
description: "Whether to set the Google maintenance policy to TERMINATE. This is required for GPUs because they do not support live migration."
default: "false"
required: false
boot_disk_type:
description: "Boot disk type for the GCE instance (https://cloud.google.com/sdk/gcloud/reference/compute/disk-types/list)"
required: false
Expand All @@ -117,6 +119,13 @@ runs:
using: "composite"
steps:
- id: gce-github-runner-script
env:
INPUT_ACTIONS_PREINSTALLED: ${{ inputs.actions_preinstalled }}
INPUT_ARM: ${{ inputs.arm }}
INPUT_EPHEMERAL: ${{ inputs.ephemeral }}
INPUT_MAINTENANCE_POLICY_TERMINATE: ${{ inputs.maintenance_policy_terminate }}
INPUT_NO_EXTERNAL_ADDRESS: ${{ inputs.no_external_address }}
INPUT_PREEMPTIBLE: ${{ inputs.preemptible }}
run: >
${{ github.action_path }}/action.sh
--command=start
Expand All @@ -138,11 +147,11 @@ runs:
--image=${{ inputs.image }}
--image_family=${{ inputs.image_family }}
--boot_disk_type=${{ inputs.boot_disk_type }}
--preemptible=${{ inputs.preemptible }}
--ephemeral=${{ inputs.ephemeral }}
--no_external_address=${{ inputs.no_external_address }}
--actions_preinstalled=${{ inputs.actions_preinstalled }}
--maintenance_policy_terminate=${{ inputs.maintenance_policy_terminate }}
--arm=${{ inputs.arm }}
--preemptible="${INPUT_PREEMPTIBLE}"
--ephemeral="${INPUT_EPHEMERAL}"
--no_external_address="${INPUT_NO_EXTERNAL_ADDRESS}"
--actions_preinstalled="${INPUT_ACTIONS_PREINSTALLED}"
--maintenance_policy_terminate="${INPUT_MAINTENANCE_POLICY_TERMINATE}"
--arm="${INPUT_ARM}"
--min_cpu_platform='${{ inputs.min_cpu_platform }}'
shell: bash
140 changes: 140 additions & 0 deletions tests/boolean_input_validation_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#!/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
printf '{"token":"runner-token"}\n'
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

cat > "$tmp_dir/bin/marker-command" <<'EOF'
#!/usr/bin/env bash
touch "${MARKER_FILE}"
EOF
chmod +x "$tmp_dir/bin/curl" "$tmp_dir/bin/gcloud" "$tmp_dir/bin/marker-command"

run_action() {
local preemptible=$1
local ephemeral=$2
local no_external_address=$3
local actions_preinstalled=$4
local arm=$5
local maintenance_policy_terminate=$6
rm -f "$tmp_dir/marker"
: > "$tmp_dir/gcloud.log"
set +e
output=$(
PATH="$tmp_dir/bin:$PATH" \
GCLOUD_LOG="$tmp_dir/gcloud.log" \
MARKER_FILE="$tmp_dir/marker" \
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=2.321.0 \
--vm_name_prefix=test-runner \
--machine_zone=us-east1-c \
--machine_type=n1-standard-1 \
--scopes=cloud-platform \
--shutdown_timeout=30 \
--preemptible="$preemptible" \
--ephemeral="$ephemeral" \
--no_external_address="$no_external_address" \
--actions_preinstalled="$actions_preinstalled" \
--arm="$arm" \
--maintenance_policy_terminate="$maintenance_policy_terminate" \
2>&1
)
status=$?
set -e
}

assert_rejected() {
local input_name=$1
if [[ $status -eq 0 ]]; then
printf 'expected %s to be rejected, got success\n%s\n' "$input_name" "$output" >&2
if [[ -e "$tmp_dir/marker" ]]; then
printf 'marker executable ran\n' >&2
fi
exit 1
fi
if [[ "$output" != *"Invalid ${input_name}"* ]]; then
printf 'missing invalid-input error for %s:\n%s\n' "$input_name" "$output" >&2
exit 1
fi
if [[ -e "$tmp_dir/marker" ]]; then
printf 'marker executable ran for rejected %s\n' "$input_name" >&2
exit 1
fi
if [[ -s "$tmp_dir/gcloud.log" ]]; then
printf 'gcloud ran for rejected %s\n' "$input_name" >&2
exit 1
fi
}

run_action false false false marker-command false ''
assert_rejected actions_preinstalled

run_action sometimes false false false false ''
assert_rejected preemptible

run_action false '' false false false ''
assert_rejected ephemeral

run_action false false TRUE false false ''
assert_rejected no_external_address

run_action false false false false 'false ' ''
assert_rejected arm

run_action false false false false false enabled
assert_rejected maintenance_policy_terminate

run_action false false false false false ''
if [[ $status -ne 0 ]]; then
printf 'valid false/empty inputs failed:\n%s\n' "$output" >&2
exit 1
fi
if grep -q -- '--provisioning-model=SPOT\|--ephemeral\|--no-address\|--maintenance-policy=TERMINATE' "$tmp_dir/gcloud.log"; then
printf 'false Boolean inputs unexpectedly enabled flags\n' >&2
exit 1
fi

run_action true true true true true true
if [[ $status -ne 0 ]]; then
printf 'valid true inputs failed:\n%s\n' "$output" >&2
exit 1
fi
for expected_flag in '--provisioning-model=SPOT' '--no-address' '--maintenance-policy=TERMINATE'; do
if ! grep -q -- "$expected_flag" "$tmp_dir/gcloud.log"; then
printf 'missing flag for true input: %s\n' "$expected_flag" >&2
exit 1
fi
done

for input_name in preemptible ephemeral no_external_address actions_preinstalled arm maintenance_policy_terminate; do
if grep -Fq -- "--${input_name}=\${{ inputs.${input_name} }}" "$repo_root/action.yml"; then
printf '%s is still rendered directly into shell source\n' "$input_name" >&2
exit 1
fi
done

printf 'Boolean input validation tests passed\n'