88 - master
99 paths :
1010 - ' **/Dockerfile'
11+ pull_request :
12+ paths :
13+ - ' **/Dockerfile'
1114 workflow_dispatch :
1215 inputs :
1316 tag :
2427
2528env :
2629 REGISTRY : ghcr.io
27- # Use release tag for releases, input tag for manual runs, or 'latest' for push
28- IMAGE_TAG : ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.tag || 'latest' }}
2930 # Use provided connect-client version or default to 0.9
3031 CONNECT_CLIENT_VERSION : ${{ inputs.connect_client_version || '0.9' }}
3132
3233jobs :
34+ setup :
35+ runs-on : ubuntu-latest
36+ outputs :
37+ matrix : ${{ steps.set-matrix.outputs.matrix }}
38+ steps :
39+ - name : Define container matrix
40+ id : set-matrix
41+ run : |
42+ echo 'matrix={"container":[{"name":"ttyd","path":"./ttyd"},{"name":"streamlit","path":"./streamlit"},{"name":"cellxgene","path":"./cellxgene"},{"name":"shiny","path":"./shiny-simple-example"},{"name":"marimo","path":"./marimo"}]}' >> "$GITHUB_OUTPUT"
43+
3344 build-and-push :
3445 runs-on : ubuntu-latest
46+ needs : setup
3547 permissions :
3648 contents : read
3749 packages : write
38-
50+
3951 strategy :
40- matrix :
41- container : [
42- { name: 'ttyd', path: './ttyd' },
43- { name: 'streamlit', path: './streamlit' },
44- { name: 'cellxgene', path: './cellxgene' },
45- { name: 'shiny', path: './shiny-simple-example' },
46- { name: 'marimo', path: './marimo' },
47- { name: 'napari', path: './kasmVNC/napari' },
48- { name: 'qupath', path: './kasmVNC/qupath' },
49- { name: 'cellprofiler', path: './kasmVNC/cellprofiler' }
50- ]
52+ matrix : ${{ fromJson(needs.setup.outputs.matrix) }}
5153 fail-fast : false
5254
5355 steps :
6466 - name : Set up Docker Buildx
6567 uses : docker/setup-buildx-action@v3
6668
69+ - name : Determine image tag
70+ id : image-tag
71+ run : |
72+ if [[ "${{ github.event_name }}" == "pull_request" ]]; then
73+ echo "tag=pr-${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT"
74+ echo "is_pr=true" >> "$GITHUB_OUTPUT"
75+ elif [[ "${{ github.event_name }}" == "release" ]]; then
76+ echo "tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT"
77+ echo "is_pr=false" >> "$GITHUB_OUTPUT"
78+ elif [[ -n "${{ inputs.tag }}" && "${{ inputs.tag }}" != "latest" ]]; then
79+ echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
80+ echo "is_pr=false" >> "$GITHUB_OUTPUT"
81+ else
82+ echo "tag=latest" >> "$GITHUB_OUTPUT"
83+ echo "is_pr=false" >> "$GITHUB_OUTPUT"
84+ fi
85+
6786 - name : Build and push ${{ matrix.container.name }}
6887 uses : docker/build-push-action@v5
6988 with :
@@ -73,12 +92,74 @@ jobs:
7392 build-args : |
7493 CONNECT_CLIENT_VERSION=${{ env.CONNECT_CLIENT_VERSION }}
7594 tags : |
76- ${{ env.REGISTRY }}/${{ github.repository }}/${{ matrix.container.name }}:${{ env.IMAGE_TAG }}
77- ${{ env.REGISTRY }}/${{ github.repository }}/${{ matrix.container.name }}:latest
95+ ${{ env.REGISTRY }}/${{ github.repository }}/${{ matrix.container.name }}:${{ steps.image-tag.outputs.tag }}
96+ ${{ steps.image-tag.outputs.is_pr == 'false' && format('{0}/{1}/{2}:latest', env.REGISTRY, github.repository, matrix.container.name) || '' }}
7897 labels : |
7998 org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
8099 org.opencontainers.image.description=Custom Studios Example Container for ${{ matrix.container.name }}
81100 org.opencontainers.image.licenses=MIT
82- org.opencontainers.image.version=${{ env.IMAGE_TAG }}
101+ org.opencontainers.image.version=${{ steps.image-tag.outputs.tag }}
83102 cache-from : type=gha,scope=${{ matrix.container.name }}
84- cache-to : type=gha,mode=max,scope=${{ matrix.container.name }}
103+ cache-to : type=gha,mode=max,scope=${{ matrix.container.name }}
104+
105+ cleanup-pr-images :
106+ runs-on : ubuntu-latest
107+ needs : [setup, build-and-push]
108+ if : github.event_name == 'push' && github.ref == 'refs/heads/master'
109+ permissions :
110+ packages : write
111+
112+ strategy :
113+ matrix : ${{ fromJson(needs.setup.outputs.matrix) }}
114+
115+ steps :
116+ - name : Extract PR number from merge commit
117+ id : extract-pr
118+ env :
119+ COMMIT_MESSAGE : ${{ github.event.head_commit.message }}
120+ run : |
121+ # Extract PR number from merge commit message (format: "Merge pull request #123 from...")
122+ PR_NUMBER=$(echo "$COMMIT_MESSAGE" | grep -oP 'Merge pull request #\K[0-9]+' || echo "")
123+ if [[ -z "$PR_NUMBER" ]]; then
124+ echo "No PR number found in commit message, skipping cleanup"
125+ echo "found=false" >> "$GITHUB_OUTPUT"
126+ else
127+ echo "Found PR number: $PR_NUMBER"
128+ echo "found=true" >> "$GITHUB_OUTPUT"
129+ echo "number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
130+ fi
131+
132+ - name : Delete PR image for ${{ matrix.container.name }}
133+ if : steps.extract-pr.outputs.found == 'true'
134+ env :
135+ GH_TOKEN : ${{ secrets.GHCR_TOKEN }}
136+ PR_NUMBER : ${{ steps.extract-pr.outputs.number }}
137+ CONTAINER_NAME : ${{ matrix.container.name }}
138+ run : |
139+ PACKAGE_NAME="custom-studios-examples/${CONTAINER_NAME}"
140+ TAG="pr-${PR_NUMBER}"
141+
142+ echo "Checking for $PACKAGE_NAME:$TAG..."
143+
144+ # Get all versions of the package
145+ VERSIONS=$(gh api \
146+ -H "Accept: application/vnd.github+json" \
147+ -H "X-GitHub-Api-Version: 2022-11-28" \
148+ "/orgs/seqeralabs/packages/container/${PACKAGE_NAME}/versions" \
149+ --paginate 2>/dev/null || echo "[]")
150+
151+ # Find the version ID for our PR tag
152+ VERSION_ID=$(echo "$VERSIONS" | jq -r ".[] | select(.metadata.container.tags[] == \"$TAG\") | .id" 2>/dev/null || echo "")
153+
154+ if [[ -n "$VERSION_ID" ]]; then
155+ echo "Deleting $PACKAGE_NAME:$TAG (version ID: $VERSION_ID)..."
156+ gh api \
157+ --method DELETE \
158+ -H "Accept: application/vnd.github+json" \
159+ -H "X-GitHub-Api-Version: 2022-11-28" \
160+ "/orgs/seqeralabs/packages/container/${PACKAGE_NAME}/versions/${VERSION_ID}" \
161+ && echo "Successfully deleted $PACKAGE_NAME:$TAG" \
162+ || echo "Failed to delete $PACKAGE_NAME:$TAG (may not exist)"
163+ else
164+ echo "No image found for $PACKAGE_NAME:$TAG, skipping"
165+ fi
0 commit comments