diff --git a/.github/workflows/docker-cleanup.yml b/.github/workflows/docker-cleanup.yml new file mode 100644 index 0000000..0e5db91 --- /dev/null +++ b/.github/workflows/docker-cleanup.yml @@ -0,0 +1,107 @@ +name: Cleanup PR Docker Images + +on: + pull_request: + types: [closed] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + cleanup: + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + + steps: + - name: Delete PR Docker images + uses: actions/github-script@v7 + with: + script: | + const owner = context.repo.owner.toLowerCase(); + const repo = context.repo.repo.toLowerCase(); + const prNumber = context.payload.pull_request.number; + + // Get the package + const packageName = `${repo}`; + + console.log(`Cleaning up images for PR #${prNumber}`); + + // Check if owner is an org or user + let isOrg = false; + try { + await github.rest.orgs.get({ org: owner }); + isOrg = true; + console.log(`Repository owner is an organization`); + } catch (error) { + console.log(`Repository owner is a user account`); + } + + try { + // Get all versions of the package + let versions; + if (isOrg) { + versions = await github.rest.packages.getAllPackageVersionsForPackageOwnedByOrg({ + package_type: 'container', + package_name: packageName, + org: owner, + per_page: 100 + }); + } else { + versions = await github.rest.packages.getAllPackageVersionsForPackageOwnedByUser({ + package_type: 'container', + package_name: packageName, + username: owner, + per_page: 100 + }); + } + + // Filter versions that match this PR (pr-123, pr-123-amd64, pr-123-arm64) + const prVersions = versions.data.filter(version => { + const tags = version.metadata?.container?.tags || []; + // Match tags like: pr-123, pr-123-amd64, pr-123-arm64 + return tags.some(tag => + tag === `pr-${prNumber}` || + tag === `pr-${prNumber}-amd64` || + tag === `pr-${prNumber}-arm64` + ); + }); + + console.log(`Found ${prVersions.length} image version(s) to delete`); + + // Delete each version + for (const version of prVersions) { + console.log(`Deleting version ${version.id} with tags: ${version.metadata?.container?.tags?.join(', ')}`); + try { + if (isOrg) { + await github.rest.packages.deletePackageVersionForOrg({ + package_type: 'container', + package_name: packageName, + org: owner, + package_version_id: version.id + }); + } else { + await github.rest.packages.deletePackageVersionForUser({ + package_type: 'container', + package_name: packageName, + username: owner, + package_version_id: version.id + }); + } + console.log(`✓ Deleted version ${version.id}`); + } catch (error) { + console.error(`Failed to delete version ${version.id}:`, error.message); + } + } + + console.log('Cleanup completed'); + } catch (error) { + if (error.status === 404) { + console.log('No package found - nothing to clean up'); + } else { + console.error('Error during cleanup:', error.message); + throw error; + } + } diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 7f07913..6035144 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -12,8 +12,26 @@ on: branches: ["*"] # Publish semver tags as releases. tags: ["v*.*.*"] + paths: + - "src/**" + - "main.py" + - "tests/**" + - "Dockerfile" + - "pyproject.toml" + - "uv.lock" + - "compose.yaml" + - ".github/workflows/docker-publish.yml" pull_request: branches: ["main"] + paths: + - "src/**" + - "main.py" + - "tests/**" + - "Dockerfile" + - "pyproject.toml" + - "uv.lock" + - "compose.yaml" + - ".github/workflows/docker-publish.yml" workflow_dispatch: env: @@ -54,7 +72,6 @@ jobs: build: needs: test - if: github.event_name != 'pull_request' runs-on: ubuntu-latest permissions: contents: read @@ -74,8 +91,13 @@ jobs: run: | SURFIX=$(echo ${{ matrix.platform }} | cut -d'/' -f2) echo "SURFIX=$SURFIX" >> $GITHUB_OUTPUT - # Generate a unique local tag for the image - echo "LOCAL_TAG=${{ github.sha }}-$SURFIX" >> $GITHUB_OUTPUT + + # Use PR number for PRs, SHA for everything else + if [ "${{ github.event_name }}" == "pull_request" ]; then + echo "LOCAL_TAG=pr-${{ github.event.pull_request.number }}-$SURFIX" >> $GITHUB_OUTPUT + else + echo "LOCAL_TAG=${{ github.sha }}-$SURFIX" >> $GITHUB_OUTPUT + fi - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -100,14 +122,14 @@ jobs: tags: type=raw,value=${{ steps.vars.outputs.LOCAL_TAG }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - # Build and export Docker image for each platform (without pushing) + # Build and push Docker image for each platform - name: Build Docker image id: build uses: docker/build-push-action@v6 with: context: . pull: true - push: ${{ github.event_name != 'pull_request' }} + push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} platforms: ${{ matrix.platform }} @@ -178,9 +200,16 @@ jobs: echo $args - docker buildx imagetools create $args \ - ${image}:${{github.sha}}-amd64 \ - ${image}:${{github.sha}}-arm64 + # Use PR-based tags for PRs, SHA-based tags for everything else + if [ "${{ github.event_name }}" == "pull_request" ]; then + docker buildx imagetools create $args \ + ${image}:pr-${{ github.event.pull_request.number }}-amd64 \ + ${image}:pr-${{ github.event.pull_request.number }}-arm64 + else + docker buildx imagetools create $args \ + ${image}:${{github.sha}}-amd64 \ + ${image}:${{github.sha}}-arm64 + fi # Sign the manifest - name: Sign the manifests diff --git a/README.md b/README.md index e83bc60..05f2453 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ Recently I've partnered with a _new in town_ proxy service - ProxyBase - to offe - `v*.*.*`/`latest` - Releases considered stable - `main` - Latest release from main branch (untested) +- `pr-{number}` - Pull request images for testing (automatically cleaned up when PR closes) ## Usage