Skip to content

Merge pull request #121 from Dstack-TEE/feat/ingress-image-metadata #12

Merge pull request #121 from Dstack-TEE/feat/ingress-image-metadata

Merge pull request #121 from Dstack-TEE/feat/ingress-image-metadata #12

name: dstack-ingress Release
on:
workflow_dispatch:
push:
tags:
- 'dstack-ingress-v*'
permissions:
contents: write
packages: write
attestations: write
id-token: write
jobs:
build-and-attest:
runs-on: ubuntu-latest
defaults:
run:
working-directory: custom-domain/dstack-ingress
env:
IMAGE_REGISTRY: docker.io
IMAGE_REPOSITORY: ${{ vars.DOCKERHUB_ORG }}/dstack-ingress
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Parse and check version
run: |
# The image records its version from the committed VERSION file, so
# that a plain checkout reproduces the digest. The release tag only
# selects which commit to build and must agree with that file.
VERSION=$(tr -d '[:space:]' < VERSION)
if [ -z "${VERSION}" ]; then
echo "VERSION file is empty" >&2
exit 1
fi
case "${GITHUB_REF}" in
refs/tags/dstack-ingress-v*)
TAG_VERSION=${GITHUB_REF#refs/tags/dstack-ingress-v}
if [ "${TAG_VERSION}" != "${VERSION}" ]; then
echo "Tag dstack-ingress-v${TAG_VERSION} does not match the VERSION file (${VERSION})." >&2
echo "Update VERSION and re-tag, so the image version matches the release." >&2
exit 1
fi
;;
*)
echo "This workflow builds a release and must run on a dstack-ingress-v* tag." >&2
echo "Got ref: ${GITHUB_REF}. Re-run it selecting the release tag." >&2
exit 1
;;
esac
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
echo "IMAGE_REFERENCE=${IMAGE_REGISTRY}/${IMAGE_REPOSITORY}:${VERSION}" >> "$GITHUB_ENV"
echo "Parsed version: ${VERSION}"
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y skopeo jq
- name: Log in to Docker registry
uses: docker/login-action@v3
with:
registry: ${{ env.IMAGE_REGISTRY }}
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build reproducible image and push
env:
IMAGE_REFERENCE: ${{ env.IMAGE_REFERENCE }}
run: |
./build-image.sh --require-clean --push "${IMAGE_REFERENCE}"
- name: Capture image digest
id: capture-digest
run: |
DIGEST=$(skopeo inspect oci-archive:./oci.tar | jq -r '.Digest')
if [ -z "${DIGEST}" ]; then
echo "Failed to determine image digest" >&2
exit 1
fi
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-name: docker.io/${{ env.IMAGE_REPOSITORY }}
subject-digest: ${{ steps.capture-digest.outputs.digest }}
push-to-registry: true
- name: Publish summary
env:
IMAGE_REFERENCE: ${{ env.IMAGE_REFERENCE }}
IMAGE_DIGEST: ${{ steps.capture-digest.outputs.digest }}
run: |
{
echo "## dstack-ingress image"
echo ""
echo "- Tag: \`${IMAGE_REFERENCE}\`"
echo "- Digest: \`${IMAGE_DIGEST}\`"
echo "- Source: \`${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/tree/${GITHUB_SHA}/custom-domain/dstack-ingress\`"
echo "- Sigstore: https://search.sigstore.dev/?hash=${IMAGE_DIGEST}"
} >> "$GITHUB_STEP_SUMMARY"
- name: Release
uses: softprops/action-gh-release@v1
with:
body: |
## Docker Image Digest (SHA256)
| Image | Digest | Verification |
|---|---|---|
| ${{ env.IMAGE_REFERENCE }} | ${{ steps.capture-digest.outputs.digest }} | [Verify on Sigstore](https://search.sigstore.dev/?hash=${{ steps.capture-digest.outputs.digest }}) |
## Source
Built from [`${{ github.sha }}`](${{ github.server_url }}/${{ github.repository }}/tree/${{ github.sha }}/custom-domain/dstack-ingress). The image records its source repository, commit and version as OCI labels and manifest annotations:
```bash
skopeo inspect docker://${{ env.IMAGE_REFERENCE }} | jq .Labels
skopeo inspect --raw docker://${{ env.IMAGE_REFERENCE }} | jq .annotations
```
## Reproducible Build
Build on a native Linux amd64 host with Docker Buildx, Skopeo, jq and Git installed:
```bash
git clone ${{ github.server_url }}/${{ github.repository }}.git
cd dstack-examples/custom-domain/dstack-ingress
git checkout ${{ github.sha }}
./build-image.sh
skopeo inspect oci-archive:./oci.tar | jq -r '.Digest'
```
Expected digest: `${{ steps.capture-digest.outputs.digest }}`