Actions Developer Guide

Advanced Runtime Examples

See advanced examples for chaining APIs, WebAssembly, and secure webhooks.

Advanced Runtime Examples

GraphQL API Chaining

The api engine supports declarative GraphQL queries alongside REST endpoints, enabling seamless integration with GraphQL servers.

# actions/query_infrastructure.toml
name = "query_infrastructure"
description = "Query infrastructure state via GraphQL"

[[input]]
name = "environment"
description = "Target environment"
type = "string"
required = true

[vault_secrets.metrics_token]
collection = "observability"
secret = "metrics_api_token"

[runtime]
engine = "http"

# Native GraphQL support
[[runtime.steps]]
method = "POST"
url = "https://api.internal/graphql"
graphql_query = """
  query GetDeployments($env: String!) {
    deployments(environment: $env) {
      id
      name
      status
      lastDeployed
    }
  }
"""

[runtime.steps.graphql_variables]
env = "{{ input.environment }}"

[[runtime.steps]]
method = "POST"
url = "https://metrics.internal/graphql"
headers = { "Authorization" = "Bearer ${secrets.metrics_token}" }
graphql_query = """
  query GetMetrics($env: String!) {
    metrics(environment: $env) {
      cpu
      memory
      requests
    }
  }
"""

[runtime.steps.graphql_variables]
env = "{{ input.environment }}"

REST API Chaining with Templates

Chain multiple REST API calls with input and secret templating for orchestrated workflows.

# actions/provision_database.toml
name = "provision_database"
description = "Provision a database and configure backups via API chaining"

[[input]]
name = "db_name"
description = "Database name"
type = "string"
required = true

[[input]]
name = "instance_type"
description = "Instance type (small, medium, large)"
type = "string"
default = "medium"

[[mount]]
resource_type = "payload"
origin_resource = "infrastructure"
filename = "create_db_payload.json"
mount_path = "create_db_payload.json"

[vault_secrets.cloud_api_key]
collection = "cloud_credentials"
secret = "api_key"

[vault_secrets.provisioning_token]
collection = "internal_services"
secret = "provisioning_webhook_token"

[runtime]
engine = "http"

# Step 1: Create database instance
[[runtime.steps]]
method = "POST"
url = "https://api.cloud.internal/v1/databases"
headers = { "Authorization" = "Bearer ${secrets.cloud_api_key}", "Content-Type" = "application/json" }
body = "@create_db_payload.json"

# Step 2: Enable automated backups
[[runtime.steps]]
method = "POST"
url = "https://api.cloud.internal/v1/databases/{{ input.db_name }}/backups"
headers = { "Authorization" = "Bearer ${secrets.cloud_api_key}" }
body = """
{
  "schedule": "0 2 * * *",
  "retentionDays": 30,
  "encrypted": true,
  "storageClass": "standard"
}
"""

# Step 3: Notify provisioning system
[[runtime.steps]]
method = "POST"
url = "https://provisioning.internal/webhooks/db-created"
headers = { "X-Webhook-Secret" = "${secrets.provisioning_token}" }
body = """
{
  "databaseName": "{{ input.db_name }}",
  "timestamp": "{{ .now }}"
}
"""

Secure Signed Webhook Headers

The http engine supports HMAC-SHA256 signed payloads for securely triggering external CI/CD pipelines.

# actions/trigger_deployment.toml
name = "trigger_deployment"
description = "Securely trigger a deployment via signed webhook"

[[input]]
name = "version"
description = "Version to deploy"
type = "string"
required = true

[[input]]
name = "environment"
description = "Target environment"
type = "string"
required = true

[vault_secrets.github_webhook_secret]
collection = "github_credentials"
secret = "webhook_secret"

[vault_secrets.github_token]
collection = "github_credentials"
secret = "api_token"

[runtime]
engine = "http"

[[runtime.steps]]
method = "POST"
url = "https://github.com/repos/org/service/dispatches"
signature_secret = "${secrets.github_webhook_secret}"
signature_header = "X-Hub-Signature-256"
headers = { "Authorization" = "Bearer ${secrets.github_token}" }

When executing via webhook (fetch mode) directly from the Controller:

rescile-ce runner \
  --inputs version=1.2.3,environment=production \
  "http://127.0.0.1:7600/api/actions/module_name/trigger_deployment"

The runner automatically:

  1. Serializes inputs into a JSON payload
  2. Computes HMAC-SHA256 signature using the secret
  3. Sends POST request with signature header: X-Hub-Signature-256: sha256=<hex_signature>
  4. GitHub (or any compatible system) verifies the signature before processing

Tip: You can access the dynamic URLs of the current rescile environment via the rescile_endpoints map (e.g., {{ rescile_endpoints.controller }}). These are automatically populated, but can be overridden by setting the RESCILE_CTX_URL_CONTROLLER, RESCILE_CTX_URL_MCP, and RESCILE_CTX_URL_VAULT environment variables.

Dynamic Service Tunneling & Proxy Routing

Actions that act as long-running services (e.g., API Gateways) can declare network exposure using the [runtime.expose] block.

You can expose services either via dynamic HTTP ports or via secure Unix Domain Sockets (recommended on Linux for zero TCP overhead and enhanced isolation).

Exposing via Unix Socket (Linux only):

name = "start_acme_gateway"
run_mode = "service"

[runtime.expose]
protocol = "socket"
env_var = "UNIX_SOCKET" # Injects the generated socket path into the sandbox

[runtime]
engine = "nix"
flake = "./flake.nix"
exec = ["python3", "acme_gateway.py"]

Exposing via Dynamic HTTP Port:

[runtime.expose]
protocol = "http"
env_var = "PORT" # Injects the dynamic port into the sandbox

How it works:

  1. The runner either dynamically allocates a free port on the host (e.g., 38192) or generates a temporary Unix Domain Socket path.
  2. It injects this value into the sandbox as the configured environment variable (UNIX_SOCKET or PORT).
  3. The runner reports the dynamically allocated endpoint back to the Controller’s Active Execution state.
  4. Your application frontend (proxy.toml) routes directly to the action, oblivious to whether it’s running via port or socket:
    [[route]]
    path = "/acme/*"
    target = "action://cert-manager/start_acme_gateway/acme"
    

WebAssembly (WASM) Execution

Execute compiled WebAssembly modules with strict sandboxing and efficient resource isolation. WASM modules must export a _start function and are executed with wasmtime.

# actions/validate_config.toml
name = "validate_config"
description = "Validate configuration using a WASM module"

[[input]]
name = "config_path"
description = "Path to configuration file"
type = "string"
required = true
default = "config.json"

[runtime]
engine = "wasi"
wasm = "./validator.wasm"
files = ["./config.json", "./schema.json"]

[[mount]]
resource_type = "config"
origin_resource = "infrastructure"
filename = "config.json"
mount_path = "config.json"

[[mount]]
resource_type = "schema"
origin_resource = "infrastructure"
filename = "schema.json"
mount_path = "schema.json"

Building WASM modules for rescile:

# Rust example with wasm-pack
wasm-pack build --target wasi --release

# The _start function serves as the entry point
#[no_mangle]
pub extern "C" fn _start() {
    // Read from /workspace/config.json
    // Validate against /workspace/schema.json
    // Exit with status 0 on success, 1 on failure
}

WASM execution provides:

  • Zero external dependencies: No syscall overhead
  • Strict I/O isolation: Only explicit mounts are accessible
  • High performance: Near-native execution speed
  • Deterministic behavior: Same code always produces same output

Combining Engines: Multi-Stage Pipelines

Complex workflows can chain multiple engines in sequence:

# actions/deploy_with_validation.toml
name = "deploy_with_validation"
description = "Validate config (WASM) → Query state (GraphQL) → Deploy (Nix)"

[[input]]
name = "version"
type = "string"
required = true

# Stage 1: Native WASM Engine
[[stages]]
name = "validate"
[stages.runtime]
engine = "wasi"
wasm = "./validator.wasm"
args = ["{{ input.version }}"]

# Stage 2: Native API Engine
[[stages]]
name = "query_state"
[stages.runtime]
engine = "http"
[[stages.runtime.steps]]
method = "POST"
url = "https://api.internal/graphql"
graphql_query = """
  query GetState($version: String!) {
    state(version: $version) {
      status
    }
  }
"""
[stages.runtime.steps.graphql_variables]
version = "{{ input.version }}"

# Stage 3: Native Nix Engine
[[stages]]
name = "deploy"
[stages.runtime]
engine = "nix"
flake = "./flake.nix"
exec = [
    ["nix", "flake", "update"],
    ["nix", "develop", "--command", "deploy.sh", "{{ input.version }}"]
]