Build and Push #158
Workflow file for this run
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: Build and Push | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| schedule: | |
| # Weekly rebuild on Mondays at 04:00 UTC to pick up fresh base image packages | |
| - cron: "0 4 * * 1" | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| packages: write | |
| id-token: write | |
| attestations: write | |
| concurrency: | |
| group: build-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| docker: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Set GHCR image name | |
| run: echo "GHCR_IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV" | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Log in to Docker Hub | |
| uses: docker/login-action@v4 | |
| with: | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Extract Docker metadata | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: | | |
| ${{ env.GHCR_IMAGE }} | |
| canvascoding/canvas-notebook | |
| tags: | | |
| type=ref,event=tag | |
| type=raw,value=latest | |
| - name: Build and push | |
| id: build | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| file: ./Dockerfile | |
| push: true | |
| platforms: linux/amd64 | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| provenance: mode=max | |
| sbom: true | |
| no-cache: ${{ github.event_name == 'schedule' }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Notify Control Plane release webhook | |
| env: | |
| CONTROL_PLANE_RELEASE_WEBHOOK_URL: ${{ secrets.CONTROL_PLANE_RELEASE_WEBHOOK_URL }} | |
| CONTROL_PLANE_RELEASE_WEBHOOK_SECRET: ${{ secrets.CONTROL_PLANE_RELEASE_WEBHOOK_SECRET }} | |
| DOCKER_METADATA_TAGS: ${{ steps.meta.outputs.tags }} | |
| IMAGE_DIGEST: ${{ steps.build.outputs.digest }} | |
| run: | | |
| if [ -z "${CONTROL_PLANE_RELEASE_WEBHOOK_URL}" ]; then | |
| echo "CONTROL_PLANE_RELEASE_WEBHOOK_URL secret is missing." | |
| exit 1 | |
| fi | |
| if [ -z "${CONTROL_PLANE_RELEASE_WEBHOOK_SECRET}" ]; then | |
| echo "CONTROL_PLANE_RELEASE_WEBHOOK_SECRET secret is missing." | |
| exit 1 | |
| fi | |
| node --input-type=module <<'NODE' | |
| import crypto from 'node:crypto'; | |
| import fs from 'node:fs'; | |
| const tags = (process.env.DOCKER_METADATA_TAGS || '') | |
| .split(/\r?\n/) | |
| .map((tag) => tag.trim()) | |
| .filter(Boolean); | |
| const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')); | |
| const isTag = process.env.GITHUB_REF_TYPE === 'tag'; | |
| const tag = isTag ? process.env.GITHUB_REF_NAME || '' : ''; | |
| const eventName = process.env.GITHUB_EVENT_NAME || ''; | |
| const event = isTag ? 'release_published' : eventName === 'schedule' ? 'image_rebuilt' : 'image_pushed'; | |
| const version = tag ? tag.replace(/^v/i, '') : String(packageJson.version || ''); | |
| const payload = { | |
| event, | |
| repository: process.env.GITHUB_REPOSITORY, | |
| ref: process.env.GITHUB_REF, | |
| tag: tag || undefined, | |
| version, | |
| commitSha: process.env.GITHUB_SHA, | |
| image: { | |
| name: process.env.GHCR_IMAGE, | |
| tags, | |
| digest: process.env.IMAGE_DIGEST || undefined, | |
| }, | |
| workflow: { | |
| runId: process.env.GITHUB_RUN_ID, | |
| runNumber: process.env.GITHUB_RUN_NUMBER, | |
| runAttempt: process.env.GITHUB_RUN_ATTEMPT, | |
| }, | |
| source: 'github_actions', | |
| publishedAt: new Date().toISOString(), | |
| metadata: { | |
| actor: process.env.GITHUB_ACTOR, | |
| eventName, | |
| workflow: process.env.GITHUB_WORKFLOW, | |
| }, | |
| }; | |
| const body = JSON.stringify(payload); | |
| const timestamp = Math.floor(Date.now() / 1000).toString(); | |
| const delivery = `${process.env.GITHUB_RUN_ID}-${process.env.GITHUB_RUN_ATTEMPT}-${process.env.GITHUB_SHA}`; | |
| const signature = 'sha256=' + crypto | |
| .createHmac('sha256', process.env.CONTROL_PLANE_RELEASE_WEBHOOK_SECRET) | |
| .update(`${timestamp}.${body}`) | |
| .digest('hex'); | |
| const response = await fetch(process.env.CONTROL_PLANE_RELEASE_WEBHOOK_URL, { | |
| method: 'POST', | |
| headers: { | |
| 'content-type': 'application/json', | |
| 'x-canvas-delivery': delivery, | |
| 'x-canvas-timestamp': timestamp, | |
| 'x-canvas-signature': signature, | |
| }, | |
| body, | |
| }); | |
| const text = await response.text(); | |
| if (!response.ok) { | |
| console.error(`Control Plane release webhook failed (${response.status}): ${text}`); | |
| process.exit(1); | |
| } | |
| console.log(`Control Plane release webhook accepted: ${text}`); | |
| NODE |