working out the worker/execution interface

This commit is contained in:
2026-02-08 12:55:33 -06:00
parent c62f41669d
commit a74e13fa0b
108 changed files with 21162 additions and 674 deletions

View File

@@ -0,0 +1,321 @@
# Core Pack Actions
## Overview
All actions in the core pack follow Attune's secure-by-design architecture:
- **Parameter delivery:** stdin (JSON format) - never environment variables
- **Output format:** Explicitly declared (text, json, or yaml)
- **Output schema:** Describes structured data shape (json/yaml only)
- **Execution metadata:** Automatically captured (stdout/stderr/exit_code)
## Parameter Delivery Method
**All actions:**
- Read parameters from **stdin** as JSON
- Use `parameter_delivery: stdin` and `parameter_format: json` in their YAML definitions
- **DO NOT** use environment variables for parameters
## Output Format
**All actions must specify an `output_format`:**
- `text` - Plain text output (stored as-is, no parsing)
- `json` - JSON structured data (parsed into JSONB field)
- `yaml` - YAML structured data (parsed into JSONB field)
**Output schema:**
- Only applicable for `json` and `yaml` formats
- Describes the structure of data written to stdout
- **Should NOT include** stdout/stderr/exit_code (captured automatically)
## Environment Variables
### Standard Environment Variables (Provided by Worker)
The worker automatically provides these environment variables to all action executions:
| Variable | Description | Always Present |
|----------|-------------|----------------|
| `ATTUNE_ACTION` | Action ref (e.g., `core.http_request`) | ✅ Yes |
| `ATTUNE_EXEC_ID` | Execution database ID | ✅ Yes |
| `ATTUNE_API_TOKEN` | Execution-scoped API token | ✅ Yes |
| `ATTUNE_RULE` | Rule ref that triggered execution | ❌ Only if from rule |
| `ATTUNE_TRIGGER` | Trigger ref that caused enforcement | ❌ Only if from trigger |
**Use cases:**
- Logging with execution context
- Calling Attune API (using `ATTUNE_API_TOKEN`)
- Conditional logic based on rule/trigger
- Creating child executions
- Accessing secrets via API
**Example:**
```bash
#!/bin/bash
# Log with context
echo "[$ATTUNE_ACTION] [Exec: $ATTUNE_EXEC_ID] Processing..." >&2
# Call Attune API
curl -s -H "Authorization: Bearer $ATTUNE_API_TOKEN" \
"$ATTUNE_API_URL/api/v1/executions/$ATTUNE_EXEC_ID"
# Conditional behavior
if [ -n "$ATTUNE_RULE" ]; then
echo "Triggered by rule: $ATTUNE_RULE" >&2
fi
```
See [Execution Environment Variables](../../../docs/QUICKREF-execution-environment.md) for complete documentation.
### Custom Environment Variables (Optional)
Custom environment variables can be set via `execution.env_vars` field for:
- **Debug/logging controls** (e.g., `DEBUG=1`, `LOG_LEVEL=debug`)
- **Runtime configuration** (e.g., custom paths, feature flags)
- **Action-specific context** (non-sensitive execution context)
Environment variables should **NEVER** be used for:
- Action parameters (use stdin instead)
- Secrets or credentials (use `ATTUNE_API_TOKEN` to fetch from key vault)
- User-provided data (use stdin parameters)
## Implementation Patterns
### Bash/Shell Actions
Shell actions read JSON from stdin using `jq`:
```bash
#!/bin/bash
set -e
set -o pipefail
# Read JSON parameters from stdin
INPUT=$(cat)
# Parse parameters using jq
PARAM1=$(echo "$INPUT" | jq -r '.param1 // "default_value"')
PARAM2=$(echo "$INPUT" | jq -r '.param2 // ""')
# Check for null values (optional parameters)
if [ -n "$PARAM2" ] && [ "$PARAM2" != "null" ]; then
echo "Param2 provided: $PARAM2"
fi
# Use the parameters
echo "Param1: $PARAM1"
```
### Advanced Bash Actions
For more complex bash actions (like http_request.sh), use `curl` or other standard utilities:
```bash
#!/bin/bash
set -e
set -o pipefail
# Read JSON parameters from stdin
INPUT=$(cat)
# Parse parameters
URL=$(echo "$INPUT" | jq -r '.url // ""')
METHOD=$(echo "$INPUT" | jq -r '.method // "GET"')
# Validate required parameters
if [ -z "$URL" ]; then
echo "ERROR: url parameter is required" >&2
exit 1
fi
# Make HTTP request with curl
RESPONSE=$(curl -s -X "$METHOD" "$URL")
# Output result as JSON
jq -n \
--arg body "$RESPONSE" \
--argjson success true \
'{body: $body, success: $success}'
```
## Core Pack Actions
### Simple Actions
1. **echo.sh** - Outputs a message
2. **sleep.sh** - Pauses execution for a specified duration
3. **noop.sh** - Does nothing (useful for testing)
### HTTP Action
4. **http_request.sh** - Makes HTTP requests with authentication support (curl-based)
### Pack Management Actions (API Wrappers)
These actions wrap API endpoints and pass parameters to the Attune API:
5. **download_packs.sh** - Downloads packs from git/HTTP/registry
6. **build_pack_envs.sh** - Builds runtime environments for packs
7. **register_packs.sh** - Registers packs in the database
8. **get_pack_dependencies.sh** - Analyzes pack dependencies
## Testing Actions Locally
You can test actions locally by piping JSON to stdin:
```bash
# Test echo action
echo '{"message": "Hello from stdin!"}' | ./echo.sh
# Test echo with no message (outputs empty line)
echo '{}' | ./echo.sh
# Test sleep action
echo '{"seconds": 2, "message": "Sleeping..."}' | ./sleep.sh
# Test http_request action
echo '{"url": "https://api.github.com", "method": "GET"}' | ./http_request.sh
# Test with file input
cat params.json | ./echo.sh
```
## Migration Summary
**Before (using environment variables):**
```bash
MESSAGE="${ATTUNE_ACTION_MESSAGE:-}"
```
**After (using stdin JSON):**
```bash
INPUT=$(cat)
MESSAGE=$(echo "$INPUT" | jq -r '.message // ""')
```
## Security Benefits
1. **No process exposure** - Parameters never appear in `ps`, `/proc/<pid>/environ`
2. **Secure by default** - All actions use stdin, no special configuration needed
3. **Clear separation** - Action parameters vs. environment configuration
4. **Audit friendly** - All sensitive data flows through stdin, not environment
## YAML Configuration
All action YAML files explicitly declare parameter delivery and output format:
```yaml
name: example_action
ref: core.example_action
runner_type: shell
entry_point: example.sh
# Parameter delivery: stdin for secure parameter passing (no env vars)
parameter_delivery: stdin
parameter_format: json
# Output format: text, json, or yaml
output_format: text
parameters:
type: object
properties:
message:
type: string
description: "Message to output (empty string if not provided)"
required: []
# Output schema: not applicable for text output format
# For json/yaml formats, describe the structure of data your action outputs
# Do NOT include stdout/stderr/exit_code - those are captured automatically
# Do NOT include generic "status" or "result" wrappers - output your data directly
```
## Best Practices
### Parameters
1. **Always use stdin** for action parameters
2. **Use jq for bash** scripts to parse JSON
3. **Handle null values** - Use jq's `// "default"` operator to provide defaults
4. **Provide sensible defaults** - Use empty string, 0, false, or empty array/object as appropriate
5. **Validate required params** - Exit with error if required parameters are missing (when truly required)
6. **Mark secrets** - Use `secret: true` in YAML for sensitive parameters
7. **Never use env vars for parameters** - Parameters come from stdin, not environment
### Environment Variables
1. **Use standard ATTUNE_* variables** - Worker provides execution context
2. **Access API with ATTUNE_API_TOKEN** - Execution-scoped authentication
3. **Log with context** - Include `ATTUNE_ACTION` and `ATTUNE_EXEC_ID` in logs
4. **Custom env vars via execution.env_vars** - For debug flags and configuration only
5. **Never log ATTUNE_API_TOKEN** - Security sensitive
6. **Check ATTUNE_RULE/ATTUNE_TRIGGER** - Conditional behavior for automated vs manual
7. **Use env vars for runtime context** - Not for user data or parameters
### Output Format
1. **Specify output_format** - Always set to "text", "json", or "yaml"
2. **Use text for simple output** - Messages, logs, unstructured data
3. **Use json for structured data** - API responses, complex results
4. **Use yaml for readable config** - Human-readable structured output
5. **Define schema for structured output** - Only for json/yaml formats
6. **Don't include execution metadata** - No stdout/stderr/exit_code in schema
7. **Use stderr for errors** - Diagnostic messages go to stderr, not stdout
8. **Return proper exit codes** - 0 for success, non-zero for failure
## Dependencies
All core pack actions have **zero runtime dependencies**:
- **Bash actions**: Require `jq` (for JSON parsing) and `curl` (for HTTP requests)
- Both `jq` and `curl` are standard utilities available in all Attune worker containers
- **No Python, Node.js, or other runtime dependencies required**
## Execution Metadata (Automatic)
The following are **automatically captured** by the worker and should **NOT** be included in output schemas:
- `stdout` - Raw standard output (captured as-is)
- `stderr` - Standard error output (written to log file)
- `exit_code` - Process exit code (0 = success)
- `duration_ms` - Execution duration in milliseconds
These are execution system concerns, not action output concerns.
## Example: Using Environment Variables and Parameters
```bash
#!/bin/bash
set -e
set -o pipefail
# Standard environment variables (provided by worker)
echo "[$ATTUNE_ACTION] [Exec: $ATTUNE_EXEC_ID] Starting execution" >&2
# Read action parameters from stdin
INPUT=$(cat)
URL=$(echo "$INPUT" | jq -r '.url // ""')
if [ -z "$URL" ]; then
echo "ERROR: url parameter is required" >&2
exit 1
fi
# Log execution context
if [ -n "$ATTUNE_RULE" ]; then
echo "Triggered by rule: $ATTUNE_RULE" >&2
fi
# Make request
RESPONSE=$(curl -s "$URL")
# Output result
echo "$RESPONSE"
echo "[$ATTUNE_ACTION] [Exec: $ATTUNE_EXEC_ID] Completed successfully" >&2
exit 0
```
## Future Considerations
- Consider adding a bash library for common parameter parsing patterns
- Add parameter validation helpers
- Create templates for new actions in different languages
- Add output schema validation tooling
- Add helper functions for API interaction using ATTUNE_API_TOKEN

