distributable, please
Some checks failed
CI / Rustfmt (push) Successful in 22s
CI / Cargo Audit & Deny (push) Successful in 36s
CI / Security Blocking Checks (push) Successful in 6s
CI / Web Blocking Checks (push) Successful in 53s
CI / Web Advisory Checks (push) Successful in 34s
Publish Images / Resolve Publish Metadata (push) Successful in 1s
CI / Security Advisory Checks (push) Successful in 38s
CI / Clippy (push) Successful in 2m7s
Publish Images / Publish Docker Dist Bundle (push) Failing after 19s
Publish Images / Publish web (amd64) (push) Successful in 49s
Publish Images / Publish web (arm64) (push) Successful in 3m31s
CI / Tests (push) Successful in 8m48s
Publish Images / Build Rust Bundles (amd64) (push) Successful in 12m42s
Publish Images / Build Rust Bundles (arm64) (push) Successful in 12m19s
Publish Images / Publish agent (amd64) (push) Successful in 26s
Publish Images / Publish api (amd64) (push) Successful in 38s
Publish Images / Publish notifier (amd64) (push) Successful in 42s
Publish Images / Publish executor (amd64) (push) Successful in 46s
Publish Images / Publish agent (arm64) (push) Successful in 56s
Publish Images / Publish api (arm64) (push) Successful in 1m52s
Publish Images / Publish executor (arm64) (push) Successful in 2m2s
Publish Images / Publish notifier (arm64) (push) Successful in 2m3s
Publish Images / Publish manifest attune/agent (push) Successful in 6s
Publish Images / Publish manifest attune/api (push) Successful in 11s
Publish Images / Publish manifest attune/executor (push) Successful in 10s
Publish Images / Publish manifest attune/notifier (push) Successful in 8s
Publish Images / Publish manifest attune/web (push) Successful in 8s

This commit is contained in:
2026-03-26 12:26:23 -05:00
parent da8055cb79
commit 938c271ff5
72 changed files with 14798 additions and 6 deletions

View File

