Commit cdefeb6d by Brian Coca

refactored most binaries

added AnsibleOptionsError
removed pulicate parser error class
parent 900b992b
......@@ -40,13 +40,15 @@ def get_config(p, section, key, env_var, default, boolean=False, integer=False,
''' return a configuration variable with casting '''
value = _get_config(p, section, key, env_var, default)
if boolean:
return mk_boolean(value)
if value and integer:
return int(value)
if value and floating:
return float(value)
if value and islist:
return [x.strip() for x in value.split(',')]
value = mk_boolean(value)
if value:
if integer:
value = int(value)
if floating:
value = float(value)
if islist:
if isinstance(value, basestring):
value = [x.strip() for x in value.split(',')]
return value
def _get_config(p, section, key, env_var, default):
......@@ -104,7 +106,7 @@ DEFAULTS='defaults'
# configurable things
DEFAULT_DEBUG = get_config(p, DEFAULTS, 'debug', 'ANSIBLE_DEBUG', False, boolean=True)
DEFAULT_HOST_LIST = shell_expand_path(get_config(p, DEFAULTS, 'inventory', 'ANSIBLE_INVENTORY', get_config(p, DEFAULTS,'hostfile','ANSIBLE_HOSTS', '/etc/ansible/hosts')))
DEFAULT_HOST_LIST = shell_expand_path(get_config(p, DEFAULTS, 'hostfile', 'ANSIBLE_HOSTS', get_config(p, DEFAULTS,'inventory','ANSIBLE_INVENTORY', '/etc/ansible/hosts')))
DEFAULT_MODULE_PATH = get_config(p, DEFAULTS, 'library', 'ANSIBLE_LIBRARY', None)
DEFAULT_ROLES_PATH = shell_expand_path(get_config(p, DEFAULTS, 'roles_path', 'ANSIBLE_ROLES_PATH', '/etc/ansible/roles'))
DEFAULT_REMOTE_TMP = get_config(p, DEFAULTS, 'remote_tmp', 'ANSIBLE_REMOTE_TEMP', '$HOME/.ansible/tmp')
......@@ -212,6 +214,7 @@ GALAXY_SCMS = get_config(p, 'galaxy', 'scms', 'ANSIBLE_GALAXY
DEFAULT_PASSWORD_CHARS = ascii_letters + digits + ".,:-_"
# non-configurable things
MODULE_REQUIRE_ARGS = ['command', 'shell', 'raw', 'script']
DEFAULT_BECOME_PASS = None
DEFAULT_SUDO_PASS = None
DEFAULT_REMOTE_PASS = None
......
......@@ -140,6 +140,10 @@ class AnsibleError(Exception):
return error_message
class AnsibleOptionsError(AnsibleError):
''' bad or incomplete options passed '''
pass
class AnsibleParserError(AnsibleError):
''' something was detected early that is wrong about a playbook or data file '''
pass
......@@ -164,6 +168,14 @@ class AnsibleFilterError(AnsibleRuntimeError):
''' a templating failure '''
pass
class AnsibleLookupError(AnsibleRuntimeError):
''' a lookup failure '''
pass
class AnsibleCallbackError(AnsibleRuntimeError):
''' a callback failure '''
pass
class AnsibleUndefinedVariable(AnsibleRuntimeError):
''' a templating failure '''
pass
......@@ -171,7 +183,3 @@ class AnsibleUndefinedVariable(AnsibleRuntimeError):
class AnsibleFileNotFound(AnsibleRuntimeError):
''' a file missing failure '''
pass
class AnsibleParserError(AnsibleRuntimeError):
''' a parser error '''
pass
......@@ -36,6 +36,8 @@ class GalaxyRole(object):
SUPPORTED_SCMS = set(['git', 'hg'])
META_MAIN = os.path.join('meta', 'main.yml')
META_INSTALL = os.path.join('meta', '.galaxy_install_info')
ROLE_DIRS = ('defaults','files','handlers','meta','tasks','templates','vars')
def __init__(self, galaxy, role_name, role_version=None, role_url=None):
......@@ -45,13 +47,13 @@ class GalaxyRole(object):
self.name = role_name
self.meta_data = None
self.install_info = None
self.role_path = (os.path.join(self.roles_path, self.name))
self.path = (os.path.join(galaxy.roles_path, self.name))
# TODO: possibly parse version and url from role_name
self.version = role_version
self.url = role_url
if self.url is None and '://' in self.name:
self.url = self.name
if self.url is None:
self._spec_parse()
if C.GALAXY_SCMS:
self.scms = self.SUPPORTED_SCMS.intersection(set(C.GALAXY_SCMS))
......@@ -62,7 +64,7 @@ class GalaxyRole(object):
self.display.warning("No valid SCMs configured for Galaxy.")
def fetch_from_scm_archive(self, scm, role_url, role_version):
def fetch_from_scm_archive(self):
# this can be configured to prevent unwanted SCMS but cannot add new ones unless the code is also updated
if scm not in self.scms:
......@@ -111,12 +113,21 @@ class GalaxyRole(object):
return temp_file.name
def get_metadata(self):
"""
Returns role metadata
"""
if self.meta_data is None:
self._read_metadata
return self.meta_data
def read_metadata(self):
def _read_metadata(self):
"""
Reads the metadata as YAML, if the file 'meta/main.yml' exists
"""
meta_path = os.path.join(self.role_path, self.META_MAIN)
meta_path = os.path.join(self.path, self.META_MAIN)
if os.path.isfile(meta_path):
try:
f = open(meta_path, 'r')
......@@ -127,15 +138,24 @@ class GalaxyRole(object):
finally:
f.close()
return True
def read_galaxy_install_info(self):
def get_galaxy_install_info(self):
"""
Returns role install info
"""
if self.install_info is None:
self._read_galaxy_isntall_info()
return self.install_info
def _read_galaxy_install_info(self):
"""
Returns the YAML data contained in 'meta/.galaxy_install_info',
if it exists.
"""
info_path = os.path.join(self.role_path, self.META_INSTALL)
info_path = os.path.join(self.path, self.META_INSTALL)
if os.path.isfile(info_path):
try:
f = open(info_path, 'r')
......@@ -146,9 +166,7 @@ class GalaxyRole(object):
finally:
f.close()
return True
def write_galaxy_install_info(self):
def _write_galaxy_install_info(self):
"""
Writes a YAML-formatted file to the role's meta/ directory
(named .galaxy_install_info) which contains some information
......@@ -159,7 +177,7 @@ class GalaxyRole(object):
version=self.version,
install_date=datetime.datetime.utcnow().strftime("%c"),
)
info_path = os.path.join(self.role_path, self.META_INSTALL)
info_path = os.path.join(self.path, self.META_INSTALL)
try:
f = open(info_path, 'w+')
self.install_info = yaml.safe_dump(info, f)
......@@ -178,7 +196,7 @@ class GalaxyRole(object):
"""
if self.read_metadata():
try:
rmtree(self.role_path)
rmtree(self.path)
return True
except:
pass
......@@ -213,7 +231,7 @@ class GalaxyRole(object):
self.display.error("failed to download the file.")
return False
def install(self, role_version, role_filename):
def install(self, role_filename):
# the file is a tar, so open it that way and extract it
# to the specified (or default) roles directory
......@@ -246,10 +264,10 @@ class GalaxyRole(object):
# we strip off the top-level directory for all of the files contained within
# the tar file here, since the default is 'github_repo-target', and change it
# to the specified role's name
self.display.display("- extracting %s to %s" % (self.name, self.role_path))
self.display.display("- extracting %s to %s" % (self.name, self.path))
try:
if os.path.exists(self.role_path):
if not os.path.isdir(self.role_path):
if os.path.exists(self.path):
if not os.path.isdir(self.path):
self.display.error("the specified roles path exists and is not a directory.")
return False
elif not getattr(self.options, "force", False):
......@@ -258,13 +276,13 @@ class GalaxyRole(object):
else:
# using --force, remove the old path
if not self.remove():
self.display.error("%s doesn't appear to contain a role." % self.role_path)
self.display.error("%s doesn't appear to contain a role." % self.path)
self.display.error(" please remove this directory manually if you really want to put the role here.")
return False
else:
os.makedirs(self.role_path)
os.makedirs(self.path)
# now we do the actual extraction to the role_path
# now we do the actual extraction to the path
for member in members:
# we only extract files, and remove any relative path
# bits that might be in the file for security purposes
......@@ -276,15 +294,62 @@ class GalaxyRole(object):
if part != '..' and '~' not in part and '$' not in part:
final_parts.append(part)
member.name = os.path.join(*final_parts)
role_tar_file.extract(member, self.role_path)
role_tar_file.extract(member, self.path)
# write out the install info file for later use
self.version = role_version
self.write_galaxy_install_info()
self._write_galaxy_install_info()
except OSError as e:
self.display.error("Could not update files in %s: %s" % (self.role_path, str(e)))
self.display.error("Could not update files in %s: %s" % (self.path, str(e)))
return False
# return the parsed yaml metadata
self.display.display("- %s was installed successfully" % self.role_name)
self.display.display("- %s was installed successfully" % self.name)
return True
def get_spec(self):
"""
Returns role spec info
{
'scm': 'git',
'src': 'http://git.example.com/repos/repo.git',
'version': 'v1.0',
'name': 'repo'
}
"""
if self.scm is None and self.url is None:
self._read_galaxy_isntall_info()
return dict(scm=self.scm, src=self.url, version=self.version, role_name=self.name)
def _spec_parse(self):
''' creates separated parts of role spec '''
default_role_versions = dict(git='master', hg='tip')
if not self.url and '://' in self.name:
role_spec = self.name.strip()
if role_spec == "" or role_spec.startswith("#"):
return
tokens = [s.strip() for s in role_spec.split(',')]
# assume https://github.com URLs are git+https:// URLs and not tarballs unless they end in '.zip'
if 'github.com/' in tokens[0] and not tokens[0].startswith("git+") and not tokens[0].endswith('.tar.gz'):
tokens[0] = 'git+' + tokens[0]
if '+' in tokens[0]:
(self.scm, self.url) = tokens[0].split('+')
else:
self.scm = None
self.url = tokens[0]
if len(tokens) >= 2:
self.version = tokens[1]
if len(tokens) == 3:
self.name = tokens[2]
else:
self.name = self._repo_url_to_role_name(tokens[0])
if self.scm and not self.version:
self.version = default_role_versions.get(scm, '')
......@@ -40,28 +40,20 @@ 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.cli import base_parser, validate_conflicts, normalize_become_options, ask_passwords
from ansible.utils.cli import CLI
from ansible.utils.display import Display
from ansible.utils.vault import read_vault_file
from ansible.vars import VariableManager
########################################################
class Cli(object):
''' code behind bin/ansible '''
def __init__(self, display=None):
if display is None:
self.display = Display()
else:
self.display = display
class AdHocCli(CLI):
''' code behind ansible ad-hoc cli'''
def parse(self):
''' create an options parser for bin/ansible '''
parser = base_parser(
self.parser = CLI.base_parser(
usage='%prog <host-pattern> [options]',
runas_opts=True,
async_opts=True,
......@@ -71,102 +63,110 @@ class Cli(object):
)
# options unique to ansible ad-hoc
parser.add_option('-a', '--args', dest='module_args',
self.parser.add_option('-a', '--args', dest='module_args',
help="module arguments", default=C.DEFAULT_MODULE_ARGS)
parser.add_option('-m', '--module-name', dest='module_name',
self.parser.add_option('-m', '--module-name', dest='module_name',
help="module name to execute (default=%s)" % C.DEFAULT_MODULE_NAME,
default=C.DEFAULT_MODULE_NAME)
options, args = parser.parse_args()
self.options, self.args = self.parser.parse_args()
if len(args) == 0 or len(args) > 1:
parser.print_help()
if len(self.args) != 1:
self.parser.print_help()
sys.exit(1)
display.verbosity = options.verbosity
validate_conflicts(parser,options)
self.display.verbosity = self.options.verbosity
self.validate_conflicts()
return (options, args)
return True
# ----------------------------------------------
def run(self, options, args):
def run(self):
''' use Runner lib to do SSH things '''
pattern = args[0]
# only thing left should be host pattern
pattern = self.args[0]
if options.connection == "local":
options.ask_pass = False
# ignore connection password cause we are local
if self.options.connection == "local":
self.options.ask_pass = False
sshpass = None
becomepass = None
vault_pass = None
normalize_become_options(options)
(sshpass, becomepass, vault_pass) = ask_passwords(options)
self.normalize_become_options()
(sshpass, becomepass) = self.ask_passwords()
passwords = { 'conn_pass': sshpass, 'become_pass': becomepass }
if options.vault_password_file:
# read vault_pass from a file
vault_pass = read_vault_file(options.vault_password_file)
if self.options.vault_password_file:
# read vault_pass from a file
vault_pass = read_vault_file(self.options.vault_password_file)
elif self.options.ask_vault_pass:
vault_pass = self.ask_vault_passwords(ask_vault_pass=True, ask_new_vault_pass=False, confirm_new=False)[0]
loader = DataLoader(vault_password=vault_pass)
variable_manager = VariableManager()
inventory = Inventory(loader=loader, variable_manager=variable_manager, host_list=options.inventory)
inventory = Inventory(loader=loader, variable_manager=variable_manager, host_list=self.options.inventory)
hosts = inventory.list_hosts(pattern)
if len(hosts) == 0:
d = Display()
d.warning("provided hosts list is empty, only localhost is available")
self.display.warning("provided hosts list is empty, only localhost is available")
if options.listhosts:
if self.options.listhosts:
for host in hosts:
self.display.display(' %s' % host.name)
sys.exit(0)
return 0
if ((options.module_name == 'command' or options.module_name == 'shell') and not options.module_args):
raise AnsibleError("No argument passed to %s module" % options.module_name)
if self.options.module_name in C.MODULE_REQUIRE_ARGS and not self.options.module_args:
raise AnsibleError("No argument passed to %s module" % self.options.module_name)
# FIXME: async support needed
#if options.seconds:
#TODO: implement async support
#if self.options.seconds:
# callbacks.display("background launch...\n\n", color='cyan')
# results, poller = runner.run_async(options.seconds)
# results = self.poll_while_needed(poller, options)
# results, poller = runner.run_async(self.options.seconds)
# results = self.poll_while_needed(poller)
#else:
# results = runner.run()
# create a pseudo-play to execute the specified module via a single task
play_ds = dict(
name = "Ansible Ad-Hoc",
hosts = pattern,
gather_facts = 'no',
tasks = [
dict(action=dict(module=options.module_name, args=parse_kv(options.module_args))),
]
tasks = [ dict(action=dict(module=self.options.module_name, args=parse_kv(self.options.module_args))), ]
)
play = Play().load(play_ds, variable_manager=variable_manager, loader=loader)
# now create a task queue manager to execute the play
try:
display = Display()
tqm = TaskQueueManager(inventory=inventory, variable_manager=variable_manager, loader=loader, display=display, options=options, passwords=passwords, stdout_callback='minimal')
tqm = TaskQueueManager(
inventory=inventory,
callback='minimal',
variable_manager=variable_manager,
loader=loader,
display=self.display,
options=self.options,
passwords=passwords,
stdout_callback='minimal',
)
result = tqm.run(play)
tqm.cleanup()
except AnsibleError:
tqm.cleanup()
raise
finally:
if tqm:
tqm.cleanup()
return result
# ----------------------------------------------
def poll_while_needed(self, poller, options):
def poll_while_needed(self, poller):
''' summarize results from Runner '''
# BACKGROUND POLL LOGIC when -B and -P are specified
if options.seconds and options.poll_interval > 0:
poller.wait(options.seconds, options.poll_interval)
if self.options.seconds and self.options.poll_interval > 0:
poller.wait(self.options.seconds, self.options.poll_interval)
return poller.results
......@@ -176,14 +176,12 @@ class Cli(object):
if __name__ == '__main__':
display = Display()
#display.display(" ".join(sys.argv))
try:
cli = Cli(display=display)
(options, args) = cli.parse()
sys.exit(cli.run(options, args))
cli = AdHocCli(sys.argv, display=display)
cli.parse()
sys.exit(cli.run())
except AnsibleError as e:
display.error(str(e))
display.display(str(e), stderr=True, color='red')
sys.exit(1)
except KeyboardInterrupt:
display.error("interrupted")
......
......@@ -35,141 +35,100 @@ import traceback
from ansible.errors import AnsibleError
from ansible.parsing.vault import VaultEditor
from ansible.utils.cli import base_parser, ask_vault_passwords
from ansible.utils.cli import CLI
from ansible.utils.display import Display
#-------------------------------------------------------------------------------------
# Utility functions for parsing actions/options
#-------------------------------------------------------------------------------------
class Cli(object):
class VaultCli(CLI):
""" Vault command line class """
VALID_ACTIONS = ("create", "decrypt", "edit", "encrypt", "rekey", "view")
CIPHER = 'AES256'
def __init__(self, display=None):
def __init__(self, args, display=None):
self.vault_pass = None
if display is None:
self.display = Display()
else:
self.display = display
super(VaultCli, self).__init__(args, display)
def parse(self):
# create parser for CLI options
parser = base_parser(
self.parser = CLI.base_parser(
usage = "%prog vaultfile.yml",
)
return parser.parse_args()
self.set_action()
# options specific to self.actions
if self.action == "create":
self.parser.set_usage("usage: %prog create [options] file_name")
elif self.action == "decrypt":
self.parser.set_usage("usage: %prog decrypt [options] file_name")
elif self.action == "edit":
self.parser.set_usage("usage: %prog edit [options] file_name")
elif self.action == "view":
self.parser.set_usage("usage: %prog view [options] file_name")
elif self.action == "encrypt":
self.parser.set_usage("usage: %prog encrypt [options] file_name")
elif action == "rekey":
self.parser.set_usage("usage: %prog rekey [options] file_name")
def run(self, options, args):
self.options, self.args = self.parser.parse_args()
action = self.get_action(args)
if len(self.args) == 0 or len(self.args) > 1:
self.parser.print_help()
raise AnsibleError("Vault requires a single filename as a parameter")
if not action:
parser.print_help()
raise AnsibleError("missing required action")
def run(self):
# options specific to actions
if action == "create":
parser.set_usage("usage: %prog create [options] file_name")
elif action == "decrypt":
parser.set_usage("usage: %prog decrypt [options] file_name")
elif action == "edit":
parser.set_usage("usage: %prog edit [options] file_name")
elif action == "view":
parser.set_usage("usage: %prog view [options] file_name")
elif action == "encrypt":
parser.set_usage("usage: %prog encrypt [options] file_name")
elif action == "rekey":
parser.set_usage("usage: %prog rekey [options] file_name")
if self.options.vault_password_file:
# read vault_pass from a file
self.vault_pass = read_vault_file(self.options.vault_password_file)
elif self.options.ask_vault_pass:
self.vault_pass, _= self.ask_vault_passwords(ask_vault_pass=True, ask_new_vault_pass=False, confirm_new=False)
if len(args) == 0 or len(args) > 1:
parser.print_help()
raise AnsibleError("Vault requires a single filename as a parameter")
self.execute()
if options.vault_password_file:
# read vault_pass from a file
self.vault_pass = read_vault_file(options.vault_password_file)
else:
self.vault_pass, _= ask_vault_passwords(ask_vault_pass=True, ask_new_vault_pass=False, confirm_new=False)
# execute the desired action
fn = getattr(self, "execute_%s" % action)
fn(args, options)
def get_action(self, args):
"""
Get the action the user wants to execute from the
sys argv list.
"""
for i in range(0,len(args)):
arg = args[i]
if arg in VALID_ACTIONS:
del args[i]
return arg
return None
def execute_create(args, options):
cipher = 'AES256'
if hasattr(options, 'cipher'):
cipher = options.cipher
this_editor = VaultEditor(cipher, self.vault_pass, args[0])
this_editor.create_file()
def execute_create(self):
def execute_decrypt(args, options):
cipher = getattr(self.options, 'cipher', self.CIPHER)
this_editor = VaultEditor(cipher, self.vault_pass, self.args[0])
this_editor.create_file()
cipher = 'AES256'
if hasattr(options, 'cipher'):
cipher = options.cipher
def execute_decrypt(self):
for f in args:
cipher = getattr(self.options, 'cipher', self.CIPHER)
for f in self.args:
this_editor = VaultEditor(cipher, self.vault_pass, f)
this_editor.decrypt_file()
self.display.display("Decryption successful")
def execute_edit(args, options):
def execute_edit(self):
cipher = None
for f in args:
this_editor = VaultEditor(cipher, self.vault_pass, f)
for f in self.args:
this_editor = VaultEditor(None, self.vault_pass, f)
this_editor.edit_file()
def execute_view(args, options):
def execute_view(self):
cipher = None
for f in args:
this_editor = VaultEditor(cipher, self.vault_pass, f)
for f in self.args:
this_editor = VaultEditor(None, self.vault_pass, f)
this_editor.view_file()
def execute_encrypt(args, options):
cipher = 'AES256'
if hasattr(options, 'cipher'):
cipher = options.cipher
def execute_encrypt(self):
for f in args:
cipher = getattr(self.options, 'cipher', self.CIPHER)
for f in self.args:
this_editor = VaultEditor(cipher, self.vault_pass, f)
this_editor.encrypt_file()
self.display.display("Encryption successful")
def execute_rekey(args, options ):
__, new_password = ask_vault_passwords(ask_vault_pass=False, ask_new_vault_pass=True, confirm_new=True)
def execute_rekey(self):
__, new_password = self.ask_vault_passwords(ask_vault_pass=False, ask_new_vault_pass=True, confirm_new=True)
cipher = None
for f in args:
this_editor = VaultEditor(cipher, self.vault_pass, f)
for f in self.args:
this_editor = VaultEditor(None, self.vault_pass, f)
this_editor.rekey_file(new_password)
self.display.display("Rekey successful")
......@@ -179,14 +138,12 @@ class Cli(object):
if __name__ == "__main__":
display = Display()
#display.display(" ".join(sys.argv), log_only=True)
try:
cli = Cli(display=display)
(options, args) = cli.parse()
sys.exit(cli.run(options, args))
cli = VaultCli(sys.argv, display=display)
cli.parse()
sys.exit(cli.run())
except AnsibleError as e:
display.error(str(e))
display.display(str(e), stderr=True, color='red')
sys.exit(1)
except KeyboardInterrupt:
display.error("interrupted")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment