11 KiB
Checklist: Migrating Actions to Stdin Parameter Delivery & Output Format
Purpose: Convert existing actions from environment variable-based parameter handling to secure stdin-based JSON parameter delivery, and ensure proper output format configuration.
Target Audience: Pack developers updating existing actions or creating new ones.
Pre-Migration
- Review current action - Understand what parameters it uses
- Identify sensitive parameters - Note which params are secrets (API keys, passwords, tokens)
- Check dependencies - Ensure
jqavailable for bash actions - Backup original files - Copy action scripts before modifying
- Read reference docs - Review
attune/docs/QUICKREF-action-parameters.md
YAML Configuration Updates
-
Add parameter delivery config to action YAML:
# Parameter delivery: stdin for secure parameter passing (no env vars) parameter_delivery: stdin parameter_format: json -
Mark sensitive parameters with
secret: true:parameters: properties: api_key: type: string secret: true # ← Add this -
Validate YAML syntax - Run:
python3 -c "import yaml; yaml.safe_load(open('action.yaml'))"
Add Output Format Configuration
-
Add
output_formatfield to action YAML:# Output format: text, json, or yaml output_format: text # or json, or yaml -
Choose appropriate format:
text- Plain text output (simple messages, logs, unstructured data)json- JSON structured data (API responses, complex results)yaml- YAML structured data (human-readable configuration)
Update Output Schema
-
Remove execution metadata from output schema:
# DELETE these from output_schema: stdout: # ❌ Automatically captured type: string stderr: # ❌ Automatically captured type: string exit_code: # ❌ Automatically captured type: integer -
For text format actions - Remove or simplify output schema:
output_format: text # Output schema: not applicable for text output format # The action outputs plain text to stdout -
For json/yaml format actions - Keep schema describing actual data:
output_format: json # Output schema: describes the JSON structure written to stdout output_schema: type: object properties: count: type: integer items: type: array items: type: string # No stdout/stderr/exit_code
Bash/Shell Script Migration
Remove Environment Variable Reading
- Delete all
ATTUNE_ACTION_*references:# DELETE these lines: MESSAGE="${ATTUNE_ACTION_MESSAGE:-default}" COUNT="${ATTUNE_ACTION_COUNT:-1}" API_KEY="${ATTUNE_ACTION_API_KEY}"
Add Stdin JSON Reading
-
Add stdin input reading at script start:
#!/bin/bash set -e set -o pipefail # Read JSON parameters from stdin INPUT=$(cat) -
Parse parameters with jq:
MESSAGE=$(echo "$INPUT" | jq -r '.message // "default"') COUNT=$(echo "$INPUT" | jq -r '.count // 1') API_KEY=$(echo "$INPUT" | jq -r '.api_key // ""')
Handle Optional Parameters
- Add null checks for optional params:
if [ -n "$API_KEY" ] && [ "$API_KEY" != "null" ]; then # Use API key fi
Boolean Parameters
- Handle boolean values correctly (jq outputs lowercase):
ENABLED=$(echo "$INPUT" | jq -r '.enabled // false') if [ "$ENABLED" = "true" ]; then # Feature enabled fi
Array Parameters
- Parse arrays with jq -c:
ITEMS=$(echo "$INPUT" | jq -c '.items // []') ITEM_COUNT=$(echo "$ITEMS" | jq 'length')
Python Script Migration
Remove Environment Variable Reading
-
Delete
os.environreferences:# DELETE these lines: import os message = os.environ.get('ATTUNE_ACTION_MESSAGE', 'default') -
Remove environment helper functions like
get_env_param(),parse_json_param(), etc.
Add Stdin JSON Reading
-
Add parameter reading function:
import json import sys from typing import Dict, Any def read_parameters() -> Dict[str, Any]: """Read and parse JSON parameters from stdin.""" try: input_data = sys.stdin.read() if not input_data: return {} return json.loads(input_data) except json.JSONDecodeError as e: print(f"ERROR: Invalid JSON input: {e}", file=sys.stderr) sys.exit(1) -
Call reading function in main():
def main(): params = read_parameters() message = params.get('message', 'default') count = params.get('count', 1)
Update Parameter Access
-
Replace all parameter reads with
.get():# OLD: get_env_param('message', 'default') # NEW: params.get('message', 'default') -
Update required parameter validation:
if not params.get('url'): print("ERROR: 'url' parameter is required", file=sys.stderr) sys.exit(1)
Node.js Script Migration
Remove Environment Variable Reading
- Delete
process.envreferences:// DELETE these lines: const message = process.env.ATTUNE_ACTION_MESSAGE || 'default';
Add Stdin JSON Reading
-
Add parameter reading function:
const readline = require('readline'); async function readParameters() { const rl = readline.createInterface({ input: process.stdin, terminal: false }); let input = ''; for await (const line of rl) { input += line; } try { return JSON.parse(input || '{}'); } catch (err) { console.error('ERROR: Invalid JSON input:', err.message); process.exit(1); } } -
Update main function to use async/await:
async function main() { const params = await readParameters(); const message = params.message || 'default'; } main().catch(err => { console.error('ERROR:', err.message); process.exit(1); });
Testing
Local Testing
-
Test with specific parameters:
echo '{"message": "test", "count": 5}' | ./action.sh -
Test with empty JSON (defaults):
echo '{}' | ./action.sh -
Test with file input:
cat test-params.json | ./action.sh -
Test required parameters - Verify error when missing:
echo '{"count": 5}' | ./action.sh # Should fail if 'message' required -
Test optional parameters - Verify defaults work:
echo '{"message": "test"}' | ./action.sh # count should use default -
Test null handling:
echo '{"message": "test", "api_key": null}' | ./action.sh
Integration Testing
- Test via Attune API - Execute action through API endpoint
- Test in workflow - Run action as part of a workflow
- Test with secrets - Verify secret parameters are not exposed
- Verify no env var exposure - Check
psoutput during execution
Security Review
- No secrets in logs - Ensure sensitive params aren't printed
- No parameter echoing - Don't include input JSON in error messages
- Generic error messages - Don't expose parameter values in errors
- Marked all secrets - All sensitive parameters have
secret: true
Documentation
- Update action README - Document parameter changes if exists
- Add usage examples - Show how to call action with new format
- Update pack CHANGELOG - Note breaking change from env vars to stdin
- Document default values - List all parameter defaults
Post-Migration Cleanup
- Remove old helper functions - Delete unused env var parsers
- Remove unused imports - Clean up
osimport in Python if not needed - Update comments - Fix any comments mentioning environment variables
- Validate YAML again - Final check of action.yaml syntax
- Run linters -
shellcheckfor bash,pylint/flake8for Python - Commit changes - Commit with clear message about stdin migration
Verification
- Script runs with stdin - Basic execution works
- Defaults work correctly - Empty JSON triggers default values
- Required params validated - Missing required params cause error
- Optional params work - Optional params with null/missing handled
- Exit codes correct - Success = 0, errors = non-zero
- Output format unchanged - Stdout/stderr output still correct
- No breaking changes to output - JSON output schema maintained
Example: Complete Migration
Before (Environment Variables)
#!/bin/bash
set -e
MESSAGE="${ATTUNE_ACTION_MESSAGE:-Hello}"
COUNT="${ATTUNE_ACTION_COUNT:-1}"
echo "Message: $MESSAGE (repeated $COUNT times)"
After (Stdin JSON)
#!/bin/bash
set -e
set -o pipefail
# Read JSON parameters from stdin
INPUT=$(cat)
# Parse parameters with defaults
MESSAGE=$(echo "$INPUT" | jq -r '.message // "Hello"')
COUNT=$(echo "$INPUT" | jq -r '.count // 1')
# Validate required parameters
if ! [[ "$COUNT" =~ ^[0-9]+$ ]]; then
echo "ERROR: count must be a positive integer" >&2
exit 1
fi
echo "Message: $MESSAGE (repeated $COUNT times)"
References
- Quick Reference: Action Parameters
- Quick Reference: Action Output Format
- Core Pack Actions README
- Worker Service Architecture
Common Issues
Issue: jq: command not found
Solution: Ensure jq is installed in worker container/environment
Issue: Parameters showing as null
Solution: Check for both empty string and "null" literal:
if [ -n "$PARAM" ] && [ "$PARAM" != "null" ]; then
Issue: Boolean not working as expected
Solution: jq outputs lowercase "true"/"false", compare as strings:
if [ "$ENABLED" = "true" ]; then
Issue: Array not parsing correctly
Solution: Use jq -c for compact JSON output:
ITEMS=$(echo "$INPUT" | jq -c '.items // []')
Issue: Action hangs waiting for input
Solution: Ensure JSON is being passed to stdin, or pass empty object:
echo '{}' | ./action.sh
Success Criteria
✅ Migration complete when:
- Action reads ALL parameters from stdin JSON
- NO environment variables used for parameters
- All tests pass with new parameter format
- YAML updated with
parameter_delivery: stdin - YAML includes
output_format: text|json|yaml - Output schema describes data structure only (no stdout/stderr/exit_code)
- Sensitive parameters marked with
secret: true - Documentation updated
- Local testing confirms functionality