Skip to content

Commit

Permalink
feat: now really support GitHub Actions, not only Workflows (#15)
Browse files Browse the repository at this point in the history
feat: now really support GitHub Actions, not only Workflows
  • Loading branch information
mschuett authored Sep 8, 2024
1 parent 3aa80ef commit 004cf0d
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 7 deletions.
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,19 @@ is considered a shell script.

### GitHub Actions & Forgejo Actions

GitHub Actions are similar to Bitbucket. A file with a `jobs` object
is read as a GitHub Actions file, and every `run` attribute inside
is considered a shell script.
GitHub has Actions (reusable components) and Workflows (the real CI config
that may run scripts and/or Actions). This tool tries to handle both
file types in the same function.

GitHub Workflows are similar to Bitbucket. A file with a `on` attribute
and a `jobs` object is read as a GitHub file, and every `run` attribute
inside is considered a shell script.

Alternatively a file with an `inputs` and a `runs` object is read as a GitHub Actions file.

As far as I can tell [Forgejo Actions](https://forgejo.org/docs/latest/user/actions/) (as used e.g. by https://codeberg.org/) intentionally use the same structure as GitHub Actions, so these are covered here as well.
As far as I can tell [Forgejo Actions](https://forgejo.org/docs/latest/user/actions/)
(as used e.g. by https://codeberg.org/) intentionally use the same structure as
GitHub Actions/Workflows, so these are covered here as well.

* `shell` attributes are not supported
this is a todo, it should be simple enough to only check `sh` and `bash` scripts with right shebang line
Expand Down
7 changes: 7 additions & 0 deletions test-input/github-action.yml.test_expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
SC2086
SC2116
SC3028
SC2086
SC3028
SC2086
SC2116
131 changes: 131 additions & 0 deletions test-input/github-action2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# larger example from https://github.com/tailscale/github-action/blob/main/action.yml
# Copyright (c) Tailscale Inc & AUTHORS
# SPDX-License-Identifier: BSD-3-Clause
#
name: 'Connect Tailscale'
description: 'Connect your GitHub Action workflow to Tailscale'
branding:
icon: 'arrow-right-circle'
color: 'gray-dark'
inputs:
authkey:
description: 'Your Tailscale authentication key, from the admin panel.'
required: false
deprecationMessage: 'An OAuth API client https://tailscale.com/s/oauth-clients is recommended instead of an authkey'
oauth-client-id:
description: 'Your Tailscale OAuth Client ID.'
required: false
oauth-secret:
description: 'Your Tailscale OAuth Client Secret.'
required: false
tags:
description: 'Comma separated list of Tags to be applied to nodes. The OAuth client must have permission to apply these tags.'
required: false
version:
description: 'Tailscale version to use.'
required: true
default: '1.66.3'
sha256sum:
description: 'Expected SHA256 checksum of the tarball.'
required: false
default: ''
args:
description: 'Optional additional arguments to `tailscale up`'
required: false
default: ''
tailscaled-args:
description: 'Optional additional arguments to `tailscaled`'
required: false
default: ''
hostname:
description: 'Fixed hostname to use.'
required: false
default: ''
statedir:
description: 'Optional state directory to use (if unset, memory state is used)'
required: false
default: ''
runs:
using: 'composite'
steps:
- name: Check Runner OS
if: ${{ runner.os != 'Linux' }}
shell: bash
run: |
echo "::error title=⛔ error hint::Support Linux Only"
exit 1
- name: Check Auth Info Empty
if: ${{ inputs.authkey == '' && (inputs['oauth-secret'] == '' || inputs.tags == '') }}
shell: bash
run: |
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"
exit 1
- name: Download Tailscale
shell: bash
id: download
env:
VERSION: ${{ inputs.version }}
SHA256SUM: ${{ inputs.sha256sum }}
run: |
if [ ${{ runner.arch }} = "ARM64" ]; then
TS_ARCH="arm64"
elif [ ${{ runner.arch }} = "ARM" ]; then
TS_ARCH="arm"
elif [ ${{ runner.arch }} = "X86" ]; then
TS_ARCH="386"
elif [ ${{ runner.arch }} = "X64" ]; then
TS_ARCH="amd64"
else
TS_ARCH="amd64"
fi
MINOR=$(echo "$VERSION" | awk -F '.' {'print $2'})
if [ $((MINOR % 2)) -eq 0 ]; then
URL="https://pkgs.tailscale.com/stable/tailscale_${VERSION}_${TS_ARCH}.tgz"
else
URL="https://pkgs.tailscale.com/unstable/tailscale_${VERSION}_${TS_ARCH}.tgz"
fi
echo "Downloading $URL"
curl -H user-agent:tailscale-github-action -L "$URL" -o tailscale.tgz --max-time 300 --fail
if ! [[ "$SHA256SUM" ]] ; then
SHA256SUM="$(curl -H user-agent:tailscale-github-action -L "${URL}.sha256" --fail)"
fi
echo "Expected sha256: $SHA256SUM"
echo "Actual sha256: $(sha256sum tailscale.tgz)"
echo "$SHA256SUM tailscale.tgz" | sha256sum -c
tar -C /tmp -xzf tailscale.tgz
rm tailscale.tgz
TSPATH=/tmp/tailscale_${VERSION}_${TS_ARCH}
sudo mv "${TSPATH}/tailscale" "${TSPATH}/tailscaled" /usr/bin
- name: Start Tailscale Daemon
shell: bash
env:
ADDITIONAL_DAEMON_ARGS: ${{ inputs.tailscaled-args }}
STATEDIR: ${{ inputs.statedir }}
run: |
if [ "$STATEDIR" == "" ]; then
STATE_ARGS="--state=mem:"
else
STATE_ARGS="--statedir=${STATEDIR}"
mkdir -p "$STATEDIR"
fi
sudo -E tailscaled ${STATE_ARGS} ${ADDITIONAL_DAEMON_ARGS} 2>~/tailscaled.log &
# And check that tailscaled came up. The CLI will block for a bit waiting
# for it. And --json will make it exit with status 0 even if we're logged
# out (as we will be). Without --json it returns an error if we're not up.
sudo -E tailscale status --json >/dev/null
- name: Connect to Tailscale
shell: bash
env:
TAILSCALE_AUTHKEY: ${{ inputs.authkey }}
ADDITIONAL_ARGS: ${{ inputs.args }}
HOSTNAME: ${{ inputs.hostname }}
TS_EXPERIMENT_OAUTH_AUTHKEY: true
run: |
if [ -z "${HOSTNAME}" ]; then
HOSTNAME="github-$(cat /etc/hostname)"
fi
if [ -n "${{ inputs['oauth-secret'] }}" ]; then
TAILSCALE_AUTHKEY="${{ inputs['oauth-secret'] }}?preauthorized=true&ephemeral=true"
TAGS_ARG="--advertise-tags=${{ inputs.tags }}"
fi
timeout 5m sudo -E tailscale up ${TAGS_ARG} --authkey=${TAILSCALE_AUTHKEY} --hostname=${HOSTNAME} --accept-routes ${ADDITIONAL_ARGS}
17 changes: 17 additions & 0 deletions test-input/github-action2.yml.test_expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
SC2086
SC2086
SC2086
SC2086
SC1083
SC1083
SC3010
SC3014
SC2086
SC2086
SC2086
SC2086
SC2086
SC2086
SC1083
SC3010
SC3014
15 changes: 12 additions & 3 deletions yaml_shellcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,13 @@ def get_scripts(data, path):


def get_github_scripts(data):
"""GitHub: from the docs the search pattern should be `jobs.<job_id>.steps[*].run`
"""GitHub Workflows: from the docs the search pattern should be `jobs.<job_id>.steps[*].run`
https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions
as a simple first step we match on `jobs.**.run`, excluding `jobs.**.defaults.run`
and,
GitHub Actions: match on runs.steps[*].run
"""

def get_runs(data, path):
Expand Down Expand Up @@ -151,9 +154,12 @@ def get_runs(data, path):
return results

result = {}
if "jobs" not in data:
if "jobs" in data: # workflow
result = get_runs(data["jobs"], "jobs")
elif "runs" in data: # actions
result = get_runs(data["runs"], "runs")
else: # neither
return result
result = get_runs(data["jobs"], "jobs")
logging.debug("got scripts: %s", result)
for key in result:
logging.debug("%s: %s", key, result[key])
Expand Down Expand Up @@ -336,6 +342,9 @@ def select_yaml_schema(documents, filename):
logging.info(f"read {filename} as Bitbucket Pipelines config...")
return get_bitbucket_scripts, 0
elif isinstance(data, dict) and "on" in data and "jobs" in data:
logging.info(f"read {filename} as GitHub Workflows config...")
return get_github_scripts, 0
elif isinstance(data, dict) and "inputs" in data and "runs" in data:
logging.info(f"read {filename} as GitHub Actions config...")
return get_github_scripts, 0
elif isinstance(data, dict) and "version" in data and "jobs" in data:
Expand Down

0 comments on commit 004cf0d

Please sign in to comment.