Release #58
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| workflow_run: # would only fire after file is merged to master | |
| workflows: ["Lint and test"] | |
| types: | |
| - completed | |
| branches: | |
| - master | |
| - 'ci/**' # ci testing, pre-releases | |
| #- develop # can emit -dev releases but we do not want to | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: "Run in dry-run mode (no publish)" | |
| required: false | |
| default: "true" | |
| push: # only temporary, until this file lands on master (see above) | |
| branches: | |
| - 'ci/**' | |
| # MUSTHAVE: Trusted publisher access for both repos. | |
| # NOTE: according to docs, 'test' repo accounts are ephemeral and can be wiped at any time | |
| # NOTE: 'test' accs are not that ephmeperal -- losing access to sandbox account (2FA issue) effectively locked us out of project; good test for workarounds though | |
| # NOTE: as a part of regaining-control scenario we may use distinct project names in pyroject.toml (e.g. appmap-dev, appmap-ng) | |
| env: | |
| DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }} | |
| pypi_project: appmap | |
| #testpypi_project: appmap-dev # workaround for lost-access scenario | |
| testpypi_project: appmapcitest | |
| jobs: | |
| setup: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| distribution_name: ${{ steps.configure.outputs.distribution_name }} | |
| publish_to: ${{ steps.configure.outputs.publish_to }} | |
| publish_env: ${{ steps.configure.outputs.publish_env }} | |
| steps: | |
| - id: configure | |
| shell: bash | |
| run: | | |
| case "${{ github.ref_name }}" in | |
| ci/*) | |
| echo "publish_env=testpypi" >> $GITHUB_OUTPUT | |
| echo "distribution_name=${{ env.testpypi_project }}" >> $GITHUB_OUTPUT | |
| echo "publish_to=https://test.pypi.org/project/${{ env.testpypi_project }}" >> $GITHUB_OUTPUT | |
| ;; | |
| master) | |
| echo "publish_env=pypi" >> $GITHUB_OUTPUT | |
| echo "distribution_name=${{ env.pypi_project }}" >> $GITHUB_OUTPUT | |
| echo "publish_to=https://pypi.org/project/${{ env.pypi_project }}" >> $GITHUB_OUTPUT | |
| ;; | |
| *) | |
| echo "publish_env=SKIP" >> $GITHUB_OUTPUT | |
| echo "distribution_name=${{ env.pypi_project }}" >> $GITHUB_OUTPUT | |
| echo "publish_to=https://test.pypi.org/project/${{ env.pypi_project }}" >> $GITHUB_OUTPUT | |
| ;; | |
| esac | |
| release: | |
| runs-on: ubuntu-latest | |
| needs: setup | |
| if: github.event_name == 'workflow_dispatch' || (github.event_name=='push' && startsWith(github.ref_name,'ci/') ) || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && (github.event.workflow_run.head_branch == 'master' || startsWith(github.event.workflow_run.head_branch, 'ci/') ) ) | |
| steps: | |
| - name: Generate token | |
| uses: actions/create-github-app-token@v1 | |
| id: app-token | |
| with: | |
| app-id: ${{ secrets.RELEASE_APP_ID }} | |
| private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} | |
| - uses: actions/checkout@v5 | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} | |
| - uses: ./.github/actions/setup-semantic-release # node+semantic-release | |
| - uses: ./.github/actions/setup | |
| - id: semantic-release # branch policies defined in .releaserc | |
| env: | |
| GIT_AUTHOR_NAME: appland-release | |
| GIT_AUTHOR_EMAIL: release@app.land | |
| GIT_COMMITTER_NAME: appland-release | |
| GIT_COMMITTER_EMAIL: release@app.land | |
| GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} | |
| DISTRIBUTION_NAME: ${{ needs.setup.outputs.distribution_name }} | |
| run: | | |
| if [ "$DRY_RUN" = "true" ]; then | |
| semantic-release --dry-run | |
| else | |
| semantic-release | |
| fi | |
| - name: Get version | |
| if: env.DRY_RUN != 'true' | |
| id: version | |
| run: | | |
| VERSION=$(grep '^version = ' pyproject.toml | cut -d'"' -f2) | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| - name: Upload wheel | |
| if: env.DRY_RUN != 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: wheel | |
| path: dist/*.whl | |
| - name: Upload sdist | |
| if: env.DRY_RUN != 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: sdist | |
| path: dist/*.tar.gz | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| smoketest: | |
| runs-on: ubuntu-latest | |
| needs: ['setup', 'release'] | |
| if: github.event.inputs.dry_run!='true' | |
| continue-on-error: ${{ needs.setup.outputs.distribution_name!='appmap' }} # altered names won't work anyway | |
| steps: | |
| - name: Generate token | |
| uses: actions/create-github-app-token@v1 | |
| id: app-token | |
| with: | |
| app-id: ${{ secrets.RELEASE_APP_ID }} | |
| private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} | |
| - uses: actions/checkout@v5 | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} | |
| - uses: ./.github/actions/refetch-artifacts | |
| - name: dockerhub login (for seamless docker pulling) | |
| uses: ./.github/actions/dockerhub-login | |
| env: | |
| DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} | |
| DOCKERHUB_USERNAME: ${{ vars.DOCKERHUB_USERNAME }} | |
| continue-on-error: true | |
| - name: Run smoke tests | |
| id: smoketest | |
| run: ci/scripts/run_tests.sh | |
| continue-on-error: true | |
| env: | |
| SMOKETEST_DOCKER_IMAGE: python:3.12-slim | |
| DISTRIBUTION_NAME: ${{ needs.setup.outputs.distribution_name }} | |
| - name: Cleanup on failure | |
| if: steps.smoketest.outcome == 'failure' | |
| run: | | |
| echo "::error::Smoke tests failed, cleaning up release v${{ needs.release.outputs.version }}" | |
| # Sanity check: verify HEAD commit is the release commit | |
| COMMIT_MSG=$(git log -1 --pretty=%s) | |
| if [[ "$COMMIT_MSG" != "chore(release): ${{ needs.release.outputs.version }}"* ]]; then | |
| echo "::error::HEAD commit message doesn't match expected release commit!" | |
| echo "::error::Expected: chore(release): ${{ needs.release.outputs.version }}" | |
| echo "::error::Got: $COMMIT_MSG" | |
| echo "::error::Aborting cleanup - manual intervention required" | |
| exit 1 | |
| fi | |
| # Delete the tag from remote | |
| git push --delete origin "v${{ needs.release.outputs.version }}" || true | |
| # Reset to commit before the release commit and force push | |
| git reset --hard HEAD~1 | |
| git push --force origin HEAD:${{ github.ref_name }} | |
| exit 1 | |
| # as a workaround to ownership issues (lost access to project) | |
| publish: | |
| name: publish package on PyPI and create GitHub release | |
| needs: ['setup', 'release', 'smoketest'] | |
| if: (( github.event.inputs.dry_run != 'true' ) && ( (needs.setup.outputs.publish_env == 'pypi') || (needs.setup.outputs.publish_env == 'testpypi') ) ) | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: ${{ needs.setup.outputs.publish_env }} | |
| url: ${{ needs.setup.outputs.publish_to }} | |
| permissions: | |
| id-token: write | |
| contents: write # needed for creating GitHub releases | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| ref: v${{ needs.release.outputs.version }} # checkout the tag | |
| - uses: ./.github/actions/refetch-artifacts | |
| - name: Create GitHub release | |
| run: | | |
| gh release create "v${{ needs.release.outputs.version }}" \ | |
| dist/*.whl dist/*.tar.gz \ | |
| --notes-from-tag | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| - name: Publish to PyPI | |
| if: needs.setup.outputs.publish_env=='pypi' | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| - name: Publish to TestPyPI | |
| if: needs.setup.outputs.publish_env=='testpypi' | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| repository-url: https://test.pypi.org/legacy/ # trailing slash matters! |