Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
3 changes: 2 additions & 1 deletion .github/actions/check-vcluster-exists/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ runs:
env:
VCLUSTER_NAME: ${{ inputs.vcluster_name }}
NAMESPACE: ${{ inputs.vcluster_namespace }}
KUBECONFIG_BASE64: ${{ inputs.kubeconfig_base64 }}
run: |
# Use a unique temp file so concurrent runs on the same self-hosted
# runner don't clobber each other's kubeconfig. Trap on EXIT to
# remove the credential whether the step succeeds or fails.
KUBECONFIG_FILE=$(mktemp)
trap 'rm -f "${KUBECONFIG_FILE}"' EXIT
echo "${{ inputs.kubeconfig_base64 }}" | base64 -d > "${KUBECONFIG_FILE}"
echo "${KUBECONFIG_BASE64}" | base64 -d > "${KUBECONFIG_FILE}"
chmod 600 "${KUBECONFIG_FILE}"
export KUBECONFIG="${KUBECONFIG_FILE}"
RESULT=$(vcluster list --output json -n ${NAMESPACE})
Expand Down
6 changes: 4 additions & 2 deletions .github/actions/install-vcluster-cli/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ runs:
steps:
- name: Install vCluster CLI
shell: bash
env:
VCLUSTER_VERSION: ${{ inputs.vcluster_version }}
run: |
echo "::group::Install vCluster CLI ${{ inputs.vcluster_version }}"
echo "::group::Install vCluster CLI ${VCLUSTER_VERSION}"
if command -v vcluster &>/dev/null; then
echo "vCluster CLI already installed: $(vcluster version)"
else
Expand All @@ -31,7 +33,7 @@ runs:
--connect-timeout 10 --max-time 120 \
--fail --show-error -sL \
-o "${TMP_BIN}" \
"https://github.com/loft-sh/vcluster/releases/download/${{ inputs.vcluster_version }}/vcluster-linux-${VCLUSTER_ARCH}"
"https://github.com/loft-sh/vcluster/releases/download/${VCLUSTER_VERSION}/vcluster-linux-${VCLUSTER_ARCH}"
sudo install -m 0755 "${TMP_BIN}" /usr/local/bin/vcluster
rm -f "${TMP_BIN}"
vcluster version
Expand Down
15 changes: 9 additions & 6 deletions .github/actions/setup-mx-vcluster/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,12 @@ runs:
steps:
- name: Setup host kubeconfig
shell: bash
env:
KUBECONFIG_BASE64: ${{ inputs.kubeconfig_base64 }}
run: |
echo "${{ inputs.kubeconfig_base64 }}" | base64 -d > ${{ github.workspace }}/.kubeconfig-host
chmod 600 ${{ github.workspace }}/.kubeconfig-host
echo "KUBECONFIG=${{ github.workspace }}/.kubeconfig-host" >> $GITHUB_ENV
echo "${KUBECONFIG_BASE64}" | base64 -d > "${GITHUB_WORKSPACE}/.kubeconfig-host"
chmod 600 "${GITHUB_WORKSPACE}/.kubeconfig-host"
echo "KUBECONFIG=${GITHUB_WORKSPACE}/.kubeconfig-host" >> $GITHUB_ENV

- name: Install vCluster CLI
uses: ./.github/actions/install-vcluster-cli
Expand Down Expand Up @@ -105,6 +107,7 @@ runs:
env:
VCLUSTER_NAME: ${{ inputs.vcluster_name }}
NAMESPACE: ${{ inputs.vcluster_namespace }}
VCLUSTER_K8S_VERSION: ${{ inputs.vcluster_k8s_version }}
run: |
echo "::group::Create vCluster ${VCLUSTER_NAME} in ${NAMESPACE}"
# GPU workloads require syncing host nodes into the vCluster so the GPU
Expand All @@ -114,7 +117,7 @@ runs:
--connect=false \
--upgrade \
--set controlPlane.distro.k8s.enabled=true \
--set controlPlane.distro.k8s.version=${{ inputs.vcluster_k8s_version }} \
--set controlPlane.distro.k8s.version=${VCLUSTER_K8S_VERSION} \
--set sync.fromHost.nodes.enabled=true \
--set sync.fromHost.nodes.selector.all=true
echo "::endgroup::"
Expand Down Expand Up @@ -146,7 +149,7 @@ runs:
echo "::group::Apply ModelExpress CRD inside vCluster"
# CRD is cluster-scoped — applied once here on bootstrap.
# run-mx-p2p-test also applies it on every run for idempotency.
kubectl --kubeconfig=${{ github.workspace }}/.kubeconfig-vcluster apply \
kubectl --kubeconfig="${GITHUB_WORKSPACE}/.kubeconfig-vcluster" apply \
-f ci/k8s/server/crd-modelmetadata.yaml
echo "::endgroup::"