View File

@@ -0,0 +1,102 @@
#!/bin/bash
# Build Pack Environments Action - API Wrapper
# Thin wrapper around POST /api/v1/packs/build-envs
set -e
set -o pipefail
# Read JSON parameters from stdin
INPUT=$(cat)
# Parse parameters using jq
PACK_PATHS=$(echo "$INPUT" | jq -c '.pack_paths // []')
PACKS_BASE_DIR=$(echo "$INPUT" | jq -r '.packs_base_dir // "/opt/attune/packs"')
PYTHON_VERSION=$(echo "$INPUT" | jq -r '.python_version // "3.11"')
NODEJS_VERSION=$(echo "$INPUT" | jq -r '.nodejs_version // "20"')
SKIP_PYTHON=$(echo "$INPUT" | jq -r '.skip_python // false')
SKIP_NODEJS=$(echo "$INPUT" | jq -r '.skip_nodejs // false')
FORCE_REBUILD=$(echo "$INPUT" | jq -r '.force_rebuild // false')
TIMEOUT=$(echo "$INPUT" | jq -r '.timeout // 600')
API_URL=$(echo "$INPUT" | jq -r '.api_url // "http://localhost:8080"')
API_TOKEN=$(echo "$INPUT" | jq -r '.api_token // ""')
# Validate required parameters
PACK_COUNT=$(echo "$PACK_PATHS" | jq -r 'length' 2>/dev/null || echo "0")
if [[ "$PACK_COUNT" -eq 0 ]]; then
echo '{"built_environments":[],"failed_environments":[],"summary":{"total_packs":0,"success_count":0,"failure_count":0,"python_envs_built":0,"nodejs_envs_built":0,"total_duration_ms":0}}' >&1
exit 1
fi
# Build request body
REQUEST_BODY=$(jq -n \
--argjson pack_paths "$PACK_PATHS" \
--arg packs_base_dir "$PACKS_BASE_DIR" \
--arg python_version "$PYTHON_VERSION" \
--arg nodejs_version "$NODEJS_VERSION" \
--argjson skip_python "$([[ "$SKIP_PYTHON" == "true" ]] && echo true || echo false)" \
--argjson skip_nodejs "$([[ "$SKIP_NODEJS" == "true" ]] && echo true || echo false)" \
--argjson force_rebuild "$([[ "$FORCE_REBUILD" == "true" ]] && echo true || echo false)" \
--argjson timeout "$TIMEOUT" \
'{
pack_paths: $pack_paths,
packs_base_dir: $packs_base_dir,
python_version: $python_version,
nodejs_version: $nodejs_version,
skip_python: $skip_python,
skip_nodejs: $skip_nodejs,
force_rebuild: $force_rebuild,
timeout: $timeout
}')
# Make API call
CURL_ARGS=(
-X POST
-H "Content-Type: application/json"
-H "Accept: application/json"
-d "$REQUEST_BODY"
-s
-w "\n%{http_code}"
--max-time $((TIMEOUT + 30))
--connect-timeout 10
)
if [[ -n "$API_TOKEN" ]] && [[ "$API_TOKEN" != "null" ]]; then
CURL_ARGS+=(-H "Authorization: Bearer ${API_TOKEN}")
fi
RESPONSE=$(curl "${CURL_ARGS[@]}" "${API_URL}/api/v1/packs/build-envs" 2>/dev/null || echo -e "\n000")
# Extract status code (last line)
HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
BODY=$(echo "$RESPONSE" | head -n -1)
# Check HTTP status
if [[ "$HTTP_CODE" -ge 200 ]] && [[ "$HTTP_CODE" -lt 300 ]]; then
# Extract data field from API response
echo "$BODY" | jq -r '.data // .'
exit 0
else
# Error response
ERROR_MSG=$(echo "$BODY" | jq -r '.error // .message // "API request failed"' 2>/dev/null || echo "API request failed")
cat <<EOF
{
"built_environments": [],
"failed_environments": [{
"pack_ref": "api",
"pack_path": "",
"runtime": "unknown",
"error": "API call failed (HTTP $HTTP_CODE): $ERROR_MSG"
}],
"summary": {
"total_packs": 0,
"success_count": 0,
"failure_count": 1,
"python_envs_built": 0,
"nodejs_envs_built": 0,
"total_duration_ms": 0
}
}
EOF
exit 1
fi

View File

