diff --git a/.github/scripts/actionlint.sh b/.github/scripts/actionlint.sh index 129ffd6..d2089fe 100755 --- a/.github/scripts/actionlint.sh +++ b/.github/scripts/actionlint.sh @@ -15,6 +15,7 @@ readonly SCRIPT_DIR # To update the actionlint version, replace this file with the updated checksums file from the GitHub release readonly CHECKSUMS_FILE="$SCRIPT_DIR/actionlint_checksums.txt" readonly ACTIONLINT_PATH="$SCRIPT_DIR/actionlint" +readonly REPO_ROOT="${REPO_ROOT:-.}" source "$SCRIPT_DIR/shellUtils.sh" @@ -72,16 +73,17 @@ else # It's only possible to have one exit trap, so we have to update to include both items we wish to remove trap 'rm -rf "$TMPDIR" "$TARBALL"' EXIT tar -C "$TMPDIR" -xzf "$TARBALL" - mv "$TMPDIR/actionlint" "$SCRIPT_DIR/actionlint" + mv "$TMPDIR/actionlint" "$ACTIONLINT_PATH" - INSTALLED_VERSION="$("$SCRIPT_DIR/actionlint" -version | head -n 1)" + INSTALLED_VERSION="$("$ACTIONLINT_PATH" -version | head -n 1)" readonly INSTALLED_VERSION info "Successfully installed actionlint version $INSTALLED_VERSION" >&2 fi info "Linting workflows..." echo -if ! "$SCRIPT_DIR/actionlint" -color; then +cd "$REPO_ROOT" || exit 1 +if ! "$ACTIONLINT_PATH" -color; then error "Workflows did not pass actionlint :(" exit 1 fi diff --git a/.github/scripts/shellUtils.sh b/.github/scripts/shellUtils.sh index e8282a9..837016f 100755 --- a/.github/scripts/shellUtils.sh +++ b/.github/scripts/shellUtils.sh @@ -2,41 +2,50 @@ # Check if GREEN has already been defined if [ -z "${GREEN+x}" ]; then - declare -r GREEN=$'\e[1;32m' + readonly GREEN=$'\e[1;32m' +fi + +# Check if YELLOW has already been defined +if [ -z "${YELLOW+x}" ]; then + readonly YELLOW=$'\e[1;33m' fi # Check if RED has already been defined if [ -z "${RED+x}" ]; then - declare -r RED=$'\e[1;31m' + readonly RED=$'\e[1;31m' fi # Check if BLUE has already been defined if [ -z "${BLUE+x}" ]; then - declare -r BLUE=$'\e[1;34m' + readonly BLUE=$'\e[1;34m' fi # Check if TITLE has already been defined if [ -z "${TITLE+x}" ]; then - declare -r TITLE=$'\e[1;4;34m' + readonly TITLE=$'\e[1;4;34m' fi # Check if RESET has already been defined if [ -z "${RESET+x}" ]; then - declare -r RESET=$'\e[0m' + readonly RESET=$'\e[0m' fi -function success { +function success() { echo "🎉 $GREEN$1$RESET" } -function error { +function warning() { + echo "⚠️ $YELLOW$1$RESET" +} + +function error() { echo "💥 $RED$1$RESET" } -function info { +function info() { echo "$BLUE$1$RESET" } -function title { +function title() { printf "\n%s%s%s\n" "$TITLE" "$1" "$RESET" } diff --git a/.github/scripts/validateImmutableActionRefs.sh b/.github/scripts/validateImmutableActionRefs.sh index 8de6583..a80f422 100755 --- a/.github/scripts/validateImmutableActionRefs.sh +++ b/.github/scripts/validateImmutableActionRefs.sh @@ -3,10 +3,12 @@ # Check for unsafe action references # ############################################ -GITHUB_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." &>/dev/null && pwd)" -readonly GITHUB_DIR +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +readonly SCRIPT_DIR -source "$GITHUB_DIR/scripts/shellUtils.sh" +readonly REPO_ROOT="${REPO_ROOT:-.}" + +source "$SCRIPT_DIR/shellUtils.sh" title "Checking for mutable action references..." @@ -14,9 +16,18 @@ ACTION_USAGES="" # Find yaml files - these can be either: # - workflows, which are always stored in .github/workflows, or -YAML_FILES="$(find "$GITHUB_DIR/workflows" -type f \( -name "*.yml" -o -name "*.yaml" \))" +WORKFLOWS="$(find "$REPO_ROOT/.github/workflows" -type f \( -name "*.yml" -o -name "*.yaml" \))" +if [[ -z "$WORKFLOWS" ]]; then + warning "No workflows found. Did you remember to run this script from the root of a repository?" >&2 +fi + # - action metadata files, which can be anywhere in the repo, but must be called action.yml or action.yaml -YAML_FILES+=" $(find "$GITHUB_DIR/.." -type f \( -name "action.yml" -o -name "action.yaml" \))" +ACTIONS="$(find "$REPO_ROOT" -type f \( -name "action.yml" -o -name "action.yaml" \))" +if [[ -z "$ACTIONS" ]]; then + warning "No workflows found. Did you remember to run this script from the root of a repository?" >&2 +fi + +readonly YAML_FILES="$WORKFLOWS $ACTIONS" # Find yaml files in the `.github` directory for FILE in $YAML_FILES; do @@ -74,7 +85,7 @@ function check_remote_ref() { local REPO REPO="$(echo "$ACTION" | awk -F/ '{print $1 "/" $2}')" - local REPO_URL="git@github.com:${REPO}.git" + local REPO_URL="https://github.com/${REPO}" if git ls-remote --quiet --tags --heads --exit-code "$REPO_URL" "refs/*/$REF*" ; then error "Found remote branch or tag that looks like a commit hash! ${ACTION}@${REF}" return 1 diff --git a/.github/scripts/validateWorkflowSchemas.sh b/.github/scripts/validateWorkflowSchemas.sh index 15c6fe7..d9fbaef 100755 --- a/.github/scripts/validateWorkflowSchemas.sh +++ b/.github/scripts/validateWorkflowSchemas.sh @@ -3,10 +3,12 @@ # Validate GitHub action and workflow yaml schemas # ########################################################## -GITHUB_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." &>/dev/null && pwd)" -readonly GITHUB_DIR +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +readonly SCRIPT_DIR -source "$GITHUB_DIR/scripts/shellUtils.sh" +source "$SCRIPT_DIR/shellUtils.sh" + +readonly REPO_ROOT="${REPO_ROOT:-.}" title "Validating the Github Actions and workflows using the json schemas provided by (https://www.schemastore.org/json/)" @@ -21,7 +23,7 @@ for SCHEMA in github-action.json github-workflow.json; do if curl "https://json.schemastore.org/$SCHEMA" --output "$TEMP_SCHEMA_DIR/$SCHEMA" --silent; then success "Successfully downloaded $SCHEMA schema!" else - error "Failed downloading $SCHEMA schema" + error "Failed downloading $SCHEMA schema" >&2 exit 1 fi done @@ -33,7 +35,10 @@ info "Validating action metadata files against their JSON schema..." echo # Get all actions, delimited by -d (data arg for ajv) -ACTIONS="$(find "$GITHUB_DIR/.." -type f \( -name "action.yml" -o -name "action.yaml" \) -exec echo -n " -d "{} \;)" +ACTIONS="$(find "$REPO_ROOT" -type f \( -name "action.yml" -o -name "action.yaml" \) -exec echo -n " -d "{} \;)" +if [[ -z "$ACTIONS" ]]; then + warning "No actions found. Did you remember to run this script from the root of a repo?" >&2 +fi # Disabling shellcheck because we WANT word-splitting on ACTIONS in this case # shellcheck disable=SC2086 @@ -46,7 +51,10 @@ info "Validating workflows against their JSON schema..." echo # Get all workflows, delimited by -d (data arg for ajv) -WORKFLOWS="$(find "$GITHUB_DIR/workflows" -type f \( -name "*.yml" -o -name "*.yaml" \) -exec echo -n " -d "{} \;)"\ +WORKFLOWS="$(find "${REPO_ROOT}/.github/workflows" -type f \( -name "*.yml" -o -name "*.yaml" \) -exec echo -n " -d "{} \;)" +if [[ -z "$WORKFLOWS" ]]; then + warning "No workflows found. Did you remember to run this script from the root of a repo?" >&2 +fi # shellcheck disable=SC2086 if ! npx ajv --strict=false -s "$TEMP_SCHEMA_DIR/github-workflow.json" $WORKFLOWS; then @@ -56,7 +64,7 @@ fi echo if [[ $EXIT_CODE -ne 0 ]]; then - error "Some actions and/or workflows are invalid" + error "Some actions and/or workflows are invalid" >&2 exit $EXIT_CODE fi diff --git a/.github/workflows/validateActions.yml b/.github/workflows/validateActions.yml index becbacf..ca6f66f 100644 --- a/.github/workflows/validateActions.yml +++ b/.github/workflows/validateActions.yml @@ -10,9 +10,9 @@ jobs: validateSchemas: runs-on: ubuntu-latest steps: - # v4.2.2 - - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - name: Checkout repos + id: repo + uses: Expensify/GitHub-Actions/checkoutRepoAndGitHubActions@main # v4.3.0 - name: Setup Node @@ -20,27 +20,33 @@ jobs: # Install node to get the ajv-cli - name: Install node modules - run: npm ci + run: npm i -g ajv-cli@5.0.0 - name: Validate action and workflow schemas - run: .github/scripts/validateWorkflowSchemas.sh + run: GitHub-Actions/.github/scripts/validateWorkflowSchemas.sh + env: + REPO_ROOT: ${{ steps.repo.outputs.NAME }} actionlint: runs-on: ubuntu-latest steps: - # v4.2.2 - - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - name: Checkout repos + id: repo + uses: Expensify/GitHub-Actions/checkoutRepoAndGitHubActions@main - name: Lint workflows with actionlint - run: .github/scripts/actionlint.sh + run: GitHub-Actions/.github/scripts/actionlint.sh + env: + REPO_ROOT: ${{ steps.repo.outputs.NAME }} validateImmutableActionRefs: runs-on: ubuntu-latest steps: - # v4.2.2 - - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - name: Checkout repos + id: repo + uses: Expensify/GitHub-Actions/checkoutRepoAndGitHubActions@main - name: Validate actions refs are immutable - run: .github/scripts/validateImmutableActionRefs.sh + run: GitHub-Actions/.github/scripts/validateImmutableActionRefs.sh + env: + REPO_ROOT: ${{ steps.repo.outputs.NAME }} diff --git a/checkoutRepoAndGitHubActions/action.yml b/checkoutRepoAndGitHubActions/action.yml new file mode 100644 index 0000000..d19eaaf --- /dev/null +++ b/checkoutRepoAndGitHubActions/action.yml @@ -0,0 +1,26 @@ +name: Checkout target repo and GitHub Actions repo + +outputs: + NAME: + description: The target repo where this workflow is running (repo name w/o org prefix) + value: ${{ steps.repo.outputs.NAME }} + +runs: + using: composite + steps: + - name: Get target repo name + id: repo + run: echo "NAME=$(basename ${{ github.repository }})" >> "$GITHUB_OUTPUT" + shell: bash + + # v4.2.2 + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + path: ${{ steps.repo.outputs.NAME }} + + - name: Checkout Expensify/GitHub-Actions + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + repository: Expensify/GitHub-Actions + path: GitHub-Actions