@@ -0,0 +1,362 @@
# Core Pack Actions
## Overview
All actions in the core pack are implemented as **pure POSIX shell scripts** with **zero external dependencies** (except `curl` for HTTP actions). This design ensures maximum portability and minimal runtime requirements.
**Key Principles:**
- **POSIX shell only** - No bash-specific features, works everywhere
- **DOTENV parameter format** - Simple key=value format, no JSON parsing needed
- **No jq/yq/Python/Node.js** - Core pack depends only on standard POSIX utilities
- **Stdin parameter delivery** - Secure, never exposed in process list
- **Explicit output formats** - text, json, or yaml
## Parameter Delivery Method
**All actions use stdin with DOTENV format:**
- Parameters read from **stdin** in `key=value` format
- Use `parameter_delivery: stdin` and `parameter_format: dotenv` in YAML
- Stdin is closed after delivery; scripts read until EOF
- **DO NOT** use environment variables for parameters
**Example DOTENV input:**
```
message="Hello World"
seconds=5
enabled=true
```
## 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
### 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)
Environment variables should **NEVER** be used for:
- Action parameters (use stdin DOTENV instead)
- Secrets or credentials (use `ATTUNE_API_TOKEN` to fetch from key vault)
- User-provided data (use stdin parameters)
## Implementation Pattern
### POSIX Shell Actions (Standard Pattern)
All core pack actions follow this pattern:
```sh
#!/bin/sh
# Action Name - Core Pack
# Brief description
#
# This script uses pure POSIX shell without external dependencies like jq.
# It reads parameters in DOTENV format from stdin until EOF.
set -e
# Initialize variables with defaults
param1=""
param2="default_value"
# Read DOTENV-formatted parameters from stdin until EOF
while IFS= read -r line; do
[ -z "$line" ] && continue
key="${line%%=*}"
value="${line#*=}"
# Remove quotes if present
case "$value" in
\"*\") value="${value#\"}"; value="${value%\"}" ;;
\'*\') value="${value#\'}"; value="${value%\'}" ;;
esac
# Process parameters
case "$key" in
param1) param1="$value" ;;
param2) param2="$value" ;;
esac
done
# Validate required parameters
if [ -z "$param1" ]; then
echo "ERROR: param1 is required" >&2
exit 1
fi
# Action logic
echo "Processing: $param1"
exit 0
```
### Boolean Normalization
```sh
case "$bool_param" in
true|True|TRUE|yes|Yes|YES|1) bool_param="true" ;;
*) bool_param="false" ;;
esac
```
### Numeric Validation
```sh
case "$number" in
''|*[!0-9]*)
echo "ERROR: must be a number" >&2
exit 1
;;
esac
```
## Core Pack Actions
### Simple Actions
1. **echo.sh** - Outputs a message (reference implementation)
2. **sleep.sh** - Pauses execution for a specified duration
3. **noop.sh** - Does nothing (useful for testing and placeholder workflows)
### HTTP Action
4. **http_request.sh** - Makes HTTP requests with full feature support:
- Multiple HTTP methods (GET, POST, PUT, PATCH, DELETE, etc.)
- Custom headers and query parameters
- Authentication (basic, bearer token)
- SSL verification control
- Redirect following
- JSON output with parsed response
### Pack Management Actions (API Wrappers)
These actions wrap Attune API endpoints for pack management:
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
All API wrappers:
- Accept parameters via DOTENV format
- Build JSON request bodies manually (no jq)
- Make authenticated API calls with curl
- Extract response data using simple sed patterns
- Return structured JSON output
## Testing Actions Locally
Test actions by echoing DOTENV format to stdin:
```bash
# Test echo action
printf 'message="Hello World"\n' | ./echo.sh
# Test with empty parameters
printf '' | ./echo.sh
# Test sleep action
printf 'seconds=2\nmessage="Sleeping..."\n' | ./sleep.sh
# Test http_request action
printf 'url="https://api.github.com"\nmethod="GET"\n' | ./http_request.sh
# Test with file input
cat params.dotenv | ./echo.sh
```
## YAML Configuration Example
```yaml
ref: core.example_action
label: "Example Action"
description: "Example action demonstrating DOTENV format"
enabled: true
runner_type: shell
entry_point: example.sh
# IMPORTANT: Use DOTENV format for POSIX shell compatibility
parameter_delivery: stdin
parameter_format: dotenv
# Output format: text, json, or yaml
output_format: text
parameters:
type: object
properties:
message:
type: string
description: "Message to output"
default: ""
count:
type: integer
description: "Number of times to repeat"
default: 1
required:
- message
```
## Dependencies
**Core pack has ZERO runtime dependencies:**
**Required (universally available):**
- POSIX-compliant shell (`/bin/sh`)
- `curl` (for HTTP actions only)
- Standard POSIX utilities: `sed`, `mktemp`, `cat`, `printf`, `sleep`
**NOT Required:**
- `jq` - Eliminated (was used for JSON parsing)
- `yq` - Never used
- Python - Not used in core pack actions
- Node.js - Not used in core pack actions
- bash - Scripts are POSIX-compliant
- Any other external tools or libraries
This makes the core pack **maximally portable** and suitable for minimal containers (Alpine, distroless, etc.).
## 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
5. **Minimal attack surface** - No external dependencies to exploit
## Best Practices
### Parameters
1. **Always use stdin with DOTENV format** for action parameters
2. **Handle quoted values** - Remove both single and double quotes
3. **Provide sensible defaults** - Use empty string, 0, false as appropriate
4. **Validate required params** - Exit with error if truly required parameters missing
5. **Mark secrets** - Use `secret: true` in YAML for sensitive parameters
6. **Never use env vars for parameters** - Parameters come from stdin only
### 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. **Never log ATTUNE_API_TOKEN** - Security sensitive
5. **Use env vars for runtime config only** - 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. **Define schema for structured output** - Only for json/yaml formats
5. **Use stderr for diagnostics** - Error messages go to stderr, not stdout
6. **Return proper exit codes** - 0 for success, non-zero for failure
### Shell Script Best Practices
1. **Use `#!/bin/sh`** - POSIX shell, not bash
2. **Use `set -e`** - Exit on error
3. **Quote all variables** - `"$var"` not `$var`
4. **Use `case` not `if`** - More portable for pattern matching
5. **Clean up temp files** - Use trap handlers
6. **Avoid bash-isms** - No `[[`, `${var^^}`, `=~`, arrays, etc.
## 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: Complete Action
```sh
#!/bin/sh
# Example Action - Core Pack
# Demonstrates DOTENV parameter parsing and environment variable usage
#
# This script uses pure POSIX shell without external dependencies like jq.
set -e
# Log execution start
echo "[$ATTUNE_ACTION] [Exec: $ATTUNE_EXEC_ID] Starting" >&2
# Initialize variables
url=""
timeout="30"
# Read DOTENV parameters from stdin until EOF
while IFS= read -r line; do
[ -z "$line" ] && continue
key="${line%%=*}"
value="${line#*=}"
case "$value" in
\"*\") value="${value#\"}"; value="${value%\"}" ;;
esac
case "$key" in
url) url="$value" ;;
timeout) timeout="$value" ;;
esac
done
# Validate
if [ -z "$url" ]; then
echo "ERROR: url is required" >&2
exit 1
fi
# Execute
echo "Fetching: $url" >&2
result=$(curl -s --max-time "$timeout" "$url")
# Output
echo "$result"
echo "[$ATTUNE_ACTION] [Exec: $ATTUNE_EXEC_ID] Completed" >&2
exit 0
```
## Further Documentation
- **Pattern Reference:** `docs/QUICKREF-dotenv-shell-actions.md`
- **Pack Structure:** `docs/pack-structure.md`
- **Example Actions:**
- `echo.sh` - Simplest reference implementation
- `http_request.sh` - Complex action with full HTTP client
- `register_packs.sh` - API wrapper with JSON construction

View File

