diff --git a/lib/ansible/cli/__init__.py b/lib/ansible/cli/__init__.py index daf14aab1f7..c2ae98b1b80 100644 --- a/lib/ansible/cli/__init__.py +++ b/lib/ansible/cli/__init__.py @@ -174,32 +174,34 @@ class CLI(object): options.become_method = 'su' - def validate_conflicts(self): + def validate_conflicts(self, vault_opts=False, runas_opts=False): ''' check for conflicting options ''' op = self.options - # Check for vault related conflicts - if (op.ask_vault_pass and op.vault_password_file): - self.parser.error("--ask-vault-pass and --vault-password-file are mutually exclusive") - + if vault_opts: + # Check for vault related conflicts + if (op.ask_vault_pass and op.vault_password_file): + self.parser.error("--ask-vault-pass and --vault-password-file are mutually exclusive") - # Check for privilege escalation conflicts - if (op.su or op.su_user or op.ask_su_pass) and \ - (op.sudo or op.sudo_user or op.ask_sudo_pass) or \ - (op.su or op.su_user or op.ask_su_pass) and \ - (op.become or op.become_user or op.become_ask_pass) or \ - (op.sudo or op.sudo_user or op.ask_sudo_pass) and \ - (op.become or op.become_user or op.become_ask_pass): - self.parser.error("Sudo arguments ('--sudo', '--sudo-user', and '--ask-sudo-pass') " - "and su arguments ('-su', '--su-user', and '--ask-su-pass') " - "and become arguments ('--become', '--become-user', and '--ask-become-pass')" - " are exclusive of each other") + if runas_opts: + # Check for privilege escalation conflicts + if (op.su or op.su_user or op.ask_su_pass) and \ + (op.sudo or op.sudo_user or op.ask_sudo_pass) or \ + (op.su or op.su_user or op.ask_su_pass) and \ + (op.become or op.become_user or op.become_ask_pass) or \ + (op.sudo or op.sudo_user or op.ask_sudo_pass) and \ + (op.become or op.become_user or op.become_ask_pass): + + self.parser.error("Sudo arguments ('--sudo', '--sudo-user', and '--ask-sudo-pass') " + "and su arguments ('-su', '--su-user', and '--ask-su-pass') " + "and become arguments ('--become', '--become-user', and '--ask-become-pass')" + " are exclusive of each other") @staticmethod def base_parser(usage="", output_opts=False, runas_opts=False, meta_opts=False, runtask_opts=False, vault_opts=False, - async_opts=False, connect_opts=False, subset_opts=False, check_opts=False, diff_opts=False, epilog=None): + async_opts=False, connect_opts=False, subset_opts=False, check_opts=False, diff_opts=False, epilog=None, fork_opts=False): ''' create an options parser for most ansible scripts ''' #FIXME: implemente epilog parsing @@ -211,8 +213,6 @@ class CLI(object): help="verbose mode (-vvv for more, -vvvv to enable connection debugging)") if runtask_opts: - parser.add_option('-f','--forks', dest='forks', default=C.DEFAULT_FORKS, type='int', - help="specify number of parallel processes to use (default=%s)" % C.DEFAULT_FORKS) parser.add_option('-i', '--inventory-file', dest='inventory', help="specify inventory host file (default=%s)" % C.DEFAULT_HOST_LIST, default=C.DEFAULT_HOST_LIST) @@ -223,6 +223,10 @@ class CLI(object): parser.add_option('-e', '--extra-vars', dest="extra_vars", action="append", help="set additional variables as key=value or YAML/JSON", default=[]) + if fork_opts: + parser.add_option('-f','--forks', dest='forks', default=C.DEFAULT_FORKS, type='int', + help="specify number of parallel processes to use (default=%s)" % C.DEFAULT_FORKS) + if vault_opts: parser.add_option('--ask-vault-pass', default=False, dest='ask_vault_pass', action='store_true', help='ask for vault password') @@ -273,7 +277,7 @@ class CLI(object): if connect_opts: parser.add_option('-k', '--ask-pass', default=False, dest='ask_pass', action='store_true', help='ask for connection password') - parser.add_option('--private-key', default=C.DEFAULT_PRIVATE_KEY_FILE, dest='private_key_file', + parser.add_option('--private-key','--key-file', default=C.DEFAULT_PRIVATE_KEY_FILE, dest='private_key_file', help='use this file to authenticate the connection') parser.add_option('-u', '--user', default=C.DEFAULT_REMOTE_USER, dest='remote_user', help='connect as this user (default=%s)' % C.DEFAULT_REMOTE_USER) @@ -282,7 +286,6 @@ class CLI(object): parser.add_option('-T', '--timeout', default=C.DEFAULT_TIMEOUT, type='int', dest='timeout', help="override the connection timeout in seconds (default=%s)" % C.DEFAULT_TIMEOUT) - if async_opts: parser.add_option('-P', '--poll', default=C.DEFAULT_POLL_INTERVAL, type='int', dest='poll_interval', diff --git a/lib/ansible/cli/adhoc.py b/lib/ansible/cli/adhoc.py index 9a055e5e625..0d63a562842 100644 --- a/lib/ansible/cli/adhoc.py +++ b/lib/ansible/cli/adhoc.py @@ -60,7 +60,7 @@ class AdHocCLI(CLI): raise AnsibleOptionsError("Missing target hosts") self.display.verbosity = self.options.verbosity - self.validate_conflicts() + self.validate_conflicts(runas_opts=True, vault_opts=True) return True diff --git a/lib/ansible/cli/playbook.py b/lib/ansible/cli/playbook.py index 1c59d5dde6f..e10ffb71d0b 100644 --- a/lib/ansible/cli/playbook.py +++ b/lib/ansible/cli/playbook.py @@ -55,6 +55,7 @@ class PlaybookCLI(CLI): diff_opts=True, runtask_opts=True, vault_opts=True, + fork_opts=True, ) # ansible playbook specific opts @@ -76,7 +77,7 @@ class PlaybookCLI(CLI): raise AnsibleOptionsError("You must specify a playbook file to run") self.display.verbosity = self.options.verbosity - self.validate_conflicts() + self.validate_conflicts(runas_opts=True, vault_opts=True) def run(self): diff --git a/lib/ansible/cli/pull.py b/lib/ansible/cli/pull.py index 6b087d4ec06..0275a8c3475 100644 --- a/lib/ansible/cli/pull.py +++ b/lib/ansible/cli/pull.py @@ -21,12 +21,15 @@ import os import random import shutil import socket +import sys from ansible import constants as C from ansible.errors import AnsibleError, AnsibleOptionsError from ansible.cli import CLI from ansible.utils.display import Display from ansible.utils.vault import read_vault_file +from ansible.utils.plugins import module_finder +from ansible.utils.cmd_functions import run_cmd ######################################################## @@ -48,6 +51,7 @@ class PullCLI(CLI): usage='%prog [options]', connect_opts=True, vault_opts=True, + runtask_opts=True, ) # options unique to pull @@ -87,7 +91,7 @@ class PullCLI(CLI): raise AnsibleOptionsError("Unsuported repo module %s, choices are %s" % (self.options.module_name, ','.join(self.SUPPORTED_REPO_MODULES))) self.display.verbosity = self.options.verbosity - self.validate_conflicts() + self.validate_conflicts(vault_opts=True) def run(self): ''' use Runner lib to do SSH things ''' @@ -120,12 +124,12 @@ class PullCLI(CLI): if self.options.accept_host_key: repo_opts += ' accept_hostkey=yes' - if self.options.key_file: - repo_opts += ' key_file=%s' % options.key_file + if self.options.private_key_file: + repo_opts += ' key_file=%s' % self.options.private_key_file - path = utils.plugins.module_finder.find_plugin(options.module_name) + path = module_finder.find_plugin(self.options.module_name) if path is None: - raise AnsibleOptionsError(("module '%s' not found.\n" % options.module_name)) + raise AnsibleOptionsError(("module '%s' not found.\n" % self.options.module_name)) bin_path = os.path.dirname(os.path.abspath(__file__)) cmd = '%s/ansible localhost -i "%s" %s -m %s -a "%s"' % ( @@ -141,7 +145,7 @@ class PullCLI(CLI): time.sleep(self.options.sleep); # RUN the Checkout command - rc, out, err = cmd_functions.run_cmd(cmd, live=True) + rc, out, err = run_cmd(cmd, live=True) if rc != 0: if self.options.force: @@ -173,7 +177,7 @@ class PullCLI(CLI): os.chdir(self.options.dest) # RUN THE PLAYBOOK COMMAND - rc, out, err = cmd_functions.run_cmd(cmd, live=True) + rc, out, err = run_cmd(cmd, live=True) if self.options.purge: os.chdir('/') diff --git a/lib/ansible/utils/cmd_functions.py b/lib/ansible/utils/cmd_functions.py new file mode 100644 index 00000000000..7cb1912d07c --- /dev/null +++ b/lib/ansible/utils/cmd_functions.py @@ -0,0 +1,59 @@ +# (c) 2012, Michael DeHaan +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +import os +import sys +import shlex +import subprocess +import select + +def run_cmd(cmd, live=False, readsize=10): + + #readsize = 10 + + cmdargs = shlex.split(cmd) + p = subprocess.Popen(cmdargs, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + stdout = '' + stderr = '' + rpipes = [p.stdout, p.stderr] + while True: + rfd, wfd, efd = select.select(rpipes, [], rpipes, 1) + + if p.stdout in rfd: + dat = os.read(p.stdout.fileno(), readsize) + if live: + sys.stdout.write(dat) + stdout += dat + if dat == '': + rpipes.remove(p.stdout) + if p.stderr in rfd: + dat = os.read(p.stderr.fileno(), readsize) + stderr += dat + if live: + sys.stdout.write(dat) + if dat == '': + rpipes.remove(p.stderr) + # only break out if we've emptied the pipes, or there is nothing to + # read from and the process has finished. + if (not rpipes or not rfd) and p.poll() is not None: + break + # Calling wait while there are still pipes to read can cause a lock + elif not rpipes and p.poll() == None: + p.wait() + + return p.returncode, stdout, stderr