@@ -0,0 +1,165 @@
# Build Pack Environments Action
# Creates runtime environments and installs dependencies for packs
name: build_pack_envs
ref: core.build_pack_envs
description: "Build runtime environments for packs and install declared dependencies (Python requirements.txt, Node.js package.json)"
enabled: true
runner_type: shell
entry_point: build_pack_envs.sh
# Parameter delivery: stdin for secure parameter passing (no env vars)
parameter_delivery: stdin
parameter_format: json
# Output format: json (structured data parsing enabled)
output_format: json
# Action parameters schema
parameters:
type: object
properties:
pack_paths:
type: array
description: "List of pack directory paths to build environments for"
items:
type: string
minItems: 1
packs_base_dir:
type: string
description: "Base directory where packs are installed"
default: "/opt/attune/packs"
python_version:
type: string
description: "Python version to use for virtualenvs"
default: "3.11"
nodejs_version:
type: string
description: "Node.js version to use"
default: "20"
skip_python:
type: boolean
description: "Skip building Python environments"
default: false
skip_nodejs:
type: boolean
description: "Skip building Node.js environments"
default: false
force_rebuild:
type: boolean
description: "Force rebuild of existing environments"
default: false
timeout:
type: integer
description: "Timeout in seconds for building each environment"
default: 600
minimum: 60
maximum: 3600
required:
- pack_paths
# Output schema: describes the JSON structure written to stdout
# Note: stdout/stderr/exit_code are captured automatically by the execution system
output_schema:
type: object
properties:
built_environments:
type: array
description: "List of successfully built environments"
items:
type: object
properties:
pack_ref:
type: string
description: "Pack reference"
pack_path:
type: string
description: "Pack directory path"
environments:
type: object
description: "Built environments for this pack"
properties:
python:
type: object
description: "Python environment details"
properties:
virtualenv_path:
type: string
description: "Path to Python virtualenv"
requirements_installed:
type: boolean
description: "Whether requirements.txt was installed"
package_count:
type: integer
description: "Number of packages installed"
python_version:
type: string
description: "Python version used"
nodejs:
type: object
description: "Node.js environment details"
properties:
node_modules_path:
type: string
description: "Path to node_modules directory"
dependencies_installed:
type: boolean
description: "Whether package.json was installed"
package_count:
type: integer
description: "Number of packages installed"
nodejs_version:
type: string
description: "Node.js version used"
duration_ms:
type: integer
description: "Time taken to build environments in milliseconds"
failed_environments:
type: array
description: "List of packs where environment build failed"
items:
type: object
properties:
pack_ref:
type: string
description: "Pack reference"
pack_path:
type: string
description: "Pack directory path"
runtime:
type: string
description: "Runtime that failed (python or nodejs)"
error:
type: string
description: "Error message"
summary:
type: object
description: "Summary of environment build process"
properties:
total_packs:
type: integer
description: "Total number of packs processed"
success_count:
type: integer
description: "Number of packs with successful builds"
failure_count:
type: integer
description: "Number of packs with failed builds"
python_envs_built:
type: integer
description: "Number of Python environments built"
nodejs_envs_built:
type: integer
description: "Number of Node.js environments built"
total_duration_ms:
type: integer
description: "Total time taken for all builds in milliseconds"
# Tags for categorization
tags:
- pack
- environment
- dependencies
- python
- nodejs
- installation

View File

@@ -0,0 +1,86 @@
#!/bin/bash
# Download Packs Action - API Wrapper
# Thin wrapper around POST /api/v1/packs/download
set -e
set -o pipefail
# Read JSON parameters from stdin
INPUT=$(cat)
# Parse parameters using jq
PACKS=$(echo "$INPUT" | jq -c '.packs // []')
DESTINATION_DIR=$(echo "$INPUT" | jq -r '.destination_dir // ""')
REGISTRY_URL=$(echo "$INPUT" | jq -r '.registry_url // "https://registry.attune.io/index.json"')
REF_SPEC=$(echo "$INPUT" | jq -r '.ref_spec // ""')
TIMEOUT=$(echo "$INPUT" | jq -r '.timeout // 300')
VERIFY_SSL=$(echo "$INPUT" | jq -r '.verify_ssl // true')
API_URL=$(echo "$INPUT" | jq -r '.api_url // "http://localhost:8080"')
API_TOKEN=$(echo "$INPUT" | jq -r '.api_token // ""')
# Validate required parameters
if [[ -z "$DESTINATION_DIR" ]] || [[ "$DESTINATION_DIR" == "null" ]]; then
echo '{"downloaded_packs":[],"failed_packs":[{"source":"input","error":"destination_dir is required"}],"total_count":0,"success_count":0,"failure_count":1}' >&1
exit 1
fi
# Build request body
REQUEST_BODY=$(jq -n \
--argjson packs "$PACKS" \
--arg destination_dir "$DESTINATION_DIR" \
--arg registry_url "$REGISTRY_URL" \
--argjson timeout "$TIMEOUT" \
--argjson verify_ssl "$([[ "$VERIFY_SSL" == "true" ]] && echo true || echo false)" \
'{
packs: $packs,
destination_dir: $destination_dir,
registry_url: $registry_url,
timeout: $timeout,
verify_ssl: $verify_ssl
}' | jq --arg ref_spec "$REF_SPEC" 'if $ref_spec != "" and $ref_spec != "null" then .ref_spec = $ref_spec else . end')
# Make API call
CURL_ARGS=(
-X POST
-H "Content-Type: application/json"
-H "Accept: application/json"
-d "$REQUEST_BODY"
-s
-w "\n%{http_code}"
--max-time $((TIMEOUT + 30))
--connect-timeout 10
)
if [[ -n "$API_TOKEN" ]] && [[ "$API_TOKEN" != "null" ]]; then
CURL_ARGS+=(-H "Authorization: Bearer ${API_TOKEN}")
fi
RESPONSE=$(curl "${CURL_ARGS[@]}" "${API_URL}/api/v1/packs/download" 2>/dev/null || echo -e "\n000")
# Extract status code (last line)
HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
BODY=$(echo "$RESPONSE" | head -n -1)
# Check HTTP status
if [[ "$HTTP_CODE" -ge 200 ]] && [[ "$HTTP_CODE" -lt 300 ]]; then
# Extract data field from API response
echo "$BODY" | jq -r '.data // .'
exit 0
else
# Error response
ERROR_MSG=$(echo "$BODY" | jq -r '.error // .message // "API request failed"' 2>/dev/null || echo "API request failed")
cat <<EOF
{
"downloaded_packs": [],
"failed_packs": [{
"source": "api",
"error": "API call failed (HTTP $HTTP_CODE): $ERROR_MSG"
}],
"total_count": 0,
"success_count": 0,
"failure_count": 1
}
EOF
exit 1
fi

View File