@@ -0,0 +1,215 @@
#!/bin/sh
# Build Pack Environments Action - Core Pack
# API Wrapper for POST /api/v1/packs/build-envs
#
# This script uses pure POSIX shell without external dependencies like jq.
# It reads parameters in DOTENV format from stdin until EOF.
set -e
# Initialize variables
pack_paths=""
packs_base_dir="/opt/attune/packs"
python_version="3.11"
nodejs_version="20"
skip_python="false"
skip_nodejs="false"
force_rebuild="false"
timeout="600"
api_url="http://localhost:8080"
api_token=""
# Read DOTENV-formatted parameters from stdin until EOF
while IFS= read -r line; do
[ -z "$line" ] && continue
key="${line%%=*}"
value="${line#*=}"
# Remove quotes if present (both single and double)
case "$value" in
\"*\")
value="${value#\"}"
value="${value%\"}"
;;
\'*\')
value="${value#\'}"
value="${value%\'}"
;;
esac
# Process parameters
case "$key" in
pack_paths)
pack_paths="$value"
;;
packs_base_dir)
packs_base_dir="$value"
;;
python_version)
python_version="$value"
;;
nodejs_version)
nodejs_version="$value"
;;
skip_python)
skip_python="$value"
;;
skip_nodejs)
skip_nodejs="$value"
;;
force_rebuild)
force_rebuild="$value"
;;
timeout)
timeout="$value"
;;
api_url)
api_url="$value"
;;
api_token)
api_token="$value"
;;
esac
done
# Validate required parameters
if [ -z "$pack_paths" ]; then
printf '{"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}}\n'
exit 1
fi
# Normalize booleans
case "$skip_python" in
true|True|TRUE|yes|Yes|YES|1) skip_python="true" ;;
*) skip_python="false" ;;
esac
case "$skip_nodejs" in
true|True|TRUE|yes|Yes|YES|1) skip_nodejs="true" ;;
*) skip_nodejs="false" ;;
esac
case "$force_rebuild" in
true|True|TRUE|yes|Yes|YES|1) force_rebuild="true" ;;
*) force_rebuild="false" ;;
esac
# Validate timeout is numeric
case "$timeout" in
''|*[!0-9]*)
timeout="600"
;;
esac
# Escape values for JSON
pack_paths_escaped=$(printf '%s' "$pack_paths" | sed 's/\\/\\\\/g; s/"/\\"/g')
packs_base_dir_escaped=$(printf '%s' "$packs_base_dir" | sed 's/\\/\\\\/g; s/"/\\"/g')
python_version_escaped=$(printf '%s' "$python_version" | sed 's/\\/\\\\/g; s/"/\\"/g')
nodejs_version_escaped=$(printf '%s' "$nodejs_version" | sed 's/\\/\\\\/g; s/"/\\"/g')
# Build JSON request body
request_body=$(cat <<EOF
{
"pack_paths": $pack_paths_escaped,
"packs_base_dir": "$packs_base_dir_escaped",
"python_version": "$python_version_escaped",
"nodejs_version": "$nodejs_version_escaped",
"skip_python": $skip_python,
"skip_nodejs": $skip_nodejs,
"force_rebuild": $force_rebuild,
"timeout": $timeout
}
EOF
)
# Create temp files for curl
temp_response=$(mktemp)
temp_headers=$(mktemp)
cleanup() {
rm -f "$temp_response" "$temp_headers"
}
trap cleanup EXIT
# Calculate curl timeout (request timeout + buffer)
curl_timeout=$((timeout + 30))
# Make API call
http_code=$(curl -X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
${api_token:+-H "Authorization: Bearer ${api_token}"} \
-d "$request_body" \
-s \
-w "%{http_code}" \
-o "$temp_response" \
--max-time "$curl_timeout" \
--connect-timeout 10 \
"${api_url}/api/v1/packs/build-envs" 2>/dev/null || echo "000")
# Check HTTP status
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
# Success - extract data field from API response
response_body=$(cat "$temp_response")
# Try to extract .data field using simple text processing
# If response contains "data" field, extract it; otherwise use whole response
case "$response_body" in
*'"data":'*)
# Extract content after "data": up to the closing brace
# This is a simple extraction - assumes well-formed JSON
data_content=$(printf '%s' "$response_body" | sed -n 's/.*"data":\s*\(.*\)}/\1/p')
if [ -n "$data_content" ]; then
printf '%s\n' "$data_content"
else
cat "$temp_response"
fi
;;
*)
cat "$temp_response"
;;
esac
exit 0
else
# Error response - try to extract error message
error_msg="API request failed"
if [ -s "$temp_response" ]; then
# Try to extract error or message field
response_content=$(cat "$temp_response")
case "$response_content" in
*'"error":'*)
error_msg=$(printf '%s' "$response_content" | sed -n 's/.*"error":\s*"\([^"]*\)".*/\1/p')
[ -z "$error_msg" ] && error_msg="API request failed"
;;
*'"message":'*)
error_msg=$(printf '%s' "$response_content" | sed -n 's/.*"message":\s*"\([^"]*\)".*/\1/p')
[ -z "$error_msg" ] && error_msg="API request failed"
;;
esac
fi
# Escape error message for JSON
error_msg_escaped=$(printf '%s' "$error_msg" | sed 's/\\/\\\\/g; s/"/\\"/g')
cat <<EOF
{
"built_environments": [],
"failed_environments": [{
"pack_ref": "api",
"pack_path": "",
"runtime": "unknown",
"error": "API call failed (HTTP $http_code): $error_msg_escaped"
}],
"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,160 @@
# Build Pack Environments Action
# Creates runtime environments and installs dependencies for packs
ref: core.build_pack_envs
label: "Build Pack Environments"
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: dotenv
# Output format: json (structured data parsing enabled)
output_format: json
# Action parameters schema (StackStorm-style with inline required/secret)
parameters:
pack_paths:
type: array
description: "List of pack directory paths to build environments for"
required: true
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
# Output schema: describes the JSON structure written to stdout
# Note: stdout/stderr/exit_code are captured automatically by the execution system
output_schema:
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,201 @@
#!/bin/sh
# Download Packs Action - Core Pack
# API Wrapper for POST /api/v1/packs/download
#
# This script uses pure POSIX shell without external dependencies like jq.
# It reads parameters in DOTENV format from stdin until EOF.
set -e
# Initialize variables
packs=""
destination_dir=""
registry_url="https://registry.attune.io/index.json"
ref_spec=""
timeout="300"
verify_ssl="true"
api_url="http://localhost:8080"
api_token=""
# Read DOTENV-formatted parameters from stdin until EOF
while IFS= read -r line; do
[ -z "$line" ] && continue
key="${line%%=*}"
value="${line#*=}"
# Remove quotes if present (both single and double)
case "$value" in
\"*\")
value="${value#\"}"
value="${value%\"}"
;;
\'*\')
value="${value#\'}"
value="${value%\'}"
;;
esac
# Process parameters
case "$key" in
packs)
packs="$value"
;;
destination_dir)
destination_dir="$value"
;;
registry_url)
registry_url="$value"
;;
ref_spec)
ref_spec="$value"
;;
timeout)
timeout="$value"
;;
verify_ssl)
verify_ssl="$value"
;;
api_url)
api_url="$value"
;;
api_token)
api_token="$value"
;;
esac
done
# Validate required parameters
if [ -z "$destination_dir" ]; then
printf '{"downloaded_packs":[],"failed_packs":[{"source":"input","error":"destination_dir is required"}],"total_count":0,"success_count":0,"failure_count":1}\n'
exit 1
fi
# Normalize boolean
case "$verify_ssl" in
true|True|TRUE|yes|Yes|YES|1) verify_ssl="true" ;;
*) verify_ssl="false" ;;
esac
# Validate timeout is numeric
case "$timeout" in
''|*[!0-9]*)
timeout="300"
;;
esac
# Escape values for JSON
packs_escaped=$(printf '%s' "$packs" | sed 's/\\/\\\\/g; s/"/\\"/g')
destination_dir_escaped=$(printf '%s' "$destination_dir" | sed 's/\\/\\\\/g; s/"/\\"/g')
registry_url_escaped=$(printf '%s' "$registry_url" | sed 's/\\/\\\\/g; s/"/\\"/g')
# Build JSON request body
if [ -n "$ref_spec" ]; then
ref_spec_escaped=$(printf '%s' "$ref_spec" | sed 's/\\/\\\\/g; s/"/\\"/g')
request_body=$(cat <<EOF
{
"packs": $packs_escaped,
"destination_dir": "$destination_dir_escaped",
"registry_url": "$registry_url_escaped",
"ref_spec": "$ref_spec_escaped",
"timeout": $timeout,
"verify_ssl": $verify_ssl
}
EOF
)
else
request_body=$(cat <<EOF
{
"packs": $packs_escaped,
"destination_dir": "$destination_dir_escaped",
"registry_url": "$registry_url_escaped",
"timeout": $timeout,
"verify_ssl": $verify_ssl
}
EOF
)
fi
# Create temp files for curl
temp_response=$(mktemp)
temp_headers=$(mktemp)
cleanup() {
rm -f "$temp_response" "$temp_headers"
}
trap cleanup EXIT
# Calculate curl timeout (request timeout + buffer)
curl_timeout=$((timeout + 30))
# Make API call
http_code=$(curl -X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
${api_token:+-H "Authorization: Bearer ${api_token}"} \
-d "$request_body" \
-s \
-w "%{http_code}" \
-o "$temp_response" \
--max-time "$curl_timeout" \
--connect-timeout 10 \
"${api_url}/api/v1/packs/download" 2>/dev/null || echo "000")
# Check HTTP status
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
# Success - extract data field from API response
response_body=$(cat "$temp_response")
# Try to extract .data field using simple text processing
# If response contains "data" field, extract it; otherwise use whole response
case "$response_body" in
*'"data":'*)
# Extract content after "data": up to the closing brace
# This is a simple extraction - assumes well-formed JSON
data_content=$(printf '%s' "$response_body" | sed -n 's/.*"data":\s*\(.*\)}/\1/p')
if [ -n "$data_content" ]; then
printf '%s\n' "$data_content"
else
cat "$temp_response"
fi
;;
*)
cat "$temp_response"
;;
esac
exit 0
else
# Error response - try to extract error message
error_msg="API request failed"
if [ -s "$temp_response" ]; then
# Try to extract error or message field
response_content=$(cat "$temp_response")
case "$response_content" in
*'"error":'*)
error_msg=$(printf '%s' "$response_content" | sed -n 's/.*"error":\s*"\([^"]*\)".*/\1/p')
[ -z "$error_msg" ] && error_msg="API request failed"
;;
*'"message":'*)
error_msg=$(printf '%s' "$response_content" | sed -n 's/.*"message":\s*"\([^"]*\)".*/\1/p')
[ -z "$error_msg" ] && error_msg="API request failed"
;;
esac
fi
# Escape error message for JSON
error_msg_escaped=$(printf '%s' "$error_msg" | sed 's/\\/\\\\/g; s/"/\\"/g')
cat <<EOF
{
"downloaded_packs": [],
"failed_packs": [{
"source": "api",
"error": "API call failed (HTTP $http_code): $error_msg_escaped"
}],
"total_count": 0,
"success_count": 0,
"failure_count": 1
}
EOF
exit 1
fi

