Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# SPDX-FileCopyrightText: © 2026 Phala Network <dstack@phala.network>
#
# SPDX-License-Identifier: Apache-2.0

# CodeQL, as an advanced configuration.
#
# THIS FILE CANNOT BE MERGED WHILE CODE SCANNING DEFAULT SETUP IS ENABLED.
# GitHub refuses uploads from an advanced configuration while the default one is
# active, so that setting has to be turned off first:
#
# Settings -> Code security -> Code scanning -> CodeQL analysis -> Disable
#
# or
#
# gh api -X PATCH repos/Dstack-TEE/dstack/code-scanning/default-setup \
# -f state=not-configured
#
# What changes, and what it costs:
#
# Default setup runs every language on every pull request. `Analyze (rust)` is
# 17.4 minutes of that -- the single largest job in the repository, larger than
# the full Rust test suite -- because CodeQL builds the whole workspace under
# its tracer. The other five languages together come to about five minutes.
#
# Rust moves to pushes on `next` and to the weekly schedule. Every finding it
# would have made is still made, on the merge commit rather than on the pull
# request that produced it: a regression is reported one merge later than it
# would have been, not missed. The cheap languages keep running per pull
# request, where they cost little and catch things early.
#
# `c-cpp` is dropped. The repository contains exactly one C file,
# `tools/sca/examples/hello-c/src/server.c`, which is example material for a
# supply-chain-analysis demo and is not shipped in any image.

name: CodeQL

on:
push:
branches: [next, 'release/**']
pull_request:
branches: [next, 'release/**']
schedule:
# Weekly, matching the cadence default setup was running.
- cron: '31 4 * * 1'

permissions:
contents: read

jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
permissions:
security-events: write
packages: read
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: actions
build-mode: none
- language: go
build-mode: autobuild
- language: javascript-typescript
build-mode: none
- language: python
build-mode: none
steps:
- uses: actions/checkout@v5

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: /language:${{ matrix.language }}

# Rust is separated for what it costs, not because it matters less. Gated with
# `if:` rather than a `paths:` filter so the weekly schedule still reaches it.
analyze-rust:
name: Analyze (rust)
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
permissions:
security-events: write
packages: read
actions: read
contents: read
steps:
- uses: actions/checkout@v5

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: rust
build-mode: autobuild

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: /language:rust
40 changes: 40 additions & 0 deletions .github/workflows/docker-build-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,24 @@

name: Docker Build Check

# Not a required status check, so a workflow-level filter is safe here: a pull
# request that skips it is not left waiting on a check that never arrives. What
# it verifies -- that the builder images still build and that their pinned
# package lists still match -- cannot change unless something under `dstack/`
# does.
on:
push:
branches: [ next, 'release/**' ]
paths:
- 'dstack/**'
- 'rust-toolchain.toml'
- '.github/workflows/docker-build-check.yml'
pull_request:
branches: [ next, 'release/**' ]
paths:
- 'dstack/**'
- 'rust-toolchain.toml'
- '.github/workflows/docker-build-check.yml'

env:
DSTACK_REV: ${{ github.event.pull_request.head.sha || github.sha }}
Expand Down Expand Up @@ -36,6 +49,15 @@ jobs:
build-args: |
DSTACK_REV=${{ env.DSTACK_REV }}
DSTACK_SRC_URL=${{ env.DSTACK_SRC_URL }}
# Layer cache, scoped per image so the three jobs do not overwrite one
# another's. It covers the pinned-package install and the toolchain
# setup. The cargo build below cannot be cached: the source arrives
# through `git clone` at DSTACK_REV inside the build rather than from
# the build context, so BuildKit has nothing to key it on. That is
# deliberate -- the revision is what the image records in
# /etc/.GIT_REV -- so the ceiling here is the layers above the clone.
cache-from: type=gha,scope=gateway
cache-to: type=gha,mode=max,scope=gateway

- name: Verify pinned packages
run: |
Expand Down Expand Up @@ -87,6 +109,15 @@ jobs:
build-args: |
DSTACK_REV=${{ env.DSTACK_REV }}
DSTACK_SRC_URL=${{ env.DSTACK_SRC_URL }}
# Layer cache, scoped per image so the three jobs do not overwrite one
# another's. It covers the pinned-package install and the toolchain
# setup. The cargo build below cannot be cached: the source arrives
# through `git clone` at DSTACK_REV inside the build rather than from
# the build context, so BuildKit has nothing to key it on. That is
# deliberate -- the revision is what the image records in
# /etc/.GIT_REV -- so the ceiling here is the layers above the clone.
cache-from: type=gha,scope=kms
cache-to: type=gha,mode=max,scope=kms

- name: Verify pinned packages
run: |
Expand Down Expand Up @@ -140,6 +171,15 @@ jobs:
build-args: |
DSTACK_REV=${{ env.DSTACK_REV }}
DSTACK_SRC_URL=${{ env.DSTACK_SRC_URL }}
# Layer cache, scoped per image so the three jobs do not overwrite one
# another's. It covers the pinned-package install and the toolchain
# setup. The cargo build below cannot be cached: the source arrives
# through `git clone` at DSTACK_REV inside the build rather than from
# the build context, so BuildKit has nothing to key it on. That is
# deliberate -- the revision is what the image records in
# /etc/.GIT_REV -- so the ceiling here is the layers above the clone.
cache-from: type=gha,scope=verifier
cache-to: type=gha,mode=max,scope=verifier