@@ -0,0 +1,120 @@
# Download Packs Action
# Downloads packs from various sources (git repositories, HTTP archives, or pack registry)
name: download_packs
ref: core.download_packs
description: "Download packs from git repositories, HTTP archives, or pack registry to a temporary directory"
enabled: true
runner_type: shell
entry_point: download_packs.sh
# Parameter delivery: stdin for secure parameter passing (no env vars)
parameter_delivery: stdin
parameter_format: json
# Output format: json (structured data parsing enabled)
output_format: json
# Action parameters schema
parameters:
type: object
properties:
packs:
type: array
description: "List of packs to download (git URLs, HTTP URLs, or pack refs)"
items:
type: string
minItems: 1
destination_dir:
type: string
description: "Destination directory for downloaded packs"
registry_url:
type: string
description: "Pack registry URL for resolving pack refs (optional)"
default: "https://registry.attune.io/index.json"
ref_spec:
type: string
description: "Git reference to checkout (branch, tag, or commit) - applies to all git URLs"
timeout:
type: integer
description: "Download timeout in seconds per pack"
default: 300
minimum: 10
maximum: 3600
verify_ssl:
type: boolean
description: "Verify SSL certificates for HTTPS downloads"
default: true
api_url:
type: string
description: "Attune API URL for making registry lookups"
default: "http://localhost:8080"
required:
- packs
- destination_dir
# Output schema: describes the JSON structure written to stdout
# Note: stdout/stderr/exit_code are captured automatically by the execution system
output_schema:
type: object
properties:
downloaded_packs:
type: array
description: "List of successfully downloaded packs"
items:
type: object
properties:
source:
type: string
description: "Original pack source (URL or ref)"
source_type:
type: string
description: "Type of source"
enum:
- git
- http
- registry
pack_path:
type: string
description: "Local filesystem path to downloaded pack"
pack_ref:
type: string
description: "Pack reference (from pack.yaml)"
pack_version:
type: string
description: "Pack version (from pack.yaml)"
git_commit:
type: string
description: "Git commit hash (for git sources)"
checksum:
type: string
description: "Directory checksum"
failed_packs:
type: array
description: "List of packs that failed to download"
items:
type: object
properties:
source:
type: string
description: "Pack source that failed"
error:
type: string
description: "Error message"
total_count:
type: integer
description: "Total number of packs requested"
success_count:
type: integer
description: "Number of packs successfully downloaded"
failure_count:
type: integer
description: "Number of packs that failed"
# Tags for categorization
tags:
- pack
- download
- git
- installation
- registry

View File

@@ -1,21 +1,42 @@
#!/bin/bash
#!/bin/sh
# Echo Action - Core Pack
# Outputs a message to stdout with optional uppercase conversion
# Outputs a message to stdout
#
# This script uses pure POSIX shell without external dependencies like jq or yq.
# It reads parameters in DOTENV format from stdin until the delimiter.
set -e
# Parse parameters from environment variables
# Attune passes action parameters as environment variables prefixed with ATTUNE_ACTION_
MESSAGE="${ATTUNE_ACTION_MESSAGE:-Hello, World!}"
UPPERCASE="${ATTUNE_ACTION_UPPERCASE:-false}"
# Initialize message variable
message=""
# Convert to uppercase if requested
if [ "$UPPERCASE" = "true" ]; then
MESSAGE=$(echo "$MESSAGE" | tr '[:lower:]' '[:upper:]')
fi
# Read DOTENV-formatted parameters from stdin until delimiter
while IFS= read -r line; do
# Check for parameter delimiter
case "$line" in
*"---ATTUNE_PARAMS_END---"*)
break
;;
message=*)
# Extract value after message=
message="${line#message=}"
# Remove quotes if present (both single and double)
case "$message" in
\"*\")
message="${message#\"}"
message="${message%\"}"
;;
\'*\')
message="${message#\'}"
message="${message%\'}"
;;
esac
;;
esac
done
# Echo the message
echo "$MESSAGE"
# Echo the message (even if empty)
echo "$message"
# Exit successfully
exit 0

View File

@@ -12,37 +12,24 @@ runner_type: shell
# Entry point is the shell command or script to execute
entry_point: echo.sh
# Parameter delivery: stdin for secure parameter passing (no env vars)
parameter_delivery: stdin
parameter_format: dotenv
# Output format: text (no structured data parsing)
output_format: text
# Action parameters schema (standard JSON Schema format)
parameters:
type: object
properties:
message:
type: string
description: "Message to echo"
default: "Hello, World!"
uppercase:
type: boolean
description: "Convert message to uppercase before echoing"
default: false
required:
- message
description: "Message to echo (empty string if not provided)"
required: []
# Output schema
output_schema:
type: object
properties:
stdout:
type: string
description: "Standard output from the echo command"
stderr:
type: string
description: "Standard error output (usually empty)"
exit_code:
type: integer
description: "Exit code of the command (0 = success)"
result:
type: string
description: "The echoed message"
# Output schema: not applicable for text output format
# The action outputs plain text to stdout
# Tags for categorization
tags:

View File

@@ -0,0 +1,77 @@
#!/bin/bash
# Get Pack Dependencies Action - API Wrapper
# Thin wrapper around POST /api/v1/packs/dependencies
set -e
set -o pipefail
# Read JSON parameters from stdin
INPUT=$(cat)
# Parse parameters using jq
PACK_PATHS=$(echo "$INPUT" | jq -c '.pack_paths // []')
SKIP_VALIDATION=$(echo "$INPUT" | jq -r '.skip_validation // false')
API_URL=$(echo "$INPUT" | jq -r '.api_url // "http://localhost:8080"')
API_TOKEN=$(echo "$INPUT" | jq -r '.api_token // ""')
# Validate required parameters
PACK_COUNT=$(echo "$PACK_PATHS" | jq -r 'length' 2>/dev/null || echo "0")
if [[ "$PACK_COUNT" -eq 0 ]]; then
echo '{"dependencies":[],"runtime_requirements":{},"missing_dependencies":[],"analyzed_packs":[],"errors":[{"pack_path":"input","error":"No pack paths provided"}]}' >&1
exit 1
fi
# Build request body
REQUEST_BODY=$(jq -n \
--argjson pack_paths "$PACK_PATHS" \
--argjson skip_validation "$([[ "$SKIP_VALIDATION" == "true" ]] && echo true || echo false)" \
'{
pack_paths: $pack_paths,
skip_validation: $skip_validation
}')
# Make API call
CURL_ARGS=(
-X POST
-H "Content-Type: application/json"
-H "Accept: application/json"
-d "$REQUEST_BODY"
-s
-w "\n%{http_code}"
--max-time 60
--connect-timeout 10
)
if [[ -n "$API_TOKEN" ]] && [[ "$API_TOKEN" != "null" ]]; then
CURL_ARGS+=(-H "Authorization: Bearer ${API_TOKEN}")
fi
RESPONSE=$(curl "${CURL_ARGS[@]}" "${API_URL}/api/v1/packs/dependencies" 2>/dev/null || echo -e "\n000")
# Extract status code (last line)
HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
BODY=$(echo "$RESPONSE" | head -n -1)
# Check HTTP status
if [[ "$HTTP_CODE" -ge 200 ]] && [[ "$HTTP_CODE" -lt 300 ]]; then
# Extract data field from API response
echo "$BODY" | jq -r '.data // .'
exit 0
else
# Error response
ERROR_MSG=$(echo "$BODY" | jq -r '.error // .message // "API request failed"' 2>/dev/null || echo "API request failed")
cat <<EOF
{
"dependencies": [],
"runtime_requirements": {},
"missing_dependencies": [],
"analyzed_packs": [],
"errors": [{
"pack_path": "api",
"error": "API call failed (HTTP $HTTP_CODE): $ERROR_MSG"
}]
}
EOF
exit 1
fi

View File