View File

@@ -0,0 +1,115 @@
# Download Packs Action
# Downloads packs from various sources (git repositories, HTTP archives, or pack registry)
ref: core.download_packs
label: "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: dotenv
# Output format: json (structured data parsing enabled)
output_format: json
# Action parameters schema (StackStorm-style with inline required/secret)
parameters:
packs:
type: array
description: "List of packs to download (git URLs, HTTP URLs, or pack refs)"
items:
type: string
minItems: 1
required: true
destination_dir:
type: string
description: "Destination directory for downloaded packs"
required: true
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"
# Output schema: describes the JSON structure written to stdout
# Note: stdout/stderr/exit_code are captured automatically by the execution system
output_schema:
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

@@ -0,0 +1,38 @@
#!/bin/sh
# Echo Action - Core Pack
# 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 EOF.
set -e
# Initialize message variable
message=""
# Read DOTENV-formatted parameters from stdin until EOF
while IFS= read -r line; do
case "$line" in
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 (even if empty)
echo -n "$message"
# Exit successfully
exit 0

View File

@@ -0,0 +1,35 @@
# Echo Action
# Outputs a message to stdout
ref: core.echo
label: "Echo"
description: "Echo a message to stdout"
enabled: true
# Runner type determines how the action is executed
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 (StackStorm-style: inline required/secret per parameter)
parameters:
message:
type: string
description: "Message to echo (empty string if not provided)"
# Output schema: not applicable for text output format
# The action outputs plain text to stdout
# Tags for categorization
tags:
- utility
- testing
- debug

View File