Expand Down Expand Up @@ -232,7 +235,7 @@ runs:
echo "::group::Events (host namespace)"
kubectl get events -n ${NAMESPACE} --sort-by='.lastTimestamp' 2>&1 || true
echo "::endgroup::"
VC_KC=${{ github.workspace }}/.kubeconfig-vcluster
VC_KC="${GITHUB_WORKSPACE}/.kubeconfig-vcluster"
if [ -f "${VC_KC}" ]; then
echo "::group::Pod status (vCluster, all namespaces)"
kubectl --kubeconfig=${VC_KC} get pods -A -o wide 2>&1 || true
Expand Down
97 changes: 97 additions & 0 deletions .github/workflows/build-wheels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Builds the modelexpress Python client wheels via docker/Dockerfile.client-wheel
# and uploads the resulting artifacts to NV Artifactory.
#
# Triggered on:
# - push to pull-request/<pr_id> (mirrored from contributor PRs by
# copy-pr-bot): lands under pr/<pr_id>/<sha>/<run_id>/<run_attempt>/<arch>/
# - push to main and release/**: lands under
# post-merge/<sha>/<run_id>/<run_attempt>/<arch>/
#
# copy-pr-bot mirrors PRs (including fork PRs) into the pull-request/<pr_id>
# branch namespace inside this repo, which lets the workflow access
# environment-scoped secrets that fork PRs can't reach directly.
#
# Runs in parallel to (does not gate on) the rest of CI. Upload is
# environment-gated on `automated-release`.

name: Build & upload client wheels

on:
push:
branches:
- 'pull-request/*'
- main
- 'release/**'

concurrency:
group: build-wheels-${{ github.ref }}
cancel-in-progress: ${{ startsWith(github.ref, 'refs/heads/pull-request/') }}

permissions:
contents: read

jobs:
build-and-upload:
name: Build & upload wheels (${{ matrix.arch }})
environment: automated-release
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: prod-modelexpress-builder-amd-v1
platform: linux/amd64
- arch: arm64
runner: prod-modelexpress-builder-arm-v1
platform: linux/arm64

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Compute artifactory subpath
id: dest
run: |
case "${GITHUB_REF_NAME}" in
pull-request/*)
PR_ID="${GITHUB_REF_NAME#pull-request/}"
SUBPATH="pr/${PR_ID}/${{ github.sha }}/${{ github.run_id }}/${{ github.run_attempt }}/${{ matrix.arch }}"
;;
*)
SUBPATH="post-merge/${{ github.sha }}/${{ github.run_id }}/${{ github.run_attempt }}/${{ matrix.arch }}"
;;
esac
echo "subpath=${SUBPATH}" >> "$GITHUB_OUTPUT"
echo "Destination subpath: ${SUBPATH}"

- name: Build wheels
run: |
mkdir -p dist
docker buildx build \
--platform ${{ matrix.platform }} \
-f docker/Dockerfile.client-wheel \
--target export \
--output type=local,dest=./dist \
.
echo "Built artifacts:"
ls -la dist/

- name: Set up JFrog CLI
uses: jfrog/setup-jfrog-cli@v4
env:
JF_URL: ${{ secrets.ARTIFACTORY_URL }}
JF_ACCESS_TOKEN: ${{ secrets.ARTIFACTORY_TOKEN }}

- name: Upload to Artifactory
run: |
DEST="${{ secrets.ARTIFACTORY_PYPI_REPO_NAME }}/${{ steps.dest.outputs.subpath }}/"
echo "Uploading to: ${DEST}"
jf rt upload --flat "dist/*.whl" "${DEST}"
jf rt upload --flat "dist/*.tar.gz" "${DEST}"
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ jobs:

- name: Build Docker image
run: |
docker build -t model-express:latest .
docker build -f docker/Dockerfile -t model-express:latest .

- name: Test Docker image
run: |
Expand Down
Loading
Loading