Validate and limit service inputs with decorators

This topic describes how you can use decorators within IAG 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.

1---
2- name: A Simple Hello World Example
3 hosts: localhost
4 gather_facts: no
5 tasks:
6 - name: Just Say Hello
7 debug:
8 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.

1{
2 "$id": "root",
3 "$schema": "http://json-schema.org/draft-07/schema#",
4 "type": "object",
5 "properties": {
6 "caller": {
7 "type": "string",
8 "enum": ["documentation", "learner"]
9 }
10 },
11 "required": [
12 "caller"
13 ],
14 "additionalProperties": false
15}

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 IAG 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:

1{
2 "$id": "root",
3 "$schema": "http://json-schema.org/draft-07/schema#",
4 "type": "object",
5 "properties": {
6 "verbose": {
7 "type": "boolean"
8 },
9 "host": {
10 "type": "string"
11 }
12 },
13 "required": [
14 "host"
15 ]
16}

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 IAG passes the flag to your script depends on whether the service has a decorator with the property typed as boolean:

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

1import argparse
2
3def main():
4 parser = argparse.ArgumentParser()
5 parser.add_argument('--verbose', action='store_true', help="Enable verbose output")
6 parser.add_argument('--host', required=True, help="Target host")
7 args = parser.parse_args()
8
9 if args.verbose:
10 print(f"Connecting to {args.host} with verbose output enabled")
11 else:
12 print(f"Connecting to {args.host}")

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

1import argparse
2
3def main():
4 parser = argparse.ArgumentParser()
5 parser.add_argument('--verbose', default='false', help="Enable verbose output")
6 parser.add_argument('--host', required=True, help="Target host")
7 args = parser.parse_args()
8
9 if args.verbose.lower() == 'true':
10 print(f"Connecting to {args.host} with verbose output enabled")
11 else:
12 print(f"Connecting to {args.host}")

Learn more

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