name: Release on: release: types: [published] jobs: release: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 persist-credentials: false - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.13' - name: Install build tooling uses: ./.github/actions/setup-hatch - name: Get Version id: version env: REF_NAME: ${{ github.ref_name }} run: | RAW_VERSION=$(hatch version) echo "VERSION=$RAW_VERSION" >> $GITHUB_ENV if [ "v$RAW_VERSION" != "$REF_NAME" ]; then echo "Error: Git tag ($REF_NAME) does not match hatch version (v$RAW_VERSION)" exit 1 fi - name: Check if version exists on PyPI id: version_check env: VERSION: ${{ env.VERSION }} run: | if curl -s -f https://pypi.org/pypi/socketsecurity/$VERSION/json > /dev/null; then echo "Version ${VERSION} already exists on PyPI" echo "pypi_exists=true" >> $GITHUB_OUTPUT else echo "Version ${VERSION} not found on PyPI - proceeding with PyPI deployment" echo "pypi_exists=false" >> $GITHUB_OUTPUT fi - name: Check Docker image existence id: docker_check env: VERSION: ${{ env.VERSION }} run: | if curl -s -f "https://hub.docker.com/v2/repositories/socketdev/cli/tags/${VERSION}" > /dev/null; then echo "Docker image socketdev/cli:${VERSION} already exists" echo "docker_exists=true" >> $GITHUB_OUTPUT else echo "docker_exists=false" >> $GITHUB_OUTPUT fi - name: Build package if: steps.version_check.outputs.pypi_exists != 'true' run: | pip install hatchling hatch build - name: Publish to PyPI if: steps.version_check.outputs.pypi_exists != 'true' uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 - name: Set up Docker publishing uses: ./.github/actions/setup-docker with: dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} - name: Verify package is installable id: verify_package env: VERSION: ${{ env.VERSION }} run: | # The first lookup can race PyPI's Simple-index propagation, and a delayed # CDN purge can leave the index stale well after a successful upload. # pip caches HTTP responses by default, so without --no-cache-dir every # retry can reuse that initial stale response instead of checking whether # the release has appeared. Budget: 30 minutes. MAX_ATTEMPTS=60 for i in $(seq 1 "$MAX_ATTEMPTS"); do if python -m pip install \ --no-cache-dir \ --index-url https://pypi.org/simple/ \ "socketsecurity==${VERSION}"; then echo "Package ${VERSION} is now available and installable on PyPI" python -m pip uninstall -y socketsecurity echo "success=true" >> "$GITHUB_OUTPUT" exit 0 fi if curl -s -f "https://pypi.org/pypi/socketsecurity/${VERSION}/json" > /dev/null; then echo "Release ${VERSION} exists on PyPI (JSON API) but is not in the Simple index yet - CDN propagation delay" fi if [ "$i" -lt "$MAX_ATTEMPTS" ]; then echo "Attempt $i: Package not yet installable, waiting 30s... (${i}/${MAX_ATTEMPTS})" sleep 30 fi done echo "success=false" >> "$GITHUB_OUTPUT" exit 1 - name: Build & Push Docker if: | steps.verify_package.outputs.success == 'true' && steps.docker_check.outputs.docker_exists != 'true' uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 env: VERSION: ${{ env.VERSION }} with: push: true platforms: linux/amd64,linux/arm64 cache-from: type=gha cache-to: type=gha,mode=max tags: | socketdev/cli:latest socketdev/cli:${{ env.VERSION }} build-args: | CLI_VERSION=${{ env.VERSION }}