diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml new file mode 100644 index 0000000..33446ca --- /dev/null +++ b/.github/workflows/auto-tag.yml @@ -0,0 +1,12 @@ +name: Auto-tag +on: + push: + tags: + - '*.*.*' +jobs: + auto-tag: + name: Auto-tag + runs-on: ubuntu-latest + steps: + - name: Auto-tag + uses: silverstripe/gha-auto-tag@main diff --git a/README.md b/README.md index a3705ef..7aea143 100644 --- a/README.md +++ b/README.md @@ -1 +1,32 @@ -# gha-tag-release \ No newline at end of file +# GitHub Actions - Tag release + +Create a tag and an optional release + +## Usage + +**workflow.yml** +```yml +steps: + - name: Create tag and release + uses: silverstripe/gha-pull-request@main + with: + tag: 1.2.3 + release: true +``` + +Read more information about the inputs available for [silverstripe/gha-pull-request](https://github.com/silverstripe/gha-pull-request). + +## Why there is no SHA input paramater + +Creating a tag for a particular SHA, either via the GitHub API or via CLI (i.e. git tag) in an action is strangely blocked. The error is "Resource not accessible by integration" which is a permissions error. + +However, tags can be created with the following methods: +- Using `${{ github.sha }}` which is the latest sha in a context instead of historic sha +- Creating a release via GitHub API, which will also create a tag. While it's tempting to just use this and then delete the release, it's seems possible that this may stop working in the future + +The following methods have been attempted: +- Using third party actions to create tags +- Passing in `permissions: write-all` from the calling workflow +- Passing in a github token from the calling workflow + +It's likely that `${{ github.sha }}` will be good enough though - the intention is that this action will be used to tag the _most recent commit_ on a given branch, e.g. after a pull request is merged. diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..ad7f774 --- /dev/null +++ b/action.yml @@ -0,0 +1,169 @@ +name: Tag and release +description: GitHub Action to create a tag and an optional release + +inputs: + # Note: there is an explicit reason why there is no sha input parameter - see the readme + tag: + type: string + required: true + delete_existing: + type: boolean + required: false + default: false + release: + type: boolean + required: false + default: false + release_description: + type: string + required: false + default: '' + +runs: + using: composite + steps: + + - name: Validate inputs + shell: bash + env: + TAG: ${{ inputs.tag }} + run: | + git check-ref-format "tags/$TAG" > /dev/null + if [[ $? != "0" ]]; then + echo "Invalid tag" + exit 1 + fi + + - name: Delete existing release if one exists + if: ${{ inputs.release == 'true' && inputs.delete_existing == 'true' }} + shell: bash + env: + TAG: ${{ inputs.tag }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: | + # Get id for an existing release matching $TAG + # https://docs.github.com/en/rest/releases/releases#get-a-release-by-tag-name + RESP_CODE=$(curl -w %{http_code} -s -o __response.json \ + -X GET https://api.github.com/repos/$GITHUB_REPOSITORY/releases/tags/$TAG \ + -H "Accept: application/vnd.github.v3+json") + if [[ $RESP_CODE != "200" ]]; + echo "Unable to check tag status for $TAG - HTTP response code was $RESP_CODE" + exit 1 + fi + RELEASE_ID=$https://docs.github.com/en/rest/git/refs#get-a-reference(jq .id __response.json) + if [[ $RELEASE_ID == "null" ]]; then + echo "Did not find an existing release for tag $TAG" + else + # https://docs.github.com/en/rest/releases/releases#delete-a-release + RESP_CODE=$(curl -w %{http_code} -s -o /dev/null \ + -X DELETE https://api.github.com/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${{ github.token }}" + if [[ $RESP_CODE != "204" ]]; + echo "Unable to delete release $RELEASE_ID for tag $TAG - HTTP response code was $RESP_CODE" + exit 1 + fi + echo "Deleted existing release $RELEASE_ID for tag $TAG" + fi + + - name: Delete existing tag if one exists + if: ${{ inputs.delete_existing == 'true' }} + shell: bash + env: + TAG: ${{ inputs.tag }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: | + # Check if tag currently exists + # Note: not using https://api.github.com/repos/$GITHUB_REPOSITORY/git/refs/tags/ + # because that uses a "starts-with" filter, so it will also match -beta1 and -rc1 tags + # https://docs.github.com/en/rest/git/refs#get-a-reference + RESP_CODE=$(curl -w %{http_code} -s -o __response.json \ + -X GET https://api.github.com/repos/$GITHUB_REPOSITORY/git/refs/tags \ + -H "Accept: application/vnd.github.v3+json") + if [[ $RESP_CODE != "200" ]]; + echo "Unable to check tag status - HTTP response code was $RESP_CODE" + exit 1 + fi + FOUND="true" + # Check there are any tags so we can use jq array selector later + if [[ $(jq 'map(type)' __response.json) =~ object ]]; then + if [[ $(jq '.[] | select(.ref == "refs/tags/$TAG")' __response.json) == "" ]]; then + FOUND="false" + fi + fi + if [[ $FOUND == "false" ]]; then + echo "Did not find an existing tag for $TAG" + else + # Delete tag via GitHub API + # https://docs.github.com/en/rest/reference/git#delete-a-reference + RESP_CODE=$(curl -w %{http_code} -s -o /dev/null \ + curl -s \ + -X DELETE https://api.github.com/repos/$GITHUB_REPOSITORY/git/refs/tags/$TAG \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${{ github.token }}") + if [[ $RESP_CODE != "204" ]]; + echo "Unable to delete existing TAG $TAG - HTTP response code was $RESP_CODE" + exit 1 + fi + echo "Deleted existing tag $TAG" + fi + + - name: Create tag + # Creating a release will also create a tag, so only create explicitly create tag if not creating release + if: ${{ inputs.release == 'false' }} + shell: bash + env: + TAG: ${{ inputs.tag }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: | + # Create new tag via GitHub API + # https://docs.github.com/en/rest/reference/git#create-a-reference + RESP_CODE=$(curl -w %{http_code} -s -o /dev/null \ + -X POST https://api.github.com/repos/$GITHUB_REPOSITORY/git/refs \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${{ github.token }}" \ + -d @- << EOF + { + "sha": "${{ github.sha }}", + "ref": "refs/tags/$TAG" + } + EOF + ) + if [[ $RESP_CODE != "201" ]]; + echo "Unable to create tag $TAG for sha ${{ github.sha }} - HTTP response code was $RESP_CODE" + exit 1 + fi + echo "New tag $TAG created for sha ${{ github.sha }}" + + - name: Create release + if: ${{ inputs.release == 'true' }} + shell: bash + env: + TAG: ${{ inputs.tag }} + RELEASE_DESCRIPTION: ${{ inputs.release_description }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: | + # Create new release via GitHub API + # https://docs.github.com/en/rest/releases/releases#create-a-release + # Escape double quotes '"' => '\"' + RELEASE_DESCRIPTION=${RELEASE_DESCRIPTION//\"/\\\"} + RESP_CODE=$(curl -w %{http_code} -s -o /dev/null \ + -X POST https://api.github.com/repos/$GITHUB_REPOSITORY/releases \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${{ github.token }}" \ + -d @- << EOF + { + "tag_name": "$TAG", + "target_commitish": "${{ github.sha }}", + "name": "$TAG", + "body": "$RELEASE_DESCRIPTION", + "draft": false, + "prerelease": false + } + EOF + ) + if [[ $RESP_CODE != "201" ]]; + echo "Unable to create release for tag $TAG - HTTP response code was $RESP_CODE" + exit 1 + fi + echo "New release $TAG created"