@@ -0,0 +1,142 @@
# Get Pack Dependencies Action
# Parses pack.yaml files to identify pack and runtime dependencies
name: get_pack_dependencies
ref: core.get_pack_dependencies
description: "Parse pack.yaml files to extract pack dependencies and runtime requirements"
enabled: true
runner_type: shell
entry_point: get_pack_dependencies.sh
# Parameter delivery: stdin for secure parameter passing (no env vars)
parameter_delivery: stdin
parameter_format: json
# Output format: json (structured data parsing enabled)
output_format: json
# Action parameters schema
parameters:
type: object
properties:
pack_paths:
type: array
description: "List of pack directory paths to analyze"
items:
type: string
minItems: 1
skip_validation:
type: boolean
description: "Skip validation of pack.yaml schema"
default: false
api_url:
type: string
description: "Attune API URL for checking installed packs"
default: "http://localhost:8080"
required:
- pack_paths
# Output schema: describes the JSON structure written to stdout
# Note: stdout/stderr/exit_code are captured automatically by the execution system
output_schema:
type: object
properties:
dependencies:
type: array
description: "List of pack dependencies that need to be installed"
items:
type: object
properties:
pack_ref:
type: string
description: "Pack reference (e.g., 'core', 'slack')"
version_spec:
type: string
description: "Version specification (e.g., '>=1.0.0', '^2.1.0')"
required_by:
type: string
description: "Pack that requires this dependency"
already_installed:
type: boolean
description: "Whether this dependency is already installed"
runtime_requirements:
type: object
description: "Runtime environment requirements by pack"
additionalProperties:
type: object
properties:
pack_ref:
type: string
description: "Pack reference"
python:
type: object
description: "Python runtime requirements"
properties:
version:
type: string
description: "Python version requirement"
requirements_file:
type: string
description: "Path to requirements.txt"
nodejs:
type: object
description: "Node.js runtime requirements"
properties:
version:
type: string
description: "Node.js version requirement"
package_file:
type: string
description: "Path to package.json"
missing_dependencies:
type: array
description: "Pack dependencies that are not yet installed"
items:
type: object
properties:
pack_ref:
type: string
description: "Pack reference"
version_spec:
type: string
description: "Version specification"
required_by:
type: string
description: "Pack that requires this dependency"
analyzed_packs:
type: array
description: "List of packs that were analyzed"
items:
type: object
properties:
pack_ref:
type: string
description: "Pack reference"
pack_path:
type: string
description: "Path to pack directory"
has_dependencies:
type: boolean
description: "Whether pack has dependencies"
dependency_count:
type: integer
description: "Number of dependencies"
errors:
type: array
description: "Errors encountered during analysis"
items:
type: object
properties:
pack_path:
type: string
description: "Pack path where error occurred"
error:
type: string
description: "Error message"
# Tags for categorization
tags:
- pack
- dependencies
- validation
- installation

View File

