Skip to content
Merged
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
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ push-launcher: launcher

## Build + push sandbox and launcher, then run full tests on both platforms
test: sandbox push-launcher
./test-flow.sh all --full
./test/test-flow.sh all --full

## Build + push sandbox and launcher, then run full podman test
test-podman: sandbox push-launcher
./test-flow.sh podman --full
./test/test-flow.sh podman --full

## Build + push sandbox and launcher, then run full OCP test
test-ocp: sandbox push-launcher
./test-flow.sh ocp --full
./test/test-flow.sh ocp --full

## ── Convenience targets ───────────────────────────────────────────────

Expand Down
52 changes: 52 additions & 0 deletions bin/harness
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# OpenShell Harness CLI
#
# Usage:
# harness new [--local|--remote] [--profile NAME] [SANDBOX_NAME]
# harness connect [SANDBOX_NAME]
# harness deploy [--local|--remote]
# harness teardown [--sandboxes] [--providers] [--k8s]
# harness preflight
# harness providers
# harness test [podman|ocp|all] [--full]
set -euo pipefail

HARNESS_DIR="$(cd "$(dirname "$0")/.." && pwd)"
SCRIPT_DIR="$HARNESS_DIR/bin/scripts"

case "${1:-}" in
new) shift; exec "$SCRIPT_DIR/new.sh" "$@" ;;
connect) shift; exec "$SCRIPT_DIR/connect.sh" "$@" ;;
deploy) shift; exec "$SCRIPT_DIR/deploy.sh" "$@" ;;
teardown) shift; exec "$SCRIPT_DIR/teardown.sh" "$@" ;;
preflight) shift; exec "$SCRIPT_DIR/preflight.sh" "$@" ;;
providers) shift; exec "$SCRIPT_DIR/providers.sh" "$@" ;;
test) shift; exec "$HARNESS_DIR/test/test-flow.sh" "$@" ;;
*)
echo "OpenShell Harness"
echo ""
echo "Usage: harness <command>"
echo ""
echo "Commands:"
echo " new [--local|--remote] [--profile NAME] [SANDBOX_NAME]"
echo " Create a new sandbox. Deploys gateway and providers if needed."
echo ""
echo " connect [SANDBOX_NAME]"
echo " Reconnect to a running sandbox."
echo ""
echo " deploy [--local|--remote]"
echo " Deploy or verify the gateway."
echo ""
echo " teardown [--sandboxes] [--providers] [--k8s]"
echo " Tear down sandboxes, providers, or k8s resources."
echo ""
echo " preflight"
echo " Check environment prerequisites."
echo ""
echo " providers"
echo " Register providers with the gateway."
echo ""
echo " test [podman|ocp|all] [--full]"
echo " End-to-end validation."
;;
esac
5 changes: 5 additions & 0 deletions bin/scripts/connect.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
# Connect to a running sandbox.
set -euo pipefail
CLI="${OPENSHELL_CLI:-openshell}"
exec "$CLI" sandbox connect "${1:-}"
File renamed without changes.
235 changes: 235 additions & 0 deletions bin/scripts/deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
#!/usr/bin/env bash
# Deploy or verify an OpenShell gateway.
#
# Usage:
# deploy.sh --local # verify local podman gateway
# deploy.sh --remote # deploy to OpenShift cluster
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
HARNESS_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
source "$SCRIPT_DIR/lib/common.sh"

PLATFORM=""
while [[ $# -gt 0 ]]; do
case "$1" in
--local) PLATFORM="local"; shift ;;
--remote) PLATFORM="remote"; shift ;;
--kubeconfig) export KUBECONFIG="$2"; shift 2 ;;
*) echo "Usage: $0 [--local|--remote]"; exit 1 ;;
esac
done

if [[ -z "$PLATFORM" ]]; then
echo "ERROR: specify --local or --remote"
exit 1
fi

