Skip to content
Open
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
157 changes: 157 additions & 0 deletions container-exec/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# syntax=docker/dockerfile:1

FROM node:18-slim

# Install common CLI tools that might be useful for exec commands
RUN apt-get update && apt-get install -y \
curl \
wget \
git \
vim \
nano \
htop \
net-tools \
iputils-ping \
dnsutils \
procps \
coreutils \
findutils \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Create the exec server application
RUN cat > server.js << 'EOF'
const http = require('http');
const { spawn } = require('child_process');
const { promisify } = require('util');

const server = http.createServer(async (req, res) => {
// Set CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}

// Handle exec endpoint
if (req.url === '/__exec' && req.method === 'POST') {
try {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', async () => {
try {
const payload = JSON.parse(body);
const result = await executeCommand(payload);

res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(result));
} catch (error) {
console.error('Exec error:', error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
exitCode: 1,
stdout: '',
stderr: error.message,
duration: 0
}));
}
});
} catch (error) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
exitCode: 1,
stdout: '',
stderr: 'Failed to process exec request',
duration: 0
}));
}
return;
}

// Handle regular HTTP requests
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Container Exec Demo Server\\nUse the /__exec endpoint for command execution');
});

async function executeCommand(payload) {
const { command, workingDirectory, env, timeout = 30000 } = payload;
const startTime = Date.now();

return new Promise((resolve) => {
const options = {
cwd: workingDirectory || '/app',
env: { ...process.env, ...(env || {}) },
timeout: timeout,
killSignal: 'SIGKILL'
};

console.log('Executing command:', command, 'with options:', options);

const child = spawn(command[0], command.slice(1), options);

let stdout = '';
let stderr = '';

child.stdout?.on('data', (data) => {
stdout += data.toString();
});

child.stderr?.on('data', (data) => {
stderr += data.toString();
});

child.on('close', (code, signal) => {
const duration = Date.now() - startTime;

// Handle timeout
if (signal === 'SIGKILL' && duration >= timeout - 100) {
resolve({
exitCode: 124,
stdout: stdout,
stderr: stderr + '\\nCommand timed out',
duration: duration
});
return;
}

resolve({
exitCode: code || 0,
stdout: stdout,
stderr: stderr,
duration: duration
});
});

child.on('error', (error) => {
resolve({
exitCode: 1,
stdout: stdout,
stderr: stderr + '\\nError: ' + error.message,
duration: Date.now() - startTime
});
});

// Set up timeout
setTimeout(() => {
if (!child.killed) {
child.kill('SIGKILL');
}
}, timeout);
});
}

const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
console.log(\`Container exec server running on port \${PORT}\`);
console.log('Ready to handle exec requests at /__exec');
});
EOF

EXPOSE 8080

CMD ["node", "server.js"]
74 changes: 74 additions & 0 deletions container-exec/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Container Exec Demo

This demo showcases the `container.exec()` functionality, allowing you to execute arbitrary commands inside running containers with a simple, clean API.

## Features

- **Simple API**: Just `await container.exec('your command')` - no manual container state checking required
- **Automatic Container Management**: The container is automatically started if not running
- **Rich Command Support**: Execute both string commands and command arrays
- **Comprehensive Results**: Get stdout, stderr, exit codes, and execution duration
- **Error Handling**: Graceful handling of timeouts, network errors, and command failures
- **Flexible Options**: Working directory, environment variables, and custom timeouts

## Usage Examples

### Basic Command Execution
```typescript
const result = await container.exec('echo "Hello from container!"');
console.log(result.stdout); // "Hello from container!"
```

### Command with Options
```typescript
const result = await container.exec('echo $NODE_ENV', {
env: { NODE_ENV: 'production' },
workingDirectory: '/app',
timeout: 5000
});
```

### Array Commands
```typescript
const result = await container.exec(['ls', '-la', '/app']);
```

## Demo Endpoints

- `GET /` - Interactive web interface for testing exec commands
- `POST /exec` - API endpoint for executing commands
- `GET /examples` - Pre-built command examples
- `GET /status` - Container and execution status

## Running the Demo

```bash
npm install
npm run dev
```

Then visit http://localhost:8787 to interact with the container exec demo.

## Container Setup

The demo uses a simple Node.js container that:
- Listens on port 8080 for HTTP requests
- Implements the `/__exec` endpoint for command execution
- Provides a clean execution environment with common CLI tools

## Command Examples to Try

- **System Info**: `uname -a`
- **File System**: `ls -la /`
- **Environment**: `env | grep NODE`
- **Process List**: `ps aux`
- **Network**: `curl -s https://httpbin.org/json`
- **Custom Script**: `echo "console.log('Hello from Node!')" | node`

## Error Handling

The exec API gracefully handles various error scenarios:
- **Command not found**: Returns exit code 127 with appropriate stderr
- **Timeout**: Returns exit code 124 with "Command timed out" message
- **Network errors**: Returns exit code 1 with connection error details
- **Container failures**: Automatically attempts to restart and retry
16 changes: 16 additions & 0 deletions container-exec/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "container-exec-demo",
"version": "0.0.0",
"private": true,
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
"cf-typegen": "wrangler types"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250403.0",
"typescript": "^5.5.2",
"wrangler": "^4.25.0"
}
}
Loading