Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
239cec7
feat: add Dockerfile validation for custom kernels
rryoung98 Mar 23, 2026
7debe7e
feat: add kernel catalog validation to course validator
rryoung98 Mar 23, 2026
4ea7c7f
feat: add deploy-kernel composite action
rryoung98 Mar 23, 2026
85491fa
feat: add deployment workflow and example Dockerfile for custom kernels
Mar 24, 2026
220a982
feat: add unit tests for Dockerfile validation and deployment functio…
Mar 24, 2026
2f0185e
feat: update kernel name and ID to qbraid_python in test cases
Mar 24, 2026
9e1b67a
fix: make kernel deploy action idempotent and DRY
rryoung98 Apr 8, 2026
3b14d31
fix: fail fast on terminal kernel deploy poll errors
rryoung98 Apr 8, 2026
8845a52
fix: unblock deploy-kernel wrapper CI checks
rryoung98 Apr 8, 2026
d8949f1
chore: fix isort formatting in deploy-kernel wrapper test
rryoung98 Apr 8, 2026
6c1d9a5
fix: address upload action review comments
rryoung98 Apr 8, 2026
d52396c
style: sort course validator imports
rryoung98 Apr 8, 2026
4c98310
fix: secure custom kernel deploy action inputs and outputs
rryoung98 Apr 9, 2026
548d4cb
style: align deploy action python formatting checks
rryoung98 Apr 9, 2026
1306f55
style: format deploy action test suite
rryoung98 Apr 9, 2026
45ceb5a
Update src/scripts/validate_dockerfile.py
rryoung98 Apr 13, 2026
a968261
feat(deploy-kernel): preflight COPY/ADD source-path validation
rryoung98 May 14, 2026
f140cbf
style: apply Black formatting to COPY/ADD validator additions
rryoung98 May 14, 2026
819ef4f
feat(deploy-kernel): pass cpu/memory/memory-limit through to the API
rryoung98 May 15, 2026
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ uses: qBraid/upload-course-action@v0.1.0-beta
- Fixed JSend response parsing in deploy and polling scripts to support `{status, data}` envelope format
- Added guard in `action.yml` to fail early with a clear error when `course_custom_id` is empty after create/update step
- Replaced silent `pass` with warning logs when API response parsing fails
- Failed kernel deployment polling immediately on terminal API errors instead of retrying until timeout
- Made the deploy-kernel wrapper reuse the shared Dockerfile validator implementation

### Changed
- Treat active pre-existing kernels as a successful deploy outcome for rerun-safe custom kernel workflows

### Added
- Unit tests for JSend response handling in course creation and polling
Expand Down
100 changes: 100 additions & 0 deletions deploy-kernel/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: 'Deploy Custom Kernel to qBraid'
description: 'Validates and deploys a custom Jupyter kernel image to qBraid qBook'
author: 'qBraid'

branding:
icon: 'cpu'
color: 'purple'

inputs:
api-key:
description: 'qBraid API key for authentication'
required: true
dockerfile-path:
description: 'Path to the kernel Dockerfile'
required: false
default: 'Dockerfile.kernel'
kernel-name:
description: 'Unique kernel name (lowercase alphanumeric + underscore, 3-64 chars)'
required: true
language:
description: 'Kernel language (python, cpp, julia, r, rust, go, javascript)'
required: true
display-name:
description: 'Human-readable display name for the kernel'
required: true
context-dir:
description: 'Directory containing additional build context files (requirements.txt, kernel.json, etc.)'
required: false
default: '.'
api-base-url:
description: 'qBraid API base URL'
required: false
default: 'https://api-v2.qbraid.com/api/v1'
timeout-seconds:
description: 'Maximum time to wait for deployment to finish'
required: false
default: '1800'
cpu:
description: 'Default CPU per kernel pod (e.g. "2", "3", "2500m"). Server enforces a minimum of 2 cores and a maximum of 4. Omit to leave the kernel''s existing default unchanged.'
required: false
memory:
description: 'Default memory request per kernel pod (e.g. "4Gi", "8Gi"). Server enforces a minimum of 4 GiB and a maximum of 8 GiB. Omit to leave the kernel''s existing default unchanged.'
required: false
memory-limit:
description: 'Default memory limit per kernel pod (must be >= memory). Same units and ceiling as memory. Omit to leave the kernel''s existing default unchanged.'
required: false