# ── Local (Podman) ────────────────────────────────────────────────────
deploy_local() {
CLI="${OPENSHELL_CLI:-openshell}"
command -v "$CLI" &>/dev/null || {
echo "ERROR: openshell CLI not found. Install it first:"
echo " curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh"
exit 1
}

echo "=== Container Runtime ==="
if command -v podman &>/dev/null; then
echo " ✓ Podman: $(podman --version 2>/dev/null)"
else
echo " ✗ Podman not found"
exit 1
fi

echo ""
echo "=== Gateway ==="
LOCAL_GW=$("$CLI" gateway list 2>/dev/null | awk '/127\.0\.0\.1/ {gsub(/^\*/, ""); print $1; exit}')

if [[ -z "$LOCAL_GW" ]]; then
echo " ✗ No local gateway found"
echo ""
echo " Install OpenShell (auto-registers the gateway):"
echo " curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh"
exit 1
fi

"$CLI" gateway select "$LOCAL_GW" 2>/dev/null || true

if "$CLI" inference get &>/dev/null; then
echo " ✓ $LOCAL_GW (active, reachable)"
else
echo " ✗ $LOCAL_GW (not responding)"
echo ""
echo " Start the gateway:"
echo " macOS: brew services start openshell"
echo " Linux: systemctl --user start openshell"
exit 1
fi

echo ""
echo "Done."
}

