Files
attune/docs/CHECKLIST-action-parameter-migration.md

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 jq available 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_format field 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.environ references:

    # 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.env references:
    // 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 ps output 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 os import in Python if not needed
  • Update comments - Fix any comments mentioning environment variables
  • Validate YAML again - Final check of action.yaml syntax
  • Run linters - shellcheck for bash, pylint/flake8 for 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


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