more internal polish, resilient workers
This commit is contained in:
337
docs/QUICKREF-dotenv-shell-actions.md
Normal file
337
docs/QUICKREF-dotenv-shell-actions.md
Normal file
@@ -0,0 +1,337 @@
|
||||
# Quick Reference: DOTENV Shell Actions Pattern
|
||||
|
||||
**Purpose:** Standard pattern for writing portable shell actions without external dependencies like `jq`.
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **Use POSIX shell** (`#!/bin/sh`), not bash
|
||||
2. **Read parameters in DOTENV format** from stdin
|
||||
3. **No external JSON parsers** (jq, yq, etc.)
|
||||
4. **Minimal dependencies** (only POSIX utilities + curl)
|
||||
|
||||
## Complete Template
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
# Action Name - Core Pack
|
||||
# Brief description of what this action does
|
||||
#
|
||||
# This script uses pure POSIX shell without external dependencies like jq.
|
||||
# It reads parameters in DOTENV format from stdin until the delimiter.
|
||||
|
||||
set -e
|
||||
|
||||
# Initialize variables with defaults
|
||||
param1=""
|
||||
param2="default_value"
|
||||
bool_param="false"
|
||||
numeric_param="0"
|
||||
|
||||
# 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
|
||||
;;
|
||||
esac
|
||||
[ -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
|
||||
param1)
|
||||
param1="$value"
|
||||
;;
|
||||
param2)
|
||||
param2="$value"
|
||||
;;
|
||||
bool_param)
|
||||
bool_param="$value"
|
||||
;;
|
||||
numeric_param)
|
||||
numeric_param="$value"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Normalize boolean values
|
||||
case "$bool_param" in
|
||||
true|True|TRUE|yes|Yes|YES|1) bool_param="true" ;;
|
||||
*) bool_param="false" ;;
|
||||
esac
|
||||
|
||||
# Validate numeric parameters
|
||||
case "$numeric_param" in
|
||||
''|*[!0-9]*)
|
||||
echo "ERROR: numeric_param must be a positive integer" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Validate required parameters
|
||||
if [ -z "$param1" ]; then
|
||||
echo "ERROR: param1 is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Action logic goes here
|
||||
echo "Processing with param1=$param1, param2=$param2"
|
||||
|
||||
# Exit successfully
|
||||
exit 0
|
||||
```
|
||||
|
||||
## YAML Metadata Configuration
|
||||
|
||||
```yaml
|
||||
ref: core.action_name
|
||||
label: "Action Name"
|
||||
description: "Brief description"
|
||||
enabled: true
|
||||
runner_type: shell
|
||||
entry_point: action_name.sh
|
||||
|
||||
# IMPORTANT: Use dotenv format for POSIX shell compatibility
|
||||
parameter_delivery: stdin
|
||||
parameter_format: dotenv
|
||||
|
||||
# Output format (text or json)
|
||||
output_format: text
|
||||
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
param1:
|
||||
type: string
|
||||
description: "First parameter"
|
||||
param2:
|
||||
type: string
|
||||
description: "Second parameter"
|
||||
default: "default_value"
|
||||
bool_param:
|
||||
type: boolean
|
||||
description: "Boolean parameter"
|
||||
default: false
|
||||
required:
|
||||
- param1
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### 1. Parameter Parsing
|
||||
|
||||
**Read until delimiter:**
|
||||
```sh
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
*"---ATTUNE_PARAMS_END---"*) break ;;
|
||||
esac
|
||||
done
|
||||
```
|
||||
|
||||
**Extract key-value:**
|
||||
```sh
|
||||
key="${line%%=*}" # Everything before first =
|
||||
value="${line#*=}" # Everything after first =
|
||||
```
|
||||
|
||||
**Remove quotes:**
|
||||
```sh
|
||||
case "$value" in
|
||||
\"*\") value="${value#\"}"; value="${value%\"}" ;;
|
||||
\'*\') value="${value#\'}"; value="${value%\'}" ;;
|
||||
esac
|
||||
```
|
||||
|
||||
### 2. Boolean Normalization
|
||||
|
||||
```sh
|
||||
case "$bool_param" in
|
||||
true|True|TRUE|yes|Yes|YES|1) bool_param="true" ;;
|
||||
*) bool_param="false" ;;
|
||||
esac
|
||||
```
|
||||
|
||||
### 3. Numeric Validation
|
||||
|
||||
```sh
|
||||
case "$number" in
|
||||
''|*[!0-9]*)
|
||||
echo "ERROR: must be a number" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
```
|
||||
|
||||
### 4. JSON Output (without jq)
|
||||
|
||||
**Escape special characters:**
|
||||
```sh
|
||||
escaped=$(printf '%s' "$value" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
||||
```
|
||||
|
||||
**Build JSON:**
|
||||
```sh
|
||||
cat <<EOF
|
||||
{
|
||||
"field": "$escaped",
|
||||
"boolean": $bool_value,
|
||||
"number": $number
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### 5. Making HTTP Requests
|
||||
|
||||
**With curl and temp files:**
|
||||
```sh
|
||||
temp_response=$(mktemp)
|
||||
cleanup() { rm -f "$temp_response"; }
|
||||
trap cleanup EXIT
|
||||
|
||||
http_code=$(curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
${api_token:+-H "Authorization: Bearer ${api_token}"} \
|
||||
-d "$request_body" \
|
||||
-s \
|
||||
-w "%{http_code}" \
|
||||
-o "$temp_response" \
|
||||
--max-time 60 \
|
||||
"${api_url}/api/v1/endpoint" 2>/dev/null || echo "000")
|
||||
|
||||
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
|
||||
cat "$temp_response"
|
||||
exit 0
|
||||
else
|
||||
echo "ERROR: API call failed (HTTP $http_code)" >&2
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### 6. Extracting JSON Fields (simple cases)
|
||||
|
||||
**Extract field value:**
|
||||
```sh
|
||||
case "$response" in
|
||||
*'"field":'*)
|
||||
value=$(printf '%s' "$response" | sed -n 's/.*"field":\s*"\([^"]*\)".*/\1/p')
|
||||
;;
|
||||
esac
|
||||
```
|
||||
|
||||
**Note:** For complex JSON, consider having the API return the exact format needed.
|
||||
|
||||
## Anti-Patterns (DO NOT DO)
|
||||
|
||||
❌ **Using jq:**
|
||||
```sh
|
||||
value=$(echo "$json" | jq -r '.field') # NO!
|
||||
```
|
||||
|
||||
❌ **Using bash-specific features:**
|
||||
```sh
|
||||
#!/bin/bash # NO! Use #!/bin/sh
|
||||
[[ "$var" == "value" ]] # NO! Use [ "$var" = "value" ]
|
||||
```
|
||||
|
||||
❌ **Reading JSON directly from stdin:**
|
||||
```yaml
|
||||
parameter_format: json # NO! Use dotenv
|
||||
```
|
||||
|
||||
❌ **Using Python/Node.js in core pack:**
|
||||
```yaml
|
||||
runner_type: python # NO! Use shell for core pack
|
||||
```
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Script has `#!/bin/sh` shebang
|
||||
- [ ] Script is executable (`chmod +x`)
|
||||
- [ ] All parameters have defaults or validation
|
||||
- [ ] Boolean values are normalized
|
||||
- [ ] Numeric values are validated
|
||||
- [ ] Required parameters are checked
|
||||
- [ ] Error messages go to stderr (`>&2`)
|
||||
- [ ] Successful output goes to stdout
|
||||
- [ ] Temp files are cleaned up (trap handler)
|
||||
- [ ] YAML has `parameter_format: dotenv`
|
||||
- [ ] YAML has `runner_type: shell`
|
||||
- [ ] No `jq`, `yq`, or bash-isms used
|
||||
- [ ] Works on Alpine Linux (minimal environment)
|
||||
|
||||
## Examples from Core Pack
|
||||
|
||||
### Simple Action (echo.sh)
|
||||
- Minimal parameter parsing
|
||||
- Single string parameter
|
||||
- Text output
|
||||
|
||||
### Complex Action (http_request.sh)
|
||||
- Multiple parameters (headers, query params)
|
||||
- HTTP client implementation
|
||||
- JSON output construction
|
||||
- Error handling
|
||||
|
||||
### API Wrapper (register_packs.sh)
|
||||
- JSON request body construction
|
||||
- API authentication
|
||||
- Response parsing
|
||||
- Structured error messages
|
||||
|
||||
## DOTENV Format Specification
|
||||
|
||||
**Format:** Each parameter on a new line as `key=value`
|
||||
|
||||
**Example:**
|
||||
```
|
||||
param1="string value"
|
||||
param2=42
|
||||
bool_param=true
|
||||
---ATTUNE_PARAMS_END---
|
||||
```
|
||||
|
||||
**Key Rules:**
|
||||
- Parameters end with `---ATTUNE_PARAMS_END---` delimiter
|
||||
- Values may be quoted (single or double quotes)
|
||||
- Empty lines are skipped
|
||||
- No multiline values (use base64 if needed)
|
||||
- Array/object parameters passed as JSON strings
|
||||
|
||||
## When to Use This Pattern
|
||||
|
||||
✅ **Use DOTENV shell pattern for:**
|
||||
- Core pack actions
|
||||
- Simple utility actions
|
||||
- Actions that need maximum portability
|
||||
- Actions that run in minimal containers
|
||||
- Actions that don't need complex JSON parsing
|
||||
|
||||
❌ **Consider other runtimes if you need:**
|
||||
- Complex JSON manipulation
|
||||
- External libraries (AWS SDK, etc.)
|
||||
- Advanced string processing
|
||||
- Parallel processing
|
||||
- Language-specific features
|
||||
|
||||
## Further Reading
|
||||
|
||||
- `packs/core/actions/echo.sh` - Simplest example
|
||||
- `packs/core/actions/http_request.sh` - Complex example
|
||||
- `packs/core/actions/register_packs.sh` - API wrapper example
|
||||
- `docs/pack-structure.md` - Pack development guide
|
||||
Reference in New Issue
Block a user