mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-04-25 03:41:25 -07:00
The goal of breaking apart the base_parser() function is to get rid of a bunch of conditionals and parameters in the code and, instead, make code look like simple composition. When splitting, a choice had to be made as to whether this would operate by side effect (modifying a passed in parser) or side effect-free (returning a new parser everytime). Making a version that's side-effect-free appears to be fighting with the optparse API (it wants to work by creating a parser object, configuring the object, and then parsing the arguments with it) so instead, make it clear that our helper functions are modifying the passed in parser by (1) not returning the parser and (2) changing the function names to be more clear that it is operating by side-effect. Also move all of the generic optparse code, along with the argument context classes, into a new subdirectory.
185 lines
8.2 KiB
Python
185 lines
8.2 KiB
Python
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
|
# Copyright: (c) 2018, Ansible Project
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
from __future__ import (absolute_import, division, print_function)
|
|
__metaclass__ = type
|
|
|
|
import os
|
|
import stat
|
|
|
|
from ansible import context
|
|
from ansible.arguments import optparse_helpers as opt_help
|
|
from ansible.cli import CLI
|
|
from ansible.errors import AnsibleError, AnsibleOptionsError
|
|
from ansible.executor.playbook_executor import PlaybookExecutor
|
|
from ansible.playbook.block import Block
|
|
from ansible.playbook.play_context import PlayContext
|
|
from ansible.utils.display import Display
|
|
|
|
display = Display()
|
|
|
|
|
|
class PlaybookCLI(CLI):
|
|
''' the tool to run *Ansible playbooks*, which are a configuration and multinode deployment system.
|
|
See the project home page (https://docs.ansible.com) for more information. '''
|
|
|
|
def init_parser(self):
|
|
|
|
# create parser for CLI options
|
|
super(PlaybookCLI, self).init_parser(
|
|
usage="%prog [options] playbook.yml [playbook2 ...]",
|
|
desc="Runs Ansible playbooks, executing the defined tasks on the targeted hosts.")
|
|
|
|
opt_help.add_connect_options(self.parser)
|
|
opt_help.add_meta_options(self.parser)
|
|
opt_help.add_runas_options(self.parser)
|
|
opt_help.add_subset_options(self.parser)
|
|
opt_help.add_check_options(self.parser)
|
|
opt_help.add_inventory_options(self.parser)
|
|
opt_help.add_runtask_options(self.parser)
|
|
opt_help.add_vault_options(self.parser)
|
|
opt_help.add_fork_options(self.parser)
|
|
opt_help.add_module_options(self.parser)
|
|
|
|
# ansible playbook specific opts
|
|
self.parser.add_option('--list-tasks', dest='listtasks', action='store_true',
|
|
help="list all tasks that would be executed")
|
|
self.parser.add_option('--list-tags', dest='listtags', action='store_true',
|
|
help="list all available tags")
|
|
self.parser.add_option('--step', dest='step', action='store_true',
|
|
help="one-step-at-a-time: confirm each task before running")
|
|
self.parser.add_option('--start-at-task', dest='start_at_task',
|
|
help="start the playbook at the task matching this name")
|
|
|
|
def post_process_args(self, options, args):
|
|
options, args = super(PlaybookCLI, self).post_process_args(options, args)
|
|
|
|
if len(args) == 0:
|
|
raise AnsibleOptionsError("You must specify a playbook file to run")
|
|
|
|
display.verbosity = options.verbosity
|
|
self.validate_conflicts(options, runas_opts=True, vault_opts=True, fork_opts=True)
|
|
|
|
options = self.normalize_become_options(options)
|
|
|
|
return options, args
|
|
|
|
def run(self):
|
|
|
|
super(PlaybookCLI, self).run()
|
|
|
|
# Note: slightly wrong, this is written so that implicit localhost
|
|
# manages passwords
|
|
sshpass = None
|
|
becomepass = None
|
|
passwords = {}
|
|
|
|
# initial error check, to make sure all specified playbooks are accessible
|
|
# before we start running anything through the playbook executor
|
|
for playbook in context.CLIARGS['args']:
|
|
if not os.path.exists(playbook):
|
|
raise AnsibleError("the playbook: %s could not be found" % playbook)
|
|
if not (os.path.isfile(playbook) or stat.S_ISFIFO(os.stat(playbook).st_mode)):
|
|
raise AnsibleError("the playbook: %s does not appear to be a file" % playbook)
|
|
|
|
# don't deal with privilege escalation or passwords when we don't need to
|
|
if not (context.CLIARGS['listhosts'] or context.CLIARGS['listtasks'] or
|
|
context.CLIARGS['listtags'] or context.CLIARGS['syntax']):
|
|
(sshpass, becomepass) = self.ask_passwords()
|
|
passwords = {'conn_pass': sshpass, 'become_pass': becomepass}
|
|
|
|
loader, inventory, variable_manager = self._play_prereqs()
|
|
|
|
# (which is not returned in list_hosts()) is taken into account for
|
|
# warning if inventory is empty. But it can't be taken into account for
|
|
# checking if limit doesn't match any hosts. Instead we don't worry about
|
|
# limit if only implicit localhost was in inventory to start with.
|
|
#
|
|
# Fix this when we rewrite inventory by making localhost a real host (and thus show up in list_hosts())
|
|
hosts = self.get_host_list(inventory, context.CLIARGS['subset'])
|
|
|
|
# flush fact cache if requested
|
|
if context.CLIARGS['flush_cache']:
|
|
self._flush_cache(inventory, variable_manager)
|
|
|
|
# create the playbook executor, which manages running the plays via a task queue manager
|
|
pbex = PlaybookExecutor(playbooks=context.CLIARGS['args'], inventory=inventory,
|
|
variable_manager=variable_manager, loader=loader,
|
|
passwords=passwords)
|
|
|
|
results = pbex.run()
|
|
|
|
if isinstance(results, list):
|
|
for p in results:
|
|
|
|
display.display('\nplaybook: %s' % p['playbook'])
|
|
for idx, play in enumerate(p['plays']):
|
|
if play._included_path is not None:
|
|
loader.set_basedir(play._included_path)
|
|
else:
|
|
pb_dir = os.path.realpath(os.path.dirname(p['playbook']))
|
|
loader.set_basedir(pb_dir)
|
|
|
|
msg = "\n play #%d (%s): %s" % (idx + 1, ','.join(play.hosts), play.name)
|
|
mytags = set(play.tags)
|
|
msg += '\tTAGS: [%s]' % (','.join(mytags))
|
|
|
|
if context.CLIARGS['listhosts']:
|
|
playhosts = set(inventory.get_hosts(play.hosts))
|
|
msg += "\n pattern: %s\n hosts (%d):" % (play.hosts, len(playhosts))
|
|
for host in playhosts:
|
|
msg += "\n %s" % host
|
|
|
|
display.display(msg)
|
|
|
|
all_tags = set()
|
|
if context.CLIARGS['listtags'] or context.CLIARGS['listtasks']:
|
|
taskmsg = ''
|
|
if context.CLIARGS['listtasks']:
|
|
taskmsg = ' tasks:\n'
|
|
|
|
def _process_block(b):
|
|
taskmsg = ''
|
|
for task in b.block:
|
|
if isinstance(task, Block):
|
|
taskmsg += _process_block(task)
|
|
else:
|
|
if task.action == 'meta':
|
|
continue
|
|
|
|
all_tags.update(task.tags)
|
|
if context.CLIARGS['listtasks']:
|
|
cur_tags = list(mytags.union(set(task.tags)))
|
|
cur_tags.sort()
|
|
if task.name:
|
|
taskmsg += " %s" % task.get_name()
|
|
else:
|
|
taskmsg += " %s" % task.action
|
|
taskmsg += "\tTAGS: [%s]\n" % ', '.join(cur_tags)
|
|
|
|
return taskmsg
|
|
|
|
all_vars = variable_manager.get_vars(play=play)
|
|
play_context = PlayContext(play=play)
|
|
for block in play.compile():
|
|
block = block.filter_tagged_tasks(play_context, all_vars)
|
|
if not block.has_tasks():
|
|
continue
|
|
taskmsg += _process_block(block)
|
|
|
|
if context.CLIARGS['listtags']:
|
|
cur_tags = list(mytags.union(all_tags))
|
|
cur_tags.sort()
|
|
taskmsg += " TASK TAGS: [%s]\n" % ', '.join(cur_tags)
|
|
|
|
display.display(taskmsg)
|
|
|
|
return 0
|
|
else:
|
|
return results
|
|
|
|
def _flush_cache(self, inventory, variable_manager):
|
|
for host in inventory.list_hosts():
|
|
hostname = host.get_name()
|
|
variable_manager.clear_facts(hostname)
|