one cli to bind them all

This commit is contained in:
Brian Coca 2015-04-30 21:22:23 -04:00
commit 9de6fea2fa
13 changed files with 298 additions and 57 deletions

View file

@ -34,11 +34,12 @@ from ansible.utils.unicode import to_bytes
class SortedOptParser(optparse.OptionParser): class SortedOptParser(optparse.OptionParser):
'''Optparser which sorts the options by opt before outputting --help''' '''Optparser which sorts the options by opt before outputting --help'''
def format_help(self, formatter=None): #FIXME: epilog parsing: OptionParser.format_epilog = lambda self, formatter: self.epilog
def format_help(self, formatter=None, epilog=None):
self.option_list.sort(key=operator.methodcaller('get_opt_string')) self.option_list.sort(key=operator.methodcaller('get_opt_string'))
return optparse.OptionParser.format_help(self, formatter=None) return optparse.OptionParser.format_help(self, formatter=None)
#TODO: move many cli only functions in this file into the CLI class
class CLI(object): class CLI(object):
''' code behind bin/ansible* programs ''' ''' code behind bin/ansible* programs '''
@ -71,8 +72,7 @@ class CLI(object):
break break
if not self.action: if not self.action:
self.parser.print_help() raise AnsibleOptionsError("Missing required action")
raise AnsibleError("Missing required action")
def execute(self): def execute(self):
""" """
@ -184,36 +184,37 @@ class CLI(object):
" are exclusive of each other") " are exclusive of each other")
@staticmethod @staticmethod
def base_parser(usage="", output_opts=False, runas_opts=False, meta_opts=False, 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): async_opts=False, connect_opts=False, subset_opts=False, check_opts=False, diff_opts=False, epilog=None):
''' create an options parser for most ansible scripts ''' ''' create an options parser for most ansible scripts '''
parser = SortedOptParser(usage, version=CLI.version("%prog")) #FIXME: implemente epilog parsing
#OptionParser.format_epilog = lambda self, formatter: self.epilog
parser.add_option('-u', '--user', default=C.DEFAULT_REMOTE_USER, dest='remote_user', # base opts
help='connect as this user (default=%s)' % C.DEFAULT_REMOTE_USER) parser = SortedOptParser(usage, version=CLI.version("%prog"))
parser.add_option('-v','--verbose', dest='verbosity', default=0, action="count", parser.add_option('-v','--verbose', dest='verbosity', default=0, action="count",
help="verbose mode (-vvv for more, -vvvv to enable connection debugging)") help="verbose mode (-vvv for more, -vvvv to enable connection debugging)")
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 runtask_opts:
parser.add_option('-i', '--inventory-file', dest='inventory', parser.add_option('-f','--forks', dest='forks', default=C.DEFAULT_FORKS, type='int',
help="specify inventory host file (default=%s)" % C.DEFAULT_HOST_LIST, help="specify number of parallel processes to use (default=%s)" % C.DEFAULT_FORKS)
default=C.DEFAULT_HOST_LIST) parser.add_option('-i', '--inventory-file', dest='inventory',
parser.add_option('-k', '--ask-pass', default=False, dest='ask_pass', action='store_true', help="specify inventory host file (default=%s)" % C.DEFAULT_HOST_LIST,
help='ask for connection password') default=C.DEFAULT_HOST_LIST)
parser.add_option('--private-key', default=C.DEFAULT_PRIVATE_KEY_FILE, dest='private_key_file', parser.add_option('--list-hosts', dest='listhosts', action='store_true',
help='use this file to authenticate the connection') help='outputs a list of matching hosts; does not execute anything else')
parser.add_option('--ask-vault-pass', default=False, dest='ask_vault_pass', action='store_true', parser.add_option('-M', '--module-path', dest='module_path',
help='ask for vault password') help="specify path(s) to module library (default=%s)" % C.DEFAULT_MODULE_PATH, default=None)
parser.add_option('--vault-password-file', default=C.DEFAULT_VAULT_PASSWORD_FILE, parser.add_option('-e', '--extra-vars', dest="extra_vars", action="append",
dest='vault_password_file', help="vault password file") help="set additional variables as key=value or YAML/JSON", default=[])
parser.add_option('--list-hosts', dest='listhosts', action='store_true',
help='outputs a list of matching hosts; does not execute anything else') if vault_opts:
parser.add_option('-M', '--module-path', dest='module_path', parser.add_option('--ask-vault-pass', default=False, dest='ask_vault_pass', action='store_true',
help="specify path(s) to module library (default=%s)" % C.DEFAULT_MODULE_PATH, help='ask for vault password')
default=None) parser.add_option('--vault-password-file', default=C.DEFAULT_VAULT_PASSWORD_FILE,
parser.add_option('-e', '--extra-vars', dest="extra_vars", action="append", dest='vault_password_file', help="vault password file")
help="set additional variables as key=value or YAML/JSON", default=[])
if subset_opts: if subset_opts:
parser.add_option('-l', '--limit', default=C.DEFAULT_SUBSET, dest='subset', parser.add_option('-l', '--limit', default=C.DEFAULT_SUBSET, dest='subset',
@ -256,6 +257,12 @@ class CLI(object):
if connect_opts: 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',
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)
parser.add_option('-c', '--connection', dest='connection', default=C.DEFAULT_TRANSPORT, parser.add_option('-c', '--connection', dest='connection', default=C.DEFAULT_TRANSPORT,
help="connection type to use (default=%s)" % C.DEFAULT_TRANSPORT) help="connection type to use (default=%s)" % C.DEFAULT_TRANSPORT)
parser.add_option('-T', '--timeout', default=C.DEFAULT_TIMEOUT, type='int', dest='timeout', parser.add_option('-T', '--timeout', default=C.DEFAULT_TIMEOUT, type='int', dest='timeout',
@ -292,7 +299,7 @@ class CLI(object):
def version(prog): def version(prog):
''' return ansible version ''' ''' return ansible version '''
result = "{0} {1}".format(prog, __version__) result = "{0} {1}".format(prog, __version__)
gitinfo = _gitinfo() gitinfo = CLI._gitinfo()
if gitinfo: if gitinfo:
result = result + " {0}".format(gitinfo) result = result + " {0}".format(gitinfo)
result = result + "\n configured module search path = %s" % C.DEFAULT_MODULE_PATH result = result + "\n configured module search path = %s" % C.DEFAULT_MODULE_PATH
@ -369,7 +376,7 @@ class CLI(object):
def _gitinfo(): def _gitinfo():
basedir = os.path.join(os.path.dirname(__file__), '..', '..', '..') basedir = os.path.join(os.path.dirname(__file__), '..', '..', '..')
repo_path = os.path.join(basedir, '.git') repo_path = os.path.join(basedir, '.git')
result = _git_repo_info(repo_path) result = CLI._git_repo_info(repo_path)
submodules = os.path.join(basedir, '.gitmodules') submodules = os.path.join(basedir, '.gitmodules')
if not os.path.exists(submodules): if not os.path.exists(submodules):
return result return result
@ -378,7 +385,7 @@ class CLI(object):
tokens = line.strip().split(' ') tokens = line.strip().split(' ')
if tokens[0] == 'path': if tokens[0] == 'path':
submodule_path = tokens[2] submodule_path = tokens[2]
submodule_info =_git_repo_info(os.path.join(basedir, submodule_path, '.git')) submodule_info = CLI._git_repo_info(os.path.join(basedir, submodule_path, '.git'))
if not submodule_info: if not submodule_info:
submodule_info = ' not found - use git submodule update --init ' + submodule_path submodule_info = ' not found - use git submodule update --init ' + submodule_path
result += "\n {0}: {1}".format(submodule_path, submodule_info) result += "\n {0}: {1}".format(submodule_path, submodule_info)

View file

@ -16,17 +16,14 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>. # along with Ansible. If not, see <http://www.gnu.org/licenses/>.
######################################################## ########################################################
import os
import sys
from ansible import constants as C from ansible import constants as C
from ansible.errors import * from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.executor.task_queue_manager import TaskQueueManager from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.inventory import Inventory from ansible.inventory import Inventory
from ansible.parsing import DataLoader from ansible.parsing import DataLoader
from ansible.parsing.splitter import parse_kv from ansible.parsing.splitter import parse_kv
from ansible.playbook.play import Play from ansible.playbook.play import Play
from ansible.utils.cli import CLI from ansible.cli import CLI
from ansible.utils.display import Display from ansible.utils.display import Display
from ansible.utils.vault import read_vault_file from ansible.utils.vault import read_vault_file
from ansible.vars import VariableManager from ansible.vars import VariableManager
@ -46,6 +43,8 @@ class AdHocCLI(CLI):
output_opts=True, output_opts=True,
connect_opts=True, connect_opts=True,
check_opts=True, check_opts=True,
runtask_opts=True,
vault_opts=True,
) )
# options unique to ansible ad-hoc # options unique to ansible ad-hoc
@ -101,7 +100,7 @@ class AdHocCLI(CLI):
if self.options.listhosts: if self.options.listhosts:
for host in hosts: for host in hosts:
self.display.display(' %s' % host.name) self.display.display(' %s' % host)
return 0 return 0
if self.options.module_name in C.MODULE_REQUIRE_ARGS and not self.options.module_args: if self.options.module_name in C.MODULE_REQUIRE_ARGS and not self.options.module_args:

83
v2/ansible/cli/doc.py Normal file
View file

@ -0,0 +1,83 @@
# (c) 2014, James Tanner <tanner.jc@gmail.com>
#
# 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 <http://www.gnu.org/licenses/>.
#
# ansible-vault is a script that encrypts/decrypts YAML files. See
# http://docs.ansible.com/playbooks_vault.html for more details.
import os
import sys
import traceback
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.cli import CLI
#from ansible.utils import module_docs
class DocCLI(CLI):
""" Vault command line class """
BLACKLIST_EXTS = ('.pyc', '.swp', '.bak', '~', '.rpm')
IGNORE_FILES = [ "COPYING", "CONTRIBUTING", "LICENSE", "README", "VERSION"]
_ITALIC = re.compile(r"I\(([^)]+)\)")
_BOLD = re.compile(r"B\(([^)]+)\)")
_MODULE = re.compile(r"M\(([^)]+)\)")
_URL = re.compile(r"U\(([^)]+)\)")
_CONST = re.compile(r"C\(([^)]+)\)")
PAGER = 'less'
LESS_OPTS = 'FRSX' # -F (quit-if-one-screen) -R (allow raw ansi control chars)
# -S (chop long lines) -X (disable termcap init and de-init)
def parse(self):
self.parser = optparse.OptionParser(
version=version("%prog"),
usage='usage: %prog [options] [module...]',
description='Show Ansible module documentation',
)
self.parser.add_option("-M", "--module-path", action="store", dest="module_path", default=C.DEFAULT_MODULE_PATH,
help="Ansible modules/ directory")
self.parser.add_option("-l", "--list", action="store_true", default=False, dest='list_dir',
help='List available modules')
self.parser.add_option("-s", "--snippet", action="store_true", default=False, dest='show_snippet',
help='Show playbook snippet for specified module(s)')
self.parser.add_option('-v', action='version', help='Show version number and exit')
self.options, self.args = self.parser.parse_args()
self.display.verbosity = self.options.verbosity
def run(self):
if options.module_path is not None:
for i in options.module_path.split(os.pathsep):
utils.plugins.module_finder.add_directory(i)
if options.list_dir:
# list modules
paths = utils.plugins.module_finder._get_paths()
module_list = []
for path in paths:
find_modules(path, module_list)
pager(get_module_list_text(module_list))
if len(args) == 0:
raise AnsibleOptionsError("Incorrect options passed")

View file

@ -40,13 +40,13 @@ from optparse import OptionParser
import ansible.constants as C import ansible.constants as C
import ansible.utils import ansible.utils
import ansible.galaxy import ansible.galaxy
from ansible.cli import CLI
from ansible.errors import AnsibleError, AnsibleOptionsError from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.galaxy import Galaxy from ansible.galaxy import Galaxy
from ansible.galaxy.api import GalaxyAPI from ansible.galaxy.api import GalaxyAPI
from ansible.galaxy.role import GalaxyRole from ansible.galaxy.role import GalaxyRole
from ansible.playbook.role.requirement import RoleRequirement from ansible.playbook.role.requirement import RoleRequirement
from ansible.utils.display import Display from ansible.utils.display import Display
from ansible.utils.cli import CLI
class GalaxyCLI(CLI): class GalaxyCLI(CLI):
@ -62,18 +62,14 @@ class GalaxyCLI(CLI):
def parse(self): def parse(self):
''' create an options parser for bin/ansible ''' ''' create an options parser for bin/ansible '''
usage = "usage: %%prog [%s] [--help] [options] ..." % "|".join(self.VALID_ACTIONS) self.parser = CLI.base_parser(
epilog = "\nSee '%s <command> --help' for more information on a specific command.\n\n" % os.path.basename(sys.argv[0]) usage = "usage: %%prog [%s] [--help] [options] ..." % "|".join(self.VALID_ACTIONS),
OptionParser.format_epilog = lambda self, formatter: self.epilog epilog = "\nSee '%s <command> --help' for more information on a specific command.\n\n" % os.path.basename(sys.argv[0])
parser = OptionParser(usage=usage, epilog=epilog) )
self.parser = parser
self.set_action() self.set_action()
# verbose
self.parser.add_option('-v','--verbose', dest='verbosity', default=0, action="count",
help="verbose mode (-vvv for more, -vvvv to enable connection debugging)")
# options specific to actions # options specific to actions
if self.action == "info": if self.action == "info":
self.parser.set_usage("usage: %prog info [options] role_name[,version]") self.parser.set_usage("usage: %prog info [options] role_name[,version]")

View file

@ -23,6 +23,7 @@ import stat
import sys import sys
from ansible import constants as C from ansible import constants as C
from ansible.cli import CLI
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.executor.playbook_executor import PlaybookExecutor from ansible.executor.playbook_executor import PlaybookExecutor
from ansible.inventory import Inventory from ansible.inventory import Inventory
@ -30,7 +31,6 @@ from ansible.parsing import DataLoader
from ansible.parsing.splitter import parse_kv from ansible.parsing.splitter import parse_kv
from ansible.playbook import Playbook from ansible.playbook import Playbook
from ansible.playbook.task import Task from ansible.playbook.task import Task
from ansible.utils.cli import CLI
from ansible.utils.display import Display from ansible.utils.display import Display
from ansible.utils.unicode import to_unicode from ansible.utils.unicode import to_unicode
from ansible.utils.vars import combine_vars from ansible.utils.vars import combine_vars
@ -53,6 +53,8 @@ class PlaybookCLI(CLI):
subset_opts=True, subset_opts=True,
check_opts=True, check_opts=True,
diff_opts=True, diff_opts=True,
runtask_opts=True,
vault_opts=True,
) )
# ansible playbook specific opts # ansible playbook specific opts
@ -68,8 +70,7 @@ class PlaybookCLI(CLI):
self.options, self.args = parser.parse_args() self.options, self.args = parser.parse_args()
if len(self.args) == 0: if len(self.args) == 0:
parser.print_help(file=sys.stderr) raise AnsibleOptionsError("You must specify a playbook file to run")
raise AnsibleError("You must specify a playbook file to run")
self.parser = parser self.parser = parser