- name: Verify pinned packages (runtime)
run: |
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,63 @@ jobs:
working-directory: dstack
steps:
- uses: actions/checkout@v5
with:
# The change detection below diffs against the base commit.
fetch-depth: 0

# Which files changed, decided here rather than with a workflow-level
# `paths:` filter.
#
# `rust-checks` is a required status check. A workflow skipped by `paths:`
# reports nothing at all, so the check never arrives and the pull request
# waits on it forever -- the filter has to live inside a job that always
# runs and always reports. Everything expensive below is gated on this
# output; when nothing relevant moved the job costs a checkout and a diff.
- name: Detect relevant changes
id: changes
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
if [ -z "$BASE_SHA" ]; then
echo "relevant=true" >> "$GITHUB_OUTPUT"
echo "push build: running everything"
exit 0
fi
changed=$(git diff --name-only "$BASE_SHA...HEAD")
echo "$changed"
if echo "$changed" | grep -qE '^(dstack/|sdk/simulator/|rust-toolchain\.toml|\.github/workflows/rust\.yml)'; then
echo "relevant=true" >> "$GITHUB_OUTPUT"
else
echo "relevant=false" >> "$GITHUB_OUTPUT"
echo "nothing this job covers changed"
fi

- name: Install Rust
uses: dtolnay/rust-toolchain@1.92.0
with:
components: clippy, rustfmt

# The largest job in the repository was compiling the workspace from nothing
# on every run. `simulator-release.yml` already caches this way; this brings
# the check that gates every merge in line with it.
#
# `workspaces` because the manifest lives in `dstack/`, not at the root, and
# the action keys the cache on the lockfile it finds there. `sdk/` is a
# separate workspace, cached by its own job.
- name: Cache cargo
if: steps.changes.outputs.relevant == 'true'
uses: Swatinem/rust-cache@v2
with:
workspaces: dstack

- name: Run Clippy
if: steps.changes.outputs.relevant == 'true'
run: cargo clippy -- -D warnings -D clippy::expect_used -D clippy::unwrap_used --allow unused_variables

- name: Cargo fmt check
if: steps.changes.outputs.relevant == 'true'
run: cargo fmt --check --all

- name: Run tests
if: steps.changes.outputs.relevant == 'true'
run: ./run-tests.sh
60 changes: 60 additions & 0 deletions .github/workflows/sdk-compat.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,29 @@ name: SDK compatibility
permissions:
contents: read

# Released SDKs against the current agent: nothing to re-check unless the SDKs
# or the agent they talk to changed. Not a required status check, so a
# workflow-level filter is safe.
#
# `dstack/**` rather than the guest-agent directories alone. The agent under
# test is built through `simulator_start`, whose dependency closure reaches a
# dozen crates in that workspace; naming the three obvious ones would leave the
# rest silently uncovered the moment one of them changed behaviour.
on:
push:
branches: [next, 'release/**']
paths:
- 'sdk/**'
- 'dstack/**'
- 'rust-toolchain.toml'
- '.github/workflows/sdk-compat.yaml'
pull_request:
branches: [next, 'release/**']
paths:
- 'sdk/**'
- 'dstack/**'
- 'rust-toolchain.toml'
- '.github/workflows/sdk-compat.yaml'

env:
CARGO_TERM_COLOR: always
Expand All @@ -24,8 +42,40 @@ env:
RUSTUP_TOOLCHAIN: 1.92.0

jobs:
# The agent the released SDKs are tested against, built once.
#
# Both matrix legs call `simulator_start`, which builds
# `dstack-guest-agent-simulator` out of the current tree -- the same binary,
# from the same commit, compiled twice in parallel. Nothing here replaces that
# call or hands the legs a binary from elsewhere: they still build from their
# own checkout, so what they test is still what this commit produces. This job
# only puts the artifacts in the cache first, under a key both legs restore,
# so the build they run finds its work already done.
#
# A `needs:` rather than a skip switch in `simulator_build` on purpose. That
# switch would be a path where the binary under test did not come from the
# checkout, which is not worth trading for a few minutes.
simulator:
name: Build the agent under test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- name: Install Rust
uses: dtolnay/rust-toolchain@1.92.0

- name: Cache cargo
uses: Swatinem/rust-cache@v2
with:
workspaces: dstack
shared-key: sdk-compat-agent

- name: Build the simulator
run: ./sdk/simulator/build.sh

sdk-compat:
name: ${{ matrix.tag }} SDKs vs current agent
needs: simulator
runs-on: ubuntu-latest
strategy:
# Each tag is an independent claim; one failing should not hide the other.
Expand Down Expand Up @@ -62,6 +112,16 @@ jobs:
- name: Install Rust
uses: dtolnay/rust-toolchain@1.92.0

# Restore only. The `simulator` job above populated this key; a leg that
# saved as well would race its twin for the same entry and gain nothing,
# since the next run's `simulator` job writes it again anyway.
- name: Restore the agent build
uses: Swatinem/rust-cache@v2
with:
workspaces: dstack
shared-key: sdk-compat-agent
save-if: false

- name: Install Go
uses: actions/setup-go@v5
with:
Expand Down
Loading
Loading