Skip to content

Commit 3b65a3b

Browse files
committed
add action yaml
1 parent 7c1d2a7 commit 3b65a3b

File tree

1 file changed

+320
-0
lines changed

1 file changed

+320
-0
lines changed

.github/workflows/action.yaml

+320
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
# Copyright (c) Tailscale Inc & AUTHORS
2+
# SPDX-License-Identifier: BSD-3-Clause
3+
#
4+
name: 'Connect Tailscale'
5+
description: 'Connect your GitHub Action workflow to Tailscale'
6+
branding:
7+
icon: 'arrow-right-circle'
8+
color: 'gray-dark'
9+
inputs:
10+
authkey:
11+
description: 'Your Tailscale authentication key, from the admin panel.'
12+
required: false
13+
deprecationMessage: 'An OAuth API client https://tailscale.com/s/oauth-clients is recommended instead of an authkey'
14+
oauth-client-id:
15+
description: 'Your Tailscale OAuth Client ID.'
16+
required: false
17+
oauth-secret:
18+
description: 'Your Tailscale OAuth Client Secret.'
19+
required: false
20+
tags:
21+
description: 'Comma separated list of Tags to be applied to nodes. The OAuth client must have permission to apply these tags.'
22+
required: false
23+
version:
24+
description: 'Tailscale version to use.'
25+
required: true
26+
default: '1.68.1'
27+
sha256sum:
28+
description: 'Expected SHA256 checksum of the tarball.'
29+
required: false
30+
default: '18edc3067c41bfbcd6eeffeab45d93e10566219cb0ebb78a0f59eba56acc37e3'
31+
containerMode:
32+
description: 'This mode will use the Userspace networking mode (specially for container where tunnel VPN is not possible). DEPRECATED, not used anymore'
33+
type: boolean
34+
required: false
35+
default: true
36+
debug:
37+
description: 'This mode generate the tailscale bug report'
38+
type: boolean
39+
required: false
40+
default: false
41+
debugEnabled:
42+
description: 'This mode will allow to SSH to the runner. DEPRECATED, not used, replaced by github runner variable'
43+
type: boolean
44+
required: false
45+
default: false
46+
acceptDns:
47+
description: ''
48+
type: boolean
49+
required: false
50+
default: true
51+
acceptRoutes:
52+
description: ''
53+
type: boolean
54+
required: false
55+
default: true
56+
slackChannel:
57+
description: 'Provide Slack Channel to send SSH information'
58+
type: string
59+
required: false
60+
slackToken:
61+
description: 'Slack Token to send message'
62+
type: string
63+
required: false
64+
waitForSSH:
65+
description: 'You can use this action at the end of your job with waitForSSH=true to handle SSH connection in case of workflow failed'
66+
type: boolean
67+
required: false
68+
default: false
69+
sshTimeout:
70+
description: 'Number of minute to wait for SSH connection before ending the job'
71+
type: string
72+
required: false
73+
default: "5m"
74+
sshKeyId:
75+
description: 'Internal usage. when the SSH Key changed on tailscale, please update the default value. Its in use in this action, to take the following decisions'
76+
# - if this key is used on the WF, and debug mode is not enabled during action run, no need to connect to tailscale
77+
# - if tailscale is used for something else than SSH (like internal ressources access), so we are testing internal url access.
78+
type: string
79+
required: false
80+
default: "tskey-auth-kBgJJWKh3311CNTRL"
81+
runs:
82+
using: 'composite'
83+
steps:
84+
- name: Check Runner OS
85+
if: ${{ runner.os != 'Linux' }}
86+
shell: bash
87+
run: |
88+
echo "::error title=⛔ error hint::Support Linux Only"
89+
exit 1
90+
- name: check debug
91+
#if: ${{ runner.debug == '1' }}
92+
shell: bash
93+
run: |
94+
if [ "${{ runner.debug }}" = "1" ]; then
95+
echo "debug"
96+
else
97+
echo "no debug"
98+
fi
99+
100+
- name: Check Tailscale Action Usage mode (waitForSSH or Normal)
101+
id: tailscale-mode
102+
shell: bash
103+
run: |
104+
#if waitForSSH is enabled, we need to check if Tailscale is already connected or not (this parameter must be used for debugging usage when a step of worklfow failed)
105+
#if Tailscale is already connected, it means the action was already called at the start of the WF with all mandatory inputs, we just need to enable SSH and wait for connection.
106+
#if Tailscale is not yet connected, we need to execute the entire action (aka setup tailscale) and wait for SSH at the end
107+
if [ "${{ inputs['waitForSSH'] }}" = true ]; then
108+
if ! command -v tailscale &> /dev/null
109+
then
110+
echo "INSTALL=true" >> $GITHUB_OUTPUT
111+
echo "WITHSSH=true" >> $GITHUB_OUTPUT
112+
echo "WAITFORSSH=true" >> $GITHUB_OUTPUT
113+
else
114+
echo "INSTALL=false" >> $GITHUB_OUTPUT
115+
echo "WITHSSH=true" >> $GITHUB_OUTPUT
116+
echo "WAITFORSSH=true" >> $GITHUB_OUTPUT
117+
fi
118+
else
119+
#if Debug is not enabled, so we need to check why this action is called by checking AUTH_KEY : if the workflow is using SSH'ed key, and debug is not enabled, no need to execute Tailscale
120+
if [ "${{ runner.debug }}" = "1" ]; then #debug enabled, so connect to tailscale with SSH
121+
echo "INSTALL=true" >> $GITHUB_OUTPUT
122+
echo "WITHSSH=true" >> $GITHUB_OUTPUT
123+
echo "WAITFORSSH=false" >> $GITHUB_OUTPUT
124+
else
125+
if [[ "${{ inputs['authkey'] }}" =~ "${{ inputs['sshKeyId'] }}" ]]; then #debug not enabled, so if the Key is the SSH'ed one, no need to execute tailscale
126+
echo "INSTALL=false" >> $GITHUB_OUTPUT
127+
echo "WITHSSH=false" >> $GITHUB_OUTPUT
128+
echo "WAITFORSSH=false" >> $GITHUB_OUTPUT
129+
else #debug not enable, but need to execute tailscale because it's standard Tailscale Key
130+
echo "INSTALL=true" >> $GITHUB_OUTPUT
131+
echo "WITHSSH=false" >> $GITHUB_OUTPUT
132+
echo "WAITFORSSH=false" >> $GITHUB_OUTPUT
133+
fi
134+
fi
135+
fi
136+
- name: Check Auth Info Empty
137+
if: ${{ steps.tailscale-mode.outputs.INSTALL == 'true' && inputs.authkey == '' && (inputs['oauth-secret'] == '' || inputs.tags == '') }}
138+
shell: bash
139+
run: |
140+
echo "::error title=⛔ error hint::OAuth identity empty, Maybe you need to populate it in the Secrets for your workflow, see more in https://docs.github.com/en/actions/security-guides/encrypted-secrets and https://tailscale.com/s/oauth-clients"
141+
exit 1
142+
143+
- name: Install prerequistes
144+
if: ${{ steps.tailscale-mode.outputs.INSTALL == 'true' }}
145+
shell: bash
146+
run: |
147+
if ! command -v sudo &> /dev/null
148+
then
149+
apt-get update
150+
apt-get install -y sudo
151+
else
152+
sudo apt-get update
153+
fi
154+
sudo apt-get install -y curl iptables
155+
156+
- name: Download Tailscale
157+
if: ${{ steps.tailscale-mode.outputs.INSTALL == 'true' }}
158+
shell: bash
159+
id: download
160+
env:
161+
VERSION: ${{ inputs.version }}
162+
SHA256SUM: ${{ inputs.sha256sum }}
163+
run: |
164+
if [ ${{ runner.arch }} = "ARM64" ]; then
165+
TS_ARCH="arm64"
166+
elif [ ${{ runner.arch }} = "ARM" ]; then
167+
TS_ARCH="arm"
168+
elif [ ${{ runner.arch }} = "X86" ]; then
169+
TS_ARCH="386"
170+
elif [ ${{ runner.arch }} = "X64" ]; then
171+
TS_ARCH="amd64"
172+
else
173+
TS_ARCH="amd64"
174+
fi
175+
MINOR=$(echo "$VERSION" | awk -F '.' {'print $2'})
176+
if [ $((MINOR % 2)) -eq 0 ]; then
177+
URL="https://pkgs.tailscale.com/stable/tailscale_${VERSION}_${TS_ARCH}.tgz"
178+
else
179+
URL="https://pkgs.tailscale.com/unstable/tailscale_${VERSION}_${TS_ARCH}.tgz"
180+
fi
181+
if ! [[ "$SHA256SUM" ]] ; then
182+
SHA256SUM="$(curl -H user-agent:tailscale-github-action -L "${URL}.sha256")"
183+
fi
184+
curl -H user-agent:tailscale-github-action -L "$URL" -o tailscale.tgz --max-time 300
185+
echo "Expected sha256: $SHA256SUM"
186+
echo "Actual sha256: $(sha256sum tailscale.tgz)"
187+
echo "$SHA256SUM tailscale.tgz" | sha256sum -c
188+
tar -C /tmp -xzf tailscale.tgz
189+
rm tailscale.tgz
190+
TSPATH=/tmp/tailscale_${VERSION}_${TS_ARCH}
191+
sudo mv "${TSPATH}/tailscale" "${TSPATH}/tailscaled" /usr/bin
192+
193+
- name: Start Tailscale Daemon
194+
if: ${{ steps.tailscale-mode.outputs.INSTALL == 'true' }}
195+
shell: bash
196+
run: |
197+
sudo mkdir -p /dev/net
198+
if [ ! -e /dev/net/tun ]
199+
then
200+
sudo mknod /dev/net/tun c 10 200
201+
fi
202+
sudo -E tailscaled --state=mem: 2>~/tailscaled.log &
203+
# And check that tailscaled came up. The CLI will block for a bit waiting
204+
# for it. And --json will make it exit with status 0 even if we're logged
205+
# out (as we will be). Without --json it returns an error if we're not up.
206+
sudo -E tailscale status --json >/dev/null
207+
208+
- name: Connect to Tailscale
209+
if: ${{ steps.tailscale-mode.outputs.INSTALL == 'true' }}
210+
shell: bash
211+
env:
212+
TAILSCALE_AUTHKEY: ${{ inputs.authkey }}
213+
HOSTNAME: ${{ inputs.hostname }}
214+
TS_EXPERIMENT_OAUTH_AUTHKEY: true
215+
DNS: ${{ inputs.acceptDns }}
216+
ACCEPT_ROUTES: ${{ inputs.acceptRoutes }}
217+
run: |
218+
if [ -z "${HOSTNAME}" ]; then
219+
HOSTNAME="github-$(cat /etc/hostname)"
220+
fi
221+
if [ -n "${{ inputs['oauth-secret'] }}" ]; then
222+
TAILSCALE_AUTHKEY="${{ inputs['oauth-secret'] }}?preauthorized=true&ephemeral=true"
223+
TAGS_ARG="--advertise-tags=${{ inputs.tags }}"
224+
fi
225+
timeout 5m sudo -E tailscale up ${TAGS_ARG} --authkey=${TAILSCALE_AUTHKEY} --hostname=${HOSTNAME} --accept-dns=${DNS} --accept-routes=${ACCEPT_ROUTES} ${ADDITIONAL_ARGS}
226+
227+
#we don't need network test anymore, because we are not using tailscale on CI for accessing registry
228+
#- name: Network Test
229+
# if: ${{ steps.tailscale-mode.outputs.INSTALL == 'true' }}
230+
# shell: bash
231+
# run: |
232+
# if [[ "${{ inputs['authkey'] }}" =~ "${{ inputs['sshKeyId'] }}" ]]; then
233+
# url=https://status.taildb5d.ts.net/status/default
234+
# else
235+
# url=https://registry.internal.huggingface.tech
236+
# fi
237+
# echo $url
238+
# curl --head -X GET --retry 30 --retry-connrefused --retry-delay 1 $url
239+
# echo "WAN IP:"
240+
# curl ifconfig.me/ip
241+
- name: Store Slack infos
242+
#because the SSH can be enabled dynamically if the workflow failed, so we need to store slack infos to be able to retrieve them during the waitforssh step
243+
if: ${{ inputs.slackChannel != '' }}
244+
shell: bash
245+
run: |
246+
echo "SLACKCHANNEL=${{ inputs['slackChannel'] }}" >> $GITHUB_ENV
247+
echo "SLACKTOKEN=${{ inputs['slackToken'] }}" >> $GITHUB_ENV
248+
- name: Enable SSH
249+
id: ssh
250+
if: ${{ steps.tailscale-mode.outputs.WITHSSH == 'true' }}
251+
shell: bash
252+
run: |
253+
sudo apt-get -y install openssh-server >> /dev/null
254+
sudo tailscale set --ssh
255+
echo "TS_IP=$(tailscale ip --4)" >> $GITHUB_OUTPUT
256+
echo "USER=$(whoami)" >> $GITHUB_OUTPUT
257+
echo "warning message content : ${{ inputs['warningMessage'] }}"
258+
if [ "${{ runner.debug }}" = "1" ]; then
259+
echo "MESSAGEHEADER=New Job started in debug mode" >> $GITHUB_OUTPUT
260+
else
261+
echo "MESSAGEHEADER=:x:JOB FAILED !" >> $GITHUB_OUTPUT
262+
fi
263+
- name: Send SSH informations to Slack channel
264+
if: ${{ steps.tailscale-mode.outputs.WITHSSH == 'true' && env.SLACKCHANNEL != '' }}
265+
uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001
266+
with:
267+
# Slack channel id, channel name, or user id to post message.
268+
# See also: https://api.slack.com/methods/chat.postMessage#channels
269+
channel-id: ${{ env.SLACKCHANNEL }}
270+
# For posting a rich message using Block Kit
271+
payload: |
272+
{
273+
"blocks": [
274+
{
275+
"type": "header",
276+
"text": {
277+
"type": "plain_text",
278+
"text": "${{ steps.ssh.outputs.MESSAGEHEADER }}",
279+
"emoji": true
280+
}
281+
},
282+
{
283+
"type": "section",
284+
"fields": [
285+
{
286+
"type": "mrkdwn",
287+
"text": "*Repository:*\n `${{ github.repository }}`"
288+
},
289+
{
290+
"type": "mrkdwn",
291+
"text": "*Workflow:*\n `${{ github.workflow }}`"
292+
},
293+
{
294+
"type": "mrkdwn",
295+
"text": "*Started by:*\n ${{ github.actor }}"
296+
},
297+
{
298+
"type": "mrkdwn",
299+
"text": "*Job:*\n `${{ github.job}}`"
300+
}
301+
]
302+
},
303+
{
304+
"type": "section",
305+
"text": {
306+
"type": "mrkdwn",
307+
"text": "start your VPN (tailscale) and : `ssh ${{ steps.ssh.outputs.USER }}@${{ steps.ssh.outputs.TS_IP }} `"
308+
}
309+
}
310+
]
311+
}
312+
env:
313+
SLACK_BOT_TOKEN: ${{ env.SLACKTOKEN }}
314+
315+
- name: Wait for SSH
316+
if: ${{ steps.tailscale-mode.outputs.WAITFORSSH == 'true' }}
317+
shell: bash
318+
run: |
319+
sleep "${{ inputs['sshTimeout'] }}"
320+
while [ "$(last | grep '^\(ubuntu\|runner\|root\).*still logged in$')" ]; do sleep 1m; done

0 commit comments

Comments
 (0)