# ── Remote (OpenShift) ────────────────────────────────────────────────
deploy_remote() {
for cmd in kubectl helm; do
command -v "$cmd" &>/dev/null || { echo "ERROR: $cmd is required but not found."; exit 1; }
done

# Read chart version from openshell.toml or env
CHART_VERSION="${OPENSHELL_CHART_VERSION:-}"
if [[ -z "$CHART_VERSION" && -f "$HARNESS_DIR/openshell.toml" ]]; then
CHART_VERSION=$(python3 -c "
import tomllib
with open('$HARNESS_DIR/openshell.toml', 'rb') as f:
print(tomllib.load(f).get('upstream', {}).get('chart-version', ''))
" 2>/dev/null || true)
fi
CHART_VERSION="${CHART_VERSION:-0.0.55}"
CHART="oci://ghcr.io/nvidia/openshell/helm-chart"

echo "OpenShell chart: $CHART_VERSION"
echo "KUBECONFIG: ${KUBECONFIG:-default}"
echo ""

echo "=== Step 1: Creating namespace ==="
kubectl create ns openshell 2>/dev/null || true
kubectl label ns openshell \
pod-security.kubernetes.io/enforce=privileged \
pod-security.kubernetes.io/warn=privileged \
--overwrite

echo "=== Step 2: Installing Sandbox CRD ==="
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/latest/download/manifest.yaml

echo "=== Step 3: Granting OpenShift SCCs ==="
for sa in openshell openshell-sandbox default; do
oc adm policy add-scc-to-user privileged -z "$sa" -n openshell 2>/dev/null || true
done
oc adm policy add-scc-to-user anyuid -z openshell -n openshell 2>/dev/null || true
kubectl create clusterrolebinding agent-sandbox-admin \
--clusterrole=cluster-admin \
--serviceaccount=agent-sandbox-system:agent-sandbox-controller 2>/dev/null || true

kubectl apply -n openshell -f - <<'RBAC'
apiVersion: v1
kind: ServiceAccount
metadata:
name: openshell-launcher
namespace: openshell
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: openshell-launcher
namespace: openshell
rules:
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: openshell-launcher
namespace: openshell
subjects:
- kind: ServiceAccount
name: openshell-launcher
namespace: openshell
roleRef:
kind: Role
name: openshell-launcher
apiGroup: rbac.authorization.k8s.io
RBAC

echo "=== Step 4: Deploying gateway via Helm ==="
SANDBOX_IMAGE="${SANDBOX_IMAGE:-quay.io/rcochran/openshell:sandbox}"

APPS_DOMAIN=$(kubectl get ingresses.config.openshift.io cluster -o jsonpath='{.spec.domain}' 2>/dev/null)
if [[ -z "$APPS_DOMAIN" ]]; then
echo "ERROR: Could not determine OpenShift apps domain."
exit 1
fi
ROUTE_HOST="gateway-openshell.${APPS_DOMAIN}"

HELM_ARGS=(
--values "$HARNESS_DIR/values-ocp.yaml"
--set server.sandboxImage="$SANDBOX_IMAGE"
--set pkiInitJob.serverDnsNames[0]="$ROUTE_HOST"
)

[[ -n "${PULL_SECRET:-}" ]] && HELM_ARGS+=(--set imagePullSecrets[0].name="$PULL_SECRET")
[[ -n "${SANDBOX_PULL_SECRET:-}" ]] && HELM_ARGS+=(--set server.sandboxImagePullSecrets[0].name="$SANDBOX_PULL_SECRET")

helm upgrade --install openshell "$CHART" --version "$CHART_VERSION" -n openshell \
"${HELM_ARGS[@]}"

echo "=== Waiting for gateway ==="
kubectl rollout status statefulset/openshell -n openshell --timeout=300s

echo "=== Step 5: Creating OpenShift route ==="
if ! kubectl get route gateway -n openshell &>/dev/null; then
cat <<'ROUTE' | kubectl apply -f -
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: gateway
namespace: openshell
spec:
tls:
termination: passthrough
to:
kind: Service
name: openshell
port:
targetPort: grpc
ROUTE
fi
echo " Route: $ROUTE_HOST"

echo "=== Step 6: Configuring CLI gateway ==="
GATEWAY_NAME="${GATEWAY_NAME:-openshell-remote-ocp}"
GATEWAY_URL="https://${ROUTE_HOST}:443"
CLI="${OPENSHELL_CLI:-openshell}"

if command -v "$CLI" &>/dev/null; then
for gw in $("$CLI" gateway list 2>/dev/null | grep "$ROUTE_HOST" | awk '{gsub(/^\*/, ""); print $1}'); do
"$CLI" gateway remove "$gw" 2>/dev/null || true
done

"$CLI" gateway add "$GATEWAY_URL" --name "$GATEWAY_NAME" --local 2>/dev/null || true

MTLS_DIR="$HOME/.config/openshell/gateways/$GATEWAY_NAME/mtls"
kubectl get secret openshell-client-tls -n openshell \
-o jsonpath='{.data.ca\.crt}' | base64 -d > "$MTLS_DIR/ca.crt"
kubectl get secret openshell-client-tls -n openshell \
-o jsonpath='{.data.tls\.crt}' | base64 -d > "$MTLS_DIR/tls.crt"
kubectl get secret openshell-client-tls -n openshell \
-o jsonpath='{.data.tls\.key}' | base64 -d > "$MTLS_DIR/tls.key"

"$CLI" gateway select "$GATEWAY_NAME" 2>/dev/null || true
echo " ✓ $GATEWAY_NAME registered (certs from cluster)"

echo -n " Waiting for gateway..."
for i in $(seq 1 30); do
if "$CLI" inference get &>/dev/null; then
echo " ✓ reachable"
break
fi
sleep 2
echo -n "."
[[ $i -eq 30 ]] && echo " ✗ timed out (try: openshell inference get)"
done
fi

echo ""
echo "Done."
}

# ── Main ──────────────────────────────────────────────────────────────
case "$PLATFORM" in
local) deploy_local ;;
remote) deploy_remote ;;
esac
File renamed without changes.
2 changes: 1 addition & 1 deletion lib/agent.sh → bin/scripts/lib/profile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Agent config parsing helpers.
#
# Source from any script:
# source "$(dirname "$0")/lib/agent.sh"
# source "$(dirname "$0")/lib/profile.sh"
#
# Usage:
# parse_agent agents/default.toml
Expand Down
2 changes: 1 addition & 1 deletion lib/providers.py → bin/scripts/lib/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import tomllib
from pathlib import Path

ROOT = Path(__file__).parent.parent
ROOT = Path(__file__).parent.parent.parent.parent
PROVIDERS_TOML = ROOT / "providers.toml"
CONFIG_TOML = ROOT / "openshell.toml"
CLI = os.environ.get("OPENSHELL_CLI", "openshell")
Expand Down
Loading