def __init__(self, *args, **kwargs): if not ANSIBLE_UTILS_IS_INSTALLED: raise AnsibleActionFail( "ansible.utils is not installed. Execute 'ansible-galaxy collection install ansible.utils'" ) super(ActionModule, self).__init__(*args, **kwargs) self._supports_async = False self._supports_check_mode = False self._result = None
def run(self, tmp=None, task_vars=None): result = super(ActionModule, self).run(tmp, task_vars) result['changed'] = False args = self._task.args uid = args.get('uid') groups = args.get('groups', []) wanted_state = args.get('state', 'present') if uid is None: raise AnsibleActionFail('uid cannot be empty for dcos_user') user = iam.get_user(uid) current_state = 'present' if user is not None else 'absent' if self._play_context.check_mode: if current_state != wanted_state: result['changed'] = True result['msg'] = 'would change user {} to be {}'.format( uid, wanted_state) return result if current_state == wanted_state: display.vvv("User {} already {}".format(uid, wanted_state)) if wanted_state == 'present': result['changed'] = update_group_memberships(uid, groups) else: display.vvv("User {} not {}".format(uid, wanted_state)) try: if wanted_state == 'present': iam.create_user(uid, **iam.check_user_args(**args)) update_group_memberships(uid, groups) result['msg'] = "User {} was created".format(uid) elif wanted_state == 'absent': iam.delete_user(uid) result['msg'] = "User {} was deleted".format(uid) except DCOSException as e: raise AnsibleActionFail(e.text) result['changed'] = True return result
def set_value(self, obj, path, val): """Set a value :param obj: The object to modify :type obj: mutable object :param path: The path to where the update should be made :type path: list :param val: The new value to place at path :type val: string, dict, list, bool, etc """ first, rest = path[0], path[1:] if rest: try: new_obj = obj[first] except (KeyError, TypeError): msg = ("Error: the key '{first}' was not found " "in {obj}.".format(obj=obj, first=first)) raise AnsibleActionFail(msg) self.set_value(new_obj, rest, val) else: if isinstance(obj, MutableMapping): if obj.get(first) != val: self._result["changed"] = True obj[first] = val elif isinstance(obj, MutableSequence): if not isinstance(first, int): msg = ("Error: {obj} is a list, " "but index provided was not an integer: '{first}'" ).format(obj=obj, first=first) raise AnsibleActionFail(msg) if first > len(obj): msg = "Error: {obj} not long enough for item #{first} to be set.".format( obj=obj, first=first) raise AnsibleActionFail(msg) if first == len(obj): obj.append(val) self._result["changed"] = True else: if obj[first] != val: obj[first] = val self._result["changed"] = True else: msg = "update_fact can only modify mutable objects." raise AnsibleActionFail(msg)
def run(self, tmp=None, task_vars=None): result = super(ActionModule, self).run(tmp, task_vars) result['changed'] = False args = self._task.args gid = args.get('gid') description = args.get('description', 'created by Ansible') permissions = args.get('permissions', []) wanted_state = args.get('state', 'present') if gid is None: raise AnsibleActionFail('gid cannot be empty for dcos_user') group = iam.get_group(gid) current_state = 'present' if group is not None else 'absent' if self._play_context.check_mode: if current_state != wanted_state: result['changed'] = True result['msg'] = 'would change group {} to be {}'.format( gid, wanted_state) return result if current_state == wanted_state: display.vvv("User {} already {}".format(gid, wanted_state)) if wanted_state == 'present': result['changed'] = update_permissions(gid, permissions) else: display.vvv("User {} not {}".format(gid, wanted_state)) try: if wanted_state == 'present': iam.create_group(gid, description) update_permissions(gid, permissions) result['msg'] = 'Group {} was created'.format(gid) elif wanted_state == 'absent': iam.delete_group(gid) result['msg'] = 'Group {} was deleted'.format(gid) except DCOSException as e: raise AnsibleActionFail(e) result['changed'] = True return result
def run(self, tmp=None, task_vars=None): result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect if self._play_context.check_mode: # in --check mode, always skip this module execution result['skipped'] = True result['msg'] = 'The dcos task does not support check mode' return result args = self._task.args package_name = args.get('name', None) package_version = args.get('version', None) if package_version is None: raise AnsibleActionFail('version cannot be empty for dcos_package') state = args.get('state', 'present') # ensure app_id has no leading or trailing / app_id = args.get('app_id', package_name).strip('/') options = args.get('options') or {} try: options['service']['name'] = app_id except KeyError: options['service'] = {'name': app_id} ensure_dcos() current_version = get_current_version(package_name, app_id) wanted_version = get_wanted_version(package_version, state) if current_version == wanted_version: display.vvv( "Package {} already in desired state".format(package_name)) if state == "present": update_package(package_name, app_id, wanted_version, options) result['changed'] = False else: display.vvv("Package {} not in desired state".format(package_name)) if wanted_version is not None: if current_version is not None: update_package(package_name, app_id, wanted_version, options) else: install_package(package_name, wanted_version, options) else: uninstall_package(package_name, app_id) result['changed'] = True return result
def run(self, tmp=None, task_vars=None): if task_vars is None: task_vars = dict() result = super(ActionModule, self).run(tmp, task_vars) tmp = self._connection._shell.tempdir src = self._task.args.get('src', None) remote_src = boolean(self._task.args.get('remote_src', 'no'), strict=False) try: if src is None: raise AnsibleActionFail("src is required") elif remote_src: # everything is remote, so we just execute the module # without changing any of the module arguments raise _AnsibleActionDone(result=self._execute_module( task_vars=task_vars)) try: src = self._find_needle('files', src) except AnsibleError as e: raise AnsibleActionFail(to_native(e)) tmp_src = self._connection._shell.join_path( tmp, os.path.basename(src)) self._transfer_file(src, tmp_src) self._fixup_perms2((tmp_src, )) new_module_args = self._task.args.copy() new_module_args.update(dict(src=tmp_src, )) result.update( self._execute_module('patch', module_args=new_module_args, task_vars=task_vars)) except AnsibleAction as e: result.update(e.result) finally: self._remove_tmp_path(tmp) return result
def run(self, tmp=None, task_vars=None): """ Action Plugins should implement this method to perform their tasks. Everything else in this base class is a helper method for the action plugin to do that. :kwarg tmp: Deprecated parameter. This is no longer used. An action plugin that calls another one and wants to use the same remote tmp for both should set self._connection._shell.tmpdir rather than this parameter. :kwarg task_vars: The variables (host vars, group vars, config vars, etc) associated with this task. :returns: dictionary of results from the module Implementors of action modules may find the following variables especially useful: * Module parameters. These are stored in self._task.args """ result = {} if tmp is not None: result['warning'] = ['ActionModule.run() no longer honors the tmp parameter. Action' ' plugins should set self._connection._shell.tmpdir to share' ' the tmpdir'] del tmp if self._task.async_val and not self._supports_async: raise AnsibleActionFail('async is not supported for this task.') elif self._play_context.check_mode and not self._supports_check_mode: raise AnsibleActionSkip('check mode is not supported for this task.') elif self._task.async_val and self._play_context.check_mode: raise AnsibleActionFail('check mode and async cannot be used on same task.') # Error if invalid argument is passed if self._VALID_ARGS: task_opts = frozenset(self._task.args.keys()) bad_opts = task_opts.difference(self._VALID_ARGS) if bad_opts: raise AnsibleActionFail('Invalid options for %s: %s' % (self._task.action, ','.join(list(bad_opts)))) if self._connection._shell.tmpdir is None and self._early_needs_tmp_path(): self._make_tmp_path() return result
def readAKS(json): aks = dict() results = json.get('results', None) if results is None: raise AnsibleActionFail( "Activation Key API data doesn't have results array!") for item in results: aks[item['name']] = dict(id=item['id'], subscriptions=[], to_add=[]) return aks
def recheck (self): """Call holds() again, and this time it better return True. Call from within enforce() at the end, if you are unable to deduce whether enforce() was successful from the information you have. """ if not self.holds(): raise AnsibleActionFail("%s: still doesn't hold after attempt to enforce" % self.explainer())
def run(self, tmp=None, task_vars=None): if task_vars is None: task_vars = dict() result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect facts = {} cacheable = boolean(self._task.args.pop('cacheable', False)) if self._task.args: for (k, v) in iteritems(self._task.args): k = self._templar.template(k) if not isidentifier(k): raise AnsibleActionFail( "The variable name '%s' is not valid. Variables must start with a letter or underscore character, " "and contain only letters, numbers and underscores." % k) # NOTE: this should really use BOOLEANS from convert_bool, but only in the k=v case, # right now it converts matching explicit YAML strings also when 'jinja2_native' is disabled. if not C.DEFAULT_JINJA2_NATIVE and isinstance( v, string_types) and v.lower() in ('true', 'false', 'yes', 'no'): v = boolean(v, strict=False) facts[k] = v else: raise AnsibleActionFail( 'No key/value pairs provided, at least one is required for this action to succeed' ) if facts: # just as _facts actions, we don't set changed=true as we are not modifying the actual host result['ansible_facts'] = facts result['_ansible_facts_cacheable'] = cacheable else: # this should not happen, but JIC we get here raise AnsibleActionFail( 'Unable to create any variables with provided arguments') return result
def _fail_json(self, msg): """ Replace the AnsibleModule fai_json here :param msg: The message for the failure :type msg: str """ msg = re.sub( r"\(basic\.pyc?\)", "'{action}'".format(action=self._task.action), msg, ) raise AnsibleActionFail(msg)
def get_file_realpath(self, local_path): # local_path is only supported by k8s_cp module. if self._task.action not in ( "k8s_cp", "kubernetes.core.k8s_cp", "community.kubernetes.k8s_cp", ): raise AnsibleActionFail( "'local_path' is only supported parameter for 'k8s_cp' module." ) if os.path.exists(local_path): return local_path try: # find in expected paths return self._find_needle("files", local_path) except AnsibleError: raise AnsibleActionFail("%s does not exist in local filesystem" % local_path)
def run(self, tmp=None, task_vars=None): result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect if self._play_context.check_mode: # in --check mode, always skip this module execution result['skipped'] = True result['msg'] = 'The dcos task does not support check mode' return result args = self._task.args path = args.get('path') if path is None: raise AnsibleActionFail('path cannot be empty for dcos_secret') store = args.get('store', 'default') file = args.get('file') with open(file, "rb") as wanted_value: wanted_state = args.get('state', 'present') ensure_dcos() ensure_dcos_security() current_value = get_secret_value(path, store) current_state = 'present' if current_value is not None else 'absent' if current_state == wanted_state: display.vvv( "DC/OS Secret {} already in desired state {}".format(path, wanted_state)) result['changed'] = False if wanted_state == "present" and current_value != wanted_value: secret_update_from_file(path, file, store) result['changed'] = True result['msg'] = "Secret {} was updated".format(path) else: display.vvv("DC/OS Secret {} not in desired state {}".format(path, wanted_state)) if wanted_state != 'absent': secret_create_from_file(path, file, store) result['msg'] = "Secret {} was created".format(path) else: secret_delete(path, store) result['msg'] = "Secret {} was deleted".format(path) result['changed'] = True return result
def readAKSubs(json): akSubs = dict() results = json.get('results', None) if results is None: raise AnsibleActionFail( "Activation Key Subscription API data doesn't have results array!" ) for item in results: dd = [] results2 = item.get('json', dict(json={})).get('results', None) if results2 is None: raise AnsibleActionFail( "Activation Key Subscription API data doesn't have results array!" ) for sub in results2: dd.append(sub['product_id']) akSubs[item['item']['name']] = list(set(dd)) return akSubs
def _validateOptions(self): # Options type validation # stings for s_type in "name": if s_type in self._task.args: value = ensure_type(self._task.args[s_type], "string") if value is not None and not isinstance(value, string_types): raise AnsibleActionFail( "%s is expected to be a string, but got %s instead" % (s_type, type(value))) self._task.args[s_type] = value
def _check_argspec(self): aav = AnsibleArgSpecValidator( data=self._task.args, schema=dict(argument_spec=argument_spec), schema_format="argspec", schema_conditionals=dict(required_if=required_if), name=self._task.action, ) valid, errors, self._task.args = aav.validate() if not valid: raise AnsibleActionFail(errors)
def _mkdir(self, path, task_vars): if path == '/': return stat = self._execute_module(module_name='stat', module_args=dict(path=path), task_vars=task_vars) if stat.get('failed'): raise AnsibleActionFail(stat['msg']) if not stat['stat']['exists']: parent = os.path.dirname(path) self._mkdir(parent, task_vars) mkdir = self._execute_module(module_name='file', module_args=dict(path=path, state='directory'), task_vars=task_vars) if mkdir.get('failed'): raise AnsibleActionFail(mkdir['msg'])
def _get_current_state(self, name): path = self._get_plugin_path(name) plugin_stat = self._run_action('stat', {'path': path}) if 'failed' in plugin_stat: raise AnsibleActionFail("Cannot stat() %s" % path) if not ('stat' in plugin_stat and plugin_stat['stat']['exists']): return set(['absent']) elif plugin_stat['stat']['islnk']: return set(['symlinked', self._get_plugin_activation_state(name)]) else: return set(['installed', self._get_plugin_activation_state(name)])
def run(self, tmp=None, task_vars=None): ''' handler for package operations ''' self._supports_check_mode = True self._supports_async = True result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect module = self._task.args.get('use', 'auto').lower() if module == 'auto': try: if self._task.delegate_to: # if we delegate, we should use delegated host's facts module = self._templar.template("{{hostvars['%s']['ansible_facts']['service_mgr']}}" % self._task.delegate_to) else: module = self._templar.template('{{ansible_facts.service_mgr}}') except Exception: pass # could not get it from template! try: if module == 'auto': facts = self._execute_module(module_name='setup', module_args=dict(gather_subset='!all', filter='ansible_service_mgr'), task_vars=task_vars) self._display.debug("Facts %s" % facts) module = facts.get('ansible_facts', {}).get('ansible_service_mgr', 'auto') if not module or module == 'auto' or module not in self._shared_loader_obj.module_loader: module = 'service' if module != 'auto': # run the 'service' module new_module_args = self._task.args.copy() if 'use' in new_module_args: del new_module_args['use'] if module in self.UNUSED_PARAMS: for unused in self.UNUSED_PARAMS[module]: if unused in new_module_args: del new_module_args[unused] self._display.warning('Ignoring "%s" as it is not used in "%s"' % (unused, module)) self._display.vvvv("Running %s" % module) result.update(self._execute_module(module_name=module, module_args=new_module_args, task_vars=task_vars, wrap_async=self._task.async_val)) else: raise AnsibleActionFail('Could not detect which service manager to use. Try gathering facts or setting the "use" option.') except AnsibleAction as e: result.update(e.result) finally: if not self._task.async_val: self._remove_tmp_path(self._connection._shell.tmpdir) return result
def _pwd_entry_get(self, path, task_vars): slurp = self._execute_module(module_name='slurp', module_args=dict(src=path), task_vars=task_vars) if slurp.get('failed'): raise AnsibleActionFail(slurp['msg']) secret = b64decode(slurp['content']) secret = b64decode(secret) salt = secret[0:16] ciphertext = secret[16:] return [salt, ciphertext]
def _mkdirs(self, path, task_vars): path = path.rstrip('/') parent = os.path.dirname(path) self._mkdir(parent, task_vars) mkdir = self._execute_module(module_name='file', module_args=dict(path=path, mode=0o700, state='directory'), task_vars=task_vars) if mkdir.get('failed'): raise AnsibleActionFail(mkdir['msg'])
def run(self, tmp=None, task_vars=None): if task_vars is None: task_vars = dict() result = super(ActionModule, self).run(tmp, task_vars) del tmp # parse args args = self._get_args() changed = False src_path = args['path'] # check if source file exists file_stat = self._execute_module(module_name='stat', module_args=dict(path=src_path), task_vars=task_vars) timestamp = self._get_date_string(args['date_format']) dest_path = '.'.join([src_path, timestamp]) if file_stat.get('stat', {}).get('exists', False) is False: # file doesn't exist so we're done raise AnsibleActionSkip("{} does not exist.".format(src_path)) # check if destination file exists file_stat = self._execute_module(module_name='stat', module_args=dict(path=dest_path), task_vars=task_vars) if (not args['force'] and file_stat.get('stat', {}).get('exists', False) is True): raise AnsibleActionFail("Destination file {} exists. Use force " "option to proceed.".format(dest_path)) # copy file out of the way copy_result = self._execute_module(module_name='copy', module_args=dict(src=src_path, dest=dest_path, remote_src=True), task_vars=task_vars) if copy_result.get('failed', False): return copy_result changed = True if boolean(args.get('remove', False), strict=False): # cleanup original file as requested file_result = self._execute_module(module_name='file', module_args=dict( path=src_path, state='absent'), task_vars=task_vars) if file_result.get('failed', False): return file_result result['dest'] = copy_result['dest'] result['changed'] = changed return result
def set_value(self, obj, path, val): first, rest = path[0], path[1:] if rest: try: new_obj = obj[first] except (KeyError, TypeError): msg = ( "Error: the key '{first}' was not found " "in {obj}.".format(obj=obj, first=first) ) raise AnsibleActionFail(msg) self.set_value(new_obj, rest, val) else: if isinstance(obj, MutableMapping): if obj.get(first) != val: self._result["changed"] = True obj[first] = val elif isinstance(obj, MutableSequence): if not isinstance(first, int): msg = ( "Error: {obj} is a list, " "but index provided was not an integer: '{first}'" ).format(obj=obj, first=first) raise AnsibleActionFail(msg) if first > len(obj): msg = "Error: {obj} not long enough for item #{first} to be set.".format( obj=obj, first=first ) raise AnsibleActionFail(msg) if first == len(obj): obj.append(val) self._result["changed"] = True else: if obj[first] != val: obj[first] = val self._result["changed"] = True else: msg = "update_fact can only modify mutable objects." raise AnsibleActionFail(msg)
def _get_template_contents(self): """ Retrieve the contents of the parser template :return: The parser's contents :rtype: str """ template_contents = None template_path = self._task.args.get("parser").get("template_path") if template_path: try: with open(template_path, "rb") as file_handler: try: template_contents = to_text( file_handler.read(), errors="surrogate_or_strict") except UnicodeError: raise AnsibleActionFail( "Template source files must be utf-8 encoded") except FileNotFoundError as exc: raise AnsibleActionFail( "Failed to open template '{tpath}'. Error: {err}".format( tpath=template_path, err=to_native(exc))) return template_contents
def run(self, tmp=None, task_vars=None): self.result = super(ActionModule, self).run(tmp, task_vars) class Run: pass args = self._task.args self.run = Run() self.run.name = args.get('name', args.get('metadata', {}).get('name')) if not self.run.name: raise AnsibleActionFail( "Missing field `name` in `openshift_imagestream`") self.run.namespace = args.get('namespace', args.get('metadata', {}).get('namespace')) if not self.run.name: raise AnsibleActionFail( "Missing field `namespace` in `openshift_imagestream`") self.run.tmp = tmp self.run.task_vars = task_vars self.run.state = args.get('state', 'latest') self.run.metadata = args.get('metadata', {}) self.run.tag = args.get('tag', 'latest') self.run.webhook_secret_name = args.get('git', {}).get('webhook_secret_name', None) self.run.webhook_secret = args.get('git', {}).get('webhook_secret', None) if self.run.webhook_secret and not self.run.webhook_secret_name: self.run.webhook_secret_name = '%s-webhook' % self.run.name frm = self._get_from_struct(args) if self._has_build_steps(args): self._run_openshift_imagestream_action() self._run_openshift_buildconfig_action(frm, args) else: self._run_openshift_imagestream_action(frm) if self.run.webhook_secret: self._run_openshift_secret_action() return self.result
def ensure_dcos(): """Check whether the dcos cli is installed.""" try: r = subprocess.check_output(['dcos', '--version'], env=_dcos_path()).decode() except subprocess.CalledProcessError: raise AnsibleActionFail("DC/OS CLI is not installed!") raw_version = '' for line in r.strip().split('\n'): display.vvv(line) k, v = line.split('=') if k == 'dcoscli.version': raw_version = v v = _version(raw_version) if v < (0, 5, 0): raise AnsibleActionFail( "DC/OS CLI 0.5.x is required, found {}".format(v)) if v >= (0, 7, 0): raise AnsibleActionFail( "DC/OS CLI version > 0.7.x detected, may not work") display.vvv("dcos: all prerequisites seem to be in order")
def _ensure_valid_jinja(self): errors = [] for entry in self._task.args["updates"]: try: Template("{{" + entry["path"] + "}}") except TemplateSyntaxError as exc: error = ( "While processing '{path}' found malformed path." " Ensure syntax follows valid jinja format. The error was:" " {error}" ).format(path=entry["path"], error=to_native(exc)) errors.append(error) if errors: raise AnsibleActionFail(" ".join(errors))
def _fetch_container_state(self, containers, task_vars): """Return container states of finished containers with retries. :params containers: List of containers. :params task_vars: Dictionary of Ansible tasks variables. :returns container_results: Dictionary of container infos. """ containers_results = self._get_container_infos(containers, task_vars) for container in containers_results: name = container.get('Name') if self._is_container_running(container): raise AnsibleActionFail('Container {} has not finished yet, ' 'retrying...'.format(name)) return containers_results
def _get_from_struct(self, args): """Returns the "from" sub-structure to use for ImageStreams and BuildConfigs. Both are mutually exclusive in practice. A "from" sub-structure in an ImageStream means that that image is downloaded or copied, not built. """ from_arg = args.get('from') if not from_arg: dockerfile_text = self._get_immediate_dockerfile(args) if dockerfile_text is not None: local_froms = self._parse_local_from_lines(dockerfile_text) if len(local_froms) == 0: return None elif len(local_froms) > 1: raise AnsibleActionFail("Cannot guess `from:` structure from multi-stage Dockerfile; please provide an explicit one.") else: local = local_froms[0] return { 'kind': 'ImageStreamTag', 'name': local.name_and_tag, 'namespace': local.namespace } else: return None if not isinstance(from_arg, string_types): return from_arg if "/" in from_arg: return { 'kind': 'DockerImage', 'name': from_arg } else: # Assume the image is a "local" ImageStream (in # same namespace). Note: that won't work if what # you wanted was to e.g. pull "busybox" from the # Docker Hub. Either pass a full Docker URL (e.g. # docker.io/busybox), or pas a data structure in # the 'from:' argument. from_parts = from_arg.split(':', 2) if len(from_parts) < 2: from_parts.append('latest') return { 'kind': 'ImageStreamTag', 'name': '%s:%s' % tuple(from_parts), 'namespace': self.run.namespace }
def run(self, tmp=None, task_vars=None): result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect if self._play_context.check_mode: # in --check mode, always skip this module execution result['skipped'] = True result['msg'] = 'The dcos task does not support check mode' return result args = self._task.args gid = args.get('gid') description = args.get('description', 'Created by Ansible') permissions = args.get('permissions', []) wanted_state = args.get('state', 'present') if gid is None: raise AnsibleActionFail('gid cannot be empty for dcos_iam_group') ensure_dcos() ensure_dcos_security() current_state = get_group_state(gid) if current_state == wanted_state: display.vvv( "DC/OS IAM group {} already in desired state {}".format( gid, wanted_state)) if wanted_state == "present": group_update(gid, permissions) result['changed'] = False else: display.vvv("DC/OS: IAM group {} not in desired state {}".format( gid, wanted_state)) if wanted_state != 'absent': group_create(gid, description) group_update(gid, permissions) else: group_delete(gid) result['changed'] = True return result