Beta weight vega greeks against volatility index instruments (#4097) #2809
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: docker | |
| permissions: # Principle of least privilege | |
| contents: read | |
| actions: read | |
| on: | |
| push: | |
| branches: [master, nightly, test-docker] | |
| workflow_dispatch: | |
| env: | |
| # SPDX in-toto predicate URI emitted by `actions/attest-sbom` for the SPDX 2.3 | |
| # SBOMs produced by anchore/sbom-action (syft 1.42.x). Must exact-match | |
| # `--predicate-type` since cosign's `spdx` alias maps to the unversioned | |
| # `https://spdx.dev/Document` and would skip these attestations. | |
| SPDX_PREDICATE_TYPE: https://spdx.dev/Document/v2.3 | |
| jobs: | |
| # Test builds only - no publishing | |
| test-docker-build: | |
| name: test-build-${{ matrix.platform }} | |
| if: github.ref == 'refs/heads/test-docker' | |
| runs-on: ${{ matrix.runner }} | |
| permissions: | |
| packages: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: linux/amd64 | |
| runner: ubuntu-22.04 | |
| - platform: linux/arm64 | |
| runner: ubuntu-22.04-arm | |
| env: | |
| BUILD_MODE: release | |
| steps: | |
| # https://github.com/step-security/harden-runner | |
| - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 | |
| with: | |
| egress-policy: ${{ vars.STEP_SECURITY_EGRESS_POLICY || 'block' }} | |
| allowed-endpoints: >- | |
| ${{ vars.COMMON_ALLOWED_ENDPOINTS }} | |
| ${{ vars.CI_ALLOWED_ENDPOINTS }} | |
| # Docker Hub (pull base images) | |
| registry-1.docker.io:443 | |
| auth.docker.io:443 | |
| production.cloudflare.docker.com:443 | |
| # GitHub Container Registry (pull images) | |
| ghcr.io:443 | |
| # Debian package mirrors (inside docker build) | |
| deb.debian.org:443 | |
| security.debian.org:443 | |
| # Rust toolchain install | |
| sh.rustup.rs:443 | |
| static.rust-lang.org:443 | |
| # PyPI for Python packages during image build | |
| pypi.org:443 | |
| files.pythonhosted.org:443 | |
| # Cargo crates downloads | |
| crates.io:443 | |
| static.crates.io:443 | |
| - name: Free disk space (Ubuntu) | |
| # https://github.com/jlumbroso/free-disk-space | |
| uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 | |
| with: | |
| tool-cache: true | |
| android: false | |
| dotnet: false | |
| haskell: false | |
| large-packages: true | |
| docker-images: true | |
| swap-storage: true | |
| - name: Checkout repository | |
| # https://github.com/actions/checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| fetch-depth: 1 | |
| - name: Start local registry | |
| run: docker run -d -p 5000:5000 --name registry registry:2 | |
| - name: Set up Docker Buildx | |
| # https://github.com/docker/setup-buildx-action | |
| uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 | |
| with: | |
| driver-opts: network=host | |
| - name: Login to GHCR | |
| # https://github.com/docker/login-action | |
| uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.repository_owner }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Generate platform slug | |
| id: platform-slug | |
| run: | | |
| echo "slug=$(echo '${{ matrix.platform }}' | sed 's/\//-/g')" >> "$GITHUB_OUTPUT" | |
| - name: Build nautilus_trader (push to local registry) | |
| # https://github.com/docker/build-push-action | |
| uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 | |
| with: | |
| context: . | |
| file: .docker/nautilus_trader.dockerfile | |
| platforms: ${{ matrix.platform }} | |
| push: true | |
| tags: localhost:5000/nautilus_trader:test-${{ steps.platform-slug.outputs.slug }} | |
| cache-from: type=gha,scope=nautilus_trader-test-${{ steps.platform-slug.outputs.slug }} | |
| cache-to: type=gha,mode=max,scope=nautilus_trader-test-${{ steps.platform-slug.outputs.slug }} | |
| - name: Create temporary jupyterlab Dockerfile | |
| run: | | |
| sed 's|ghcr.io/nautechsystems/nautilus_trader|localhost:5000/nautilus_trader|' \ | |
| .docker/jupyterlab.dockerfile > /tmp/jupyterlab-test.dockerfile | |
| - name: Build jupyterlab (push to local registry) | |
| # https://github.com/docker/build-push-action | |
| uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 | |
| with: | |
| context: . | |
| file: /tmp/jupyterlab-test.dockerfile | |
| platforms: ${{ matrix.platform }} | |
| push: true | |
| tags: localhost:5000/jupyterlab:test-${{ steps.platform-slug.outputs.slug }} | |
| cache-from: type=gha,scope=jupyterlab-test-${{ steps.platform-slug.outputs.slug }} | |
| cache-to: type=gha,mode=max,scope=jupyterlab-test-${{ steps.platform-slug.outputs.slug }} | |
| build-args: | | |
| GIT_TAG=test-${{ steps.platform-slug.outputs.slug }} | |
| # ----------------------------------------------------------------------------------------------- | |
| # Production builds with multi-platform support | |
| # ----------------------------------------------------------------------------------------------- | |
| build-docker-images-nautilus-trader: | |
| name: build-docker-images (nautilus_trader-${{ matrix.platform }}) | |
| if: github.ref != 'refs/heads/test-docker' | |
| runs-on: ${{ matrix.runner }} | |
| permissions: | |
| packages: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: linux/amd64 | |
| runner: ubuntu-22.04 | |
| - platform: linux/arm64 | |
| runner: ubuntu-22.04-arm | |
| env: | |
| BUILD_MODE: release | |
| steps: | |
| # https://github.com/step-security/harden-runner | |
| - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 | |
| with: | |
| egress-policy: ${{ vars.STEP_SECURITY_EGRESS_POLICY || 'block' }} | |
| allowed-endpoints: >- | |
| ${{ vars.COMMON_ALLOWED_ENDPOINTS }} | |
| ${{ vars.CI_ALLOWED_ENDPOINTS }} | |
| # Docker Hub (pull base images) | |
| registry-1.docker.io:443 | |
| auth.docker.io:443 | |
| production.cloudflare.docker.com:443 | |
| # GitHub Container Registry (push images) | |
| ghcr.io:443 | |
| # Debian package mirrors (inside docker build) | |
| deb.debian.org:443 | |
| security.debian.org:443 | |
| # Rust toolchain install | |
| sh.rustup.rs:443 | |
| static.rust-lang.org:443 | |
| # PyPI for Python packages during image build | |
| pypi.org:443 | |
| files.pythonhosted.org:443 | |
| # Cargo crates downloads | |
| crates.io:443 | |
| static.crates.io:443 | |
| - name: Free disk space (Ubuntu) | |
| # https://github.com/jlumbroso/free-disk-space | |
| uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 | |
| with: | |
| tool-cache: true | |
| android: false | |
| dotnet: false | |
| haskell: false | |
| large-packages: true | |
| docker-images: true | |
| swap-storage: true | |
| - name: Checkout repository | |
| # https://github.com/actions/checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| fetch-depth: 1 | |
| - name: Set up Docker Buildx | |
| # https://github.com/docker/setup-buildx-action | |
| uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 | |
| - name: Login to GHCR | |
| # https://github.com/docker/login-action | |
| uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.repository_owner }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Get branch name | |
| id: branch-name | |
| run: | | |
| echo "current_branch=${GITHUB_REF#refs/heads/}" >> "$GITHUB_OUTPUT" | |
| - name: Generate platform slug | |
| id: platform-slug | |
| run: | | |
| echo "slug=$(echo '${{ matrix.platform }}' | sed 's/\//-/g')" >> "$GITHUB_OUTPUT" | |
| - name: Prepare Docker metadata | |
| id: meta | |
| # https://github.com/docker/metadata-action | |
| uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 | |
| with: | |
| images: ghcr.io/${{ github.repository_owner }}/nautilus_trader | |
| tags: | | |
| type=raw,value=nightly,enable=${{ steps.branch-name.outputs.current_branch == 'nightly' }} | |
| type=raw,value=latest,enable=${{ steps.branch-name.outputs.current_branch == 'master' }} | |
| - name: Build and push by digest | |
| id: build | |
| # https://github.com/docker/build-push-action | |
| uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 | |
| with: | |
| context: . | |
| file: .docker/nautilus_trader.dockerfile | |
| platforms: ${{ matrix.platform }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha,scope=nautilus_trader-${{ steps.platform-slug.outputs.slug }} | |
| cache-to: type=gha,mode=max,scope=nautilus_trader-${{ steps.platform-slug.outputs.slug }} | |
| # yamllint disable-line rule:line-length | |
| outputs: type=image,name=ghcr.io/${{ github.repository_owner }}/nautilus_trader,push-by-digest=true, name-canonical=true,push=true | |
| - name: Export digest | |
| run: | | |
| mkdir -p /tmp/digests/nautilus_trader | |
| digest="${{ steps.build.outputs.digest }}" | |
| touch "/tmp/digests/nautilus_trader/${digest#sha256:}" | |
| - name: Upload digest | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: digests-nautilus_trader-${{ strategy.job-index }} | |
| path: /tmp/digests/nautilus_trader/* | |
| if-no-files-found: error | |
| retention-days: 1 | |
| merge-nautilus-trader: | |
| name: merge-nautilus_trader | |
| runs-on: ubuntu-latest | |
| permissions: | |
| packages: write | |
| id-token: write | |
| attestations: write | |
| needs: build-docker-images-nautilus-trader | |
| steps: | |
| # https://github.com/step-security/harden-runner | |
| - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 | |
| with: | |
| egress-policy: ${{ vars.STEP_SECURITY_EGRESS_POLICY || 'block' }} | |
| allowed-endpoints: >- | |
| ${{ vars.COMMON_ALLOWED_ENDPOINTS }} | |
| ${{ vars.CI_ALLOWED_ENDPOINTS }} | |
| # GitHub Container Registry (push manifests, signatures, attestations) | |
| ghcr.io:443 | |
| # Sigstore TUF trust root (used by cosign 3.x and actions/attest) | |
| tuf-repo-cdn.sigstore.dev:443 | |
| - name: Checkout repository | |
| # Required so the local ./.github/actions/attest-sbom-retry composite | |
| # resolves at runtime; merge jobs only download digests otherwise. | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Download digests | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| path: /tmp/digests/nautilus_trader | |
| pattern: digests-nautilus_trader-* | |
| merge-multiple: true | |
| - name: Set up Docker Buildx | |
| # https://github.com/docker/setup-buildx-action | |
| uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 | |
| - name: Login to GHCR | |
| # https://github.com/docker/login-action | |
| uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.repository_owner }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Get branch name | |
| id: branch-name | |
| run: | | |
| echo "current_branch=${GITHUB_REF#refs/heads/}" >> "$GITHUB_OUTPUT" | |
| - name: Prepare Docker metadata | |
| id: meta | |
| # https://github.com/docker/metadata-action | |
| uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 | |
| with: | |
| images: ghcr.io/${{ github.repository_owner }}/nautilus_trader | |
| tags: | | |
| type=raw,value=nightly,enable=${{ steps.branch-name.outputs.current_branch == 'nightly' }} | |
| type=raw,value=latest,enable=${{ steps.branch-name.outputs.current_branch == 'master' }} | |
| - name: Create manifest list and push | |
| id: push | |
| working-directory: /tmp/digests/nautilus_trader | |
| shell: bash | |
| run: | | |
| mapfile -t tags < <(jq -cr '.tags[] | "-t", .' <<< "$DOCKER_METADATA_OUTPUT_JSON") | |
| mapfile -t digests < <(printf 'ghcr.io/${{ github.repository_owner }}/nautilus_trader@sha256:%s\n' *) | |
| docker buildx imagetools create "${tags[@]}" "${digests[@]}" | |
| TAG=$(jq -r '.tags[0]' <<< "$DOCKER_METADATA_OUTPUT_JSON" | cut -d: -f2) | |
| IMAGE="ghcr.io/${{ github.repository_owner }}/nautilus_trader:$TAG" | |
| DIGEST=$(docker buildx imagetools inspect "$IMAGE" --format '{{json .Manifest}}' | jq -r '.digest') | |
| echo "digest=$DIGEST" >> "$GITHUB_OUTPUT" | |
| - name: Inspect image | |
| run: | | |
| docker buildx imagetools inspect \ | |
| "ghcr.io/${{ github.repository_owner }}/nautilus_trader@${{ steps.push.outputs.digest }}" | |
| - name: Install cosign | |
| uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 | |
| with: | |
| cosign-release: v3.0.6 | |
| - name: Sign image with cosign | |
| run: | | |
| cosign sign --yes \ | |
| "ghcr.io/${{ github.repository_owner }}/nautilus_trader@${{ steps.push.outputs.digest }}" | |
| - name: Generate SBOM | |
| uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 | |
| with: | |
| image: ghcr.io/${{ github.repository_owner }}/nautilus_trader@${{ steps.push.outputs.digest }} | |
| output-file: sbom.spdx.json | |
| - name: Attest SBOM | |
| uses: ./.github/actions/attest-sbom-retry | |
| with: | |
| subject-name: ghcr.io/${{ github.repository_owner }}/nautilus_trader | |
| subject-digest: ${{ steps.push.outputs.digest }} | |
| sbom-path: sbom.spdx.json | |
| - name: Verify image signature and SBOM attestation | |
| timeout-minutes: 15 | |
| env: | |
| IMAGE: ghcr.io/${{ github.repository_owner }}/nautilus_trader@${{ steps.push.outputs.digest }} | |
| IDENTITY: ${{ github.server_url }}/${{ github.workflow_ref }} | |
| ISSUER: https://token.actions.githubusercontent.com | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| retry() { | |
| local label=$1 | |
| shift | |
| for attempt in 1 2 3; do | |
| if "$@"; then | |
| return 0 | |
| fi | |
| if [ "$attempt" -eq 3 ]; then | |
| echo "::error::${label} failed after 3 attempts" | |
| return 1 | |
| fi | |
| echo "::warning::${label} attempt ${attempt} failed, retrying in 30s" | |
| sleep 30 | |
| done | |
| } | |
| retry "cosign verify" \ | |
| timeout 90 cosign verify "$IMAGE" \ | |
| --timeout=60s \ | |
| --certificate-identity "$IDENTITY" \ | |
| --certificate-oidc-issuer "$ISSUER" | |
| retry "gh attestation verify" \ | |
| timeout 90 gh attestation verify "oci://${IMAGE}" \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --predicate-type "$SPDX_PREDICATE_TYPE" \ | |
| --cert-identity "$IDENTITY" \ | |
| --cert-oidc-issuer "$ISSUER" | |
| build-docker-images-jupyterlab: | |
| name: build-docker-images (jupyterlab-${{ matrix.platform }}) | |
| if: github.ref != 'refs/heads/test-docker' | |
| runs-on: ${{ matrix.runner }} | |
| permissions: | |
| packages: write | |
| needs: merge-nautilus-trader | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: linux/amd64 | |
| runner: ubuntu-22.04 | |
| - platform: linux/arm64 | |
| runner: ubuntu-22.04-arm | |
| env: | |
| BUILD_MODE: release | |
| steps: | |
| # https://github.com/step-security/harden-runner | |
| - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 | |
| with: | |
| egress-policy: ${{ vars.STEP_SECURITY_EGRESS_POLICY || 'block' }} | |
| allowed-endpoints: >- | |
| ${{ vars.COMMON_ALLOWED_ENDPOINTS }} | |
| ${{ vars.CI_ALLOWED_ENDPOINTS }} | |
| # Docker Hub (pull base images) | |
| registry-1.docker.io:443 | |
| auth.docker.io:443 | |
| production.cloudflare.docker.com:443 | |
| # GitHub Container Registry (push images) | |
| ghcr.io:443 | |
| # Debian package mirrors (inside docker build) | |
| deb.debian.org:443 | |
| security.debian.org:443 | |
| # Rust toolchain install | |
| sh.rustup.rs:443 | |
| static.rust-lang.org:443 | |
| # PyPI for Python packages during image build | |
| pypi.org:443 | |
| files.pythonhosted.org:443 | |
| # Cargo crates downloads | |
| crates.io:443 | |
| static.crates.io:443 | |
| - name: Free disk space (Ubuntu) | |
| # https://github.com/jlumbroso/free-disk-space | |
| uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 | |
| with: | |
| tool-cache: true | |
| android: false | |
| dotnet: false | |
| haskell: false | |
| large-packages: true | |
| docker-images: true | |
| swap-storage: true | |
| - name: Checkout repository | |
| # https://github.com/actions/checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| fetch-depth: 1 | |
| - name: Set up Docker Buildx | |
| # https://github.com/docker/setup-buildx-action | |
| uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 | |
| - name: Login to GHCR | |
| # https://github.com/docker/login-action | |
| uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.repository_owner }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Get branch name | |
| id: branch-name | |
| run: | | |
| echo "current_branch=${GITHUB_REF#refs/heads/}" >> "$GITHUB_OUTPUT" | |
| - name: Generate platform slug | |
| id: platform-slug | |
| run: | | |
| echo "slug=$(echo '${{ matrix.platform }}' | sed 's/\//-/g')" >> "$GITHUB_OUTPUT" | |
| - name: Determine image tag | |
| id: image-tag | |
| run: | | |
| if [ "${{ steps.branch-name.outputs.current_branch }}" = "master" ]; then | |
| echo "tag=latest" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "tag=${{ steps.branch-name.outputs.current_branch }}" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Prepare Docker metadata | |
| id: meta | |
| # https://github.com/docker/metadata-action | |
| uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 | |
| with: | |
| images: ghcr.io/${{ github.repository_owner }}/jupyterlab | |
| tags: | | |
| type=raw,value=nightly,enable=${{ steps.branch-name.outputs.current_branch == 'nightly' }} | |
| type=raw,value=latest,enable=${{ steps.branch-name.outputs.current_branch == 'master' }} | |
| - name: Build and push by digest | |
| id: build | |
| # https://github.com/docker/build-push-action | |
| uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 | |
| with: | |
| context: . | |
| file: .docker/jupyterlab.dockerfile | |
| platforms: ${{ matrix.platform }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha,scope=jupyterlab-${{ steps.platform-slug.outputs.slug }} | |
| cache-to: type=gha,mode=max,scope=jupyterlab-${{ steps.platform-slug.outputs.slug }} | |
| # yamllint disable-line rule:line-length | |
| outputs: type=image,name=ghcr.io/${{ github.repository_owner }}/jupyterlab,push-by-digest=true, name-canonical=true,push=true | |
| build-args: | | |
| GIT_TAG=${{ steps.image-tag.outputs.tag }} | |
| - name: Export digest | |
| run: | | |
| mkdir -p /tmp/digests/jupyterlab | |
| digest="${{ steps.build.outputs.digest }}" | |
| touch "/tmp/digests/jupyterlab/${digest#sha256:}" | |
| - name: Upload digest | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: digests-jupyterlab-${{ strategy.job-index }} | |
| path: /tmp/digests/jupyterlab/* | |
| if-no-files-found: error | |
| retention-days: 1 | |
| merge-jupyterlab: | |
| name: merge-jupyterlab | |
| runs-on: ubuntu-latest | |
| permissions: | |
| packages: write | |
| id-token: write | |
| attestations: write | |
| needs: build-docker-images-jupyterlab | |
| steps: | |
| # https://github.com/step-security/harden-runner | |
| - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 | |
| with: | |
| egress-policy: ${{ vars.STEP_SECURITY_EGRESS_POLICY || 'block' }} | |
| allowed-endpoints: >- | |
| ${{ vars.COMMON_ALLOWED_ENDPOINTS }} | |
| ${{ vars.CI_ALLOWED_ENDPOINTS }} | |
| # GitHub Container Registry (push manifests, signatures, attestations) | |
| ghcr.io:443 | |
| # Sigstore TUF trust root (used by cosign 3.x and actions/attest) | |
| tuf-repo-cdn.sigstore.dev:443 | |
| - name: Checkout repository | |
| # Required so the local ./.github/actions/attest-sbom-retry composite | |
| # resolves at runtime; merge jobs only download digests otherwise. | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Download digests | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| path: /tmp/digests/jupyterlab | |
| pattern: digests-jupyterlab-* | |
| merge-multiple: true | |
| - name: Set up Docker Buildx | |
| # https://github.com/docker/setup-buildx-action | |
| uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 | |
| - name: Login to GHCR | |
| # https://github.com/docker/login-action | |
| uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.repository_owner }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Get branch name | |
| id: branch-name | |
| run: | | |
| echo "current_branch=${GITHUB_REF#refs/heads/}" >> "$GITHUB_OUTPUT" | |
| - name: Prepare Docker metadata | |
| id: meta | |
| # https://github.com/docker/metadata-action | |
| uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 | |
| with: | |
| images: ghcr.io/${{ github.repository_owner }}/jupyterlab | |
| tags: | | |
| type=raw,value=nightly,enable=${{ steps.branch-name.outputs.current_branch == 'nightly' }} | |
| type=raw,value=latest,enable=${{ steps.branch-name.outputs.current_branch == 'master' }} | |
| - name: Create manifest list and push | |
| id: push | |
| working-directory: /tmp/digests/jupyterlab | |
| shell: bash | |
| run: | | |
| mapfile -t tags < <(jq -cr '.tags[] | "-t", .' <<< "$DOCKER_METADATA_OUTPUT_JSON") | |
| mapfile -t digests < <(printf 'ghcr.io/${{ github.repository_owner }}/jupyterlab@sha256:%s\n' *) | |
| docker buildx imagetools create "${tags[@]}" "${digests[@]}" | |
| TAG=$(jq -r '.tags[0]' <<< "$DOCKER_METADATA_OUTPUT_JSON" | cut -d: -f2) | |
| IMAGE="ghcr.io/${{ github.repository_owner }}/jupyterlab:$TAG" | |
| DIGEST=$(docker buildx imagetools inspect "$IMAGE" --format '{{json .Manifest}}' | jq -r '.digest') | |
| echo "digest=$DIGEST" >> "$GITHUB_OUTPUT" | |
| - name: Inspect image | |
| run: | | |
| docker buildx imagetools inspect \ | |
| "ghcr.io/${{ github.repository_owner }}/jupyterlab@${{ steps.push.outputs.digest }}" | |
| - name: Install cosign | |
| uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 | |
| with: | |
| cosign-release: v3.0.6 | |
| - name: Sign image with cosign | |
| run: | | |
| cosign sign --yes \ | |
| "ghcr.io/${{ github.repository_owner }}/jupyterlab@${{ steps.push.outputs.digest }}" | |
| - name: Generate SBOM | |
| uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 | |
| with: | |
| image: ghcr.io/${{ github.repository_owner }}/jupyterlab@${{ steps.push.outputs.digest }} | |
| output-file: sbom.spdx.json | |
| - name: Attest SBOM | |
| uses: ./.github/actions/attest-sbom-retry | |
| with: | |
| subject-name: ghcr.io/${{ github.repository_owner }}/jupyterlab | |
| subject-digest: ${{ steps.push.outputs.digest }} | |
| sbom-path: sbom.spdx.json | |
| - name: Verify image signature and SBOM attestation | |
| timeout-minutes: 15 | |
| env: | |
| IMAGE: ghcr.io/${{ github.repository_owner }}/jupyterlab@${{ steps.push.outputs.digest }} | |
| IDENTITY: ${{ github.server_url }}/${{ github.workflow_ref }} | |
| ISSUER: https://token.actions.githubusercontent.com | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: |- | |
| retry() { | |
| local label=$1 | |
| shift | |
| for attempt in 1 2 3; do | |
| if "$@"; then | |
| return 0 | |
| fi | |
| if [ "$attempt" -eq 3 ]; then | |
| echo "::error::${label} failed after 3 attempts" | |
| return 1 | |
| fi | |
| echo "::warning::${label} attempt ${attempt} failed, retrying in 30s" | |
| sleep 30 | |
| done | |
| } | |
| retry "cosign verify" \ | |
| timeout 90 cosign verify "$IMAGE" \ | |
| --timeout=60s \ | |
| --certificate-identity "$IDENTITY" \ | |
| --certificate-oidc-issuer "$ISSUER" | |
| retry "gh attestation verify" \ | |
| timeout 90 gh attestation verify "oci://${IMAGE}" \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --predicate-type "$SPDX_PREDICATE_TYPE" \ | |
| --cert-identity "$IDENTITY" \ | |
| --cert-oidc-issuer "$ISSUER" |