outputs:
kernel-name:
description: 'The deployed kernel name'
value: ${{ steps.deploy-kernel.outputs.kernel-name }}
image-uri:
description: 'The Artifact Registry image URI'
value: ${{ steps.deploy-kernel.outputs.image-uri }}
status:
description: 'Deployment status (active, failed, timeout)'
value: ${{ steps.deploy-kernel.outputs.status }}
build-id:
description: 'Cloud Build ID'
value: ${{ steps.deploy-kernel.outputs.build-id }}

runs:
using: 'composite'
steps:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install dependencies
shell: bash
run: |
python -m pip install -q -r "${{ github.action_path }}/requirements.txt"

- name: Validate Dockerfile
shell: bash
run: |
python ${{ github.action_path }}/src/scripts/validate_dockerfile.py \
"${{ inputs.dockerfile-path }}" \
"${{ inputs.context-dir }}"

- name: Deploy kernel
id: deploy-kernel
shell: bash
env:
QBRAID_API_KEY: ${{ inputs.api-key }}
QBRAID_DEPLOY_TIMEOUT_SECONDS: ${{ inputs.timeout-seconds }}

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The composite action exports QBRAID_DEPLOY_TIMEOUT_SECONDS, but deploy_kernel.py doesn't read this environment variable (timeout is passed via --timeout-seconds). This extra env var can confuse users—either remove it or wire the script to honor it when the CLI arg isn't provided.

Suggested change
QBRAID_DEPLOY_TIMEOUT_SECONDS: ${{ inputs.timeout-seconds }}

Copilot uses AI. Check for mistakes.
run: |
echo "::add-mask::$QBRAID_API_KEY"
python ${{ github.action_path }}/src/scripts/deploy_kernel.py \
--dockerfile-path "${{ inputs.dockerfile-path }}" \
--kernel-name "${{ inputs.kernel-name }}" \
--language "${{ inputs.language }}" \
--display-name "${{ inputs.display-name }}" \
--context-dir "${{ inputs.context-dir }}" \
--api-base-url "${{ inputs.api-base-url }}" \
--timeout-seconds "${{ inputs.timeout-seconds }}" \
--cpu "${{ inputs.cpu }}" \
--memory "${{ inputs.memory }}" \
--memory-limit "${{ inputs.memory-limit }}"
1 change: 1 addition & 0 deletions deploy-kernel/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
requests>=2.31.0
34 changes: 34 additions & 0 deletions deploy-kernel/src/scripts/context_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Delegate to the shared context-files helper (single source of truth)."""

from __future__ import annotations

import importlib.util
import sys
from pathlib import Path


def _load_shared_context_files_module():
repo_root = Path(__file__).resolve().parents[3]
shared_path = repo_root / "src" / "scripts" / "context_files.py"

spec = importlib.util.spec_from_file_location("shared_context_files", shared_path)
if spec is None or spec.loader is None:
raise RuntimeError(f"Unable to load shared context_files from {shared_path}")

module = importlib.util.module_from_spec(spec)
sys.path.insert(0, str(shared_path.parent))
try:
spec.loader.exec_module(module)
finally:
sys.path.pop(0)
return module


_shared = _load_shared_context_files_module()

MAX_CONTEXT_FILE_BYTES = _shared.MAX_CONTEXT_FILE_BYTES
SKIPPED_CONTEXT_FILES = _shared.SKIPPED_CONTEXT_FILES
SKIPPED_CONTEXT_SUFFIXES = _shared.SKIPPED_CONTEXT_SUFFIXES
SKIPPED_CONTEXT_SUBSTRINGS = _shared.SKIPPED_CONTEXT_SUBSTRINGS
is_skipped_context_file = _shared.is_skipped_context_file
list_context_files = _shared.list_context_files
Loading
Loading