re-uploading work
This commit is contained in:
699
docs/pack-runtime-environments.md
Normal file
699
docs/pack-runtime-environments.md
Normal file
@@ -0,0 +1,699 @@
|
||||
# Pack Runtime Environments
|
||||
|
||||
**Status:** Production-ready
|
||||
**Version:** 1.0
|
||||
**Last Updated:** 2024-02-03
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Pack runtime environments provide **isolated dependency management** for each pack. When a pack declares runtime dependencies (e.g., Python packages, npm modules), these are installed in a pack-specific environment rather than system-wide. This prevents conflicts between packs that use the same runtime but require different dependency versions.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
/opt/attune/
|
||||
├── packs/ # Pack code and metadata
|
||||
│ ├── mycompany.monitoring/
|
||||
│ │ ├── pack.yaml
|
||||
│ │ ├── requirements.txt # Python dependencies
|
||||
│ │ └── actions/
|
||||
│ └── core/
|
||||
│ └── ...
|
||||
└── packenvs/ # Isolated runtime environments
|
||||
├── mycompany.monitoring/
|
||||
│ ├── python/ # Python venv for this pack
|
||||
│ │ ├── bin/
|
||||
│ │ │ ├── python
|
||||
│ │ │ └── pip
|
||||
│ │ └── lib/
|
||||
│ └── nodejs/ # Node.js modules for this pack
|
||||
│ └── node_modules/
|
||||
└── core/
|
||||
└── python/
|
||||
```
|
||||
|
||||
### Database Schema
|
||||
|
||||
#### `runtime` Table Enhancement
|
||||
|
||||
New field: `installers` (JSONB)
|
||||
|
||||
Stores instructions for creating pack-specific environments:
|
||||
|
||||
```json
|
||||
{
|
||||
"base_path_template": "/opt/attune/packenvs/{pack_ref}/{runtime_name_lower}",
|
||||
"installers": [
|
||||
{
|
||||
"name": "create_venv",
|
||||
"description": "Create Python virtual environment",
|
||||
"command": "python3",
|
||||
"args": ["-m", "venv", "{env_path}"],
|
||||
"cwd": "{pack_path}",
|
||||
"env": {},
|
||||
"order": 1,
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "install_requirements",
|
||||
"description": "Install pack Python dependencies",
|
||||
"command": "{env_path}/bin/pip",
|
||||
"args": ["install", "-r", "{pack_path}/requirements.txt"],
|
||||
"cwd": "{pack_path}",
|
||||
"env": {},
|
||||
"order": 2,
|
||||
"optional": false,
|
||||
"condition": {
|
||||
"file_exists": "{pack_path}/requirements.txt"
|
||||
}
|
||||
}
|
||||
],
|
||||
"executable_templates": {
|
||||
"python": "{env_path}/bin/python",
|
||||
"pip": "{env_path}/bin/pip"
|
||||
},
|
||||
"requires_environment": true
|
||||
}
|
||||
```
|
||||
|
||||
#### `pack_environment` Table
|
||||
|
||||
Tracks installed environments:
|
||||
|
||||
```sql
|
||||
CREATE TABLE pack_environment (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
pack BIGINT NOT NULL REFERENCES pack(id),
|
||||
pack_ref TEXT NOT NULL,
|
||||
runtime BIGINT NOT NULL REFERENCES runtime(id),
|
||||
runtime_ref TEXT NOT NULL,
|
||||
env_path TEXT NOT NULL,
|
||||
status pack_environment_status_enum NOT NULL,
|
||||
installed_at TIMESTAMPTZ,
|
||||
last_verified TIMESTAMPTZ,
|
||||
install_log TEXT,
|
||||
install_error TEXT,
|
||||
metadata JSONB,
|
||||
UNIQUE(pack, runtime)
|
||||
);
|
||||
```
|
||||
|
||||
**Status Values:**
|
||||
- `pending` - Environment creation scheduled
|
||||
- `installing` - Currently installing
|
||||
- `ready` - Environment ready for use
|
||||
- `failed` - Installation failed
|
||||
- `outdated` - Pack updated, needs rebuild
|
||||
|
||||
---
|
||||
|
||||
## Template Variables
|
||||
|
||||
Installer commands support template variable substitution:
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `{env_path}` | Full path to environment | `/opt/attune/packenvs/mypack/python` |
|
||||
| `{pack_path}` | Full path to pack directory | `/opt/attune/packs/mypack` |
|
||||
| `{pack_ref}` | Pack reference | `mycompany.monitoring` |
|
||||
| `{runtime_ref}` | Runtime reference | `core.python` |
|
||||
| `{runtime_name_lower}` | Lowercase runtime name | `python` |
|
||||
|
||||
---
|
||||
|
||||
## Runtime-Specific Configuration
|
||||
|
||||
### Python
|
||||
|
||||
**Installer Actions:**
|
||||
1. Create virtual environment: `python3 -m venv {env_path}`
|
||||
2. Upgrade pip: `{env_path}/bin/pip install --upgrade pip`
|
||||
3. Install requirements: `{env_path}/bin/pip install -r {pack_path}/requirements.txt`
|
||||
|
||||
**Pack Requirements File:** `requirements.txt` (optional)
|
||||
|
||||
**Executable Templates:**
|
||||
- `python`: `{env_path}/bin/python`
|
||||
- `pip`: `{env_path}/bin/pip`
|
||||
|
||||
**Example `requirements.txt`:**
|
||||
```
|
||||
requests>=2.28.0
|
||||
pyyaml>=6.0
|
||||
psutil>=5.9.0
|
||||
```
|
||||
|
||||
### Node.js
|
||||
|
||||
**Installer Actions:**
|
||||
1. Install dependencies: `npm install --prefix {env_path}`
|
||||
|
||||
**Pack Requirements File:** `package.json` (optional)
|
||||
|
||||
**Environment Variables:**
|
||||
- `NODE_PATH`: `{env_path}/node_modules`
|
||||
|
||||
**Example `package.json`:**
|
||||
```json
|
||||
{
|
||||
"name": "mypack",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0",
|
||||
"dotenv": "^16.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Shell
|
||||
|
||||
**No environment needed** - Uses system shell directly.
|
||||
|
||||
**Installer Actions:** None
|
||||
|
||||
**`requires_environment`:** `false`
|
||||
|
||||
### Native (Compiled Binaries)
|
||||
|
||||
**No environment needed** - Binaries are standalone executables.
|
||||
|
||||
**Installer Actions:** None
|
||||
|
||||
**`requires_environment`:** `false`
|
||||
|
||||
---
|
||||
|
||||
## Pack Environment Manager API
|
||||
|
||||
### Module Location
|
||||
|
||||
`attune_common::pack_environment::PackEnvironmentManager`
|
||||
|
||||
### Methods
|
||||
|
||||
```rust
|
||||
use attune_common::pack_environment::PackEnvironmentManager;
|
||||
use std::path::Path;
|
||||
|
||||
// Create manager
|
||||
let manager = PackEnvironmentManager::new(pool.clone(), &config);
|
||||
|
||||
// Ensure environment exists (creates if needed)
|
||||
let env = manager.ensure_environment(
|
||||
pack_id,
|
||||
"mycompany.monitoring",
|
||||
runtime_id,
|
||||
"core.python",
|
||||
Path::new("/opt/attune/packs/mycompany.monitoring")
|
||||
).await?;
|
||||
|
||||
// Get environment details
|
||||
let env = manager.get_environment(pack_id, runtime_id).await?;
|
||||
|
||||
// Get executable path
|
||||
let python_path = manager.get_executable_path(
|
||||
pack_id,
|
||||
runtime_id,
|
||||
"python"
|
||||
).await?;
|
||||
|
||||
// Verify environment is functional
|
||||
let is_valid = manager.verify_environment(pack_id, runtime_id).await?;
|
||||
|
||||
// Delete environment
|
||||
manager.delete_environment(pack_id, runtime_id).await?;
|
||||
|
||||
// List all environments for a pack
|
||||
let envs = manager.list_pack_environments(pack_id).await?;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pack Installation Flow
|
||||
|
||||
### 1. Pack Installation Triggered
|
||||
|
||||
When a pack is installed via:
|
||||
- CLI: `attune pack install mypack.tar.gz`
|
||||
- API: `POST /api/packs/install`
|
||||
|
||||
### 2. Pack Metadata Parsed
|
||||
|
||||
System reads `pack.yaml` to determine runtime dependencies:
|
||||
|
||||
```yaml
|
||||
name: mycompany.monitoring
|
||||
runtime_dependencies:
|
||||
- python: ">=3.8"
|
||||
```
|
||||
|
||||
### 3. Environments Created
|
||||
|
||||
For each runtime dependency:
|
||||
|
||||
1. **Check if environment exists**
|
||||
- Query `pack_environment` table
|
||||
- If `status = 'ready'`, skip creation
|
||||
|
||||
2. **Create environment record**
|
||||
- Insert into `pack_environment` with `status = 'pending'`
|
||||
- Calculate `env_path` using template
|
||||
|
||||
3. **Execute installer actions**
|
||||
- Update `status = 'installing'`
|
||||
- Run each installer in `order` sequence
|
||||
- Capture logs in `install_log`
|
||||
- On success: `status = 'ready'`, `installed_at = NOW()`
|
||||
- On failure: `status = 'failed'`, error in `install_error`
|
||||
|
||||
4. **Record completion**
|
||||
- Update `last_verified` timestamp
|
||||
- Store metadata (installed packages, versions)
|
||||
|
||||
### 4. Pack Ready
|
||||
|
||||
Once all environments are `ready`, pack is available for use.
|
||||
|
||||
---
|
||||
|
||||
## Action/Sensor Execution Flow
|
||||
|
||||
### 1. Execution Request Received
|
||||
|
||||
Worker receives execution request for an action.
|
||||
|
||||
### 2. Pack Environment Lookup
|
||||
|
||||
```rust
|
||||
// Get pack environment for this runtime
|
||||
let env = pack_env_manager
|
||||
.get_environment(pack_id, runtime_id)
|
||||
.await?;
|
||||
|
||||
if env.status != PackEnvironmentStatus::Ready {
|
||||
return Err("Pack environment not ready");
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Executable Path Resolution
|
||||
|
||||
```rust
|
||||
// Get pack-specific Python interpreter
|
||||
let python_path = pack_env_manager
|
||||
.get_executable_path(pack_id, runtime_id, "python")
|
||||
.await?
|
||||
.unwrap_or_else(|| "python3".to_string()); // Fallback to system
|
||||
|
||||
// Execute action with pack environment
|
||||
let mut cmd = Command::new(&python_path);
|
||||
cmd.arg(action_script_path);
|
||||
cmd.current_dir(pack_path);
|
||||
// ... rest of execution
|
||||
```
|
||||
|
||||
### 4. Environment Variables
|
||||
|
||||
Set runtime-specific environment variables:
|
||||
|
||||
**Python:**
|
||||
```rust
|
||||
cmd.env("VIRTUAL_ENV", env_path);
|
||||
cmd.env("PATH", format!("{}/bin:{}", env_path, std::env::var("PATH")?));
|
||||
```
|
||||
|
||||
**Node.js:**
|
||||
```rust
|
||||
cmd.env("NODE_PATH", format!("{}/node_modules", env_path));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Installer Conditions
|
||||
|
||||
Installers can specify conditions to control execution:
|
||||
|
||||
### File Exists
|
||||
|
||||
```json
|
||||
{
|
||||
"condition": {
|
||||
"file_exists": "{pack_path}/requirements.txt"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Only runs if the specified file exists.
|
||||
|
||||
### Future Conditions
|
||||
|
||||
Planned support for:
|
||||
- `command_exists`: Check if command is available
|
||||
- `env_var_set`: Check if environment variable is set
|
||||
- `platform`: Run only on specific platforms (Linux, Darwin, etc.)
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Installation Failures
|
||||
|
||||
When an installer fails:
|
||||
|
||||
1. **Non-optional installer fails** → Environment marked `failed`
|
||||
2. **Optional installer fails** → Log warning, continue
|
||||
3. **Installation log** → Stored in `pack_environment.install_log`
|
||||
4. **Error message** → Stored in `pack_environment.install_error`
|
||||
|
||||
**Example Error:**
|
||||
|
||||
```
|
||||
Installer 'install_requirements' failed: Command failed with exit code Some(1)
|
||||
STDOUT:
|
||||
Collecting requests>=2.28.0
|
||||
ERROR: Could not find a version that satisfies the requirement requests>=2.28.0
|
||||
|
||||
STDERR:
|
||||
ERROR: No matching distribution found for requests>=2.28.0
|
||||
```
|
||||
|
||||
### Recovery
|
||||
|
||||
**Retry Installation:**
|
||||
|
||||
```sql
|
||||
-- Reset environment to pending
|
||||
UPDATE pack_environment
|
||||
SET status = 'pending', install_error = NULL, install_log = NULL
|
||||
WHERE pack = $pack_id AND runtime = $runtime_id;
|
||||
```
|
||||
|
||||
Then trigger re-installation via pack manager.
|
||||
|
||||
**Manual Fix:**
|
||||
|
||||
```bash
|
||||
# Fix dependencies manually
|
||||
cd /opt/attune/packenvs/mypack/python
|
||||
source bin/activate
|
||||
pip install requests==2.31.0
|
||||
|
||||
# Mark as ready
|
||||
psql -U attune -d attune -c \
|
||||
"UPDATE pack_environment SET status = 'ready', installed_at = NOW() WHERE id = $env_id;"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoring & Verification
|
||||
|
||||
### Check Environment Status
|
||||
|
||||
```sql
|
||||
-- View all environments with health status
|
||||
SELECT * FROM v_pack_environment_status;
|
||||
|
||||
-- Check specific pack
|
||||
SELECT
|
||||
pack_ref,
|
||||
runtime_ref,
|
||||
status,
|
||||
installed_at,
|
||||
last_verified
|
||||
FROM v_pack_environment_status
|
||||
WHERE pack_ref = 'mycompany.monitoring';
|
||||
```
|
||||
|
||||
### Periodic Verification
|
||||
|
||||
**Recommended:** Verify environments weekly.
|
||||
|
||||
```rust
|
||||
use attune_common::pack_environment::PackEnvironmentManager;
|
||||
|
||||
async fn verify_all_environments(manager: &PackEnvironmentManager) -> Result<()> {
|
||||
let envs = sqlx::query("SELECT pack, runtime FROM pack_environment WHERE status = 'ready'")
|
||||
.fetch_all(&manager.pool)
|
||||
.await?;
|
||||
|
||||
for env in envs {
|
||||
let pack_id: i64 = env.get("pack");
|
||||
let runtime_id: i64 = env.get("runtime");
|
||||
|
||||
if !manager.verify_environment(pack_id, runtime_id).await? {
|
||||
warn!("Environment verification failed: pack={} runtime={}", pack_id, runtime_id);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Cleanup Orphaned Environments
|
||||
|
||||
```sql
|
||||
-- Find environments for deleted packs
|
||||
SELECT pe.*
|
||||
FROM pack_environment pe
|
||||
LEFT JOIN pack p ON pe.pack = p.id
|
||||
WHERE p.id IS NULL;
|
||||
|
||||
-- Delete orphaned environments
|
||||
DELETE FROM pack_environment
|
||||
WHERE pack NOT IN (SELECT id FROM pack);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database Functions
|
||||
|
||||
### `get_pack_environment_path(pack_ref, runtime_ref)`
|
||||
|
||||
Calculate filesystem path for an environment.
|
||||
|
||||
```sql
|
||||
SELECT get_pack_environment_path('mycompany.monitoring', 'core.python');
|
||||
-- Returns: /opt/attune/packenvs/mycompany.monitoring/python
|
||||
```
|
||||
|
||||
### `runtime_requires_environment(runtime_ref)`
|
||||
|
||||
Check if a runtime needs a pack-specific environment.
|
||||
|
||||
```sql
|
||||
SELECT runtime_requires_environment('core.python'); -- true
|
||||
SELECT runtime_requires_environment('core.shell'); -- false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Pack Authors
|
||||
|
||||
1. **Specify Dependencies Explicitly**
|
||||
- Include `requirements.txt` or `package.json`
|
||||
- Pin versions or use version ranges
|
||||
|
||||
2. **Minimal Dependencies**
|
||||
- Only include required packages
|
||||
- Avoid large frameworks if not needed
|
||||
|
||||
3. **Test Locally**
|
||||
- Create test environment: `python3 -m venv testenv`
|
||||
- Install and test: `testenv/bin/pip install -r requirements.txt`
|
||||
|
||||
4. **Document Requirements**
|
||||
- List dependencies in pack README
|
||||
- Note any system-level requirements
|
||||
|
||||
### Operators
|
||||
|
||||
1. **Monitor Environment Health**
|
||||
- Check `v_pack_environment_status` regularly
|
||||
- Set up alerts for `failed` status
|
||||
|
||||
2. **Disk Space Management**
|
||||
- Environments can be large (100MB+ for Python)
|
||||
- Monitor `/opt/attune/packenvs` disk usage
|
||||
|
||||
3. **Rebuild Outdated Environments**
|
||||
- When packs update, rebuild environments
|
||||
- Use `DELETE FROM pack_environment` and reinstall
|
||||
|
||||
4. **Backup Considerations**
|
||||
- Environments are **reproducible** - don't need backup
|
||||
- Backup pack files and database only
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Environment Stuck in "installing"
|
||||
|
||||
**Cause:** Installation process crashed or was interrupted.
|
||||
|
||||
**Fix:**
|
||||
```sql
|
||||
UPDATE pack_environment SET status = 'pending' WHERE status = 'installing';
|
||||
```
|
||||
|
||||
Then trigger re-installation.
|
||||
|
||||
### Python venv Creation Fails
|
||||
|
||||
**Error:** `python3: command not found`
|
||||
|
||||
**Cause:** Python 3 not installed on worker.
|
||||
|
||||
**Fix:**
|
||||
```bash
|
||||
# Install Python 3
|
||||
apt-get install python3 python3-venv # Debian/Ubuntu
|
||||
yum install python3 # RHEL/CentOS
|
||||
```
|
||||
|
||||
### Dependency Installation Fails
|
||||
|
||||
**Error:** `ERROR: Could not find a version that satisfies the requirement ...`
|
||||
|
||||
**Cause:** Package not available or version conflict.
|
||||
|
||||
**Fix:**
|
||||
1. Check package name and version in `requirements.txt`
|
||||
2. Test installation manually: `pip install <package>`
|
||||
3. Update pack dependencies to compatible versions
|
||||
|
||||
### Node.js npm install Fails
|
||||
|
||||
**Error:** `npm ERR! Cannot read property 'match' of undefined`
|
||||
|
||||
**Cause:** Corrupted `package-lock.json` or npm cache.
|
||||
|
||||
**Fix:**
|
||||
```bash
|
||||
cd /opt/attune/packs/mypack
|
||||
rm package-lock.json
|
||||
rm -rf node_modules
|
||||
# Reinstall pack to trigger environment rebuild
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### Existing Packs
|
||||
|
||||
For packs installed before pack environments:
|
||||
|
||||
1. **No changes required** - Packs without dependency files work as before
|
||||
2. **Add dependency files** - Create `requirements.txt` or `package.json`
|
||||
3. **Reinstall pack** - Trigger environment creation
|
||||
|
||||
```bash
|
||||
# Reinstall pack
|
||||
attune pack uninstall mypack
|
||||
attune pack install mypack.tar.gz
|
||||
```
|
||||
|
||||
### Database Migration
|
||||
|
||||
Run migration `20260203000002_add_pack_environments.sql`:
|
||||
|
||||
```bash
|
||||
cd attune
|
||||
sqlx migrate run
|
||||
```
|
||||
|
||||
**Changes:**
|
||||
- Adds `runtime.installers` column
|
||||
- Creates `pack_environment` table
|
||||
- Populates installer metadata for core runtimes
|
||||
|
||||
---
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Planned Features
|
||||
|
||||
1. **Version Pinning**
|
||||
- Store detected runtime versions
|
||||
- Require specific versions per pack
|
||||
- Example: `python: "3.11.x"`
|
||||
|
||||
2. **Shared Base Environments**
|
||||
- Common dependencies in shared layer
|
||||
- Pack-specific on top
|
||||
- Reduces disk usage
|
||||
|
||||
3. **Container-Based Environments**
|
||||
- Run each pack in isolated container
|
||||
- Full OS-level isolation
|
||||
- Resource limits per pack
|
||||
|
||||
4. **Dependency Caching**
|
||||
- Cache downloaded packages
|
||||
- Faster environment creation
|
||||
- Offline installation support
|
||||
|
||||
5. **Environment Templates**
|
||||
- Pre-built environment images
|
||||
- Quick cloning for new packs
|
||||
- Standardized base environments
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### Pack Environment Status Codes
|
||||
|
||||
| Status | Description | Recovery |
|
||||
|--------|-------------|----------|
|
||||
| `pending` | Not yet installed | Normal - will install |
|
||||
| `installing` | Installation in progress | Wait or reset to pending |
|
||||
| `ready` | Installed and verified | None needed |
|
||||
| `failed` | Installation failed | Check logs, fix issues, reset |
|
||||
| `outdated` | Pack updated | Reinstall environment |
|
||||
|
||||
### Common SQL Queries
|
||||
|
||||
**List all ready environments:**
|
||||
```sql
|
||||
SELECT pack_ref, runtime_ref, env_path
|
||||
FROM pack_environment
|
||||
WHERE status = 'ready';
|
||||
```
|
||||
|
||||
**Find failed installations:**
|
||||
```sql
|
||||
SELECT pack_ref, runtime_ref, install_error
|
||||
FROM pack_environment
|
||||
WHERE status = 'failed';
|
||||
```
|
||||
|
||||
**Recent installations:**
|
||||
```sql
|
||||
SELECT pack_ref, runtime_ref, installed_at
|
||||
FROM pack_environment
|
||||
WHERE installed_at > NOW() - INTERVAL '24 hours'
|
||||
ORDER BY installed_at DESC;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
✅ **Isolated Environments** - Each pack gets its own dependencies
|
||||
✅ **Conflict Prevention** - No version conflicts between packs
|
||||
✅ **Database-Driven** - Installation instructions in database
|
||||
✅ **Automatic Setup** - Environments created on pack install
|
||||
✅ **Runtime Agnostic** - Supports Python, Node.js, and extensible to others
|
||||
✅ **Production Ready** - Includes monitoring, verification, and error handling
|
||||
|
||||
**Migration Required:** Yes (`20260203000002_add_pack_environments.sql`)
|
||||
**Breaking Changes:** None (additive feature)
|
||||
**Production Ready:** ✅ Yes
|
||||
Reference in New Issue
Block a user