# Rule Parameter Mapping ## Overview Rules in Attune can specify parameters to pass to actions when triggered. These parameters can be: 1. **Static values** - Hard-coded values defined in the rule 2. **Dynamic from trigger payload** - Values extracted from the event that triggered the rule 3. **Dynamic from pack config** - Values from the pack's configuration This enables flexible parameter passing without hardcoding values or requiring custom code. --- ## Parameter Mapping Format Rule `action_params` uses a JSON object where each value can be: - **Static**: A literal value (string, number, boolean, object, array) - **Dynamic**: A template string using `{{ }}` syntax to reference runtime values ### Template Syntax ``` {{ source.path.to.value }} ``` **Available Sources:** - `trigger.payload.*` - Data from the event payload - `pack.config.*` - Configuration values from the pack - `system.*` - System-provided values (timestamp, execution context) --- ## Static Parameter Example The simplest form - just pass fixed values to the action: ```json { "ref": "slack.notify_on_error", "pack_ref": "slack", "trigger_ref": "core.error_event", "action_ref": "slack.post_message", "action_params": { "channel": "#alerts", "message": "An error occurred in the system", "color": "danger" } } ``` When this rule triggers, the action receives exactly these parameters. --- ## Dynamic Parameters from Trigger Payload Extract values from the event that triggered the rule. ### Example: Alert with Event Data **Trigger Payload:** ```json { "severity": "error", "service": "api-gateway", "message": "Database connection failed", "timestamp": "2024-01-15T10:30:00Z", "metadata": { "host": "api-01.example.com", "error_code": "DB_CONN_TIMEOUT" } } ``` **Rule Definition:** ```json { "ref": "alerts.error_notification", "pack_ref": "alerts", "trigger_ref": "core.error_event", "action_ref": "slack.post_message", "action_params": { "channel": "#incidents", "message": "Error in {{ trigger.payload.service }}: {{ trigger.payload.message }}", "severity": "{{ trigger.payload.severity }}", "host": "{{ trigger.payload.metadata.host }}", "timestamp": "{{ trigger.payload.timestamp }}" } } ``` **Resulting Action Parameters:** ```json { "channel": "#incidents", "message": "Error in api-gateway: Database connection failed", "severity": "error", "host": "api-01.example.com", "timestamp": "2024-01-15T10:30:00Z" } ``` --- ## Dynamic Parameters from Pack Config Use configuration values stored at the pack level (useful for API keys, URLs, etc.). ### Example: Using Pack Configuration **Pack Configuration:** ```json { "ref": "slack", "config": { "api_token": "xoxb-1234567890-abcdefghijk", "default_channel": "#general", "webhook_url": "https://hooks.slack.com/services/...", "bot_name": "Attune Bot" } } ``` **Rule Definition:** ```json { "ref": "slack.auto_notify", "pack_ref": "slack", "trigger_ref": "core.notification_event", "action_ref": "slack.post_message", "action_params": { "token": "{{ pack.config.api_token }}", "channel": "{{ pack.config.default_channel }}", "username": "{{ pack.config.bot_name }}", "message": "{{ trigger.payload.message }}" } } ``` **Benefits:** - Secrets stored in pack config, not in rules - Easy to update credentials without changing rules - Reuse configuration across multiple rules --- ## Mixed Parameters (Static + Dynamic) Combine static and dynamic values in the same rule: ```json { "ref": "github.create_issue", "pack_ref": "github", "trigger_ref": "core.error_event", "action_ref": "github.create_issue", "action_params": { "repo": "myorg/myrepo", "token": "{{ pack.config.github_token }}", "title": "Error: {{ trigger.payload.message }}", "body": "Service {{ trigger.payload.service }} reported an error at {{ trigger.payload.timestamp }}", "labels": ["bug", "automated"], "assignees": ["oncall"] } } ``` --- ## Nested Object Access Access nested properties using dot notation: ```json { "action_params": { "user_id": "{{ trigger.payload.user.id }}", "user_name": "{{ trigger.payload.user.profile.name }}", "metadata": { "ip_address": "{{ trigger.payload.request.client_ip }}", "user_agent": "{{ trigger.payload.request.headers.user_agent }}" } } } ``` --- ## Array Access Access array elements by index: ```json { "action_params": { "first_error": "{{ trigger.payload.errors.0 }}", "primary_tag": "{{ trigger.payload.tags.0 }}" } } ``` --- ## Default Values and Fallbacks Provide default values when the referenced field doesn't exist: ```json { "action_params": { "priority": "{{ trigger.payload.priority | default: 'medium' }}", "assignee": "{{ trigger.payload.assignee | default: 'unassigned' }}" } } ``` --- ## Type Preservation Template values preserve their JSON types: ```json { "action_params": { "count": "{{ trigger.payload.count }}", // Number: 42 "enabled": "{{ trigger.payload.enabled }}", // Boolean: true "tags": "{{ trigger.payload.tags }}", // Array: ["a", "b"] "metadata": "{{ trigger.payload.metadata }}" // Object: {"key": "value"} } } ``` --- ## System Variables Access system-provided values: ```json { "action_params": { "execution_time": "{{ system.timestamp }}", "rule_id": "{{ system.rule.id }}", "rule_ref": "{{ system.rule.ref }}", "event_id": "{{ system.event.id }}", "enforcement_id": "{{ system.enforcement.id }}" } } ``` --- ## String Interpolation Embed multiple values in a single string: ```json { "action_params": { "message": "User {{ trigger.payload.user_id }} performed {{ trigger.payload.action }} at {{ system.timestamp }}", "subject": "[{{ trigger.payload.severity | upper }}] {{ trigger.payload.service }} Alert" } } ``` --- ## Filters (Future Enhancement) Apply transformations to values: ```json { "action_params": { "uppercase_name": "{{ trigger.payload.name | upper }}", "lowercase_email": "{{ trigger.payload.email | lower }}", "formatted_date": "{{ trigger.payload.timestamp | date: '%Y-%m-%d' }}", "truncated": "{{ trigger.payload.message | truncate: 100 }}" } } ``` **Available Filters:** - `upper` - Convert to uppercase - `lower` - Convert to lowercase - `trim` - Remove whitespace - `default: ` - Use default if null/missing - `date: ` - Format timestamp - `truncate: ` - Truncate string - `json` - Serialize to JSON string - `base64` - Base64 encode - `length` - Get length/count --- ## Real-World Examples ### 1. Webhook to Slack Alert ```json { "ref": "monitoring.webhook_to_slack", "pack_ref": "monitoring", "trigger_ref": "core.webhook", "action_ref": "slack.post_message", "action_params": { "channel": "{{ pack.config.alert_channel }}", "token": "{{ pack.config.slack_token }}", "message": "⚠️ Alert from {{ trigger.payload.source }}: {{ trigger.payload.message }}", "attachments": [ { "color": "{{ trigger.payload.severity | default: 'warning' }}", "fields": [ { "title": "Service", "value": "{{ trigger.payload.service }}", "short": true }, { "title": "Environment", "value": "{{ trigger.payload.environment | default: 'production' }}", "short": true } ], "footer": "Attune Automation", "ts": "{{ system.timestamp }}" } ] } } ``` ### 2. Error to Ticket System ```json { "ref": "errors.create_ticket", "pack_ref": "errors", "trigger_ref": "core.error_event", "action_ref": "jira.create_issue", "action_params": { "project": "{{ pack.config.jira_project }}", "auth": { "username": "{{ pack.config.jira_username }}", "token": "{{ pack.config.jira_token }}" }, "issuetype": "Bug", "summary": "[{{ trigger.payload.severity }}] {{ trigger.payload.service }}: {{ trigger.payload.message }}", "description": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": "Error Details:\n\nService: {{ trigger.payload.service }}\nHost: {{ trigger.payload.host }}\nTimestamp: {{ trigger.payload.timestamp }}\n\nStack Trace:\n{{ trigger.payload.stack_trace }}" } ] } ] }, "priority": "{{ trigger.payload.priority | default: 'Medium' }}", "labels": ["automated", "{{ trigger.payload.service }}"] } } ``` ### 3. Metric Threshold to PagerDuty ```json { "ref": "monitoring.critical_alert", "pack_ref": "monitoring", "trigger_ref": "metrics.threshold_exceeded", "action_ref": "pagerduty.trigger_incident", "action_params": { "routing_key": "{{ pack.config.pagerduty_routing_key }}", "event_action": "trigger", "payload": { "summary": "{{ trigger.payload.metric_name }} exceeded threshold on {{ trigger.payload.host }}", "severity": "critical", "source": "{{ trigger.payload.host }}", "custom_details": { "metric": "{{ trigger.payload.metric_name }}", "current_value": "{{ trigger.payload.current_value }}", "threshold": "{{ trigger.payload.threshold }}", "duration": "{{ trigger.payload.duration_seconds }}s" } }, "dedup_key": "{{ trigger.payload.host }}_{{ trigger.payload.metric_name }}" } } ``` ### 4. Timer to HTTP Request ```json { "ref": "healthcheck.periodic_ping", "pack_ref": "healthcheck", "trigger_ref": "core.interval_timer", "action_ref": "http.request", "action_params": { "method": "POST", "url": "{{ pack.config.healthcheck_endpoint }}", "headers": { "Authorization": "Bearer {{ pack.config.api_token }}", "Content-Type": "application/json" }, "body": { "source": "attune", "timestamp": "{{ system.timestamp }}", "rule": "{{ system.rule.ref }}" }, "timeout": 30 } } ``` --- ## Implementation Details ### Template Processing Flow 1. **Rule Evaluation** - When an event matches a rule 2. **Template Extraction** - Identify `{{ }}` patterns in `action_params` 3. **Context Building** - Assemble available data: - `trigger.payload` - Event payload data - `pack.config` - Pack configuration - `system.*` - System-provided values 4. **Value Resolution** - Extract values from context using dot notation paths 5. **Type Conversion** - Preserve JSON types (string, number, boolean, object, array) 6. **Parameter Assembly** - Build final parameter object 7. **Enforcement Creation** - Store resolved parameters in enforcement config 8. **Execution Creation** - Pass parameters to action execution ### Error Handling **Missing Values:** - If a referenced value doesn't exist and no default is provided, use `null` - Log warning: `"Template reference not found: trigger.payload.missing_field"` **Invalid Syntax:** - If template syntax is invalid, log error and use the raw string - Log error: `"Invalid template syntax: {{ incomplete"` **Type Mismatches:** - Preserve JSON types when possible - Convert to string as fallback for complex interpolation --- ## Configuration in Pack Pack configuration should be stored securely and can include: ```json { "ref": "mypack", "config": { "api_token": "secret-token-here", "api_url": "https://api.example.com", "default_timeout": 30, "retry_attempts": 3, "enable_notifications": true, "notification_channels": ["#alerts", "#monitoring"] } } ``` **Security Note:** Sensitive values (API keys, tokens, passwords) should be stored in pack config, not in rule definitions, since: - Pack configs can be encrypted - Easier to rotate credentials - Rules can be version controlled without exposing secrets --- ## Best Practices ### 1. Use Pack Config for Secrets ❌ **Bad:** ```json { "action_params": { "api_key": "sk_live_abc123xyz789" // Hardcoded secret } } ``` ✅ **Good:** ```json { "action_params": { "api_key": "{{ pack.config.api_key }}" } } ``` ### 2. Provide Defaults for Optional Fields ```json { "action_params": { "priority": "{{ trigger.payload.priority | default: 'medium' }}", "assignee": "{{ trigger.payload.assignee | default: 'unassigned' }}" } } ``` ### 3. Use Descriptive Template Paths ```json { "action_params": { "user_email": "{{ trigger.payload.user.email }}", "user_id": "{{ trigger.payload.user.id }}" } } ``` ### 4. Keep Static Values Where Appropriate If a value never changes, keep it static: ```json { "action_params": { "service_name": "my-service", // Static - never changes "error_code": "{{ trigger.payload.code }}" // Dynamic - from event } } ``` ### 5. Test Your Templates Create test events with sample payloads to verify your templates extract the correct values. --- ## Testing Parameter Mapping ### 1. Manual Testing via API Create a test event with known payload: ```bash curl -X POST http://localhost:8080/api/v1/events \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "trigger_ref": "core.test_event", "payload": { "message": "Test message", "severity": "info", "user": { "id": 123, "name": "Alice" } } }' ``` Check the resulting enforcement and execution to verify parameters were resolved correctly: ```bash # Check enforcement curl -X GET http://localhost:8080/api/v1/enforcements/1 \ -H "Authorization: Bearer $TOKEN" # Check execution curl -X GET http://localhost:8080/api/v1/executions/1 \ -H "Authorization: Bearer $TOKEN" ``` ### 2. Validate Parameter Resolution Look for the resolved parameters in the execution's `config` field: ```json { "id": 1, "config": { "message": "Test message", // Resolved from trigger.payload.message "severity": "info", // Resolved from trigger.payload.severity "user_id": 123, // Resolved from trigger.payload.user.id "user_name": "Alice" // Resolved from trigger.payload.user.name } } ``` --- ## Migration Guide ### From Static to Dynamic Parameters **Before (Static):** ```json { "action_params": { "message": "An error occurred" } } ``` **After (Dynamic):** ```json { "action_params": { "message": "Error: {{ trigger.payload.message }}" } } ``` ### From Hardcoded Secrets to Pack Config **Before (Hardcoded):** ```json { "action_params": { "api_key": "sk_live_abc123" } } ``` **Steps:** 1. Add secret to pack config 2. Update rule to reference pack config 3. Remove hardcoded value **After (Secure):** ```json { "action_params": { "api_key": "{{ pack.config.api_key }}" } } ``` --- ## Troubleshooting ### Templates Not Resolving **Problem:** Parameters contain literal `{{ ... }}` strings instead of resolved values. **Solutions:** 1. Check template syntax is correct 2. Verify the referenced path exists in the event payload 3. Check sensor service logs for template resolution errors 4. Use default values for optional fields ### Incorrect Values **Problem:** Parameters have wrong values. **Solutions:** 1. Inspect event payload structure: `SELECT payload FROM attune.event WHERE id = X;` 2. Verify the dot notation path matches the payload structure 3. Check for typos in template paths 4. Use system logs to see template resolution details ### Type Conversion Issues **Problem:** Numbers or booleans become strings. **Solutions:** 1. Ensure the source value is the correct type in the payload 2. Check if string interpolation is converting types 3. Use direct references without string interpolation for non-string types --- ## Future Enhancements ### 1. Conditional Parameters ```json { "action_params": { "channel": "{% if trigger.payload.severity == 'critical' %}#incidents{% else %}#monitoring{% endif %}" } } ``` ### 2. Advanced Filters - Mathematical operations: `{{ trigger.payload.value | multiply: 100 }}` - String manipulation: `{{ trigger.payload.text | replace: 'old', 'new' }}` - Array operations: `{{ trigger.payload.items | join: ', ' }}` ### 3. Custom Functions ```json { "action_params": { "timestamp": "{{ now() }}", "uuid": "{{ uuid() }}", "hash": "{{ hash(trigger.payload.data) }}" } } ``` ### 4. Multi-Source Merging ```json { "action_params": { "user": "{{ trigger.payload.user | merge: pack.config.default_user }}" } } ``` --- ## Related Documentation - [Rule Management API](./api-rules.md) - [Event Management API](./api-events-enforcements.md) - [Pack Management API](./api-packs.md) - [Sensor Service Architecture](./sensor-service.md) - [Security Best Practices](./security-review-2024-01-02.md) - [Secrets Management](./secrets-management.md) --- ## Summary Rule parameter mapping provides a powerful way to: 1. **Decouple rules from data** - Rules reference data locations, not specific values 2. **Reuse pack configuration** - Share credentials and settings across rules 3. **Dynamic automation** - Respond to events with context-aware actions 4. **Secure secrets** - Store sensitive data in pack config, not rule definitions 5. **Flexible workflows** - Build complex automations without custom code **Key Concepts:** - Static values for constants - `{{ trigger.payload.* }}` for event data - `{{ pack.config.* }}` for pack configuration - `{{ system.* }}` for system-provided values - Filters and defaults for robust templates This feature enables Attune to match the flexibility of platforms like StackStorm while maintaining a clean, declarative approach to automation.