From 20bb91509279ff91bf8410972827a95e1af55075 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Thu, 23 Jul 2020 18:24:02 +0200 Subject: [PATCH] Refactor Python API examples and docs PR #70446: it's a follow-up for #70445. It includes a merge of `examples/scripts/uptime.py` and a similar code snippet from `docs/docsite/rst/dev_guide/developing_api.rst`. This patch also changes the docs RST file to include contents of the example file instead of holding a copy of a similar code. --- docs/docsite/rst/dev_guide/developing_api.rst | 88 +------------------ examples/scripts/uptime.py | 82 +++++++++++------ 2 files changed, 57 insertions(+), 113 deletions(-) diff --git a/docs/docsite/rst/dev_guide/developing_api.rst b/docs/docsite/rst/dev_guide/developing_api.rst index 5e08c7cc60b..c7505a747b7 100644 --- a/docs/docsite/rst/dev_guide/developing_api.rst +++ b/docs/docsite/rst/dev_guide/developing_api.rst @@ -23,92 +23,10 @@ or have access control and logging demands, please see the `Ansible Tower docume Python API example ================== -This example is a simple demonstration that shows how to minimally run a couple of tasks:: - - #!/usr/bin/env python - - import json - import shutil - from ansible.module_utils.common.collections import ImmutableDict - from ansible.parsing.dataloader import DataLoader - from ansible.vars.manager import VariableManager - from ansible.inventory.manager import InventoryManager - from ansible.playbook.play import Play - from ansible.executor.task_queue_manager import TaskQueueManager - from ansible.plugins.callback import CallbackBase - from ansible import context - import ansible.constants as C - - class ResultCallback(CallbackBase): - """A sample callback plugin used for performing an action as results come in - - If you want to collect all results into a single object for processing at - the end of the execution, look into utilizing the ``json`` callback plugin - or writing your own custom callback plugin - """ - def v2_runner_on_ok(self, result, **kwargs): - """Print a json representation of the result - - This method could store the result in an instance attribute for retrieval later - """ - host = result._host - print(json.dumps({host.name: result._result}, indent=4)) - - # since the API is constructed for CLI it expects certain options to always be set in the context object - context.CLIARGS = ImmutableDict(connection='local', module_path=['/to/mymodules'], forks=10, become=None, - become_method=None, become_user=None, check=False, diff=False) - - # initialize needed objects - loader = DataLoader() # Takes care of finding and reading yaml, json and ini files - passwords = dict(vault_pass='secret') - - # Instantiate our ResultCallback for handling results as they come in. Ansible expects this to be one of its main display outlets - results_callback = ResultCallback() - - # create inventory, use path to host config file as source or hosts in a comma separated string - inventory = InventoryManager(loader=loader, sources='localhost,') - - # variable manager takes care of merging all the different sources to give you a unified view of variables available in each context - variable_manager = VariableManager(loader=loader, inventory=inventory) - - # Instantiate task queue manager, which takes care of forking - # and setting up all objects to iterate over host list and tasks. - # IMPORTANT: This also adds library dirs paths to the module loader - # IMPORTANT: and so it must be initialized before calling `Play.load()`. - tqm = TaskQueueManager( - inventory=inventory, - variable_manager=variable_manager, - loader=loader, - passwords=passwords, - stdout_callback=results_callback, # Use our custom callback instead of the ``default`` callback plugin, which prints to stdout - ) - - # create data structure that represents our play, including tasks, this is basically what our YAML loader does internally. - play_source = dict( - name = "Ansible Play", - hosts = 'localhost', - gather_facts = 'no', - tasks = [ - dict(action=dict(module='shell', args='ls'), register='shell_out'), - dict(action=dict(module='debug', args=dict(msg='{{shell_out.stdout}}'))) - ] - ) - - # Create play object, playbook objects use .load instead of init or new methods, - # this will also automatically create the task objects from the info provided in play_source - play = Play().load(play_source, variable_manager=variable_manager, loader=loader) - - # Actually run it - try: - result = tqm.run(play) # most interesting data for a play is actually sent to the callback's methods - finally: - # we always need to cleanup child procs and the structures we use to communicate with them - if tqm is not None: - tqm.cleanup() - - # Remove ansible tmpdir - shutil.rmtree(C.DEFAULT_LOCAL_TMP, True) +This example is a simple demonstration that shows how to minimally run a couple of tasks: +.. literalinclude:: ../../../../examples/scripts/uptime.py + :language: python .. note:: Ansible emits warnings and errors via the display object, which prints directly to stdout, stderr and the Ansible log. diff --git a/examples/scripts/uptime.py b/examples/scripts/uptime.py index 6542ff55b39..d2cb3b0c52a 100755 --- a/examples/scripts/uptime.py +++ b/examples/scripts/uptime.py @@ -3,41 +3,58 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import json +import shutil + +import ansible.constants as C from ansible.executor.task_queue_manager import TaskQueueManager +from ansible.module_utils.common.collections import ImmutableDict from ansible.inventory.manager import InventoryManager from ansible.parsing.dataloader import DataLoader from ansible.playbook.play import Play from ansible.plugins.callback import CallbackBase from ansible.vars.manager import VariableManager from ansible import context -from ansible.module_utils.common.collections import ImmutableDict -# Create a callback object so we can capture the output -class ResultsCollector(CallbackBase): +# Create a callback plugin so we can capture the output +class ResultsCollectorJSONCallback(CallbackBase): + """A sample callback plugin used for performing an action as results come in. + + If you want to collect all results into a single object for processing at + the end of the execution, look into utilizing the ``json`` callback plugin + or writing your own custom callback plugin. + """ def __init__(self, *args, **kwargs): - super(ResultsCollector, self).__init__(*args, **kwargs) + super(ResultsCollectorJSONCallback, self).__init__(*args, **kwargs) self.host_ok = {} self.host_unreachable = {} self.host_failed = {} def v2_runner_on_unreachable(self, result): - self.host_unreachable[result._host.get_name()] = result + host = result._host + self.host_unreachable[host.get_name()] = result def v2_runner_on_ok(self, result, *args, **kwargs): - self.host_ok[result._host.get_name()] = result + """Print a json representation of the result. + + Also, store the result in an instance attribute for retrieval later + """ + host = result._host + self.host_ok[host.get_name()] = result + print(json.dumps({host.name: result._result}, indent=4)) def v2_runner_on_failed(self, result, *args, **kwargs): - self.host_failed[result._host.get_name()] = result + host = result._host + self.host_failed[host.get_name()] = result def main(): host_list = ['localhost', 'www.example.com', 'www.google.com'] # since the API is constructed for CLI it expects certain options to always be set in the context object - context.CLIARGS = ImmutableDict(connection='smart', module_path=['/usr/share/ansible'], forks=10, become=None, + context.CLIARGS = ImmutableDict(connection='smart', module_path=['/to/mymodules', '/usr/share/ansible'], forks=10, become=None, become_method=None, become_user=None, check=False, diff=False) - # required for # https://github.com/ansible/ansible/blob/devel/lib/ansible/inventory/manager.py#L204 sources = ','.join(host_list) @@ -45,20 +62,19 @@ def main(): sources += ',' # initialize needed objects - loader = DataLoader() - passwords = dict() + loader = DataLoader() # Takes care of finding and reading yaml, json and ini files + passwords = dict(vault_pass='secret') - # Instantiate our ResultsCollector for handling results as - # they come in. Ansible expects this to be one of its main - # display outlets. - callback = ResultsCollector() + # Instantiate our ResultsCollectorJSONCallback for handling results as they come in. Ansible expects this to be one of its main display outlets + results_callback = ResultsCollectorJSONCallback() - # create inventory and pass to var manager + # create inventory, use path to host config file as source or hosts in a comma separated string inventory = InventoryManager(loader=loader, sources=sources) + + # variable manager takes care of merging all the different sources to give you a unified view of variables available in each context variable_manager = VariableManager(loader=loader, inventory=inventory) - # Instantiate task queue manager, which takes care of forking - # and setting up all objects to iterate over host list and tasks. + # instantiate task queue manager, which takes care of forking and setting up all objects to iterate over host list and tasks # IMPORTANT: This also adds library dirs paths to the module loader # IMPORTANT: and so it must be initialized before calling `Play.load()`. tqm = TaskQueueManager( @@ -66,37 +82,47 @@ def main(): variable_manager=variable_manager, loader=loader, passwords=passwords, - stdout_callback=callback, + stdout_callback=results_callback, # Use our custom callback instead of the ``default`` callback plugin, which prints to stdout ) - # create play with tasks + # create data structure that represents our play, including tasks, this is basically what our YAML loader does internally. play_source = dict( name="Ansible Play", hosts=host_list, gather_facts='no', - tasks=[dict(action=dict(module='command', args=dict(cmd='/usr/bin/uptime')))] + tasks=[ + dict(action=dict(module='shell', args='ls'), register='shell_out'), + dict(action=dict(module='debug', args=dict(msg='{{shell_out.stdout}}'))), + dict(action=dict(module='command', args=dict(cmd='/usr/bin/uptime'))), + ] ) + + # Create play object, playbook objects use .load instead of init or new methods, + # this will also automatically create the task objects from the info provided in play_source play = Play().load(play_source, variable_manager=variable_manager, loader=loader) - # actually run it + # Actually run it try: - result = tqm.run(play) + result = tqm.run(play) # most interesting data for a play is actually sent to the callback's methods finally: - if tqm is not None: - tqm.cleanup() + # we always need to cleanup child procs and the structures we use to communicate with them + tqm.cleanup() if loader: loader.cleanup_all_tmp_files() + # Remove ansible tmpdir + shutil.rmtree(C.DEFAULT_LOCAL_TMP, True) + print("UP ***********") - for host, result in callback.host_ok.items(): + for host, result in results_callback.host_ok.items(): print('{0} >>> {1}'.format(host, result._result['stdout'])) print("FAILED *******") - for host, result in callback.host_failed.items(): + for host, result in results_callback.host_failed.items(): print('{0} >>> {1}'.format(host, result._result['msg'])) print("DOWN *********") - for host, result in callback.host_unreachable.items(): + for host, result in results_callback.host_unreachable.items(): print('{0} >>> {1}'.format(host, result._result['msg']))