69
v2/ansible/cli/pull.py Normal file
View file

@ -0,0 +1,69 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# 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 <http://www.gnu.org/licenses/>.
########################################################
import os
import sys
from ansible import constants as C
from ansible.errors import *
from ansible.cli import CLI
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.inventory import Inventory
from ansible.parsing import DataLoader
from ansible.parsing.splitter import parse_kv
from ansible.playbook.play import Play
from ansible.utils.display import Display
from ansible.utils.vault import read_vault_file
from ansible.vars import VariableManager
########################################################
class PullCLI(CLI):
''' code behind ansible ad-hoc cli'''
def parse(self):
''' create an options parser for bin/ansible '''
self.parser = CLI.base_parser(
usage='%prog <host-pattern> [options]',
runas_opts=True,
async_opts=True,
output_opts=True,
connect_opts=True,
check_opts=True,
runtask_opts=True,
vault_opts=True,
)
# options unique to pull
self.options, self.args = self.parser.parse_args()
if len(self.args) != 1:
raise AnsibleOptionsError("Missing target hosts")
self.display.verbosity = self.options.verbosity
self.validate_conflicts()
return True
def run(self):
''' use Runner lib to do SSH things '''
raise AnsibleError("Not ported to v2 yet")

View file

@ -20,9 +20,10 @@ import os
import sys import sys
import traceback import traceback
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleOptionsError from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.parsing.vault import VaultEditor from ansible.parsing.vault import VaultEditor
from ansible.utils.cli import CLI from ansible.cli import CLI
from ansible.utils.display import Display from ansible.utils.display import Display
class VaultCLI(CLI): class VaultCLI(CLI):
@ -34,13 +35,14 @@ class VaultCLI(CLI):
def __init__(self, args, display=None): def __init__(self, args, display=None):
self.vault_pass = None self.vault_pass = None
super(VaultCli, self).__init__(args, display) super(VaultCLI, self).__init__(args, display)
def parse(self): def parse(self):
# create parser for CLI options
self.parser = CLI.base_parser( self.parser = CLI.base_parser(
usage = "%prog vaultfile.yml", vault_opts=True,
usage = "usage: %%prog [%s] [--help] [options] vaultfile.yml" % "|".join(self.VALID_ACTIONS),
epilog = "\nSee '%s <command> --help' for more information on a specific command.\n\n" % os.path.basename(sys.argv[0])
) )
self.set_action() self.set_action()
@ -60,10 +62,10 @@ class VaultCLI(CLI):
self.parser.set_usage("usage: %prog rekey [options] file_name") self.parser.set_usage("usage: %prog rekey [options] file_name")
self.options, self.args = self.parser.parse_args() self.options, self.args = self.parser.parse_args()
self.display.verbosity = self.options.verbosity
if len(self.args) == 0 or len(self.args) > 1: if len(self.args) == 0 or len(self.args) > 1:
self.parser.print_help() raise AnsibleOptionsError("Vault requires a single filename as a parameter")
raise AnsibleError("Vault requires a single filename as a parameter")
def run(self): def run(self):

