[backport][2.10][PR #70446] Refactor Python API examples and docs (#70850)

* Add boilerplate snippet into `examples/`

It is a partial backport of #70224

(partially cherry picked from commit 4816bb4f43)

* 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.

(cherry picked from commit 20bb915092)
This commit is contained in:
Sviatoslav Sydorenko 2020-07-29 22:51:21 +02:00 committed by GitHub
parent 4280efccc4
commit be28e98cf2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 60 additions and 115 deletions

View file

@ -23,92 +23,10 @@ or have access control and logging demands, please see the `Ansible Tower docume
Python API example Python API example
================== ==================
This example is a simple demonstration that shows how to minimally run a couple of tasks:: 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)
.. 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. .. note:: Ansible emits warnings and errors via the display object, which prints directly to stdout, stderr and the Ansible log.

View file

@ -1,40 +1,60 @@
#!/usr/bin/env python #!/usr/bin/env python
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.executor.task_queue_manager import TaskQueueManager
from ansible.module_utils.common.collections import ImmutableDict
from ansible.inventory.manager import InventoryManager from ansible.inventory.manager import InventoryManager
from ansible.parsing.dataloader import DataLoader from ansible.parsing.dataloader import DataLoader
from ansible.playbook.play import Play from ansible.playbook.play import Play
from ansible.plugins.callback import CallbackBase from ansible.plugins.callback import CallbackBase
from ansible.vars.manager import VariableManager from ansible.vars.manager import VariableManager
from ansible import context from ansible import context
from ansible.module_utils.common.collections import ImmutableDict
# Create a callback object so we can capture the output # Create a callback plugin so we can capture the output
class ResultsCollector(CallbackBase): 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): def __init__(self, *args, **kwargs):
super(ResultsCollector, self).__init__(*args, **kwargs) super(ResultsCollectorJSONCallback, self).__init__(*args, **kwargs)
self.host_ok = {} self.host_ok = {}
self.host_unreachable = {} self.host_unreachable = {}
self.host_failed = {} self.host_failed = {}
def v2_runner_on_unreachable(self, result): 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): 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): 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(): def main():
host_list = ['localhost', 'www.example.com', 'www.google.com'] 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 # 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) become_method=None, become_user=None, check=False, diff=False)
# required for # required for
# https://github.com/ansible/ansible/blob/devel/lib/ansible/inventory/manager.py#L204 # https://github.com/ansible/ansible/blob/devel/lib/ansible/inventory/manager.py#L204
sources = ','.join(host_list) sources = ','.join(host_list)
@ -42,20 +62,19 @@ def main():
sources += ',' sources += ','
# initialize needed objects # initialize needed objects
loader = DataLoader() loader = DataLoader() # Takes care of finding and reading yaml, json and ini files
passwords = dict() passwords = dict(vault_pass='secret')
# Instantiate our ResultsCollector for handling results as # Instantiate our ResultsCollectorJSONCallback for handling results as they come in. Ansible expects this to be one of its main display outlets
# they come in. Ansible expects this to be one of its main results_callback = ResultsCollectorJSONCallback()
# display outlets.
callback = ResultsCollector()
# 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) 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) variable_manager = VariableManager(loader=loader, inventory=inventory)
# Instantiate task queue manager, which takes care of forking # instantiate task queue manager, which takes care of forking and setting up all objects to iterate over host list and tasks
# and setting up all objects to iterate over host list and tasks.
# IMPORTANT: This also adds library dirs paths to the module loader # IMPORTANT: This also adds library dirs paths to the module loader
# IMPORTANT: and so it must be initialized before calling `Play.load()`. # IMPORTANT: and so it must be initialized before calling `Play.load()`.
tqm = TaskQueueManager( tqm = TaskQueueManager(
@ -63,37 +82,47 @@ def main():
variable_manager=variable_manager, variable_manager=variable_manager,
loader=loader, loader=loader,
passwords=passwords, 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( play_source = dict(
name="Ansible Play", name="Ansible Play",
hosts=host_list, hosts=host_list,
gather_facts='no', 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) play = Play().load(play_source, variable_manager=variable_manager, loader=loader)
# actually run it # Actually run it
try: try:
result = tqm.run(play) result = tqm.run(play) # most interesting data for a play is actually sent to the callback's methods
finally: finally:
if tqm is not None: # we always need to cleanup child procs and the structures we use to communicate with them
tqm.cleanup() tqm.cleanup()
if loader: if loader:
loader.cleanup_all_tmp_files() loader.cleanup_all_tmp_files()
# Remove ansible tmpdir
shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)
print("UP ***********") 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('{0} >>> {1}'.format(host, result._result['stdout']))
print("FAILED *******") 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('{0} >>> {1}'.format(host, result._result['msg']))
print("DOWN *********") 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'])) print('{0} >>> {1}'.format(host, result._result['msg']))

View file

@ -11,8 +11,6 @@ examples/play.yml shebang
examples/scripts/ConfigureRemotingForAnsible.ps1 pslint:PSCustomUseLiteralPath examples/scripts/ConfigureRemotingForAnsible.ps1 pslint:PSCustomUseLiteralPath
examples/scripts/upgrade_to_ps3.ps1 pslint:PSCustomUseLiteralPath examples/scripts/upgrade_to_ps3.ps1 pslint:PSCustomUseLiteralPath
examples/scripts/upgrade_to_ps3.ps1 pslint:PSUseApprovedVerbs examples/scripts/upgrade_to_ps3.ps1 pslint:PSUseApprovedVerbs
examples/scripts/uptime.py future-import-boilerplate
examples/scripts/uptime.py metaclass-boilerplate
hacking/build-ansible.py shebang # only run by release engineers, Python 3.6+ required hacking/build-ansible.py shebang # only run by release engineers, Python 3.6+ required
hacking/build_library/build_ansible/announce.py compile-2.6!skip # release process only, 3.6+ required hacking/build_library/build_ansible/announce.py compile-2.6!skip # release process only, 3.6+ required
hacking/build_library/build_ansible/announce.py compile-2.7!skip # release process only, 3.6+ required hacking/build_library/build_ansible/announce.py compile-2.7!skip # release process only, 3.6+ required