Skip to content

Commit 5545b71

Browse files
authored
Featrue: Add retry logic for runner deletion step (#13)
* retry delete * sort input
1 parent 62495bf commit 5545b71

File tree

3 files changed

+102
-82
lines changed

3 files changed

+102
-82
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ jobs:
141141

142142
| Name | Required | Description | Default |
143143
|---------------------|----------|-------------|---------|
144-
| `create_wait` | | Wait up to 'create_wait' retries (10 sec each) to create the Hetzner Cloud Server resource. | `360` (1 hour) |
144+
| `create_wait` | | Wait up to `create_wait` retries (10 sec each) to create the Server resource via the Hetzner Cloud API. Retry if: Resource is not available ([Limited availability of Cloud plans](https://status.hetzner.com/incident/aa5ce33b-faa5-4fd0-9782-fde43cd270cf)). | `360` (1 hour) |
145+
| `delete_wait` | | Wait up to `delete_wait` retries (10 sec each) to delete the Server resource via the Hetzner Cloud API. Retry if: Temporary outage of the API ([Fault report on Cloud API and Cloud Console](https://status.hetzner.com/incident/440e6b5f-249c-45fd-8074-e5d79cc4e2a6)). | `360` (1 hour) |
145146
| `enable_ipv4` | | Attach an IPv4 on the public NIC (true/false). If false, no IPv4 address will be attached. Warning: The GitHub API requires IPv4. Disabling it will result in connection failures. | `true` |
146147
| `enable_ipv6` | | Attach an IPv6 on the public NIC (true/false). If false, no IPv6 address will be attached. | `true` |
147148
| `github_token` | ✓ (always) | Fine-grained GitHub Personal Access Token (PAT) with 'Read and write' access to 'Administration' assigned. | |

action.sh

Lines changed: 68 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ for MY_FILE in "${MY_FILES[@]}"; do
5151
fi
5252
done
5353

54+
# Retry wait time in secounds
55+
WAIT_SEC=10
56+
5457
#
5558
# INPUT
5659
#
@@ -59,11 +62,32 @@ done
5962
# https://docs.github.com/en/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#inputs
6063
# When you specify an input, GitHub creates an environment variable for the input with the name INPUT_<VARIABLE_NAME>.
6164

62-
# Set the Hetzner Cloud API token.
63-
# Retrieves the value from the INPUT_HCLOUD_TOKEN environment variable.
64-
MY_HETZNER_TOKEN=${INPUT_HCLOUD_TOKEN}
65-
if [[ -z "$MY_HETZNER_TOKEN" ]]; then
66-
exit_with_failure "Hetzner Cloud API token is not set."
65+
# Set maximum retries * WAIT_SEC (10 sec) for Hetzner Server creation via the Hetzer Cloud API (default: 360 [1 hour])
66+
# If INPUT_CREATE_WAIT is set, use its value; otherwise, use "360".
67+
MY_CREATE_WAIT=${INPUT_CREATE_WAIT:-360}
68+
if [[ ! "$MY_CREATE_WAIT" =~ ^[0-9]+$ ]]; then
69+
exit_with_failure "The maximum retries for Hetzner Server creation via the Hetzer Cloud API must be an integer!"
70+
fi
71+
72+
# Set maximum retries * WAIT_SEC (10 sec) for Hetzner Server deletion via the Hetzer Cloud API (default: 360 [1 hour])
73+
# If INPUT_DELETE_WAIT is set, use its value; otherwise, use "360".
74+
MY_DELETE_WAIT=${INPUT_DELETE_WAIT:-360}
75+
if [[ ! "$MY_DELETE_WAIT" =~ ^[0-9]+$ ]]; then
76+
exit_with_failure "The maximum retries for Hetzner Server deletion via the Hetzer Cloud API must be an integer!"
77+
fi
78+
79+
# Enable IPv4 (default: false)
80+
# If INPUT_ENABLE_IPV4 is set, use its value; otherwise, use "false".
81+
MY_ENABLE_IPV4=${INPUT_ENABLE_IPV4:-"true"}
82+
if [[ "$MY_ENABLE_IPV4" != "true" && "$MY_ENABLE_IPV4" != "false" ]]; then
83+
exit_with_failure "Enable IPv4 must be 'true' or 'false'."
84+
fi
85+
86+
# Enable IPv6 (default: true)
87+
# If INPUT_ENABLE_IPV6 is set, use its value; otherwise, use "true".
88+
MY_ENABLE_IPV6=${INPUT_ENABLE_IPV6:-"true"}
89+
if [[ "$MY_ENABLE_IPV6" != "true" && "$MY_ENABLE_IPV6" != "false" ]]; then
90+
exit_with_failure "Enable IPv6 must be 'true' or 'false'."
6791
fi
6892

6993
# Set the GitHub Personal Access Token (PAT).
@@ -86,27 +110,11 @@ MY_GITHUB_REPOSITORY_OWNER_ID=${GITHUB_REPOSITORY_OWNER_ID:-"0"}
86110
# Set The ID of the repository (used for Hetzner Cloud Server label).
87111
MY_GITHUB_REPOSITORY_ID=${GITHUB_REPOSITORY_ID:-"0"}
88112

89-
# Specify here which mode you want to use (default: create):
90-
# - create : Create a new runner
91-
# - delete : Delete the previously created runner
92-
# If INPUT_MODE is set, use its value; otherwise, use "create".
93-
MY_MODE=${INPUT_MODE:-"create"}
94-
if [[ "$MY_MODE" != "create" && "$MY_MODE" != "delete" ]]; then
95-
exit_with_failure "Mode must be 'create' or 'delete'."
96-
fi
97-
98-
# Enable IPv4 (default: false)
99-
# If INPUT_ENABLE_IPV4 is set, use its value; otherwise, use "false".
100-
MY_ENABLE_IPV4=${INPUT_ENABLE_IPV4:-"true"}
101-
if [[ "$MY_ENABLE_IPV4" != "true" && "$MY_ENABLE_IPV4" != "false" ]]; then
102-
exit_with_failure "Enable IPv4 must be 'true' or 'false'."
103-
fi
104-
105-
# Enable IPv6 (default: true)
106-
# If INPUT_ENABLE_IPV6 is set, use its value; otherwise, use "true".
107-
MY_ENABLE_IPV6=${INPUT_ENABLE_IPV6:-"true"}
108-
if [[ "$MY_ENABLE_IPV6" != "true" && "$MY_ENABLE_IPV6" != "false" ]]; then
109-
exit_with_failure "Enable IPv6 must be 'true' or 'false'."
113+
# Set the Hetzner Cloud API token.
114+
# Retrieves the value from the INPUT_HCLOUD_TOKEN environment variable.
115+
MY_HETZNER_TOKEN=${INPUT_HCLOUD_TOKEN}
116+
if [[ -z "$MY_HETZNER_TOKEN" ]]; then
117+
exit_with_failure "Hetzner Cloud API token is not set."
110118
fi
111119

112120
# Set the image to use for the instance (default: ubuntu-24.04)
@@ -121,6 +129,15 @@ fi
121129
# If INPUT_LOCATION is set, use its value; otherwise, use "nbg1".
122130
MY_LOCATION=${INPUT_LOCATION:-"nbg1"}
123131

132+
# Specify here which mode you want to use (default: create):
133+
# - create : Create a new runner
134+
# - delete : Delete the previously created runner
135+
# If INPUT_MODE is set, use its value; otherwise, use "create".
136+
MY_MODE=${INPUT_MODE:-"create"}
137+
if [[ "$MY_MODE" != "create" && "$MY_MODE" != "delete" ]]; then
138+
exit_with_failure "Mode must be 'create' or 'delete'."
139+
fi
140+
124141
# Set the name of the instance (default: gh-runner-$RANDOM)
125142
# If INPUT_NAME is set, use its value; otherwise, generate a random name using "gh-runner-$RANDOM".
126143
MY_NAME=${INPUT_NAME:-"gh-runner-$RANDOM"}
@@ -160,26 +177,6 @@ if [[ "$MY_PRIMARY_IPV6" != "null" && ! "$MY_PRIMARY_IPV6" =~ ^[0-9]+$ ]]; then
160177
exit_with_failure "The primary IPv6 ID must be 'null' or an integer!"
161178
fi
162179

163-
# Set the server type/instance type (default: cx22)
164-
# If INPUT_SERVER_TYPE is set, use its value; otherwise, use "cx22".
165-
MY_SERVER_TYPE=${INPUT_SERVER_TYPE:-"cx22"}
166-
167-
# Set maximal wait time (retries * 10 sec) for Hetzner Cloud Server (default: 30 [5 min])
168-
# If INPUT_SERVER_WAIT is set, use its value; otherwise, use "30".
169-
MY_SERVER_WAIT=${INPUT_SERVER_WAIT:-"30"}
170-
# Check if MY_RUNNER_WAIT is an integer
171-
if [[ ! "$MY_SERVER_WAIT" =~ ^[0-9]+$ ]]; then
172-
exit_with_failure "The maximum wait time (reties) for a running Hetzner Cloud Server must be an integer!"
173-
fi
174-
175-
# Set the SSH key to use for the instance (default: null)
176-
# If INPUT_SSH_KEY is set, use its value; otherwise, use "null".
177-
MY_SSH_KEY=${INPUT_SSH_KEY:-"null"}
178-
# Check if MY_SSH_KEY is an integer
179-
if [[ "$MY_SSH_KEY" != "null" && ! "$MY_SSH_KEY" =~ ^[0-9]+$ ]]; then
180-
exit_with_failure "The SSH key ID must be 'null' or an integer!"
181-
fi
182-
183180
# Set default GitHub Actions Runner installation directory (default: /actions-runner)
184181
# If INPUT_RUNNER_DIR is set, its value is used. Otherwise, the default value "/actions-runner" is used.
185182
MY_RUNNER_DIR=${INPUT_RUNNER_DIR:-"/actions-runner"}
@@ -197,24 +194,37 @@ if [[ "$MY_RUNNER_VERSION" != "latest" && "$MY_RUNNER_VERSION" != "skip" && ! "$
197194
exit_with_failure "'$MY_RUNNER_VERSION' is not a valid GitHub Actions Runner version! Enter 'latest', 'skip' or the version without 'v'."
198195
fi
199196

200-
# Set maximal wait time (retries * 10 sec) for GitHub Actions Runner registration (default: 60 [10 min])
197+
# Set maximal retries * WAIT_SEC (10 sec) for GitHub Actions Runner registration (default: 60 [10 min])
201198
# If INPUT_RUNNER_WAIT is set, use its value; otherwise, use "60".
202199
MY_RUNNER_WAIT=${INPUT_RUNNER_WAIT:-"60"}
203200
# Check if MY_RUNNER_WAIT is an integer
204201
if [[ ! "$MY_RUNNER_WAIT" =~ ^[0-9]+$ ]]; then
205202
exit_with_failure "The maximum wait time (retries) for GitHub Action Runner registration must be an integer!"
206203
fi
207204

208-
# Set maximal wait time (retries * 10 sec) for Hetzner Server creation (default: 360 [1 hour])
209-
# If INPUT_CREATE_WAIT is set, use its value; otherwise, use "360".
210-
MY_CREATE_WAIT=${INPUT_CREATE_WAIT:-360}
211-
if [[ ! "$MY_CREATE_WAIT" =~ ^[0-9]+$ ]]; then
212-
exit_with_failure "The maximum wait time (retries) for Hetzner Server creation must be an integer!"
213-
fi
214-
215205
# Set Hetzner Cloud Server ID
206+
# Check only if mode is delete.
216207
MY_HETZNER_SERVER_ID=${INPUT_SERVER_ID}
217208

209+
# Set the server type/instance type (default: cx22)
210+
# If INPUT_SERVER_TYPE is set, use its value; otherwise, use "cx22".
211+
MY_SERVER_TYPE=${INPUT_SERVER_TYPE:-"cx22"}
212+
213+
# Set maximal retries * WAIT_SEC (10 sec) for Hetzner Cloud Server (default: 30 [5 min])
214+
# If INPUT_SERVER_WAIT is set, use its value; otherwise, use "30".
215+
MY_SERVER_WAIT=${INPUT_SERVER_WAIT:-"30"}
216+
# Check if MY_RUNNER_WAIT is an integer
217+
if [[ ! "$MY_SERVER_WAIT" =~ ^[0-9]+$ ]]; then
218+
exit_with_failure "The maximum wait time (reties) for a running Hetzner Cloud Server must be an integer!"
219+
fi
220+
221+
# Set the SSH key to use for the instance (default: null)
222+
# If INPUT_SSH_KEY is set, use its value; otherwise, use "null".
223+
MY_SSH_KEY=${INPUT_SSH_KEY:-"null"}
224+
# Check if MY_SSH_KEY is an integer
225+
if [[ "$MY_SSH_KEY" != "null" && ! "$MY_SSH_KEY" =~ ^[0-9]+$ ]]; then
226+
exit_with_failure "The SSH key ID must be 'null' or an integer!"
227+
fi
218228

219229
#
220230
# DELETE
@@ -228,9 +238,13 @@ if [[ "$MY_MODE" == "delete" ]]; then
228238

229239
# Send a DELETE request to the Hetzner Cloud API to delete the server.
230240
# https://docs.hetzner.cloud/#servers-delete-a-server
241+
# curl retry: https://everything.curl.dev/usingcurl/downloads/retry.html
231242
echo "Delete server..."
232243
curl \
233244
-X DELETE \
245+
--retry "$MY_DELETE_WAIT" \
246+
--retry-delay "$WAIT_SEC" \
247+
--retry-all-errors \
234248
--fail-with-body \
235249
-H "Content-Type: application/json" \
236250
-H "Authorization: Bearer ${MY_HETZNER_TOKEN}" \
@@ -372,7 +386,6 @@ fi
372386
# https://docs.hetzner.cloud/#servers-create-a-server
373387
MAX_RETRIES=$MY_CREATE_WAIT
374388
RETRY_COUNT=0
375-
WAIT_SEC=10
376389
while [[ $RETRY_COUNT -lt $MAX_RETRIES ]]; do
377390
echo "Create Server..."
378391
if curl \

action.yml

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,16 @@ branding:
77
color: 'red'
88

99
inputs:
10-
mode:
11-
description: >-
12-
Choose either 'create' to create a new GitHub Actions Runner or 'delete' to delete a previously created one.
13-
required: true
14-
github_token:
10+
create_wait:
1511
description: >-
16-
Fine-grained GitHub Personal Access Token (PAT) with 'Read and write' access to 'Administration' assigned.
17-
required: true
18-
hcloud_token:
12+
Wait up to 'create_wait' retries (10 sec each) to create the Server resource via the Hetzner Cloud API (default: 360 = 1 hour).
13+
required: false
14+
default: '360'
15+
delete_wait:
1916
description: >-
20-
Hetzner Cloud API token with 'Read & Write' permissions assigned.
21-
required: true
17+
Wait up to 'delete_wait' retries (10 sec each) to delete the Server resource via the Hetzner Cloud API (default: 360 = 1 hour).
18+
required: false
19+
default: '360'
2220
enable_ipv4:
2321
description: >-
2422
Attach an IPv4 on the public NIC (true/false) . If false, no IPv4 address will be attached.
@@ -30,6 +28,14 @@ inputs:
3028
Attach an IPv6 on the public NIC (true/false). If false, no IPv6 address will be attached.
3129
required: false
3230
default: 'true'
31+
github_token:
32+
description: >-
33+
Fine-grained GitHub Personal Access Token (PAT) with 'Read and write' access to 'Administration' assigned.
34+
required: true
35+
hcloud_token:
36+
description: >-
37+
Hetzner Cloud API token with 'Read & Write' permissions assigned.
38+
required: true
3339
image:
3440
description: >-
3541
Name or ID (integer) of the Image the Server is created from.
@@ -40,6 +46,10 @@ inputs:
4046
Name of Location to create Server in.
4147
required: false
4248
default: 'nbg1'
49+
mode:
50+
description: >-
51+
Choose either 'create' to create a new GitHub Actions Runner or 'delete' to delete a previously created one.
52+
required: true
4353
name:
4454
description: >-
4555
The name for the server and label for the GitHub Actions Runner (must be unique within the project and conform to hostname rules: '[a-zA-Z0-9_-]').
@@ -49,6 +59,11 @@ inputs:
4959
Network ID (integer) which should be attached to the Server private network interface at the creation time.
5060
required: false
5161
default: 'null'
62+
pre_runner_script:
63+
description: >-
64+
Specifies bash commands to run before the GitHub Actions Runner starts.
65+
It's useful for installing dependencies with apt-get, dnf, zypper etc.
66+
required: false
5267
primary_ipv4:
5368
description: >-
5469
ID (integer) of the IPv4 Primary IP to use.
@@ -66,23 +81,18 @@ inputs:
6681
GitHub Actions Runner installation directory (created automatically; no trailing slash).
6782
required: false
6883
default: '/actions-runner'
84+
runner_wait:
85+
description: >-
86+
Wait up to 'runner_wait' retries (10 sec each) for runner registration (default: 10 minutes).
87+
required: false
88+
default: '60'
6989
runner_version:
7090
description: >-
7191
GitHub Actions Runner version (omit 'v'; e.g., '2.321.0').
7292
'latest' will install the latest version.
7393
'skip' will skip the installation. A working installation is expected in the 'runner_dir'.
7494
required: false
7595
default: 'latest'
76-
create_wait:
77-
description: >-
78-
Wait up to 'create_wait' retries (10 sec each) to create the Hetzner Cloud Server resource (default: 360 = 1 hour).
79-
required: false
80-
default: '360'
81-
runner_wait:
82-
description: >-
83-
Wait up to 'runner_wait' retries (10 sec each) for runner registration (default: 10 minutes).
84-
required: false
85-
default: '60'
8696
server_id:
8797
description: >-
8898
ID (integer) of Hetzner Cloud Server to delete.
@@ -102,11 +112,6 @@ inputs:
102112
SSH key ID (integer) or name which should be injected into the Server at creation time.
103113
required: false
104114
default: 'null'
105-
pre_runner_script:
106-
description: >-
107-
Specifies bash commands to run before the GitHub Actions Runner starts.
108-
It's useful for installing dependencies with apt-get, dnf, zypper etc.
109-
required: false
110115

111116
outputs:
112117
label:
@@ -128,6 +133,8 @@ runs:
128133
working-directory: ${{ github.action_path }}
129134
run: bash action.sh
130135
env:
136+
INPUT_CREATE_WAIT: ${{ inputs.create_wait }}
137+
INPUT_DELETE_WAIT: ${{ inputs.delete_wait }}
131138
INPUT_ENABLE_IPV4: ${{ inputs.enable_ipv4 }}
132139
INPUT_ENABLE_IPV6: ${{ inputs.enable_ipv6 }}
133140
INPUT_GITHUB_TOKEN: ${{ inputs.github_token }}
@@ -147,4 +154,3 @@ runs:
147154
INPUT_SERVER_TYPE: ${{ inputs.server_type }}
148155
INPUT_SERVER_WAIT: ${{ inputs.server_wait }}
149156
INPUT_SSH_KEY: ${{ inputs.ssh_key }}
150-
INPUT_CREATE_WAIT: ${{ inputs.create_wait }}

0 commit comments

Comments
 (0)