79
v2/bin/ansible Executable file
View file

@ -0,0 +1,79 @@
#!/usr/bin/env python
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# 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 <http://www.gnu.org/licenses/>.
########################################################
from __future__ import (absolute_import)
__metaclass__ = type
__requires__ = ['ansible']
try:
import pkg_resources
except Exception:
# Use pkg_resources to find the correct versions of libraries and set
# sys.path appropriately when there are multiversion installs. But we
# have code that better expresses the errors in the places where the code
# is actually used (the deps are optional for many code paths) so we don't
# want to fail here.
pass
import os
import sys
from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.utils.display import Display
########################################################
if __name__ == '__main__':
cli = None
display = Display()
me = os.path.basename(__file__)
try:
if me == 'ansible-playbook':
from ansible.cli.playbook import PlaybookCLI as mycli
elif me == 'ansible':
from ansible.cli.adhoc import AdHocCLI as mycli
elif me == 'ansible-pull':
from ansible.cli.pull import PullCLI as mycli
elif me == 'ansible-doc':
from ansible.cli.doc import DocCLI as mycli
elif me == 'ansible-vault':
from ansible.cli.vault import VaultCLI as mycli
elif me == 'ansible-galaxy':
from ansible.cli.galaxy import GalaxyCLI as mycli
cli = mycli(sys.argv, display=display)
if cli:
cli.parse()
sys.exit(cli.run())
else:
raise AnsibleError("Program not implemented: %s" % me)
except AnsibleOptionsError as e:
cli.parser.print_help()
display.display(str(e), stderr=True, color='red')
sys.exit(1)
except AnsibleError as e:
display.display(str(e), stderr=True, color='red')
sys.exit(2)
except KeyboardInterrupt:
display.error("interrupted")
sys.exit(4)

1
v2/bin/ansible-doc Symbolic link
View file

@ -0,0 +1 @@
ansible

1
v2/bin/ansible-galaxy Symbolic link
View file

@ -0,0 +1 @@
ansible

1
v2/bin/ansible-playbook Symbolic link
View file

@ -0,0 +1 @@
ansible

1
v2/bin/ansible-pull Symbolic link
View file

@ -0,0 +1 @@
ansible

1
v2/bin/ansible-vault Symbolic link
View file

@ -0,0 +1 @@
ansible