[wip] cli capability parity
Some checks failed
CI / Rustfmt (push) Successful in 23s
CI / Cargo Audit & Deny (push) Successful in 30s
CI / Web Blocking Checks (push) Successful in 48s
CI / Security Blocking Checks (push) Successful in 8s
CI / Clippy (push) Failing after 1m55s
CI / Web Advisory Checks (push) Successful in 35s
CI / Security Advisory Checks (push) Successful in 37s
CI / Tests (push) Successful in 8m5s
Some checks failed
CI / Rustfmt (push) Successful in 23s
CI / Cargo Audit & Deny (push) Successful in 30s
CI / Web Blocking Checks (push) Successful in 48s
CI / Security Blocking Checks (push) Successful in 8s
CI / Clippy (push) Failing after 1m55s
CI / Web Advisory Checks (push) Successful in 35s
CI / Security Advisory Checks (push) Successful in 37s
CI / Tests (push) Successful in 8m5s
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
## Core Principles
|
||||
|
||||
1. **Use POSIX shell** (`#!/bin/sh`), not bash
|
||||
2. **Read parameters in DOTENV format** from stdin
|
||||
2. **Read parameters in DOTENV format** from stdin until EOF
|
||||
3. **No external JSON parsers** (jq, yq, etc.)
|
||||
4. **Minimal dependencies** (only POSIX utilities + curl)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
# 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.
|
||||
# It reads parameters in DOTENV format from stdin until EOF.
|
||||
|
||||
set -e
|
||||
|
||||
@@ -27,14 +27,8 @@ param2="default_value"
|
||||
bool_param="false"
|
||||
numeric_param="0"
|
||||
|
||||
# Read DOTENV-formatted parameters from stdin until delimiter
|
||||
# Read DOTENV-formatted parameters from stdin until EOF
|
||||
while IFS= read -r line; do
|
||||
# Check for parameter delimiter
|
||||
case "$line" in
|
||||
*"---ATTUNE_PARAMS_END---"*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
[ -z "$line" ] && continue
|
||||
|
||||
key="${line%%=*}"
|
||||
@@ -135,12 +129,11 @@ parameters:
|
||||
|
||||
### 1. Parameter Parsing
|
||||
|
||||
**Read until delimiter:**
|
||||
**Read until EOF:**
|
||||
```sh
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
*"---ATTUNE_PARAMS_END---"*) break ;;
|
||||
esac
|
||||
[ -z "$line" ] && continue
|
||||
# ... process line
|
||||
done
|
||||
```
|
||||
|
||||
@@ -303,11 +296,10 @@ runner_type: python # NO! Use shell for core pack
|
||||
param1="string value"
|
||||
param2=42
|
||||
bool_param=true
|
||||
---ATTUNE_PARAMS_END---
|
||||
```
|
||||
|
||||
**Key Rules:**
|
||||
- Parameters end with `---ATTUNE_PARAMS_END---` delimiter
|
||||
- Parameters are delivered via stdin; the script reads until EOF (stdin is closed after delivery)
|
||||
- Values may be quoted (single or double quotes)
|
||||
- Empty lines are skipped
|
||||
- No multiline values (use base64 if needed)
|
||||
|
||||
@@ -107,17 +107,15 @@ parameter_format: json
|
||||
|
||||
**Reading stdin parameters:**
|
||||
|
||||
The worker writes parameters to stdin with a delimiter:
|
||||
The worker writes a single document to stdin containing all parameters (including secrets merged in), followed by a newline, then closes stdin:
|
||||
|
||||
```
|
||||
<formatted_parameters>
|
||||
---ATTUNE_PARAMS_END---
|
||||
<secrets_json>
|
||||
<formatted_parameters>\n
|
||||
```
|
||||
|
||||
- Parameters come first in your chosen format
|
||||
- Delimiter `---ATTUNE_PARAMS_END---` separates parameters from secrets
|
||||
- Secrets follow as JSON (if any)
|
||||
- Parameters and secrets are merged into a single document
|
||||
- Secrets are included as top-level keys in the parameters object
|
||||
- The action reads until EOF (stdin is closed after delivery)
|
||||
|
||||
#### 2. **File Delivery**
|
||||
|
||||
@@ -174,10 +172,9 @@ COUNT=$(echo "$PARAMS_JSON" | jq -r '.count // 1')
|
||||
import json
|
||||
import sys
|
||||
|
||||
# Read until delimiter
|
||||
content = sys.stdin.read()
|
||||
parts = content.split('---ATTUNE_PARAMS_END---')
|
||||
params = json.loads(parts[0].strip()) if parts[0].strip() else {}
|
||||
# Read all parameters from stdin (secrets are merged in)
|
||||
content = sys.stdin.read().strip()
|
||||
params = json.loads(content) if content else {}
|
||||
|
||||
message = params.get('message', '')
|
||||
count = params.get('count', 1)
|
||||
@@ -206,9 +203,9 @@ nested:
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
content = sys.stdin.read()
|
||||
parts = content.split('---ATTUNE_PARAMS_END---')
|
||||
params = yaml.safe_load(parts[0].strip()) if parts[0].strip() else {}
|
||||
# Read all parameters from stdin (secrets are merged in)
|
||||
content = sys.stdin.read().strip()
|
||||
params = yaml.safe_load(content) if content else {}
|
||||
|
||||
message = params.get('message', '')
|
||||
```
|
||||
@@ -240,7 +237,6 @@ count=""
|
||||
# Read until delimiter
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
*"---ATTUNE_PARAMS_END---"*) break ;;
|
||||
message=*)
|
||||
message="${line#message=}"
|
||||
# Remove quotes
|
||||
@@ -564,10 +560,9 @@ import json
|
||||
import sys
|
||||
|
||||
def main():
|
||||
# Read parameters
|
||||
content = sys.stdin.read()
|
||||
parts = content.split('---ATTUNE_PARAMS_END---')
|
||||
params = json.loads(parts[0].strip()) if parts[0].strip() else {}
|
||||
# Read parameters (secrets are merged into the same document)
|
||||
content = sys.stdin.read().strip()
|
||||
params = json.loads(content) if content else {}
|
||||
|
||||
# Process
|
||||
message = params.get('message', '')
|
||||
@@ -614,7 +609,6 @@ async function main() {
|
||||
|
||||
let input = '';
|
||||
for await (const line of rl) {
|
||||
if (line.includes('---ATTUNE_PARAMS_END---')) break;
|
||||
input += line;
|
||||
}
|
||||
|
||||
@@ -815,15 +809,9 @@ import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
def read_stdin_params():
|
||||
"""Read parameters and secrets from stdin."""
|
||||
content = sys.stdin.read()
|
||||
parts = content.split('---ATTUNE_PARAMS_END---')
|
||||
|
||||
params = json.loads(parts[0].strip()) if parts[0].strip() else {}
|
||||
secrets = json.loads(parts[1].strip()) if len(parts) > 1 and parts[1].strip() else {}
|
||||
|
||||
# Merge secrets into params
|
||||
return {**params, **secrets}
|
||||
"""Read parameters from stdin. Secrets are already merged into the parameters."""
|
||||
content = sys.stdin.read().strip()
|
||||
return json.loads(content) if content else {}
|
||||
|
||||
def main():
|
||||
try:
|
||||
@@ -903,10 +891,9 @@ import sys
|
||||
import time
|
||||
|
||||
def main():
|
||||
# Read parameters
|
||||
content = sys.stdin.read()
|
||||
parts = content.split('---ATTUNE_PARAMS_END---')
|
||||
params = json.loads(parts[0].strip()) if parts[0].strip() else {}
|
||||
# Read parameters (secrets are merged into the same document)
|
||||
content = sys.stdin.read().strip()
|
||||
params = json.loads(content) if content else {}
|
||||
|
||||
items = params.get('items', [])
|
||||
|
||||
@@ -975,7 +962,6 @@ compress="true"
|
||||
# Read dotenv format from stdin
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
*"---ATTUNE_PARAMS_END---"*) break ;;
|
||||
source=*)
|
||||
source="${line#source=}"
|
||||
source="${source#[\"\']}"
|
||||
@@ -1060,7 +1046,7 @@ exit 0
|
||||
**Check:**
|
||||
- Is `parameter_delivery` set correctly?
|
||||
- Are you reading from stdin or checking `$ATTUNE_PARAMETER_FILE`?
|
||||
- Are you reading until the delimiter `---ATTUNE_PARAMS_END---`?
|
||||
- Are you reading stdin until EOF?
|
||||
|
||||
**Debug:**
|
||||
```bash
|
||||
@@ -1100,15 +1086,13 @@ sys.stdout.flush() # Ensure output is written immediately
|
||||
|
||||
**Check:**
|
||||
- Are secrets configured for the action?
|
||||
- Are you reading past the delimiter in stdin?
|
||||
- Secrets come as JSON after `---ATTUNE_PARAMS_END---`
|
||||
- Secrets are merged into the parameters document — access them by key name just like regular parameters
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
content = sys.stdin.read()
|
||||
parts = content.split('---ATTUNE_PARAMS_END---')
|
||||
params = json.loads(parts[0].strip()) if parts[0].strip() else {}
|
||||
secrets = json.loads(parts[1].strip()) if len(parts) > 1 else {}
|
||||
content = sys.stdin.read().strip()
|
||||
params = json.loads(content) if content else {}
|
||||
api_key = params.get('api_key', '') # Secrets are regular keys
|
||||
```
|
||||
|
||||
### Environment variables are missing
|
||||
|
||||
@@ -50,11 +50,10 @@ parameter_format: yaml
|
||||
```
|
||||
|
||||
```python
|
||||
# Read from stdin
|
||||
# Read from stdin (secrets are merged into parameters)
|
||||
import sys, json
|
||||
content = sys.stdin.read()
|
||||
params_str = content.split('---ATTUNE_PARAMS_END---')[0]
|
||||
params = json.loads(params_str)
|
||||
content = sys.stdin.read().strip()
|
||||
params = json.loads(content) if content else {}
|
||||
api_key = params['api_key'] # Secure!
|
||||
```
|
||||
|
||||
@@ -119,11 +118,9 @@ import sys
|
||||
import json
|
||||
|
||||
def read_params():
|
||||
content = sys.stdin.read()
|
||||
parts = content.split('---ATTUNE_PARAMS_END---')
|
||||
params = json.loads(parts[0].strip()) if parts[0].strip() else {}
|
||||
secrets = json.loads(parts[1].strip()) if len(parts) > 1 and parts[1].strip() else {}
|
||||
return {**params, **secrets}
|
||||
"""Read parameters from stdin. Secrets are already merged in."""
|
||||
content = sys.stdin.read().strip()
|
||||
return json.loads(content) if content else {}
|
||||
|
||||
params = read_params()
|
||||
api_key = params['api_key']
|
||||
@@ -279,10 +276,10 @@ ps aux | grep attune-worker
|
||||
- Read from stdin or parameter file
|
||||
|
||||
```python
|
||||
# Read parameters from stdin
|
||||
# Read parameters from stdin (secrets are merged in)
|
||||
import sys, json
|
||||
content = sys.stdin.read()
|
||||
params = json.loads(content.split('---ATTUNE_PARAMS_END---')[0])
|
||||
content = sys.stdin.read().strip()
|
||||
params = json.loads(content) if content else {}
|
||||
api_key = params['api_key'] # Secure!
|
||||
```
|
||||
|
||||
|
||||
@@ -57,9 +57,9 @@ parameters:
|
||||
# my_action.py
|
||||
import sys, json
|
||||
|
||||
# Read from stdin (the default)
|
||||
content = sys.stdin.read()
|
||||
params = json.loads(content.split('---ATTUNE_PARAMS_END---')[0])
|
||||
# Read from stdin (the default) — secrets are merged into parameters
|
||||
content = sys.stdin.read().strip()
|
||||
params = json.loads(content) if content else {}
|
||||
api_key = params['api_key'] # Secure - not in process list!
|
||||
```
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ Environment variables provide execution context and configuration:
|
||||
**Security**: ✅ **High** - Not visible in process listings
|
||||
**Use Case**: Sensitive data, structured parameters, credentials
|
||||
|
||||
Parameters are serialized in the specified format and passed via stdin. A delimiter `---ATTUNE_PARAMS_END---` separates parameters from secrets.
|
||||
Parameters are serialized in the specified format and written to stdin as a single document (secrets are merged into the parameters), followed by a newline, then stdin is closed.
|
||||
|
||||
**Example** (this is the default):
|
||||
```yaml
|
||||
@@ -56,9 +56,7 @@ parameter_format: json
|
||||
|
||||
**Stdin content (JSON format)**:
|
||||
```
|
||||
{"message":"Hello","count":42,"enabled":true}
|
||||
---ATTUNE_PARAMS_END---
|
||||
{"api_key":"secret123","db_password":"pass456"}
|
||||
{"message":"Hello","count":42,"enabled":true,"api_key":"secret123","db_password":"pass456"}
|
||||
```
|
||||
|
||||
**Python script example**:
|
||||
@@ -68,23 +66,13 @@ import sys
|
||||
import json
|
||||
|
||||
def read_stdin_params():
|
||||
"""Read parameters and secrets from stdin."""
|
||||
content = sys.stdin.read()
|
||||
parts = content.split('---ATTUNE_PARAMS_END---')
|
||||
|
||||
# Parse parameters
|
||||
params = json.loads(parts[0].strip()) if parts[0].strip() else {}
|
||||
|
||||
# Parse secrets (if present)
|
||||
secrets = {}
|
||||
if len(parts) > 1 and parts[1].strip():
|
||||
secrets = json.loads(parts[1].strip())
|
||||
|
||||
return params, secrets
|
||||
"""Read parameters from stdin. Secrets are already merged into parameters."""
|
||||
content = sys.stdin.read().strip()
|
||||
return json.loads(content) if content else {}
|
||||
|
||||
params, secrets = read_stdin_params()
|
||||
params = read_stdin_params()
|
||||
message = params.get('message', 'default')
|
||||
api_key = secrets.get('api_key')
|
||||
api_key = params.get('api_key')
|
||||
print(f"Message: {message}")
|
||||
```
|
||||
|
||||
@@ -373,10 +361,10 @@ If you were previously passing data as environment variables, you now have two o
|
||||
|
||||
**Option 1: Move to Parameters** (for action data):
|
||||
```python
|
||||
# Read from stdin
|
||||
# Read from stdin (secrets are merged into parameters)
|
||||
import sys, json
|
||||
content = sys.stdin.read()
|
||||
params = json.loads(content.split('---ATTUNE_PARAMS_END---')[0])
|
||||
content = sys.stdin.read().strip()
|
||||
params = json.loads(content) if content else {}
|
||||
value = params.get('key')
|
||||
```
|
||||
|
||||
@@ -434,16 +422,9 @@ import json
|
||||
import requests
|
||||
|
||||
def read_stdin_params():
|
||||
"""Read parameters and secrets from stdin."""
|
||||
content = sys.stdin.read()
|
||||
parts = content.split('---ATTUNE_PARAMS_END---')
|
||||
|
||||
params = json.loads(parts[0].strip()) if parts[0].strip() else {}
|
||||
secrets = {}
|
||||
if len(parts) > 1 and parts[1].strip():
|
||||
secrets = json.loads(parts[1].strip())
|
||||
|
||||
return {**params, **secrets}
|
||||
"""Read parameters from stdin. Secrets are already merged into parameters."""
|
||||
content = sys.stdin.read().strip()
|
||||
return json.loads(content) if content else {}
|
||||
|
||||
def main():
|
||||
params = read_stdin_params()
|
||||
|
||||
@@ -241,12 +241,9 @@ import json
|
||||
import sys
|
||||
|
||||
def read_stdin_params():
|
||||
"""Read parameters from stdin."""
|
||||
content = sys.stdin.read()
|
||||
parts = content.split('---ATTUNE_PARAMS_END---')
|
||||
params = json.loads(parts[0].strip()) if parts[0].strip() else {}
|
||||
secrets = json.loads(parts[1].strip()) if len(parts) > 1 and parts[1].strip() else {}
|
||||
return {**params, **secrets}
|
||||
"""Read parameters from stdin. Secrets are already merged into parameters."""
|
||||
content = sys.stdin.read().strip()
|
||||
return json.loads(content) if content else {}
|
||||
|
||||
def main():
|
||||
params = read_stdin_params()
|
||||
|
||||
@@ -102,11 +102,8 @@ message='It'\''s working!'
|
||||
```bash
|
||||
#!/bin/sh
|
||||
|
||||
# Read DOTENV-formatted parameters from stdin
|
||||
# Read DOTENV-formatted parameters from stdin until EOF
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
*"---ATTUNE_PARAMS_END---"*) break ;;
|
||||
esac
|
||||
[ -z "$line" ] && continue
|
||||
|
||||
key="${line%%=*}"
|
||||
@@ -137,9 +134,6 @@ headers_file=$(mktemp)
|
||||
query_params_file=$(mktemp)
|
||||
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
*"---ATTUNE_PARAMS_END---"*) break ;;
|
||||
esac
|
||||
[ -z "$line" ] && continue
|
||||
|
||||
key="${line%%=*}"
|
||||
@@ -212,14 +206,13 @@ This combination provides several security benefits:
|
||||
|
||||
### Secret Handling
|
||||
|
||||
Secrets are passed separately via stdin after parameters. They are never included in environment variables or parameter files.
|
||||
Secrets are merged into the parameters document before delivery. They appear as regular key-value pairs in the DOTENV output. Secrets are never included in environment variables or parameter files.
|
||||
|
||||
```bash
|
||||
# Parameters are sent first
|
||||
# All parameters (including secrets) delivered as a single document
|
||||
url='https://api.example.com'
|
||||
---ATTUNE_PARAMS_END---
|
||||
# Then secrets (as JSON)
|
||||
{"api_key":"secret123","password":"hunter2"}
|
||||
api_key='secret123'
|
||||
password='hunter2'
|
||||
```
|
||||
|
||||
## Examples
|
||||
@@ -257,7 +250,6 @@ method='POST'
|
||||
query_params.limit='10'
|
||||
query_params.page='1'
|
||||
url='https://api.example.com/users'
|
||||
---ATTUNE_PARAMS_END---
|
||||
```
|
||||
|
||||
### Example 2: Simple Shell Action
|
||||
@@ -281,7 +273,6 @@ parameter_format: dotenv
|
||||
```bash
|
||||
greeting='Hello'
|
||||
name='Alice'
|
||||
---ATTUNE_PARAMS_END---
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
@@ -290,13 +281,11 @@ name='Alice'
|
||||
|
||||
**Symptom:** Action receives empty or incorrect parameter values.
|
||||
|
||||
**Solution:** Ensure you're reading until the `---ATTUNE_PARAMS_END---` delimiter:
|
||||
**Solution:** Ensure you're reading stdin until EOF:
|
||||
|
||||
```bash
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
*"---ATTUNE_PARAMS_END---"*) break ;; # Important!
|
||||
esac
|
||||
[ -z "$line" ] && continue
|
||||
# ... parse line
|
||||
done
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user