Validate and limit service inputs with decorators

This topic describes how you can use decorators within Gateway to limit the inputs passed into a service that you execute using the iagctl run service command.

A decorator defines the inputs a service expects: which parameters it accepts, what type they must be, and which are required. When a user runs a service through a workflow, the decorator validates that the correct inputs were provided before execution begins. A single decorator can be shared across multiple services that accept the same inputs, reducing duplication. Think of a decorator as a form definition that you attach to one or more services.

Decorators in playbooks

To best understand decorators, consider an Ansible Playbook service called simple-ansible that uses the following playbook.

---
- name: A Simple Hello World Example
hosts: localhost
gather_facts: no
tasks:
- name: Just Say Hello
debug:
msg: "Hello Mr. gateway this is from '{{ caller }}'"

Notice that the example playbook takes in a single variable called caller. When running the service, you can use the --set flag to pass a value for caller to the playbook.

iagctl run ansible-playbook simple-ansible --set caller=documentation

JSON Schema in decorators

Suppose that you want to limit the types of inputs that can pass to the playbook using the --set flag. Decorators allow you to limit the available parameters by using defined JSON Schema standards.

Consider the example JSON Schema shown below that limits the available inputs to a key of caller with values of documentation or learner.

{
"$id": "root",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"caller": {
"type": "string",
"enum": ["documentation", "learner"]
}
},
"required": [
"caller"
],
"additionalProperties": false
}

You can explore all available options within JSON Schema by referencing the official spec.

Create a decorator resource

First, save the JSON schema above as a file called simple-deco.json.

Next, create a decorator resource called simple-deco within Gateway using the json schema file that you created in the previous step.

iagctl create decorator simple-deco --schema @./simple-deco.json

Create a gateway service with decorators

Once you create the decorator resource, create a service that uses it. The following example creates an Ansible Playbook service with the simple-deco decorator.

iagctl create ansible-playbook ansible-with-deco --repository gateway-resources --working-dir ansibleplaybook --playbook hello-world.yml --decorator simple-deco
Output:
Successfully created the Ansible playbook(s)
Name: ansible-with-deco
Repo Name: gateway-resources
Working Dir: ansibleplaybook
Playbook(s): hello-world.yml
Decorator: simple-deco
Description:
Tags:
Runtime Arguments:

Set the --use flag when using the service run command to get basic, high-level information about the decorator.

iagctl run service ansible-playbook ansible-with-deco --use
Output:
Decoration Usage:
-----------------------------
The following keys can be set on the command line via the set command.
+----------+--------+-------------+--------------------+
| NAME | TYPE | DESCRIPTION | EXAMPLE |
+----------+--------+-------------+--------------------+
| caller** | string | | --set caller=value |
+----------+--------+-------------+--------------------+
** is Required

Successful decoration

When you pass in a valid value for caller and run the Ansible playbook service, the playbook succeeds.

iagctl run service ansible-playbook ansible-with-deco --set caller=documentation

Decoration error

If you try to pass in a value for caller that is not documentation or learner, you receive an error.

iagctl run service ansible-playbook ansible-with-deco --set caller=someWrongInput
Output:
Error: failed to run ansible playbook 'ansible-with-deco': decoration errors have been encountered: should be one of ["documentation", "learner"] /caller

Additionally, if you try to set any value for a key that is not caller, an error returns.

iagctl run service ansible-playbook ansible-with-deco --set caller=documentation --set someBadKey=value
Output:
Error: failed to run ansible playbook 'ansible-with-deco': decoration errors have been encountered: extra input found someBadKey

Boolean properties in Python script services

Decorators support boolean as a property type for Python script services. Boolean properties enable flag-style argument passing — instead of --set verbose=true, you pass --set verbose to set the flag, and omit it entirely to leave it unset.

Define a boolean property

To define a boolean property, set "type": "boolean" in the decorator schema:

{
"$id": "root",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"verbose": {
"type": "boolean"
},
"host": {
"type": "string"
}
},
"required": [
"host"
]
}

Pass boolean arguments at runtime

When a property is defined as boolean in the decorator, use bare --set key syntax to pass the flag. Omit the key entirely to leave it unset.

# Sets --verbose flag; script receives: python main.py --verbose --host='10.0.0.1'
iagctl run service python-script my-script --set verbose --set host=10.0.0.1
# Omits --verbose flag; script receives: python main.py --host='10.0.0.1'
iagctl run service python-script my-script --set host=10.0.0.1

How Gateway passes the flag to your script depends on whether the service has a decorator with the property typed as boolean:

With a decorator, Gateway passes the flag as --verbose. Parse it in your script using action='store_true':

import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--verbose', action='store_true', help="Enable verbose output")
parser.add_argument('--host', required=True, help="Target host")
args = parser.parse_args()
if args.verbose:
print(f"Connecting to {args.host} with verbose output enabled")
else:
print(f"Connecting to {args.host}")

Without a decorator, Gateway passes the flag as --verbose=true. Parse it as a string value:

import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--verbose', default='false', help="Enable verbose output")
parser.add_argument('--host', required=True, help="Target host")
args = parser.parse_args()
if args.verbose.lower() == 'true':
print(f"Connecting to {args.host} with verbose output enabled")
else:
print(f"Connecting to {args.host}")

Pass large values as files in Python script services

Gateway 5.5.2+

Gateway passes decorator property values to a Python script as CLI arguments by default. Linux limits the total size of CLI arguments to roughly 128 KB, so a property with a large value—a full running-config, device session details, or query results—can exceed that limit and cause the script to fail.

To avoid this, mark a decorator property as file-backed. Instead of passing the value inline as a CLI argument, Gateway writes it to a temporary file at execution time and passes your script an environment variable that contains the file’s path. Gateway deletes the temporary file after the script finishes running, whether it succeeds or fails.

Define a file-backed property

File-backed properties are supported for Python script services only.

To mark a property as file-backed, add two keywords to it in the decorator schema:

  • x-itential-payload-type: "file" tells Gateway to write the value to a temporary file instead of passing it inline.
  • x-itential-payload-target sets the name of the environment variable that Gateway uses to pass the file path to your script.

The property keeps "type": "string" because the underlying content is still text, typically JSON. The x-itential-payload-* keywords only change how Gateway delivers the value to your script — they don’t change the property’s type or validation.

{
"properties": {
"pre_check_result_json": {
"type": "string",
"description": "Pre-check results, written to a temp file by Gateway rather than inlined",
"x-itential-payload-type": "file",
"x-itential-payload-target": "PRE_CHECK_RESULT_FILE"
}
}
}

Read a file-backed value in your script

At runtime, check for the environment variable you set in x-itential-payload-target. If it’s present, open the file at that path to read the value.

import os
path = os.environ.get("PRE_CHECK_RESULT_FILE")
if path:
with open(path, "r") as f:
pre_check_result_json = f.read()

Because a file-backed property is still a normal decorator property, you can still pass it inline with --set pre_check_result_json=<value> for local testing or smaller payloads. Gateway only redirects the value to a file when needed. Write your script to check for the CLI argument first, then fall back to the file path, so the same script works whether the value arrives inline or as a file:

def load_pre_check_result(arg_value):
if arg_value:
return arg_value # passed inline via --set pre_check_result_json
path = os.environ.get("PRE_CHECK_RESULT_FILE")
if not path:
raise RuntimeError("pre_check_result_json was not provided as an argument or via PRE_CHECK_RESULT_FILE")
with open(path, "r") as f:
return f.read()

Learn more

For more information on decorator operations, see the following iagctl CLI commands: