working out the worker/execution interface
This commit is contained in:
321
packs/core/actions/README.md
Normal file
321
packs/core/actions/README.md
Normal 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
|
||||
102
packs/core/actions/build_pack_envs.sh
Normal file
102
packs/core/actions/build_pack_envs.sh
Normal 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
|
||||
165
packs/core/actions/build_pack_envs.yaml
Normal file
165
packs/core/actions/build_pack_envs.yaml
Normal 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
|
||||
86
packs/core/actions/download_packs.sh
Normal file
86
packs/core/actions/download_packs.sh
Normal 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
|
||||
120
packs/core/actions/download_packs.yaml
Normal file
120
packs/core/actions/download_packs.yaml
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
77
packs/core/actions/get_pack_dependencies.sh
Normal file
77
packs/core/actions/get_pack_dependencies.sh
Normal 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
|
||||
142
packs/core/actions/get_pack_dependencies.yaml
Normal file
142
packs/core/actions/get_pack_dependencies.yaml
Normal 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
|
||||
@@ -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()
|
||||
209
packs/core/actions/http_request.sh
Executable file
209
packs/core/actions/http_request.sh
Executable 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
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
92
packs/core/actions/register_packs.sh
Normal file
92
packs/core/actions/register_packs.sh
Normal 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
|
||||
192
packs/core/actions/register_packs.yaml
Normal file
192
packs/core/actions/register_packs.yaml
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user