diff --git a/.github/workflows/build-and-push-docker-image.yml b/.github/workflows/build-and-push-docker-image.yml new file mode 100644 index 0000000..d8fd716 --- /dev/null +++ b/.github/workflows/build-and-push-docker-image.yml @@ -0,0 +1,54 @@ +--- + +name: ๐Ÿ—๏ธ + +on: # yamllint disable-line rule:truthy + pull_request: + push: + branches: ["release/*", "unstable/*"] + workflow_dispatch: + inputs: + tag: + description: Docker image tag + required: true + type: string + +jobs: + smoke-test: + uses: ./.github/workflows/reusable-smoke-test.yml + build-and-push: + if: github.event_name != 'pull_request' + runs-on: ubuntu-latest + needs: + - smoke-test + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - name: Build Docker image + run: | + DOCKER_TAG="${DOCKER_TAG/'/'/'-'}" + DOCKER_TAG_MAJOR=$(echo "$DOCKER_TAG" | cut -d '.' -f 1) + DOCKER_TAG_MAJOR_MINOR=$(echo "$DOCKER_TAG" | cut -d '.' -f 1-2) + IMAGE="ghcr.io/$GITHUB_REPOSITORY:${DOCKER_TAG}" + IMAGE_MAJOR="ghcr.io/$GITHUB_REPOSITORY:${DOCKER_TAG_MAJOR}" + IMAGE_MAJOR_MINOR="ghcr.io/$GITHUB_REPOSITORY:${DOCKER_TAG_MAJOR_MINOR}" + echo "IMAGE=$IMAGE" >>"$GITHUB_ENV" + echo "IMAGE_MAJOR=$IMAGE_MAJOR" >>"$GITHUB_ENV" + echo "IMAGE_MAJOR_MINOR=$IMAGE_MAJOR_MINOR" >>"$GITHUB_ENV" + docker build . \ + --build-arg BUILDKIT_INLINE_CACHE=1 \ + --cache-from $IMAGE \ + --tag $IMAGE + docker tag $IMAGE $IMAGE_MAJOR + docker tag $IMAGE $IMAGE_MAJOR_MINOR + env: + DOCKER_TAG: ${{ inputs.tag || github.ref_name }} + - name: Log in to GHCR + run: >- + echo ${{ secrets.GITHUB_TOKEN }} | + docker login ghcr.io -u $GITHUB_ACTOR --password-stdin + - name: Push Docker image to GHCR + run: | + docker push $IMAGE + docker push $IMAGE_MAJOR + docker push $IMAGE_MAJOR_MINOR diff --git a/.github/workflows/self-smoke-test-action.yml b/.github/workflows/reusable-smoke-test.yml similarity index 80% rename from .github/workflows/self-smoke-test-action.yml rename to .github/workflows/reusable-smoke-test.yml index b655019..ac59f08 100644 --- a/.github/workflows/self-smoke-test-action.yml +++ b/.github/workflows/reusable-smoke-test.yml @@ -1,10 +1,9 @@ --- -name: ๐Ÿงช +name: โ™ป๏ธ ๐Ÿงช on: # yamllint disable-line rule:truthy - push: - pull_request: + workflow_call: env: devpi-password: abcd1234 @@ -27,7 +26,33 @@ env: PYTEST_THEME_MODE jobs: + fail-fast: + + strategy: + matrix: + os: [macos-latest, windows-latest] + + runs-on: ${{ matrix.os }} + + timeout-minutes: 2 + + steps: + - name: Check out the action locally + uses: actions/checkout@v4 + with: + path: test + - name: Fail-fast in unsupported environments + continue-on-error: true + id: fail-fast + uses: ./test + - name: Error if action did not fail-fast in unsupported environments + if: steps.fail-fast.outcome == 'success' + run: | + >&2 echo This action should fail-fast in unsupported environments. + exit 1 + smoke-test: + runs-on: ubuntu-latest services: diff --git a/action.yml b/action.yml index 40fed97..66c6639 100644 --- a/action.yml +++ b/action.yml @@ -91,15 +91,70 @@ branding: color: yellow icon: upload-cloud runs: - using: docker - image: Dockerfile - args: - - ${{ inputs.user }} - - ${{ inputs.password }} - - ${{ inputs.repository-url }} - - ${{ inputs.packages-dir }} - - ${{ inputs.verify-metadata }} - - ${{ inputs.skip-existing }} - - ${{ inputs.verbose }} - - ${{ inputs.print-hash }} - - ${{ inputs.attestations }} + using: composite + steps: + - name: Fail-fast in unsupported environments + if: runner.os != 'Linux' + run: | + >&2 echo This action is only able to run under GNU/Linux environments + exit 1 + shell: bash -eEuo pipefail {0} + - name: Reset path if needed + run: | + # Reset path if needed + # https://github.com/pypa/gh-action-pypi-publish/issues/112 + if [[ $PATH != *"/usr/bin"* ]]; then + echo "\$PATH=$PATH. Resetting \$PATH for GitHub Actions." + PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + echo "PATH=$PATH" >>"$GITHUB_ENV" + echo "$PATH" >>"$GITHUB_PATH" + echo "\$PATH reset. \$PATH=$PATH" + fi + shell: bash + - name: Set repo and ref from which to run Docker container action + id: set-repo-and-ref + run: | + # Set repo and ref from which to run Docker container action + # to handle cases in which `github.action_` context is not set + # https://github.com/actions/runner/issues/2473 + REF=${{ env.ACTION_REF || env.PR_REF || github.ref_name }} + REPO=${{ env.ACTION_REPO || env.PR_REPO || github.repository }} + REPO_ID=${{ env.PR_REPO_ID || github.repository_id }} + echo "ref=$REF" >>"$GITHUB_OUTPUT" + echo "repo=$REPO" >>"$GITHUB_OUTPUT" + echo "repo-id=$REPO_ID" >>"$GITHUB_OUTPUT" + shell: bash + env: + ACTION_REF: ${{ github.action_ref }} + ACTION_REPO: ${{ github.action_repository }} + PR_REF: ${{ github.event.pull_request.head.ref }} + PR_REPO: ${{ github.event.pull_request.head.repo.full_name }} + PR_REPO_ID: ${{ github.event.pull_request.base.repo.id }} + - name: Check out action repo + uses: actions/checkout@v4 + with: + path: action-repo + ref: ${{ steps.set-repo-and-ref.outputs.ref }} + repository: ${{ steps.set-repo-and-ref.outputs.repo }} + - name: Create Docker container action + run: | + # Create Docker container action + python create-docker-action.py + env: + REF: ${{ steps.set-repo-and-ref.outputs.ref }} + REPO: ${{ steps.set-repo-and-ref.outputs.repo }} + REPO_ID: ${{ steps.set-repo-and-ref.outputs.repo-id }} + shell: bash + working-directory: action-repo + - name: Run Docker container + uses: ./action-repo/.github/actions/run-docker-container + with: + user: ${{ inputs.user }} + password: ${{ inputs.password }} + repository-url: ${{ inputs.repository-url || inputs.repository_url }} + packages-dir: ${{ inputs.packages-dir || inputs.packages_dir }} + verify-metadata: ${{ inputs.verify-metadata || inputs.verify_metadata }} + skip-existing: ${{ inputs.skip-existing || inputs.skip_existing }} + verbose: ${{ inputs.verbose }} + print-hash: ${{ inputs.print-hash || inputs.print_hash }} + attestations: ${{ inputs.attestations }} diff --git a/create-docker-action.py b/create-docker-action.py new file mode 100644 index 0000000..16aa54c --- /dev/null +++ b/create-docker-action.py @@ -0,0 +1,75 @@ +import json +import os +import pathlib + +DESCRIPTION = 'description' +REQUIRED = 'required' + +REF = os.environ['REF'] +REPO = os.environ['REPO'] +REPO_ID = os.environ['REPO_ID'] +REPO_ID_GH_ACTION = '178055147' + + +def set_image(ref: str, repo: str, repo_id: str) -> str: + if repo_id == REPO_ID_GH_ACTION: + return '../../../Dockerfile' + docker_ref = ref.replace('/', '-') + return f'docker://ghcr.io/{repo}:{docker_ref}' + + +image = set_image(REF, REPO, REPO_ID) + +action = { + 'name': '๐Ÿƒ', + DESCRIPTION: ( + 'Run Docker container to upload Python distribution packages to PyPI' + ), + 'inputs': { + 'user': {DESCRIPTION: 'PyPI user', REQUIRED: False}, + 'password': { + DESCRIPTION: 'Password for your PyPI user or an access token', + REQUIRED: False, + }, + 'repository-url': { + DESCRIPTION: 'The repository URL to use', + REQUIRED: False, + }, + 'packages-dir': { + DESCRIPTION: 'The target directory for distribution', + REQUIRED: False, + }, + 'verify-metadata': { + DESCRIPTION: 'Check metadata before uploading', + REQUIRED: False, + }, + 'skip-existing': { + DESCRIPTION: ( + 'Do not fail if a Python package distribution' + ' exists in the target package index' + ), + REQUIRED: False, + }, + 'verbose': {DESCRIPTION: 'Show verbose output.', REQUIRED: False}, + 'print-hash': { + DESCRIPTION: 'Show hash values of files to be uploaded', + REQUIRED: False, + }, + 'attestations': { + DESCRIPTION: ( + '[EXPERIMENTAL]' + ' Enable experimental support for PEP 740 attestations.' + ' Only works with PyPI and TestPyPI via Trusted Publishing.' + ), + REQUIRED: False, + }, + }, + 'runs': { + 'using': 'docker', + 'image': image, + }, +} + +action_path = pathlib.Path('.github/actions/run-docker-container/action.yml') +action_path.parent.mkdir(parents=True, exist_ok=True) +action_path.write_text(json.dumps(action, ensure_ascii=False), encoding='utf-8')