@@ -0,0 +1,154 @@
#!/bin/sh
# Get Pack Dependencies Action - Core Pack
# API Wrapper for POST /api/v1/packs/dependencies
#
# This script uses pure POSIX shell without external dependencies like jq.
# It reads parameters in DOTENV format from stdin until EOF.
set -e
# Initialize variables
pack_paths=""
skip_validation="false"
api_url="http://localhost:8080"
api_token=""
# Read DOTENV-formatted parameters from stdin until EOF
while IFS= read -r line; do
[ -z "$line" ] && continue
key="${line%%=*}"
value="${line#*=}"
# Remove quotes if present (both single and double)
case "$value" in
\"*\")
value="${value#\"}"
value="${value%\"}"
;;
\'*\')
value="${value#\'}"
value="${value%\'}"
;;
esac
# Process parameters
case "$key" in
pack_paths)
pack_paths="$value"
;;
skip_validation)
skip_validation="$value"
;;
api_url)
api_url="$value"
;;
api_token)
api_token="$value"
;;
esac
done
# Validate required parameters
if [ -z "$pack_paths" ]; then
printf '{"dependencies":[],"runtime_requirements":{},"missing_dependencies":[],"analyzed_packs":[],"errors":[{"pack_path":"input","error":"No pack paths provided"}]}\n'
exit 1
fi
# Normalize boolean
case "$skip_validation" in
true|True|TRUE|yes|Yes|YES|1) skip_validation="true" ;;
*) skip_validation="false" ;;
esac
# Build JSON request body (escape pack_paths value for JSON)
pack_paths_escaped=$(printf '%s' "$pack_paths" | sed 's/\\/\\\\/g; s/"/\\"/g')
request_body=$(cat <<EOF
{
"pack_paths": $pack_paths_escaped,
"skip_validation": $skip_validation
}
EOF
)
# Create temp files for curl
temp_response=$(mktemp)
temp_headers=$(mktemp)
cleanup() {
rm -f "$temp_response" "$temp_headers"
}
trap cleanup EXIT
# Make API call
http_code=$(curl -X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
${api_token:+-H "Authorization: Bearer ${api_token}"} \
-d "$request_body" \
-s \
-w "%{http_code}" \
-o "$temp_response" \
--max-time 60 \
--connect-timeout 10 \
"${api_url}/api/v1/packs/dependencies" 2>/dev/null || echo "000")
# Check HTTP status
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
# Success - extract data field from API response
response_body=$(cat "$temp_response")
# Try to extract .data field using simple text processing
# If response contains "data" field, extract it; otherwise use whole response
case "$response_body" in
*'"data":'*)
# Extract content after "data": up to the closing brace
# This is a simple extraction - assumes well-formed JSON
data_content=$(printf '%s' "$response_body" | sed -n 's/.*"data":\s*\(.*\)}/\1/p')
if [ -n "$data_content" ]; then
printf '%s\n' "$data_content"
else
cat "$temp_response"
fi
;;
*)
cat "$temp_response"
;;
esac
exit 0
else
# Error response - try to extract error message
error_msg="API request failed"
if [ -s "$temp_response" ]; then
# Try to extract error or message field
response_content=$(cat "$temp_response")
case "$response_content" in
*'"error":'*)
error_msg=$(printf '%s' "$response_content" | sed -n 's/.*"error":\s*"\([^"]*\)".*/\1/p')
[ -z "$error_msg" ] && error_msg="API request failed"
;;
*'"message":'*)
error_msg=$(printf '%s' "$response_content" | sed -n 's/.*"message":\s*"\([^"]*\)".*/\1/p')
[ -z "$error_msg" ] && error_msg="API request failed"
;;
esac
fi
# Escape error message for JSON
error_msg_escaped=$(printf '%s' "$error_msg" | sed 's/\\/\\\\/g; s/"/\\"/g')
cat <<EOF
{
"dependencies": [],
"runtime_requirements": {},
"missing_dependencies": [],
"analyzed_packs": [],
"errors": [{
"pack_path": "api",
"error": "API call failed (HTTP $http_code): $error_msg_escaped"
}]
}
EOF
exit 1
fi

View File

@@ -0,0 +1,137 @@
# Get Pack Dependencies Action
# Parses pack.yaml files to identify pack and runtime dependencies
ref: core.get_pack_dependencies
label: "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: dotenv
# Output format: json (structured data parsing enabled)
output_format: json
# Action parameters schema (StackStorm-style with inline required/secret)
parameters:
pack_paths:
type: array
description: "List of pack directory paths to analyze"
items:
type: string
minItems: 1
required: true
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"
# Output schema: describes the JSON structure written to stdout
# Note: stdout/stderr/exit_code are captured automatically by the execution system
output_schema:
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