@@ -1,206 +0,0 @@
#!/usr/bin/env python3
"""
HTTP Request Action - Core Pack
Make HTTP requests to external APIs with support for various methods, headers, and authentication
"""
import json
import os
import sys
import time
from typing import Any, Dict, Optional
try:
import requests
from requests.auth import HTTPBasicAuth
except ImportError:
print(
"ERROR: requests library not installed. Run: pip install requests>=2.28.0",
file=sys.stderr,
)
sys.exit(1)
def get_env_param(name: str, default: Any = None, required: bool = False) -> Any:
"""Get action parameter from environment variable."""
env_key = f"ATTUNE_ACTION_{name.upper()}"
value = os.environ.get(env_key, default)
if required and value is None:
raise ValueError(f"Required parameter '{name}' not provided")
return value
def parse_json_param(name: str, default: Any = None) -> Any:
"""Parse JSON parameter from environment variable."""
value = get_env_param(name)
if value is None:
return default
try:
return json.loads(value)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON for parameter '{name}': {e}")
def parse_bool_param(name: str, default: bool = False) -> bool:
"""Parse boolean parameter from environment variable."""
value = get_env_param(name)
if value is None:
return default
if isinstance(value, bool):
return value
return str(value).lower() in ("true", "1", "yes", "on")
def parse_int_param(name: str, default: int = 0) -> int:
"""Parse integer parameter from environment variable."""
value = get_env_param(name)
if value is None:
return default
try:
return int(value)
except (ValueError, TypeError):
raise ValueError(f"Invalid integer for parameter '{name}': {value}")
def make_http_request() -> Dict[str, Any]:
"""Execute HTTP request with provided parameters."""
# Parse required parameters
url = get_env_param("url", required=True)
# Parse optional parameters
method = get_env_param("method", "GET").upper()
headers = parse_json_param("headers", {})
body = get_env_param("body")
json_body = parse_json_param("json_body")
query_params = parse_json_param("query_params", {})
timeout = parse_int_param("timeout", 30)
verify_ssl = parse_bool_param("verify_ssl", True)
auth_type = get_env_param("auth_type", "none")
follow_redirects = parse_bool_param("follow_redirects", True)
max_redirects = parse_int_param("max_redirects", 10)
# Prepare request kwargs
request_kwargs = {
"method": method,
"url": url,
"headers": headers,
"params": query_params,
"timeout": timeout,
"verify": verify_ssl,
"allow_redirects": follow_redirects,
}
# Handle authentication
if auth_type == "basic":
username = get_env_param("auth_username")
password = get_env_param("auth_password")
if username and password:
request_kwargs["auth"] = HTTPBasicAuth(username, password)
elif auth_type == "bearer":
token = get_env_param("auth_token")
if token:
request_kwargs["headers"]["Authorization"] = f"Bearer {token}"
# Handle request body
if json_body is not None:
request_kwargs["json"] = json_body
elif body is not None:
request_kwargs["data"] = body
# Make the request
start_time = time.time()
try:
response = requests.request(**request_kwargs)
elapsed_ms = int((time.time() - start_time) * 1000)
# Parse response
result = {
"status_code": response.status_code,
"headers": dict(response.headers),
"body": response.text,
"elapsed_ms": elapsed_ms,
"url": response.url,
"success": 200 <= response.status_code < 300,
}
# Try to parse JSON response
try:
result["json"] = response.json()
except (json.JSONDecodeError, ValueError):
result["json"] = None
return result
except requests.exceptions.Timeout:
return {
"status_code": 0,
"headers": {},
"body": "",
"json": None,
"elapsed_ms": int((time.time() - start_time) * 1000),
"url": url,
"success": False,
"error": "Request timeout",
}
except requests.exceptions.ConnectionError as e:
return {
"status_code": 0,
"headers": {},
"body": "",
"json": None,
"elapsed_ms": int((time.time() - start_time) * 1000),
"url": url,
"success": False,
"error": f"Connection error: {str(e)}",
}
except requests.exceptions.RequestException as e:
return {
"status_code": 0,
"headers": {},
"body": "",
"json": None,
"elapsed_ms": int((time.time() - start_time) * 1000),
"url": url,
"success": False,
"error": f"Request error: {str(e)}",
}
def main():
"""Main entry point for the action."""
try:
result = make_http_request()
# Output result as JSON
print(json.dumps(result, indent=2))
# Exit with success/failure based on HTTP status
if result.get("success", False):
sys.exit(0)
else:
# Non-2xx status code or error
error = result.get("error")
if error:
print(f"ERROR: {error}", file=sys.stderr)
else:
print(
f"ERROR: HTTP request failed with status {result.get('status_code')}",
file=sys.stderr,
)
sys.exit(1)
except Exception as e:
print(f"ERROR: {str(e)}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,209 @@
#!/bin/bash
# HTTP Request Action - Core Pack
# Make HTTP requests to external APIs using curl
set -e
set -o pipefail
# Read JSON parameters from stdin
INPUT=$(cat)
# Parse required parameters
URL=$(echo "$INPUT" | jq -r '.url // ""')
if [ -z "$URL" ] || [ "$URL" = "null" ]; then
echo "ERROR: 'url' parameter is required" >&2
exit 1
fi
# Parse optional parameters
METHOD=$(echo "$INPUT" | jq -r '.method // "GET"' | tr '[:lower:]' '[:upper:]')
HEADERS=$(echo "$INPUT" | jq -r '.headers // {}')
BODY=$(echo "$INPUT" | jq -r '.body // ""')
JSON_BODY=$(echo "$INPUT" | jq -c '.json_body // null')
QUERY_PARAMS=$(echo "$INPUT" | jq -r '.query_params // {}')
TIMEOUT=$(echo "$INPUT" | jq -r '.timeout // 30')
VERIFY_SSL=$(echo "$INPUT" | jq -r '.verify_ssl // true')
AUTH_TYPE=$(echo "$INPUT" | jq -r '.auth_type // "none"')
FOLLOW_REDIRECTS=$(echo "$INPUT" | jq -r '.follow_redirects // true')
MAX_REDIRECTS=$(echo "$INPUT" | jq -r '.max_redirects // 10')
# Build URL with query parameters
FINAL_URL="$URL"
if [ "$QUERY_PARAMS" != "{}" ] && [ "$QUERY_PARAMS" != "null" ]; then
QUERY_STRING=$(echo "$QUERY_PARAMS" | jq -r 'to_entries | map("\(.key)=\(.value | @uri)") | join("&")')
if [[ "$FINAL_URL" == *"?"* ]]; then
FINAL_URL="${FINAL_URL}&${QUERY_STRING}"
else
FINAL_URL="${FINAL_URL}?${QUERY_STRING}"
fi
fi
# Build curl arguments array
CURL_ARGS=(
-X "$METHOD"
-s # Silent mode
-w "\n%{http_code}\n%{time_total}\n%{url_effective}\n" # Write out metadata
--max-time "$TIMEOUT"
--connect-timeout 10
)
# Handle SSL verification
if [ "$VERIFY_SSL" = "false" ]; then
CURL_ARGS+=(-k)
fi
# Handle redirects
if [ "$FOLLOW_REDIRECTS" = "true" ]; then
CURL_ARGS+=(-L --max-redirs "$MAX_REDIRECTS")
fi
# Add headers
if [ "$HEADERS" != "{}" ] && [ "$HEADERS" != "null" ]; then
while IFS= read -r header; do
if [ -n "$header" ]; then
CURL_ARGS+=(-H "$header")
fi
done < <(echo "$HEADERS" | jq -r 'to_entries | map("\(.key): \(.value)") | .[]')
fi
# Handle authentication
case "$AUTH_TYPE" in
basic)
AUTH_USERNAME=$(echo "$INPUT" | jq -r '.auth_username // ""')
AUTH_PASSWORD=$(echo "$INPUT" | jq -r '.auth_password // ""')
if [ -n "$AUTH_USERNAME" ] && [ "$AUTH_USERNAME" != "null" ]; then
CURL_ARGS+=(-u "${AUTH_USERNAME}:${AUTH_PASSWORD}")
fi
;;
bearer)
AUTH_TOKEN=$(echo "$INPUT" | jq -r '.auth_token // ""')
if [ -n "$AUTH_TOKEN" ] && [ "$AUTH_TOKEN" != "null" ]; then
CURL_ARGS+=(-H "Authorization: Bearer ${AUTH_TOKEN}")
fi
;;
esac
# Handle request body
if [ "$JSON_BODY" != "null" ] && [ "$JSON_BODY" != "" ]; then
CURL_ARGS+=(-H "Content-Type: application/json")
CURL_ARGS+=(-d "$JSON_BODY")
elif [ -n "$BODY" ] && [ "$BODY" != "null" ]; then
CURL_ARGS+=(-d "$BODY")
fi
# Capture start time
START_TIME=$(date +%s%3N)
# Make the request and capture response headers
TEMP_HEADERS=$(mktemp)
CURL_ARGS+=(--dump-header "$TEMP_HEADERS")
# Execute curl and capture output
set +e
RESPONSE=$(curl "${CURL_ARGS[@]}" "$FINAL_URL" 2>&1)
CURL_EXIT_CODE=$?
set -e
# Calculate elapsed time
END_TIME=$(date +%s%3N)
ELAPSED_MS=$((END_TIME - START_TIME))
# Parse curl output (last 3 lines are: http_code, time_total, url_effective)
BODY_OUTPUT=$(echo "$RESPONSE" | head -n -3)
HTTP_CODE=$(echo "$RESPONSE" | tail -n 3 | head -n 1 | tr -d '\r\n')
CURL_TIME=$(echo "$RESPONSE" | tail -n 2 | head -n 1 | tr -d '\r\n')
EFFECTIVE_URL=$(echo "$RESPONSE" | tail -n 1 | tr -d '\r\n')
# Ensure HTTP_CODE is numeric, default to 0 if not
if ! [[ "$HTTP_CODE" =~ ^[0-9]+$ ]]; then
HTTP_CODE=0
fi
# If curl failed, handle error
if [ "$CURL_EXIT_CODE" -ne 0 ]; then
ERROR_MSG="curl failed with exit code $CURL_EXIT_CODE"
# Determine specific error
case $CURL_EXIT_CODE in
6) ERROR_MSG="Could not resolve host" ;;
7) ERROR_MSG="Failed to connect to host" ;;
28) ERROR_MSG="Request timeout" ;;
35) ERROR_MSG="SSL/TLS connection error" ;;
52) ERROR_MSG="Empty reply from server" ;;
56) ERROR_MSG="Failure receiving network data" ;;
*) ERROR_MSG="curl error code $CURL_EXIT_CODE" ;;
esac
# Output error result as JSON
jq -n \
--arg error "$ERROR_MSG" \
--argjson elapsed "$ELAPSED_MS" \
--arg url "$FINAL_URL" \
'{
status_code: 0,
headers: {},
body: "",
json: null,
elapsed_ms: $elapsed,
url: $url,
success: false,
error: $error
}'
rm -f "$TEMP_HEADERS"
exit 1
fi
# Parse response headers into JSON
HEADERS_JSON="{}"
if [ -f "$TEMP_HEADERS" ]; then
# Skip the status line and parse headers
HEADERS_JSON=$(grep -v "^HTTP/" "$TEMP_HEADERS" | grep ":" | sed 's/\r$//' | jq -R -s -c '
split("\n") |
map(select(length > 0)) |
map(split(": "; "") | select(length > 1) | {key: .[0], value: (.[1:] | join(": "))}) |
map({(.key): .value}) |
add // {}
' || echo '{}')
rm -f "$TEMP_HEADERS"
fi
# Ensure HEADERS_JSON is valid JSON
if ! echo "$HEADERS_JSON" | jq empty 2>/dev/null; then
HEADERS_JSON="{}"
fi
# Determine if successful (2xx status code)
SUCCESS=false
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
SUCCESS=true
fi
# Try to parse body as JSON
JSON_PARSED="null"
if [ -n "$BODY_OUTPUT" ] && echo "$BODY_OUTPUT" | jq empty 2>/dev/null; then
JSON_PARSED=$(echo "$BODY_OUTPUT" | jq -c '.' || echo 'null')
fi
# Output result as JSON
jq -n \
--argjson status_code "$HTTP_CODE" \
--argjson headers "$HEADERS_JSON" \
--arg body "$BODY_OUTPUT" \
--argjson json "$JSON_PARSED" \
--argjson elapsed "$ELAPSED_MS" \
--arg url "$EFFECTIVE_URL" \
--argjson success "$SUCCESS" \
'{
status_code: $status_code,
headers: $headers,
body: $body,
json: $json,
elapsed_ms: $elapsed,
url: $url,
success: $success
}'
# Exit with success
exit 0

View File

