def from_fqcr(ref, ref_type): """ Parse a string as a fully-qualified collection reference, raises ValueError if invalid :param ref: collection reference to parse (a valid ref is of the form 'ns.coll.resource' or 'ns.coll.subdir1.subdir2.resource') :param ref_type: the type of the reference, eg 'module', 'role', 'doc_fragment' :return: a populated AssibleCollectionRef object """ # assuming the fq_name is of the form (ns).(coll).(optional_subdir_N).(resource_name), # we split the resource name off the right, split ns and coll off the left, and we're left with any optional # subdirs that need to be added back below the plugin-specific subdir we'll add. So: # ns.coll.resource -> assible_collections.ns.coll.plugins.(plugintype).resource # ns.coll.subdir1.resource -> assible_collections.ns.coll.plugins.subdir1.(plugintype).resource # ns.coll.rolename -> assible_collections.ns.coll.roles.rolename if not AssibleCollectionRef.is_valid_fqcr(ref): raise ValueError('{0} is not a valid collection reference'.format(to_native(ref))) ref = to_text(ref, errors='strict') ref_type = to_text(ref_type, errors='strict') resource_splitname = ref.rsplit(u'.', 1) package_remnant = resource_splitname[0] resource = resource_splitname[1] # split the left two components of the collection package name off, anything remaining is plugin-type # specific subdirs to be added back on below the plugin type package_splitname = package_remnant.split(u'.', 2) if len(package_splitname) == 3: subdirs = package_splitname[2] else: subdirs = u'' collection_name = u'.'.join(package_splitname[0:2]) return AssibleCollectionRef(collection_name, subdirs, resource, ref_type)
def main(): module_args = dict( username=dict(type='str', required=True), password=dict(type='str', required=True, no_log=True), ) module = AssibleModule( argument_spec=module_args, required_together=[('username', 'password')], ) # Debugging purposes, get the Kerberos version. On platforms like OpenSUSE this may not be on the PATH. try: process = subprocess.Popen(['krb5-config', '--version'], stdout=subprocess.PIPE) stdout, stderr = process.communicate() version = to_text(stdout) except OSError as e: if e.errno != errno.ENOENT: raise version = 'Unknown (no krb5-config)' # Heimdal has a few quirks that we want to paper over in this module # 1. KRB5_TRACE does not work in any released version (<=7.7), we need to use a custom krb5.config to enable it # 2. When reading the password it reads from the pty not stdin by default causing an issue with subprocess. We # can control that behaviour with '--password-file=STDIN' is_heimdal = os.uname()[0] in ['Darwin', 'FreeBSD'] kinit_args = ['kinit'] config = {} if is_heimdal: kinit_args.append('--password-file=STDIN') config['logging'] = {'krb5': 'FILE:/dev/stdout'} kinit_args.append( to_text(module.params['username'], errors='surrogate_or_strict')) with krb5_conf(module, config): # Weirdly setting KRB5_CONFIG in the modules environment block does not work unless we pass it in explicitly. # Take a copy of the existing environment to make sure the process has the same env vars as ours. Also set # KRB5_TRACE to output and debug logs helping to identify problems when calling kinit with MIT. kinit_env = os.environ.copy() kinit_env['KRB5_TRACE'] = '/dev/stdout' process = subprocess.Popen(kinit_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=kinit_env) stdout, stderr = process.communicate( to_bytes(module.params['password'], errors='surrogate_or_strict') + b'\n') rc = process.returncode module.exit_json(changed=True, stdout=to_text(stdout), stderr=to_text(stderr), rc=rc, version=version)
def __init__(self, collection_name, subdirs, resource, ref_type): """ Create an AssibleCollectionRef from components :param collection_name: a collection name of the form 'namespace.collectionname' :param subdirs: optional subdir segments to be appended below the plugin type (eg, 'subdir1.subdir2') :param resource: the name of the resource being references (eg, 'mymodule', 'someaction', 'a_role') :param ref_type: the type of the reference, eg 'module', 'role', 'doc_fragment' """ collection_name = to_text(collection_name, errors='strict') if subdirs is not None: subdirs = to_text(subdirs, errors='strict') resource = to_text(resource, errors='strict') ref_type = to_text(ref_type, errors='strict') if not self.is_valid_collection_name(collection_name): raise ValueError('invalid collection name (must be of the form namespace.collection): {0}'.format(to_native(collection_name))) if ref_type not in self.VALID_REF_TYPES: raise ValueError('invalid collection ref_type: {0}'.format(ref_type)) self.collection = collection_name if subdirs: if not re.match(self.VALID_SUBDIRS_RE, subdirs): raise ValueError('invalid subdirs entry: {0} (must be empty/None or of the form subdir1.subdir2)'.format(to_native(subdirs))) self.subdirs = subdirs else: self.subdirs = u'' self.resource = resource self.ref_type = ref_type package_components = [u'assible_collections', self.collection] fqcr_components = [self.collection] self.n_python_collection_package_name = to_native('.'.join(package_components)) if self.ref_type == u'role': package_components.append(u'roles') else: # we assume it's a plugin package_components += [u'plugins', self.ref_type] if self.subdirs: package_components.append(self.subdirs) fqcr_components.append(self.subdirs) if self.ref_type == u'role': # roles are their own resource package_components.append(self.resource) fqcr_components.append(self.resource) self.n_python_package_name = to_native('.'.join(package_components)) self._fqcr = u'.'.join(fqcr_components)
def _get_collection_role_path(role_name, collection_list=None): acr = AssibleCollectionRef.try_parse_fqcr(role_name, 'role') if acr: # looks like a valid qualified collection ref; skip the collection_list collection_list = [acr.collection] subdirs = acr.subdirs resource = acr.resource elif not collection_list: return None # not a FQ role and no collection search list spec'd, nothing to do else: resource = role_name # treat as unqualified, loop through the collection search list to try and resolve subdirs = '' for collection_name in collection_list: try: acr = AssibleCollectionRef(collection_name=collection_name, subdirs=subdirs, resource=resource, ref_type='role') # FIXME: error handling/logging; need to catch any import failures and move along pkg = import_module(acr.n_python_package_name) if pkg is not None: # the package is now loaded, get the collection's package and ask where it lives path = os.path.dirname(to_bytes(sys.modules[acr.n_python_package_name].__file__, errors='surrogate_or_strict')) return resource, to_text(path, errors='surrogate_or_strict'), collection_name except IOError: continue except Exception as ex: # FIXME: pick out typical import errors first, then error logging continue return None
def yaml_to_dict(yaml, content_id): """ Return a Python dict version of the provided YAML. Conversion is done in a subprocess since the current Python interpreter does not have access to PyYAML. """ if content_id in yaml_to_dict_cache: return yaml_to_dict_cache[content_id] try: cmd = [external_python, yaml_to_json_path] proc = subprocess.Popen([to_bytes(c) for c in cmd], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout_bytes, stderr_bytes = proc.communicate(to_bytes(yaml)) if proc.returncode != 0: raise Exception( 'command %s failed with return code %d: %s' % ([to_native(c) for c in cmd ], proc.returncode, to_native(stderr_bytes))) data = yaml_to_dict_cache[content_id] = json.loads( to_text(stdout_bytes), object_hook=object_hook) return data except Exception as ex: raise Exception( 'internal importer error - failed to parse yaml: %s' % to_native(ex))
def is_valid_collection_name(collection_name): """ Validates if the given string is a well-formed collection name (does not look up the collection itself) :param collection_name: candidate collection name to validate (a valid name is of the form 'ns.collname') :return: True if the collection name passed is well-formed, False otherwise """ collection_name = to_text(collection_name) return bool(re.match(AssibleCollectionRef.VALID_COLLECTION_NAME_RE, collection_name))
def scm_archive_resource(src, scm='git', name=None, version='HEAD', keep_scm_meta=False): def run_scm_cmd(cmd, tempdir): try: stdout = '' stderr = '' popen = Popen(cmd, cwd=tempdir, stdout=PIPE, stderr=PIPE) stdout, stderr = popen.communicate() except Exception as e: ran = " ".join(cmd) display.debug("ran %s:" % ran) raise AssibleError("when executing %s: %s" % (ran, to_native(e))) if popen.returncode != 0: raise AssibleError("- command %s failed in directory %s (rc=%s) - %s" % (' '.join(cmd), tempdir, popen.returncode, to_native(stderr))) if scm not in ['hg', 'git']: raise AssibleError("- scm %s is not currently supported" % scm) try: scm_path = get_bin_path(scm) except (ValueError, OSError, IOError): raise AssibleError("could not find/use %s, it is required to continue with installing %s" % (scm, src)) tempdir = tempfile.mkdtemp(dir=C.DEFAULT_LOCAL_TMP) clone_cmd = [scm_path, 'clone', src, name] run_scm_cmd(clone_cmd, tempdir) if scm == 'git' and version: checkout_cmd = [scm_path, 'checkout', to_text(version)] run_scm_cmd(checkout_cmd, os.path.join(tempdir, name)) temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.tar', dir=C.DEFAULT_LOCAL_TMP) archive_cmd = None if keep_scm_meta: display.vvv('tarring %s from %s to %s' % (name, tempdir, temp_file.name)) with tarfile.open(temp_file.name, "w") as tar: tar.add(os.path.join(tempdir, name), arcname=name) elif scm == 'hg': archive_cmd = [scm_path, 'archive', '--prefix', "%s/" % name] if version: archive_cmd.extend(['-r', version]) archive_cmd.append(temp_file.name) elif scm == 'git': archive_cmd = [scm_path, 'archive', '--prefix=%s/' % name, '--output=%s' % temp_file.name] if version: archive_cmd.append(version) else: archive_cmd.append('HEAD') if archive_cmd is not None: display.vvv('archiving %s' % archive_cmd) run_scm_cmd(archive_cmd, os.path.join(tempdir, name)) return temp_file.name
def resource_from_fqcr(ref): """ Return resource from a fully-qualified collection reference, or from a simple resource name. For fully-qualified collection references, this is equivalent to ``AssibleCollectionRef.from_fqcr(ref).resource``. :param ref: collection reference to parse :return: the resource as a unicode string """ ref = to_text(ref, errors='strict') return ref.split(u'.')[-1]
def is_valid_fqcr(ref, ref_type=None): """ Validates if is string is a well-formed fully-qualified collection reference (does not look up the collection itself) :param ref: candidate collection reference to validate (a valid ref is of the form 'ns.coll.resource' or 'ns.coll.subdir1.subdir2.resource') :param ref_type: optional reference type to enable deeper validation, eg 'module', 'role', 'doc_fragment' :return: True if the collection ref passed is well-formed, False otherwise """ ref = to_text(ref) if not ref_type: return bool(re.match(AssibleCollectionRef.VALID_FQCR_RE, ref)) return bool(AssibleCollectionRef.try_parse_fqcr(ref, ref_type))
def legacy_plugin_dir_to_plugin_type(legacy_plugin_dir_name): """ Utility method to convert from a PluginLoader dir name to a plugin ref_type :param legacy_plugin_dir_name: PluginLoader dir name (eg, 'action_plugins', 'library') :return: the corresponding plugin ref_type (eg, 'action', 'role') """ legacy_plugin_dir_name = to_text(legacy_plugin_dir_name) plugin_type = legacy_plugin_dir_name.replace(u'_plugins', u'') if plugin_type == u'library': plugin_type = u'modules' if plugin_type not in AssibleCollectionRef.VALID_REF_TYPES: raise ValueError('{0} cannot be mapped to a valid collection ref type'.format(to_native(legacy_plugin_dir_name))) return plugin_type
def test_to_text_unsafe(): assert isinstance(to_text(AssibleUnsafeBytes(b'foo')), AssibleUnsafeText) assert to_text(AssibleUnsafeBytes(b'foo')) == AssibleUnsafeText(u'foo')
def test_to_text(in_string, encoding, expected): """test happy path of decoding to text""" assert to_text(in_string, encoding) == expected
def playbook_paths(cls): cls._require_finder() return [to_text(p) for p in cls._collection_finder._n_playbook_paths]
class AssibleCollectionRef: # FUTURE: introspect plugin loaders to get these dynamically? VALID_REF_TYPES = frozenset(to_text(r) for r in ['action', 'become', 'cache', 'callback', 'cliconf', 'connection', 'doc_fragments', 'filter', 'httpapi', 'inventory', 'lookup', 'module_utils', 'modules', 'netconf', 'role', 'shell', 'strategy', 'terminal', 'test', 'vars']) # FIXME: tighten this up to match Python identifier reqs, etc VALID_COLLECTION_NAME_RE = re.compile(to_text(r'^(\w+)\.(\w+)$')) VALID_SUBDIRS_RE = re.compile(to_text(r'^\w+(\.\w+)*$')) VALID_FQCR_RE = re.compile(to_text(r'^\w+\.\w+\.\w+(\.\w+)*$')) # can have 0-N included subdirs as well def __init__(self, collection_name, subdirs, resource, ref_type): """ Create an AssibleCollectionRef from components :param collection_name: a collection name of the form 'namespace.collectionname' :param subdirs: optional subdir segments to be appended below the plugin type (eg, 'subdir1.subdir2') :param resource: the name of the resource being references (eg, 'mymodule', 'someaction', 'a_role') :param ref_type: the type of the reference, eg 'module', 'role', 'doc_fragment' """ collection_name = to_text(collection_name, errors='strict') if subdirs is not None: subdirs = to_text(subdirs, errors='strict') resource = to_text(resource, errors='strict') ref_type = to_text(ref_type, errors='strict') if not self.is_valid_collection_name(collection_name): raise ValueError('invalid collection name (must be of the form namespace.collection): {0}'.format(to_native(collection_name))) if ref_type not in self.VALID_REF_TYPES: raise ValueError('invalid collection ref_type: {0}'.format(ref_type)) self.collection = collection_name if subdirs: if not re.match(self.VALID_SUBDIRS_RE, subdirs): raise ValueError('invalid subdirs entry: {0} (must be empty/None or of the form subdir1.subdir2)'.format(to_native(subdirs))) self.subdirs = subdirs else: self.subdirs = u'' self.resource = resource self.ref_type = ref_type package_components = [u'assible_collections', self.collection] fqcr_components = [self.collection] self.n_python_collection_package_name = to_native('.'.join(package_components)) if self.ref_type == u'role': package_components.append(u'roles') else: # we assume it's a plugin package_components += [u'plugins', self.ref_type] if self.subdirs: package_components.append(self.subdirs) fqcr_components.append(self.subdirs) if self.ref_type == u'role': # roles are their own resource package_components.append(self.resource) fqcr_components.append(self.resource) self.n_python_package_name = to_native('.'.join(package_components)) self._fqcr = u'.'.join(fqcr_components) def __repr__(self): return 'AssibleCollectionRef(collection={0!r}, subdirs={1!r}, resource={2!r})'.format(self.collection, self.subdirs, self.resource) @property def fqcr(self): return self._fqcr @staticmethod def from_fqcr(ref, ref_type): """ Parse a string as a fully-qualified collection reference, raises ValueError if invalid :param ref: collection reference to parse (a valid ref is of the form 'ns.coll.resource' or 'ns.coll.subdir1.subdir2.resource') :param ref_type: the type of the reference, eg 'module', 'role', 'doc_fragment' :return: a populated AssibleCollectionRef object """ # assuming the fq_name is of the form (ns).(coll).(optional_subdir_N).(resource_name), # we split the resource name off the right, split ns and coll off the left, and we're left with any optional # subdirs that need to be added back below the plugin-specific subdir we'll add. So: # ns.coll.resource -> assible_collections.ns.coll.plugins.(plugintype).resource # ns.coll.subdir1.resource -> assible_collections.ns.coll.plugins.subdir1.(plugintype).resource # ns.coll.rolename -> assible_collections.ns.coll.roles.rolename if not AssibleCollectionRef.is_valid_fqcr(ref): raise ValueError('{0} is not a valid collection reference'.format(to_native(ref))) ref = to_text(ref, errors='strict') ref_type = to_text(ref_type, errors='strict') resource_splitname = ref.rsplit(u'.', 1) package_remnant = resource_splitname[0] resource = resource_splitname[1] # split the left two components of the collection package name off, anything remaining is plugin-type # specific subdirs to be added back on below the plugin type package_splitname = package_remnant.split(u'.', 2) if len(package_splitname) == 3: subdirs = package_splitname[2] else: subdirs = u'' collection_name = u'.'.join(package_splitname[0:2]) return AssibleCollectionRef(collection_name, subdirs, resource, ref_type) @staticmethod def try_parse_fqcr(ref, ref_type): """ Attempt to parse a string as a fully-qualified collection reference, returning None on failure (instead of raising an error) :param ref: collection reference to parse (a valid ref is of the form 'ns.coll.resource' or 'ns.coll.subdir1.subdir2.resource') :param ref_type: the type of the reference, eg 'module', 'role', 'doc_fragment' :return: a populated AssibleCollectionRef object on successful parsing, else None """ try: return AssibleCollectionRef.from_fqcr(ref, ref_type) except ValueError: pass @staticmethod def legacy_plugin_dir_to_plugin_type(legacy_plugin_dir_name): """ Utility method to convert from a PluginLoader dir name to a plugin ref_type :param legacy_plugin_dir_name: PluginLoader dir name (eg, 'action_plugins', 'library') :return: the corresponding plugin ref_type (eg, 'action', 'role') """ legacy_plugin_dir_name = to_text(legacy_plugin_dir_name) plugin_type = legacy_plugin_dir_name.replace(u'_plugins', u'') if plugin_type == u'library': plugin_type = u'modules' if plugin_type not in AssibleCollectionRef.VALID_REF_TYPES: raise ValueError('{0} cannot be mapped to a valid collection ref type'.format(to_native(legacy_plugin_dir_name))) return plugin_type @staticmethod def is_valid_fqcr(ref, ref_type=None): """ Validates if is string is a well-formed fully-qualified collection reference (does not look up the collection itself) :param ref: candidate collection reference to validate (a valid ref is of the form 'ns.coll.resource' or 'ns.coll.subdir1.subdir2.resource') :param ref_type: optional reference type to enable deeper validation, eg 'module', 'role', 'doc_fragment' :return: True if the collection ref passed is well-formed, False otherwise """ ref = to_text(ref) if not ref_type: return bool(re.match(AssibleCollectionRef.VALID_FQCR_RE, ref)) return bool(AssibleCollectionRef.try_parse_fqcr(ref, ref_type)) @staticmethod def is_valid_collection_name(collection_name): """ Validates if the given string is a well-formed collection name (does not look up the collection itself) :param collection_name: candidate collection name to validate (a valid name is of the form 'ns.collname') :return: True if the collection name passed is well-formed, False otherwise """ collection_name = to_text(collection_name) return bool(re.match(AssibleCollectionRef.VALID_COLLECTION_NAME_RE, collection_name))