@@ -0,0 +1,268 @@
#!/bin/sh
# HTTP Request Action - Core Pack
# Make HTTP requests to external APIs using curl
#
# This script uses pure POSIX shell without external dependencies like jq.
# It reads parameters in DOTENV format from stdin until EOF.
set -e
# Initialize variables
url=""
method="GET"
body=""
json_body=""
timeout="30"
verify_ssl="true"
auth_type="none"
auth_username=""
auth_password=""
auth_token=""
follow_redirects="true"
max_redirects="10"
# Temporary files
headers_file=$(mktemp)
query_params_file=$(mktemp)
body_file=""
temp_headers=$(mktemp)
curl_output=$(mktemp)
write_out_file=$(mktemp)
cleanup() {
local exit_code=$?
rm -f "$headers_file" "$query_params_file" "$temp_headers" "$curl_output" "$write_out_file"
[ -n "$body_file" ] && [ -f "$body_file" ] && rm -f "$body_file"
return "$exit_code"
}
trap cleanup EXIT
# Read DOTENV-formatted parameters from stdin until EOF
while IFS= read -r line; do
[ -z "$line" ] && continue
key="${line%%=*}"
value="${line#*=}"
# Remove quotes
case "$value" in
\"*\") value="${value#\"}"; value="${value%\"}" ;;
\'*\') value="${value#\'}"; value="${value%\'}" ;;
esac
# Process parameters
case "$key" in
url) url="$value" ;;
method) method="$value" ;;
body) body="$value" ;;
json_body) json_body="$value" ;;
timeout) timeout="$value" ;;
verify_ssl) verify_ssl="$value" ;;
auth_type) auth_type="$value" ;;
auth_username) auth_username="$value" ;;
auth_password) auth_password="$value" ;;
auth_token) auth_token="$value" ;;
follow_redirects) follow_redirects="$value" ;;
max_redirects) max_redirects="$value" ;;
headers.*)
printf '%s: %s\n' "${key#headers.}" "$value" >> "$headers_file"
;;
query_params.*)
printf '%s=%s\n' "${key#query_params.}" "$value" >> "$query_params_file"
;;
esac
done
# Validate required
if [ -z "$url" ]; then
printf '{"status_code":0,"headers":{},"body":"","json":null,"elapsed_ms":0,"url":"","success":false,"error":"url parameter is required"}\n'
exit 1
fi
# Normalize method
method=$(printf '%s' "$method" | tr '[:lower:]' '[:upper:]')
# URL encode helper
url_encode() {
printf '%s' "$1" | sed 's/ /%20/g; s/!/%21/g; s/"/%22/g; s/#/%23/g; s/\$/%24/g; s/&/%26/g; s/'\''/%27/g'
}
# Build URL with query params
final_url="$url"
if [ -s "$query_params_file" ]; then
query_string=""
while IFS='=' read -r param_name param_value; do
[ -z "$param_name" ] && continue
encoded=$(url_encode "$param_value")
[ -z "$query_string" ] && query_string="${param_name}=${encoded}" || query_string="${query_string}&${param_name}=${encoded}"
done < "$query_params_file"
if [ -n "$query_string" ]; then
case "$final_url" in
*\?*) final_url="${final_url}&${query_string}" ;;
*) final_url="${final_url}?${query_string}" ;;
esac
fi
fi
# Prepare body
if [ -n "$json_body" ]; then
body_file=$(mktemp)
printf '%s' "$json_body" > "$body_file"
elif [ -n "$body" ]; then
body_file=$(mktemp)
printf '%s' "$body" > "$body_file"
fi
# Build curl args file (avoid shell escaping issues)
curl_args=$(mktemp)
{
printf -- '-X\n%s\n' "$method"
printf -- '-s\n'
# Use @file for -w to avoid xargs escape interpretation issues
# curl's @file mode requires literal \n (two chars) not actual newlines
printf '\\n%%{http_code}\\n%%{url_effective}\\n' > "$write_out_file"
printf -- '-w\n@%s\n' "$write_out_file"
printf -- '--max-time\n%s\n' "$timeout"
printf -- '--connect-timeout\n10\n'
printf -- '--dump-header\n%s\n' "$temp_headers"
[ "$verify_ssl" = "false" ] && printf -- '-k\n'
if [ "$follow_redirects" = "true" ]; then
printf -- '-L\n'
printf -- '--max-redirs\n%s\n' "$max_redirects"
fi
if [ -s "$headers_file" ]; then
while IFS= read -r h; do
[ -n "$h" ] && printf -- '-H\n%s\n' "$h"
done < "$headers_file"
fi
case "$auth_type" in
basic)
[ -n "$auth_username" ] && printf -- '-u\n%s:%s\n' "$auth_username" "$auth_password"
;;
bearer)
[ -n "$auth_token" ] && printf -- '-H\nAuthorization: Bearer %s\n' "$auth_token"
;;
esac
if [ -n "$body_file" ] && [ -f "$body_file" ]; then
[ -n "$json_body" ] && printf -- '-H\nContent-Type: application/json\n'
printf -- '-d\n@%s\n' "$body_file"
fi
printf -- '%s\n' "$final_url"
} > "$curl_args"
# Execute curl
start_time=$(date +%s%3N 2>/dev/null || echo $(($(date +%s) * 1000)))
set +e
xargs -a "$curl_args" curl > "$curl_output" 2>&1
curl_exit_code=$?
set -e
rm -f "$curl_args"
end_time=$(date +%s%3N 2>/dev/null || echo $(($(date +%s) * 1000)))
elapsed_ms=$((end_time - start_time))
# Parse output
response=$(cat "$curl_output")
total_lines=$(printf '%s\n' "$response" | wc -l)
body_lines=$((total_lines - 2))
if [ "$body_lines" -gt 0 ]; then
body_output=$(printf '%s\n' "$response" | head -n "$body_lines")
else
body_output=""
fi
http_code=$(printf '%s\n' "$response" | tail -n 2 | head -n 1 | tr -d '\r\n ')
effective_url=$(printf '%s\n' "$response" | tail -n 1 | tr -d '\r\n')
case "$http_code" in
''|*[!0-9]*) http_code=0 ;;
esac
# Handle errors
if [ "$curl_exit_code" -ne 0 ]; then
error_msg="curl error code $curl_exit_code"
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" ;;
esac
error_msg=$(printf '%s' "$error_msg" | sed 's/\\/\\\\/g; s/"/\\"/g')
printf '{"status_code":0,"headers":{},"body":"","json":null,"elapsed_ms":%d,"url":"%s","success":false,"error":"%s"}\n' \
"$elapsed_ms" "$final_url" "$error_msg"
exit 1
fi
# Parse headers
headers_json="{"
first_header=true
if [ -f "$temp_headers" ]; then
while IFS= read -r line; do
case "$line" in HTTP/*|'') continue ;; esac
header_name="${line%%:*}"
header_value="${line#*:}"
[ "$header_name" = "$line" ] && continue
header_value=$(printf '%s' "$header_value" | sed 's/^ *//; s/ *$//; s/\r$//; s/\\/\\\\/g; s/"/\\"/g')
header_name=$(printf '%s' "$header_name" | sed 's/\\/\\\\/g; s/"/\\"/g')
if [ "$first_header" = true ]; then
headers_json="${headers_json}\"${header_name}\":\"${header_value}\""
first_header=false
else
headers_json="${headers_json},\"${header_name}\":\"${header_value}\""
fi
done < "$temp_headers"
fi
headers_json="${headers_json}}"
# Success check
success="false"
[ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ] && success="true"
# Escape body
body_escaped=$(printf '%s' "$body_output" | sed 's/\\/\\\\/g; s/"/\\"/g; s/ /\\t/g' | awk '{printf "%s\\n", $0}' | sed 's/\\n$//')
# Detect JSON
json_parsed="null"
if [ -n "$body_output" ]; then
first_char=$(printf '%s' "$body_output" | sed 's/^[[:space:]]*//' | head -c 1)
last_char=$(printf '%s' "$body_output" | sed 's/[[:space:]]*$//' | tail -c 1)
case "$first_char" in
'{'|'[')
case "$last_char" in
'}'|']')
# Compact multi-line JSON to single line to avoid breaking
# the worker's last-line JSON parser. In valid JSON, literal
# newlines only appear as whitespace outside strings (inside
# strings they must be escaped as \n), so tr is safe here.
json_parsed=$(printf '%s' "$body_output" | tr '\n' ' ' | tr '\r' ' ')
;;
esac
;;
esac
fi
# Output
if [ "$json_parsed" = "null" ]; then
printf '{"status_code":%d,"headers":%s,"body":"%s","json":null,"elapsed_ms":%d,"url":"%s","success":%s}\n' \
"$http_code" "$headers_json" "$body_escaped" "$elapsed_ms" "$effective_url" "$success"
else
printf '{"status_code":%d,"headers":%s,"body":"%s","json":%s,"elapsed_ms":%d,"url":"%s","success":%s}\n' \
"$http_code" "$headers_json" "$body_escaped" "$json_parsed" "$elapsed_ms" "$effective_url" "$success"
fi
exit 0