@@ -7,10 +7,18 @@ description: "Make HTTP requests to external APIs with support for various metho
enabled: true
# Runner type determines how the action is executed
runner_type: python
runner_type: shell
# Entry point is the Python script to execute
entry_point: http_request.py
# Entry point is the bash script to execute
entry_point: http_request.sh
# Parameter delivery configuration (for security)
# Use stdin + JSON for secure parameter passing (credentials won't appear in process list)
parameter_delivery: stdin
parameter_format: json
# Output format: json (structured data parsing enabled)
output_format: json
# Action parameters schema (standard JSON Schema format)
parameters:
@@ -84,7 +92,8 @@ parameters:
required:
- url
# Output schema
# Output schema: describes the JSON structure written to stdout
# Note: stdout/stderr/exit_code are captured automatically by the execution system
output_schema:
type: object
properties:
@@ -99,7 +108,7 @@ output_schema:
description: "Response body as text"
json:
type: object
description: "Parsed JSON response (if applicable)"
description: "Parsed JSON response (if applicable, null otherwise)"
elapsed_ms:
type: integer
description: "Request duration in milliseconds"
@@ -109,6 +118,9 @@ output_schema:
success:
type: boolean
description: "Whether the request was successful (2xx status code)"
error:
type: string
description: "Error message if request failed (only present on failure)"
# Tags for categorization
tags:

View File

@@ -1,31 +1,77 @@
#!/bin/bash
#!/bin/sh
# No Operation Action - Core Pack
# Does nothing - useful for testing and placeholder workflows
#
# This script uses pure POSIX shell without external dependencies like jq or yq.
# It reads parameters in DOTENV format from stdin until the delimiter.
set -e
# Parse parameters from environment variables
MESSAGE="${ATTUNE_ACTION_MESSAGE:-}"
EXIT_CODE="${ATTUNE_ACTION_EXIT_CODE:-0}"
# Initialize variables
message=""
exit_code="0"
# Validate exit code parameter
if ! [[ "$EXIT_CODE" =~ ^[0-9]+$ ]]; then
echo "ERROR: exit_code must be a positive integer" >&2
exit 1
fi
# Read DOTENV-formatted parameters from stdin until delimiter
while IFS= read -r line; do
# Check for parameter delimiter
case "$line" in
*"---ATTUNE_PARAMS_END---"*)
break
;;
message=*)
# Extract value after message=
message="${line#message=}"
# Remove quotes if present (both single and double)
case "$message" in
\"*\")
message="${message#\"}"
message="${message%\"}"
;;
\'*\')
message="${message#\'}"
message="${message%\'}"
;;
esac
;;
exit_code=*)
# Extract value after exit_code=
exit_code="${line#exit_code=}"
# Remove quotes if present
case "$exit_code" in
\"*\")
exit_code="${exit_code#\"}"
exit_code="${exit_code%\"}"
;;
\'*\')
exit_code="${exit_code#\'}"
exit_code="${exit_code%\'}"
;;
esac
;;
esac
done
if [ "$EXIT_CODE" -lt 0 ] || [ "$EXIT_CODE" -gt 255 ]; then
# Validate exit code parameter (must be numeric)
case "$exit_code" in
''|*[!0-9]*)
echo "ERROR: exit_code must be a positive integer" >&2
exit 1
;;
esac
# Validate exit code range (0-255)
if [ "$exit_code" -lt 0 ] || [ "$exit_code" -gt 255 ]; then
echo "ERROR: exit_code must be between 0 and 255" >&2
exit 1
fi
# Log message if provided
if [ -n "$MESSAGE" ]; then
echo "[NOOP] $MESSAGE"
if [ -n "$message" ]; then
echo "[NOOP] $message"
fi
# Output result
echo "No operation completed successfully"
# Exit with specified code
exit "$EXIT_CODE"
exit "$exit_code"

View File

@@ -12,6 +12,13 @@ runner_type: shell
# Entry point is the shell command or script to execute
entry_point: noop.sh
# Parameter delivery: stdin for secure parameter passing (no env vars)
parameter_delivery: stdin
parameter_format: dotenv
# Output format: text (no structured data parsing)
output_format: text
# Action parameters schema (standard JSON Schema format)
parameters:
type: object
@@ -27,22 +34,8 @@ parameters:
maximum: 255
required: []
# Output schema
output_schema:
type: object
properties:
stdout:
type: string
description: "Standard output (empty unless message provided)"
stderr:
type: string
description: "Standard error output (usually empty)"
exit_code:
type: integer
description: "Exit code of the command"
result:
type: string
description: "Operation result"
# Output schema: not applicable for text output format
# The action outputs plain text to stdout
# Tags for categorization
tags:

View File

@@ -0,0 +1,92 @@
#!/bin/bash
# Register Packs Action - API Wrapper
# Thin wrapper around POST /api/v1/packs/register-batch
set -e
set -o pipefail
# Read JSON parameters from stdin
INPUT=$(cat)
# Parse parameters using jq
PACK_PATHS=$(echo "$INPUT" | jq -c '.pack_paths // []')
PACKS_BASE_DIR=$(echo "$INPUT" | jq -r '.packs_base_dir // "/opt/attune/packs"')
SKIP_VALIDATION=$(echo "$INPUT" | jq -r '.skip_validation // false')
SKIP_TESTS=$(echo "$INPUT" | jq -r '.skip_tests // false')
FORCE=$(echo "$INPUT" | jq -r '.force // false')
API_URL=$(echo "$INPUT" | jq -r '.api_url // "http://localhost:8080"')
API_TOKEN=$(echo "$INPUT" | jq -r '.api_token // ""')
# Validate required parameters
PACK_COUNT=$(echo "$PACK_PATHS" | jq -r 'length' 2>/dev/null || echo "0")
if [[ "$PACK_COUNT" -eq 0 ]]; then
echo '{"registered_packs":[],"failed_packs":[{"pack_ref":"input","pack_path":"","error":"No pack paths provided","error_stage":"input_validation"}],"summary":{"total_packs":0,"success_count":0,"failure_count":1,"total_components":0,"duration_ms":0}}' >&1
exit 1
fi
# Build request body
REQUEST_BODY=$(jq -n \
--argjson pack_paths "$PACK_PATHS" \
--arg packs_base_dir "$PACKS_BASE_DIR" \
--argjson skip_validation "$([[ "$SKIP_VALIDATION" == "true" ]] && echo true || echo false)" \
--argjson skip_tests "$([[ "$SKIP_TESTS" == "true" ]] && echo true || echo false)" \
--argjson force "$([[ "$FORCE" == "true" ]] && echo true || echo false)" \
'{
pack_paths: $pack_paths,
packs_base_dir: $packs_base_dir,
skip_validation: $skip_validation,
skip_tests: $skip_tests,
force: $force
}')
# Make API call
CURL_ARGS=(
-X POST
-H "Content-Type: application/json"
-H "Accept: application/json"
-d "$REQUEST_BODY"
-s
-w "\n%{http_code}"
--max-time 300
--connect-timeout 10
)
if [[ -n "$API_TOKEN" ]] && [[ "$API_TOKEN" != "null" ]]; then
CURL_ARGS+=(-H "Authorization: Bearer ${API_TOKEN}")
fi
RESPONSE=$(curl "${CURL_ARGS[@]}" "${API_URL}/api/v1/packs/register-batch" 2>/dev/null || echo -e "\n000")
# Extract status code (last line)
HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
BODY=$(echo "$RESPONSE" | head -n -1)
# Check HTTP status
if [[ "$HTTP_CODE" -ge 200 ]] && [[ "$HTTP_CODE" -lt 300 ]]; then
# Extract data field from API response
echo "$BODY" | jq -r '.data // .'
exit 0
else
# Error response
ERROR_MSG=$(echo "$BODY" | jq -r '.error // .message // "API request failed"' 2>/dev/null || echo "API request failed")
cat <<EOF
{
"registered_packs": [],
"failed_packs": [{
"pack_ref": "api",
"pack_path": "",
"error": "API call failed (HTTP $HTTP_CODE): $ERROR_MSG",
"error_stage": "api_call"
}],
"summary": {
"total_packs": 0,
"success_count": 0,
"failure_count": 1,
"total_components": 0,
"duration_ms": 0
}
}
EOF
exit 1
fi

