From ba0fec4f4238e381adc5f5a2f286bcd5b32f0586 Mon Sep 17 00:00:00 2001 From: James Tanner Date: Mon, 10 Mar 2014 16:11:24 -0500 Subject: [PATCH] Force command action to not be executed by the shell unless specifically enabled --- lib/ansible/module_utils/basic.py | 50 +++-- lib/ansible/module_utils/redhat.py | 252 ++++++++++++++++++++++ lib/ansible/runner/lookup_plugins/pipe.py | 2 +- library/cloud/virt | 18 +- library/commands/command | 2 +- library/files/synchronize | 9 +- library/notification/osx_say | 2 - library/packaging/easy_install | 4 +- library/packaging/npm | 5 +- library/packaging/pacman | 12 +- library/packaging/pip | 13 +- library/packaging/redhat_subscription | 81 +++---- library/packaging/rhn_register | 81 +------ library/packaging/urpmi | 13 +- library/source_control/bzr | 29 ++- library/source_control/git | 67 +++--- library/system/service | 4 +- library/system/setup | 25 ++- library/web_infrastructure/django_manage | 3 +- 19 files changed, 427 insertions(+), 245 deletions(-) create mode 100644 lib/ansible/module_utils/redhat.py diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index 8025563e58e..6e47dd4560d 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -991,12 +991,13 @@ class AnsibleModule(object): # rename might not preserve context self.set_context_if_different(dest, context, False) - def run_command(self, args, check_rc=False, close_fds=False, executable=None, data=None, binary_data=False, path_prefix=None): + def run_command(self, args, check_rc=False, close_fds=False, executable=None, data=None, binary_data=False, path_prefix=None, cwd=None, use_unsafe_shell=False): ''' Execute a command, returns rc, stdout, and stderr. args is the command to run If args is a list, the command will be run with shell=False. - Otherwise, the command will be run with shell=True when args is a string. + If args is a string and use_unsafe_shell=False it will split args to a list and run with shell=False + If args is a string and use_unsafe_shell=True it run with shell=True. Other arguments: - check_rc (boolean) Whether to call fail_json in case of non zero RC. Default is False. @@ -1005,13 +1006,18 @@ class AnsibleModule(object): - executable (string) See documentation for subprocess.Popen(). Default is None. ''' + + shell = False if isinstance(args, list): - shell = False - elif isinstance(args, basestring): + pass + elif isinstance(args, basestring) and use_unsafe_shell: shell = True + elif isinstance(args, basestring): + args = shlex.split(args) else: msg = "Argument 'args' to run_command must be list or string" self.fail_json(rc=257, cmd=args, msg=msg) + rc = 0 msg = None st_in = None @@ -1047,25 +1053,25 @@ class AnsibleModule(object): if data: st_in = subprocess.PIPE + + kwargs = dict( + executable=executable, + shell=shell, + close_fds=close_fds, + stdin= st_in, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + if path_prefix: + kwargs['env'] = env + if cwd: + kwargs['cwd'] = cwd + + try: - if path_prefix is not None: - cmd = subprocess.Popen(args, - executable=executable, - shell=shell, - close_fds=close_fds, - stdin=st_in, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env=env) - else: - cmd = subprocess.Popen(args, - executable=executable, - shell=shell, - close_fds=close_fds, - stdin=st_in, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - + cmd = subprocess.Popen(args, **kwargs) + if data: if not binary_data: data += '\\n' diff --git a/lib/ansible/module_utils/redhat.py b/lib/ansible/module_utils/redhat.py new file mode 100644 index 00000000000..a1081f9c8c7 --- /dev/null +++ b/lib/ansible/module_utils/redhat.py @@ -0,0 +1,252 @@ +import os +import re +import types +import ConfigParser +import shlex + + +class RegistrationBase(object): + def __init__(self, module, username=None, password=None): + self.module = module + self.username = username + self.password = password + + def configure(self): + raise NotImplementedError("Must be implemented by a sub-class") + + def enable(self): + # Remove any existing redhat.repo + redhat_repo = '/etc/yum.repos.d/redhat.repo' + if os.path.isfile(redhat_repo): + os.unlink(redhat_repo) + + def register(self): + raise NotImplementedError("Must be implemented by a sub-class") + + def unregister(self): + raise NotImplementedError("Must be implemented by a sub-class") + + def unsubscribe(self): + raise NotImplementedError("Must be implemented by a sub-class") + + def update_plugin_conf(self, plugin, enabled=True): + plugin_conf = '/etc/yum/pluginconf.d/%s.conf' % plugin + if os.path.isfile(plugin_conf): + cfg = ConfigParser.ConfigParser() + cfg.read([plugin_conf]) + if enabled: + cfg.set('main', 'enabled', 1) + else: + cfg.set('main', 'enabled', 0) + fd = open(plugin_conf, 'rwa+') + cfg.write(fd) + fd.close() + + def subscribe(self, **kwargs): + raise NotImplementedError("Must be implemented by a sub-class") + + +class Rhsm(RegistrationBase): + def __init__(self, module, username=None, password=None): + RegistrationBase.__init__(self, module, username, password) + self.config = self._read_config() + self.module = module + + def _read_config(self, rhsm_conf='/etc/rhsm/rhsm.conf'): + ''' + Load RHSM configuration from /etc/rhsm/rhsm.conf. + Returns: + * ConfigParser object + ''' + + # Read RHSM defaults ... + cp = ConfigParser.ConfigParser() + cp.read(rhsm_conf) + + # Add support for specifying a default value w/o having to standup some configuration + # Yeah, I know this should be subclassed ... but, oh well + def get_option_default(self, key, default=''): + sect, opt = key.split('.', 1) + if self.has_section(sect) and self.has_option(sect, opt): + return self.get(sect, opt) + else: + return default + + cp.get_option = types.MethodType(get_option_default, cp, ConfigParser.ConfigParser) + + return cp + + def enable(self): + ''' + Enable the system to receive updates from subscription-manager. + This involves updating affected yum plugins and removing any + conflicting yum repositories. + ''' + RegistrationBase.enable(self) + self.update_plugin_conf('rhnplugin', False) + self.update_plugin_conf('subscription-manager', True) + + def configure(self, **kwargs): + ''' + Configure the system as directed for registration with RHN + Raises: + * Exception - if error occurs while running command + ''' + args = ['subscription-manager', 'config'] + + # Pass supplied **kwargs as parameters to subscription-manager. Ignore + # non-configuration parameters and replace '_' with '.'. For example, + # 'server_hostname' becomes '--system.hostname'. + for k,v in kwargs.items(): + if re.search(r'^(system|rhsm)_', k): + args.append('--%s=%s' % (k.replace('_','.'), v)) + + self.module.run_command(args, check_rc=True) + + @property + def is_registered(self): + ''' + Determine whether the current system + Returns: + * Boolean - whether the current system is currently registered to + RHN. + ''' + # Quick version... + if False: + return os.path.isfile('/etc/pki/consumer/cert.pem') and \ + os.path.isfile('/etc/pki/consumer/key.pem') + + args = ['subscription-manager', 'identity'] + rc, stdout, stderr = self.module.run_command(args, check_rc=False) + if rc == 0: + return True + else: + return False + + def register(self, username, password, autosubscribe, activationkey): + ''' + Register the current system to the provided RHN server + Raises: + * Exception - if error occurs while running command + ''' + args = ['subscription-manager', 'register'] + + # Generate command arguments + if activationkey: + args.append('--activationkey "%s"' % activationkey) + else: + if autosubscribe: + args.append('--autosubscribe') + if username: + args.extend(['--username', username]) + if password: + args.extend(['--password', password]) + + # Do the needful... + rc, stderr, stdout = self.module.run_command(args, check_rc=True) + + def unsubscribe(self): + ''' + Unsubscribe a system from all subscribed channels + Raises: + * Exception - if error occurs while running command + ''' + args = ['subscription-manager', 'unsubscribe', '--all'] + rc, stderr, stdout = self.module.run_command(args, check_rc=True) + + def unregister(self): + ''' + Unregister a currently registered system + Raises: + * Exception - if error occurs while running command + ''' + args = ['subscription-manager', 'unregister'] + rc, stderr, stdout = self.module.run_command(args, check_rc=True) + + def subscribe(self, regexp): + ''' + Subscribe current system to available pools matching the specified + regular expression + Raises: + * Exception - if error occurs while running command + ''' + + # Available pools ready for subscription + available_pools = RhsmPools(self.module) + + for pool in available_pools.filter(regexp): + pool.subscribe() + + +class RhsmPool(object): + ''' + Convenience class for housing subscription information + ''' + + def __init__(self, module, **kwargs): + self.module = module + for k,v in kwargs.items(): + setattr(self, k, v) + + def __str__(self): + return str(self.__getattribute__('_name')) + + def subscribe(self): + args = "subscription-manager subscribe --pool %s" % self.PoolId + rc, stdout, stderr = self.module.run_command(args, check_rc=True) + if rc == 0: + return True + else: + return False + + +class RhsmPools(object): + """ + This class is used for manipulating pools subscriptions with RHSM + """ + def __init__(self, module): + self.module = module + self.products = self._load_product_list() + + def __iter__(self): + return self.products.__iter__() + + def _load_product_list(self): + """ + Loads list of all availaible pools for system in data structure + """ + args = "subscription-manager list --available" + rc, stdout, stderr = self.module.run_command(args, check_rc=True) + + products = [] + for line in stdout.split('\n'): + # Remove leading+trailing whitespace + line = line.strip() + # An empty line implies the end of a output group + if len(line) == 0: + continue + # If a colon ':' is found, parse + elif ':' in line: + (key, value) = line.split(':',1) + key = key.strip().replace(" ", "") # To unify + value = value.strip() + if key in ['ProductName', 'SubscriptionName']: + # Remember the name for later processing + products.append(RhsmPool(self.module, _name=value, key=value)) + elif products: + # Associate value with most recently recorded product + products[-1].__setattr__(key, value) + # FIXME - log some warning? + #else: + # warnings.warn("Unhandled subscription key/value: %s/%s" % (key,value)) + return products + + def filter(self, regexp='^$'): + ''' + Return a list of RhsmPools whose name matches the provided regular expression + ''' + r = re.compile(regexp) + for product in self.products: + if r.search(product._name): + yield product + diff --git a/lib/ansible/runner/lookup_plugins/pipe.py b/lib/ansible/runner/lookup_plugins/pipe.py index 4205b887ffe..62ec7e129ed 100644 --- a/lib/ansible/runner/lookup_plugins/pipe.py +++ b/lib/ansible/runner/lookup_plugins/pipe.py @@ -32,7 +32,7 @@ class LookupModule(object): ret = [] for term in terms: - p = subprocess.Popen(term, cwd=self.basedir, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + p = subprocess.Popen(term, cwd=self.basedir, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE) (stdout, stderr) = p.communicate() if p.returncode == 0: ret.append(stdout.decode("utf-8").rstrip()) diff --git a/library/cloud/virt b/library/cloud/virt index 42e99209b14..8cbf7fc895a 100644 --- a/library/cloud/virt +++ b/library/cloud/virt @@ -113,13 +113,14 @@ class VMNotFound(Exception): class LibvirtConnection(object): - def __init__(self, uri): + def __init__(self, uri, module): - cmd = subprocess.Popen("uname -r", shell=True, stdout=subprocess.PIPE, - close_fds=True) - output = cmd.communicate()[0] + self.module = module - if output.find("xen") != -1: + cmd = "uname -r" + rc, stdout, stderr = self.module.run_command(cmd) + + if stdout.find("xen") != -1: conn = libvirt.open(None) else: conn = libvirt.open(uri) @@ -221,11 +222,12 @@ class LibvirtConnection(object): class Virt(object): - def __init__(self, uri): + def __init__(self, uri, module): + self.module = module self.uri = uri def __get_conn(self): - self.conn = LibvirtConnection(self.uri) + self.conn = LibvirtConnection(self.uri, self.module) return self.conn def get_vm(self, vmid): @@ -399,7 +401,7 @@ def core(module): uri = module.params.get('uri', None) xml = module.params.get('xml', None) - v = Virt(uri) + v = Virt(uri, module) res = {} if state and command=='list_vms': diff --git a/library/commands/command b/library/commands/command index 76d2f828d0c..ba9ae30a7f2 100644 --- a/library/commands/command +++ b/library/commands/command @@ -136,7 +136,7 @@ def main(): args = shlex.split(args) startd = datetime.datetime.now() - rc, out, err = module.run_command(args, executable=executable) + rc, out, err = module.run_command(args, executable=executable, use_unsafe_shell=shell) endd = datetime.datetime.now() delta = endd - startd diff --git a/library/files/synchronize b/library/files/synchronize index 493322393bc..eb556c30f53 100644 --- a/library/files/synchronize +++ b/library/files/synchronize @@ -16,8 +16,6 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . -import subprocess - DOCUMENTATION = ''' --- module: synchronize @@ -272,6 +270,13 @@ def main(): cmd = cmd + " --rsync-path '%s'" %(rsync_path) changed_marker = '<>' cmd = cmd + " --out-format='" + changed_marker + "%i %n%L'" + + # expand the paths + if '@' not in source: + source = os.path.expanduser(source) + if '@' not in dest: + dest = os.path.expanduser(dest) + cmd = ' '.join([cmd, source, dest]) cmdstr = cmd (rc, out, err) = module.run_command(cmd) diff --git a/library/notification/osx_say b/library/notification/osx_say index de5d1917c5f..39e3da88c19 100644 --- a/library/notification/osx_say +++ b/library/notification/osx_say @@ -44,8 +44,6 @@ EXAMPLES = ''' - local_action: osx_say msg="{{inventory_hostname}} is all done" voice=Zarvox ''' -import subprocess - DEFAULT_VOICE='Trinoids' def say(module, msg, voice): diff --git a/library/packaging/easy_install b/library/packaging/easy_install index bdacf8e464b..889a81f025a 100644 --- a/library/packaging/easy_install +++ b/library/packaging/easy_install @@ -151,8 +151,8 @@ def main(): command = '%s %s' % (virtualenv, env) if site_packages: command += ' --system-site-packages' - os.chdir(tempfile.gettempdir()) - rc_venv, out_venv, err_venv = module.run_command(command) + cwd = tempfile.gettempdir() + rc_venv, out_venv, err_venv = module.run_command(command, cwd=cwd) rc += rc_venv out += out_venv diff --git a/library/packaging/npm b/library/packaging/npm index 62179c373aa..c623b6f7e6d 100644 --- a/library/packaging/npm +++ b/library/packaging/npm @@ -125,10 +125,11 @@ class Npm(object): cmd.append(self.name_version) #If path is specified, cd into that path and run the command. + cwd = None if self.path: - os.chdir(self.path) + cwd = self.path - rc, out, err = self.module.run_command(cmd, check_rc=check_rc) + rc, out, err = self.module.run_command(cmd, check_rc=check_rc, cwd=cwd) return out return '' diff --git a/library/packaging/pacman b/library/packaging/pacman index 3080cb4a607..a4a24ca5fd1 100644 --- a/library/packaging/pacman +++ b/library/packaging/pacman @@ -90,7 +90,8 @@ def query_package(module, name, state="installed"): # pacman -Q returns 0 if the package is installed, # 1 if it is not installed if state == "installed": - rc = os.system("pacman -Q %s" % (name)) + cmd = "pacman -Q %s" % (name) + rc, stdout, stderr = module.run_command(cmd, check_rc=False) if rc == 0: return True @@ -99,7 +100,8 @@ def query_package(module, name, state="installed"): def update_package_db(module): - rc = os.system("pacman -Syy > /dev/null") + cmd = "pacman -Syy > /dev/null" + rc, stdout, stderr = module.run_command(cmd, check_rc=False) if rc != 0: module.fail_json(msg="could not update package db") @@ -118,7 +120,8 @@ def remove_packages(module, packages): if not query_package(module, package): continue - rc = os.system("pacman -%s %s --noconfirm > /dev/null" % (args, package)) + cmd = "pacman -%s %s --noconfirm > /dev/null" % (args, package) + rc, stdout, stderr = module.run_command(cmd, check_rc=False) if rc != 0: module.fail_json(msg="failed to remove %s" % (package)) @@ -145,7 +148,8 @@ def install_packages(module, packages, package_files): else: params = '-S %s' % package - rc = os.system("pacman %s --noconfirm > /dev/null" % (params)) + cmd = "pacman %s --noconfirm > /dev/null" % (params) + rc, stdout, stderr = module.run_command(cmd, check_rc=False) if rc != 0: module.fail_json(msg="failed to install %s" % (package)) diff --git a/library/packaging/pip b/library/packaging/pip index 35487c32963..aa55bf8ba0b 100644 --- a/library/packaging/pip +++ b/library/packaging/pip @@ -253,10 +253,10 @@ def main(): cmd = '%s --no-site-packages %s' % (virtualenv, env) else: cmd = '%s %s' % (virtualenv, env) - os.chdir(tempfile.gettempdir()) + this_dir = tempfile.gettempdir() if chdir: - os.chdir(chdir) - rc, out_venv, err_venv = module.run_command(cmd) + this_dir = os.path.join(this_dir, chdir) + rc, out_venv, err_venv = module.run_command(cmd, cwd=this_dir) out += out_venv err += err_venv if rc != 0: @@ -298,10 +298,11 @@ def main(): if module.check_mode: module.exit_json(changed=True) - os.chdir(tempfile.gettempdir()) + this_dir = tempfile.gettempdir() if chdir: - os.chdir(chdir) - rc, out_pip, err_pip = module.run_command(cmd, path_prefix=path_prefix) + this_dir = os.path.join(this_dir, chdir) + + rc, out_pip, err_pip = module.run_command(cmd, path_prefix=path_prefix, cwd=this_dir) out += out_pip err += err_pip if rc == 1 and state == 'absent' and 'not installed' in out_pip: diff --git a/library/packaging/redhat_subscription b/library/packaging/redhat_subscription index e363aa0946a..bb5d655a52f 100644 --- a/library/packaging/redhat_subscription +++ b/library/packaging/redhat_subscription @@ -75,39 +75,13 @@ EXAMPLES = ''' import os import re import types -import subprocess import ConfigParser import shlex -class CommandException(Exception): - pass - - -def run_command(args): - ''' - Convenience method to run a command, specified as a list of arguments. - Returns: - * tuple - (stdout, stder, retcode) - ''' - - # Coerce into a string - if isinstance(args, str): - args = shlex.split(args) - - # Run desired command - proc = subprocess.Popen(args, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - (stdout, stderr) = proc.communicate() - returncode = proc.poll() - if returncode != 0: - cmd = ' '.join(args) - raise CommandException("Command failed (%s): %s\n%s" % (returncode, cmd, stdout)) - return (stdout, stderr, returncode) - - -class RegistrationBase (object): - def __init__(self, username=None, password=None): +class RegistrationBase(object): + def __init__(self, module, username=None, password=None): + self.module = module self.username = username self.password = password @@ -147,9 +121,10 @@ class RegistrationBase (object): class Rhsm(RegistrationBase): - def __init__(self, username=None, password=None): - RegistrationBase.__init__(self, username, password) + def __init__(self, module, username=None, password=None): + RegistrationBase.__init__(self, module, username, password) self.config = self._read_config() + self.module = module def _read_config(self, rhsm_conf='/etc/rhsm/rhsm.conf'): ''' @@ -199,8 +174,8 @@ class Rhsm(RegistrationBase): for k,v in kwargs.items(): if re.search(r'^(system|rhsm)_', k): args.append('--%s=%s' % (k.replace('_','.'), v)) - - run_command(args) + + self.module.run_command(args, check_rc=True) @property def is_registered(self): @@ -216,13 +191,11 @@ class Rhsm(RegistrationBase): os.path.isfile('/etc/pki/consumer/key.pem') args = ['subscription-manager', 'identity'] - try: - (stdout, stderr, retcode) = run_command(args) - except CommandException, e: - return False - else: - # Display some debug output + rc, stdout, stderr = self.module.run_command(args, check_rc=False) + if rc == 0: return True + else: + return False def register(self, username, password, autosubscribe, activationkey): ''' @@ -244,7 +217,7 @@ class Rhsm(RegistrationBase): args.extend(['--password', password]) # Do the needful... - run_command(args) + rc, stderr, stdout = self.module.run_command(args, check_rc=True) def unsubscribe(self): ''' @@ -253,7 +226,7 @@ class Rhsm(RegistrationBase): * Exception - if error occurs while running command ''' args = ['subscription-manager', 'unsubscribe', '--all'] - run_command(args) + rc, stderr, stdout = self.module.run_command(args, check_rc=True) def unregister(self): ''' @@ -262,7 +235,7 @@ class Rhsm(RegistrationBase): * Exception - if error occurs while running command ''' args = ['subscription-manager', 'unregister'] - run_command(args) + rc, stderr, stdout = self.module.run_command(args, check_rc=True) def subscribe(self, regexp): ''' @@ -273,7 +246,7 @@ class Rhsm(RegistrationBase): ''' # Available pools ready for subscription - available_pools = RhsmPools() + available_pools = RhsmPools(self.module) for pool in available_pools.filter(regexp): pool.subscribe() @@ -284,7 +257,8 @@ class RhsmPool(object): Convenience class for housing subscription information ''' - def __init__(self, **kwargs): + def __init__(self, module, **kwargs): + self.module = module for k,v in kwargs.items(): setattr(self, k, v) @@ -292,15 +266,20 @@ class RhsmPool(object): return str(self.__getattribute__('_name')) def subscribe(self): - (stdout, stderr, retcode) = run_command("subscription-manager subscribe --pool %s" % self.PoolId) - return True + args = "subscription-manager subscribe --pool %s" % self.PoolId + rc, stdout, stderr = self.module.run_command(args, check_rc=True) + if rc == 0: + return True + else: + return False class RhsmPools(object): """ This class is used for manipulating pools subscriptions with RHSM """ - def __init__(self): + def __init__(self, module): + self.module = module self.products = self._load_product_list() def __iter__(self): @@ -310,7 +289,8 @@ class RhsmPools(object): """ Loads list of all availaible pools for system in data structure """ - (stdout, stderr, retval) = run_command("subscription-manager list --available") + args = "subscription-manager list --available" + rc, stdout, stderr = self.module.run_command(args, check_rc=True) products = [] for line in stdout.split('\n'): @@ -326,7 +306,7 @@ class RhsmPools(object): value = value.strip() if key in ['ProductName', 'SubscriptionName']: # Remember the name for later processing - products.append(RhsmPool(_name=value, key=value)) + products.append(RhsmPool(self.module, _name=value, key=value)) elif products: # Associate value with most recently recorded product products[-1].__setattr__(key, value) @@ -348,7 +328,7 @@ class RhsmPools(object): def main(): # Load RHSM configuration from file - rhn = Rhsm() + rhn = Rhsm(AnsibleModule()) module = AnsibleModule( argument_spec = dict( @@ -364,6 +344,7 @@ def main(): ) ) + rhn.module = module state = module.params['state'] username = module.params['username'] password = module.params['password'] diff --git a/library/packaging/rhn_register b/library/packaging/rhn_register index 5e8c3718f98..28d91a6a027 100644 --- a/library/packaging/rhn_register +++ b/library/packaging/rhn_register @@ -72,12 +72,7 @@ EXAMPLES = ''' ''' import sys -import os -import re import types -import subprocess -import ConfigParser -import shlex import xmlrpclib import urlparse @@ -90,75 +85,9 @@ except ImportError, e: module.fail_json(msg="Unable to import up2date_client. Is 'rhn-client-tools' installed?\n%s" % e) -class CommandException(Exception): - pass - - -def run_command(args): - ''' - Convenience method to run a command, specified as a list of arguments. - Returns: - * tuple - (stdout, stder, retcode) - ''' - - # Coerce into a string - if isinstance(args, str): - args = shlex.split(args) - - # Run desired command - proc = subprocess.Popen(args, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - (stdout, stderr) = proc.communicate() - returncode = proc.poll() - if returncode != 0: - cmd = ' '.join(args) - raise CommandException("Command failed (%s): %s\n%s" % (returncode, cmd, stdout)) - return (stdout, stderr, returncode) - - -class RegistrationBase (object): - def __init__(self, username=None, password=None): - self.username = username - self.password = password - - def configure(self): - raise NotImplementedError("Must be implemented by a sub-class") - - def enable(self): - # Remove any existing redhat.repo - redhat_repo = '/etc/yum.repos.d/redhat.repo' - if os.path.isfile(redhat_repo): - os.unlink(redhat_repo) - - def register(self): - raise NotImplementedError("Must be implemented by a sub-class") - - def unregister(self): - raise NotImplementedError("Must be implemented by a sub-class") - - def unsubscribe(self): - raise NotImplementedError("Must be implemented by a sub-class") - - def update_plugin_conf(self, plugin, enabled=True): - plugin_conf = '/etc/yum/pluginconf.d/%s.conf' % plugin - if os.path.isfile(plugin_conf): - cfg = ConfigParser.ConfigParser() - cfg.read([plugin_conf]) - if enabled: - cfg.set('main', 'enabled', 1) - else: - cfg.set('main', 'enabled', 0) - fd = open(plugin_conf, 'rwa+') - cfg.write(fd) - fd.close() - - def subscribe(self, **kwargs): - raise NotImplementedError("Must be implemented by a sub-class") - - class Rhn(RegistrationBase): - def __init__(self, username=None, password=None): + def __init__(self, module, username=None, password=None): RegistrationBase.__init__(self, username, password) self.config = self.load_config() @@ -271,7 +200,7 @@ class Rhn(RegistrationBase): register_cmd += " --activationkey '%s'" % activationkey # FIXME - support --profilename # FIXME - support --systemorgid - run_command(register_cmd) + rc, stdout, stderr = self.module.run_command(register_command, check_rc=True) def api(self, method, *args): ''' @@ -309,14 +238,14 @@ class Rhn(RegistrationBase): Subscribe to requested yum repositories using 'rhn-channel' command ''' rhn_channel_cmd = "rhn-channel --user='%s' --password='%s'" % (self.username, self.password) - (stdout, stderr, rc) = run_command(rhn_channel_cmd + " --available-channels") + rc, stdout, stderr = self.module.run_command(rhn_channel_cmd + " --available-channels", check_rc=True) # Enable requested repoid's for wanted_channel in channels: # Each inserted repo regexp will be matched. If no match, no success. for availaible_channel in stdout.rstrip().split('\n'): # .rstrip() because of \n at the end -> empty string at the end if re.search(wanted_repo, available_channel): - run_command(rhn_channel_cmd + " --add --channel=%s" % available_channel) + rc, stdout, stderr = self.module.run_command(rhn_channel_cmd + " --add --channel=%s" % available_channel, check_rc=True) def main(): @@ -379,4 +308,6 @@ def main(): # import module snippets from ansible.module_utils.basic import * +from ansible.module_utils.redhat import * + main() diff --git a/library/packaging/urpmi b/library/packaging/urpmi index b001ed94dee..72dfef02011 100644 --- a/library/packaging/urpmi +++ b/library/packaging/urpmi @@ -91,7 +91,8 @@ def query_package(module, name): # rpm -q returns 0 if the package is installed, # 1 if it is not installed - rc = os.system("rpm -q %s" % (name)) + cmd = "rpm -q %s" % (name) + rc, stdout, stderr = module.run_command(cmd, check_rc=False) if rc == 0: return True else: @@ -103,13 +104,14 @@ def query_package_provides(module, name): # rpm -q returns 0 if the package is installed, # 1 if it is not installed - rc = os.system("rpm -q --provides %s >/dev/null" % (name)) + cmd = "rpm -q --provides %s >/dev/null" % (name) + rc, stdout, stderr = module.run_command(cmd, check_rc=False) return rc == 0 def update_package_db(module): - rc = os.system("urpmi.update -a -q") - + cmd = "urpmi.update -a -q" + rc, stdout, stderr = module.run_command(cmd, check_rc=False) if rc != 0: module.fail_json(msg="could not update package db") @@ -123,7 +125,8 @@ def remove_packages(module, packages): if not query_package(module, package): continue - rc = os.system("%s --auto %s > /dev/null" % (URPME_PATH, package)) + cmd = "%s --auto %s > /dev/null" % (URPME_PATH, package) + rc, stdout, stderr = module.run_command(cmd, check_rc=False) if rc != 0: module.fail_json(msg="failed to remove %s" % (package)) diff --git a/library/source_control/bzr b/library/source_control/bzr index bc2dfc3089f..5217e469900 100644 --- a/library/source_control/bzr +++ b/library/source_control/bzr @@ -75,16 +75,17 @@ class Bzr(object): self.version = version self.bzr_path = bzr_path - def _command(self, args_list, **kwargs): + def _command(self, args_list, cwd=None, **kwargs): (rc, out, err) = self.module.run_command( - [self.bzr_path] + args_list, **kwargs) + [self.bzr_path] + args_list, cwd=cwd, **kwargs) return (rc, out, err) def get_version(self): '''samples the version of the bzr branch''' - os.chdir(self.dest) + cmd = "%s revno" % self.bzr_path - revno = os.popen(cmd).read().strip() + rc, stdout, stderr = self.module.run_command(cmd, cwd=self.dest) + revno = stdout.strip() return revno def clone(self): @@ -94,17 +95,18 @@ class Bzr(object): os.makedirs(dest_dirname) except: pass - os.chdir(dest_dirname) if self.version.lower() != 'head': args_list = ["branch", "-r", self.version, self.parent, self.dest] else: args_list = ["branch", self.parent, self.dest] - return self._command(args_list, check_rc=True) + return self._command(args_list, check_rc=True, cwd=dest_dirname) def has_local_mods(self): - os.chdir(self.dest) + cmd = "%s status -S" % self.bzr_path - lines = os.popen(cmd).read().splitlines() + rc, stdout, stderr = self.module.run_command(cmd, cwd=self.dest) + lines = stdout.splitlines() + lines = filter(lambda c: not re.search('^\\?\\?.*$', c), lines) return len(lines) > 0 @@ -114,30 +116,27 @@ class Bzr(object): Discards any changes to tracked files in the working tree since that commit. ''' - os.chdir(self.dest) if not force and self.has_local_mods(): self.module.fail_json(msg="Local modifications exist in branch (force=no).") - return self._command(["revert"], check_rc=True) + return self._command(["revert"], check_rc=True, cwd=self.dest) def fetch(self): '''updates branch from remote sources''' - os.chdir(self.dest) if self.version.lower() != 'head': - (rc, out, err) = self._command(["pull", "-r", self.version]) + (rc, out, err) = self._command(["pull", "-r", self.version], cwd=self.dest) else: - (rc, out, err) = self._command(["pull"]) + (rc, out, err) = self._command(["pull"], cwd=self.dest) if rc != 0: self.module.fail_json(msg="Failed to pull") return (rc, out, err) def switch_version(self): '''once pulled, switch to a particular revno or revid''' - os.chdir(self.dest) if self.version.lower() != 'head': args_list = ["revert", "-r", self.version] else: args_list = ["revert"] - return self._command(args_list, check_rc=True) + return self._command(args_list, check_rc=True, cwd=self.dest) # =========================================== diff --git a/library/source_control/git b/library/source_control/git index ca876c666b5..4f885c94001 100644 --- a/library/source_control/git +++ b/library/source_control/git @@ -181,11 +181,12 @@ def set_git_ssh(ssh_wrapper, key_file, ssh_opts): if ssh_opts: os.environ["GIT_SSH_OPTS"] = ssh_opts -def get_version(git_path, dest, ref="HEAD"): +def get_version(module, git_path, dest, ref="HEAD"): ''' samples the version of the git repo ''' - os.chdir(dest) + cmd = "%s rev-parse %s" % (git_path, ref) - sha = os.popen(cmd).read().rstrip("\n") + rc, stdout, stderr = module.run_command(cmd, cwd=dest) + sha = stdout.rstrip('\n') return sha def clone(git_path, module, repo, dest, remote, depth, version, bare, reference): @@ -195,7 +196,6 @@ def clone(git_path, module, repo, dest, remote, depth, version, bare, reference) os.makedirs(dest_dirname) except: pass - os.chdir(dest_dirname) cmd = [ git_path, 'clone' ] if bare: cmd.append('--bare') @@ -209,19 +209,19 @@ def clone(git_path, module, repo, dest, remote, depth, version, bare, reference) if reference: cmd.extend([ '--reference', str(reference) ]) cmd.extend([ repo, dest ]) - module.run_command(cmd, check_rc=True) + module.run_command(cmd, check_rc=True, cwd=dest_dirname) if bare: - os.chdir(dest) if remote != 'origin': - module.run_command([git_path, 'remote', 'add', remote, repo], check_rc=True) + module.run_command([git_path, 'remote', 'add', remote, repo], check_rc=True, cwd=dest) -def has_local_mods(git_path, dest, bare): +def has_local_mods(module, git_path, dest, bare): if bare: return False - os.chdir(dest) - cmd = "%s status -s" % (git_path,) - lines = os.popen(cmd).read().splitlines() - lines = filter(lambda c: not re.search('^\\?\\?.*$', c), lines) + + cmd = "%s status -s" % (git_path) + rc, stdout, stderr = module.run_command(cmd, cwd=dest) + lines = stdout.splitlines() + return len(lines) > 0 def reset(git_path, module, dest): @@ -230,16 +230,16 @@ def reset(git_path, module, dest): Discards any changes to tracked files in working tree since that commit. ''' - os.chdir(dest) cmd = "%s reset --hard HEAD" % (git_path,) - return module.run_command(cmd, check_rc=True) + return module.run_command(cmd, check_rc=True, cwd=dest) def get_remote_head(git_path, module, dest, version, remote, bare): cloning = False + cwd = None if remote == module.params['repo']: cloning = True else: - os.chdir(dest) + cwd = dest if version == 'HEAD': if cloning: # cloning the repo, just get the remote's HEAD version @@ -255,7 +255,7 @@ def get_remote_head(git_path, module, dest, version, remote, bare): # appears to be a sha1. return as-is since it appears # cannot check for a specific sha1 on remote return version - (rc, out, err) = module.run_command(cmd, check_rc=True ) + (rc, out, err) = module.run_command(cmd, check_rc=True, cwd=cwd) if len(out) < 1: module.fail_json(msg="Could not determine remote revision for %s" % version) rev = out.split()[0] @@ -270,10 +270,9 @@ def is_remote_tag(git_path, module, dest, remote, version): return False def get_branches(git_path, module, dest): - os.chdir(dest) branches = [] cmd = '%s branch -a' % (git_path,) - (rc, out, err) = module.run_command(cmd) + (rc, out, err) = module.run_command(cmd, cwd=dest) if rc != 0: module.fail_json(msg="Could not determine branch data - received %s" % out) for line in out.split('\n'): @@ -281,10 +280,9 @@ def get_branches(git_path, module, dest): return branches def get_tags(git_path, module, dest): - os.chdir(dest) tags = [] cmd = '%s tag' % (git_path,) - (rc, out, err) = module.run_command(cmd) + (rc, out, err) = module.run_command(cmd, cwd=dest) if rc != 0: module.fail_json(msg="Could not determine tag data - received %s" % out) for line in out.split('\n'): @@ -352,18 +350,17 @@ def get_head_branch(git_path, module, dest, remote, bare=False): def fetch(git_path, module, repo, dest, version, remote, bare): ''' updates repo from remote sources ''' - os.chdir(dest) if bare: - (rc, out1, err1) = module.run_command([git_path, 'fetch', remote, '+refs/heads/*:refs/heads/*']) + (rc, out1, err1) = module.run_command([git_path, 'fetch', remote, '+refs/heads/*:refs/heads/*'], cwd=dest) else: - (rc, out1, err1) = module.run_command("%s fetch %s" % (git_path, remote)) + (rc, out1, err1) = module.run_command("%s fetch %s" % (git_path, remote), cwd=dest) if rc != 0: module.fail_json(msg="Failed to download remote objects and refs") if bare: - (rc, out2, err2) = module.run_command([git_path, 'fetch', remote, '+refs/tags/*:refs/tags/*']) + (rc, out2, err2) = module.run_command([git_path, 'fetch', remote, '+refs/tags/*:refs/tags/*'], cwd=dest) else: - (rc, out2, err2) = module.run_command("%s fetch --tags %s" % (git_path, remote)) + (rc, out2, err2) = module.run_command("%s fetch --tags %s" % (git_path, remote), cwd=dest) if rc != 0: module.fail_json(msg="Failed to download remote objects and refs") (rc, out3, err3) = submodule_update(git_path, module, dest) @@ -371,28 +368,26 @@ def fetch(git_path, module, repo, dest, version, remote, bare): def submodule_update(git_path, module, dest): ''' init and update any submodules ''' - os.chdir(dest) # skip submodule commands if .gitmodules is not present if not os.path.exists(os.path.join(dest, '.gitmodules')): return (0, '', '') cmd = [ git_path, 'submodule', 'sync' ] - (rc, out, err) = module.run_command(cmd, check_rc=True) + (rc, out, err) = module.run_command(cmd, check_rc=True, cwd=dest) cmd = [ git_path, 'submodule', 'update', '--init', '--recursive' ] - (rc, out, err) = module.run_command(cmd) + (rc, out, err) = module.run_command(cmd, cwd=dest) if rc != 0: module.fail_json(msg="Failed to init/update submodules") return (rc, out, err) def switch_version(git_path, module, dest, remote, version): ''' once pulled, switch to a particular SHA, tag, or branch ''' - os.chdir(dest) cmd = '' if version != 'HEAD': if is_remote_branch(git_path, module, dest, remote, version): if not is_local_branch(git_path, module, dest, version): cmd = "%s checkout --track -b %s %s/%s" % (git_path, version, remote, version) else: - (rc, out, err) = module.run_command("%s checkout --force %s" % (git_path, version)) + (rc, out, err) = module.run_command("%s checkout --force %s" % (git_path, version), cwd=dest) if rc != 0: module.fail_json(msg="Failed to checkout branch %s" % version) cmd = "%s reset --hard %s/%s" % (git_path, remote, version) @@ -400,11 +395,11 @@ def switch_version(git_path, module, dest, remote, version): cmd = "%s checkout --force %s" % (git_path, version) else: branch = get_head_branch(git_path, module, dest, remote) - (rc, out, err) = module.run_command("%s checkout --force %s" % (git_path, branch)) + (rc, out, err) = module.run_command("%s checkout --force %s" % (git_path, branch), cwd=dest) if rc != 0: module.fail_json(msg="Failed to checkout branch %s" % branch) cmd = "%s reset --hard %s" % (git_path, remote) - (rc, out1, err1) = module.run_command(cmd) + (rc, out1, err1) = module.run_command(cmd, cwd=dest) if rc != 0: if version != 'HEAD': module.fail_json(msg="Failed to checkout %s" % (version)) @@ -484,12 +479,12 @@ def main(): # Just return having found a repo already in the dest path # this does no checking that the repo is the actual repo # requested. - before = get_version(git_path, dest) + before = get_version(module, git_path, dest) module.exit_json(changed=False, before=before, after=before) else: # else do a pull - local_mods = has_local_mods(git_path, dest, bare) - before = get_version(git_path, dest) + local_mods = has_local_mods(module, git_path, dest, bare) + before = get_version(module, git_path, dest) if local_mods: # failure should happen regardless of check mode if not force: @@ -519,7 +514,7 @@ def main(): switch_version(git_path, module, dest, remote, version) # determine if we changed anything - after = get_version(git_path, dest) + after = get_version(module, git_path, dest) changed = False if before != after or local_mods: diff --git a/library/system/service b/library/system/service index 2e26a47b636..5180a14d82b 100644 --- a/library/system/service +++ b/library/system/service @@ -207,7 +207,9 @@ class Service(object): os._exit(0) # Start the command - p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=lambda: os.close(pipe[1])) + if isinstance(cmd, basestring): + cmd = shlex.split(cmd) + p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=lambda: os.close(pipe[1])) stdout = "" stderr = "" fds = [p.stdout, p.stderr] diff --git a/library/system/setup b/library/system/setup index f140991dc27..c2e9d9792f9 100644 --- a/library/system/setup +++ b/library/system/setup @@ -29,7 +29,6 @@ import socket import struct import datetime import getpass -import subprocess import ConfigParser import StringIO @@ -1432,7 +1431,8 @@ class LinuxNetwork(Network): """ platform = 'Linux' - def __init__(self): + def __init__(self, module): + self.module = module Network.__init__(self) def populate(self): @@ -1618,12 +1618,15 @@ class LinuxNetwork(Network): ips['all_ipv6_addresses'].append(address) ip_path = module.get_bin_path("ip") - primary_data = subprocess.Popen( - [ip_path, 'addr', 'show', 'primary', device], - stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0] - secondary_data = subprocess.Popen( - [ip_path, 'addr', 'show', 'secondary', device], - stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0] + + args = [ip_path, 'addr', 'show', 'primary', device] + rc, stdout, stderr = self.module.run_command(args) + primary_data = stdout + + args = [ip_path, 'addr', 'show', 'secondary', device] + rc, stdout, stderr = self.module.run_command(args) + secondary_data = stdout + parse_ip_output(primary_data) parse_ip_output(secondary_data, secondary=True) @@ -2283,11 +2286,11 @@ def get_file_content(path, default=None): data = default return data -def ansible_facts(): +def ansible_facts(module): facts = {} facts.update(Facts().populate()) facts.update(Hardware().populate()) - facts.update(Network().populate()) + facts.update(Network(module).populate()) facts.update(Virtual().populate()) return facts @@ -2296,7 +2299,7 @@ def ansible_facts(): def run_setup(module): setup_options = {} - facts = ansible_facts() + facts = ansible_facts(module) for (k, v) in facts.items(): setup_options["ansible_%s" % k.replace('-', '_')] = v diff --git a/library/web_infrastructure/django_manage b/library/web_infrastructure/django_manage index 68eb92c1bfe..b02a9398f52 100644 --- a/library/web_infrastructure/django_manage +++ b/library/web_infrastructure/django_manage @@ -232,7 +232,6 @@ def main(): _ensure_virtualenv(module) - os.chdir(app_path) cmd = "python manage.py %s" % (command, ) if command in noinput_commands: @@ -251,7 +250,7 @@ def main(): if module.params[param]: cmd = '%s %s' % (cmd, module.params[param]) - rc, out, err = module.run_command(cmd) + rc, out, err = module.run_command(cmd, cwd=app_path) if rc != 0: if command == 'createcachetable' and 'table' in err and 'already exists' in err: out = 'Already exists.'