View File

@@ -0,0 +1,126 @@
# HTTP Request Action
# Make HTTP requests to external APIs
ref: core.http_request
label: "HTTP Request"
description: "Make HTTP requests to external APIs with support for various methods, headers, and authentication"
enabled: true
# Runner type determines how the action is executed
runner_type: shell
# Entry point is the bash script to execute
entry_point: http_request.sh
# Parameter delivery configuration (for security)
# Use stdin + DOTENV for secure parameter passing (credentials won't appear in process list)
parameter_delivery: stdin
parameter_format: dotenv
# Output format: json (structured data parsing enabled)
output_format: json
# Action parameters schema (StackStorm-style with inline required/secret)
parameters:
url:
type: string
description: "URL to send the request to"
required: true
method:
type: string
description: "HTTP method to use"
default: "GET"
enum:
- GET
- POST
- PUT
- PATCH
- DELETE
- HEAD
- OPTIONS
headers:
type: object
description: "HTTP headers to include in the request"
default: {}
body:
type: string
description: "Request body (for POST, PUT, PATCH methods)"
json_body:
type: object
description: "JSON request body (alternative to body parameter)"
query_params:
type: object
description: "URL query parameters as key-value pairs"
default: {}
timeout:
type: integer
description: "Request timeout in seconds"
default: 30
minimum: 1
maximum: 300
verify_ssl:
type: boolean
description: "Verify SSL certificates"
default: true
auth_type:
type: string
description: "Authentication type"
enum:
- none
- basic
- bearer
auth_username:
type: string
description: "Username for basic authentication"
auth_password:
type: string
description: "Password for basic authentication"
secret: true
auth_token:
type: string
description: "Bearer token for bearer authentication"
secret: true
follow_redirects:
type: boolean
description: "Follow HTTP redirects"
default: true
max_redirects:
type: integer
description: "Maximum number of redirects to follow"
default: 10
# Output schema: describes the JSON structure written to stdout
# Note: stdout/stderr/exit_code are captured automatically by the execution system
output_schema:
status_code:
type: integer
description: "HTTP status code"
headers:
type: object
description: "Response headers"
body:
type: string
description: "Response body as text"
json:
type: object
description: "Parsed JSON response (if applicable, null otherwise)"
elapsed_ms:
type: integer
description: "Request duration in milliseconds"
url:
type: string
description: "Final URL after redirects"
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:
- http
- api
- web
- utility
- integration

View File

@@ -0,0 +1,73 @@
#!/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 EOF.
set -e
# Initialize variables
message=""
exit_code="0"
# Read DOTENV-formatted parameters from stdin until EOF
while IFS= read -r line; do
case "$line" in
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
# 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"
fi
# Output result
echo "No operation completed successfully"
# Exit with specified code
exit "$exit_code"

View File

@@ -0,0 +1,42 @@
# No Operation Action
# Does nothing - useful for testing and placeholder workflows
ref: core.noop
label: "No-Op"
description: "Does nothing - useful for testing and placeholder workflows"
enabled: true
# Runner type determines how the action is executed
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 (StackStorm-style inline format)
parameters:
message:
type: string
description: "Optional message to log (for debugging)"
exit_code:
type: integer
description: "Exit code to return (default: 0 for success)"
default: 0
minimum: 0
maximum: 255
# Output schema: not applicable for text output format
# The action outputs plain text to stdout
# Tags for categorization
tags:
- utility
- testing
- placeholder
- noop

View File