View File

@@ -0,0 +1,192 @@
# Register Packs Action
# Validates pack structure and loads components into database
name: register_packs
ref: core.register_packs
description: "Register packs by validating schemas, loading components into database, and copying to permanent storage"
enabled: true
runner_type: shell
entry_point: register_packs.sh
# Parameter delivery: stdin for secure parameter passing (no env vars)
parameter_delivery: stdin
parameter_format: json
# Output format: json (structured data parsing enabled)
output_format: json
# Action parameters schema
parameters:
type: object
properties:
pack_paths:
type: array
description: "List of pack directory paths to register"
items:
type: string
minItems: 1
packs_base_dir:
type: string
description: "Base directory where packs are permanently stored"
default: "/opt/attune/packs"
skip_validation:
type: boolean
description: "Skip schema validation of pack components"
default: false
skip_tests:
type: boolean
description: "Skip running pack tests before registration"
default: false
force:
type: boolean
description: "Force registration even if pack already exists (will replace)"
default: false
api_url:
type: string
description: "Attune API URL for registration calls"
default: "http://localhost:8080"
api_token:
type: string
description: "API authentication token"
secret: true
required:
- pack_paths
# Output schema: describes the JSON structure written to stdout
# Note: stdout/stderr/exit_code are captured automatically by the execution system
output_schema:
type: object
properties:
registered_packs:
type: array
description: "List of successfully registered packs"
items:
type: object
properties:
pack_ref:
type: string
description: "Pack reference"
pack_id:
type: integer
description: "Database ID of registered pack"
pack_version:
type: string
description: "Pack version"
storage_path:
type: string
description: "Permanent storage path"
components_registered:
type: object
description: "Count of registered components by type"
properties:
actions:
type: integer
description: "Number of actions registered"
sensors:
type: integer
description: "Number of sensors registered"
triggers:
type: integer
description: "Number of triggers registered"
rules:
type: integer
description: "Number of rules registered"
workflows:
type: integer
description: "Number of workflows registered"
policies:
type: integer
description: "Number of policies registered"
test_result:
type: object
description: "Pack test results (if tests were run)"
properties:
status:
type: string
description: "Test status"
enum:
- passed
- failed
- skipped
total_tests:
type: integer
description: "Total number of tests"
passed:
type: integer
description: "Number of passed tests"
failed:
type: integer
description: "Number of failed tests"
validation_results:
type: object
description: "Component validation results"
properties:
valid:
type: boolean
description: "Whether all components are valid"
errors:
type: array
description: "Validation errors found"
items:
type: object
properties:
component_type:
type: string
description: "Type of component"
component_file:
type: string
description: "File with validation error"
error:
type: string
description: "Error message"
failed_packs:
type: array
description: "List of packs that failed to register"
items:
type: object
properties:
pack_ref:
type: string
description: "Pack reference"
pack_path:
type: string
description: "Pack directory path"
error:
type: string
description: "Error message"
error_stage:
type: string
description: "Stage where error occurred"
enum:
- validation
- testing
- database_registration
- file_copy
- api_call
summary:
type: object
description: "Summary of registration process"
properties:
total_packs:
type: integer
description: "Total number of packs processed"
success_count:
type: integer
description: "Number of successfully registered packs"
failure_count:
type: integer
description: "Number of failed registrations"
total_components:
type: integer
description: "Total number of components registered"
duration_ms:
type: integer
description: "Total registration time in milliseconds"
# Tags for categorization
tags:
- pack
- registration
- validation
- installation
- database

View File

@@ -1,34 +1,80 @@
#!/bin/bash
#!/bin/sh
# Sleep Action - Core Pack
# Pauses execution for a specified duration
#
# This script uses pure POSIX shell without external dependencies like jq or yq.
# It reads parameters in DOTENV format from stdin until the delimiter.
set -e
# Parse parameters from environment variables
SLEEP_SECONDS="${ATTUNE_ACTION_SECONDS:-1}"
MESSAGE="${ATTUNE_ACTION_MESSAGE:-}"
# Initialize variables
seconds="1"
message=""
# Validate seconds parameter
if ! [[ "$SLEEP_SECONDS" =~ ^[0-9]+$ ]]; then
echo "ERROR: seconds must be a positive integer" >&2
exit 1
fi
# Read DOTENV-formatted parameters from stdin until delimiter
while IFS= read -r line; do
# Check for parameter delimiter
case "$line" in
*"---ATTUNE_PARAMS_END---"*)
break
;;
seconds=*)
# Extract value after seconds=
seconds="${line#seconds=}"
# Remove quotes if present (both single and double)
case "$seconds" in
\"*\")
seconds="${seconds#\"}"
seconds="${seconds%\"}"
;;
\'*\')
seconds="${seconds#\'}"
seconds="${seconds%\'}"
;;
esac
;;
message=*)
# Extract value after message=
message="${line#message=}"
# Remove quotes if present
case "$message" in
\"*\")
message="${message#\"}"
message="${message%\"}"
;;
\'*\')
message="${message#\'}"
message="${message%\'}"
;;
esac
;;
esac
done
if [ "$SLEEP_SECONDS" -lt 0 ] || [ "$SLEEP_SECONDS" -gt 3600 ]; then
# Validate seconds parameter (must be numeric)
case "$seconds" in
''|*[!0-9]*)
echo "ERROR: seconds must be a positive integer" >&2
exit 1
;;
esac
# Validate seconds range (0-3600)
if [ "$seconds" -lt 0 ] || [ "$seconds" -gt 3600 ]; then
echo "ERROR: seconds must be between 0 and 3600" >&2
exit 1
fi
# Display message if provided
if [ -n "$MESSAGE" ]; then
echo "$MESSAGE"
if [ -n "$message" ]; then
echo "$message"
fi
# Sleep for the specified duration
sleep "$SLEEP_SECONDS"
sleep "$seconds"
# Output result
echo "Slept for $SLEEP_SECONDS seconds"
echo "Slept for $seconds seconds"
# Exit successfully
exit 0

View File

@@ -12,6 +12,13 @@ runner_type: shell
# Entry point is the shell command or script to execute
entry_point: sleep.sh
# Parameter delivery: stdin for secure parameter passing (no env vars)
parameter_delivery: stdin
parameter_format: dotenv
# Output format: text (no structured data parsing)
output_format: text
# Action parameters schema (standard JSON Schema format)
parameters:
type: object
@@ -28,22 +35,8 @@ parameters:
required:
- seconds
# Output schema
output_schema:
type: object
properties:
stdout:
type: string
description: "Standard output (empty unless message provided)"
stderr:
type: string
description: "Standard error output (usually empty)"
exit_code:
type: integer
description: "Exit code of the command (0 = success)"
duration:
type: integer
description: "Number of seconds slept"
# Output schema: not applicable for text output format
# The action outputs plain text to stdout
# Tags for categorization
tags: