mirror of
https://github.com/Monadical-SAS/cubbi.git
synced 2025-12-20 12:19:07 +00:00
feat: add Claude Code image support (#16)
* feat: add Claude Code image support Add a new Cubbi image for Claude Code (Anthropic's official CLI) with: - Full Claude Code CLI functionality via NPM package - Secure API key management with multiple authentication options - Enterprise support (Bedrock, Vertex AI, proxy configuration) - Persistent configuration and cache directories - Comprehensive test suite and documentation The image allows users to run Claude Code in containers with proper isolation, persistent settings, and seamless Cubbi integration. It gracefully handles missing API keys to allow flexible authentication. Also adds optional Claude Code API keys to container.py for enterprise deployments. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Pre-commit fixes --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Your Name <you@example.com>
This commit is contained in:
committed by
GitHub
parent
e70ec3538b
commit
b28c2bd63e
@@ -203,6 +203,8 @@ class ContainerManager:
|
||||
api_keys = [
|
||||
"OPENAI_API_KEY",
|
||||
"ANTHROPIC_API_KEY",
|
||||
"ANTHROPIC_AUTH_TOKEN",
|
||||
"ANTHROPIC_CUSTOM_HEADERS",
|
||||
"OPENROUTER_API_KEY",
|
||||
"GOOGLE_API_KEY",
|
||||
"LANGFUSE_INIT_PROJECT_PUBLIC_KEY",
|
||||
|
||||
72
cubbi/images/claudecode/Dockerfile
Normal file
72
cubbi/images/claudecode/Dockerfile
Normal file
@@ -0,0 +1,72 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
LABEL maintainer="team@monadical.com"
|
||||
LABEL description="Claude Code for Cubbi"
|
||||
|
||||
# Install system dependencies including gosu for user switching
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
gosu \
|
||||
passwd \
|
||||
bash \
|
||||
curl \
|
||||
bzip2 \
|
||||
iputils-ping \
|
||||
iproute2 \
|
||||
libxcb1 \
|
||||
libdbus-1-3 \
|
||||
nano \
|
||||
tmux \
|
||||
git-core \
|
||||
ripgrep \
|
||||
openssh-client \
|
||||
vim \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install uv (Python package manager)
|
||||
WORKDIR /tmp
|
||||
RUN curl -fsSL https://astral.sh/uv/install.sh -o install.sh && \
|
||||
sh install.sh && \
|
||||
mv /root/.local/bin/uv /usr/local/bin/uv && \
|
||||
mv /root/.local/bin/uvx /usr/local/bin/uvx && \
|
||||
rm install.sh
|
||||
|
||||
# Install Node.js (for Claude Code NPM package)
|
||||
RUN mkdir -p /opt/node && \
|
||||
curl -fsSL https://nodejs.org/dist/v22.16.0/node-v22.16.0-linux-x64.tar.gz -o node.tar.gz && \
|
||||
tar -xf node.tar.gz -C /opt/node --strip-components=1 && \
|
||||
rm node.tar.gz
|
||||
|
||||
ENV PATH="/opt/node/bin:$PATH"
|
||||
|
||||
# Install Claude Code globally
|
||||
RUN npm install -g @anthropic-ai/claude-code
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy initialization system
|
||||
COPY cubbi_init.py /cubbi/cubbi_init.py
|
||||
COPY claudecode_plugin.py /cubbi/claudecode_plugin.py
|
||||
COPY cubbi_image.yaml /cubbi/cubbi_image.yaml
|
||||
COPY init-status.sh /cubbi/init-status.sh
|
||||
|
||||
# Make scripts executable
|
||||
RUN chmod +x /cubbi/cubbi_init.py /cubbi/init-status.sh
|
||||
|
||||
# Add Node.js to PATH in bashrc and init status check
|
||||
RUN echo 'PATH="/opt/node/bin:$PATH"' >> /etc/bash.bashrc
|
||||
RUN echo '[ -x /cubbi/init-status.sh ] && /cubbi/init-status.sh' >> /etc/bash.bashrc
|
||||
|
||||
# Set up environment
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
ENV UV_LINK_MODE=copy
|
||||
|
||||
# Pre-install the cubbi_init
|
||||
RUN /cubbi/cubbi_init.py --help
|
||||
|
||||
# Set WORKDIR to /app
|
||||
WORKDIR /app
|
||||
|
||||
ENTRYPOINT ["/cubbi/cubbi_init.py"]
|
||||
CMD ["tail", "-f", "/dev/null"]
|
||||
222
cubbi/images/claudecode/README.md
Normal file
222
cubbi/images/claudecode/README.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# Claude Code for Cubbi
|
||||
|
||||
This image provides Claude Code (Anthropic's official CLI for Claude) in a Cubbi container environment.
|
||||
|
||||
## Overview
|
||||
|
||||
Claude Code is an interactive CLI tool that helps with software engineering tasks. This Cubbi image integrates Claude Code with secure API key management, persistent configuration, and enterprise features.
|
||||
|
||||
## Features
|
||||
|
||||
- **Claude Code CLI**: Full access to Claude's coding capabilities
|
||||
- **Secure Authentication**: API key management through Cubbi's secure environment system
|
||||
- **Persistent Configuration**: Settings and cache preserved across container restarts
|
||||
- **Enterprise Support**: Bedrock and Vertex AI integration
|
||||
- **Network Support**: Proxy configuration for corporate environments
|
||||
- **Tool Permissions**: Pre-configured permissions for all Claude Code tools
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Set up API Key
|
||||
|
||||
```bash
|
||||
# Set your Anthropic API key in Cubbi configuration
|
||||
cubbi config set services.anthropic.api_key "your-api-key-here"
|
||||
```
|
||||
|
||||
### 2. Run Claude Code Environment
|
||||
|
||||
```bash
|
||||
# Start Claude Code container
|
||||
cubbi run claudecode
|
||||
|
||||
# Execute Claude Code commands
|
||||
cubbi exec claudecode "claude 'help me write a Python function'"
|
||||
|
||||
# Start interactive session
|
||||
cubbi exec claudecode "claude"
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Required Environment Variables
|
||||
|
||||
- `ANTHROPIC_API_KEY`: Your Anthropic API key (required)
|
||||
|
||||
### Optional Environment Variables
|
||||
|
||||
- `ANTHROPIC_AUTH_TOKEN`: Custom authorization token for enterprise deployments
|
||||
- `ANTHROPIC_CUSTOM_HEADERS`: Additional HTTP headers (JSON format)
|
||||
- `CLAUDE_CODE_USE_BEDROCK`: Set to "true" to use Amazon Bedrock
|
||||
- `CLAUDE_CODE_USE_VERTEX`: Set to "true" to use Google Vertex AI
|
||||
- `HTTP_PROXY`: HTTP proxy server URL
|
||||
- `HTTPS_PROXY`: HTTPS proxy server URL
|
||||
- `DISABLE_TELEMETRY`: Set to "true" to disable telemetry
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
```bash
|
||||
# Enterprise deployment with Bedrock
|
||||
cubbi config set environment.claude_code_use_bedrock true
|
||||
cubbi run claudecode
|
||||
|
||||
# With custom proxy
|
||||
cubbi config set network.https_proxy "https://proxy.company.com:8080"
|
||||
cubbi run claudecode
|
||||
|
||||
# Disable telemetry
|
||||
cubbi config set environment.disable_telemetry true
|
||||
cubbi run claudecode
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# Get help
|
||||
cubbi exec claudecode "claude --help"
|
||||
|
||||
# One-time task
|
||||
cubbi exec claudecode "claude 'write a unit test for this function'"
|
||||
|
||||
# Interactive mode
|
||||
cubbi exec claudecode "claude"
|
||||
```
|
||||
|
||||
### Working with Projects
|
||||
|
||||
```bash
|
||||
# Start Claude Code in your project directory
|
||||
cubbi run claudecode --mount /path/to/your/project:/app
|
||||
cubbi exec claudecode "cd /app && claude"
|
||||
|
||||
# Create a commit
|
||||
cubbi exec claudecode "cd /app && claude commit"
|
||||
```
|
||||
|
||||
### Advanced Features
|
||||
|
||||
```bash
|
||||
# Run with specific model configuration
|
||||
cubbi exec claudecode "claude -m claude-3-5-sonnet-20241022 'analyze this code'"
|
||||
|
||||
# Use with plan mode
|
||||
cubbi exec claudecode "claude -p 'refactor this function'"
|
||||
```
|
||||
|
||||
## Persistent Configuration
|
||||
|
||||
The following directories are automatically persisted:
|
||||
|
||||
- `~/.claude/`: Claude Code settings and configuration
|
||||
- `~/.cache/claude/`: Claude Code cache and temporary files
|
||||
|
||||
Configuration files are maintained across container restarts, ensuring your settings and preferences are preserved.
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
cubbi/images/claudecode/
|
||||
├── Dockerfile # Container image definition
|
||||
├── cubbi_image.yaml # Cubbi image configuration
|
||||
├── claudecode_plugin.py # Authentication and setup plugin
|
||||
├── cubbi_init.py # Initialization script (shared)
|
||||
├── init-status.sh # Status check script (shared)
|
||||
└── README.md # This documentation
|
||||
```
|
||||
|
||||
## Authentication Flow
|
||||
|
||||
1. **Environment Variables**: API key passed from Cubbi configuration
|
||||
2. **Plugin Setup**: `claudecode_plugin.py` creates `~/.claude/settings.json`
|
||||
3. **Verification**: Plugin verifies Claude Code installation and configuration
|
||||
4. **Ready**: Claude Code is ready for use with configured authentication
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**API Key Not Set**
|
||||
```
|
||||
⚠️ No authentication configuration found
|
||||
Please set ANTHROPIC_API_KEY environment variable
|
||||
```
|
||||
**Solution**: Set API key in Cubbi configuration:
|
||||
```bash
|
||||
cubbi config set services.anthropic.api_key "your-api-key-here"
|
||||
```
|
||||
|
||||
**Claude Code Not Found**
|
||||
```
|
||||
❌ Claude Code not properly installed
|
||||
```
|
||||
**Solution**: Rebuild the container image:
|
||||
```bash
|
||||
docker build -t cubbi-claudecode:latest cubbi/images/claudecode/
|
||||
```
|
||||
|
||||
**Network Issues**
|
||||
```
|
||||
Connection timeout or proxy errors
|
||||
```
|
||||
**Solution**: Configure proxy settings:
|
||||
```bash
|
||||
cubbi config set network.https_proxy "your-proxy-url"
|
||||
```
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable verbose output for debugging:
|
||||
|
||||
```bash
|
||||
# Check configuration
|
||||
cubbi exec claudecode "cat ~/.claude/settings.json"
|
||||
|
||||
# Verify installation
|
||||
cubbi exec claudecode "claude --version"
|
||||
cubbi exec claudecode "which claude"
|
||||
cubbi exec claudecode "node --version"
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- **API Keys**: Stored securely with 0o600 permissions
|
||||
- **Configuration**: Settings files have restricted access
|
||||
- **Environment**: Isolated container environment
|
||||
- **Telemetry**: Can be disabled for privacy
|
||||
|
||||
## Development
|
||||
|
||||
### Building the Image
|
||||
|
||||
```bash
|
||||
# Build locally
|
||||
docker build -t cubbi-claudecode:test cubbi/images/claudecode/
|
||||
|
||||
# Test basic functionality
|
||||
docker run --rm -it \
|
||||
-e ANTHROPIC_API_KEY="your-api-key" \
|
||||
cubbi-claudecode:test \
|
||||
bash -c "claude --version"
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run through Cubbi
|
||||
cubbi run claudecode --name test-claude
|
||||
cubbi exec test-claude "claude --version"
|
||||
cubbi stop test-claude
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
For issues related to:
|
||||
- **Cubbi Integration**: Check Cubbi documentation or open an issue
|
||||
- **Claude Code**: Visit [Claude Code documentation](https://docs.anthropic.com/en/docs/claude-code)
|
||||
- **API Keys**: Visit [Anthropic Console](https://console.anthropic.com/)
|
||||
|
||||
## License
|
||||
|
||||
This image configuration is provided under the same license as the Cubbi project. Claude Code is licensed separately by Anthropic.
|
||||
193
cubbi/images/claudecode/claudecode_plugin.py
Executable file
193
cubbi/images/claudecode/claudecode_plugin.py
Executable file
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Claude Code Plugin for Cubbi
|
||||
Handles authentication setup and configuration for Claude Code
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import stat
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from cubbi_init import ToolPlugin
|
||||
|
||||
# API key mappings from environment variables to Claude Code configuration
|
||||
API_KEY_MAPPINGS = {
|
||||
"ANTHROPIC_API_KEY": "api_key",
|
||||
"ANTHROPIC_AUTH_TOKEN": "auth_token",
|
||||
"ANTHROPIC_CUSTOM_HEADERS": "custom_headers",
|
||||
}
|
||||
|
||||
# Enterprise integration environment variables
|
||||
ENTERPRISE_MAPPINGS = {
|
||||
"CLAUDE_CODE_USE_BEDROCK": "use_bedrock",
|
||||
"CLAUDE_CODE_USE_VERTEX": "use_vertex",
|
||||
"HTTP_PROXY": "http_proxy",
|
||||
"HTTPS_PROXY": "https_proxy",
|
||||
"DISABLE_TELEMETRY": "disable_telemetry",
|
||||
}
|
||||
|
||||
|
||||
class ClaudeCodePlugin(ToolPlugin):
|
||||
"""Plugin for setting up Claude Code authentication and configuration"""
|
||||
|
||||
@property
|
||||
def tool_name(self) -> str:
|
||||
return "claudecode"
|
||||
|
||||
def _get_user_ids(self) -> tuple[int, int]:
|
||||
"""Get the cubbi user and group IDs from environment"""
|
||||
user_id = int(os.environ.get("CUBBI_USER_ID", "1000"))
|
||||
group_id = int(os.environ.get("CUBBI_GROUP_ID", "1000"))
|
||||
return user_id, group_id
|
||||
|
||||
def _set_ownership(self, path: Path) -> None:
|
||||
"""Set ownership of a path to the cubbi user"""
|
||||
user_id, group_id = self._get_user_ids()
|
||||
try:
|
||||
os.chown(path, user_id, group_id)
|
||||
except OSError as e:
|
||||
self.status.log(f"Failed to set ownership for {path}: {e}", "WARNING")
|
||||
|
||||
def _get_claude_dir(self) -> Path:
|
||||
"""Get the Claude Code configuration directory"""
|
||||
return Path("/home/cubbi/.claude")
|
||||
|
||||
def _ensure_claude_dir(self) -> Path:
|
||||
"""Ensure Claude directory exists with correct ownership"""
|
||||
claude_dir = self._get_claude_dir()
|
||||
|
||||
try:
|
||||
claude_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
self._set_ownership(claude_dir)
|
||||
except OSError as e:
|
||||
self.status.log(
|
||||
f"Failed to create Claude directory {claude_dir}: {e}", "ERROR"
|
||||
)
|
||||
|
||||
return claude_dir
|
||||
|
||||
def initialize(self) -> bool:
|
||||
"""Initialize Claude Code configuration"""
|
||||
self.status.log("Setting up Claude Code authentication...")
|
||||
|
||||
# Ensure Claude directory exists
|
||||
claude_dir = self._ensure_claude_dir()
|
||||
|
||||
# Create settings configuration
|
||||
settings = self._create_settings()
|
||||
|
||||
if settings:
|
||||
settings_file = claude_dir / "settings.json"
|
||||
success = self._write_settings(settings_file, settings)
|
||||
if success:
|
||||
self.status.log("✅ Claude Code authentication configured successfully")
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
self.status.log("⚠️ No authentication configuration found", "WARNING")
|
||||
self.status.log(
|
||||
" Please set ANTHROPIC_API_KEY environment variable", "WARNING"
|
||||
)
|
||||
self.status.log(" Claude Code will run without authentication", "INFO")
|
||||
# Return True to allow container to start without API key
|
||||
# Users can still use Claude Code with their own authentication methods
|
||||
return True
|
||||
|
||||
def _create_settings(self) -> Optional[Dict]:
|
||||
"""Create Claude Code settings configuration"""
|
||||
settings = {}
|
||||
|
||||
# Core authentication
|
||||
api_key = os.environ.get("ANTHROPIC_API_KEY")
|
||||
if not api_key:
|
||||
return None
|
||||
|
||||
# Basic authentication setup
|
||||
settings["apiKey"] = api_key
|
||||
|
||||
# Custom authorization token (optional)
|
||||
auth_token = os.environ.get("ANTHROPIC_AUTH_TOKEN")
|
||||
if auth_token:
|
||||
settings["authToken"] = auth_token
|
||||
|
||||
# Custom headers (optional)
|
||||
custom_headers = os.environ.get("ANTHROPIC_CUSTOM_HEADERS")
|
||||
if custom_headers:
|
||||
try:
|
||||
# Expect JSON string format
|
||||
settings["customHeaders"] = json.loads(custom_headers)
|
||||
except json.JSONDecodeError:
|
||||
self.status.log(
|
||||
"⚠️ Invalid ANTHROPIC_CUSTOM_HEADERS format, skipping", "WARNING"
|
||||
)
|
||||
|
||||
# Enterprise integration settings
|
||||
if os.environ.get("CLAUDE_CODE_USE_BEDROCK") == "true":
|
||||
settings["provider"] = "bedrock"
|
||||
|
||||
if os.environ.get("CLAUDE_CODE_USE_VERTEX") == "true":
|
||||
settings["provider"] = "vertex"
|
||||
|
||||
# Network proxy settings
|
||||
http_proxy = os.environ.get("HTTP_PROXY")
|
||||
https_proxy = os.environ.get("HTTPS_PROXY")
|
||||
if http_proxy or https_proxy:
|
||||
settings["proxy"] = {}
|
||||
if http_proxy:
|
||||
settings["proxy"]["http"] = http_proxy
|
||||
if https_proxy:
|
||||
settings["proxy"]["https"] = https_proxy
|
||||
|
||||
# Telemetry settings
|
||||
if os.environ.get("DISABLE_TELEMETRY") == "true":
|
||||
settings["telemetry"] = {"enabled": False}
|
||||
|
||||
# Tool permissions (allow all by default in Cubbi environment)
|
||||
settings["permissions"] = {
|
||||
"tools": {
|
||||
"read": {"allowed": True},
|
||||
"write": {"allowed": True},
|
||||
"edit": {"allowed": True},
|
||||
"bash": {"allowed": True},
|
||||
"webfetch": {"allowed": True},
|
||||
"websearch": {"allowed": True},
|
||||
}
|
||||
}
|
||||
|
||||
return settings
|
||||
|
||||
def _write_settings(self, settings_file: Path, settings: Dict) -> bool:
|
||||
"""Write settings to Claude Code configuration file"""
|
||||
try:
|
||||
# Write settings with secure permissions
|
||||
with open(settings_file, "w") as f:
|
||||
json.dump(settings, f, indent=2)
|
||||
|
||||
# Set ownership and secure file permissions (read/write for owner only)
|
||||
self._set_ownership(settings_file)
|
||||
os.chmod(settings_file, stat.S_IRUSR | stat.S_IWUSR)
|
||||
|
||||
self.status.log(f"Created Claude Code settings at {settings_file}")
|
||||
return True
|
||||
except Exception as e:
|
||||
self.status.log(f"Failed to write Claude Code settings: {e}", "ERROR")
|
||||
return False
|
||||
|
||||
def setup_tool_configuration(self) -> bool:
|
||||
"""Set up Claude Code configuration - called by base class"""
|
||||
# Additional tool configuration can be added here if needed
|
||||
return True
|
||||
|
||||
def integrate_mcp_servers(self, mcp_config: Dict[str, Any]) -> bool:
|
||||
"""Integrate Claude Code with available MCP servers"""
|
||||
if mcp_config["count"] == 0:
|
||||
self.status.log("No MCP servers to integrate")
|
||||
return True
|
||||
|
||||
# Claude Code has built-in MCP support, so we can potentially
|
||||
# configure MCP servers in the settings if needed
|
||||
self.status.log("MCP server integration available for Claude Code")
|
||||
return True
|
||||
68
cubbi/images/claudecode/cubbi_image.yaml
Normal file
68
cubbi/images/claudecode/cubbi_image.yaml
Normal file
@@ -0,0 +1,68 @@
|
||||
name: claudecode
|
||||
description: Claude Code AI environment
|
||||
version: 1.0.0
|
||||
maintainer: team@monadical.com
|
||||
image: monadical/cubbi-claudecode:latest
|
||||
|
||||
init:
|
||||
pre_command: /cubbi-init.sh
|
||||
command: /entrypoint.sh
|
||||
|
||||
environment:
|
||||
# Core Anthropic Authentication
|
||||
- name: ANTHROPIC_API_KEY
|
||||
description: Anthropic API key for Claude
|
||||
required: true
|
||||
sensitive: true
|
||||
|
||||
# Optional Enterprise Integration
|
||||
- name: ANTHROPIC_AUTH_TOKEN
|
||||
description: Custom authorization token for Claude
|
||||
required: false
|
||||
sensitive: true
|
||||
|
||||
- name: ANTHROPIC_CUSTOM_HEADERS
|
||||
description: Additional HTTP headers for Claude API requests
|
||||
required: false
|
||||
sensitive: true
|
||||
|
||||
# Enterprise Deployment Options
|
||||
- name: CLAUDE_CODE_USE_BEDROCK
|
||||
description: Use Amazon Bedrock instead of direct API
|
||||
required: false
|
||||
|
||||
- name: CLAUDE_CODE_USE_VERTEX
|
||||
description: Use Google Vertex AI instead of direct API
|
||||
required: false
|
||||
|
||||
# Network Configuration
|
||||
- name: HTTP_PROXY
|
||||
description: HTTP proxy server URL
|
||||
required: false
|
||||
|
||||
- name: HTTPS_PROXY
|
||||
description: HTTPS proxy server URL
|
||||
required: false
|
||||
|
||||
# Optional Telemetry Control
|
||||
- name: DISABLE_TELEMETRY
|
||||
description: Disable Claude Code telemetry
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
ports: []
|
||||
|
||||
volumes:
|
||||
- mountPath: /app
|
||||
description: Application directory
|
||||
|
||||
persistent_configs:
|
||||
- source: "/home/cubbi/.claude"
|
||||
target: "/cubbi-config/claude-settings"
|
||||
type: "directory"
|
||||
description: "Claude Code settings and configuration"
|
||||
|
||||
- source: "/home/cubbi/.cache/claude"
|
||||
target: "/cubbi-config/claude-cache"
|
||||
type: "directory"
|
||||
description: "Claude Code cache directory"
|
||||
251
cubbi/images/claudecode/test_claudecode.py
Executable file
251
cubbi/images/claudecode/test_claudecode.py
Executable file
@@ -0,0 +1,251 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Automated test suite for Claude Code Cubbi integration
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
|
||||
|
||||
def run_test(description: str, command: list, timeout: int = 30) -> bool:
|
||||
"""Run a test command and return success status"""
|
||||
print(f"🧪 Testing: {description}")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
command, capture_output=True, text=True, timeout=timeout
|
||||
)
|
||||
if result.returncode == 0:
|
||||
print(" ✅ PASS")
|
||||
return True
|
||||
else:
|
||||
print(f" ❌ FAIL: {result.stderr}")
|
||||
if result.stdout:
|
||||
print(f" 📋 stdout: {result.stdout}")
|
||||
return False
|
||||
except subprocess.TimeoutExpired:
|
||||
print(f" ⏰ TIMEOUT: Command exceeded {timeout}s")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f" ❌ ERROR: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_suite():
|
||||
"""Run complete test suite"""
|
||||
tests_passed = 0
|
||||
total_tests = 0
|
||||
|
||||
print("🚀 Starting Claude Code Cubbi Integration Test Suite")
|
||||
print("=" * 60)
|
||||
|
||||
# Test 1: Build image
|
||||
total_tests += 1
|
||||
if run_test(
|
||||
"Build Claude Code image",
|
||||
["docker", "build", "-t", "cubbi-claudecode:test", "cubbi/images/claudecode/"],
|
||||
timeout=180,
|
||||
):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 2: Tag image for Cubbi
|
||||
total_tests += 1
|
||||
if run_test(
|
||||
"Tag image for Cubbi",
|
||||
["docker", "tag", "cubbi-claudecode:test", "monadical/cubbi-claudecode:latest"],
|
||||
):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 3: Basic container startup
|
||||
total_tests += 1
|
||||
if run_test(
|
||||
"Container startup with test API key",
|
||||
[
|
||||
"docker",
|
||||
"run",
|
||||
"--rm",
|
||||
"-e",
|
||||
"ANTHROPIC_API_KEY=test-key",
|
||||
"cubbi-claudecode:test",
|
||||
"bash",
|
||||
"-c",
|
||||
"claude --version",
|
||||
],
|
||||
):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 4: Cubbi image list
|
||||
total_tests += 1
|
||||
if run_test(
|
||||
"Cubbi image list includes claudecode",
|
||||
["uv", "run", "-m", "cubbi.cli", "image", "list"],
|
||||
):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 5: Cubbi session creation
|
||||
total_tests += 1
|
||||
session_result = subprocess.run(
|
||||
[
|
||||
"uv",
|
||||
"run",
|
||||
"-m",
|
||||
"cubbi.cli",
|
||||
"session",
|
||||
"create",
|
||||
"--image",
|
||||
"claudecode",
|
||||
"--name",
|
||||
"test-automation",
|
||||
"--no-connect",
|
||||
"--env",
|
||||
"ANTHROPIC_API_KEY=test-key",
|
||||
"--run",
|
||||
"claude --version",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60,
|
||||
)
|
||||
|
||||
if session_result.returncode == 0:
|
||||
print("🧪 Testing: Cubbi session creation")
|
||||
print(" ✅ PASS")
|
||||
tests_passed += 1
|
||||
|
||||
# Extract session ID for cleanup
|
||||
session_id = None
|
||||
for line in session_result.stdout.split("\n"):
|
||||
if "Session ID:" in line:
|
||||
session_id = line.split("Session ID: ")[1].strip()
|
||||
break
|
||||
|
||||
if session_id:
|
||||
# Test 6: Session cleanup
|
||||
total_tests += 1
|
||||
if run_test(
|
||||
"Clean up test session",
|
||||
["uv", "run", "-m", "cubbi.cli", "session", "close", session_id],
|
||||
):
|
||||
tests_passed += 1
|
||||
else:
|
||||
print("🧪 Testing: Clean up test session")
|
||||
print(" ⚠️ SKIP: Could not extract session ID")
|
||||
total_tests += 1
|
||||
else:
|
||||
print("🧪 Testing: Cubbi session creation")
|
||||
print(f" ❌ FAIL: {session_result.stderr}")
|
||||
total_tests += 2 # This test and cleanup test both fail
|
||||
|
||||
# Test 7: Session without API key
|
||||
total_tests += 1
|
||||
no_key_result = subprocess.run(
|
||||
[
|
||||
"uv",
|
||||
"run",
|
||||
"-m",
|
||||
"cubbi.cli",
|
||||
"session",
|
||||
"create",
|
||||
"--image",
|
||||
"claudecode",
|
||||
"--name",
|
||||
"test-no-key",
|
||||
"--no-connect",
|
||||
"--run",
|
||||
"claude --version",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60,
|
||||
)
|
||||
|
||||
if no_key_result.returncode == 0:
|
||||
print("🧪 Testing: Session without API key")
|
||||
print(" ✅ PASS")
|
||||
tests_passed += 1
|
||||
|
||||
# Extract session ID and close
|
||||
session_id = None
|
||||
for line in no_key_result.stdout.split("\n"):
|
||||
if "Session ID:" in line:
|
||||
session_id = line.split("Session ID: ")[1].strip()
|
||||
break
|
||||
|
||||
if session_id:
|
||||
subprocess.run(
|
||||
["uv", "run", "-m", "cubbi.cli", "session", "close", session_id],
|
||||
capture_output=True,
|
||||
timeout=30,
|
||||
)
|
||||
else:
|
||||
print("🧪 Testing: Session without API key")
|
||||
print(f" ❌ FAIL: {no_key_result.stderr}")
|
||||
|
||||
# Test 8: Persistent configuration test
|
||||
total_tests += 1
|
||||
persist_result = subprocess.run(
|
||||
[
|
||||
"uv",
|
||||
"run",
|
||||
"-m",
|
||||
"cubbi.cli",
|
||||
"session",
|
||||
"create",
|
||||
"--image",
|
||||
"claudecode",
|
||||
"--name",
|
||||
"test-persist-auto",
|
||||
"--project",
|
||||
"test-automation",
|
||||
"--no-connect",
|
||||
"--env",
|
||||
"ANTHROPIC_API_KEY=test-key",
|
||||
"--run",
|
||||
"echo 'automation test' > ~/.claude/automation.txt && cat ~/.claude/automation.txt",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60,
|
||||
)
|
||||
|
||||
if persist_result.returncode == 0:
|
||||
print("🧪 Testing: Persistent configuration")
|
||||
print(" ✅ PASS")
|
||||
tests_passed += 1
|
||||
|
||||
# Extract session ID and close
|
||||
session_id = None
|
||||
for line in persist_result.stdout.split("\n"):
|
||||
if "Session ID:" in line:
|
||||
session_id = line.split("Session ID: ")[1].strip()
|
||||
break
|
||||
|
||||
if session_id:
|
||||
subprocess.run(
|
||||
["uv", "run", "-m", "cubbi.cli", "session", "close", session_id],
|
||||
capture_output=True,
|
||||
timeout=30,
|
||||
)
|
||||
else:
|
||||
print("🧪 Testing: Persistent configuration")
|
||||
print(f" ❌ FAIL: {persist_result.stderr}")
|
||||
|
||||
print("=" * 60)
|
||||
print(f"📊 Test Results: {tests_passed}/{total_tests} tests passed")
|
||||
|
||||
if tests_passed == total_tests:
|
||||
print("🎉 All tests passed! Claude Code integration is working correctly.")
|
||||
return True
|
||||
else:
|
||||
print(
|
||||
f"❌ {total_tests - tests_passed} test(s) failed. Please check the output above."
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Main test entry point"""
|
||||
success = test_suite()
|
||||
exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user