Skip to content

Commit

Permalink
feat: new commands (#46)
Browse files Browse the repository at this point in the history
* ci: add pr checks

* chore: improve store_compressed_artifacts command output

* feat: add get_pr_info and post_pr_comment commands

* feat: add get_test_timings and save_test_timings commands
  • Loading branch information
ankorstore-haddowg authored Aug 7, 2023
1 parent 75e27d9 commit 191b6fa
Show file tree
Hide file tree
Showing 12 changed files with 411 additions and 0 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/conventional-pr-title.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: "Check PR Title"

on:
pull_request:
types:
- opened
- reopened
- edited
- synchronize

jobs:
conventional-pr-title:
name: Validate Conventional PR Title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
19 changes: 19 additions & 0 deletions .github/workflows/semver.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Check Semver Label
on:
pull_request:
types:
- opened
- labeled
- unlabeled
- synchronize
jobs:
check-semver-label:
name: Check Semver Label
runs-on: ubuntu-latest
steps:
- id: check-semver-label
uses: mheap/github-action-required-labels@v3
with:
mode: exactly
count: 1
labels: "patch, minor, major"
3 changes: 3 additions & 0 deletions src/@orb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ description: >
display:
source_url: "https://github.com/ankorstore/orb-toolbelt"

orbs:
github-cli: circleci/[email protected]
38 changes: 38 additions & 0 deletions src/commands/get_pr_info.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
description: >
Find the latest GitHub PR associated with the head commit and populate environment variables with information about it.
Sets the following environment variables:
* `GITHUB_PR_BASE_BRANCH` - The base branch for the PR.
* `GITHUB_PR_BASE_BRANCH_SLUG` - The base branch for the PR, slugified.
* `CIRCLE_BRANCH_SLUG` - The PR branch, slugified.
* `GITHUB_PR_NUMBER` - The PR number.
* `GITHUB_PR_TITLE` - The title of the PR.
* `GITHUB_PR_LABELS` - The labels of the PR, comma seperated.
* `GITHUB_PR_COMMIT_MESSAGE` - The PR head commit message. (Optional, see `get_commit_message` parameter
* `GITHUB_PR_AUTHOR_USERNAME` - The PR author's username.
* `GITHUB_PR_AUTHOR_NAME` - The PR author's name. (Optional, see `get_pr_author` parameter)
* `GITHUB_PR_AUTHOR_EMAIL` - The PR author's email address. (Optional, see `get_pr_author` parameter)
This command requires a GITHUB_TOKEN env var to be set with a valid GitHub API Token.
parameters:
get_commit_message:
default: false
description: If true, also sets GITHUB_PR_COMMIT_MESSAGE. This requires an additional API call.
type: boolean
get_pr_author:
default: false
description: If true, also sets GITHUB_PR_AUTHOR_EMAIL and GITHUB_PR_AUTHOR_NAME. This requires an additional API call.
type: boolean
fail_without_pr:
default: true
description: If true, this command will fail if there is no associated PR, if false the job will continue anyway.
type: boolean
steps:
- github-cli/install
- run:
name: Get PR information
environment:
GET_COMMIT_MESSAGE: << parameters.get_commit_message >>
GET_PR_AUTHOR: << parameters.get_pr_author >>
FAIL_WITHOUT_PR: << parameters.fail_without_pr >>
command: << include(scripts/get_pr_info.sh) >>
61 changes: 61 additions & 0 deletions src/commands/get_test_timings.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
description: >
Retrieve previous test timings for the job to facilitate test splitting.
parameters:
timings_dir:
type: string
default: build/test-timings
description: Path to test timings relative to working directory
test_report_dir:
type: string
description: Path to test reports relative to working directory
fallback_branch:
type: string
description: The default branch for the repository or pr target branch, used as fallback if no timings are found for the current branch
key:
type: string
description: The cache key to use, omit to use the job name
default: ''
steps:
- when:
condition: << parameters.key >>
steps:
- restore_cache:
name: Locating test timing data
keys:
- test-timings-<< parameters.key >>-{{ .Branch }}-{{ .Revision }}
- test-timings-<< parameters.key >>-{{ .Branch }}
- test-timings-<< parameters.key >>-<< parameters.fallback_branch >>
- test-timings-<< parameters.key >>
- when:
condition:
not: << parameters.key >>
steps:
- restore_cache:
name: Locating test timing data
keys:
- test-timings-{{ .Environment.CIRCLE_JOB }}-{{ .Branch }}-{{ .Revision }}
- test-timings-{{ .Environment.CIRCLE_JOB }}-{{ .Branch }}
- test-timings-{{ .Environment.CIRCLE_JOB }}-<< parameters.fallback_branch >>
- test-timings-{{ .Environment.CIRCLE_JOB }}
- run:
name: Inspect test timing data
environment:
TIMINGS_DIR: << parameters.timings_dir >>
command: << include(scripts/test_timings/inspect_timings.sh) >>
- when:
condition: << parameters.key >>
steps:
- save_cache:
name: Fixing test timing data for revision
key: test-timings-<< parameters.key >>-{{ .Branch }}-{{ .Revision }}-{{ .BuildNum }}
paths:
- << parameters.timings_dir >>
- when:
condition:
not: << parameters.key >>
steps:
- save_cache:
name: Fixing test timing data for revision
key: test-timings-{{ .Environment.CIRCLE_JOB }}-{{ .Branch }}-{{ .Revision }}-{{ .BuildNum }}
paths:
- << parameters.timings_dir >>
35 changes: 35 additions & 0 deletions src/commands/post_pr_comment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
description: >
Post a comment on a PR.
Provide a comment to post on the PR, or a path to a file containing the comment.
Optionally provide a tag to identify the comment, if a comment with this tag exists it will be updated otherwise
a new comment will be posted.
This command requires a GITHUB_TOKEN env var to be set with a valid GitHub API Token.
parameters:
comment:
default: ""
description: The comment to post on the PR.
type: string
comment_file:
default: ""
description: The path to a file containing the comment to post on the PR. If specified, this will override the comment parameter.
type: string
comment_tag:
default: ""
description: The tag to use to identify the comment, if a comment with this tag exists it will be updated.
type: string
fail_without_pr:
default: false
description: If true, this command will fail if there is no associated PR, if false the job will continue anyway.
type: boolean
steps:
- github-cli/install
- run:
name: Post PR comment
environment:
COMMENT: << parameters.comment >>
COMMENT_FILE: << parameters.comment_file >>
COMMENT_TAG: << parameters.comment_tag >>
FAIL_WITHOUT_PR: << parameters.fail_without_pr >>
command: << include(scripts/post_pr_comment.sh) >>
42 changes: 42 additions & 0 deletions src/commands/save_test_timings.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
description: >
Save test timings to facilitate test splitting.
parameters:
timings_dir:
type: string
default: build/test-timings
description: Path to store test timings relative to working directory
test_report_dir:
type: string
description: Path to test reports relative to working directory
key:
type: string
description: The cache key to use, omit to use the job name
default: ''
steps:
- run:
name: Storing test timings
when: on_success
environment:
TEST_REPORT_DIR: << parameters.test_report_dir >>
TIMINGS_DIR: << parameters.timings_dir >>
TIMINGS_KEY: << parameters.key >>
command: << include(scripts/test_timings/store_timings.sh) >>
- when:
condition: << parameters.key >>
steps:
- save_cache:
name: Saving test timing data
key: test-timings-<< parameters.key >>-{{ .Branch }}-{{ .Revision }}-{{ epoch }}
when: on_success
paths:
- << parameters.timings_dir >>
- when:
condition:
not: << parameters.key >>
steps:
- save_cache:
name: Saving test timing data
key: test-timings-{{ .Environment.CIRCLE_JOB }}-{{ .Branch }}-{{ .Revision }}-{{ epoch }}
when: on_success
paths:
- << parameters.timings_dir >>
108 changes: 108 additions & 0 deletions src/scripts/get_pr_info.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/bin/bash

set -eo pipefail

if ! (command -v jq >/dev/null 2>&1); then
echo "This command requires jq to be installed"
exit 1
fi

if [ -z "$CIRCLE_BRANCH" ]; then
echo "No Branch detected, tag pipelines will not have a PR."
exit 0
fi

# Get the associated PR number
PR_NUMBER=$(gh pr list --head "$CIRCLE_BRANCH" --json number -q '.[] | .number')

if [ -z "$PR_NUMBER" ]; then
echo "No associated PR"
exit "$FAIL_WITHOUT_PR"
fi

slugify() {
printf "%s" "$1" | sed -e "s/[^[:alnum:]]/-/g" | tr -s "-" | tr "[:upper:]" "[:lower:]"
}

escape_double_quotes() {
local STR="$1"
echo "${STR//"\""/"\\\""}"
}

echo "PR_NUMBER: $PR_NUMBER"

API_GITHUB="https://api.github.com/repos/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME"
PR_REQUEST_URL="$API_GITHUB/pulls/$PR_NUMBER"
PR_RESPONSE=$(curl -s -f --retry 3 -H "Authorization: token $GITHUB_TOKEN" "$PR_REQUEST_URL")

PR_TITLE=$(echo "$PR_RESPONSE" | jq -re '.title')
echo "PR_TITLE: $PR_TITLE"

CIRCLE_BRANCH_SLUG="$(slugify "$CIRCLE_BRANCH")"
echo "CIRCLE_BRANCH_SLUG: $CIRCLE_BRANCH_SLUG"

PR_BASE_BRANCH=$(echo "$PR_RESPONSE" | jq -re '.base.ref')
echo "PR_BASE_BRANCH: $PR_BASE_BRANCH"

PR_BASE_BRANCH_SLUG="$(slugify "$PR_BASE_BRANCH")"
echo "PR_BASE_BRANCH_SLUG: $PR_BASE_BRANCH_SLUG"

PR_AUTHOR_USERNAME=$(echo "$PR_RESPONSE" | jq -re '.user.login')
echo "PR_AUTHOR_USERNAME: $PR_AUTHOR_USERNAME"

PR_LABELS=$(echo "$PR_RESPONSE" | jq -r '.labels | map(.name) | join(",")')
echo "PR_LABELS: $PR_LABELS"

cat << EOF >> "$BASH_ENV"
GITHUB_PR_NUMBER=$PR_NUMBER
export GITHUB_PR_NUMBER
GITHUB_PR_TITLE="$(escape_double_quotes "$PR_TITLE")"
export GITHUB_PR_TITLE
CIRCLE_BRANCH_SLUG="$(escape_double_quotes "$CIRCLE_BRANCH_SLUG")"
export CIRCLE_BRANCH_SLUG
GITHUB_PR_BASE_BRANCH="$(escape_double_quotes "$PR_BASE_BRANCH")"
export GITHUB_PR_BASE_BRANCH
GITHUB_PR_BASE_BRANCH_SLUG="$(escape_double_quotes "$PR_BASE_BRANCH_SLUG")"
export GITHUB_PR_BASE_BRANCH_SLUG
GITHUB_PR_AUTHOR_USERNAME="$(escape_double_quotes "$PR_AUTHOR_USERNAME")"
export GITHUB_PR_AUTHOR_USERNAME
GITHUB_PR_LABELS="$(escape_double_quotes "$PR_LABELS")"
export GITHUB_PR_LABELS
EOF

if [ "$GET_PR_AUTHOR" == 1 ]; then
# We need to use the email address associated with the merge_commit_sha since
# CIRCLE_SHA1 may have been authored by someone who is not the PR author.
# Sadly, PR_RESPONSE doesn't include the email associated with the merge_commit_sha.
# So we have to get that from the commit information.

PR_MERGE_COMMIT_SHA=$(echo "$PR_RESPONSE" | jq -re '.merge_commit_sha')
COMMIT_REQUEST_URL="$API_GITHUB/commits/$PR_MERGE_COMMIT_SHA"
COMMIT_RESPONSE=$(curl -s -f --retry 3 -H "Authorization: token $GITHUB_TOKEN" "$COMMIT_REQUEST_URL")

PR_AUTHOR_EMAIL=$(echo "$COMMIT_RESPONSE" | jq -re '.commit.author.email')
echo "PR_AUTHOR_EMAIL: $PR_AUTHOR_EMAIL"

PR_AUTHOR_NAME=$(echo "$COMMIT_RESPONSE" | jq -re '.commit.author.name')
echo "PR_AUTHOR_NAME: $PR_AUTHOR_NAME"

cat << EOF >> "$BASH_ENV"
GITHUB_PR_AUTHOR_EMAIL="$(escape_double_quotes "$GITHUB_PR_AUTHOR_EMAIL")"
export GITHUB_PR_AUTHOR_EMAIL
GITHUB_PR_AUTHOR_NAME="$(escape_double_quotes "$GITHUB_PR_AUTHOR_NAME")"
export GITHUB_PR_AUTHOR_NAME
EOF
fi

if [ "$GET_COMMIT_MESSAGE" = 1 ]; then
COMMIT_REQUEST_URL="$API_GITHUB/commits/$CIRCLE_SHA1"
COMMIT_RESPONSE=$(curl -s -f --retry 3 -H "Authorization: token $GITHUB_TOKEN" "$COMMIT_REQUEST_URL")

PR_COMMIT_MESSAGE=$(echo "$COMMIT_RESPONSE" | jq -re '.commit.message')
echo "PR_COMMIT_MESSAGE: $PR_COMMIT_MESSAGE"

cat << EOF >> "$BASH_ENV"
GITHUB_PR_COMMIT_MESSAGE="$(escape_double_quotes "$GITHUB_PR_COMMIT_MESSAGE")"
export GITHUB_PR_COMMIT_MESSAGE
EOF
fi
49 changes: 49 additions & 0 deletions src/scripts/post_pr_comment.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/bin/bash

set -eo pipefail

if ! (command -v jq >/dev/null 2>&1); then
echo "This command requires jq to be installed"
exit 1
fi

if [ -z "$CIRCLE_BRANCH" ]; then
echo "No Branch detected, tag pipelines will not have a PR."
exit 0
fi

# Get the associated PR number
PR_NUMBER=$(gh pr list --head "$CIRCLE_BRANCH" --json number -q '.[] | .number')

if [ -z "$PR_NUMBER" ]; then
echo "No associated PR"
exit "$FAIL_WITHOUT_PR"
fi

echo "PR_NUMBER: $PR_NUMBER"
FULL_COMMENT_TAG=""
COMMENT_ID=""
if [ -n "$COMMENT_FILE" ] && [ -f "$COMMENT_FILE" ]; then
COMMENT=$(cat "$COMMENT_FILE")
fi
echo "$COMMENT" > body.md

if [ -n "$COMMENT_TAG" ]; then
echo "COMMENT_TAG: $COMMENT_TAG"
FULL_COMMENT_TAG="<!-- toolbelt/post_pr_comment ${COMMENT_TAG} -->"
COMMENT_ID=$(gh api "/repos/{owner}/{repo}/issues/$PR_NUMBER/comments" --jq ".[] | select(.body | contains(\"$FULL_COMMENT_TAG\")) | .id")
printf "\n%s" "$FULL_COMMENT_TAG" >> body.md
fi

if [ -z "${COMMENT_ID}" ]; then
if [ -n "$COMMENT_TAG" ]; then
echo "No comment tagged \`$COMMENT_TAG\` found, creating new comment"
else
echo "Creating new comment"
fi
gh pr comment --body-file body.md
else
echo "Updating comment"
echo "COMMENT_ID: $COMMENT_ID"
gh api "/repos/{owner}/{repo}/issues/comments/$COMMENT_ID" -X PATCH -f body="$(cat body.md)"
fi
6 changes: 6 additions & 0 deletions src/scripts/store_compressed_artifacts.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
#!/bin/bash
mkdir "/tmp/$TARBALL_NAME"

echo "Creating tarball of $ARTIFACT_PATH"
echo " => /tmp/$TARBALL_NAME/$TARBALL_NAME-$CIRCLE_NODE_INDEX.tar.gz"

ls -lah "$ARTIFACT_PATH"

tar -czf "/tmp/$TARBALL_NAME/$TARBALL_NAME-$CIRCLE_NODE_INDEX.tar.gz" "$ARTIFACT_PATH"
Loading

0 comments on commit 191b6fa

Please sign in to comment.