@@ -0,0 +1,187 @@
#!/bin/sh
# Register Packs Action - Core Pack
# API Wrapper for POST /api/v1/packs/register-batch
#
# This script uses pure POSIX shell without external dependencies like jq.
# It reads parameters in DOTENV format from stdin until EOF.
set -e
# Initialize variables
pack_paths=""
packs_base_dir="/opt/attune/packs"
skip_validation="false"
skip_tests="false"
force="false"
api_url="http://localhost:8080"
api_token=""
# Read DOTENV-formatted parameters from stdin until EOF
while IFS= read -r line; do
[ -z "$line" ] && continue
key="${line%%=*}"
value="${line#*=}"
# Remove quotes if present (both single and double)
case "$value" in
\"*\")
value="${value#\"}"
value="${value%\"}"
;;
\'*\')
value="${value#\'}"
value="${value%\'}"
;;
esac
# Process parameters
case "$key" in
pack_paths)
pack_paths="$value"
;;
packs_base_dir)
packs_base_dir="$value"
;;
skip_validation)
skip_validation="$value"
;;
skip_tests)
skip_tests="$value"
;;
force)
force="$value"
;;
api_url)
api_url="$value"
;;
api_token)
api_token="$value"
;;
esac
done
# Validate required parameters
if [ -z "$pack_paths" ]; then
printf '{"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}}\n'
exit 1
fi
# Normalize booleans
case "$skip_validation" in
true|True|TRUE|yes|Yes|YES|1) skip_validation="true" ;;
*) skip_validation="false" ;;
esac
case "$skip_tests" in
true|True|TRUE|yes|Yes|YES|1) skip_tests="true" ;;
*) skip_tests="false" ;;
esac
case "$force" in
true|True|TRUE|yes|Yes|YES|1) force="true" ;;
*) force="false" ;;
esac
# Escape values for JSON
pack_paths_escaped=$(printf '%s' "$pack_paths" | sed 's/\\/\\\\/g; s/"/\\"/g')
packs_base_dir_escaped=$(printf '%s' "$packs_base_dir" | sed 's/\\/\\\\/g; s/"/\\"/g')
# Build JSON request body
request_body=$(cat <<EOF
{
"pack_paths": $pack_paths_escaped,
"packs_base_dir": "$packs_base_dir_escaped",
"skip_validation": $skip_validation,
"skip_tests": $skip_tests,
"force": $force
}
EOF
)
# Create temp files for curl
temp_response=$(mktemp)
temp_headers=$(mktemp)
cleanup() {
rm -f "$temp_response" "$temp_headers"
}
trap cleanup EXIT
# Make API call
http_code=$(curl -X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
${api_token:+-H "Authorization: Bearer ${api_token}"} \
-d "$request_body" \
-s \
-w "%{http_code}" \
-o "$temp_response" \
--max-time 300 \
--connect-timeout 10 \
"${api_url}/api/v1/packs/register-batch" 2>/dev/null || echo "000")
# Check HTTP status
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
# Success - extract data field from API response
response_body=$(cat "$temp_response")
# Try to extract .data field using simple text processing
# If response contains "data" field, extract it; otherwise use whole response
case "$response_body" in
*'"data":'*)
# Extract content after "data": up to the closing brace
# This is a simple extraction - assumes well-formed JSON
data_content=$(printf '%s' "$response_body" | sed -n 's/.*"data":\s*\(.*\)}/\1/p')
if [ -n "$data_content" ]; then
printf '%s\n' "$data_content"
else
cat "$temp_response"
fi
;;
*)
cat "$temp_response"
;;
esac
exit 0
else
# Error response - try to extract error message
error_msg="API request failed"
if [ -s "$temp_response" ]; then
# Try to extract error or message field
response_content=$(cat "$temp_response")
case "$response_content" in
*'"error":'*)
error_msg=$(printf '%s' "$response_content" | sed -n 's/.*"error":\s*"\([^"]*\)".*/\1/p')
[ -z "$error_msg" ] && error_msg="API request failed"
;;
*'"message":'*)
error_msg=$(printf '%s' "$response_content" | sed -n 's/.*"message":\s*"\([^"]*\)".*/\1/p')
[ -z "$error_msg" ] && error_msg="API request failed"
;;
esac
fi
# Escape error message for JSON
error_msg_escaped=$(printf '%s' "$error_msg" | sed 's/\\/\\\\/g; s/"/\\"/g')
cat <<EOF
{
"registered_packs": [],
"failed_packs": [{
"pack_ref": "api",
"pack_path": "",
"error": "API call failed (HTTP $http_code): $error_msg_escaped",
"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,187 @@
# Register Packs Action
# Validates pack structure and loads components into database
ref: core.register_packs
label: "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: dotenv
# Output format: json (structured data parsing enabled)
output_format: json
# Action parameters schema (StackStorm-style with inline required/secret)
parameters:
pack_paths:
type: array
description: "List of pack directory paths to register"
items:
type: string
minItems: 1
required: true
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
# Output schema: describes the JSON structure written to stdout
# Note: stdout/stderr/exit_code are captured automatically by the execution system
output_schema:
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

@@ -0,0 +1,76 @@
#!/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 EOF.
set -e
# Initialize variables
seconds="1"
message=""
# Read DOTENV-formatted parameters from stdin until EOF
while IFS= read -r line; do
case "$line" in
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
# 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"
fi
# Sleep for the specified duration
sleep "$seconds"
# Output result
echo "Slept for $seconds seconds"
# Exit successfully
exit 0

View File

@@ -0,0 +1,43 @@
# Sleep Action
# Pauses execution for a specified duration
ref: core.sleep
label: "Sleep"
description: "Sleep for a specified number of seconds"
enabled: true
# Runner type determines how the action is executed
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 (StackStorm-style with inline required/secret)
parameters:
seconds:
type: integer
description: "Number of seconds to sleep"
required: true
default: 1
minimum: 0
maximum: 3600
message:
type: string
description: "Optional message to display before sleeping"
# Output schema: not applicable for text output format
# The action outputs plain text to stdout
# Tags for categorization
tags:
- utility
- testing
- delay
- timing