Difficulty: Intermediate
Time: Approximately 10 minutes
In this exercise you will write a task with metadata.
Complete the following before you start this lesson:
Task metadata files describe task parameters, validate input, and control how tasks are executed. Adding metadata to your tasks helps others use them. You write metadata for a task in JSON and save it with the same name as your task. For example, if you write a task called great_metadata.py
its corresponding metadata file is named great_metadata.json
.
Write a simple task that formats the parameters a user gives it.
-
Save the following file to
modules/exercise8/tasks/great_metadata.py
:#!/usr/bin/env python """ This script prints the values and types passed to it via standard in. It will return a JSON string with a parameters key containing objects that describe the parameters passed by the user. """ import json import sys def make_serializable(object): if sys.version_info[0] > 2: return object if isinstance(object, unicode): return object.encode('utf-8') else: return object data = json.load(sys.stdin) message = """ Congratulations on writing your metadata! Here are the keys and the values that you passed to this task. """ result = {'message': message, 'parameters': []} for key in data: k = make_serializable(key) v = make_serializable(data[key]) t = type(data[key]).__name__ param = {'key': k, 'value': v, 'type': t} result['parameters'].append(param) print(json.dumps(result))
-
Write the accompanying metadata and save the file to
modules/exercise8/tasks/great_metadata.json
. Specify the parameters as types such as"type": "Integer"
which help validate user input as anInteger
.{ "description": "An exercise in writing great metadata", "input_method": "stdin", "parameters": { "name": { "description": "The description for the 'name' parameter", "type": "String" }, "recursive": { "description": "The description for the 'recursive' parameter", "type": "Boolean" }, "action": { "description": "The description for the 'action' parameter", "type": "Enum[stop, start, restart]" }, "timeout": { "description": "The description for the 'timeout' parameter", "type": "Optional[Integer]" } } }
-
Run 'bolt task show' to verify that the task you created appears with its description in the list of available tasks.
bolt task show --modulepath ./modules
The result:
... exercise8::great_metadata An exercise in writing great metadata facter_task Inspect the value of system facts install_puppet Install the puppet 5 agent package ...
-
Run
bolt task show <task-name>
to view the parameters that your task uses.bolt task show exercise8::great_metadata --modulepath ./modules
The result:
exercise8::great_metadata - An exercise in writing great metadata USAGE: bolt task run --nodes <node-name> exercise8::great_metadata name=<value> recursive=<value> action=<value> timeout=<value> [--noop] PARAMETERS: - name: String The description for the 'name' parameter - recursive: Boolean The description for the 'recursive' parameter - action: Enum[stop, start, restart] The description for the 'action' parameter - timeout: Optional[Integer] The description for the 'timeout' parameter MODULE: tasks-hands-on-lab/08-writing-advanced-tasks/modules/exercise8
Bolt can use the types that you have specified in your metadata to validate parameters passed to your task. Run your task with an incorrect value for the action
parameter and see what happens.
-
Run your task and pass the following parameters as a JSON string.
bolt task run exercise8::great_metadata --nodes all --modulepath ./modules --params '{"name":"poppey","action":"spinach","recursive":true}'
The result:
Task exercise8::great_metadata: parameter 'action' expects a match for Enum['restart', 'start', 'stop'], got 'spinach'
-
Correct the value for the action parameter and run the task again.
bolt task run exercise8::great_metadata --nodes node1 --modulepath ./modules --params '{"name":"poppey","action":"start","recursive":true}'
The result:
Started on node1... Finished on node1: { "message": "\nCongratulations on writing your metadata! Here are\nthe keys and the values that you passed to this task.\n", "parameters": [ { "type": "unicode", "value": "start", "key": "action" }, { "type": "unicode", "value": "exercise8::great_metadata", "key": "_task" }, { "type": "unicode", "value": "poppey", "key": "name" }, { "type": "bool", "value": true, "key": "recursive" } ] } Successful on 1 node: node1 Ran on 1 node in 0.97 seconds
You can write tasks that support no-operation mode (noop). You use noop to see what changes a task would make, but without taking any action.
-
Create the metadata for the new task and save it to
modules/exercise8/tasks/file.json
:{ "description": "Write content to a file.", "supports_noop": true, "parameters": { "filename": { "description": "the file to write to", "type": "String[1]" }, "content": { "description": "The content to write", "type": "String" } } }
-
Save the following file to
modules/exercise8/tasks/file.py
. This task uses input from stdin. When a user passes the--noop
flag, the JSON object from stdin will contain the_noop
key with a value of True.#!/usr/bin/env python """ This script attempts the creation of a file on a target system. It will return JSON string describing the actions it performed. If passed "{"_noop": True}" on stdin it will check to see if it can write the file but not actually write it. """ import json import os import sys params = json.load(sys.stdin) filename = params['filename'] content = params['content'] noop = params.get('_noop', False) exitcode = 0 def make_error(msg): error = { "_error": { "kind": "file_error", "msg": msg, "details": {}, } } return error try: if noop: path = os.path.abspath(os.path.join(filename, os.pardir)) file_exists = os.access(filename, os.F_OK) file_writable = os.access(filename, os.W_OK) path_writable = os.access(path, os.W_OK) if path_writable == False: exitcode = 1 result = make_error("Path %s is not writable" % path) elif file_exists == True and file_writable == False: exitcode = 1 result = make_error("File %s is not writable" % filename) else: result = { "success": True , '_noop': True } else: with open(filename, 'w') as fh: fh.write(content) result = { "success": True } except Exception as e: exitcode = 1 result = make_error("Could not open file %s: %s" % (filename, str(e))) print(json.dumps(result)) exit(exitcode)
-
Test the task with the
--noop
flag.bolt task run exercise8::file --nodes node1 --modulepath ./modules content=Hello_World filename=/tmp/hello_world --noop
The result:
Started on node1... Finished on node1: { "_noop": true, "success": true } Successful on 1 node: node1 Ran on 1 node in 0.96 seconds
-
Run the task again without
--noop
and see the task create the file successfully.bolt task run exercise8::file --nodes node1 --modulepath ./modules content=Hello_World filename=/tmp/hello_world
The result:
Started on node1... Finished on node1: { "success": true } Successful on 1 node: node1 Ran on 1 node in 0.98 seconds
Now that you know how to write task metadata and include the --noop
flag you can move on to: