Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 34 additions & 5 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,59 @@ permissions:
id-token: write

jobs:
test:
name: Test
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- uses: actions/setup-python@v7
with:
python-version: 3.12

- uses: pre-commit/action@v3.0.1

test:
name: Test
container:
# One or more test cases run in a container, so we can tell that the
# Action works when run in a containerized job.
image: ${{ matrix.container-image }}
runs-on: ubuntu-latest
strategy:
# Finish all test cases even once one of them fails.
fail-fast: false
matrix:
case: [runner, container]
include:
- case: container
container-image: python:3.12
steps:
- uses: actions/checkout@v7

- name: Fetch Cloudsmith OIDC token
id: cloudsmith-oidc
uses: ./
with:
account: ${{ secrets.CLOUDSMITH_ACCOUNT }}
service: ${{ secrets.CLOUDSMITH_SERVICE }}

- uses: actions/setup-python@v7
if: '! matrix.container-image'
with:
python-version: 3.12
- name: Verify Cloudsmith OIDC token
env:
TOKEN: ${{ steps.cloudsmith-oidc.outputs.token }}
FILE: ${{ steps.cloudsmith-oidc.outputs.file }}
run: |
typ=$(echo "$TOKEN" | cut -d'.' -f1 | base64 -d | jq -r .typ)
set -euxo pipefail

# jq isn't available in the container case, so check the token with
# Python in both cases.
pluck_field() {
python3 -c 'import json, sys; print(json.load(sys.stdin)[sys.argv[1]])' "$1"
}

typ=$(echo "$TOKEN" | cut -d'.' -f1 | base64 -d | pluck_field typ)
if [[ "$typ" != "JWT" ]]; then
echo "Invalid token type: $typ"
exit 1
Expand All @@ -46,3 +74,4 @@ jobs:
echo "Token not found in file $FILE"
exit 1
fi
shell: bash
10 changes: 8 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@ repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-shebang-scripts-are-executable
- id: check-yaml
# Fix the last lines' trailing whitespace before correcting that they exist.
- id: trailing-whitespace
- id: end-of-file-fixer

- id: check-shebang-scripts-are-executable
- id: check-yaml
- id: fix-byte-order-marker
- id: mixed-line-ending
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.11.0.1
hooks:
- id: shellcheck
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@ jobs:
# ...
```

Either [jq] or Python is required. To use in a [containerized job] or on a custom runner image, ensure Python is available or install jq.

[containerized job]: https://docs.github.com/en/actions/how-tos/write-workflows/choose-where-workflows-run/run-jobs-in-a-container
[jq]: https://jqlang.org
[oidc]: https://help.cloudsmith.io/docs/openid-connect
48 changes: 48 additions & 0 deletions action.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/bin/bash

set -euxo pipefail

request() {
if ! resp="$(curl --silent --fail-with-body "$@")"
then
curl_exit_code="$?"
echo "$resp" >&2
exit "$curl_exit_code"
fi
echo "$resp"
}

# pluck_field <field>
# Pluck the named field from the JSON content on stdin using jq or Python &
# print it to stdout.
pluck_field() {
field="$1"

if command -v jq > /dev/null; then
jq -r ".${field}"
elif command -v python3 > /dev/null; then
python3 -c 'import json, sys; print(json.load(sys.stdin)[sys.argv[1]])' "$field"
else
echo "Neither jq nor python3 are installed. One or the other is required to read JSON responses." >&2
exit 1
fi
}

if [[ ! -v ACTIONS_ID_TOKEN_REQUEST_TOKEN ]]
then
message="No \$ACTIONS_ID_TOKEN_REQUEST_TOKEN is set. Does this job have the \`id-token: write\` permission necessary to get a Cloudsmith token?"
echo "$message" >&2
echo "::error title=Missing \`id-token: write\` permission?::${message}"
exit 1
fi

CLOUDSMITH_ACCOUNT=${CLOUDSMITH_ACCOUNT:-$GITHUB_REPOSITORY_OWNER}
echo "Retrieving actions OIDC token"
id_token=$(request -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange" | pluck_field value)
echo "Retrieving Cloudsmith token for account: $CLOUDSMITH_ACCOUNT (service account: $SERVICE_SLUG)"
cloudsmith_token=$(request --json '{"oidc_token":"'"$id_token"'", "service_slug":"'"$SERVICE_SLUG"'"}' "https://api.cloudsmith.io/openid/$CLOUDSMITH_ACCOUNT/" | pluck_field token)
echo "value=$cloudsmith_token" >> "$GITHUB_OUTPUT"

file="$(mktemp)"
echo "CLOUDSMITH_TOKEN=$cloudsmith_token" >> "$file"
echo "file=$file" >> "$GITHUB_OUTPUT"
48 changes: 2 additions & 46 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,53 +19,9 @@ runs:
steps:
- name: Get OIDC token
id: token
shell: bash
env:
CLOUDSMITH_ACCOUNT: ${{ inputs.account }}
SERVICE_SLUG: ${{ inputs.service }}
run: |
set -eu

# https://superuser.com/a/1641410
curlf() {
output_file=$(mktemp)
code=$(curl --silent --output $output_file --write-out "%{http_code}" "$@")
if [[ ${code} -lt 200 || ${code} -gt 299 ]]; then
>&2 cat $output_file
return 22
fi
cat $output_file
rm $output_file
}

request() {
set +e
resp="$(curlf "$@")"
rc=$?
set -e
# shellcheck disable=SC2181
if [ $rc -ne 0 ]; then
echo "$resp" >> /dev/stderr
exit 1
fi
echo "$resp"
}

if [[ ! -v ACTIONS_ID_TOKEN_REQUEST_TOKEN ]]
then
message='No $ACTIONS_ID_TOKEN_REQUEST_TOKEN is set. Does this job have the `id-token: write` permission necessary to get a Cloudsmith token?'
echo "$message" >&2
echo '::error title=Missing `id-token: write` permission?::'"$message"
exit 1
fi

CLOUDSMITH_ACCOUNT=${CLOUDSMITH_ACCOUNT:-$GITHUB_REPOSITORY_OWNER}
echo "Retrieving actions OIDC token"
id_token=$(request -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange" | jq -r '.value')
echo "Retrieving Cloudsmith token for account: $CLOUDSMITH_ACCOUNT (service account: $SERVICE_SLUG)"
cloudsmith_token=$(request -X POST -H "Content-Type: application/json" -d "{\"oidc_token\":\"$id_token\", \"service_slug\":\"$SERVICE_SLUG\"}" "https://api.cloudsmith.io/openid/$CLOUDSMITH_ACCOUNT/" | jq -r '.token')
echo "value=$cloudsmith_token" >> "$GITHUB_OUTPUT"

file="$(mktemp)"
echo "CLOUDSMITH_TOKEN=$cloudsmith_token" >> "$file"
echo "file=$file" >> "$GITHUB_OUTPUT"
"$GITHUB_ACTION_PATH"/action.sh
shell: bash
Loading