diff -Nru ansible-2.2.0.0/CHANGELOG.md ansible-2.2.1.0/CHANGELOG.md --- ansible-2.2.0.0/CHANGELOG.md 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/CHANGELOG.md 2017-01-16 17:03:54.000000000 +0000 @@ -1,6 +1,35 @@ Ansible Changes By Release ========================== +## 2.2.1 "The Battle of Evermore" - 01-16-2017 + +### Major Changes: + +* Security fix for CVE-2016-9587 - An attacker with control over a client system being managed by Ansible and the ability to send facts back to the Ansible server could use this flaw to execute arbitrary code on the Ansible server as the user and group Ansible is running as. + +### Minor Changes: + +* Fixes a bug where undefined variables in with_* loops would cause a task failure even if the when condition would cause the task to be skipped. +* Fixed a bug related to roles where in certain situations a role may be run more than once despite not allowing duplicates. +* Fixed some additional bugs related to atomic_move for modules. +* Fixes multiple bugs related to field/attribute inheritance in nested blocks and includes, as well as task iteration logic during failures. +* Fixed pip installing packages into virtualenvs using the system pip instead of the virtualenv pip. +* Fixed dnf on systems with dnf-2.0.x (some changes in the API). +* Fixed traceback with dnf install of groups. +* Fixes a bug in which include_vars was not working with failed_when. +* Fix for include_vars only loading files with .yml, .yaml, and .json extensions. This was only supposed to apply to loading a directory of vars files. +* Fixes several bugs related to properly incrementing the failed count in the host statistics. +* Fixes a bug with listening handlers which did not specify a `name` field. +* Fixes a bug with the `play_hosts` internal variable, so that it properly reflects the current list of hosts. +* Fixes a bug related to the v2_playbook_on_start callback method and legacy (v1) plugins. +* Fixes an openssh related process exit race condition, related to the fact that connections using ControlPersist do not close stderr. +* Improvements and fixes to OpenBSD fact gathering. +* Updated `make deb` to use pbuilder. Use `make local_deb` for the previous non-pbuilder build. +* Fixed Windows async to avoid blocking due to handle inheritance. +* Fixed bugs in the mount module on older Linux kernels and *BSDs +* Various minor fixes for Python 3 +* Inserted some checks for jinja2-2.9, which can cause some issues with Ansible currently. + ## 2.2 "The Battle of Evermore" - 11-01-2016 ###Major Changes: diff -Nru ansible-2.2.0.0/contrib/inventory/azure_rm.py ansible-2.2.1.0/contrib/inventory/azure_rm.py --- ansible-2.2.0.0/contrib/inventory/azure_rm.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/contrib/inventory/azure_rm.py 2017-01-16 17:03:54.000000000 +0000 @@ -309,7 +309,7 @@ def _get_env_credentials(self): env_credentials = dict() - for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.iteritems(): + for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.items(): env_credentials[attribute] = os.environ.get(env_variable, None) if env_credentials['profile'] is not None: @@ -328,7 +328,7 @@ self.log('Getting credentials') arg_credentials = dict() - for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.iteritems(): + for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.items(): arg_credentials[attribute] = getattr(params, attribute) # try module params @@ -664,7 +664,7 @@ self._inventory['azure'].append(host_name) if self.group_by_tag and vars.get('tags'): - for key, value in vars['tags'].iteritems(): + for key, value in vars['tags'].items(): safe_key = self._to_safe(key) safe_value = safe_key + '_' + self._to_safe(value) if not self._inventory.get(safe_key): @@ -724,7 +724,7 @@ def _get_env_settings(self): env_settings = dict() - for attribute, env_variable in AZURE_CONFIG_SETTINGS.iteritems(): + for attribute, env_variable in AZURE_CONFIG_SETTINGS.items(): env_settings[attribute] = os.environ.get(env_variable, None) return env_settings diff -Nru ansible-2.2.0.0/contrib/inventory/collins.ini ansible-2.2.1.0/contrib/inventory/collins.ini --- ansible-2.2.0.0/contrib/inventory/collins.ini 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/contrib/inventory/collins.ini 2017-01-16 17:03:54.000000000 +0000 @@ -3,6 +3,8 @@ [collins] +# You should not have a trailing slash or collins +# will not properly match the URI host = http://localhost:9000 username = blake diff -Nru ansible-2.2.0.0/contrib/inventory/collins.py ansible-2.2.1.0/contrib/inventory/collins.py --- ansible-2.2.0.0/contrib/inventory/collins.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/contrib/inventory/collins.py 2017-01-16 17:03:54.000000000 +0000 @@ -201,7 +201,8 @@ response = open_url(query_url, timeout=self.collins_timeout_secs, url_username=self.collins_username, - url_password=self.collins_password) + url_password=self.collins_password, + force_basic_auth=True) json_response = json.loads(response.read()) # Adds any assets found to the array of assets. assets += json_response['data']['Data'] diff -Nru ansible-2.2.0.0/contrib/inventory/ec2.py ansible-2.2.1.0/contrib/inventory/ec2.py --- ansible-2.2.0.0/contrib/inventory/ec2.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/contrib/inventory/ec2.py 2017-01-16 17:03:54.000000000 +0000 @@ -1313,7 +1313,7 @@ elif key == 'ec2_tags': for k, v in value.items(): if self.expand_csv_tags and ',' in v: - v = map(lambda x: x.strip(), v.split(',')) + v = list(map(lambda x: x.strip(), v.split(','))) key = self.to_safe('ec2_tag_' + k) instance_vars[key] = v elif key == 'ec2_groups': diff -Nru ansible-2.2.0.0/contrib/inventory/gce.py ansible-2.2.1.0/contrib/inventory/gce.py --- ansible-2.2.0.0/contrib/inventory/gce.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/contrib/inventory/gce.py 2017-01-16 17:03:54.000000000 +0000 @@ -261,7 +261,7 @@ if inst is None: return {} - if inst.extra['metadata'].has_key('items'): + if 'items' in inst.extra['metadata']: for entry in inst.extra['metadata']['items']: md[entry['key']] = entry['value'] @@ -326,7 +326,7 @@ if zones and zone not in zones: continue - if groups.has_key(zone): groups[zone].append(name) + if zone in groups: groups[zone].append(name) else: groups[zone] = [name] tags = node.extra['tags'] @@ -335,25 +335,25 @@ tag = t[6:] else: tag = 'tag_%s' % t - if groups.has_key(tag): groups[tag].append(name) + if tag in groups: groups[tag].append(name) else: groups[tag] = [name] net = node.extra['networkInterfaces'][0]['network'].split('/')[-1] net = 'network_%s' % net - if groups.has_key(net): groups[net].append(name) + if net in groups: groups[net].append(name) else: groups[net] = [name] machine_type = node.size - if groups.has_key(machine_type): groups[machine_type].append(name) + if machine_type in groups: groups[machine_type].append(name) else: groups[machine_type] = [name] image = node.image and node.image or 'persistent_disk' - if groups.has_key(image): groups[image].append(name) + if image in groups: groups[image].append(name) else: groups[image] = [name] status = node.extra['status'] stat = 'status_%s' % status.lower() - if groups.has_key(stat): groups[stat].append(name) + if stat in groups: groups[stat].append(name) else: groups[stat] = [name] groups["_meta"] = meta diff -Nru ansible-2.2.0.0/contrib/inventory/nova.py ansible-2.2.1.0/contrib/inventory/nova.py --- ansible-2.2.0.0/contrib/inventory/nova.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/contrib/inventory/nova.py 2017-01-16 17:03:54.000000000 +0000 @@ -26,7 +26,7 @@ import os import ConfigParser from novaclient import client as nova_client -from six import iteritems +from six import iteritems, itervalues try: import json @@ -105,7 +105,7 @@ # Iterate through each servers network(s), get addresses and get type addresses = getattr(server, 'addresses', {}) if len(addresses) > 0: - for network in addresses.itervalues(): + for network in itervalues(addresses): for address in network: if address.get('OS-EXT-IPS:type', False) == 'fixed': private.append(address['addr']) diff -Nru ansible-2.2.0.0/contrib/inventory/nsot.py ansible-2.2.1.0/contrib/inventory/nsot.py --- ansible-2.2.0.0/contrib/inventory/nsot.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/contrib/inventory/nsot.py 2017-01-16 17:03:54.000000000 +0000 @@ -201,7 +201,7 @@ _inventory_group() ''' inventory = dict() - for group, contents in self.config.iteritems(): + for group, contents in self.config.items(): group_response = self._inventory_group(group, contents) inventory.update(group_response) inventory.update({'_meta': self._meta}) diff -Nru ansible-2.2.0.0/contrib/inventory/rackhd.py ansible-2.2.1.0/contrib/inventory/rackhd.py --- ansible-2.2.0.0/contrib/inventory/rackhd.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/contrib/inventory/rackhd.py 2017-01-16 17:03:54.000000000 +0000 @@ -14,7 +14,7 @@ for nodeid in nodeids: self._load_inventory_data(nodeid) inventory = {} - for nodeid,info in self._inventory.iteritems(): + for nodeid,info in self._inventory.items(): inventory[nodeid]= (self._format_output(nodeid, info)) print(json.dumps(inventory)) @@ -24,7 +24,7 @@ info['lookup'] = RACKHD_URL + '/api/common/lookups/?q={0}'.format(nodeid) results = {} - for key,url in info.iteritems(): + for key,url in info.items(): r = requests.get( url, verify=False) results[key] = r.text self._inventory[nodeid] = results @@ -36,7 +36,7 @@ if len(node_info) > 0: ipaddress = node_info[0]['ipAddress'] output = { 'hosts':[ipaddress],'vars':{}} - for key,result in info.iteritems(): + for key,result in info.items(): output['vars'][key] = json.loads(result) output['vars']['ansible_ssh_user'] = 'monorail' except KeyError: diff -Nru ansible-2.2.0.0/contrib/inventory/vmware_inventory.py ansible-2.2.1.0/contrib/inventory/vmware_inventory.py --- ansible-2.2.0.0/contrib/inventory/vmware_inventory.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/contrib/inventory/vmware_inventory.py 2017-01-16 17:03:54.000000000 +0000 @@ -210,7 +210,7 @@ config.read(vmware_ini_path) # apply defaults - for k,v in defaults['vmware'].iteritems(): + for k,v in defaults['vmware'].items(): if not config.has_option('vmware', k): config.set('vmware', k, str(v)) @@ -356,7 +356,7 @@ # Reset the inventory keys - for k,v in name_mapping.iteritems(): + for k,v in name_mapping.items(): if not host_mapping or not k in host_mapping: continue @@ -389,7 +389,7 @@ continue self.debugl('# filter: %s' % hf) filter_map = self.create_template_mapping(inventory, hf, dtype='boolean') - for k,v in filter_map.iteritems(): + for k,v in filter_map.items(): if not v: # delete this host inventory['all']['hosts'].remove(k) @@ -402,7 +402,7 @@ # Create groups for gbp in self.groupby_patterns: groupby_map = self.create_template_mapping(inventory, gbp) - for k,v in groupby_map.iteritems(): + for k,v in groupby_map.items(): if v not in inventory: inventory[v] = {} inventory[v]['hosts'] = [] @@ -417,7 +417,7 @@ ''' Return a hash of uuid to templated string from pattern ''' mapping = {} - for k,v in inventory['_meta']['hostvars'].iteritems(): + for k,v in inventory['_meta']['hostvars'].items(): t = jinja2.Template(pattern) newkey = None try: diff -Nru ansible-2.2.0.0/debian/changelog ansible-2.2.1.0/debian/changelog --- ansible-2.2.0.0/debian/changelog 2016-11-01 03:44:03.000000000 +0000 +++ ansible-2.2.1.0/debian/changelog 2017-01-16 17:04:31.000000000 +0000 @@ -1,8 +1,15 @@ -ansible (2.2.0.0-1ppa~trusty) trusty; urgency=low +ansible (2.2.1.0-1ppa~trusty) trusty; urgency=low - * 2.2.0.0 release + * 2.2.1.0 release + + -- Ansible, Inc. Mon, 16 Jan 2017 17:04:22 +0000 + +ansible (2.2.1.0) unstable; urgency=low + + * 2.2.1.0 + + -- Ansible, Inc. Mon, 16 Jan 2017 10:13:29 -0600 - -- Ansible, Inc. Mon, 31 Oct 2016 23:43:55 -0400 ansible (2.2.0.0) unstable; urgency=low diff -Nru ansible-2.2.0.0/debian/changeloge ansible-2.2.1.0/debian/changeloge --- ansible-2.2.0.0/debian/changeloge 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/debian/changeloge 2017-01-16 17:03:54.000000000 +0000 @@ -4,6 +4,13 @@ -- Ansible, Inc. %DATE% +ansible (2.2.1.0) unstable; urgency=low + + * 2.2.1.0 + + -- Ansible, Inc. Mon, 16 Jan 2017 10:13:29 -0600 + + ansible (2.2.0.0) unstable; urgency=low * 2.2.0.0 diff -Nru ansible-2.2.0.0/debian/Dockerfile ansible-2.2.1.0/debian/Dockerfile --- ansible-2.2.0.0/debian/Dockerfile 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.2.1.0/debian/Dockerfile 2017-01-16 17:03:54.000000000 +0000 @@ -0,0 +1,16 @@ +FROM ubuntu:xenial + +RUN apt-get update && apt-get install -y \ + asciidoc \ + cdbs \ + debootstrap \ + devscripts \ + make \ + pbuilder \ + python-setuptools + +VOLUME /ansible +WORKDIR /ansible + +ENTRYPOINT ["/bin/bash", "-c"] +CMD ["make deb"] diff -Nru ansible-2.2.0.0/debian/README.md ansible-2.2.1.0/debian/README.md --- ansible-2.2.0.0/debian/README.md 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/debian/README.md 2017-01-16 17:03:54.000000000 +0000 @@ -3,26 +3,39 @@ To create an Ansible DEB package: - sudo apt-get install python-paramiko python-yaml python-jinja2 python-httplib2 python-setuptools python-six sshpass - sudo apt-get install cdbs debhelper dpkg-dev git-core reprepro fakeroot asciidoc devscripts docbook-xml xsltproc libxml2-utils - sudo apt-get install dh-python - git clone git://github.com/ansible/ansible.git - cd ansible - make deb +__Note__: You must run this target as root or set `PBUILDER_BIN='sudo pbuilder'` -On older releases that do not have `dh-python` (like Ubuntu 12.04), install `python-support` instead: +``` +apt-get install asciidoc cdbs debootstrap devscripts make pbuilder python-setuptools +git clone git://github.com/ansible/ansible.git +cd ansible +git submodule update --init +DEB_DIST='xenial trusty precise' make deb +``` + +Building in Docker: + +``` +git clone git://github.com/ansible/ansible.git +cd ansible +git submodule update --init +docker build -t ansible-deb-builder -f packaging/debian/Dockerfile . +docker run --privileged -e DEB_DIST='trusty' -v $(pwd):/ansible ansible-deb-builder +``` - sudo apt-get install python-support - -The debian package file will be placed in the `../` directory. This can then be added to an APT repository or installed with `dpkg -i `. +The debian package file will be placed in the `deb-build` directory. This can then be added to an APT repository or installed with `dpkg -i `. Note that `dpkg -i` does not resolve dependencies. To install the Ansible DEB package and resolve dependencies: - sudo dpkg -i - sudo apt-get -fy install +``` +dpkg -i +apt-get -fy install +``` Or, if you are running Debian Stretch (or later) or Ubuntu Xenial (or later): - sudo apt install +``` +apt install /path/to/ +``` diff -Nru ansible-2.2.0.0/debian/rules ansible-2.2.1.0/debian/rules --- ansible-2.2.0.0/debian/rules 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/debian/rules 2017-01-16 17:03:54.000000000 +0000 @@ -2,9 +2,6 @@ # -- makefile -- DEB_PYTHON2_MODULE_PACKAGES=ansible -ifneq ($(shell dpkg-query -f '$${Version}' -W python-support 2>/dev/null),) -DEB_PYTHON_SYSTEM=pysupport -endif include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/class/python-distutils.mk diff -Nru ansible-2.2.0.0/docs/man/man1/ansible.1 ansible-2.2.1.0/docs/man/man1/ansible.1 --- ansible-2.2.0.0/docs/man/man1/ansible.1 2016-11-01 03:43:56.000000000 +0000 +++ ansible-2.2.1.0/docs/man/man1/ansible.1 2017-01-16 17:04:23.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 10/31/2016 +.\" Date: 01/16/2017 .\" Manual: System administration commands -.\" Source: Ansible 2.2.0.0 +.\" Source: Ansible 2.2.1.0 .\" Language: English .\" -.TH "ANSIBLE" "1" "10/31/2016" "Ansible 2\&.2\&.0\&.0" "System administration commands" +.TH "ANSIBLE" "1" "01/16/2017" "Ansible 2\&.2\&.1\&.0" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.2.0.0/docs/man/man1/ansible.1.asciidoc ansible-2.2.1.0/docs/man/man1/ansible.1.asciidoc --- ansible-2.2.0.0/docs/man/man1/ansible.1.asciidoc 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/docs/man/man1/ansible.1.asciidoc 2017-01-16 17:04:22.000000000 +0000 @@ -1,7 +1,7 @@ ansible(1) ========= :man source: Ansible -:man version: 2.2.0.0 +:man version: 2.2.1.0 :man manual: System administration commands NAME diff -Nru ansible-2.2.0.0/docs/man/man1/ansible-doc.1 ansible-2.2.1.0/docs/man/man1/ansible-doc.1 --- ansible-2.2.0.0/docs/man/man1/ansible-doc.1 2016-11-01 03:43:58.000000000 +0000 +++ ansible-2.2.1.0/docs/man/man1/ansible-doc.1 2017-01-16 17:04:25.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible-doc .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 10/31/2016 +.\" Date: 01/16/2017 .\" Manual: System administration commands -.\" Source: Ansible 2.2.0.0 +.\" Source: Ansible 2.2.1.0 .\" Language: English .\" -.TH "ANSIBLE\-DOC" "1" "10/31/2016" "Ansible 2\&.2\&.0\&.0" "System administration commands" +.TH "ANSIBLE\-DOC" "1" "01/16/2017" "Ansible 2\&.2\&.1\&.0" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.2.0.0/docs/man/man1/ansible-doc.1.asciidoc ansible-2.2.1.0/docs/man/man1/ansible-doc.1.asciidoc --- ansible-2.2.0.0/docs/man/man1/ansible-doc.1.asciidoc 2016-11-01 03:43:58.000000000 +0000 +++ ansible-2.2.1.0/docs/man/man1/ansible-doc.1.asciidoc 2017-01-16 17:04:25.000000000 +0000 @@ -2,7 +2,7 @@ ============== :doctype: manpage :man source: Ansible -:man version: 2.2.0.0 +:man version: 2.2.1.0 :man manual: System administration commands NAME diff -Nru ansible-2.2.0.0/docs/man/man1/ansible-galaxy.1 ansible-2.2.1.0/docs/man/man1/ansible-galaxy.1 --- ansible-2.2.0.0/docs/man/man1/ansible-galaxy.1 2016-11-01 03:43:59.000000000 +0000 +++ ansible-2.2.1.0/docs/man/man1/ansible-galaxy.1 2017-01-16 17:04:26.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible-galaxy .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 10/31/2016 +.\" Date: 01/16/2017 .\" Manual: System administration commands -.\" Source: Ansible 2.2.0.0 +.\" Source: Ansible 2.2.1.0 .\" Language: English .\" -.TH "ANSIBLE\-GALAXY" "1" "10/31/2016" "Ansible 2\&.2\&.0\&.0" "System administration commands" +.TH "ANSIBLE\-GALAXY" "1" "01/16/2017" "Ansible 2\&.2\&.1\&.0" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.2.0.0/docs/man/man1/ansible-galaxy.1.asciidoc ansible-2.2.1.0/docs/man/man1/ansible-galaxy.1.asciidoc --- ansible-2.2.0.0/docs/man/man1/ansible-galaxy.1.asciidoc 2016-11-01 03:43:58.000000000 +0000 +++ ansible-2.2.1.0/docs/man/man1/ansible-galaxy.1.asciidoc 2017-01-16 17:04:25.000000000 +0000 @@ -2,7 +2,7 @@ =================== :doctype: manpage :man source: Ansible -:man version: 2.2.0.0 +:man version: 2.2.1.0 :man manual: System administration commands NAME diff -Nru ansible-2.2.0.0/docs/man/man1/ansible-playbook.1 ansible-2.2.1.0/docs/man/man1/ansible-playbook.1 --- ansible-2.2.0.0/docs/man/man1/ansible-playbook.1 2016-11-01 03:43:57.000000000 +0000 +++ ansible-2.2.1.0/docs/man/man1/ansible-playbook.1 2017-01-16 17:04:24.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible-playbook .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 10/31/2016 +.\" Date: 01/16/2017 .\" Manual: System administration commands -.\" Source: Ansible 2.2.0.0 +.\" Source: Ansible 2.2.1.0 .\" Language: English .\" -.TH "ANSIBLE\-PLAYBOOK" "1" "10/31/2016" "Ansible 2\&.2\&.0\&.0" "System administration commands" +.TH "ANSIBLE\-PLAYBOOK" "1" "01/16/2017" "Ansible 2\&.2\&.1\&.0" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.2.0.0/docs/man/man1/ansible-playbook.1.asciidoc ansible-2.2.1.0/docs/man/man1/ansible-playbook.1.asciidoc --- ansible-2.2.0.0/docs/man/man1/ansible-playbook.1.asciidoc 2016-11-01 03:43:56.000000000 +0000 +++ ansible-2.2.1.0/docs/man/man1/ansible-playbook.1.asciidoc 2017-01-16 17:04:23.000000000 +0000 @@ -2,7 +2,7 @@ =================== :doctype: manpage :man source: Ansible -:man version: 2.2.0.0 +:man version: 2.2.1.0 :man manual: System administration commands NAME diff -Nru ansible-2.2.0.0/docs/man/man1/ansible-pull.1 ansible-2.2.1.0/docs/man/man1/ansible-pull.1 --- ansible-2.2.0.0/docs/man/man1/ansible-pull.1 2016-11-01 03:43:57.000000000 +0000 +++ ansible-2.2.1.0/docs/man/man1/ansible-pull.1 2017-01-16 17:04:24.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 10/31/2016 +.\" Date: 01/16/2017 .\" Manual: System administration commands -.\" Source: Ansible 2.2.0.0 +.\" Source: Ansible 2.2.1.0 .\" Language: English .\" -.TH "ANSIBLE" "1" "10/31/2016" "Ansible 2\&.2\&.0\&.0" "System administration commands" +.TH "ANSIBLE" "1" "01/16/2017" "Ansible 2\&.2\&.1\&.0" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.2.0.0/docs/man/man1/ansible-pull.1.asciidoc ansible-2.2.1.0/docs/man/man1/ansible-pull.1.asciidoc --- ansible-2.2.0.0/docs/man/man1/ansible-pull.1.asciidoc 2016-11-01 03:43:57.000000000 +0000 +++ ansible-2.2.1.0/docs/man/man1/ansible-pull.1.asciidoc 2017-01-16 17:04:24.000000000 +0000 @@ -2,7 +2,7 @@ ========= :doctype: manpage :man source: Ansible -:man version: 2.2.0.0 +:man version: 2.2.1.0 :man manual: System administration commands diff -Nru ansible-2.2.0.0/docs/man/man1/ansible-vault.1 ansible-2.2.1.0/docs/man/man1/ansible-vault.1 --- ansible-2.2.0.0/docs/man/man1/ansible-vault.1 2016-11-01 03:44:00.000000000 +0000 +++ ansible-2.2.1.0/docs/man/man1/ansible-vault.1 2017-01-16 17:04:27.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible-vault .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 10/31/2016 +.\" Date: 01/16/2017 .\" Manual: System administration commands -.\" Source: Ansible 2.2.0.0 +.\" Source: Ansible 2.2.1.0 .\" Language: English .\" -.TH "ANSIBLE\-VAULT" "1" "10/31/2016" "Ansible 2\&.2\&.0\&.0" "System administration commands" +.TH "ANSIBLE\-VAULT" "1" "01/16/2017" "Ansible 2\&.2\&.1\&.0" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.2.0.0/docs/man/man1/ansible-vault.1.asciidoc ansible-2.2.1.0/docs/man/man1/ansible-vault.1.asciidoc --- ansible-2.2.0.0/docs/man/man1/ansible-vault.1.asciidoc 2016-11-01 03:43:59.000000000 +0000 +++ ansible-2.2.1.0/docs/man/man1/ansible-vault.1.asciidoc 2017-01-16 17:04:26.000000000 +0000 @@ -2,7 +2,7 @@ ================ :doctype: manpage :man source: Ansible -:man version: 2.2.0.0 +:man version: 2.2.1.0 :man manual: System administration commands NAME diff -Nru ansible-2.2.0.0/examples/ansible.cfg ansible-2.2.1.0/examples/ansible.cfg --- ansible-2.2.0.0/examples/ansible.cfg 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/examples/ansible.cfg 2017-01-16 17:03:54.000000000 +0000 @@ -13,8 +13,8 @@ #inventory = /etc/ansible/hosts #library = /usr/share/my_modules/ -#remote_tmp = $HOME/.ansible/tmp -#local_tmp = $HOME/.ansible/tmp +#remote_tmp = ~/.ansible/tmp +#local_tmp = ~/.ansible/tmp #forks = 5 #poll_interval = 15 #sudo_user = root diff -Nru ansible-2.2.0.0/lib/ansible/cli/adhoc.py ansible-2.2.1.0/lib/ansible/cli/adhoc.py --- ansible-2.2.0.0/lib/ansible/cli/adhoc.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/cli/adhoc.py 2017-01-16 17:03:54.000000000 +0000 @@ -120,7 +120,7 @@ vault_pass = CLI.read_vault_password_file(self.options.vault_password_file, loader=loader) loader.set_vault_password(vault_pass) elif self.options.ask_vault_pass: - vault_pass = self.ask_vault_passwords()[0] + vault_pass = self.ask_vault_passwords() loader.set_vault_password(vault_pass) variable_manager = VariableManager() diff -Nru ansible-2.2.0.0/lib/ansible/cli/doc.py ansible-2.2.1.0/lib/ansible/cli/doc.py --- ansible-2.2.0.0/lib/ansible/cli/doc.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/cli/doc.py 2017-01-16 17:03:54.000000000 +0000 @@ -89,7 +89,7 @@ try: # if the module lives in a non-python file (eg, win_X.ps1), require the corresponding python file for docs - filename = module_loader.find_plugin(module, mod_type='.py') + filename = module_loader.find_plugin(module, mod_type='.py', ignore_deprecated=True) if filename is None: display.warning("module %s not found in %s\n" % (module, DocCLI.print_paths(module_loader))) continue @@ -100,7 +100,7 @@ try: doc, plainexamples, returndocs = module_docs.get_docstring(filename, verbose=(self.options.verbosity > 0)) except: - display.vvv(traceback.print_exc()) + display.vvv(traceback.format_exc()) display.error("module %s has a documentation error formatting or is missing documentation\nTo see exact traceback use -vvv" % module) continue @@ -133,7 +133,7 @@ # probably a quoting issue. raise AnsibleError("Parsing produced an empty object.") except Exception as e: - display.vvv(traceback.print_exc()) + display.vvv(traceback.format_exc()) raise AnsibleError("module %s missing documentation (or could not parse documentation): %s\n" % (module, str(e))) if text: @@ -174,7 +174,7 @@ continue # if the module lives in a non-python file (eg, win_X.ps1), require the corresponding python file for docs - filename = module_loader.find_plugin(module, mod_type='.py') + filename = module_loader.find_plugin(module, mod_type='.py', ignore_deprecated=True) if filename is None: continue diff -Nru ansible-2.2.0.0/lib/ansible/cli/__init__.py ansible-2.2.1.0/lib/ansible/cli/__init__.py --- ansible-2.2.0.0/lib/ansible/cli/__init__.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/cli/__init__.py 2017-01-16 17:03:54.000000000 +0000 @@ -157,33 +157,37 @@ @staticmethod - def ask_vault_passwords(ask_new_vault_pass=False, rekey=False): + def ask_vault_passwords(): ''' prompt for vault password and/or password change ''' vault_pass = None - new_vault_pass = None try: - if rekey or not ask_new_vault_pass: - vault_pass = getpass.getpass(prompt="Vault password: ") + vault_pass = getpass.getpass(prompt="Vault password: ") - if ask_new_vault_pass: - new_vault_pass = getpass.getpass(prompt="New Vault password: ") - new_vault_pass2 = getpass.getpass(prompt="Confirm New Vault password: ") - if new_vault_pass != new_vault_pass2: - raise AnsibleError("Passwords do not match") except EOFError: pass # enforce no newline chars at the end of passwords if vault_pass: - vault_pass = to_bytes(vault_pass, errors='strict', nonstring='simplerepr').strip() - if new_vault_pass: - new_vault_pass = to_bytes(new_vault_pass, errors='strict', nonstring='simplerepr').strip() + vault_pass = to_text(vault_pass, errors='surrogate_or_strict', nonstring='simplerepr').strip() + + return vault_pass + + @staticmethod + def ask_new_vault_passwords(): + new_vault_pass = None + try: + new_vault_pass = getpass.getpass(prompt="New Vault password: ") + new_vault_pass2 = getpass.getpass(prompt="Confirm New Vault password: ") + if new_vault_pass != new_vault_pass2: + raise AnsibleError("Passwords do not match") + except EOFError: + pass - if ask_new_vault_pass and not rekey: - vault_pass = new_vault_pass + if new_vault_pass: + new_vault_pass = to_text(new_vault_pass, errors='surrogate_or_strict', nonstring='simplerepr').strip() - return vault_pass, new_vault_pass + return new_vault_pass def ask_passwords(self): ''' prompt for connection and become passwords if needed ''' @@ -573,7 +577,7 @@ stdout, stderr = p.communicate() if p.returncode != 0: raise AnsibleError("Vault password script %s returned non-zero (%s): %s" % (this_path, p.returncode, p.stderr)) - vault_pass = stdout.strip('\r\n') + vault_pass = stdout.strip(b'\r\n') else: try: f = open(this_path, "rb") @@ -582,7 +586,7 @@ except (OSError, IOError) as e: raise AnsibleError("Could not read vault password file %s: %s" % (this_path, e)) - return vault_pass + return to_text(vault_pass, errors='surrogate_or_strict') def get_opt(self, k, defval=""): """ diff -Nru ansible-2.2.0.0/lib/ansible/cli/playbook.py ansible-2.2.1.0/lib/ansible/cli/playbook.py --- ansible-2.2.0.0/lib/ansible/cli/playbook.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/cli/playbook.py 2017-01-16 17:03:54.000000000 +0000 @@ -110,7 +110,7 @@ vault_pass = CLI.read_vault_password_file(self.options.vault_password_file, loader=loader) loader.set_vault_password(vault_pass) elif self.options.ask_vault_pass: - vault_pass = self.ask_vault_passwords()[0] + vault_pass = self.ask_vault_passwords() loader.set_vault_password(vault_pass) # initial error check, to make sure all specified playbooks are accessible @@ -163,6 +163,12 @@ display.display('\nplaybook: %s' % p['playbook']) for idx, play in enumerate(p['plays']): + if play._included_path is not None: + loader.set_basedir(play._included_path) + else: + pb_dir = os.path.realpath(os.path.dirname(p['playbook'])) + loader.set_basedir(pb_dir) + msg = "\n play #%d (%s): %s" % (idx + 1, ','.join(play.hosts), play.name) mytags = set(play.tags) msg += '\tTAGS: [%s]' % (','.join(mytags)) diff -Nru ansible-2.2.0.0/lib/ansible/cli/vault.py ansible-2.2.1.0/lib/ansible/cli/vault.py --- ansible-2.2.0.0/lib/ansible/cli/vault.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/cli/vault.py 2017-01-16 17:03:54.000000000 +0000 @@ -100,21 +100,23 @@ if self.options.vault_password_file: # read vault_pass from a file self.vault_pass = CLI.read_vault_password_file(self.options.vault_password_file, loader) - else: - newpass = False - rekey = False - if not self.options.new_vault_password_file: - newpass = (self.action in ['create', 'rekey', 'encrypt']) - rekey = (self.action == 'rekey') - self.vault_pass, self.new_vault_pass = self.ask_vault_passwords(ask_new_vault_pass=newpass, rekey=rekey) if self.options.new_vault_password_file: # for rekey only self.new_vault_pass = CLI.read_vault_password_file(self.options.new_vault_password_file, loader) + if not self.vault_pass or self.options.ask_vault_pass: + self.vault_pass = self.ask_vault_passwords() + if not self.vault_pass: raise AnsibleOptionsError("A password is required to use Ansible's Vault") + if self.action == 'rekey': + if not self.new_vault_pass: + self.new_vault_pass = self.ask_new_vault_passwords() + if not self.new_vault_pass: + raise AnsibleOptionsError("A password is required to rekey Ansible's Vault") + self.editor = VaultEditor(self.vault_pass) self.execute() diff -Nru ansible-2.2.0.0/lib/ansible/compat/six/_six.py ansible-2.2.1.0/lib/ansible/compat/six/_six.py --- ansible-2.2.0.0/lib/ansible/compat/six/_six.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/compat/six/_six.py 2017-01-16 17:03:54.000000000 +0000 @@ -43,6 +43,7 @@ class_types = type, text_type = str binary_type = bytes + cmp = lambda a, b: (a > b) - (a < b) MAXSIZE = sys.maxsize else: @@ -51,6 +52,7 @@ class_types = (type, types.ClassType) text_type = unicode binary_type = str + cmp = cmp if sys.platform.startswith("java"): # Jython always uses 32 bits. diff -Nru ansible-2.2.0.0/lib/ansible/constants.py ansible-2.2.1.0/lib/ansible/constants.py --- ansible-2.2.0.0/lib/ansible/constants.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/constants.py 2017-01-16 17:03:54.000000000 +0000 @@ -157,8 +157,8 @@ DEFAULT_HOST_LIST = get_config(p, DEFAULTS,'inventory', 'ANSIBLE_INVENTORY', DEPRECATED_HOST_LIST, ispath=True) DEFAULT_MODULE_PATH = get_config(p, DEFAULTS, 'library', 'ANSIBLE_LIBRARY', None, ispathlist=True) DEFAULT_ROLES_PATH = get_config(p, DEFAULTS, 'roles_path', 'ANSIBLE_ROLES_PATH', '/etc/ansible/roles', ispathlist=True, expand_relative_paths=True) -DEFAULT_REMOTE_TMP = get_config(p, DEFAULTS, 'remote_tmp', 'ANSIBLE_REMOTE_TEMP', '$HOME/.ansible/tmp') -DEFAULT_LOCAL_TMP = get_config(p, DEFAULTS, 'local_tmp', 'ANSIBLE_LOCAL_TEMP', '$HOME/.ansible/tmp', istmppath=True) +DEFAULT_REMOTE_TMP = get_config(p, DEFAULTS, 'remote_tmp', 'ANSIBLE_REMOTE_TEMP', '~/.ansible/tmp') +DEFAULT_LOCAL_TMP = get_config(p, DEFAULTS, 'local_tmp', 'ANSIBLE_LOCAL_TEMP', '~/.ansible/tmp', istmppath=True) DEFAULT_MODULE_NAME = get_config(p, DEFAULTS, 'module_name', None, 'command') DEFAULT_FORKS = get_config(p, DEFAULTS, 'forks', 'ANSIBLE_FORKS', 5, integer=True) DEFAULT_MODULE_ARGS = get_config(p, DEFAULTS, 'module_args', 'ANSIBLE_MODULE_ARGS', '') diff -Nru ansible-2.2.0.0/lib/ansible/executor/module_common.py ansible-2.2.1.0/lib/ansible/executor/module_common.py --- ansible-2.2.0.0/lib/ansible/executor/module_common.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/executor/module_common.py 2017-01-16 17:03:54.000000000 +0000 @@ -22,6 +22,7 @@ import ast import base64 +import datetime import imp import json import os @@ -106,10 +107,21 @@ # Ubuntu15.10 with python2.7 Works # Ubuntu15.10 with python3.4 Fails without this # Ubuntu16.04.1 with python3.5 Fails without this +# To test on another platform: +# * use the copy module (since this shadows the stdlib copy module) +# * Turn off pipelining +# * Make sure that the destination file does not exist +# * ansible ubuntu16-test -m copy -a 'src=/etc/motd dest=/var/tmp/m' +# This will traceback in shutil. Looking at the complete traceback will show +# that shutil is importing copy which finds the ansible module instead of the +# stdlib module scriptdir = None try: scriptdir = os.path.dirname(os.path.abspath(__main__.__file__)) -except AttributeError: +except (AttributeError, OSError): + # Some platforms don't set __file__ when reading from stdin + # OSX raises OSError if using abspath() in a directory we don't have + # permission to read. pass if scriptdir is not None: sys.path = [p for p in sys.path if p != scriptdir] @@ -317,7 +329,12 @@ # py3: zipped_mod will be text, py2: it's bytes. Need bytes at the end sitecustomize = u'import sys\\nsys.path.insert(0,"%%s")\\n' %% zipped_mod sitecustomize = sitecustomize.encode('utf-8') - z.writestr('sitecustomize.py', sitecustomize) + # Use a ZipInfo to work around zipfile limitation on hosts with + # clocks set to a pre-1980 year (for instance, Raspberry Pi) + zinfo = zipfile.ZipInfo() + zinfo.filename = 'sitecustomize.py' + zinfo.date_time = ( %(year)i, %(month)i, %(day)i, %(hour)i, %(minute)i, %(second)i) + z.writestr(zinfo, sitecustomize) z.close() exitcode = invoke_module(module, zipped_mod, ANSIBALLZ_PARAMS) @@ -680,6 +697,7 @@ interpreter_parts = interpreter.split(u' ') interpreter = u"'{0}'".format(u"', '".join(interpreter_parts)) + now=datetime.datetime.utcnow() output.write(to_bytes(ACTIVE_ANSIBALLZ_TEMPLATE % dict( zipdata=zipdata, ansible_module=module_name, @@ -687,6 +705,12 @@ shebang=shebang, interpreter=interpreter, coding=ENCODING_STRING, + year=now.year, + month=now.month, + day=now.day, + hour=now.hour, + minute=now.minute, + second=now.second, ))) module_data = output.getvalue() diff -Nru ansible-2.2.0.0/lib/ansible/executor/play_iterator.py ansible-2.2.1.0/lib/ansible/executor/play_iterator.py --- ansible-2.2.0.0/lib/ansible/executor/play_iterator.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/executor/play_iterator.py 2017-01-16 17:03:54.000000000 +0000 @@ -26,6 +26,7 @@ from ansible import constants as C from ansible.errors import AnsibleError +from ansible.module_utils.six import cmp from ansible.playbook.block import Block from ansible.playbook.task import Task from ansible.playbook.role_include import IncludeRole @@ -50,6 +51,7 @@ self.cur_rescue_task = 0 self.cur_always_task = 0 self.cur_role = None + self.cur_role_task = None self.cur_dep_chain = None self.run_state = PlayIterator.ITERATING_SETUP self.fail_state = PlayIterator.FAILED_NONE @@ -57,6 +59,7 @@ self.tasks_child_state = None self.rescue_child_state = None self.always_child_state = None + self.did_rescue = False self.did_start_at_task = False def __repr__(self): @@ -81,7 +84,7 @@ ret.append(states[i]) return "|".join(ret) - return "HOST STATE: block=%d, task=%d, rescue=%d, always=%d, role=%s, run_state=%s, fail_state=%s, pending_setup=%s, tasks child state? (%s), rescue child state? (%s), always child state? (%s), did start at task? %s" % ( + return "HOST STATE: block=%d, task=%d, rescue=%d, always=%d, role=%s, run_state=%s, fail_state=%s, pending_setup=%s, tasks child state? (%s), rescue child state? (%s), always child state? (%s), did rescue? %s, did start at task? %s" % ( self.cur_block, self.cur_regular_task, self.cur_rescue_task, @@ -93,6 +96,7 @@ self.tasks_child_state, self.rescue_child_state, self.always_child_state, + self.did_rescue, self.did_start_at_task, ) @@ -120,9 +124,12 @@ new_state.cur_rescue_task = self.cur_rescue_task new_state.cur_always_task = self.cur_always_task new_state.cur_role = self.cur_role + if self.cur_role_task: + new_state.cur_role_task = self.cur_role_task[:] new_state.run_state = self.run_state new_state.fail_state = self.fail_state new_state.pending_setup = self.pending_setup + new_state.did_rescue = self.did_rescue new_state.did_start_at_task = self.did_start_at_task if self.cur_dep_chain is not None: new_state.cur_dep_chain = self.cur_dep_chain[:] @@ -199,7 +206,7 @@ self._host_states[host.name] = HostState(blocks=self._blocks) # if the host's name is in the variable manager's fact cache, then set # its _gathered_facts flag to true for smart gathering tests later - if host.name in variable_manager._fact_cache and variable_manager._fact_cache.get('module_setup', False): + if host.name in variable_manager._fact_cache and variable_manager._fact_cache.get(host.name).get('module_setup', False): host._gathered_facts = True # if we're looking to start at a specific task, iterate through # the tasks for this host until we find the specified task @@ -274,12 +281,76 @@ parent = parent._parent return False + def _get_cur_task(s, depth=0): + res = [s.run_state, depth, s.cur_block, -1] + if s.run_state == self.ITERATING_TASKS: + if s.tasks_child_state: + res[-1] = [s.cur_regular_task, _get_cur_task(s.tasks_child_state, depth=depth+1)] + else: + res[-1] = s.cur_regular_task + elif s.run_state == self.ITERATING_RESCUE: + if s.rescue_child_state: + res[-1] = [s.cur_rescue_task, _get_cur_task(s.rescue_child_state, depth=depth+1)] + else: + res[-1] = s.cur_rescue_task + elif s.run_state == self.ITERATING_ALWAYS: + if s.always_child_state: + res[-1] = [s.cur_always_task, _get_cur_task(s.always_child_state, depth=depth+1)] + else: + res[-1] = s.cur_always_task + return res + + def _do_task_cmp(a, b): + ''' + Does the heavy lifting for _role_task_cmp() of comparing task state objects + returned by _get_cur_task() above. + ''' + res = cmp(a[0], b[0]) + if res == 0: + res = cmp(a[1], b[1]) + if res == 0: + res = cmp(a[2], b[2]) + if res == 0: + # if there were child states, the last value in the list may be + # a list itself representing the current task position plus a new + # list representing the child state. So here we normalize that so + # we can call this method recursively when all else is equal. + if isinstance(a[3], list): + a_i, a_il = a[3] + else: + a_i = a[3] + a_il = [-1, -1, -1, -1] + if isinstance(b[3], list): + b_i, b_il = b[3] + else: + b_i = b[3] + b_il = [-1, -1, -1, -1] + + res = cmp(a_i, b_i) + if res == 0: + res = _do_task_cmp(a_il, b_il) + return res + + def _role_task_cmp(s): + ''' + Compares the given state against the stored state from the previous role task. + ''' + if not s.cur_role_task: + return 1 + cur_task = _get_cur_task(s) + return _do_task_cmp(cur_task, s.cur_role_task) + if task and task._role: # if we had a current role, mark that role as completed - if s.cur_role and _roles_are_different(task._role, s.cur_role) and host.name in s.cur_role._had_task_run and \ - not _role_is_child(s.cur_role) and not peek: - s.cur_role._completed[host.name] = True + if s.cur_role: + role_diff = _roles_are_different(task._role, s.cur_role) + role_child = _role_is_child(s.cur_role) + tasks_cmp = _role_task_cmp(s) + host_done = host.name in s.cur_role._had_task_run + if (role_diff or (not role_diff and tasks_cmp <= 0)) and host_done and not role_child and not peek: + s.cur_role._completed[host.name] = True s.cur_role = task._role + s.cur_role_task = _get_cur_task(s) s.cur_dep_chain = task.get_dep_chain() if not peek: @@ -411,6 +482,7 @@ if len(block.rescue) > 0: state.fail_state = self.FAILED_NONE state.run_state = self.ITERATING_ALWAYS + state.did_rescue = True else: task = block.rescue[state.cur_rescue_task] if isinstance(task, Block) or state.rescue_child_state is not None: @@ -433,6 +505,7 @@ else: if task is None or state.always_child_state.run_state == self.ITERATING_COMPLETE: state.always_child_state = None + continue else: if state.cur_always_task >= len(block.always): if state.fail_state != self.FAILED_NONE: @@ -446,6 +519,7 @@ state.tasks_child_state = None state.rescue_child_state = None state.always_child_state = None + state.did_rescue = False else: task = block.always[state.cur_always_task] if isinstance(task, Block) or state.always_child_state is not None: @@ -502,6 +576,7 @@ s = self._set_failed_state(s) display.debug("^ failed state is now: %s" % s) self._host_states[host.name] = s + self._play._removed_hosts.append(host.name) def get_failed_hosts(self): return dict((host, True) for (host, state) in iteritems(self._host_states) if self._check_failed_state(state)) @@ -519,7 +594,7 @@ elif state.run_state == self.ITERATING_ALWAYS and state.fail_state&self.FAILED_ALWAYS == 0: return False else: - return True + return not state.did_rescue elif state.run_state == self.ITERATING_TASKS and self._check_failed_state(state.tasks_child_state): cur_block = self._blocks[state.cur_block] if len(cur_block.rescue) > 0 and state.fail_state & self.FAILED_RESCUE == 0: diff -Nru ansible-2.2.0.0/lib/ansible/executor/stats.py ansible-2.2.1.0/lib/ansible/executor/stats.py --- ansible-2.2.0.0/lib/ansible/executor/stats.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/executor/stats.py 2017-01-16 17:03:54.000000000 +0000 @@ -19,6 +19,8 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +from ansible.utils.vars import merge_hash + class AggregateStats: ''' holds stats about per-host activity during playbook runs ''' @@ -31,6 +33,9 @@ self.changed = {} self.skipped = {} + # user defined stats, which can be per host or global + self.custom = {} + def increment(self, what, host): ''' helper function to bump a statistic ''' @@ -49,3 +54,31 @@ skipped = self.skipped.get(host, 0) ) + def set_custom_stats(self, which, what, host=None): + ''' allow setting of a custom fact ''' + + if host is None: + host = '_run' + if host not in self.custom: + self.custom[host] = {which: what} + else: + self.custom[host][which] = what + + def update_custom_stats(self, which, what, host=None): + ''' allow aggregation of a custom fact ''' + + if host is None: + host = '_run' + if host not in self.custom or which not in self.custom[host]: + return self.set_custom_stats(which, what, host) + + # mismatching types + if type(what) != type(self.custom[host][which]): + return None + + if isinstance(what, dict): + self.custom[host][which] = merge_hash(self.custom[host][which], what) + else: + # let overloaded + take care of other types + self.custom[host][which] += what + diff -Nru ansible-2.2.0.0/lib/ansible/executor/task_executor.py ansible-2.2.1.0/lib/ansible/executor/task_executor.py --- ansible-2.2.0.0/lib/ansible/executor/task_executor.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/executor/task_executor.py 2017-01-16 17:03:54.000000000 +0000 @@ -71,6 +71,7 @@ self._shared_loader_obj = shared_loader_obj self._connection = None self._rslt_q = rslt_q + self._loop_eval_error = None self._task.squash() @@ -85,7 +86,13 @@ display.debug("in run()") try: - items = self._get_loop_items() + try: + items = self._get_loop_items() + except AnsibleUndefinedVariable as e: + # save the error raised here for use later + items = None + self._loop_eval_error = e + if items is not None: if len(items) > 0: item_results = self._run_loop(items) @@ -402,6 +409,11 @@ if not self._task.evaluate_conditional(templar, variables): display.debug("when evaluation failed, skipping this task") return dict(changed=False, skipped=True, skip_reason='Conditional check failed', _ansible_no_log=self._play_context.no_log) + # since we're not skipping, if there was a loop evaluation error + # raised earlier we need to raise it now to halt the execution of + # this task + if self._loop_eval_error is not None: + raise self._loop_eval_error except AnsibleError: # skip conditional exception in the case of includes as the vars needed might not be avaiable except in the included tasks or due to tags if self._task.action not in ['include', 'include_role']: diff -Nru ansible-2.2.0.0/lib/ansible/executor/task_queue_manager.py ansible-2.2.1.0/lib/ansible/executor/task_queue_manager.py --- ansible-2.2.0.0/lib/ansible/executor/task_queue_manager.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/executor/task_queue_manager.py 2017-01-16 17:03:54.000000000 +0000 @@ -138,8 +138,8 @@ # then initialize it with the given handler list for handler in handler_list: - if handler not in self._notified_handlers: - self._notified_handlers[handler] = [] + if handler._uuid not in self._notified_handlers: + self._notified_handlers[handler._uuid] = [] if handler.listen: listeners = handler.listen if not isinstance(listeners, list): @@ -147,7 +147,7 @@ for listener in listeners: if listener not in self._listening_handlers: self._listening_handlers[listener] = [] - self._listening_handlers[listener].append(handler.get_name()) + self._listening_handlers[listener].append(handler._uuid) def load_callbacks(self): ''' @@ -353,17 +353,20 @@ for method in methods: try: - # temporary hack, required due to a change in the callback API, so - # we don't break backwards compatibility with callbacks which were - # designed to use the original API + # Previously, the `v2_playbook_on_start` callback API did not accept + # any arguments. In recent versions of the v2 callback API, the play- + # book that started execution is given. In order to support both of + # these method signatures, we need to use this `inspect` hack to send + # no arguments to the methods that don't accept them. This way, we can + # not break backwards compatibility until that API is deprecated. # FIXME: target for removal and revert to the original code here after a year (2017-01-14) if method_name == 'v2_playbook_on_start': import inspect - (f_args, f_varargs, f_keywords, f_defaults) = inspect.getargspec(method) - if 'playbook' in f_args: - method(*args, **kwargs) - else: + argspec = inspect.getargspec(method) + if argspec.args == ['self']: method() + else: + method(*args, **kwargs) else: method(*args, **kwargs) except Exception as e: diff -Nru ansible-2.2.0.0/lib/ansible/inventory/host.py ansible-2.2.1.0/lib/ansible/inventory/host.py --- ansible-2.2.0.0/lib/ansible/inventory/host.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/inventory/host.py 2017-01-16 17:03:54.000000000 +0000 @@ -138,6 +138,6 @@ def get_group_vars(self): results = {} groups = self.get_groups() - for group in sorted(groups, key=lambda g: g.depth): + for group in sorted(groups, key=lambda g: (g.depth, g.name)): results = combine_vars(results, group.get_vars()) return results diff -Nru ansible-2.2.0.0/lib/ansible/inventory/ini.py ansible-2.2.1.0/lib/ansible/inventory/ini.py --- ansible-2.2.0.0/lib/ansible/inventory/ini.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/inventory/ini.py 2017-01-16 17:03:54.000000000 +0000 @@ -51,18 +51,21 @@ # Read in the hosts, groups, and variables defined in the # inventory file. + if loader: + (b_data, private) = loader._get_file_contents(filename) + else: + with open(filename, 'rb') as fh: + b_data = fh.read() - with open(filename, 'rb') as fh: - data = fh.read() - try: - # Faster to do to_text once on a long string than many - # times on smaller strings - data = to_text(data, errors='surrogate_or_strict') - data = [line for line in data.splitlines() if not (line.startswith(u';') or line.startswith(u'#'))] - except UnicodeError: - # Skip comment lines here to avoid potential undecodable - # errors in comments: https://github.com/ansible/ansible/issues/17593 - data = [to_text(line, errors='surrogate_or_strict') for line in data.splitlines() if not (line.startswith(b';') or line.startswith(b'#'))] + try: + # Faster to do to_text once on a long string than many + # times on smaller strings + data = to_text(b_data, errors='surrogate_or_strict') + data = [line for line in data.splitlines() if not (line.startswith(u';') or line.startswith(u'#'))] + except UnicodeError: + # Skip comment lines here to avoid potential undecodable + # errors in comments: https://github.com/ansible/ansible/issues/17593 + data = [to_text(line, errors='surrogate_or_strict') for line in b_data.splitlines() if not (line.startswith(b';') or line.startswith(b'#'))] self._parse(data) diff -Nru ansible-2.2.0.0/lib/ansible/inventory/__init__.py ansible-2.2.1.0/lib/ansible/inventory/__init__.py --- ansible-2.2.0.0/lib/ansible/inventory/__init__.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/inventory/__init__.py 2017-01-16 17:03:54.000000000 +0000 @@ -38,6 +38,7 @@ from ansible.parsing.utils.addresses import parse_address from ansible.plugins import vars_loader from ansible.utils.vars import combine_vars +from ansible.utils.path import unfrackpath try: from __main__ import display @@ -58,7 +59,7 @@ # the host file file, or script path, or list of hosts # if a list, inventory data will NOT be loaded - self.host_list = host_list + self.host_list = unfrackpath(host_list, follow=False) self._loader = loader self._variable_manager = variable_manager self.localhost = None @@ -780,7 +781,10 @@ path = os.path.realpath(os.path.join(basedir, 'group_vars')) found_vars = set() if os.path.exists(path): - found_vars = set(os.listdir(to_text(path))) + if os.path.isdir(path): + found_vars = set(os.listdir(to_text(path))) + else: + display.warning("Found group_vars that is not a directory, skipping: %s" % path) return found_vars def _find_host_vars_files(self, basedir): diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/cloud/amazon/ec2_asg.py ansible-2.2.1.0/lib/ansible/modules/core/cloud/amazon/ec2_asg.py --- ansible-2.2.0.0/lib/ansible/modules/core/cloud/amazon/ec2_asg.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/cloud/amazon/ec2_asg.py 2017-01-16 17:03:59.000000000 +0000 @@ -418,7 +418,7 @@ asg_tags = [] for tag in set_tags: - for k,v in tag.iteritems(): + for k,v in tag.items(): if k !='propagate_at_launch': asg_tags.append(Tag(key=k, value=v, diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/cloud/amazon/ec2_elb_lb.py ansible-2.2.1.0/lib/ansible/modules/core/cloud/amazon/ec2_elb_lb.py --- ansible-2.2.0.0/lib/ansible/modules/core/cloud/amazon/ec2_elb_lb.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/cloud/amazon/ec2_elb_lb.py 2017-01-16 17:03:59.000000000 +0000 @@ -907,7 +907,7 @@ if not self.elb.health_check: self.elb.health_check = HealthCheck() - for attr, desired_value in health_check_config.iteritems(): + for attr, desired_value in health_check_config.items(): if getattr(self.elb.health_check, attr) != desired_value: setattr(self.elb.health_check, attr, desired_value) update_health_check = True @@ -946,7 +946,7 @@ } update_access_logs_config = False - for attr, desired_value in access_logs_config.iteritems(): + for attr, desired_value in access_logs_config.items(): if getattr(attributes.access_log, attr) != desired_value: setattr(attributes.access_log, attr, desired_value) update_access_logs_config = True diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/cloud/amazon/ec2_facts.py ansible-2.2.1.0/lib/ansible/modules/core/cloud/amazon/ec2_facts.py --- ansible-2.2.0.0/lib/ansible/modules/core/cloud/amazon/ec2_facts.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/cloud/amazon/ec2_facts.py 2017-01-16 17:03:59.000000000 +0000 @@ -92,7 +92,7 @@ def _mangle_fields(self, fields, uri, filter_patterns=['public-keys-0']): new_fields = {} - for key, value in fields.iteritems(): + for key, value in fields.items(): split_fields = key[len(uri):].split('/') if len(split_fields) > 1 and split_fields[1]: new_key = "-".join(split_fields) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/cloud/amazon/ec2_group.py ansible-2.2.1.0/lib/ansible/modules/core/cloud/amazon/ec2_group.py --- ansible-2.2.0.0/lib/ansible/modules/core/cloud/amazon/ec2_group.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/cloud/amazon/ec2_group.py 2017-01-16 17:03:59.000000000 +0000 @@ -375,7 +375,7 @@ # Finally, remove anything left in the groupRules -- these will be defunct rules if purge_rules: - for (rule, grant) in groupRules.itervalues() : + for (rule, grant) in groupRules.values(): grantGroup = None if grant.group_id: if grant.owner_id != group.owner_id: @@ -452,7 +452,7 @@ # Finally, remove anything left in the groupRules -- these will be defunct rules if purge_rules_egress: - for (rule, grant) in groupRules.itervalues(): + for (rule, grant) in groupRules.values(): grantGroup = None if grant.group_id: grantGroup = groups[grant.group_id].id diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/cloud/amazon/ec2.py ansible-2.2.1.0/lib/ansible/modules/core/cloud/amazon/ec2.py --- ansible-2.2.0.0/lib/ansible/modules/core/cloud/amazon/ec2.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/cloud/amazon/ec2.py 2017-01-16 17:03:59.000000000 +0000 @@ -580,6 +580,8 @@ import time from ast import literal_eval +from ansible.module_utils.six import iteritems +from ansible.module_utils.six import get_function_code try: import boto.ec2 @@ -607,7 +609,7 @@ def _set_none_to_blank(dictionary): result = dictionary - for k in result.iterkeys(): + for k in result: if type(result[k]) == dict: result[k] = _set_none_to_blank(result[k]) elif not result[k]: @@ -637,14 +639,14 @@ for x in tags: if type(x) is dict: x = _set_none_to_blank(x) - filters.update(dict(("tag:"+tn, tv) for (tn,tv) in x.iteritems())) + filters.update(dict(("tag:"+tn, tv) for (tn,tv) in iteritems(x))) else: filters.update({"tag-key": x}) # if dict, add the key and value to the filter if type(tags) is dict: tags = _set_none_to_blank(tags) - filters.update(dict(("tag:"+tn, tv) for (tn,tv) in tags.iteritems())) + filters.update(dict(("tag:"+tn, tv) for (tn,tv) in iteritems(tags))) if state: # http://stackoverflow.com/questions/437511/what-are-the-valid-instancestates-for-the-amazon-ec2-api @@ -744,7 +746,7 @@ True if Boto library accept instance_profile_name argument, else false """ run_instances_method = getattr(ec2, 'run_instances') - return 'instance_profile_name' in run_instances_method.func_code.co_varnames + return 'instance_profile_name' in get_function_code(run_instances_method).co_varnames def create_block_device(module, ec2, volume): # Not aware of a way to determine this programatically @@ -794,7 +796,7 @@ True if boto library has the named param as an argument on the request_spot_instances method, else False """ method = getattr(ec2, 'request_spot_instances') - return param in method.func_code.co_varnames + return param in get_function_code(method).co_varnames def await_spot_requests(module, ec2, spot_requests, count): """ diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/cloud/amazon/elasticache.py ansible-2.2.1.0/lib/ansible/modules/core/cloud/amazon/elasticache.py --- ansible-2.2.0.0/lib/ansible/modules/core/cloud/amazon/elasticache.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/cloud/amazon/elasticache.py 2017-01-16 17:03:59.000000000 +0000 @@ -381,7 +381,7 @@ 'NumCacheNodes': self.num_nodes, 'EngineVersion': self.cache_engine_version } - for key, value in modifiable_data.iteritems(): + for key, value in modifiable_data.items(): if value is not None and self.data[key] != value: return True @@ -414,7 +414,7 @@ # Only check for modifications if zone is specified if self.zone is not None: unmodifiable_data['zone'] = self.data['PreferredAvailabilityZone'] - for key, value in unmodifiable_data.iteritems(): + for key, value in unmodifiable_data.items(): if getattr(self, key) is not None and getattr(self, key) != value: return True return False diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/cloud/amazon/s3.py ansible-2.2.1.0/lib/ansible/modules/core/cloud/amazon/s3.py --- ansible-2.2.0.0/lib/ansible/modules/core/cloud/amazon/s3.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/cloud/amazon/s3.py 2017-01-16 17:03:59.000000000 +0000 @@ -195,7 +195,7 @@ ''' import os -import urlparse +from ansible.module_utils.six.moves.urllib.parse import urlparse from ssl import SSLError try: diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/cloud/digital_ocean/digital_ocean.py ansible-2.2.1.0/lib/ansible/modules/core/cloud/digital_ocean/digital_ocean.py --- ansible-2.2.0.0/lib/ansible/modules/core/cloud/digital_ocean/digital_ocean.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/cloud/digital_ocean/digital_ocean.py 2017-01-16 17:03:59.000000000 +0000 @@ -225,7 +225,7 @@ def update_attr(self, attrs=None): if attrs: - for k, v in attrs.iteritems(): + for k, v in attrs.items(): setattr(self, k, v) else: json = self.manager.show_droplet(self.id) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/cloud/docker/docker_container.py ansible-2.2.1.0/lib/ansible/modules/core/cloud/docker/docker_container.py --- ansible-2.2.0.0/lib/ansible/modules/core/cloud/docker/docker_container.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/cloud/docker/docker_container.py 2017-01-16 17:03:59.000000000 +0000 @@ -812,7 +812,7 @@ kernel_memory='kernel_memory' ) result = dict() - for key, value in update_parameters.iteritems(): + for key, value in update_parameters.items(): if getattr(self, value, None) is not None: result[key] = getattr(self, value) return result @@ -924,7 +924,7 @@ pid_mode='pid_mode' ) params = dict() - for key, value in host_config_params.iteritems(): + for key, value in host_config_params.items(): if getattr(self, value, None) is not None: params[key] = getattr(self, value) @@ -1113,10 +1113,10 @@ final_env = {} if self.env_file: parsed_env_file = utils.parse_env_file(self.env_file) - for name, value in parsed_env_file.iteritems(): + for name, value in parsed_env_file.items(): final_env[name] = str(value) if self.env: - for name, value in self.env.iteritems(): + for name, value in self.env.items(): final_env[name] = str(value) return final_env @@ -1249,7 +1249,7 @@ ) differences = [] - for key, value in config_mapping.iteritems(): + for key, value in config_mapping.items(): self.log('check differences %s %s vs %s' % (key, getattr(self.parameters, key), str(value))) if getattr(self.parameters, key, None) is not None: if isinstance(getattr(self.parameters, key), list) and isinstance(value, list): @@ -1305,7 +1305,7 @@ ''' if not isinstance(dict_a, dict) or not isinstance(dict_b, dict): return False - for key, value in dict_a.iteritems(): + for key, value in dict_a.items(): if isinstance(value, dict): match = self._compare_dicts(value, dict_b.get(key)) elif isinstance(value, list): @@ -1344,7 +1344,7 @@ ) differences = [] - for key, value in config_mapping.iteritems(): + for key, value in config_mapping.items(): if getattr(self.parameters, key, None) and getattr(self.parameters, key) != value: # no match. record the differences item = dict() @@ -1393,7 +1393,7 @@ diff = True if network.get('links') and connected_networks[network['name']].get('Links'): expected_links = [] - for link, alias in network['links'].iteritems(): + for link, alias in network['links'].items(): expected_links.append("%s:%s" % (link, alias)) for link in expected_links: if link not in connected_networks[network['name']].get('Links', []): @@ -1424,7 +1424,7 @@ connected_networks = self.container['NetworkSettings'].get('Networks') if connected_networks: - for network, network_config in connected_networks.iteritems(): + for network, network_config in connected_networks.items(): keep = False if self.parameters.networks: for expected_network in self.parameters.networks: @@ -1476,7 +1476,7 @@ if not self.parameters.published_ports: return None expected_bound_ports = {} - for container_port, config in self.parameters.published_ports.iteritems(): + for container_port, config in self.parameters.published_ports.items(): if isinstance(container_port, int): container_port = "%s/tcp" % container_port if len(config) == 1: @@ -1495,7 +1495,7 @@ self.log('parameter links:') self.log(self.parameters.links, pretty_print=True) exp_links = [] - for link, alias in self.parameters.links.iteritems(): + for link, alias in self.parameters.links.items(): exp_links.append("/%s:%s/%s" % (link, ('/' + self.parameters.name), alias)) return exp_links @@ -1626,7 +1626,7 @@ if getattr(self.parameters, param_name, None) is None: return None results = [] - for key, value in getattr(self.parameters, param_name).iteritems(): + for key, value in getattr(self.parameters, param_name).items(): results.append("%s%s%s" % (key, join_with, value)) return results diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/cloud/docker/docker_image.py ansible-2.2.1.0/lib/ansible/modules/core/cloud/docker/docker_image.py --- ansible-2.2.0.0/lib/ansible/modules/core/cloud/docker/docker_image.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/cloud/docker/docker_image.py 2017-01-16 17:03:59.000000000 +0000 @@ -506,7 +506,7 @@ if self.container_limits: params['container_limits'] = self.container_limits if self.buildargs: - for key, value in self.buildargs.iteritems(): + for key, value in self.buildargs.items(): if not isinstance(value, basestring): self.buildargs[key] = str(value) params['buildargs'] = self.buildargs diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/cloud/docker/docker_network.py ansible-2.2.1.0/lib/ansible/modules/core/cloud/docker/docker_network.py --- ansible-2.2.0.0/lib/ansible/modules/core/cloud/docker/docker_network.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/cloud/docker/docker_network.py 2017-01-16 17:03:59.000000000 +0000 @@ -238,7 +238,7 @@ different = True differences.append('driver_options') else: - for key, value in self.parameters.driver_options.iteritems(): + for key, value in self.parameters.driver_options.items(): if not net['Options'].get(key) or value != net['Options'][key]: different = True differences.append('driver_options.%s' % key) @@ -251,7 +251,7 @@ different = True differences.append('ipam_options') else: - for key, value in self.parameters.ipam_options.iteritems(): + for key, value in self.parameters.ipam_options.items(): camelkey = None for net_key in net['IPAM']['Config'][0]: if key == net_key.lower(): diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/cloud/docker/_docker.py ansible-2.2.1.0/lib/ansible/modules/core/cloud/docker/_docker.py --- ansible-2.2.0.0/lib/ansible/modules/core/cloud/docker/_docker.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/cloud/docker/_docker.py 2017-01-16 17:03:59.000000000 +0000 @@ -898,11 +898,11 @@ self.ensure_capability('env_file') parsed_env_file = docker.utils.parse_env_file(env_file) - for name, value in parsed_env_file.iteritems(): + for name, value in parsed_env_file.items(): final_env[name] = str(value) if env: - for name, value in env.iteritems(): + for name, value in env.items(): final_env[name] = str(value) return final_env @@ -994,7 +994,7 @@ self.ensure_capability('log_driver') log_config = docker.utils.LogConfig(type=docker.utils.LogConfig.types.JSON) if optionals['log_opt'] is not None: - for k, v in optionals['log_opt'].iteritems(): + for k, v in optionals['log_opt'].items(): log_config.set_config_value(k, v) log_config.type = optionals['log_driver'] params['log_config'] = log_config @@ -1069,7 +1069,7 @@ ''' parts = [] - for k, v in self.counters.iteritems(): + for k, v in self.counters.items(): if v == 0: continue @@ -1096,7 +1096,7 @@ def get_summary_counters_msg(self): msg = "" - for k, v in self.counters.iteritems(): + for k, v in self.counters.items(): msg = msg + "%s %d " % (k, v) return msg @@ -1105,7 +1105,7 @@ self.counters[name] = self.counters[name] + 1 def has_changed(self): - for k, v in self.counters.iteritems(): + for k, v in self.counters.items(): if v > 0: return True @@ -1283,7 +1283,7 @@ expected_env[name] = value if self.environment: - for name, value in self.environment.iteritems(): + for name, value in self.environment.items(): expected_env[name] = str(value) actual_env = {} @@ -1300,7 +1300,7 @@ # LABELS expected_labels = {} - for name, value in self.module.params.get('labels').iteritems(): + for name, value in self.module.params.get('labels').items(): expected_labels[name] = str(value) if type(container['Config']['Labels']) is dict: @@ -1397,7 +1397,7 @@ expected_bound_ports = {} if self.port_bindings: - for container_port, config in self.port_bindings.iteritems(): + for container_port, config in self.port_bindings.items(): if isinstance(container_port, int): container_port = "{0}/tcp".format(container_port) if len(config) == 1: @@ -1433,7 +1433,7 @@ # LINKS expected_links = set() - for link, alias in (self.links or {}).iteritems(): + for link, alias in (self.links or {}).items(): expected_links.add("/{0}:{1}/{2}".format(link, container["Name"], alias)) actual_links = set(container['HostConfig']['Links'] or []) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/cloud/rackspace/rax_cbs_attachments.py ansible-2.2.1.0/lib/ansible/modules/core/cloud/rackspace/rax_cbs_attachments.py --- ansible-2.2.0.0/lib/ansible/modules/core/cloud/rackspace/rax_cbs_attachments.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/cloud/rackspace/rax_cbs_attachments.py 2017-01-16 17:03:59.000000000 +0000 @@ -125,7 +125,7 @@ volume.get() - for key, value in vars(volume).iteritems(): + for key, value in vars(volume).items(): if (isinstance(value, NON_CALLABLES) and not key.startswith('_')): instance[key] = value diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/cloud/rackspace/rax_cdb.py ansible-2.2.1.0/lib/ansible/modules/core/cloud/rackspace/rax_cdb.py --- ansible-2.2.0.0/lib/ansible/modules/core/cloud/rackspace/rax_cdb.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/cloud/rackspace/rax_cdb.py 2017-01-16 17:03:59.000000000 +0000 @@ -110,7 +110,7 @@ for arg, value in dict(name=name, flavor=flavor, volume=volume, type=cdb_type, version=cdb_version - ).iteritems(): + ).items(): if not value: module.fail_json(msg='%s is required for the "rax_cdb"' ' module' % arg) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/cloud/rackspace/rax_cdb_user.py ansible-2.2.1.0/lib/ansible/modules/core/cloud/rackspace/rax_cdb_user.py --- ansible-2.2.0.0/lib/ansible/modules/core/cloud/rackspace/rax_cdb_user.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/cloud/rackspace/rax_cdb_user.py 2017-01-16 17:03:59.000000000 +0000 @@ -89,7 +89,7 @@ def save_user(module, cdb_id, name, password, databases, host): - for arg, value in dict(cdb_id=cdb_id, name=name).iteritems(): + for arg, value in dict(cdb_id=cdb_id, name=name).items(): if not value: module.fail_json(msg='%s is required for the "rax_cdb_user" ' 'module' % arg) @@ -144,7 +144,7 @@ def delete_user(module, cdb_id, name): - for arg, value in dict(cdb_id=cdb_id, name=name).iteritems(): + for arg, value in dict(cdb_id=cdb_id, name=name).items(): if not value: module.fail_json(msg='%s is required for the "rax_cdb_user"' ' module' % arg) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/cloud/rackspace/rax_clb.py ansible-2.2.1.0/lib/ansible/modules/core/cloud/rackspace/rax_clb.py --- ansible-2.2.0.0/lib/ansible/modules/core/cloud/rackspace/rax_clb.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/cloud/rackspace/rax_clb.py 2017-01-16 17:03:59.000000000 +0000 @@ -196,7 +196,7 @@ 'protocol': protocol, 'timeout': timeout } - for att, value in atts.iteritems(): + for att, value in atts.items(): current = getattr(balancer, att) if current != value: changed = True diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/cloud/rackspace/rax.py ansible-2.2.1.0/lib/ansible/modules/core/cloud/rackspace/rax.py --- ansible-2.2.0.0/lib/ansible/modules/core/cloud/rackspace/rax.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/cloud/rackspace/rax.py 2017-01-16 17:03:59.000000000 +0000 @@ -480,7 +480,7 @@ if not boot_from_volume and not boot_volume and not image: module.fail_json(msg='image is required for the "rax" module') - for arg, value in dict(name=name, flavor=flavor).iteritems(): + for arg, value in dict(name=name, flavor=flavor).items(): if not value: module.fail_json(msg='%s is required for the "rax" module' % arg) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/cloud/vmware/vsphere_guest.py ansible-2.2.1.0/lib/ansible/modules/core/cloud/vmware/vsphere_guest.py --- ansible-2.2.0.0/lib/ansible/modules/core/cloud/vmware/vsphere_guest.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/cloud/vmware/vsphere_guest.py 2017-01-16 17:03:59.000000000 +0000 @@ -19,6 +19,7 @@ # TODO: # Ability to set CPU/Memory reservations + try: import json except ImportError: @@ -858,7 +859,7 @@ if vm_extra_config: spec = spec_singleton(spec, request, vm) extra_config = [] - for k,v in vm_extra_config.iteritems(): + for k,v in vm_extra_config.items(): ec = spec.new_extraConfig() ec.set_element_key(str(k)) ec.set_element_value(str(v)) @@ -986,7 +987,7 @@ disk_num = 0 dev_changes = [] disks_changed = {} - for disk in sorted(vm_disk.iterkeys()): + for disk in sorted(vm_disk): try: disksize = int(vm_disk[disk]['size_gb']) # Convert the disk size to kilobytes @@ -1080,7 +1081,7 @@ module.fail_json(msg="Cannot find datacenter named: %s" % datacenter) dcprops = VIProperty(vsphere_client, dcmor) nfmor = dcprops.networkFolder._obj - for k,v in vm_nic.iteritems(): + for k,v in vm_nic.items(): nicNum = k[len(k) -1] if vm_nic[k]['network_type'] == 'dvs': portgroupKey = find_portgroup_key(module, s, nfmor, vm_nic[k]['network']) @@ -1111,7 +1112,7 @@ module.exit_json() if len(nics) > 0: - for nic, obj in nics.iteritems(): + for nic, obj in nics.items(): """ 1,2 and 3 are used to mark which action should be taken 1 = from a distributed switch to a distributed switch @@ -1140,7 +1141,7 @@ "nic_backing").pyclass() nic_backing.set_element_deviceName(vm_nic[nic]['network']) dev._obj.set_element_backing(nic_backing) - for nic, obj in nics.iteritems(): + for nic, obj in nics.items(): dev = obj[0] spec = request.new_spec() nic_change = spec.new_deviceChange() @@ -1173,7 +1174,7 @@ def _find_path_in_tree(tree, path): - for name, o in tree.iteritems(): + for name, o in tree.items(): if name == path[0]: if len(path) == 1: return o @@ -1226,7 +1227,7 @@ # try the legacy behaviour of just matching the folder name, so 'lamp' alone matches 'production/customerA/lamp' if vmfmor is None: - for mor, name in vsphere_client._get_managed_objects(MORTypes.Folder).iteritems(): + for mor, name in vsphere_client._get_managed_objects(MORTypes.Folder).items(): if name == vm_extra_config['folder']: vmfmor = mor @@ -1342,7 +1343,7 @@ if vm_disk: disk_num = 0 disk_key = 0 - for disk in sorted(vm_disk.iterkeys()): + for disk in sorted(vm_disk): try: datastore = vm_disk[disk]['datastore'] except KeyError: @@ -1398,7 +1399,7 @@ add_floppy(module, vsphere_client, config_target, config, devices, default_devs, floppy_type, floppy_image_path) if vm_nic: - for nic in sorted(vm_nic.iterkeys()): + for nic in sorted(vm_nic): try: nictype = vm_nic[nic]['type'] except KeyError: diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/database/mysql/mysql_user.py ansible-2.2.1.0/lib/ansible/modules/core/database/mysql/mysql_user.py --- ansible-2.2.0.0/lib/ansible/modules/core/database/mysql/mysql_user.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/database/mysql/mysql_user.py 2017-01-16 17:03:59.000000000 +0000 @@ -293,7 +293,7 @@ if old_user_mgmt: cursor.execute("SET PASSWORD FOR %s@%s = PASSWORD(%s)", (user, host, password)) else: - cursor.execute("ALTER USER %s@%s IDENTIFIED BY %s", (user, host, password)) + cursor.execute("ALTER USER %s@%s IDENTIFIED WITH mysql_native_password BY %s", (user, host, password)) changed = True # Handle privileges diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/files/copy.py ansible-2.2.1.0/lib/ansible/modules/core/files/copy.py --- ansible-2.2.0.0/lib/ansible/modules/core/files/copy.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/files/copy.py 2017-01-16 17:03:59.000000000 +0000 @@ -284,18 +284,19 @@ directory_args['mode'] = None adjust_recursive_directory_permissions(pre_existing_dir, new_directory_list, module, directory_args, changed) + if os.path.isdir(b_dest): + basename = os.path.basename(src) + if original_basename: + basename = original_basename + dest = os.path.join(dest, basename) + b_dest = to_bytes(dest, errors='surrogate_or_strict') + if os.path.exists(b_dest): if os.path.islink(b_dest) and follow: b_dest = os.path.realpath(b_dest) dest = to_native(b_dest, errors='surrogate_or_strict') if not force: module.exit_json(msg="file already exists", src=src, dest=dest, changed=False) - if os.path.isdir(b_dest): - basename = os.path.basename(src) - if original_basename: - basename = original_basename - dest = os.path.join(dest, basename) - b_dest = to_bytes(dest, errors='surrogate_or_strict') if os.access(b_dest, os.R_OK): checksum_dest = module.sha1(dest) else: diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/files/file.py ansible-2.2.1.0/lib/ansible/modules/core/files/file.py --- ansible-2.2.0.0/lib/ansible/modules/core/files/file.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/files/file.py 2017-01-16 17:03:59.000000000 +0000 @@ -217,6 +217,7 @@ if follow and state == 'link': # use the current target of the link as the source src = to_native(os.path.realpath(b_path), errors='strict') + b_src = to_bytes(os.path.realpath(b_path), errors='strict') else: module.fail_json(msg='src and dest are required for creating links') diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/files/ini_file.py ansible-2.2.1.0/lib/ansible/modules/core/files/ini_file.py --- ansible-2.2.0.0/lib/ansible/modules/core/files/ini_file.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/files/ini_file.py 2017-01-16 17:03:59.000000000 +0000 @@ -80,10 +80,10 @@ create: required: false choices: [ "yes", "no" ] - default: "no" + default: "yes" description: - - If specified, the file will be created if it does not already exist. - By default it will fail if the file is missing. + - If set to 'no', the module will fail if the file does not already exist. + By default it will create the file if it is missing. version_added: "2.2" notes: - While it is possible to add an I(option) without specifying a I(value), this makes @@ -257,7 +257,7 @@ backup = dict(default='no', type='bool'), state = dict(default='present', choices=['present', 'absent']), no_extra_spaces = dict(required=False, default=False, type='bool'), - create=dict(default=False, type='bool') + create=dict(default=True, type='bool') ), add_file_common_args = True, supports_check_mode = True diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/files/stat.py ansible-2.2.1.0/lib/ansible/modules/core/files/stat.py --- ansible-2.2.0.0/lib/ansible/modules/core/files/stat.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/files/stat.py 2017-01-16 17:03:59.000000000 +0000 @@ -303,17 +303,17 @@ type: string sample: us-ascii readable: - description: Tells you if the invoking user has the right to read the path + description: Tells you if the invoking user has the right to read the path, added in version 2.2 returned: success, path exists and user can read the path type: boolean sample: False writeable: - description: Tells you if the invoking user has the right to write the path + description: Tells you if the invoking user has the right to write the path, added in version 2.2 returned: success, path exists and user can write the path type: boolean sample: False executable: - description: Tells you if the invoking user has the execute the path + description: Tells you if the invoking user has the execute the path, added in version 2.2 returned: success, path exists and user can execute the path type: boolean sample: False diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/basics/get_url.py ansible-2.2.1.0/lib/ansible/modules/core/network/basics/get_url.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/basics/get_url.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/basics/get_url.py 2017-01-16 17:03:59.000000000 +0000 @@ -227,10 +227,14 @@ if info['status'] == 304: module.exit_json(url=url, dest=dest, changed=False, msg=info.get('msg', '')) - # create a temporary file and copy content to do checksum-based replacement + # Exceptions in fetch_url may result in a status -1, the ensures a proper error to the user in all cases + if info['status'] == -1: + module.fail_json(msg=info['msg'], url=url, dest=dest) + if info['status'] != 200 and not url.startswith('file:/') and not (url.startswith('ftp:/') and info.get('msg', '').startswith('OK')): module.fail_json(msg="Request failed", status_code=info['status'], response=info['msg'], url=url, dest=dest) + # create a temporary file and copy content to do checksum-based replacement if tmp_dest != '': # tmp_dest should be an existing dir tmp_dest_is_dir = os.path.isdir(tmp_dest) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/cumulus/cl_bond.py ansible-2.2.1.0/lib/ansible/modules/core/network/cumulus/cl_bond.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/cumulus/cl_bond.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/cumulus/cl_bond.py 2017-01-16 17:03:59.000000000 +0000 @@ -445,7 +445,7 @@ # checks all lists and removes it, so that functions expecting # an empty list, get this result. May upstream this fix into # the AnsibleModule code to have it check for this. - for k, _param in module.params.iteritems(): + for k, _param in module.params.items(): if isinstance(_param, list): module.params[k] = [x for x in _param if x] diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/cumulus/cl_bridge.py ansible-2.2.1.0/lib/ansible/modules/core/network/cumulus/cl_bridge.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/cumulus/cl_bridge.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/cumulus/cl_bridge.py 2017-01-16 17:03:59.000000000 +0000 @@ -374,7 +374,7 @@ # checks all lists and removes it, so that functions expecting # an empty list, get this result. May upstream this fix into # the AnsibleModule code to have it check for this. - for k, _param in module.params.iteritems(): + for k, _param in module.params.items(): if isinstance(_param, list): module.params[k] = [x for x in _param if x] diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/cumulus/cl_interface.py ansible-2.2.1.0/lib/ansible/modules/core/network/cumulus/cl_interface.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/cumulus/cl_interface.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/cumulus/cl_interface.py 2017-01-16 17:03:59.000000000 +0000 @@ -408,7 +408,7 @@ # checks all lists and removes it, so that functions expecting # an empty list, get this result. May upstream this fix into # the AnsibleModule code to have it check for this. - for k, _param in module.params.iteritems(): + for k, _param in module.params.items(): if isinstance(_param, list): module.params[k] = [x for x in _param if x] diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/dellos10/dellos10_facts.py ansible-2.2.1.0/lib/ansible/modules/core/network/dellos10/dellos10_facts.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/dellos10/dellos10_facts.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/dellos10/dellos10_facts.py 2017-01-16 17:03:59.000000000 +0000 @@ -439,7 +439,7 @@ module.exit_json(out=module.from_json(runner.items)) ansible_facts = dict() - for key, value in facts.iteritems(): + for key, value in facts.items(): key = 'ansible_net_%s' % key ansible_facts[key] = value diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/dellos6/dellos6_facts.py ansible-2.2.1.0/lib/ansible/modules/core/network/dellos6/dellos6_facts.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/dellos6/dellos6_facts.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/dellos6/dellos6_facts.py 2017-01-16 17:03:59.000000000 +0000 @@ -236,7 +236,7 @@ def populate_interfaces(self, interfaces, desc, properties): facts = dict() - for key, value in interfaces.iteritems(): + for key, value in interfaces.items(): intf = dict() intf['description'] = self.parse_description(key,desc) intf['macaddress'] = self.parse_macaddress(value) @@ -429,7 +429,7 @@ module.exit_json(out=module.from_json(runner.items)) ansible_facts = dict() - for key, value in facts.iteritems(): + for key, value in facts.items(): key = 'ansible_net_%s' % key ansible_facts[key] = value diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/dellos9/dellos9_facts.py ansible-2.2.1.0/lib/ansible/modules/core/network/dellos9/dellos9_facts.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/dellos9/dellos9_facts.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/dellos9/dellos9_facts.py 2017-01-16 17:03:59.000000000 +0000 @@ -282,7 +282,7 @@ def populate_interfaces(self, interfaces): facts = dict() - for key, value in interfaces.iteritems(): + for key, value in interfaces.items(): intf = dict() intf['description'] = self.parse_description(value) intf['macaddress'] = self.parse_macaddress(value) @@ -303,7 +303,7 @@ return facts def populate_ipv6_interfaces(self, data): - for key, value in data.iteritems(): + for key, value in data.items(): self.facts['interfaces'][key]['ipv6'] = list() addresses = re.findall(r'\s+(.+), subnet', value, re.M) subnets = re.findall(r', subnet is (\S+)', value, re.M) @@ -552,7 +552,7 @@ module.exit_json(out=module.from_json(runner.items)) ansible_facts = dict() - for key, value in facts.iteritems(): + for key, value in facts.items(): key = 'ansible_net_%s' % key ansible_facts[key] = value diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/eos/eos_eapi.py ansible-2.2.1.0/lib/ansible/modules/core/network/eos/eos_eapi.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/eos/eos_eapi.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/eos/eos_eapi.py 2017-01-16 17:03:59.000000000 +0000 @@ -216,7 +216,7 @@ def started(module, instance, commands): commands.append('no shutdown') setters = set() - for key, value in module.argument_spec.iteritems(): + for key, value in module.argument_spec.items(): if module.params[key] is not None: setter = value.get('setter') or 'set_%s' % key if setter not in setters: diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/ios/ios_facts.py ansible-2.2.1.0/lib/ansible/modules/core/network/ios/ios_facts.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/ios/ios_facts.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/ios/ios_facts.py 2017-01-16 17:03:59.000000000 +0000 @@ -42,19 +42,31 @@ """ EXAMPLES = """ +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. +vars: + cli: + host: "{{ inventory_hostname }}" + username: cisco + password: cisco + transport: cli + # Collect all facts from the device - ios_facts: gather_subset: all + provider: "{{ cli }}" # Collect only the config and default facts - ios_facts: gather_subset: - config + provider: "{{ cli }}" # Do not collect hardware facts - ios_facts: gather_subset: - "!hardware" + provider: "{{ cli }}" """ RETURN = """ @@ -127,44 +139,35 @@ import itertools import ansible.module_utils.ios -from ansible.module_utils.netcli import CommandRunner, AddCommandError from ansible.module_utils.network import NetworkModule from ansible.module_utils.six import iteritems from ansible.module_utils.six.moves import zip -def add_command(runner, command): - try: - runner.add_command(command) - except AddCommandError: - # AddCommandError is raised for any issue adding a command to - # the runner. Silently ignore the exception in this case - pass - class FactsBase(object): - def __init__(self, runner): - self.runner = runner + def __init__(self, module): + self.module = module self.facts = dict() + self.failed_commands = list() - self.commands() + def run(self, cmd): + try: + return self.module.cli(cmd)[0] + except: + self.failed_commands.append(cmd) - def commands(self): - raise NotImplementedError class Default(FactsBase): - def commands(self): - add_command(self.runner, 'show version') - def populate(self): - data = self.runner.get_command('show version') - - self.facts['version'] = self.parse_version(data) - self.facts['serialnum'] = self.parse_serialnum(data) - self.facts['model'] = self.parse_model(data) - self.facts['image'] = self.parse_image(data) - self.facts['hostname'] = self.parse_hostname(data) + data = self.run('show version') + if data: + self.facts['version'] = self.parse_version(data) + self.facts['serialnum'] = self.parse_serialnum(data) + self.facts['model'] = self.parse_model(data) + self.facts['image'] = self.parse_image(data) + self.facts['hostname'] = self.parse_hostname(data) def parse_version(self, data): match = re.search(r'Version (\S+),', data) @@ -194,20 +197,17 @@ class Hardware(FactsBase): - def commands(self): - add_command(self.runner, 'dir | include Directory') - add_command(self.runner, 'show version') - add_command(self.runner, 'show memory statistics | include Processor') - def populate(self): - data = self.runner.get_command('dir | include Directory') - self.facts['filesystems'] = self.parse_filesystems(data) - - data = self.runner.get_command('show memory statistics | include Processor') - match = re.findall(r'\s(\d+)\s', data) - if match: - self.facts['memtotal_mb'] = int(match[0]) / 1024 - self.facts['memfree_mb'] = int(match[1]) / 1024 + data = self.run('dir | include Directory') + if data: + self.facts['filesystems'] = self.parse_filesystems(data) + + data = self.run('show memory statistics | include Processor') + if data: + match = re.findall(r'\s(\d+)\s', data) + if match: + self.facts['memtotal_mb'] = int(match[0]) / 1024 + self.facts['memfree_mb'] = int(match[1]) / 1024 def parse_filesystems(self, data): return re.findall(r'^Directory of (\S+)/', data, re.M) @@ -215,37 +215,33 @@ class Config(FactsBase): - def commands(self): - add_command(self.runner, 'show running-config') - def populate(self): - self.facts['config'] = self.runner.get_command('show running-config') + data = self.run('show running-config') + if data: + self.facts['config'] = data class Interfaces(FactsBase): - def commands(self): - add_command(self.runner, 'show interfaces') - add_command(self.runner, 'show ipv6 interface') - add_command(self.runner, 'show lldp') - add_command(self.runner, 'show lldp neighbors detail') - def populate(self): self.facts['all_ipv4_addresses'] = list() self.facts['all_ipv6_addresses'] = list() - data = self.runner.get_command('show interfaces') - interfaces = self.parse_interfaces(data) - self.facts['interfaces'] = self.populate_interfaces(interfaces) + data = self.run('show interfaces') + if data: + interfaces = self.parse_interfaces(data) + self.facts['interfaces'] = self.populate_interfaces(interfaces) - data = self.runner.get_command('show ipv6 interface') - if len(data) > 0: + data = self.run('show ipv6 interface') + if data: data = self.parse_interfaces(data) self.populate_ipv6_interfaces(data) - if 'LLDP is not enabled' not in self.runner.get_command('show lldp'): - neighbors = self.runner.get_command('show lldp neighbors detail') - self.facts['neighbors'] = self.parse_neighbors(neighbors) + data = self.run('show lldp') + if 'LLDP is not enabled' not in data: + neighbors = self.run('show lldp neighbors detail') + if neighbors: + self.facts['neighbors'] = self.parse_neighbors(neighbors) def populate_interfaces(self, interfaces): facts = dict() @@ -434,27 +430,27 @@ facts = dict() facts['gather_subset'] = list(runable_subsets) - runner = CommandRunner(module) - instances = list() for key in runable_subsets: - instances.append(FACT_SUBSETS[key](runner)) + instances.append(FACT_SUBSETS[key](module)) - runner.run() + failed_commands = list() try: for inst in instances: inst.populate() + failed_commands.extend(inst.failed_commands) facts.update(inst.facts) except Exception: - module.exit_json(out=module.from_json(runner.items)) + exc = get_exception() + module.fail_json(msg=str(exc)) ansible_facts = dict() for key, value in iteritems(facts): key = 'ansible_net_%s' % key ansible_facts[key] = value - module.exit_json(ansible_facts=ansible_facts) + module.exit_json(ansible_facts=ansible_facts, failed_commands=failed_commands) if __name__ == '__main__': diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_aaa_server_host.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_aaa_server_host.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_aaa_server_host.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_aaa_server_host.py 2017-01-16 17:03:59.000000000 +0000 @@ -507,7 +507,7 @@ auth_port=auth_port, acct_port=acct_port, tacacs_port=tacacs_port) - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) + proposed = dict((k, v) for k, v in args.items() if v is not None) changed = False if encrypt_type and not key: @@ -536,7 +536,7 @@ msg='host_timeout must be an integer between 1 and 60') delta = dict( - set(proposed.iteritems()).difference(existing.iteritems())) + set(proposed.items()).difference(existing.items())) if delta: union = existing.copy() union.update(delta) @@ -546,7 +546,7 @@ elif state == 'absent': intersect = dict( - set(proposed.iteritems()).intersection(existing.iteritems())) + set(proposed.items()).intersection(existing.items())) if intersect.get('address') and intersect.get('server_type'): command = 'no {0}-server host {1}'.format( intersect.get('server_type'), intersect.get('address')) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_aaa_server.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_aaa_server.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_aaa_server.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_aaa_server.py 2017-01-16 17:03:59.000000000 +0000 @@ -160,6 +160,7 @@ from ansible.module_utils.basic import get_exception from ansible.module_utils.netcfg import NetworkConfig, ConfigLine from ansible.module_utils.shell import ShellError +from ansible.module_utils.six import iteritems try: from ansible.module_utils.nxos import get_module @@ -502,7 +503,7 @@ server_timeout=server_timeout, directed_request=directed_request) changed = False - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) + proposed = dict((k, v) for k, v in args.items() if v is not None) existing = get_aaa_server_info(server_type, module) end_state = existing @@ -525,15 +526,15 @@ module.fail_json( msg='server_timeout must be an integer between 1 and 60') - delta = dict(set(proposed.iteritems()).difference( - existing.iteritems())) + delta = dict(set(proposed.items()).difference( + existing.items())) if delta: command = config_aaa_server(delta, server_type) if command: commands.append(command) elif state == 'default': - for key, value in proposed.iteritems(): + for key, value in proposed.items(): if key != 'server_type' and value != 'default': module.fail_json( msg='Parameters must be set to "default"' diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_acl.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_acl.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_acl.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_acl.py 2017-01-16 17:03:59.000000000 +0000 @@ -521,13 +521,13 @@ options['time_range'] = each.get('timerange') options_no_null = {} - for key, value in options.iteritems(): + for key, value in options.items(): if value is not None: options_no_null[key] = value keep['options'] = options_no_null - for key, value in temp.iteritems(): + for key, value in temp.items(): if value: keep[key] = value # ensure options is always in the dict @@ -596,7 +596,7 @@ options.pop('time_range') command = '' - for option, value in options.iteritems(): + for option, value in options.items(): if option in ENABLE_ONLY: if value == 'enable': command += ' ' + option @@ -727,11 +727,11 @@ 'dest_port1', 'dest_port2', 'remark'] proposed_core = dict((param, value) for (param, value) in - module.params.iteritems() + module.params.items() if param in CORE and value is not None) proposed_options = dict((param, value) for (param, value) in - module.params.iteritems() + module.params.items() if param in OPTIONS_NAMES and value is not None) proposed = {} proposed.update(proposed_core) @@ -753,12 +753,12 @@ if not existing_core.get('remark'): delta_core = dict( - set(proposed_core.iteritems()).difference( - existing_core.iteritems()) + set(proposed_core.items()).difference( + existing_core.items()) ) delta_options = dict( - set(proposed_options.iteritems()).difference( - existing_options.iteritems()) + set(proposed_options.items()).difference( + existing_options.items()) ) if state == 'present': diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_bgp_af.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_bgp_af.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_bgp_af.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_bgp_af.py 2017-01-16 17:03:59.000000000 +0000 @@ -734,7 +734,7 @@ commands = list() command = '' fixed_proposed = {} - for key, value in proposed.iteritems(): + for key, value in proposed.items(): if key in DAMPENING_PARAMS: if value != 'default': command = 'dampening {0} {1} {2} {3}'.format( @@ -872,7 +872,7 @@ fixed_proposed, commands = fix_proposed(module, proposed, existing) proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, fixed_proposed) existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) - for key, value in proposed_commands.iteritems(): + for key, value in proposed_commands.items(): if key == 'address-family': addr_family_command = "address-family {0} {1}".format( module.params['afi'], module.params['safi']) @@ -1058,7 +1058,7 @@ existing_asn=existing.get('asn')) end_state = existing - proposed_args = dict((k, v) for k, v in module.params.iteritems() + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) if proposed_args.get('networks'): @@ -1069,7 +1069,7 @@ proposed_args['inject_map'] = 'default' proposed = {} - for key, value in proposed_args.iteritems(): + for key, value in proposed_args.items(): if key not in ['asn', 'vrf']: if str(value).lower() == 'default': value = PARAM_TO_DEFAULT_KEYMAP.get(key) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_bgp_neighbor_af.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_bgp_neighbor_af.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_bgp_neighbor_af.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_bgp_neighbor_af.py 2017-01-16 17:03:59.000000000 +0000 @@ -894,7 +894,7 @@ 'route-map out', 'soft-reconfiguration inbound' ] - for key, value in proposed_commands.iteritems(): + for key, value in proposed_commands.items(): if key == 'send-community' and value == 'none': commands.append('{0}'.format(key)) @@ -1069,11 +1069,11 @@ module.params['advertise_map_non_exist'] = 'default' end_state = existing - proposed_args = dict((k, v) for k, v in module.params.iteritems() + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) proposed = {} - for key, value in proposed_args.iteritems(): + for key, value in proposed_args.items(): if key not in ['asn', 'vrf', 'neighbor']: if not isinstance(value, list): if str(value).lower() == 'true': diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_bgp_neighbor.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_bgp_neighbor.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_bgp_neighbor.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_bgp_neighbor.py 2017-01-16 17:03:59.000000000 +0000 @@ -575,7 +575,7 @@ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) - for key, value in proposed_commands.iteritems(): + for key, value in proposed_commands.items(): if value is True: commands.append(key) @@ -724,11 +724,11 @@ existing_asn=existing.get('asn')) end_state = existing - proposed_args = dict((k, v) for k, v in module.params.iteritems() + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) proposed = {} - for key, value in proposed_args.iteritems(): + for key, value in proposed_args.items(): if key not in ['asn', 'vrf', 'neighbor', 'pwd_type']: if str(value).lower() == 'default': value = PARAM_TO_DEFAULT_KEYMAP.get(key) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_bgp.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_bgp.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_bgp.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_bgp.py 2017-01-16 17:03:59.000000000 +0000 @@ -754,7 +754,7 @@ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) - for key, value in proposed_commands.iteritems(): + for key, value in proposed_commands.items(): if value is True: commands.append(key) elif value is False: @@ -950,7 +950,7 @@ ] if module.params['vrf'] != 'default': - for param, inserted_value in module.params.iteritems(): + for param, inserted_value in module.params.items(): if param in GLOBAL_PARAMS and inserted_value: module.fail_json(msg='Global params can be modified only' ' under "default" VRF.', @@ -967,10 +967,10 @@ existing_asn=existing.get('asn')) end_state = existing - proposed_args = dict((k, v) for k, v in module.params.iteritems() + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) proposed = {} - for key, value in proposed_args.iteritems(): + for key, value in proposed_args.items(): if key != 'asn' and key != 'vrf': if str(value).lower() == 'default': value = PARAM_TO_DEFAULT_KEYMAP.get(key) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_evpn_global.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_evpn_global.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_evpn_global.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_evpn_global.py 2017-01-16 17:03:59.000000000 +0000 @@ -268,7 +268,7 @@ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) - for key, value in proposed_commands.iteritems(): + for key, value in proposed_commands.items(): if value is True: commands.append(key) elif value is False: diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_evpn_vni.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_evpn_vni.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_evpn_vni.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_evpn_vni.py 2017-01-16 17:03:59.000000000 +0000 @@ -329,7 +329,7 @@ else: existing[arg] = get_route_target_value(arg, config, module) - existing_fix = dict((k, v) for k, v in existing.iteritems() if v) + existing_fix = dict((k, v) for k, v in existing.items() if v) if existing_fix: existing['vni'] = module.params['vni'] else: @@ -357,7 +357,7 @@ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) - for key, value in proposed_commands.iteritems(): + for key, value in proposed_commands.items(): if key.startswith('route-target'): if value == ['default']: existing_value = existing_commands.get(key) @@ -433,11 +433,11 @@ existing = invoke('get_existing', module, args) end_state = existing - proposed_args = dict((k, v) for k, v in module.params.iteritems() + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) proposed = {} - for key, value in proposed_args.iteritems(): + for key, value in proposed_args.items(): if key != 'vni': if value == 'true': value = True diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_facts.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_facts.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_facts.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_facts.py 2017-01-16 17:03:59.000000000 +0000 @@ -428,6 +428,8 @@ def parse_module(self, data): data = data['TABLE_modinfo']['ROW_modinfo'] + if isinstance(data, dict): + data = [data] objects = list(self.transform_iterable(data, self.MODULE_MAP)) return objects diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_hsrp.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_hsrp.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_hsrp.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_hsrp.py 2017-01-16 17:03:59.000000000 +0000 @@ -491,7 +491,7 @@ elif preempt == 'disabled': delta['preempt'] = 'no preempt' - for key, value in delta.iteritems(): + for key, value in delta.items(): command = config_args.get(key, 'DNE').format(**delta) if command and command != 'DNE': if key == 'group': @@ -645,7 +645,7 @@ preempt=preempt, vip=vip, auth_type=auth_type, auth_string=auth_string) - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) + proposed = dict((k, v) for k, v in args.items() if v is not None) existing = get_hsrp_group(group, interface, module) @@ -666,7 +666,7 @@ commands = [] if state == 'present': delta = dict( - set(proposed.iteritems()).difference(existing.iteritems())) + set(proposed.items()).difference(existing.items())) if delta: command = get_commands_config_hsrp(delta, interface, args) commands.extend(command) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_igmp_interface.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_igmp_interface.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_igmp_interface.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_igmp_interface.py 2017-01-16 17:03:59.000000000 +0000 @@ -620,7 +620,7 @@ commands = [] command = None - for key, value in delta.iteritems(): + for key, value in delta.items(): if key == 'oif_source' or found_both or found_prefix: pass elif key == 'oif_prefix': @@ -665,7 +665,7 @@ group_timeout=group_timeout, report_llg=report_llg, immediate_leave=immediate_leave) - default = dict((param, value) for (param, value) in args.iteritems() + default = dict((param, value) for (param, value) in args.items() if value is not None) return default @@ -674,7 +674,7 @@ def config_default_igmp_interface(existing, delta, found_both, found_prefix): commands = [] proposed = get_igmp_interface_defaults() - delta = dict(set(proposed.iteritems()).difference(existing.iteritems())) + delta = dict(set(proposed.items()).difference(existing.items())) if delta: command = config_igmp_interface(delta, found_both, found_prefix) @@ -810,7 +810,7 @@ changed = False commands = [] - proposed = dict((k, v) for k, v in module.params.iteritems() + proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args) CANNOT_ABSENT = ['version', 'startup_query_interval', @@ -827,7 +827,7 @@ 'state=absent') # delta check for all params except oif_prefix and oif_source - delta = dict(set(proposed.iteritems()).difference(existing.iteritems())) + delta = dict(set(proposed.items()).difference(existing.items())) # now check to see there is a delta for prefix and source command option found_both = False diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_igmp.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_igmp.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_igmp.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_igmp.py 2017-01-16 17:03:59.000000000 +0000 @@ -289,11 +289,11 @@ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) if module.params['state'] == 'default': - for key, value in proposed_commands.iteritems(): + for key, value in proposed_commands.items(): if existing_commands.get(key): commands.append('no {0}'.format(key)) else: - for key, value in proposed_commands.iteritems(): + for key, value in proposed_commands.items(): if value is True: commands.append(key) else: @@ -349,7 +349,7 @@ existing = invoke('get_existing', module, args) end_state = existing - proposed = dict((k, v) for k, v in module.params.iteritems() + proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args) proposed_args = proposed.copy() diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_igmp_snooping.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_igmp_snooping.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_igmp_snooping.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_igmp_snooping.py 2017-01-16 17:03:59.000000000 +0000 @@ -440,7 +440,7 @@ commands = [] command = None - for key, value in delta.iteritems(): + for key, value in delta.items(): if value: if default and key == 'group_timeout': if existing.get(key): @@ -468,7 +468,7 @@ report_supp=report_supp, v3_report_supp=v3_report_supp, group_timeout=group_timeout) - default = dict((param, value) for (param, value) in args.iteritems() + default = dict((param, value) for (param, value) in args.items() if value is not None) return default @@ -497,7 +497,7 @@ report_supp=report_supp, v3_report_supp=v3_report_supp, group_timeout=group_timeout) - proposed = dict((param, value) for (param, value) in args.iteritems() + proposed = dict((param, value) for (param, value) in args.items() if value is not None) existing = get_igmp_snooping(module) @@ -507,7 +507,7 @@ commands = [] if state == 'present': delta = dict( - set(proposed.iteritems()).difference(existing.iteritems()) + set(proposed.items()).difference(existing.items()) ) if delta: command = config_igmp_snooping(delta, existing) @@ -516,7 +516,7 @@ elif state == 'default': proposed = get_igmp_snooping_defaults() delta = dict( - set(proposed.iteritems()).difference(existing.iteritems()) + set(proposed.items()).difference(existing.items()) ) if delta: command = config_igmp_snooping(delta, existing, default=True) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_interface_ospf.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_interface_ospf.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_interface_ospf.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_interface_ospf.py 2017-01-16 17:03:59.000000000 +0000 @@ -506,7 +506,7 @@ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) - for key, value in proposed_commands.iteritems(): + for key, value in proposed_commands.items(): if value is True: commands.append(key) elif value is False: @@ -535,7 +535,7 @@ parents = ['interface {0}'.format(module.params['interface'].capitalize())] existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) - for key, value in existing_commands.iteritems(): + for key, value in existing_commands.items(): if value: if key.startswith('ip ospf message-digest-key'): if 'options' not in key: @@ -631,11 +631,11 @@ existing = invoke('get_existing', module, args) end_state = existing - proposed_args = dict((k, v) for k, v in module.params.iteritems() + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) proposed = {} - for key, value in proposed_args.iteritems(): + for key, value in proposed_args.items(): if key != 'interface': if str(value).lower() == 'true': value = True diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_interface.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_interface.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_interface.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_interface.py 2017-01-16 17:03:59.000000000 +0000 @@ -882,8 +882,8 @@ existing) commands.append(cmds) else: - delta = dict(set(proposed.iteritems()).difference( - existing.iteritems())) + delta = dict(set(proposed.items()).difference( + existing.items())) if delta: cmds = get_interface_config_commands(delta, normalized_interface, diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_ip_interface.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_ip_interface.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_ip_interface.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_ip_interface.py 2017-01-16 17:03:59.000000000 +0000 @@ -641,7 +641,7 @@ existing, address_list = get_ip_interface(interface, version, module) args = dict(addr=addr, mask=mask, interface=interface) - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) + proposed = dict((k, v) for k, v in args.items() if v is not None) commands = [] changed = False end_state = existing diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_mtu.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_mtu.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_mtu.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_mtu.py 2017-01-16 17:03:59.000000000 +0000 @@ -405,7 +405,7 @@ } commands = [] - for param, value in delta.iteritems(): + for param, value in delta.items(): command = CONFIG_ARGS.get(param, 'DNE').format(**delta) if command and command != 'DNE': commands.append(command) @@ -422,7 +422,7 @@ 'sysmtu': 'no system jumbomtu {sysmtu}', } commands = [] - for param, value in delta.iteritems(): + for param, value in delta.items(): command = CONFIG_ARGS.get(param, 'DNE').format(**delta) if command and command != 'DNE': commands.append(command) @@ -546,8 +546,8 @@ 'number between 576 and 9216') args = dict(mtu=mtu, sysmtu=sysmtu) - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) - delta = dict(set(proposed.iteritems()).difference(existing.iteritems())) + proposed = dict((k, v) for k, v in args.items() if v is not None) + delta = dict(set(proposed.items()).difference(existing.items())) changed = False end_state = existing @@ -559,7 +559,7 @@ commands.append(command) elif state == 'absent': - common = set(proposed.iteritems()).intersection(existing.iteritems()) + common = set(proposed.items()).intersection(existing.items()) if common: command = get_commands_remove_mtu(dict(common), interface) commands.append(command) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_ntp_auth.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_ntp_auth.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_ntp_auth.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_ntp_auth.py 2017-01-16 17:03:59.000000000 +0000 @@ -512,12 +512,12 @@ authentication=authentication) changed = False - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) + proposed = dict((k, v) for k, v in args.items() if v is not None) existing = get_ntp_auth_info(key_id, module) end_state = existing - delta = dict(set(proposed.iteritems()).difference(existing.iteritems())) + delta = dict(set(proposed.items()).difference(existing.items())) commands = [] if state == 'present': @@ -547,7 +547,7 @@ clie = get_exception() module.fail_json(msg=str(clie) + ": " + cmds) end_state = get_ntp_auth_info(key_id, module) - delta = dict(set(end_state.iteritems()).difference(existing.iteritems())) + delta = dict(set(end_state.items()).difference(existing.items())) if delta or (len(existing) != len(end_state)): changed = True diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_ntp_options.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_ntp_options.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_ntp_options.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_ntp_options.py 2017-01-16 17:03:59.000000000 +0000 @@ -462,13 +462,13 @@ args = dict(master=master, stratum=stratum, logging=logging) changed = False - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) + proposed = dict((k, v) for k, v in args.items() if v is not None) if master is False: proposed['stratum'] = None stratum = None - delta = dict(set(proposed.iteritems()).difference(existing.iteritems())) + delta = dict(set(proposed.items()).difference(existing.items())) delta_stratum = delta.get('stratum') if delta_stratum: @@ -482,8 +482,8 @@ commands.append(command) elif state == 'absent': if existing: - isection = dict(set(proposed.iteritems()).intersection( - existing.iteritems())) + isection = dict(set(proposed.items()).intersection( + existing.items())) command = config_ntp_options(isection, flip=True) if command: commands.append(command) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_ntp.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_ntp.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_ntp.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_ntp.py 2017-01-16 17:03:59.000000000 +0000 @@ -433,7 +433,7 @@ args = dict(peer_type=peer_type, address=address, prefer=prefer, vrf_name=vrf_name, key_id=key_id) - ntp_peer = dict((k, v) for k, v in args.iteritems()) + ntp_peer = dict((k, v) for k, v in args.items()) ntp_peer_list.append(ntp_peer) except AttributeError: ntp_peer_list = [] @@ -567,7 +567,7 @@ prefer=prefer, vrf_name=vrf_name, source_type=source_type, source=source) - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) + proposed = dict((k, v) for k, v in args.items() if v is not None) existing, peer_server_list = get_ntp_existing(address, peer_type, module) @@ -576,7 +576,7 @@ commands = [] if state == 'present': - delta = dict(set(proposed.iteritems()).difference(existing.iteritems())) + delta = dict(set(proposed.items()).difference(existing.items())) if delta: command = config_ntp(delta, existing) if command: diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_nxapi.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_nxapi.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_nxapi.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_nxapi.py 2017-01-16 17:03:59.000000000 +0000 @@ -173,7 +173,7 @@ def present(module, instance, commands): commands.append('feature nxapi') setters = set() - for key, value in module.argument_spec.iteritems(): + for key, value in module.argument_spec.items(): setter = value.get('setter') or 'set_%s' % key if setter not in setters: setters.add(setter) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_ospf_vrf.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_ospf_vrf.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_ospf_vrf.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_ospf_vrf.py 2017-01-16 17:03:59.000000000 +0000 @@ -426,7 +426,7 @@ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) - for key, value in proposed_commands.iteritems(): + for key, value in proposed_commands.items(): if value is True: commands.append(key) @@ -480,7 +480,7 @@ parents = ['router ospf {0}'.format(module.params['ospf'])] if module.params['vrf'] == 'default': existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) - for key, value in existing_commands.iteritems(): + for key, value in existing_commands.items(): if value: if key == 'timers throttle lsa': command = 'no {0} {1} {2} {3}'.format( @@ -547,11 +547,11 @@ existing = invoke('get_existing', module, args) end_state = existing - proposed_args = dict((k, v) for k, v in module.params.iteritems() + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) proposed = {} - for key, value in proposed_args.iteritems(): + for key, value in proposed_args.items(): if key != 'interface': if str(value).lower() == 'true': value = True diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_overlay_global.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_overlay_global.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_overlay_global.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_overlay_global.py 2017-01-16 17:03:59.000000000 +0000 @@ -305,7 +305,7 @@ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) - for key, value in proposed_commands.iteritems(): + for key, value in proposed_commands.items(): if value == 'default': existing_value = existing_commands.get(key) if existing_value: @@ -381,7 +381,7 @@ existing = invoke('get_existing', module, args) end_state = existing - proposed = dict((k, v) for k, v in module.params.iteritems() + proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args) result = {} diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_pim_interface.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_pim_interface.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_pim_interface.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_pim_interface.py 2017-01-16 17:03:59.000000000 +0000 @@ -655,7 +655,7 @@ if command: commands.append(command) - for k, v in delta.iteritems(): + for k, v in delta.items(): if k in ['dr_prio', 'hello_interval', 'hello_auth_key', 'border', 'sparse']: if v: @@ -720,7 +720,7 @@ hello_interval=hello_interval, hello_auth_key=hello_auth_key) - default = dict((param, value) for (param, value) in args.iteritems() + default = dict((param, value) for (param, value) in args.items() if value is not None) return default @@ -740,7 +740,7 @@ elif not jp_bidir: command = None - for k, v in existing.iteritems(): + for k, v in existing.items(): if k == 'jp_policy_in': if existing.get('jp_policy_in'): if existing.get('jp_type_in') == 'prefix': @@ -777,8 +777,8 @@ # returns a dict defaults = get_pim_interface_defaults() - delta = dict(set(defaults.iteritems()).difference( - existing.iteritems())) + delta = dict(set(defaults.items()).difference( + existing.items())) if delta: # returns a list command = config_pim_interface(delta, existing, @@ -860,7 +860,7 @@ 'neighbor_type', 'neighbor_policy' ] - proposed = dict((k, v) for k, v in module.params.iteritems() + proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args) ''' @@ -873,7 +873,7 @@ if hello_interval: proposed['hello_interval'] = str(proposed['hello_interval'] * 1000) - delta = dict(set(proposed.iteritems()).difference(existing.iteritems())) + delta = dict(set(proposed.items()).difference(existing.items())) if state == 'present': if delta: diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_pim.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_pim.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_pim.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_pim.py 2017-01-16 17:03:59.000000000 +0000 @@ -271,7 +271,7 @@ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) - for key, value in proposed_commands.iteritems(): + for key, value in proposed_commands.items(): command = '{0} {1}'.format(key, value) commands.append(command) @@ -301,7 +301,7 @@ existing = invoke('get_existing', module, args) end_state = existing - proposed = dict((k, v) for k, v in module.params.iteritems() + proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args) result = {} diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_pim_rp_address.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_pim_rp_address.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_pim_rp_address.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_pim_rp_address.py 2017-01-16 17:03:59.000000000 +0000 @@ -371,11 +371,11 @@ existing = invoke('get_existing', module, args) end_state = existing - proposed_args = dict((k, v) for k, v in module.params.iteritems() + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) proposed = {} - for key, value in proposed_args.iteritems(): + for key, value in proposed_args.items(): if str(value).lower() == 'true': value = True elif str(value).lower() == 'false': diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_ping.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_ping.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_ping.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_ping.py 2017-01-16 17:03:59.000000000 +0000 @@ -401,7 +401,7 @@ } ping_command = 'ping {0}'.format(destination) - for command, arg in OPTIONS.iteritems(): + for command, arg in OPTIONS.items(): if arg: ping_command += ' {0} {1}'.format(command, arg) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_portchannel.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_portchannel.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_portchannel.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_portchannel.py 2017-01-16 17:03:59.000000000 +0000 @@ -331,7 +331,7 @@ error=str(clie), commands=commands) except AttributeError: try: - module.config.load_config(commands) + output = module.config.load_config(commands) except NetworkError: clie = get_exception() module.fail_json(msg='Error sending CLI commands', @@ -469,7 +469,7 @@ # Ensure each member have the same mode. modes = set() - for each, value in member_dictionary.iteritems(): + for each, value in member_dictionary.items(): modes.update([value['mode']]) if len(modes) == 1: portchannel['mode'] = value['mode'] @@ -591,7 +591,7 @@ members_to_remove = set(existing_members).difference(proposed_members) members_with_mode_change = [] if members_dict: - for interface, values in members_dict.iteritems(): + for interface, values in members_dict.items(): if (interface in proposed_members and (interface not in members_to_remove)): if values['mode'] != mode: @@ -677,7 +677,7 @@ existing, interface_exist = invoke('get_existing', module, args) end_state = existing - proposed = dict((k, v) for k, v in module.params.iteritems() + proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args) result = {} diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_snmp_community.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_snmp_community.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_snmp_community.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_snmp_community.py 2017-01-16 17:03:59.000000000 +0000 @@ -54,6 +54,26 @@ required: true default: present choices: ['present','absent'] + include_defaults: + description: + - Specify to use or not the complete running configuration + for module operations. + required: false + default: false + choices: ['true','false'] + config: + description: + - Configuration string to be used for module operations. If not + specified, the module will use the current running configuration. + required: false + default: null + save: + description: + - Specify to save the running configuration after + module operations. + required: false + default: false + choices: ['true','false'] ''' EXAMPLES = ''' @@ -251,174 +271,69 @@ # END OF COMMON CODE -def execute_config_command(commands, module): - try: - module.configure(commands) - except ShellError: - clie = get_exception() - module.fail_json(msg='Error sending CLI commands', - error=str(clie), commands=commands) - except AttributeError: - try: - module.config.load_config(commands) - except NetworkError: - clie = get_exception() - module.fail_json(msg='Error sending CLI commands', - error=str(clie), commands=commands) - - -def get_cli_body_ssh(command, response, module): - """Get response for when transport=cli. This is kind of a hack and mainly - needed because these modules were originally written for NX-API. And - not every command supports "| json" when using cli/ssh. As such, we assume - if | json returns an XML string, it is a valid command, but that the - resource doesn't exist yet. Instead, the output will be a raw string - when issuing commands containing 'show run'. - """ - if 'xml' in response[0]: - body = [] - elif 'show run' in command: - body = response - else: +def get_value(module, arg, config): + existing = {} + if arg == 'group': + command = '.*snmp-server community {0} group (?P\S+).*'.format( + module.params['community']) try: - body = [json.loads(response[0])] - except ValueError: - module.fail_json(msg='Command does not support JSON output', - command=command) - return body - - -def execute_show(cmds, module, command_type=None): - command_type_map = { - 'cli_show': 'json', - 'cli_show_ascii': 'text' - } - - try: - if command_type: - response = module.execute(cmds, command_type=command_type) - else: - response = module.execute(cmds) - except ShellError: - clie = get_exception() - module.fail_json(msg='Error sending {0}'.format(cmds), - error=str(clie)) - except AttributeError: + match_group = re.match(command, config, re.DOTALL) + group = match_group.groupdict()['group'] + existing['group'] = group + existing['community'] = module.params['community'] + except AttributeError: + existing['group'] = '' + elif arg == 'acl': + command = '.*snmp-server community {0} use-acl (?P\S+).*'.format( + module.params['community']) try: - if command_type: - command_type = command_type_map.get(command_type) - module.cli.add_commands(cmds, output=command_type) - response = module.cli.run_commands() - else: - module.cli.add_commands(cmds, raw=True) - response = module.cli.run_commands() - except NetworkError: - clie = get_exception() - module.fail_json(msg='Error sending {0}'.format(cmds), - error=str(clie)) - return response - - -def execute_show_command(command, module, command_type='cli_show'): - if module.params['transport'] == 'cli': - if 'show run' not in command: - command += ' | json' - cmds = [command] - response = execute_show(cmds, module) - body = get_cli_body_ssh(command, response, module) - elif module.params['transport'] == 'nxapi': - cmds = [command] - body = execute_show(cmds, module, command_type=command_type) - - return body - - -def apply_key_map(key_map, table): - new_dict = {} - for key, value in table.items(): - new_key = key_map.get(key) - if new_key: - value = table.get(key) - if value: - new_dict[new_key] = str(value) - else: - new_dict[new_key] = value - return new_dict - - -def flatten_list(command_lists): - flat_command_list = [] - for command in command_lists: - if isinstance(command, list): - flat_command_list.extend(command) - else: - flat_command_list.append(command) - return flat_command_list - - -def get_snmp_groups(module): - command = 'show snmp group' - data = execute_show_command(command, module)[0] - - group_list = [] - - try: - group_table = data['TABLE_role']['ROW_role'] - for group in group_table: - group_list.append(group['role_name']) - except (KeyError, AttributeError): - return group_list - - return group_list - - -def get_snmp_community(module, find_filter=None): - command = 'show snmp community' - data = execute_show_command(command, module)[0] - - community_dict = {} - - community_map = { - 'grouporaccess': 'group', - 'aclfilter': 'acl' - } - - try: - community_table = data['TABLE_snmp_community']['ROW_snmp_community'] - for each in community_table: - community = apply_key_map(community_map, each) - key = each['community_name'] - community_dict[key] = community - except (KeyError, AttributeError): - return community_dict + match_acl = re.match(command, config, re.DOTALL) + acl = match_acl.groupdict()['acl'] + existing['acl'] = acl + existing['community'] = module.params['community'] + except AttributeError: + existing['acl'] = '' + return existing - if find_filter: - find = community_dict.get(find_filter, None) - if find_filter is None or find is None: - return {} - else: - fix_find = {} - for (key, value) in find.iteritems(): - if isinstance(value, str): - fix_find[key] = value.strip() - else: - fix_find[key] = value - return fix_find +def get_existing(module): + existing = {} + config = str(get_config(module)) + for arg in ['group', 'acl']: + existing.update(get_value(module, arg, config)) + if existing.get('group') or existing.get('acl'): + existing['community'] = module.params['community'] + existing = dict((key, value) for key, value in + existing.items() if value) + return existing + + +def state_absent(module, existing, proposed, candidate): + commands = ['no snmp-server community {0}'.format( + module.params['community'])] + if commands: + candidate.add(commands, parents=[]) -def config_snmp_community(delta, community): +def state_present(module, existing, proposed, candidate): CMDS = { - 'group': 'snmp-server community {0} group {group}', - 'acl': 'snmp-server community {0} use-acl {acl}' + 'group': 'snmp-server community {0} group {1}', + 'acl': 'snmp-server community {0} use-acl {1}' } commands = [] - for k, v in delta.iteritems(): - cmd = CMDS.get(k).format(community, **delta) - if cmd: - commands.append(cmd) - cmd = None - return commands + community = module.params['community'] + for key in ['group', 'acl']: + if proposed.get(key): + command = CMDS[key].format(community, proposed[key]) + commands.append(command) + if commands: + candidate.add(commands, parents=[]) + + +def invoke(name, *args, **kwargs): + func = globals().get(name) + if func: + return func(*args, **kwargs) def main(): @@ -428,6 +343,9 @@ group=dict(type='str'), acl=dict(type='str'), state=dict(choices=['absent', 'present'], default='present'), + include_defaults=dict(default=False), + config=dict(), + save=dict(type='bool', default=False) ) module = get_network_module(argument_spec=argument_spec, required_one_of=[['access', 'group']], @@ -436,8 +354,6 @@ access = module.params['access'] group = module.params['group'] - community = module.params['community'] - acl = module.params['acl'] state = module.params['state'] if access: @@ -446,49 +362,35 @@ elif access == 'rw': group = 'network-admin' - # group check - ensure group being configured exists on the device - configured_groups = get_snmp_groups(module) - - if group not in configured_groups: - module.fail_json(msg="group not on switch." - "please add before moving forward") - - existing = get_snmp_community(module, community) - args = dict(group=group, acl=acl) - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) - delta = dict(set(proposed.iteritems()).difference(existing.iteritems())) - - changed = False + args = ['community', 'acl'] + proposed = dict((k, v) for k, v in module.params.items() + if v is not None and k in args) + proposed['group'] = group + existing = get_existing(module) end_state = existing - commands = [] - if state == 'absent': - if existing: - command = "no snmp-server community {0}".format(community) - commands.append(command) - cmds = flatten_list(commands) - elif state == 'present': - if delta: - command = config_snmp_community(dict(delta), community) - commands.append(command) - cmds = flatten_list(commands) + result = {} + if state == 'present' or (state == 'absent' and existing): + candidate = CustomNetworkConfig(indent=3) + invoke('state_%s' % state, module, existing, proposed, candidate) - if cmds: - if module.check_mode: - module.exit_json(changed=True, commands=cmds) - else: - changed = True - execute_config_command(cmds, module) - end_state = get_snmp_community(module, community) - - results = {} - results['proposed'] = proposed - results['existing'] = existing - results['end_state'] = end_state - results['updates'] = cmds - results['changed'] = changed + try: + response = load_config(module, candidate) + result.update(response) + except ShellError: + exc = get_exception() + module.fail_json(msg=str(exc)) + else: + result['updates'] = [] + + result['connected'] = module.connected + if module._verbosity > 0: + end_state = invoke('get_existing', module) + result['end_state'] = end_state + result['existing'] = existing + result['proposed'] = proposed - module.exit_json(**results) + module.exit_json(**result) if __name__ == '__main__': diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_snmp_host.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_snmp_host.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_snmp_host.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_snmp_host.py 2017-01-16 17:03:59.000000000 +0000 @@ -426,7 +426,7 @@ if find: fix_find = {} - for (key, value) in find.iteritems(): + for (key, value) in find.items(): if isinstance(value, str): fix_find[key] = value.strip() else: @@ -501,7 +501,7 @@ 'src_intf': 'snmp-server host {0} source-interface {src_intf}' } - for key, value in delta.iteritems(): + for key, value in delta.items(): if key in ['vrf_filter', 'vrf', 'udp', 'src_intf']: command = CMDS.get(key, None) if command: @@ -591,9 +591,9 @@ snmp_type=snmp_type ) - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) + proposed = dict((k, v) for k, v in args.items() if v is not None) - delta = dict(set(proposed.iteritems()).difference(existing.iteritems())) + delta = dict(set(proposed.items()).difference(existing.items())) changed = False commands = [] diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_snmp_user.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_snmp_user.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_snmp_user.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_snmp_user.py 2017-01-16 17:03:59.000000000 +0000 @@ -500,7 +500,7 @@ args = dict(user=user, pwd=pwd, group=group, privacy=privacy, encrypt=encrypt, authentication=authentication) - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) + proposed = dict((k, v) for k, v in args.items() if v is not None) if not existing: if encrypt: @@ -516,7 +516,7 @@ proposed['encrypt'] = 'aes-128' delta = dict( - set(proposed.iteritems()).difference(existing.iteritems())) + set(proposed.items()).difference(existing.items())) if delta.get('pwd'): delta['authentication'] = authentication diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_static_route.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_static_route.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_static_route.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_static_route.py 2017-01-16 17:03:59.000000000 +0000 @@ -439,7 +439,7 @@ end_state = existing args = ['route_name', 'vrf', 'pref', 'tag', 'next_hop', 'prefix'] - proposed = dict((k, v) for k, v in module.params.iteritems() if v is not None and k in args) + proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args) if state == 'present' or (state == 'absent' and existing): candidate = CustomNetworkConfig(indent=3) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_switchport.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_switchport.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_switchport.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_switchport.py 2017-01-16 17:03:59.000000000 +0000 @@ -697,7 +697,7 @@ native_vlan=native_vlan, trunk_vlans=trunk_vlans, trunk_allowed_vlans=trunk_allowed_vlans) - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) + proposed = dict((k, v) for k, v in args.items() if v is not None) interface = interface.lower() diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_udld_interface.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_udld_interface.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_udld_interface.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_udld_interface.py 2017-01-16 17:03:59.000000000 +0000 @@ -471,7 +471,7 @@ existing = get_udld_interface(module, interface) end_state = existing - delta = dict(set(proposed.iteritems()).difference(existing.iteritems())) + delta = dict(set(proposed.items()).difference(existing.items())) changed = False commands = [] @@ -481,7 +481,7 @@ module, existing) commands.append(command) elif state == 'absent': - common = set(proposed.iteritems()).intersection(existing.iteritems()) + common = set(proposed.items()).intersection(existing.items()) if common: command = get_commands_remove_udld_interface( dict(common), interface, module, existing diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_udld.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_udld.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_udld.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_udld.py 2017-01-16 17:03:59.000000000 +0000 @@ -374,7 +374,7 @@ 'msg_time': 'udld message-time {msg_time}' } commands = [] - for param, value in delta.iteritems(): + for param, value in delta.items(): if param == 'aggressive': if value == 'enabled': command = 'udld aggressive' @@ -398,7 +398,7 @@ 'msg_time': 'no udld message-time {msg_time}', } commands = [] - for param, value in delta.iteritems(): + for param, value in delta.items(): command = config_args.get(param, 'DNE').format(**delta) if command and command != 'DNE': commands.append(command) @@ -454,12 +454,12 @@ 'between 7 and 90') args = dict(aggressive=aggressive, msg_time=msg_time, reset=reset) - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) + proposed = dict((k, v) for k, v in args.items() if v is not None) existing = get_udld_global(module) end_state = existing - delta = set(proposed.iteritems()).difference(existing.iteritems()) + delta = set(proposed.items()).difference(existing.items()) changed = False commands = [] @@ -469,7 +469,7 @@ commands.append(command) elif state == 'absent': - common = set(proposed.iteritems()).intersection(existing.iteritems()) + common = set(proposed.items()).intersection(existing.items()) if common: command = get_commands_remove_udld_global(dict(common)) commands.append(command) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vlan.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vlan.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vlan.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vlan.py 2017-01-16 17:03:59.000000000 +0000 @@ -360,7 +360,7 @@ commands = [] - for param, value in vlan.iteritems(): + for param, value in vlan.items(): if param == 'mapped_vni' and value == 'default': command = 'no vn-segment' else: @@ -566,7 +566,7 @@ args = dict(name=name, vlan_state=vlan_state, admin_state=admin_state, mapped_vni=mapped_vni) - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) + proposed = dict((k, v) for k, v in args.items() if v is not None) proposed_vlans_list = numerical_sort(vlan_range_to_list( vlan_id or vlan_range)) @@ -597,7 +597,7 @@ proposed.get('mapped_vni') == 'default'): proposed.pop('mapped_vni') delta = dict(set( - proposed.iteritems()).difference(existing.iteritems())) + proposed.items()).difference(existing.items())) if delta or not existing: commands = get_vlan_config_commands(delta, vlan_id) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vpc_interface.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vpc_interface.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vpc_interface.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vpc_interface.py 2017-01-16 17:03:59.000000000 +0000 @@ -440,7 +440,7 @@ mapping = get_existing_portchannel_to_vpc_mappings(module) - for existing_vpc, port_channel in mapping.iteritems(): + for existing_vpc, port_channel in mapping.items(): port_ch = str(port_channel[2:]) if port_ch == portchannel: pc = port_ch @@ -505,7 +505,7 @@ "before trying to assign it here. ", existing_portchannel=mapping[vpc]) - for vpcid, existing_pc in mapping.iteritems(): + for vpcid, existing_pc in mapping.items(): if portchannel == existing_pc.strip('Po') and vpcid != vpc: module.fail_json(msg="This portchannel already has another" " VPC configured. Remove it first " @@ -532,13 +532,13 @@ config_value = 'peer-link' - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) + proposed = dict((k, v) for k, v in args.items() if v is not None) existing = get_portchannel_vpc_config(module, portchannel) end_state = existing commands = [] if state == 'present': - delta = dict(set(proposed.iteritems()).difference(existing.iteritems())) + delta = dict(set(proposed.items()).difference(existing.items())) if delta: command = get_commands_to_config_vpc_interface( portchannel, diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vpc.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vpc.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vpc.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vpc.py 2017-01-16 17:03:59.000000000 +0000 @@ -536,7 +536,7 @@ 'auto_recovery': '{auto_recovery} auto-recovery', } - for param, value in vpc.iteritems(): + for param, value in vpc.items(): command = CONFIG_ARGS.get(param, 'DNE').format(**vpc) if command and command != 'DNE': commands.append(command.strip()) @@ -608,14 +608,14 @@ module.fail_json(msg='The VRF you are trying to use for the peer ' 'keepalive link is not on device yet. Add it' ' first, please.') - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) + proposed = dict((k, v) for k, v in args.items() if v is not None) changed = False existing = get_vpc(module) end_state = existing commands = [] if state == 'present': - delta = set(proposed.iteritems()).difference(existing.iteritems()) + delta = set(proposed.items()).difference(existing.items()) if delta: command = get_commands_to_config_vpc(module, delta, domain, existing) commands.append(command) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vrf_af.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vrf_af.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vrf_af.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vrf_af.py 2017-01-16 17:03:59.000000000 +0000 @@ -331,7 +331,7 @@ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) - for key, value in proposed_commands.iteritems(): + for key, value in proposed_commands.items(): if value is True: commands.append(key) @@ -388,11 +388,11 @@ existing = invoke('get_existing', module, args) end_state = existing - proposed_args = dict((k, v) for k, v in module.params.iteritems() + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) proposed = {} - for key, value in proposed_args.iteritems(): + for key, value in proposed_args.items(): if key != 'interface': if str(value).lower() == 'default': value = PARAM_TO_DEFAULT_KEYMAP.get(key) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vrf.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vrf.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vrf.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vrf.py 2017-01-16 17:03:59.000000000 +0000 @@ -361,7 +361,7 @@ def get_commands_to_config_vrf(delta, vrf): commands = [] - for param, value in delta.iteritems(): + for param, value in delta.items(): command = '' if param == 'description': command = 'description {0}'.format(value) @@ -474,7 +474,7 @@ end_state = existing changed = False - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) + proposed = dict((k, v) for k, v in args.items() if v is not None) """Since 'admin_state' is either 'Up' or 'Down' from outputs, we use the following to make sure right letter case is used so that delta @@ -483,7 +483,7 @@ if existing['admin_state'].lower() == admin_state: proposed['admin_state'] = existing['admin_state'] - delta = dict(set(proposed.iteritems()).difference(existing.iteritems())) + delta = dict(set(proposed.items()).difference(existing.items())) changed = False end_state = existing commands = [] diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vrrp.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vrrp.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vrrp.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vrrp.py 2017-01-16 17:03:59.000000000 +0000 @@ -597,7 +597,7 @@ vip=vip, authentication=authentication, admin_state=admin_state) - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) + proposed = dict((k, v) for k, v in args.items() if v is not None) existing = get_existing_vrrp(interface, group, module, name) changed = False @@ -606,7 +606,7 @@ if state == 'present': delta = dict( - set(proposed.iteritems()).difference(existing.iteritems())) + set(proposed.items()).difference(existing.items())) if delta: command = get_commands_config_vrrp(delta, group) commands.append(command) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vtp_domain.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vtp_domain.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vtp_domain.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vtp_domain.py 2017-01-16 17:03:59.000000000 +0000 @@ -385,8 +385,8 @@ args = dict(domain=domain) changed = False - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) - delta = dict(set(proposed.iteritems()).difference(existing.iteritems())) + proposed = dict((k, v) for k, v in args.items() if v is not None) + delta = dict(set(proposed.items()).difference(existing.items())) commands = [] if delta: diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vtp_password.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vtp_password.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vtp_password.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vtp_password.py 2017-01-16 17:03:59.000000000 +0000 @@ -280,7 +280,7 @@ """ if 'xml' in response[0] or response[0] == '\n': body = [] - elif 'show run' in command: + elif 'show run' in command or 'status' in command: body = response else: try: @@ -324,7 +324,7 @@ def execute_show_command(command, module, command_type='cli_show'): if module.params['transport'] == 'cli': - if 'show run' not in command: + if 'show run' not in command and 'status' not in command: command += ' | json' cmds = [command] response = execute_show(cmds, module) @@ -369,7 +369,6 @@ if body: version_regex = '.*VTP version running\s+:\s+(?P\d).*' domain_regex = '.*VTP Domain Name\s+:\s+(?P\S+).*' - try: match_version = re.match(version_regex, body, re.DOTALL) version = match_version.groupdict()['version'] @@ -418,8 +417,8 @@ args = dict(vtp_password=vtp_password) changed = False - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) - delta = dict(set(proposed.iteritems()).difference(existing.iteritems())) + proposed = dict((k, v) for k, v in args.items() if v is not None) + delta = dict(set(proposed.items()).difference(existing.items())) commands = [] if state == 'absent': diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vtp_version.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vtp_version.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vtp_version.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vtp_version.py 2017-01-16 17:03:59.000000000 +0000 @@ -380,8 +380,8 @@ args = dict(version=version) changed = False - proposed = dict((k, v) for k, v in args.iteritems() if v is not None) - delta = dict(set(proposed.iteritems()).difference(existing.iteritems())) + proposed = dict((k, v) for k, v in args.items() if v is not None) + delta = dict(set(proposed.items()).difference(existing.items())) commands = [] if delta: diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vxlan_vtep.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vxlan_vtep.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vxlan_vtep.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vxlan_vtep.py 2017-01-16 17:03:59.000000000 +0000 @@ -405,7 +405,7 @@ commands = list() proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) - for key, value in proposed_commands.iteritems(): + for key, value in proposed_commands.items(): if value is True: commands.append(key) @@ -471,11 +471,11 @@ existing = invoke('get_existing', module, args) end_state = existing - proposed_args = dict((k, v) for k, v in module.params.iteritems() + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) proposed = {} - for key, value in proposed_args.iteritems(): + for key, value in proposed_args.items(): if key != 'interface': if str(value).lower() == 'true': value = True diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vxlan_vtep_vni.py ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vxlan_vtep_vni.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/nxos/nxos_vxlan_vtep_vni.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/nxos/nxos_vxlan_vtep_vni.py 2017-01-16 17:03:59.000000000 +0000 @@ -413,7 +413,7 @@ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) - for key, value in proposed_commands.iteritems(): + for key, value in proposed_commands.items(): if key == 'associate-vrf': command = 'member vni {0} {1}'.format(module.params['vni'], key) @@ -528,11 +528,11 @@ existing, interface_exist = invoke('get_existing', module, args) end_state = existing - proposed_args = dict((k, v) for k, v in module.params.iteritems() + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) proposed = {} - for key, value in proposed_args.iteritems(): + for key, value in proposed_args.items(): if key != 'interface': if str(value).lower() == 'default': value = PARAM_TO_DEFAULT_KEYMAP.get(key) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/network/sros/sros_rollback.py ansible-2.2.1.0/lib/ansible/modules/core/network/sros/sros_rollback.py --- ansible-2.2.0.0/lib/ansible/modules/core/network/sros/sros_rollback.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/network/sros/sros_rollback.py 2017-01-16 17:03:59.000000000 +0000 @@ -121,7 +121,7 @@ def present(module, commands): setters = set() - for key, value in module.argument_spec.iteritems(): + for key, value in module.argument_spec.items(): if module.params[key] is not None: setter = value.get('setter') or 'set_%s' % key if setter not in setters: diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/packaging/language/easy_install.py ansible-2.2.1.0/lib/ansible/modules/core/packaging/language/easy_install.py --- ansible-2.2.0.0/lib/ansible/modules/core/packaging/language/easy_install.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/packaging/language/easy_install.py 2017-01-16 17:03:59.000000000 +0000 @@ -100,6 +100,8 @@ executable_arguments = executable_arguments + ['--dry-run'] cmd = '%s %s %s' % (easy_install, ' '.join(executable_arguments), name) rc, status_stdout, status_stderr = module.run_command(cmd) + if rc: + module.fail_json(msg=status_stderr) return not ('Reading' in status_stdout or 'Downloading' in status_stdout) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/packaging/language/pip.py ansible-2.2.1.0/lib/ansible/modules/core/packaging/language/pip.py --- ansible-2.2.0.0/lib/ansible/modules/core/packaging/language/pip.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/packaging/language/pip.py 2017-01-16 17:03:59.000000000 +0000 @@ -190,7 +190,7 @@ import os import sys -from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import AnsibleModule, is_executable from ansible.module_utils._text import to_native from ansible.module_utils.six import PY3 @@ -263,17 +263,14 @@ def _get_pip(module, env=None, executable=None): - # On Debian and Ubuntu, pip is pip. - # On Fedora18 and up, pip is python-pip. - # On Fedora17 and below, CentOS and RedHat 6 and 5, pip is pip-python. - # On Fedora, CentOS, and RedHat, the exception is in the virtualenv. - # There, pip is just pip. - # On python 3.4, pip should be default, cf PEP 453 - # By default, it will try to use pip required for the current python + # Older pip only installed under the "/usr/bin/pip" name. Many Linux + # distros install it there. + # By default, we try to use pip required for the current python # interpreter, so people can use pip to install modules dependencies - candidate_pip_basenames = ['pip2', 'pip', 'python-pip', 'pip-python'] + candidate_pip_basenames = ('pip2', 'pip') if PY3: - candidate_pip_basenames = ['pip3'] + # pip under python3 installs the "/usr/bin/pip3" name + candidate_pip_basenames = ('pip3',) pip = None if executable is not None: @@ -282,22 +279,39 @@ pip = executable else: # If you define your own executable that executable should be the only candidate. - candidate_pip_basenames = [executable] + # As noted in the docs, executable doesn't work with virtualenvs. + candidate_pip_basenames = (executable,) + if pip is None: if env is None: opt_dirs = [] + for basename in candidate_pip_basenames: + pip = module.get_bin_path(basename, False, opt_dirs) + if pip is not None: + break + else: + # For-else: Means that we did not break out of the loop + # (therefore, that pip was not found) + module.fail_json(msg='Unable to find any of %s to use. pip' + ' needs to be installed.' % ', '.join(candidate_pip_basenames)) else: - # Try pip with the virtualenv directory first. - opt_dirs = ['%s/bin' % env] - for basename in candidate_pip_basenames: - pip = module.get_bin_path(basename, False, opt_dirs) - if pip is not None: - break - # pip should have been found by now. The final call to get_bin_path will - # trigger fail_json. - if pip is None: - basename = candidate_pip_basenames[0] - pip = module.get_bin_path(basename, True, opt_dirs) + # If we're using a virtualenv we must use the pip from the + # virtualenv + venv_dir = os.path.join(env, 'bin') + candidate_pip_basenames = (candidate_pip_basenames[0], 'pip') + for basename in candidate_pip_basenames: + candidate = os.path.join(venv_dir, basename) + if os.path.exists(candidate) and is_executable(candidate): + pip = candidate + break + else: + # For-else: Means that we did not break out of the loop + # (therefore, that pip was not found) + module.fail_json(msg='Unable to find pip in the virtualenv,' + ' %s, under any of these names: %s. Make sure pip is' + ' present in the virtualenv.' % (env, + ', '.join(candidate_pip_basenames))) + return pip diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/packaging/os/apt.py ansible-2.2.1.0/lib/ansible/modules/core/packaging/os/apt.py --- ansible-2.2.0.0/lib/ansible/modules/core/packaging/os/apt.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/packaging/os/apt.py 2017-01-16 17:03:59.000000000 +0000 @@ -885,12 +885,6 @@ retvals['cache_updated'] = updated_cache # Store when the update time was last retvals['cache_update_time'] = updated_cache_time - # If the cache was updated and the general state change was set to - # False make sure that the change in cache state is accurately - # updated by setting the general changed state to the same as - # the cache state. - if updated_cache and not retvals['changed']: - retvals['changed'] = updated_cache if success: module.exit_json(**retvals) diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/packaging/os/rhn_register.py ansible-2.2.1.0/lib/ansible/modules/core/packaging/os/rhn_register.py --- ansible-2.2.0.0/lib/ansible/modules/core/packaging/os/rhn_register.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/packaging/os/rhn_register.py 2017-01-16 17:03:59.000000000 +0000 @@ -147,9 +147,10 @@ # configuration. Yeah, I know this should be subclassed ... but, oh # well def get_option_default(self, key, default=''): - # ignore pep8 W601 errors for this line - # setting this to use 'in' does not work in the rhn library - if self.has_key(key): + # the class in rhn-client-tools that this comes from didn't + # implement __contains__() until 2.5.x. That's why we check if + # the key is present in the dictionary that is the actual storage + if key in self.dict: return self[key] else: return default diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/packaging/os/yum.py ansible-2.2.1.0/lib/ansible/modules/core/packaging/os/yum.py --- ansible-2.2.0.0/lib/ansible/modules/core/packaging/os/yum.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/packaging/os/yum.py 2017-01-16 17:03:59.000000000 +0000 @@ -800,6 +800,8 @@ res['results'].append('Nothing to do here, all packages are up to date') return res elif rc == 100: + # remove incorrect new lines in longer columns in output from yum check-update + out=re.sub('\n\W+', ' ', out) available_updates = out.split('\n') # build update dictionary for line in available_updates: diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/source_control/git.py ansible-2.2.1.0/lib/ansible/modules/core/source_control/git.py --- ansible-2.2.0.0/lib/ansible/modules/core/source_control/git.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/source_control/git.py 2017-01-16 17:03:59.000000000 +0000 @@ -297,7 +297,7 @@ except (IOError, OSError): fd, wrapper_path = tempfile.mkstemp() fh = os.fdopen(fd, 'w+b') - template = """#!/bin/sh + template = b("""#!/bin/sh if [ -z "$GIT_SSH_OPTS" ]; then BASEOPTS="" else @@ -309,7 +309,7 @@ else ssh -i "$GIT_KEY" $BASEOPTS "$@" fi -""" +""") fh.write(template) fh.close() st = os.stat(wrapper_path) @@ -871,7 +871,7 @@ ssh_opts = module.params['ssh_opts'] umask = module.params['umask'] - result = dict( warnings=list() ) + result = dict(changed = False, warnings=list()) # evaluate and set the umask before doing anything else if umask is not None: @@ -951,7 +951,7 @@ # this does no checking that the repo is the actual repo # requested. result['before'] = get_version(module, git_path, dest) - result.update(changed=False, after=result['before']) + result.update(after=result['before']) module.exit_json(**result) else: # else do a pull @@ -964,6 +964,7 @@ # if force and in non-check mode, do a reset if not module.check_mode: reset(git_path, module, dest) + result.update(changed=True, msg='Local modifications exist.') # exit if already at desired sha version if module.check_mode: @@ -974,27 +975,20 @@ if remote_url_changed: result.update(remote_url_changed=True) - if need_fetch: - if module.check_mode: - remote_head = get_remote_head(git_path, module, dest, version, remote, bare) - result.update(changed=(result['before'] != remote_head), after=remote_head) - # FIXME: This diff should fail since the new remote_head is not fetched yet?! - if module._diff: - diff = get_diff(module, git_path, dest, repo, remote, depth, bare, result['before'], result['after']) - if diff: - result['diff'] = diff - module.exit_json(**result) - else: - fetch(git_path, module, repo, dest, version, remote, depth, bare, refspec, git_version_used) + if module.check_mode: + remote_head = get_remote_head(git_path, module, dest, version, remote, bare) + result.update(changed=(result['before'] != remote_head), after=remote_head) + # FIXME: This diff should fail since the new remote_head is not fetched yet?! + if module._diff: + diff = get_diff(module, git_path, dest, repo, remote, depth, bare, result['before'], result['after']) + if diff: + result['diff'] = diff + module.exit_json(**result) + else: + fetch(git_path, module, repo, dest, version, remote, depth, bare, refspec, git_version_used) result['after'] = get_version(module, git_path, dest) - if result['before'] == result['after']: - if local_mods: - result.update(changed=True, after=remote_head, msg='Local modifications exist') - # no diff, since the repo didn't change - module.exit_json(**result) - # switch to version specified regardless of whether # we got new revisions from the repository if not bare: @@ -1017,7 +1011,6 @@ # determine if we changed anything result['after'] = get_version(module, git_path, dest) - result.update(changed=False) if result['before'] != result['after'] or local_mods or submodules_updated or remote_url_changed: result.update(changed=True) if module._diff: diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/system/authorized_key.py ansible-2.2.1.0/lib/ansible/modules/core/system/authorized_key.py --- ansible-2.2.0.0/lib/ansible/modules/core/system/authorized_key.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/system/authorized_key.py 2017-01-16 17:03:59.000000000 +0000 @@ -207,16 +207,16 @@ yield key, self[key][indexes[key]] def iteritems(self): - return self._item_generator() + raise NotImplementedError("Do not use this as it's not available on py3") def items(self): - return list(self.iteritems()) + return list(self._item_generator()) def itervalues(self): - return (item[1] for item in self.iteritems()) + raise NotImplementedError("Do not use this as it's not available on py3") def values(self): - return list(self.itervalues()) + return [item[1] for item in self.items()] def keyfile(module, user, write=False, path=None, manage_dir=True): diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/system/cron.py ansible-2.2.1.0/lib/ansible/modules/core/system/cron.py --- ansible-2.2.0.0/lib/ansible/modules/core/system/cron.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/system/cron.py 2017-01-16 17:03:59.000000000 +0000 @@ -224,6 +224,7 @@ self.root = (os.getuid() == 0) self.lines = None self.ansible = "#Ansible: " + self.existing = '' if cron_file: if os.path.isabs(cron_file): @@ -242,7 +243,8 @@ # read the cronfile try: f = open(self.cron_file, 'r') - self.lines = f.read().splitlines() + self.existing = f.read() + self.lines = self.existing.splitlines() f.close() except IOError: # cron file does not exist @@ -256,6 +258,8 @@ if rc != 0 and rc != 1: # 1 can mean that there are no jobs. raise CronTabError("Unable to read crontab") + self.existing = out + lines = out.splitlines() count = 0 for l in lines: @@ -263,6 +267,9 @@ not re.match( r'# \(/tmp/.*installed on.*\)', l) and not re.match( r'# \(.*version.*\)', l)): self.lines.append(l) + else: + pattern = re.escape(l) + '[\r\n]?' + self.existing = re.sub(pattern, '', self.existing, 1) count += 1 def is_empty(self): @@ -464,8 +471,8 @@ crons.append(cron) result = '\n'.join(crons) - if result and result[-1] not in ['\n', '\r']: - result += '\n' + if result: + result = result.rstrip('\r\n') + '\n' return result def _read_user_execute(self): @@ -581,7 +588,7 @@ if module._diff: diff = dict() - diff['before'] = crontab.render() + diff['before'] = crontab.existing if crontab.cron_file: diff['before_header'] = crontab.cron_file else: @@ -660,6 +667,10 @@ crontab.remove_job(name) changed = True + # no changes to env/job, but existing crontab needs a terminating newline + if not changed and not crontab.existing.endswith(('\r', '\n')): + changed = True + res_args = dict( jobs = crontab.get_jobnames(), envs = crontab.get_envnames(), diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/system/mount.py ansible-2.2.1.0/lib/ansible/modules/core/system/mount.py --- ansible-2.2.0.0/lib/ansible/modules/core/system/mount.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/system/mount.py 2017-01-16 17:03:59.000000000 +0000 @@ -308,6 +308,15 @@ return (args['name'], changed) +def _set_fstab_args(args): + result = [] + if 'fstab' in args and args['fstab'] != '/etc/fstab': + if get_platform().lower().endswith('bsd'): + result.append('-F') + else: + result.append('-T') + result.append(args['fstab']) + return result def mount(module, args): """Mount up a path or remount if needed.""" @@ -317,13 +326,9 @@ cmd = [mount_bin] if ismount(name): - cmd += ['-o', 'remount'] + return remount(module, mount_bin, args) - if args['fstab'] != '/etc/fstab': - if get_platform() == 'FreeBSD': - cmd += ['-F', args['fstab']] - elif get_platform() == 'Linux': - cmd += ['-T', args['fstab']] + cmd += _set_fstab_args(args) cmd += [name] @@ -348,6 +353,40 @@ else: return rc, out+err +def remount(module, mount_bin, args): + ''' will try to use -o remount first and fallback to unmount/mount if unsupported''' + msg = '' + cmd = [mount_bin] + + # multiplatform remount opts + if get_platform().lower().endswith('bsd'): + cmd += ['-u'] + else: + cmd += ['-o', 'remount' ] + + cmd += _set_fstab_args(args) + cmd += [ args['name'], ] + out = err = '' + try: + if get_platform().lower().endswith('bsd'): + # Note: Forcing BSDs to do umount/mount due to BSD remount not + # working as expected (suspect bug in the BSD mount command) + # Interested contributor could rework this to use mount options on + # the CLI instead of relying on fstab + # https://github.com/ansible/ansible-modules-core/issues/5591 + rc = 1 + else: + rc, out, err = module.run_command(cmd) + except: + rc = 1 + + if rc != 0: + msg = out + err + if ismount(args['name']): + rc, msg = umount(module, args['name']) + if rc == 0: + rc, msg = mount(module, args) + return rc, msg # Note if we wanted to put this into module_utils we'd have to get permission # from @jupeter -- https://github.com/ansible/ansible-modules-core/pull/2923 @@ -369,7 +408,7 @@ is_mounted = False - if get_platform() == 'Linux': + if get_platform() == 'Linux' and linux_mounts is not None: if src is None: # That's for unmounted/absent if dest in linux_mounts: @@ -410,7 +449,7 @@ try: f = open(mntinfo_file) except IOError: - module.fail_json(msg="Cannot open file %s" % mntinfo_file) + return lines = map(str.strip, f.readlines()) @@ -575,6 +614,11 @@ if get_platform() == 'Linux': linux_mounts = get_linux_mounts(module) + if linux_mounts is None: + args['warnings'] = ( + 'Cannot open file /proc/self/mountinfo. ' + 'Bind mounts might be misinterpreted.') + # Override defaults with user specified params for key in ('src', 'fstype', 'passno', 'opts', 'dump', 'fstab'): if module.params[key] is not None: @@ -650,8 +694,7 @@ elif 'bind' in args.get('opts', []): changed = True - if is_bind_mounted( - module, linux_mounts, name, args['src'], args['fstype']): + if is_bind_mounted( module, linux_mounts, name, args['src'], args['fstype']): changed = False if changed and not module.check_mode: diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/system/service.py ansible-2.2.1.0/lib/ansible/modules/core/system/service.py --- ansible-2.2.0.0/lib/ansible/modules/core/system/service.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/system/service.py 2017-01-16 17:03:59.000000000 +0000 @@ -74,6 +74,12 @@ description: - Additional arguments provided on the command line aliases: [ 'args' ] + use: + description: + - The service module actually uses system specific modules, normally through auto detection, this setting can force a specific module. + - Normally it uses the value of the 'ansible_service_mgr' fact and falls back to the old 'service' module when none matching is found. + default: 'auto' + version_added: 2.2 ''' EXAMPLES = ''' @@ -596,11 +602,9 @@ cleanout = status_stdout.lower().replace(self.name.lower(), '') if "stop" in cleanout: self.running = False - elif "run" in cleanout and "not" in cleanout: - self.running = False - elif "run" in cleanout and "not" not in cleanout: - self.running = True - elif "start" in cleanout and "not" not in cleanout: + elif "run" in cleanout: + self.running = not ("not " in cleanout) + elif "start" in cleanout and "not " not in cleanout: self.running = True elif 'could not access pid file' in cleanout: self.running = False @@ -988,7 +992,7 @@ # and hope for the best. for rcvar in rcvars: if '=' in rcvar: - self.rcconf_key = rcvar.split('=')[0] + self.rcconf_key, default_rcconf_value = rcvar.split('=', 1) break if self.rcconf_key is None: @@ -997,8 +1001,10 @@ if self.sysrc_cmd: # FreeBSD >= 9.2 rc, current_rcconf_value, stderr = self.execute_command("%s -n %s" % (self.sysrc_cmd, self.rcconf_key)) + # it can happen that rcvar is not set (case of a system coming from the ports collection) + # so we will fallback on the default if rc != 0: - self.module.fail_json(msg="unable to get current rcvar value", stdout=stdout, stderr=stderr) + current_rcconf_value = default_rcconf_value if current_rcconf_value.strip().upper() != self.rcconf_value: @@ -1365,9 +1371,9 @@ elif self.action == 'stop': subcmd = "disable -st" elif self.action == 'reload': - subcmd = "refresh" + subcmd = "refresh -s" elif self.action == 'restart' and status == 'online': - subcmd = "restart" + subcmd = "restart -s" elif self.action == 'restart' and status != 'online': subcmd = "enable -rst" diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/system/systemd.py ansible-2.2.1.0/lib/ansible/modules/core/system/systemd.py --- ansible-2.2.0.0/lib/ansible/modules/core/system/systemd.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/system/systemd.py 2017-01-16 17:03:59.000000000 +0000 @@ -73,8 +73,10 @@ EXAMPLES = ''' # Example action to start service httpd, if not running - systemd: state=started name=httpd + # Example action to stop service cron on debian, if running - systemd: name=cron state=stopped + # Example action to restart service cron on centos, in all cases, also issue daemon-reload to pick up config changes - systemd: state=restarted daemon_reload=yes name=crond # Example action to reload service httpd, in all cases @@ -221,16 +223,15 @@ } ''' -import os -import glob from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_bytes, to_native +from ansible.module_utils.service import sysv_exists, sysv_is_enabled, fail_if_missing +from ansible.module_utils._text import to_native # =========================================== # Main control flow def main(): - # init + # initialize module = AnsibleModule( argument_spec = dict( name = dict(required=True, type='str', aliases=['unit', 'service']), @@ -244,7 +245,6 @@ required_one_of=[['state', 'enabled', 'masked', 'daemon_reload']], ) - # initialize systemctl = module.get_bin_path('systemctl') if module.params['user']: systemctl = systemctl + " --user" @@ -255,6 +255,7 @@ 'name': unit, 'changed': False, 'status': {}, + 'warnings': [], } # Run daemon-reload first, if requested @@ -263,44 +264,52 @@ if rc != 0: module.fail_json(msg='failure %d during daemon-reload: %s' % (rc, err)) - #TODO: check if service exists - (rc, out, err) = module.run_command("%s show '%s'" % (systemctl, unit)) - if rc != 0: - module.fail_json(msg='failure %d running systemctl show for %r: %s' % (rc, unit, err)) + found = False + is_initd = sysv_exists(unit) + is_systemd = False - # load return of systemctl show into dictionary for easy access and return - k = None - multival = [] - for line in to_native(out).split('\n'): # systemd can have multiline values delimited with {} - if line.strip(): - if k is None: - if '=' in line: - k,v = line.split('=', 1) - if v.lstrip().startswith('{'): - if not v.rstrip().endswith('}'): + # check service data, cannot error out on rc as it changes across versions, assume not found + (rc, out, err) = module.run_command("%s show '%s'" % (systemctl, unit)) + if rc == 0: + # load return of systemctl show into dictionary for easy access and return + multival = [] + if out: + k = None + for line in to_native(out).split('\n'): # systemd can have multiline values delimited with {} + if line.strip(): + if k is None: + if '=' in line: + k,v = line.split('=', 1) + if v.lstrip().startswith('{'): + if not v.rstrip().endswith('}'): + multival.append(line) + continue + result['status'][k] = v.strip() + k = None + else: + if line.rstrip().endswith('}'): + result['status'][k] = '\n'.join(multival).strip() + multival = [] + k = None + else: multival.append(line) - continue - result['status'][k] = v.strip() - k = None - else: - if line.rstrip().endswith('}'): - result['status'][k] = '\n'.join(multival).strip() - multival = [] - k = None - else: - multival.append(line) + is_systemd = 'LoadState' in result['status'] and result['status']['LoadState'] != 'not-found' - if 'LoadState' in result['status'] and result['status']['LoadState'] == 'not-found': - module.fail_json(msg='Could not find the requested service "%r": %s' % (unit, err)) - elif 'LoadError' in result['status']: - module.fail_json(msg="Failed to get the service status '%s': %s" % (unit, result['status']['LoadError'])) + # Check for loading error + if is_systemd and 'LoadError' in result['status']: + module.fail_json(msg="Error loading unit file '%s': %s" % (unit, result['status']['LoadError'])) + + # Does service exist? + found = is_systemd or is_initd + if is_initd and not is_systemd: + result['warnings'].append('The service (%s) is actually an init script but the system is managed by systemd' % unit) - # mask/unmask the service, if requested + # mask/unmask the service, if requested, can operate on services before they are installed if module.params['masked'] is not None: - masked = (result['status']['LoadState'] == 'masked') + # state is not masked unless systemd affirms otherwise + masked = ('LoadState' in result['status'] and result['status']['LoadState'] == 'masked') - # Change? if masked != module.params['masked']: result['changed'] = True if module.params['masked']: @@ -311,10 +320,21 @@ if not module.check_mode: (rc, out, err) = module.run_command("%s %s '%s'" % (systemctl, action, unit)) if rc != 0: + # some versions of system CAN mask/unmask non existing services, we only fail on missing if they don't + fail_if_missing(module, found, unit, "cannot %s" % (action)) module.fail_json(msg="Unable to %s service %s: %s" % (action, unit, err)) + # Enable/disable service startup at boot if requested if module.params['enabled'] is not None: + + if module.params['enabled']: + action = 'enable' + else: + action = 'disable' + + fail_if_missing(module, found, unit, "cannot %s" % (action)) + # do we need to enable the service? enabled = False (rc, out, err) = module.run_command("%s is-enabled '%s'" % (systemctl, unit)) @@ -323,11 +343,8 @@ if rc == 0: enabled = True elif rc == 1: - # Deals with init scripts # if both init script and unit file exist stdout should have enabled/disabled, otherwise use rc entries - initscript = '/etc/init.d/' + unit - if os.path.exists(initscript) and os.access(initscript, os.X_OK) and \ - (not out.startswith('disabled') or bool(glob.glob('/etc/rc?.d/S??' + unit))): + if is_initd and (not out.startswith('disabled') or sysv_is_enabled(unit)): enabled = True # default to current state @@ -336,19 +353,16 @@ # Change enable/disable if needed if enabled != module.params['enabled']: result['changed'] = True - if module.params['enabled']: - action = 'enable' - else: - action = 'disable' - if not module.check_mode: (rc, out, err) = module.run_command("%s %s '%s'" % (systemctl, action, unit)) if rc != 0: - module.fail_json(msg="Unable to %s service %s: %s" % (action, unit, err)) + module.fail_json(msg="Unable to %s service %s: %s" % (action, unit, out + err)) result['enabled'] = not enabled + # set service state if requested if module.params['state'] is not None: + fail_if_missing(module, found, unit, "cannot check nor set state") # default to desired state result['state'] = module.params['state'] @@ -359,17 +373,18 @@ if module.params['state'] == 'started': if result['status']['ActiveState'] != 'active': action = 'start' - result['changed'] = True elif module.params['state'] == 'stopped': if result['status']['ActiveState'] == 'active': action = 'stop' - result['changed'] = True else: - action = module.params['state'][:-2] # remove 'ed' from restarted/reloaded + if result['status']['ActiveState'] != 'active': + action = 'start' + else: + action = module.params['state'][:-2] # remove 'ed' from restarted/reloaded result['state'] = 'started' - result['changed'] = True if action: + result['changed'] = True if not module.check_mode: (rc, out, err) = module.run_command("%s %s '%s'" % (systemctl, action, unit)) if rc != 0: diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/system/user.py ansible-2.2.1.0/lib/ansible/modules/core/system/user.py --- ansible-2.2.0.0/lib/ansible/modules/core/system/user.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/system/user.py 2017-01-16 17:03:59.000000000 +0000 @@ -1693,7 +1693,7 @@ self.chown_homedir(int(self.uid), int(self.group), self.home) for field in self.fields: - if self.__dict__.has_key(field[0]) and self.__dict__[field[0]]: + if field[0] in self.__dict__ and self.__dict__[field[0]]: cmd = self._get_dscl() cmd += [ '-create', '/Users/%s' % self.name, field[1], self.__dict__[field[0]]] @@ -1730,7 +1730,7 @@ self._make_group_numerical() for field in self.fields: - if self.__dict__.has_key(field[0]) and self.__dict__[field[0]]: + if field[0] in self.__dict__ and self.__dict__[field[0]]: current = self._get_user_property(field[1]) if current is None or current != self.__dict__[field[0]]: cmd = self._get_dscl() diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/utilities/logic/include_role.py ansible-2.2.1.0/lib/ansible/modules/core/utilities/logic/include_role.py --- ansible-2.2.0.0/lib/ansible/modules/core/utilities/logic/include_role.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/utilities/logic/include_role.py 2017-01-16 17:03:59.000000000 +0000 @@ -37,11 +37,6 @@ - "File to load from a Role's defaults/ directory." required: False default: 'main' - static: - description: - - Gives Ansible a hint if this is a 'static' include or not. If static it implies that it won't need templating nor loops nor conditionals and will show included tasks in the --list options. - required: False - default: None allow_duplicates: description: - Overrides the role's metadata setting to allow using a role more than once with the same parameters. @@ -55,7 +50,7 @@ - THIS IS EARLY PREVIEW, THINGS MAY CHANGE - Handlers are made available to the whole play. - simple dependencies seem to work fine. - - "Things not tested (yet): plugin overrides, nesting includes, used as handler, other stuff I did not think of when I wrote this." + - As with C(include) this task can be static or dynamic, If static it implies that it won't need templating nor loops nor conditionals and will show included tasks in the --list options. Ansible will try to autodetect what is needed, but you can set `static: yes|no` at task level to control this. ''' EXAMPLES = """ diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/utilities/logic/include_vars.py ansible-2.2.1.0/lib/ansible/modules/core/utilities/logic/include_vars.py --- ansible-2.2.0.0/lib/ansible/modules/core/utilities/logic/include_vars.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/utilities/logic/include_vars.py 2017-01-16 17:03:59.000000000 +0000 @@ -10,28 +10,47 @@ DOCUMENTATION = ''' --- -author: "Benno Joy (@bennojoy)" +author: "Allen Sanabria (@linuxdynasty)" module: include_vars short_description: Load variables from files, dynamically within a task. description: - - Loads variables from a YAML/JSON file dynamically during task runtime. It can work with conditionals, or use host specific variables to determine the path name to load from. + - Loads variables from a YAML/JSON files dynamically from within a file or + from a directory recursively during task runtime. If loading a directory, the files are sorted alphabetically before being loaded. options: file: version_added: "2.2" description: - The file name from which variables should be loaded. - If the path is relative, it will look for the file in vars/ subdirectory of a role or relative to playbook. + dir: + version_added: "2.2" + description: + - The directory name from which the variables should be loaded. + - If the path is relative, it will look for the file in vars/ subdirectory of a role or relative to playbook. + default: null name: version_added: "2.2" description: - The name of a variable into which assign the included vars, if omitted (null) they will be made top level vars. default: null + depth: + version_added: "2.2" + description: + - By default, this module will recursively go through each sub directory and load up the variables. By explicitly setting the depth, this module will only go as deep as the depth. + default: 0 + files_matching: + version_added: "2.2" + description: + - Limit the variables that are loaded within any directory to this regular expression. + default: null + ignore_files: + version_added: "2.2" + description: + - List of file names to ignore. The defaults can not be overridden, but can be extended. + default: null free-form: description: - This module allows you to specify the 'file' option directly w/o any other options. -notes: - - The file is always required either as the explicit option or using the free-form. -version_added: "1.4" ''' EXAMPLES = """ @@ -54,4 +73,27 @@ # bare include (free-form) - include_vars: myvars.yml +# Include all yml files in vars/all and all nested directories +- include_vars: + dir: 'vars/all' + +# Include all yml files in vars/all and all nested directories and save the output in test. +- include_vars: + dir: 'vars/all' + name: test + +# Include all yml files in vars/services +- include_vars: + dir: 'vars/services' + depth: 1 + +# Include only bastion.yml files +- include_vars: + dir: 'vars' + files_matching: 'bastion.yml' + +# Include only all yml files exception bastion.yml +- include_vars: + dir: 'vars' + ignore_files: 'bastion.yml' """ diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/utilities/logic/wait_for.py ansible-2.2.1.0/lib/ansible/modules/core/utilities/logic/wait_for.py --- ansible-2.2.0.0/lib/ansible/modules/core/utilities/logic/wait_for.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/utilities/logic/wait_for.py 2017-01-16 17:03:59.000000000 +0000 @@ -27,6 +27,8 @@ import sys import time +from ansible.module_utils._text import to_native + HAS_PSUTIL = False try: import psutil @@ -480,7 +482,7 @@ if not response: # Server shutdown break - data += response + data += to_native(response, errors='surrogate_or_strict') if re.search(compiled_search_re, data): matched = True break diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/VERSION ansible-2.2.1.0/lib/ansible/modules/core/VERSION --- ansible-2.2.0.0/lib/ansible/modules/core/VERSION 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/VERSION 2017-01-16 17:03:59.000000000 +0000 @@ -1 +1 @@ -2.2.0.0 1 +2.2.1.0 1 diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/web_infrastructure/django_manage.py ansible-2.2.1.0/lib/ansible/modules/core/web_infrastructure/django_manage.py --- ansible-2.2.0.0/lib/ansible/modules/core/web_infrastructure/django_manage.py 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/web_infrastructure/django_manage.py 2017-01-16 17:03:59.000000000 +0000 @@ -274,7 +274,7 @@ lines = out.split('\n') filt = globals().get(command + "_filter_output", None) if filt: - filtered_output = filter(filt, lines) + filtered_output = list(filter(filt, lines)) if len(filtered_output): changed = filtered_output diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/windows/async_wrapper.ps1 ansible-2.2.1.0/lib/ansible/modules/core/windows/async_wrapper.ps1 --- ansible-2.2.0.0/lib/ansible/modules/core/windows/async_wrapper.ps1 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/windows/async_wrapper.ps1 2017-01-16 17:03:59.000000000 +0000 @@ -397,7 +397,7 @@ $exec_cmd = [Ansible.Async.NativeProcessUtil]::SearchPath("powershell.exe") $exec_args = "`"$exec_cmd`" -NoProfile -ExecutionPolicy Bypass -EncodedCommand $encoded_command" - If(-not [Ansible.Async.NativeProcessUtil]::CreateProcess($exec_cmd, $exec_args, [IntPtr]::Zero, [IntPtr]::Zero, $true, $pstartup_flags, [IntPtr]::Zero, $env:windir, [ref]$si, [ref]$pi)) { + If(-not [Ansible.Async.NativeProcessUtil]::CreateProcess($exec_cmd, $exec_args, [IntPtr]::Zero, [IntPtr]::Zero, $false, $pstartup_flags, [IntPtr]::Zero, $env:windir, [ref]$si, [ref]$pi)) { #throw New-Object System.ComponentModel.Win32Exception throw "create bang $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())" } diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/windows/win_command.ps1 ansible-2.2.1.0/lib/ansible/modules/core/windows/win_command.ps1 --- ansible-2.2.0.0/lib/ansible/modules/core/windows/win_command.ps1 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/windows/win_command.ps1 2017-01-16 17:03:59.000000000 +0000 @@ -158,4 +158,4 @@ $result.end = $end_datetime.ToString("yyyy-MM-dd hh:mm:ss.ffffff") $result.delta = $($end_datetime - $start_datetime).ToString("h\:mm\:ss\.ffffff") -ConvertTo-Json -Depth 99 $result +Exit-Json $result diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/windows/win_file.ps1 ansible-2.2.1.0/lib/ansible/modules/core/windows/win_file.ps1 --- ansible-2.2.0.0/lib/ansible/modules/core/windows/win_file.ps1 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/windows/win_file.ps1 2017-01-16 17:03:59.000000000 +0000 @@ -102,7 +102,7 @@ If ( $state -eq "directory" ) { - New-Item -ItemType directory -Path $path + New-Item -ItemType directory -Path $path | Out-Null $result.changed = $TRUE } diff -Nru ansible-2.2.0.0/lib/ansible/modules/core/windows/win_shell.ps1 ansible-2.2.1.0/lib/ansible/modules/core/windows/win_shell.ps1 --- ansible-2.2.0.0/lib/ansible/modules/core/windows/win_shell.ps1 2016-11-01 03:43:52.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/core/windows/win_shell.ps1 2017-01-16 17:03:59.000000000 +0000 @@ -139,4 +139,4 @@ $result.end = $end_datetime.ToString("yyyy-MM-dd hh:mm:ss.ffffff") $result.delta = $($end_datetime - $start_datetime).ToString("h\:mm\:ss\.ffffff") -ConvertTo-Json -Depth 99 $result +Exit-Json $result diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/dynamodb_table.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/dynamodb_table.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/dynamodb_table.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/dynamodb_table.py 2017-01-16 17:04:03.000000000 +0000 @@ -270,14 +270,14 @@ removed_indexes, added_indexes, index_throughput_changes = get_changed_global_indexes(table, global_indexes) if removed_indexes: if not check_mode: - for name, index in removed_indexes.iteritems(): + for name, index in removed_indexes.items(): global_indexes_changed = table.delete_global_secondary_index(name) or global_indexes_changed else: global_indexes_changed = True if added_indexes: if not check_mode: - for name, index in added_indexes.iteritems(): + for name, index in added_indexes.items(): global_indexes_changed = table.create_global_secondary_index(global_index=index) or global_indexes_changed else: global_indexes_changed = True @@ -324,18 +324,18 @@ set_index_info = dict((index.name, index.schema()) for index in global_indexes) set_index_objects = dict((index.name, index) for index in global_indexes) - removed_indexes = dict((name, index) for name, index in table_index_info.iteritems() if name not in set_index_info) - added_indexes = dict((name, set_index_objects[name]) for name, index in set_index_info.iteritems() if name not in table_index_info) + removed_indexes = dict((name, index) for name, index in table_index_info.items() if name not in set_index_info) + added_indexes = dict((name, set_index_objects[name]) for name, index in set_index_info.items() if name not in table_index_info) # todo: uncomment once boto has https://github.com/boto/boto/pull/3447 fixed - # index_throughput_changes = dict((name, index.throughput) for name, index in set_index_objects.iteritems() if name not in added_indexes and (index.throughput['read'] != str(table_index_objects[name].throughput['read']) or index.throughput['write'] != str(table_index_objects[name].throughput['write']))) + # index_throughput_changes = dict((name, index.throughput) for name, index in set_index_objects.items() if name not in added_indexes and (index.throughput['read'] != str(table_index_objects[name].throughput['read']) or index.throughput['write'] != str(table_index_objects[name].throughput['write']))) # todo: remove once boto has https://github.com/boto/boto/pull/3447 fixed - index_throughput_changes = dict((name, index.throughput) for name, index in set_index_objects.iteritems() if name not in added_indexes) + index_throughput_changes = dict((name, index.throughput) for name, index in set_index_objects.items() if name not in added_indexes) return removed_indexes, added_indexes, index_throughput_changes def validate_index(index, module): - for key, val in index.iteritems(): + for key, val in index.items(): if key not in INDEX_OPTIONS: module.fail_json(msg='%s is not a valid option for an index' % key) for required_option in INDEX_REQUIRED_OPTIONS: diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_asg_facts.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_asg_facts.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_asg_facts.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_asg_facts.py 2017-01-16 17:04:03.000000000 +0000 @@ -202,7 +202,7 @@ HAS_BOTO3 = False def match_asg_tags(tags_to_match, asg): - for key, value in tags_to_match.iteritems(): + for key, value in tags_to_match.items(): for tag in asg['Tags']: if key == tag['Key'] and value == tag['Value']: break diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_customer_gateway.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_customer_gateway.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_customer_gateway.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_customer_gateway.py 2017-01-16 17:04:03.000000000 +0000 @@ -204,7 +204,7 @@ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_if=[ - ('state', 'present', ['bgp_arn']) + ('state', 'present', ['bgp_asn']) ] ) diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_elb_facts.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_elb_facts.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_elb_facts.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_elb_facts.py 2017-01-16 17:04:03.000000000 +0000 @@ -205,16 +205,19 @@ self.module.fail_json(msg = "%s: %s" % (err.error_code, err.error_message)) if all_elbs: - for existing_lb in all_elbs: - if existing_lb.name in self.names: - elb_array.append(self._get_elb_info(existing_lb)) - - return elb_array + if self.names: + for existing_lb in all_elbs: + if existing_lb.name in self.names: + elb_array.append(existing_lb) + else: + elb_array = all_elbs + + return list(map(self._get_elb_info, elb_array)) def main(): argument_spec = ec2_argument_spec() argument_spec.update(dict( - names={'default': None, 'type': 'list'} + names={'default': [], 'type': 'list'} ) ) module = AnsibleModule(argument_spec=argument_spec) diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_lc_find.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_lc_find.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_lc_find.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_lc_find.py 2017-01-16 17:04:03.000000000 +0000 @@ -184,7 +184,7 @@ 'security_groups': lc['SecurityGroups'], 'kernel_id': lc['KernelId'], 'ram_disk_id': lc['RamdiskId'], - 'associate_public_address': lc['AssociatePublicIpAddress'], + 'associate_public_address': lc.get('AssociatePublicIpAddress', False), } results.append(data) diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_vol_facts.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_vol_facts.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_vol_facts.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_vol_facts.py 2017-01-16 17:04:03.000000000 +0000 @@ -77,6 +77,7 @@ volume_info = { 'create_time': volume.create_time, 'id': volume.id, + 'encrypted': volume.encrypted, 'iops': volume.iops, 'size': volume.size, 'snapshot_id': volume.snapshot_id, diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_dhcp_options_facts.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_dhcp_options_facts.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_dhcp_options_facts.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_dhcp_options_facts.py 2017-01-16 17:04:03.000000000 +0000 @@ -102,7 +102,7 @@ if module.params.get('filters'): params['Filters'] = [] - for key, value in module.params.get('filters').iteritems(): + for key, value in module.params.get('filters').items(): temp_dict = dict() temp_dict['Name'] = key if isinstance(value, basestring): diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_nacl.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_nacl.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_nacl.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_nacl.py 2017-01-16 17:04:03.000000000 +0000 @@ -157,7 +157,7 @@ def load_tags(module): tags = [] if module.params.get('tags'): - for name, value in module.params.get('tags').iteritems(): + for name, value in module.params.get('tags').items(): tags.append({'Key': name, 'Value': str(value)}) tags.append({'Key': "Name", 'Value': module.params.get('name')}) else: @@ -235,7 +235,7 @@ if nacl['NetworkAcls']: nacl_values = [t.values() for t in nacl['NetworkAcls'][0]['Tags']] nacl_tags = [item for sublist in nacl_values for item in sublist] - tag_values = [[key, str(value)] for key, value in tags.iteritems()] + tag_values = [[key, str(value)] for key, value in tags.items()] tags = [item for sublist in tag_values for item in sublist] if sorted(nacl_tags) == sorted(tags): changed = False diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_peer.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_peer.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_peer.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_peer.py 2017-01-16 17:04:03.000000000 +0000 @@ -25,6 +25,10 @@ description: - VPC id of the requesting VPC. required: false + peering_id: + description: + - Peering connection id. + required: false peer_vpc_id: description: - VPC id of the accepting VPC. @@ -202,7 +206,7 @@ if pcx['VpcPeeringConnections']: pcx_values = [t.values() for t in pcx['VpcPeeringConnections'][0]['Tags']] pcx_tags = [item for sublist in pcx_values for item in sublist] - tag_values = [[key, str(value)] for key, value in tags.iteritems()] + tag_values = [[key, str(value)] for key, value in tags.items()] tags = [item for sublist in tag_values for item in sublist] if sorted(pcx_tags) == sorted(tags): changed = False @@ -297,7 +301,7 @@ def load_tags(module): tags = [] if module.params.get('tags'): - for name, value in module.params.get('tags').iteritems(): + for name, value in module.params.get('tags').items(): tags.append({'Key': name, 'Value': str(value)}) return tags diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_route_table.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_route_table.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_route_table.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_route_table.py 2017-01-16 17:04:03.000000000 +0000 @@ -229,7 +229,7 @@ def tags_match(match_tags, candidate_tags): return all((k in candidate_tags and candidate_tags[k] == v - for k, v in match_tags.iteritems())) + for k, v in match_tags.items())) def ensure_tags(vpc_conn, resource_id, tags, add_only, check_mode): @@ -299,7 +299,7 @@ if all((not route.gateway_id, not route.instance_id, not route.interface_id, not route.vpc_peering_connection_id)): return True - for k in key_attr_map.iterkeys(): + for k in key_attr_map: if k in route_spec: if route_spec[k] != getattr(route, k): return False diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_vgw.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_vgw.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_vgw.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_vgw.py 2017-01-16 17:04:03.000000000 +0000 @@ -272,7 +272,7 @@ tags = [] if module.params.get('tags'): - for name, value in module.params.get('tags').iteritems(): + for name, value in module.params.get('tags').items(): tags.append({'Key': name, 'Value': str(value)}) tags.append({'Key': "Name", 'Value': module.params.get('name')}) else: diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/lambda_event.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/lambda_event.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/lambda_event.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/lambda_event.py 2017-01-16 17:04:03.000000000 +0000 @@ -385,7 +385,7 @@ dict( state=dict(required=False, default='present', choices=['present', 'absent']), lambda_function_arn=dict(required=True, default=None, aliases=['function_name', 'function_arn']), - event_source=dict(required=True, default="stream", choices=source_choices), + event_source=dict(required=False, default="stream", choices=source_choices), source_params=dict(type='dict', required=True, default=None), alias=dict(required=False, default=None), version=dict(type='int', required=False, default=0), diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/redshift.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/redshift.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/redshift.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/redshift.py 2017-01-16 17:04:03.000000000 +0000 @@ -37,115 +37,94 @@ node_type: description: - The node type of the cluster. Must be specified when command=create. - required: false - choices: ['dw1.xlarge', 'dw1.8xlarge', 'dw2.large', 'dw2.8xlarge', ] + choices: ['ds1.xlarge', 'ds1.8xlarge', 'ds2.xlarge', 'ds2.8xlarge', 'dc1.large', 'dc1.8xlarge', 'dw1.xlarge', 'dw1.8xlarge', 'dw2.large', 'dw2.8xlarge'] username: description: - Master database username. Used only when command=create. - required: false password: description: - Master database password. Used only when command=create. - required: false cluster_type: description: - The type of cluster. - required: false choices: ['multi-node', 'single-node' ] default: 'single-node' db_name: description: - Name of the database. - required: false default: null availability_zone: description: - availability zone in which to launch cluster - required: false aliases: ['zone', 'aws_zone'] number_of_nodes: description: - Number of nodes. Only used when cluster_type=multi-node. - required: false default: null cluster_subnet_group_name: description: - which subnet to place the cluster - required: false aliases: ['subnet'] cluster_security_groups: description: - in which security group the cluster belongs - required: false default: null aliases: ['security_groups'] vpc_security_group_ids: description: - VPC security group - required: false aliases: ['vpc_security_groups'] default: null preferred_maintenance_window: description: - maintenance window - required: false aliases: ['maintance_window', 'maint_window'] default: null cluster_parameter_group_name: description: - name of the cluster parameter group - required: false aliases: ['param_group_name'] default: null automated_snapshot_retention_period: description: - period when the snapshot take place - required: false aliases: ['retention_period'] default: null port: description: - which port the cluster is listining - required: false default: null cluster_version: description: - which version the cluster should have - required: false aliases: ['version'] choices: ['1.0'] default: null allow_version_upgrade: description: - flag to determinate if upgrade of version is possible - required: false aliases: ['version_upgrade'] - default: null + default: true publicly_accessible: description: - if the cluster is accessible publicly or not - required: false - default: null + default: false encrypted: description: - if the cluster is encrypted or not - required: false - default: null + default: false elastic_ip: description: - if the cluster has an elastic IP or not - required: false default: null new_cluster_identifier: description: - Only used when command=modify. - required: false aliases: ['new_identifier'] default: null wait: description: - When command=create, modify or restore then wait for the database to enter the 'available' state. When command=delete wait for the database to be terminated. - required: false default: "no" choices: [ "yes", "no" ] wait_timeout: @@ -160,7 +139,7 @@ # Basic cluster provisioning example - redshift: > command=create - node_type=dw1.xlarge + node_type=ds1.xlarge identifier=new_cluster username=cluster_admin password=1nsecure @@ -282,7 +261,7 @@ 'cluster_version', 'allow_version_upgrade', 'number_of_nodes', 'publicly_accessible', 'encrypted', 'elastic_ip'): - if module.params.get( p ): + if p in module.params: params[ p ] = module.params.get( p ) try: @@ -389,12 +368,11 @@ 'cluster_parameter_group_name', 'automated_snapshot_retention_period', 'port', 'cluster_version', 'allow_version_upgrade', 'number_of_nodes', 'new_cluster_identifier'): - if module.params.get(p): + if p in module.params: params[p] = module.params.get(p) try: redshift.describe_clusters(identifier)['DescribeClustersResponse']['DescribeClustersResult']['Clusters'][0] - changed = False except boto.exception.JSONResponseError as e: try: redshift.modify_cluster(identifier, **params) @@ -430,7 +408,7 @@ argument_spec.update(dict( command = dict(choices=['create', 'facts', 'delete', 'modify'], required=True), identifier = dict(required=True), - node_type = dict(choices=['dw1.xlarge', 'dw1.8xlarge', 'dw2.large', 'dw2.8xlarge', ], required=False), + node_type = dict(choices=['ds1.xlarge', 'ds1.8xlarge', 'ds2.xlarge', 'ds2.8xlarge', 'dc1.large', 'dc1.8xlarge', 'dw1.xlarge', 'dw1.8xlarge', 'dw2.large', 'dw2.8xlarge'], required=False), username = dict(required=False), password = dict(no_log=True, required=False), db_name = dict(require=False), @@ -444,10 +422,10 @@ automated_snapshot_retention_period = dict(aliases=['retention_period']), port = dict(type='int'), cluster_version = dict(aliases=['version'], choices=['1.0']), - allow_version_upgrade = dict(aliases=['version_upgrade'], type='bool'), + allow_version_upgrade = dict(aliases=['version_upgrade'], type='bool', default=True), number_of_nodes = dict(type='int'), - publicly_accessible = dict(type='bool'), - encrypted = dict(type='bool'), + publicly_accessible = dict(type='bool', default=False), + encrypted = dict(type='bool', default=False), elastic_ip = dict(required=False), new_cluster_identifier = dict(aliases=['new_identifier']), wait = dict(type='bool', default=False), diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/s3_bucket.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/s3_bucket.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/amazon/s3_bucket.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/amazon/s3_bucket.py 2017-01-16 17:04:03.000000000 +0000 @@ -137,7 +137,7 @@ tag_set = TagSet() tags_obj = Tags() - for key, val in tags.iteritems(): + for key, val in tags.items(): tag_set.add_tag(key, val) tags_obj.add_tag_set(tag_set) diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/cloudstack/cs_facts.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/cloudstack/cs_facts.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/cloudstack/cs_facts.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/cloudstack/cs_facts.py 2017-01-16 17:04:03.000000000 +0000 @@ -128,7 +128,7 @@ result = {} filter = module.params.get('filter') if not filter: - for key,path in self.fact_paths.iteritems(): + for key,path in self.fact_paths.items(): result[key] = self._fetch(CS_METADATA_BASE_URL + "/" + path) result['cloudstack_user_data'] = self._get_user_data_json() else: diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/cloudstack/cs_instance.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/cloudstack/cs_instance.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/cloudstack/cs_instance.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/cloudstack/cs_instance.py 2017-01-16 17:04:03.000000000 +0000 @@ -204,6 +204,7 @@ - "If you want to delete all tags, set a empty list e.g. C(tags: [])." required: false default: null + aliases: [ 'tag' ] poll_async: description: - Poll async jobs until job has finished. @@ -503,6 +504,10 @@ if instances: for v in instances['virtualmachine']: if instance_name.lower() in [ v['name'].lower(), v['displayname'].lower(), v['id'] ]: + # Query the user data if we need to + if 'userdata' not in v and self.get_user_data() is not None: + res = self.cs.getVirtualMachineUserData(virtualmachineid=v['id']) + v['userdata'] = res['virtualmachineuserdata'].get('userdata',"") self.instance = v break return self.instance diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/cloudstack/cs_ip_address.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/cloudstack/cs_ip_address.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/cloudstack/cs_ip_address.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/cloudstack/cs_ip_address.py 2017-01-16 17:04:03.000000000 +0000 @@ -202,15 +202,14 @@ poll_async = self.module.params.get('poll_async') if poll_async: - res = self.poll_job(res, 'ipaddress') - ip_address = res + ip_address = self.poll_job(res, 'ipaddress') return ip_address def disassociate_ip_address(self): ip_address = self.get_ip_address() - if ip_address is None: - return ip_address + if not ip_address: + return None if ip_address['isstaticnat']: self.module.fail_json(msg="IP address is allocated via static nat") diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/cloudstack/cs_iso.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/cloudstack/cs_iso.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/cloudstack/cs_iso.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/cloudstack/cs_iso.py 2017-01-16 17:04:03.000000000 +0000 @@ -240,6 +240,8 @@ self.result['changed'] = True if not self.module.check_mode: res = self.cs.registerIso(**args) + if 'errortext' in res: + self.module.fail_json(msg="Failed: '%s'" % res['errortext']) iso = res['iso'][0] return iso @@ -284,6 +286,9 @@ if not self.module.check_mode: res = self.cs.deleteIso(**args) + if 'errortext' in res: + self.module.fail_json(msg="Failed: '%s'" % res['errortext']) + self.poll_job(res, 'iso') return iso diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/cloudstack/cs_network.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/cloudstack/cs_network.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/cloudstack/cs_network.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/cloudstack/cs_network.py 2017-01-16 17:04:03.000000000 +0000 @@ -59,14 +59,14 @@ gateway: description: - The gateway of the network. - - Required for shared networks and isolated networks when it belongs to VPC. + - Required for shared networks and isolated networks when it belongs to a VPC. - Only considered on create. required: false default: null netmask: description: - The netmask of the network. - - Required for shared networks and isolated networks when it belongs to VPC. + - Required for shared networks and isolated networks when it belongs to a VPC. - Only considered on create. required: false default: null @@ -91,7 +91,7 @@ default: null gateway_ipv6: description: - - The gateway of the IPv6 network. + - The gateway of the IPv6 network. - Required for shared networks. - Only considered on create. required: false @@ -103,12 +103,12 @@ default: null vpc: description: - - The ID or VID of the network. + - Name of the VPC of the network. required: false default: null isolated_pvlan: description: - - The isolated private vlan for this network. + - The isolated private VLAN for this network. required: false default: null clean_up: @@ -342,7 +342,6 @@ 'dns1': 'dns1', 'dns2': 'dns2', } - self.network = None diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/cloudstack/cs_portforward.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/cloudstack/cs_portforward.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/cloudstack/cs_portforward.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/cloudstack/cs_portforward.py 2017-01-16 17:04:03.000000000 +0000 @@ -330,7 +330,7 @@ super(AnsibleCloudStackPortforwarding, self).get_result(portforwarding_rule) if portforwarding_rule: # Bad bad API does not always return int when it should. - for search_key, return_key in self.returns_to_int.iteritems(): + for search_key, return_key in self.returns_to_int.items(): if search_key in portforwarding_rule: self.result[return_key] = int(portforwarding_rule[search_key]) return self.result diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/cloudstack/cs_securitygroup_rule.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/cloudstack/cs_securitygroup_rule.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/cloudstack/cs_securitygroup_rule.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/cloudstack/cs_securitygroup_rule.py 2017-01-16 17:04:03.000000000 +0000 @@ -239,10 +239,10 @@ icmp_code = self.module.params.get('icmp_code') icmp_type = self.module.params.get('icmp_type') - if protocol in ['tcp', 'udp'] and not (start_port and end_port): + if protocol in ['tcp', 'udp'] and (start_port is None or end_port is None): self.module.fail_json(msg="no start_port or end_port set for protocol '%s'" % protocol) - if protocol == 'icmp' and not (icmp_type and icmp_code): + if protocol == 'icmp' and (icmp_type is None or icmp_code is None): self.module.fail_json(msg="no icmp_type or icmp_code set for protocol '%s'" % protocol) for rule in rules: diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/cloudstack/cs_snapshot_policy.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/cloudstack/cs_snapshot_policy.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/cloudstack/cs_snapshot_policy.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/cloudstack/cs_snapshot_policy.py 2017-01-16 17:04:03.000000000 +0000 @@ -230,13 +230,14 @@ policy = self.get_snapshot_policy() args = { + 'id': policy.get('id') if policy else None, 'intervaltype': self.module.params.get('interval_type'), - 'schedule': self.module.params.get('schedule'), - 'maxsnaps': self.module.params.get('max_snaps'), - 'timezone': self.module.params.get('time_zone'), - 'volumeid': self.get_volume(key='id') + 'schedule': self.module.params.get('schedule'), + 'maxsnaps': self.module.params.get('max_snaps'), + 'timezone': self.module.params.get('time_zone'), + 'volumeid': self.get_volume(key='id') } - if not policy or (policy and self.has_changed(policy, args)): + if not policy or (policy and self.has_changed(policy, args, only_keys=['schedule', 'maxsnaps', 'timezone'])): self.result['changed'] = True if not self.module.check_mode: res = self.cs.createSnapshotPolicy(**args) diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/lxd/lxd_container.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/lxd/lxd_container.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/lxd/lxd_container.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/lxd/lxd_container.py 2017-01-16 17:04:03.000000000 +0000 @@ -384,7 +384,7 @@ @staticmethod def _has_all_ipv4_addresses(addresses): - return len(addresses) > 0 and all([len(v) > 0 for v in addresses.itervalues()]) + return len(addresses) > 0 and all([len(v) > 0 for v in addresses.values()]) def _get_addresses(self): try: diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/lxd/lxd_profile.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/lxd/lxd_profile.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/lxd/lxd_profile.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/lxd/lxd_profile.py 2017-01-16 17:04:03.000000000 +0000 @@ -280,7 +280,7 @@ def _apply_profile_configs(self): config = self.old_profile_json.copy() - for k, v in self.config.iteritems(): + for k, v in self.config.items(): config[k] = v self.client.do('PUT', '/1.0/profiles/{}'.format(self.name), config) self.actions.append('apply_profile_configs') diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/misc/proxmox.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/misc/proxmox.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/misc/proxmox.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/misc/proxmox.py 2017-01-16 17:04:03.000000000 +0000 @@ -222,7 +222,7 @@ def create_instance(module, proxmox, vmid, node, disk, storage, cpus, memory, swap, timeout, **kwargs): proxmox_node = proxmox.nodes(node) - kwargs = dict((k,v) for k, v in kwargs.iteritems() if v is not None) + kwargs = dict((k,v) for k, v in kwargs.items() if v is not None) if VZ_TYPE =='lxc': kwargs['cpulimit']=cpus kwargs['rootfs']=disk diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/misc/rhevm.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/misc/rhevm.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/misc/rhevm.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/misc/rhevm.py 2017-01-16 17:04:03.000000000 +0000 @@ -1,1530 +1,1530 @@ -#!/usr/bin/python - -# (c) 2016, Timothy Vandenbrande -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . - -DOCUMENTATION = ''' ---- -module: rhevm -author: Timothy Vandenbrande -short_description: RHEV/oVirt automation -description: - - Allows you to create/remove/update or powermanage virtual machines on a RHEV/oVirt platform. -version_added: "2.2" -requirements: - - ovirtsdk -options: - user: - description: - - The user to authenticate with. - default: "admin@internal" - required: false - server: - description: - - The name/ip of your RHEV-m/oVirt instance. - default: "127.0.0.1" - required: false - port: - description: - - The port on which the API is reacheable. - default: "443" - required: false - insecure_api: - description: - - A boolean switch to make a secure or insecure connection to the server. - default: false - required: false - name: - description: - - The name of the VM. - cluster: - description: - - The rhev/ovirt cluster in which you want you VM to start. - required: false - datacenter: - description: - - The rhev/ovirt datacenter in which you want you VM to start. - required: false - default: "Default" - state: - description: - - This serves to create/remove/update or powermanage your VM. - default: "present" - required: false - choices: ['ping', 'present', 'absent', 'up', 'down', 'restarted', 'cd', 'info'] - image: - description: - - The template to use for the VM. - default: null - required: false - type: - description: - - To define if the VM is a server or desktop. - default: server - required: false - choices: [ 'server', 'desktop', 'host' ] - vmhost: - description: - - The host you wish your VM to run on. - required: false - vmcpu: - description: - - The number of CPUs you want in your VM. - default: "2" - required: false - cpu_share: - description: - - This parameter is used to configure the cpu share. - default: "0" - required: false - vmmem: - description: - - The amount of memory you want your VM to use (in GB). - default: "1" - required: false - osver: - description: - - The operationsystem option in RHEV/oVirt. - default: "rhel_6x64" - required: false - mempol: - description: - - The minimum amount of memory you wish to reserve for this system. - default: "1" - required: false - vm_ha: - description: - - To make your VM High Available. - default: true - required: false - disks: - description: - - This option uses complex arguments and is a list of disks with the options name, size and domain. - required: false - ifaces: - description: - - This option uses complex arguments and is a list of interfaces with the options name and vlan. - aliases: ['nics', 'interfaces'] - required: false - boot_order: - description: - - This option uses complex arguments and is a list of items that specify the bootorder. - default: ["network","hd"] - required: false - del_prot: - description: - - This option sets the delete protection checkbox. - default: true - required: false - cd_drive: - description: - - The CD you wish to have mounted on the VM when I(state = 'CD'). - default: null - required: false - timeout: - description: - - The timeout you wish to define for power actions. - - When I(state = 'up') - - When I(state = 'down') - - When I(state = 'restarted') - default: null - required: false -''' - -RETURN = ''' -vm: - description: Returns all of the VMs variables and execution. - returned: always - type: dict - sample: '{ - "boot_order": [ - "hd", - "network" - ], - "changed": true, - "changes": [ - "Delete Protection" - ], - "cluster": "C1", - "cpu_share": "0", - "created": false, - "datacenter": "Default", - "del_prot": true, - "disks": [ - { - "domain": "ssd-san", - "name": "OS", - "size": 40 - } - ], - "eth0": "00:00:5E:00:53:00", - "eth1": "00:00:5E:00:53:01", - "eth2": "00:00:5E:00:53:02", - "exists": true, - "failed": false, - "ifaces": [ - { - "name": "eth0", - "vlan": "Management" - }, - { - "name": "eth1", - "vlan": "Internal" - }, - { - "name": "eth2", - "vlan": "External" - } - ], - "image": false, - "mempol": "0", - "msg": [ - "VM exists", - "cpu_share was already set to 0", - "VM high availability was already set to True", - "The boot order has already been set", - "VM delete protection has been set to True", - "Disk web2_Disk0_OS already exists", - "The VM starting host was already set to host416" - ], - "name": "web2", - "type": "server", - "uuid": "4ba5a1be-e60b-4368-9533-920f156c817b", - "vm_ha": true, - "vmcpu": "4", - "vmhost": "host416", - "vmmem": "16" - }' -''' - -EXAMPLES = ''' -# basic get info from VM - action: rhevm - args: - name: "demo" - user: "{{ rhev.admin.name }}" - password: "{{ rhev.admin.pass }}" - server: "rhevm01" - state: "info" - -# basic create example from image - action: rhevm - args: - name: "demo" - user: "{{ rhev.admin.name }}" - password: "{{ rhev.admin.pass }}" - server: "rhevm01" - state: "present" - image: "centos7_x64" - cluster: "centos" - -# power management - action: rhevm - args: - name: "uptime_server" - user: "{{ rhev.admin.name }}" - password: "{{ rhev.admin.pass }}" - server: "rhevm01" - cluster: "RH" - state: "down" - image: "centos7_x64" - cluster: "centos - -# multi disk, multi nic create example - action: rhevm - args: - name: "server007" - user: "{{ rhev.admin.name }}" - password: "{{ rhev.admin.pass }}" - server: "rhevm01" - cluster: "RH" - state: "present" - type: "server" - vmcpu: 4 - vmmem: 2 - ifaces: - - name: "eth0" - vlan: "vlan2202" - - name: "eth1" - vlan: "vlan36" - - name: "eth2" - vlan: "vlan38" - - name: "eth3" - vlan: "vlan2202" - disks: - - name: "root" - size: 10 - domain: "ssd-san" - - name: "swap" - size: 10 - domain: "15kiscsi-san" - - name: "opt" - size: 10 - domain: "15kiscsi-san" - - name: "var" - size: 10 - domain: "10kiscsi-san" - - name: "home" - size: 10 - domain: "sata-san" - boot_order: - - "network" - - "hd" - -# add a CD to the disk cd_drive - action: rhevm - args: - name: 'server007' - user: "{{ rhev.admin.name }}" - password: "{{ rhev.admin.pass }}" - state: 'cd' - cd_drive: 'rhev-tools-setup.iso' - -# new host deployment + host network configuration - action: rhevm - args: - name: "ovirt_node007" - password: "{{ rhevm.admin.pass }}" - type: "host" - state: present - cluster: "rhevm01" - ifaces: - - name: em1 - - name: em2 - - name: p3p1 - ip: '172.31.224.200' - netmask: '255.255.254.0' - - name: p3p2 - ip: '172.31.225.200' - netmask: '255.255.254.0' - - name: bond0 - bond: - - em1 - - em2 - network: 'rhevm' - ip: '172.31.222.200' - netmask: '255.255.255.0' - management: True - - name: bond0.36 - network: 'vlan36' - ip: '10.2.36.200' - netmask: '255.255.254.0' - gateway: '10.2.36.254' - - name: bond0.2202 - network: 'vlan2202' - - name: bond0.38 - network: 'vlan38' -''' - -import time -import sys -import traceback -import json - -try: - from ovirtsdk.api import API - from ovirtsdk.xml import params - HAS_SDK = True -except ImportError: - HAS_SDK = False - -RHEV_FAILED = 1 -RHEV_SUCCESS = 0 -RHEV_UNAVAILABLE = 2 - -RHEV_TYPE_OPTS = ['server', 'desktop', 'host'] -STATE_OPTS = ['ping', 'present', 'absent', 'up', 'down', 'restart', 'cd', 'info'] - -global msg, changed, failed -msg = [] -changed = False -failed = False - - -class RHEVConn(object): - 'Connection to RHEV-M' - def __init__(self, module): - self.module = module - - user = module.params.get('user') - password = module.params.get('password') - server = module.params.get('server') - port = module.params.get('port') - insecure_api = module.params.get('insecure_api') - - url = "https://%s:%s" % (server, port) - - try: - api = API(url=url, username=user, password=password, insecure=str(insecure_api)) - api.test() - self.conn = api - except: - raise Exception("Failed to connect to RHEV-M.") - - def __del__(self): - self.conn.disconnect() - - def createVMimage(self, name, cluster, template): - try: - vmparams = params.VM( - name=name, - cluster=self.conn.clusters.get(name=cluster), - template=self.conn.templates.get(name=template), - disks=params.Disks(clone=True) - ) - self.conn.vms.add(vmparams) - setMsg("VM is created") - setChanged() - return True - except Exception as e: - setMsg("Failed to create VM") - setMsg(str(e)) - setFailed() - return False - - def createVM(self, name, cluster, os, actiontype): - try: - vmparams = params.VM( - name=name, - cluster=self.conn.clusters.get(name=cluster), - os=params.OperatingSystem(type_=os), - template=self.conn.templates.get(name="Blank"), - type_=actiontype - ) - self.conn.vms.add(vmparams) - setMsg("VM is created") - setChanged() - return True - except Exception as e: - setMsg("Failed to create VM") - setMsg(str(e)) - setFailed() - return False - - def createDisk(self, vmname, diskname, disksize, diskdomain, diskinterface, diskformat, diskallocationtype, diskboot): - VM = self.get_VM(vmname) - - newdisk = params.Disk( - name=diskname, - size=1024 * 1024 * 1024 * int(disksize), - wipe_after_delete=True, - sparse=diskallocationtype, - interface=diskinterface, - format=diskformat, - bootable=diskboot, - storage_domains=params.StorageDomains( - storage_domain=[self.get_domain(diskdomain)] - ) - ) - - try: - VM.disks.add(newdisk) - VM.update() - setMsg("Successfully added disk " + diskname) - setChanged() - except Exception as e: - setFailed() - setMsg("Error attaching " + diskname + "disk, please recheck and remove any leftover configuration.") - setMsg(str(e)) - return False - - try: - currentdisk = VM.disks.get(name=diskname) - attempt = 1 - while currentdisk.status.state != 'ok': - currentdisk = VM.disks.get(name=diskname) - if attempt == 100: - setMsg("Error, disk %s, state %s" % (diskname, str(currentdisk.status.state))) - raise - else: - attempt += 1 - time.sleep(2) - setMsg("The disk " + diskname + " is ready.") - except Exception as e: - setFailed() - setMsg("Error getting the state of " + diskname + ".") - setMsg(str(e)) - return False - return True - - def createNIC(self, vmname, nicname, vlan, interface): - VM = self.get_VM(vmname) - CLUSTER = self.get_cluster_byid(VM.cluster.id) - DC = self.get_DC_byid(CLUSTER.data_center.id) - newnic = params.NIC( - name=nicname, - network=DC.networks.get(name=vlan), - interface=interface - ) - - try: - VM.nics.add(newnic) - VM.update() - setMsg("Successfully added iface " + nicname) - setChanged() - except Exception as e: - setFailed() - setMsg("Error attaching " + nicname + " iface, please recheck and remove any leftover configuration.") - setMsg(str(e)) - return False - - try: - currentnic = VM.nics.get(name=nicname) - attempt = 1 - while currentnic.active is not True: - currentnic = VM.nics.get(name=nicname) - if attempt == 100: - setMsg("Error, iface %s, state %s" % (nicname, str(currentnic.active))) - raise - else: - attempt += 1 - time.sleep(2) - setMsg("The iface " + nicname + " is ready.") - except Exception as e: - setFailed() - setMsg("Error getting the state of " + nicname + ".") - setMsg(str(e)) - return False - return True - - def get_DC(self, dc_name): - return self.conn.datacenters.get(name=dc_name) - - def get_DC_byid(self, dc_id): - return self.conn.datacenters.get(id=dc_id) - - def get_VM(self, vm_name): - return self.conn.vms.get(name=vm_name) - - def get_cluster_byid(self, cluster_id): - return self.conn.clusters.get(id=cluster_id) - - def get_cluster(self, cluster_name): - return self.conn.clusters.get(name=cluster_name) - - def get_domain_byid(self, dom_id): - return self.conn.storagedomains.get(id=dom_id) - - def get_domain(self, domain_name): - return self.conn.storagedomains.get(name=domain_name) - - def get_disk(self, disk): - return self.conn.disks.get(disk) - - def get_network(self, dc_name, network_name): - return self.get_DC(dc_name).networks.get(network_name) - - def get_network_byid(self, network_id): - return self.conn.networks.get(id=network_id) - - def get_NIC(self, vm_name, nic_name): - return self.get_VM(vm_name).nics.get(nic_name) - - def get_Host(self, host_name): - return self.conn.hosts.get(name=host_name) - - def get_Host_byid(self, host_id): - return self.conn.hosts.get(id=host_id) - - def set_Memory(self, name, memory): - VM = self.get_VM(name) - VM.memory = int(int(memory) * 1024 * 1024 * 1024) - try: - VM.update() - setMsg("The Memory has been updated.") - setChanged() - return True - except Exception as e: - setMsg("Failed to update memory.") - setMsg(str(e)) - setFailed() - return False - - def set_Memory_Policy(self, name, memory_policy): - VM = self.get_VM(name) - VM.memory_policy.guaranteed = int(int(memory_policy) * 1024 * 1024 * 1024) - try: - VM.update() - setMsg("The memory policy has been updated.") - setChanged() - return True - except Exception as e: - setMsg("Failed to update memory policy.") - setMsg(str(e)) - setFailed() - return False - - def set_CPU(self, name, cpu): - VM = self.get_VM(name) - VM.cpu.topology.cores = int(cpu) - try: - VM.update() - setMsg("The number of CPUs has been updated.") - setChanged() - return True - except Exception as e: - setMsg("Failed to update the number of CPUs.") - setMsg(str(e)) - setFailed() - return False - - def set_CPU_share(self, name, cpu_share): - VM = self.get_VM(name) - VM.cpu_shares = int(cpu_share) - try: - VM.update() - setMsg("The CPU share has been updated.") - setChanged() - return True - except Exception as e: - setMsg("Failed to update the CPU share.") - setMsg(str(e)) - setFailed() - return False - - def set_Disk(self, diskname, disksize, diskinterface, diskboot): - DISK = self.get_disk(diskname) - setMsg("Checking disk " + diskname) - if DISK.get_bootable() != diskboot: - try: - DISK.set_bootable(diskboot) - setMsg("Updated the boot option on the disk.") - setChanged() - except Exception as e: - setMsg("Failed to set the boot option on the disk.") - setMsg(str(e)) - setFailed() - return False - else: - setMsg("The boot option of the disk is correct") - if int(DISK.size) < (1024 * 1024 * 1024 * int(disksize)): - try: - DISK.size = (1024 * 1024 * 1024 * int(disksize)) - setMsg("Updated the size of the disk.") - setChanged() - except Exception as e: - setMsg("Failed to update the size of the disk.") - setMsg(str(e)) - setFailed() - return False - elif int(DISK.size) < (1024 * 1024 * 1024 * int(disksize)): - setMsg("Shrinking disks is not supported") - setMsg(str(e)) - setFailed() - return False - else: - setMsg("The size of the disk is correct") - if str(DISK.interface) != str(diskinterface): - try: - DISK.interface = diskinterface - setMsg("Updated the interface of the disk.") - setChanged() - except Exception as e: - setMsg("Failed to update the interface of the disk.") - setMsg(str(e)) - setFailed() - return False - else: - setMsg("The interface of the disk is correct") - return True - - def set_NIC(self, vmname, nicname, newname, vlan, interface): - NIC = self.get_NIC(vmname, nicname) - VM = self.get_VM(vmname) - CLUSTER = self.get_cluster_byid(VM.cluster.id) - DC = self.get_DC_byid(CLUSTER.data_center.id) - NETWORK = self.get_network(str(DC.name), vlan) - checkFail() - if NIC.name != newname: - NIC.name = newname - setMsg('Updating iface name to ' + newname) - setChanged() - if str(NIC.network.id) != str(NETWORK.id): - NIC.set_network(NETWORK) - setMsg('Updating iface network to ' + vlan) - setChanged() - if NIC.interface != interface: - NIC.interface = interface - setMsg('Updating iface interface to ' + interface) - setChanged() - try: - NIC.update() - setMsg('iface has succesfully been updated.') - except Exception as e: - setMsg("Failed to update the iface.") - setMsg(str(e)) - setFailed() - return False - return True - - def set_DeleteProtection(self, vmname, del_prot): - VM = self.get_VM(vmname) - VM.delete_protected = del_prot - try: - VM.update() - setChanged() - except Exception as e: - setMsg("Failed to update delete protection.") - setMsg(str(e)) - setFailed() - return False - return True - - def set_BootOrder(self, vmname, boot_order): - VM = self.get_VM(vmname) - bootorder = [] - for device in boot_order: - bootorder.append(params.Boot(dev=device)) - VM.os.boot = bootorder - - try: - VM.update() - setChanged() - except Exception as e: - setMsg("Failed to update the boot order.") - setMsg(str(e)) - setFailed() - return False - return True - - def set_Host(self, host_name, cluster, ifaces): - HOST = self.get_Host(host_name) - CLUSTER = self.get_cluster(cluster) - - if HOST is None: - setMsg("Host does not exist.") - ifacelist = dict() - networklist = [] - manageip = '' - - try: - for iface in ifaces: - try: - setMsg('creating host interface ' + iface['name']) - if 'management' in iface.keys(): - manageip = iface['ip'] - if 'boot_protocol' not in iface.keys(): - if 'ip' in iface.keys(): - iface['boot_protocol'] = 'static' - else: - iface['boot_protocol'] = 'none' - if 'ip' not in iface.keys(): - iface['ip'] = '' - if 'netmask' not in iface.keys(): - iface['netmask'] = '' - if 'gateway' not in iface.keys(): - iface['gateway'] = '' - - if 'network' in iface.keys(): - if 'bond' in iface.keys(): - bond = [] - for slave in iface['bond']: - bond.append(ifacelist[slave]) - try: - tmpiface = params.Bonding( - slaves = params.Slaves(host_nic = bond), - options = params.Options( - option = [ - params.Option(name = 'miimon', value = '100'), - params.Option(name = 'mode', value = '4') - ] - ) - ) - except Exception as e: - setMsg('Failed to create the bond for ' + iface['name']) - setFailed() - setMsg(str(e)) - return False - try: - tmpnetwork = params.HostNIC( - network = params.Network(name = iface['network']), - name = iface['name'], - boot_protocol = iface['boot_protocol'], - ip = params.IP( - address = iface['ip'], - netmask = iface['netmask'], - gateway = iface['gateway'] - ), - override_configuration = True, - bonding = tmpiface) - networklist.append(tmpnetwork) - setMsg('Applying network ' + iface['name']) - except Exception as e: - setMsg('Failed to set' + iface['name'] + ' as network interface') - setFailed() - setMsg(str(e)) - return False - else: - tmpnetwork = params.HostNIC( - network = params.Network(name = iface['network']), - name = iface['name'], - boot_protocol = iface['boot_protocol'], - ip = params.IP( - address = iface['ip'], - netmask = iface['netmask'], - gateway = iface['gateway'] - )) - networklist.append(tmpnetwork) - setMsg('Applying network ' + iface['name']) - else: - tmpiface = params.HostNIC( - name=iface['name'], - network=params.Network(), - boot_protocol=iface['boot_protocol'], - ip=params.IP( - address=iface['ip'], - netmask=iface['netmask'], - gateway=iface['gateway'] - )) - ifacelist[iface['name']] = tmpiface - except Exception as e: - setMsg('Failed to set ' + iface['name']) - setFailed() - setMsg(str(e)) - return False - except Exception as e: - setMsg('Failed to set networks') - setMsg(str(e)) - setFailed() - return False - - if manageip == '': - setMsg('No management network is defined') - setFailed() - return False - - try: - HOST = params.Host(name=host_name, address=manageip, cluster=CLUSTER, ssh=params.SSH(authentication_method='publickey')) - if self.conn.hosts.add(HOST): - setChanged() - HOST = self.get_Host(host_name) - state = HOST.status.state - while (state != 'non_operational' and state != 'up'): - HOST = self.get_Host(host_name) - state = HOST.status.state - time.sleep(1) - if state == 'non_responsive': - setMsg('Failed to add host to RHEVM') - setFailed() - return False - - setMsg('status host: up') - time.sleep(5) - - HOST = self.get_Host(host_name) - state = HOST.status.state - setMsg('State before setting to maintenance: ' + str(state)) - HOST.deactivate() - while state != 'maintenance': - HOST = self.get_Host(host_name) - state = HOST.status.state - time.sleep(1) - setMsg('status host: maintenance') - - try: - HOST.nics.setupnetworks(params.Action( - force=True, - check_connectivity = False, - host_nics = params.HostNics(host_nic = networklist) - )) - setMsg('nics are set') - except Exception as e: - setMsg('Failed to apply networkconfig') - setFailed() - setMsg(str(e)) - return False - - try: - HOST.commitnetconfig() - setMsg('Network config is saved') - except Exception as e: - setMsg('Failed to save networkconfig') - setFailed() - setMsg(str(e)) - return False - except Exception as e: - if 'The Host name is already in use' in str(e): - setMsg("Host already exists") - else: - setMsg("Failed to add host") - setFailed() - setMsg(str(e)) - return False - - HOST.activate() - while state != 'up': - HOST = self.get_Host(host_name) - state = HOST.status.state - time.sleep(1) - if state == 'non_responsive': - setMsg('Failed to apply networkconfig.') - setFailed() - return False - setMsg('status host: up') - else: - setMsg("Host exists.") - - return True - - def del_NIC(self, vmname, nicname): - return self.get_NIC(vmname, nicname).delete() - - def remove_VM(self, vmname): - VM = self.get_VM(vmname) - try: - VM.delete() - except Exception as e: - setMsg("Failed to remove VM.") - setMsg(str(e)) - setFailed() - return False - return True - - def start_VM(self, vmname, timeout): - VM = self.get_VM(vmname) - try: - VM.start() - except Exception as e: - setMsg("Failed to start VM.") - setMsg(str(e)) - setFailed() - return False - return self.wait_VM(vmname, "up", timeout) - - def wait_VM(self, vmname, state, timeout): - VM = self.get_VM(vmname) - while VM.status.state != state: - VM = self.get_VM(vmname) - time.sleep(10) - if timeout is not False: - timeout -= 10 - if timeout <= 0: - setMsg("Timeout expired") - setFailed() - return False - return True - - def stop_VM(self, vmname, timeout): - VM = self.get_VM(vmname) - try: - VM.stop() - except Exception as e: - setMsg("Failed to stop VM.") - setMsg(str(e)) - setFailed() - return False - return self.wait_VM(vmname, "down", timeout) - - def set_CD(self, vmname, cd_drive): - VM = self.get_VM(vmname) - try: - if str(VM.status.state) == 'down': - cdrom = params.CdRom(file=cd_iso) - VM.cdroms.add(cdrom) - setMsg("Attached the image.") - setChanged() - else: - cdrom = VM.cdroms.get(id="00000000-0000-0000-0000-000000000000") - cdrom.set_file(cd_iso) - cdrom.update(current=True) - setMsg("Attached the image.") - setChanged() - except Exception as e: - setMsg("Failed to attach image.") - setMsg(str(e)) - setFailed() - return False - return True - - def set_VM_Host(self, vmname, vmhost): - VM = self.get_VM(vmname) - HOST = self.get_Host(vmhost) - try: - VM.placement_policy.host = HOST - VM.update() - setMsg("Set startup host to " + vmhost) - setChanged() - except Exception as e: - setMsg("Failed to set startup host.") - setMsg(str(e)) - setFailed() - return False - return True - - def migrate_VM(self, vmname, vmhost): - VM = self.get_VM(vmname) - - HOST = self.get_Host_byid(VM.host.id) - if str(HOST.name) != vmhost: - try: - vm.migrate( - action=params.Action( - host=params.Host( - name=vmhost, - ) - ), - ) - setChanged() - setMsg("VM migrated to " + vmhost) - except Exception as e: - setMsg("Failed to set startup host.") - setMsg(str(e)) - setFailed() - return False - return True - - def remove_CD(self, vmname): - VM = self.get_VM(vmname) - try: - VM.cdroms.get(id="00000000-0000-0000-0000-000000000000").delete() - setMsg("Removed the image.") - setChanged() - except Exception as e: - setMsg("Failed to remove the image.") - setMsg(str(e)) - setFailed() - return False - return True - - -class RHEV(object): - def __init__(self, module): - self.module = module - - def __get_conn(self): - self.conn = RHEVConn(self.module) - return self.conn - - def test(self): - self.__get_conn() - return "OK" - - def getVM(self, name): - self.__get_conn() - VM = self.conn.get_VM(name) - if VM: - vminfo = dict() - vminfo['uuid'] = VM.id - vminfo['name'] = VM.name - vminfo['status'] = VM.status.state - vminfo['cpu_cores'] = VM.cpu.topology.cores - vminfo['cpu_sockets'] = VM.cpu.topology.sockets - vminfo['cpu_shares'] = VM.cpu_shares - vminfo['memory'] = (int(VM.memory) / 1024 / 1024 / 1024) - vminfo['mem_pol'] = (int(VM.memory_policy.guaranteed) / 1024 / 1024 / 1024) - vminfo['os'] = VM.get_os().type_ - vminfo['del_prot'] = VM.delete_protected - try: - vminfo['host'] = str(self.conn.get_Host_byid(str(VM.host.id)).name) - except Exception as e: - vminfo['host'] = None - vminfo['boot_order'] = [] - for boot_dev in VM.os.get_boot(): - vminfo['boot_order'].append(str(boot_dev.dev)) - vminfo['disks'] = [] - for DISK in VM.disks.list(): - disk = dict() - disk['name'] = DISK.name - disk['size'] = (int(DISK.size) / 1024 / 1024 / 1024) - disk['domain'] = str((self.conn.get_domain_byid(DISK.get_storage_domains().get_storage_domain()[0].id)).name) - disk['interface'] = DISK.interface - vminfo['disks'].append(disk) - vminfo['ifaces'] = [] - for NIC in VM.nics.list(): - iface = dict() - iface['name'] = str(NIC.name) - iface['vlan'] = str(self.conn.get_network_byid(NIC.get_network().id).name) - iface['interface'] = NIC.interface - iface['mac'] = NIC.mac.address - vminfo['ifaces'].append(iface) - vminfo[str(NIC.name)] = NIC.mac.address - CLUSTER = self.conn.get_cluster_byid(VM.cluster.id) - if CLUSTER: - vminfo['cluster'] = CLUSTER.name - else: - vminfo = False - return vminfo - - def createVMimage(self, name, cluster, template, disks): - self.__get_conn() - return self.conn.createVMimage(name, cluster, template, disks) - - def createVM(self, name, cluster, os, actiontype): - self.__get_conn() - return self.conn.createVM(name, cluster, os, actiontype) - - def setMemory(self, name, memory): - self.__get_conn() - return self.conn.set_Memory(name, memory) - - def setMemoryPolicy(self, name, memory_policy): - self.__get_conn() - return self.conn.set_Memory_Policy(name, memory_policy) - - def setCPU(self, name, cpu): - self.__get_conn() - return self.conn.set_CPU(name, cpu) - - def setCPUShare(self, name, cpu_share): - self.__get_conn() - return self.conn.set_CPU_share(name, cpu_share) - - def setDisks(self, name, disks): - self.__get_conn() - counter = 0 - bootselect = False - for disk in disks: - if 'bootable' in disk: - if disk['bootable'] is True: - bootselect = True - - for disk in disks: - diskname = name + "_Disk" + str(counter) + "_" + disk.get('name', '').replace('/', '_') - disksize = disk.get('size', 1) - diskdomain = disk.get('domain', None) - if diskdomain is None: - setMsg("`domain` is a required disk key.") - setFailed() - return False - diskinterface = disk.get('interface', 'virtio') - diskformat = disk.get('format', 'raw') - diskallocationtype = disk.get('thin', False) - diskboot = disk.get('bootable', False) - - if bootselect is False and counter == 0: - diskboot = True - - DISK = self.conn.get_disk(diskname) - - if DISK is None: - self.conn.createDisk(name, diskname, disksize, diskdomain, diskinterface, diskformat, diskallocationtype, diskboot) - else: - self.conn.set_Disk(diskname, disksize, diskinterface, diskboot) - checkFail() - counter += 1 - - return True - - def setNetworks(self, vmname, ifaces): - self.__get_conn() - VM = self.conn.get_VM(vmname) - - counter = 0 - length = len(ifaces) - - for NIC in VM.nics.list(): - if counter < length: - iface = ifaces[counter] - name = iface.get('name', None) - if name is None: - setMsg("`name` is a required iface key.") - setFailed() - elif str(name) != str(NIC.name): - setMsg("ifaces are in the wrong order, rebuilding everything.") - for NIC in VM.nics.list(): - self.conn.del_NIC(vmname, NIC.name) - self.setNetworks(vmname, ifaces) - checkFail() - return True - vlan = iface.get('vlan', None) - if vlan is None: - setMsg("`vlan` is a required iface key.") - setFailed() - checkFail() - interface = iface.get('interface', 'virtio') - self.conn.set_NIC(vmname, str(NIC.name), name, vlan, interface) - else: - self.conn.del_NIC(vmname, NIC.name) - counter += 1 - checkFail() - - while counter < length: - iface = ifaces[counter] - name = iface.get('name', None) - if name is None: - setMsg("`name` is a required iface key.") - setFailed() - vlan = iface.get('vlan', None) - if vlan is None: - setMsg("`vlan` is a required iface key.") - setFailed() - if failed is True: - return False - interface = iface.get('interface', 'virtio') - self.conn.createNIC(vmname, name, vlan, interface) - - counter += 1 - checkFail() - return True - - def setDeleteProtection(self, vmname, del_prot): - self.__get_conn() - VM = self.conn.get_VM(vmname) - if bool(VM.delete_protected) != bool(del_prot): - self.conn.set_DeleteProtection(vmname, del_prot) - checkFail() - setMsg("`delete protection` has been updated.") - else: - setMsg("`delete protection` already has the right value.") - return True - - def setBootOrder(self, vmname, boot_order): - self.__get_conn() - VM = self.conn.get_VM(vmname) - bootorder = [] - for boot_dev in VM.os.get_boot(): - bootorder.append(str(boot_dev.dev)) - - if boot_order != bootorder: - self.conn.set_BootOrder(vmname, boot_order) - setMsg('The boot order has been set') - else: - setMsg('The boot order has already been set') - return True - - def removeVM(self, vmname): - self.__get_conn() - self.setPower(vmname, "down", 300) - return self.conn.remove_VM(vmname) - - def setPower(self, vmname, state, timeout): - self.__get_conn() - VM = self.conn.get_VM(vmname) - if VM is None: - setMsg("VM does not exist.") - setFailed() - return False - - if state == VM.status.state: - setMsg("VM state was already " + state) - else: - if state == "up": - setMsg("VM is going to start") - self.conn.start_VM(vmname, timeout) - setChanged() - elif state == "down": - setMsg("VM is going to stop") - self.conn.stop_VM(vmname, timeout) - setChanged() - elif state == "restarted": - self.setPower(vmname, "down", timeout) - checkFail() - self.setPower(vmname, "up", timeout) - checkFail() - setMsg("the vm state is set to " + state) - return True - - def setCD(self, vmname, cd_drive): - self.__get_conn() - if cd_drive: - return self.conn.set_CD(vmname, cd_drive) - else: - return self.conn.remove_CD(vmname) - - def setVMHost(self, vmname, vmhost): - self.__get_conn() - return self.conn.set_VM_Host(vmname, vmhost) - - VM = self.conn.get_VM(vmname) - HOST = self.conn.get_Host(vmhost) - - if VM.placement_policy.host is None: - self.conn.set_VM_Host(vmname, vmhost) - elif str(VM.placement_policy.host.id) != str(HOST.id): - self.conn.set_VM_Host(vmname, vmhost) - else: - setMsg("VM's startup host was already set to " + vmhost) - checkFail() - - if str(VM.status.state) == "up": - self.conn.migrate_VM(vmname, vmhost) - checkFail() - - return True - - def setHost(self, hostname, cluster, ifaces): - self.__get_conn() - return self.conn.set_Host(hostname, cluster, ifaces) - - -def checkFail(): - if failed: - module.fail_json(msg=msg) - else: - return True - - -def setFailed(): - global failed - failed = True - - -def setChanged(): - global changed - changed = True - - -def setMsg(message): - global failed - msg.append(message) - - -def core(module): - - r = RHEV(module) - - state = module.params.get('state', 'present') - - if state == 'ping': - r.test() - return RHEV_SUCCESS, {"ping": "pong"} - elif state == 'info': - name = module.params.get('name') - if not name: - setMsg("`name` is a required argument.") - return RHEV_FAILED, msg - vminfo = r.getVM(name) - return RHEV_SUCCESS, {'changed': changed, 'msg': msg, 'vm': vminfo} - elif state == 'present': - created = False - name = module.params.get('name') - if not name: - setMsg("`name` is a required argument.") - return RHEV_FAILED, msg - actiontype = module.params.get('type') - if actiontype == 'server' or actiontype == 'desktop': - vminfo = r.getVM(name) - if vminfo: - setMsg('VM exists') - else: - # Create VM - cluster = module.params.get('cluster') - if cluster is None: - setMsg("cluster is a required argument.") - setFailed() - template = module.params.get('image') - if template: - disks = module.params.get('disks') - if disks is None: - setMsg("disks is a required argument.") - setFailed() - checkFail() - if r.createVMimage(name, cluster, template, disks) is False: - return RHEV_FAILED, vminfo - else: - os = module.params.get('osver') - if os is None: - setMsg("osver is a required argument.") - setFailed() - checkFail() - if r.createVM(name, cluster, os, actiontype) is False: - return RHEV_FAILED, vminfo - created = True - - # Set MEMORY and MEMORY POLICY - vminfo = r.getVM(name) - memory = module.params.get('vmmem') - if memory is not None: - memory_policy = module.params.get('mempol') - if int(memory_policy) == 0: - memory_policy = memory - mem_pol_nok = True - if int(vminfo['mem_pol']) == int(memory_policy): - setMsg("Memory is correct") - mem_pol_nok = False - - mem_nok = True - if int(vminfo['memory']) == int(memory): - setMsg("Memory is correct") - mem_nok = False - - if memory_policy > memory: - setMsg('memory_policy cannot have a higher value than memory.') - return RHEV_FAILED, msg - - if mem_nok and mem_pol_nok: - if int(memory_policy) > int(vminfo['memory']): - r.setMemory(vminfo['name'], memory) - r.setMemoryPolicy(vminfo['name'], memory_policy) - else: - r.setMemoryPolicy(vminfo['name'], memory_policy) - r.setMemory(vminfo['name'], memory) - elif mem_nok: - r.setMemory(vminfo['name'], memory) - elif mem_pol_nok: - r.setMemoryPolicy(vminfo['name'], memory_policy) - checkFail() - - # Set CPU - cpu = module.params.get('vmcpu') - if int(vminfo['cpu_cores']) == int(cpu): - setMsg("Number of CPUs is correct") - else: - if r.setCPU(vminfo['name'], cpu) is False: - return RHEV_FAILED, msg - - # Set CPU SHARE - cpu_share = module.params.get('cpu_share') - if cpu_share is not None: - if int(vminfo['cpu_shares']) == int(cpu_share): - setMsg("CPU share is correct.") - else: - if r.setCPUShare(vminfo['name'], cpu_share) is False: - return RHEV_FAILED, msg - - # Set DISKS - disks = module.params.get('disks') - if disks is not None: - if r.setDisks(vminfo['name'], disks) is False: - return RHEV_FAILED, msg - - # Set NETWORKS - ifaces = module.params.get('ifaces', None) - if ifaces is not None: - if r.setNetworks(vminfo['name'], ifaces) is False: - return RHEV_FAILED, msg - - # Set Delete Protection - del_prot = module.params.get('del_prot') - if r.setDeleteProtection(vminfo['name'], del_prot) is False: - return RHEV_FAILED, msg - - # Set Boot Order - boot_order = module.params.get('boot_order') - if r.setBootOrder(vminfo['name'], boot_order) is False: - return RHEV_FAILED, msg - - # Set VM Host - vmhost = module.params.get('vmhost') - if vmhost is not False and vmhost is not "False": - if r.setVMHost(vminfo['name'], vmhost) is False: - return RHEV_FAILED, msg - - vminfo = r.getVM(name) - vminfo['created'] = created - return RHEV_SUCCESS, {'changed': changed, 'msg': msg, 'vm': vminfo} - - if actiontype == 'host': - cluster = module.params.get('cluster') - if cluster is None: - setMsg("cluster is a required argument.") - setFailed() - ifaces = module.params.get('ifaces') - if ifaces is None: - setMsg("ifaces is a required argument.") - setFailed() - if r.setHost(name, cluster, ifaces) is False: - return RHEV_FAILED, msg - return RHEV_SUCCESS, {'changed': changed, 'msg': msg} - - elif state == 'absent': - name = module.params.get('name') - if not name: - setMsg("`name` is a required argument.") - return RHEV_FAILED, msg - actiontype = module.params.get('type') - if actiontype == 'server' or actiontype == 'desktop': - vminfo = r.getVM(name) - if vminfo: - setMsg('VM exists') - - # Set Delete Protection - del_prot = module.params.get('del_prot') - if r.setDeleteProtection(vminfo['name'], del_prot) is False: - return RHEV_FAILED, msg - - # Remove VM - if r.removeVM(vminfo['name']) is False: - return RHEV_FAILED, msg - setMsg('VM has been removed.') - vminfo['state'] = 'DELETED' - else: - setMsg('VM was already removed.') - return RHEV_SUCCESS, {'changed': changed, 'msg': msg, 'vm': vminfo} - - elif state == 'up' or state == 'down' or state == 'restarted': - name = module.params.get('name') - if not name: - setMsg("`name` is a required argument.") - return RHEV_FAILED, msg - timeout = module.params.get('timeout') - if r.setPower(name, state, timeout) is False: - return RHEV_FAILED, msg - vminfo = r.getVM(name) - return RHEV_SUCCESS, {'changed': changed, 'msg': msg, 'vm': vminfo} - - elif state == 'cd': - name = module.params.get('name') - cd_drive = module.params.get('cd_drive') - if r.setCD(name, cd_drive) is False: - return RHEV_FAILED, msg - return RHEV_SUCCESS, {'changed': changed, 'msg': msg} - - -def main(): - global module - module = AnsibleModule( - argument_spec = dict( - state = dict(default='present', choices=['ping', 'present', 'absent', 'up', 'down', 'restarted', 'cd', 'info']), - user = dict(default="admin@internal"), - password = dict(required=True), - server = dict(default="127.0.0.1"), - port = dict(default="443"), - insecure_api = dict(default=False, type='bool'), - name = dict(), - image = dict(default=False), - datacenter = dict(default="Default"), - type = dict(default="server", choices=['server', 'desktop', 'host']), - cluster = dict(default=''), - vmhost = dict(default=False), - vmcpu = dict(default="2"), - vmmem = dict(default="1"), - disks = dict(), - osver = dict(default="rhel_6x64"), - ifaces = dict(aliases=['nics', 'interfaces']), - timeout = dict(default=False), - mempol = dict(default="1"), - vm_ha = dict(default=True), - cpu_share = dict(default="0"), - boot_order = dict(default=["network", "hd"]), - del_prot = dict(default=True, type="bool"), - cd_drive = dict(default=False) - ), - ) - - if not HAS_SDK: - module.fail_json( - msg='The `ovirtsdk` module is not importable. Check the requirements.' - ) - - rc = RHEV_SUCCESS - try: - rc, result = core(module) - except Exception as e: - module.fail_json(msg=str(e)) - - if rc != 0: # something went wrong emit the msg - module.fail_json(rc=rc, msg=result) - else: - module.exit_json(**result) - - -# import module snippets -from ansible.module_utils.basic import * - -if __name__ == '__main__': - main() +#!/usr/bin/python + +# (c) 2016, Timothy Vandenbrande +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +DOCUMENTATION = ''' +--- +module: rhevm +author: Timothy Vandenbrande +short_description: RHEV/oVirt automation +description: + - Allows you to create/remove/update or powermanage virtual machines on a RHEV/oVirt platform. +version_added: "2.2" +requirements: + - ovirtsdk +options: + user: + description: + - The user to authenticate with. + default: "admin@internal" + required: false + server: + description: + - The name/ip of your RHEV-m/oVirt instance. + default: "127.0.0.1" + required: false + port: + description: + - The port on which the API is reacheable. + default: "443" + required: false + insecure_api: + description: + - A boolean switch to make a secure or insecure connection to the server. + default: false + required: false + name: + description: + - The name of the VM. + cluster: + description: + - The rhev/ovirt cluster in which you want you VM to start. + required: false + datacenter: + description: + - The rhev/ovirt datacenter in which you want you VM to start. + required: false + default: "Default" + state: + description: + - This serves to create/remove/update or powermanage your VM. + default: "present" + required: false + choices: ['ping', 'present', 'absent', 'up', 'down', 'restarted', 'cd', 'info'] + image: + description: + - The template to use for the VM. + default: null + required: false + type: + description: + - To define if the VM is a server or desktop. + default: server + required: false + choices: [ 'server', 'desktop', 'host' ] + vmhost: + description: + - The host you wish your VM to run on. + required: false + vmcpu: + description: + - The number of CPUs you want in your VM. + default: "2" + required: false + cpu_share: + description: + - This parameter is used to configure the cpu share. + default: "0" + required: false + vmmem: + description: + - The amount of memory you want your VM to use (in GB). + default: "1" + required: false + osver: + description: + - The operationsystem option in RHEV/oVirt. + default: "rhel_6x64" + required: false + mempol: + description: + - The minimum amount of memory you wish to reserve for this system. + default: "1" + required: false + vm_ha: + description: + - To make your VM High Available. + default: true + required: false + disks: + description: + - This option uses complex arguments and is a list of disks with the options name, size and domain. + required: false + ifaces: + description: + - This option uses complex arguments and is a list of interfaces with the options name and vlan. + aliases: ['nics', 'interfaces'] + required: false + boot_order: + description: + - This option uses complex arguments and is a list of items that specify the bootorder. + default: ["network","hd"] + required: false + del_prot: + description: + - This option sets the delete protection checkbox. + default: true + required: false + cd_drive: + description: + - The CD you wish to have mounted on the VM when I(state = 'CD'). + default: null + required: false + timeout: + description: + - The timeout you wish to define for power actions. + - When I(state = 'up') + - When I(state = 'down') + - When I(state = 'restarted') + default: null + required: false +''' + +RETURN = ''' +vm: + description: Returns all of the VMs variables and execution. + returned: always + type: dict + sample: '{ + "boot_order": [ + "hd", + "network" + ], + "changed": true, + "changes": [ + "Delete Protection" + ], + "cluster": "C1", + "cpu_share": "0", + "created": false, + "datacenter": "Default", + "del_prot": true, + "disks": [ + { + "domain": "ssd-san", + "name": "OS", + "size": 40 + } + ], + "eth0": "00:00:5E:00:53:00", + "eth1": "00:00:5E:00:53:01", + "eth2": "00:00:5E:00:53:02", + "exists": true, + "failed": false, + "ifaces": [ + { + "name": "eth0", + "vlan": "Management" + }, + { + "name": "eth1", + "vlan": "Internal" + }, + { + "name": "eth2", + "vlan": "External" + } + ], + "image": false, + "mempol": "0", + "msg": [ + "VM exists", + "cpu_share was already set to 0", + "VM high availability was already set to True", + "The boot order has already been set", + "VM delete protection has been set to True", + "Disk web2_Disk0_OS already exists", + "The VM starting host was already set to host416" + ], + "name": "web2", + "type": "server", + "uuid": "4ba5a1be-e60b-4368-9533-920f156c817b", + "vm_ha": true, + "vmcpu": "4", + "vmhost": "host416", + "vmmem": "16" + }' +''' + +EXAMPLES = ''' +# basic get info from VM + action: rhevm + args: + name: "demo" + user: "{{ rhev.admin.name }}" + password: "{{ rhev.admin.pass }}" + server: "rhevm01" + state: "info" + +# basic create example from image + action: rhevm + args: + name: "demo" + user: "{{ rhev.admin.name }}" + password: "{{ rhev.admin.pass }}" + server: "rhevm01" + state: "present" + image: "centos7_x64" + cluster: "centos" + +# power management + action: rhevm + args: + name: "uptime_server" + user: "{{ rhev.admin.name }}" + password: "{{ rhev.admin.pass }}" + server: "rhevm01" + cluster: "RH" + state: "down" + image: "centos7_x64" + cluster: "centos + +# multi disk, multi nic create example + action: rhevm + args: + name: "server007" + user: "{{ rhev.admin.name }}" + password: "{{ rhev.admin.pass }}" + server: "rhevm01" + cluster: "RH" + state: "present" + type: "server" + vmcpu: 4 + vmmem: 2 + ifaces: + - name: "eth0" + vlan: "vlan2202" + - name: "eth1" + vlan: "vlan36" + - name: "eth2" + vlan: "vlan38" + - name: "eth3" + vlan: "vlan2202" + disks: + - name: "root" + size: 10 + domain: "ssd-san" + - name: "swap" + size: 10 + domain: "15kiscsi-san" + - name: "opt" + size: 10 + domain: "15kiscsi-san" + - name: "var" + size: 10 + domain: "10kiscsi-san" + - name: "home" + size: 10 + domain: "sata-san" + boot_order: + - "network" + - "hd" + +# add a CD to the disk cd_drive + action: rhevm + args: + name: 'server007' + user: "{{ rhev.admin.name }}" + password: "{{ rhev.admin.pass }}" + state: 'cd' + cd_drive: 'rhev-tools-setup.iso' + +# new host deployment + host network configuration + action: rhevm + args: + name: "ovirt_node007" + password: "{{ rhevm.admin.pass }}" + type: "host" + state: present + cluster: "rhevm01" + ifaces: + - name: em1 + - name: em2 + - name: p3p1 + ip: '172.31.224.200' + netmask: '255.255.254.0' + - name: p3p2 + ip: '172.31.225.200' + netmask: '255.255.254.0' + - name: bond0 + bond: + - em1 + - em2 + network: 'rhevm' + ip: '172.31.222.200' + netmask: '255.255.255.0' + management: True + - name: bond0.36 + network: 'vlan36' + ip: '10.2.36.200' + netmask: '255.255.254.0' + gateway: '10.2.36.254' + - name: bond0.2202 + network: 'vlan2202' + - name: bond0.38 + network: 'vlan38' +''' + +import time +import sys +import traceback +import json + +try: + from ovirtsdk.api import API + from ovirtsdk.xml import params + HAS_SDK = True +except ImportError: + HAS_SDK = False + +RHEV_FAILED = 1 +RHEV_SUCCESS = 0 +RHEV_UNAVAILABLE = 2 + +RHEV_TYPE_OPTS = ['server', 'desktop', 'host'] +STATE_OPTS = ['ping', 'present', 'absent', 'up', 'down', 'restart', 'cd', 'info'] + +global msg, changed, failed +msg = [] +changed = False +failed = False + + +class RHEVConn(object): + 'Connection to RHEV-M' + def __init__(self, module): + self.module = module + + user = module.params.get('user') + password = module.params.get('password') + server = module.params.get('server') + port = module.params.get('port') + insecure_api = module.params.get('insecure_api') + + url = "https://%s:%s" % (server, port) + + try: + api = API(url=url, username=user, password=password, insecure=str(insecure_api)) + api.test() + self.conn = api + except: + raise Exception("Failed to connect to RHEV-M.") + + def __del__(self): + self.conn.disconnect() + + def createVMimage(self, name, cluster, template): + try: + vmparams = params.VM( + name=name, + cluster=self.conn.clusters.get(name=cluster), + template=self.conn.templates.get(name=template), + disks=params.Disks(clone=True) + ) + self.conn.vms.add(vmparams) + setMsg("VM is created") + setChanged() + return True + except Exception as e: + setMsg("Failed to create VM") + setMsg(str(e)) + setFailed() + return False + + def createVM(self, name, cluster, os, actiontype): + try: + vmparams = params.VM( + name=name, + cluster=self.conn.clusters.get(name=cluster), + os=params.OperatingSystem(type_=os), + template=self.conn.templates.get(name="Blank"), + type_=actiontype + ) + self.conn.vms.add(vmparams) + setMsg("VM is created") + setChanged() + return True + except Exception as e: + setMsg("Failed to create VM") + setMsg(str(e)) + setFailed() + return False + + def createDisk(self, vmname, diskname, disksize, diskdomain, diskinterface, diskformat, diskallocationtype, diskboot): + VM = self.get_VM(vmname) + + newdisk = params.Disk( + name=diskname, + size=1024 * 1024 * 1024 * int(disksize), + wipe_after_delete=True, + sparse=diskallocationtype, + interface=diskinterface, + format=diskformat, + bootable=diskboot, + storage_domains=params.StorageDomains( + storage_domain=[self.get_domain(diskdomain)] + ) + ) + + try: + VM.disks.add(newdisk) + VM.update() + setMsg("Successfully added disk " + diskname) + setChanged() + except Exception as e: + setFailed() + setMsg("Error attaching " + diskname + "disk, please recheck and remove any leftover configuration.") + setMsg(str(e)) + return False + + try: + currentdisk = VM.disks.get(name=diskname) + attempt = 1 + while currentdisk.status.state != 'ok': + currentdisk = VM.disks.get(name=diskname) + if attempt == 100: + setMsg("Error, disk %s, state %s" % (diskname, str(currentdisk.status.state))) + raise + else: + attempt += 1 + time.sleep(2) + setMsg("The disk " + diskname + " is ready.") + except Exception as e: + setFailed() + setMsg("Error getting the state of " + diskname + ".") + setMsg(str(e)) + return False + return True + + def createNIC(self, vmname, nicname, vlan, interface): + VM = self.get_VM(vmname) + CLUSTER = self.get_cluster_byid(VM.cluster.id) + DC = self.get_DC_byid(CLUSTER.data_center.id) + newnic = params.NIC( + name=nicname, + network=DC.networks.get(name=vlan), + interface=interface + ) + + try: + VM.nics.add(newnic) + VM.update() + setMsg("Successfully added iface " + nicname) + setChanged() + except Exception as e: + setFailed() + setMsg("Error attaching " + nicname + " iface, please recheck and remove any leftover configuration.") + setMsg(str(e)) + return False + + try: + currentnic = VM.nics.get(name=nicname) + attempt = 1 + while currentnic.active is not True: + currentnic = VM.nics.get(name=nicname) + if attempt == 100: + setMsg("Error, iface %s, state %s" % (nicname, str(currentnic.active))) + raise + else: + attempt += 1 + time.sleep(2) + setMsg("The iface " + nicname + " is ready.") + except Exception as e: + setFailed() + setMsg("Error getting the state of " + nicname + ".") + setMsg(str(e)) + return False + return True + + def get_DC(self, dc_name): + return self.conn.datacenters.get(name=dc_name) + + def get_DC_byid(self, dc_id): + return self.conn.datacenters.get(id=dc_id) + + def get_VM(self, vm_name): + return self.conn.vms.get(name=vm_name) + + def get_cluster_byid(self, cluster_id): + return self.conn.clusters.get(id=cluster_id) + + def get_cluster(self, cluster_name): + return self.conn.clusters.get(name=cluster_name) + + def get_domain_byid(self, dom_id): + return self.conn.storagedomains.get(id=dom_id) + + def get_domain(self, domain_name): + return self.conn.storagedomains.get(name=domain_name) + + def get_disk(self, disk): + return self.conn.disks.get(disk) + + def get_network(self, dc_name, network_name): + return self.get_DC(dc_name).networks.get(network_name) + + def get_network_byid(self, network_id): + return self.conn.networks.get(id=network_id) + + def get_NIC(self, vm_name, nic_name): + return self.get_VM(vm_name).nics.get(nic_name) + + def get_Host(self, host_name): + return self.conn.hosts.get(name=host_name) + + def get_Host_byid(self, host_id): + return self.conn.hosts.get(id=host_id) + + def set_Memory(self, name, memory): + VM = self.get_VM(name) + VM.memory = int(int(memory) * 1024 * 1024 * 1024) + try: + VM.update() + setMsg("The Memory has been updated.") + setChanged() + return True + except Exception as e: + setMsg("Failed to update memory.") + setMsg(str(e)) + setFailed() + return False + + def set_Memory_Policy(self, name, memory_policy): + VM = self.get_VM(name) + VM.memory_policy.guaranteed = int(int(memory_policy) * 1024 * 1024 * 1024) + try: + VM.update() + setMsg("The memory policy has been updated.") + setChanged() + return True + except Exception as e: + setMsg("Failed to update memory policy.") + setMsg(str(e)) + setFailed() + return False + + def set_CPU(self, name, cpu): + VM = self.get_VM(name) + VM.cpu.topology.cores = int(cpu) + try: + VM.update() + setMsg("The number of CPUs has been updated.") + setChanged() + return True + except Exception as e: + setMsg("Failed to update the number of CPUs.") + setMsg(str(e)) + setFailed() + return False + + def set_CPU_share(self, name, cpu_share): + VM = self.get_VM(name) + VM.cpu_shares = int(cpu_share) + try: + VM.update() + setMsg("The CPU share has been updated.") + setChanged() + return True + except Exception as e: + setMsg("Failed to update the CPU share.") + setMsg(str(e)) + setFailed() + return False + + def set_Disk(self, diskname, disksize, diskinterface, diskboot): + DISK = self.get_disk(diskname) + setMsg("Checking disk " + diskname) + if DISK.get_bootable() != diskboot: + try: + DISK.set_bootable(diskboot) + setMsg("Updated the boot option on the disk.") + setChanged() + except Exception as e: + setMsg("Failed to set the boot option on the disk.") + setMsg(str(e)) + setFailed() + return False + else: + setMsg("The boot option of the disk is correct") + if int(DISK.size) < (1024 * 1024 * 1024 * int(disksize)): + try: + DISK.size = (1024 * 1024 * 1024 * int(disksize)) + setMsg("Updated the size of the disk.") + setChanged() + except Exception as e: + setMsg("Failed to update the size of the disk.") + setMsg(str(e)) + setFailed() + return False + elif int(DISK.size) < (1024 * 1024 * 1024 * int(disksize)): + setMsg("Shrinking disks is not supported") + setMsg(str(e)) + setFailed() + return False + else: + setMsg("The size of the disk is correct") + if str(DISK.interface) != str(diskinterface): + try: + DISK.interface = diskinterface + setMsg("Updated the interface of the disk.") + setChanged() + except Exception as e: + setMsg("Failed to update the interface of the disk.") + setMsg(str(e)) + setFailed() + return False + else: + setMsg("The interface of the disk is correct") + return True + + def set_NIC(self, vmname, nicname, newname, vlan, interface): + NIC = self.get_NIC(vmname, nicname) + VM = self.get_VM(vmname) + CLUSTER = self.get_cluster_byid(VM.cluster.id) + DC = self.get_DC_byid(CLUSTER.data_center.id) + NETWORK = self.get_network(str(DC.name), vlan) + checkFail() + if NIC.name != newname: + NIC.name = newname + setMsg('Updating iface name to ' + newname) + setChanged() + if str(NIC.network.id) != str(NETWORK.id): + NIC.set_network(NETWORK) + setMsg('Updating iface network to ' + vlan) + setChanged() + if NIC.interface != interface: + NIC.interface = interface + setMsg('Updating iface interface to ' + interface) + setChanged() + try: + NIC.update() + setMsg('iface has succesfully been updated.') + except Exception as e: + setMsg("Failed to update the iface.") + setMsg(str(e)) + setFailed() + return False + return True + + def set_DeleteProtection(self, vmname, del_prot): + VM = self.get_VM(vmname) + VM.delete_protected = del_prot + try: + VM.update() + setChanged() + except Exception as e: + setMsg("Failed to update delete protection.") + setMsg(str(e)) + setFailed() + return False + return True + + def set_BootOrder(self, vmname, boot_order): + VM = self.get_VM(vmname) + bootorder = [] + for device in boot_order: + bootorder.append(params.Boot(dev=device)) + VM.os.boot = bootorder + + try: + VM.update() + setChanged() + except Exception as e: + setMsg("Failed to update the boot order.") + setMsg(str(e)) + setFailed() + return False + return True + + def set_Host(self, host_name, cluster, ifaces): + HOST = self.get_Host(host_name) + CLUSTER = self.get_cluster(cluster) + + if HOST is None: + setMsg("Host does not exist.") + ifacelist = dict() + networklist = [] + manageip = '' + + try: + for iface in ifaces: + try: + setMsg('creating host interface ' + iface['name']) + if 'management' in iface.keys(): + manageip = iface['ip'] + if 'boot_protocol' not in iface.keys(): + if 'ip' in iface.keys(): + iface['boot_protocol'] = 'static' + else: + iface['boot_protocol'] = 'none' + if 'ip' not in iface.keys(): + iface['ip'] = '' + if 'netmask' not in iface.keys(): + iface['netmask'] = '' + if 'gateway' not in iface.keys(): + iface['gateway'] = '' + + if 'network' in iface.keys(): + if 'bond' in iface.keys(): + bond = [] + for slave in iface['bond']: + bond.append(ifacelist[slave]) + try: + tmpiface = params.Bonding( + slaves = params.Slaves(host_nic = bond), + options = params.Options( + option = [ + params.Option(name = 'miimon', value = '100'), + params.Option(name = 'mode', value = '4') + ] + ) + ) + except Exception as e: + setMsg('Failed to create the bond for ' + iface['name']) + setFailed() + setMsg(str(e)) + return False + try: + tmpnetwork = params.HostNIC( + network = params.Network(name = iface['network']), + name = iface['name'], + boot_protocol = iface['boot_protocol'], + ip = params.IP( + address = iface['ip'], + netmask = iface['netmask'], + gateway = iface['gateway'] + ), + override_configuration = True, + bonding = tmpiface) + networklist.append(tmpnetwork) + setMsg('Applying network ' + iface['name']) + except Exception as e: + setMsg('Failed to set' + iface['name'] + ' as network interface') + setFailed() + setMsg(str(e)) + return False + else: + tmpnetwork = params.HostNIC( + network = params.Network(name = iface['network']), + name = iface['name'], + boot_protocol = iface['boot_protocol'], + ip = params.IP( + address = iface['ip'], + netmask = iface['netmask'], + gateway = iface['gateway'] + )) + networklist.append(tmpnetwork) + setMsg('Applying network ' + iface['name']) + else: + tmpiface = params.HostNIC( + name=iface['name'], + network=params.Network(), + boot_protocol=iface['boot_protocol'], + ip=params.IP( + address=iface['ip'], + netmask=iface['netmask'], + gateway=iface['gateway'] + )) + ifacelist[iface['name']] = tmpiface + except Exception as e: + setMsg('Failed to set ' + iface['name']) + setFailed() + setMsg(str(e)) + return False + except Exception as e: + setMsg('Failed to set networks') + setMsg(str(e)) + setFailed() + return False + + if manageip == '': + setMsg('No management network is defined') + setFailed() + return False + + try: + HOST = params.Host(name=host_name, address=manageip, cluster=CLUSTER, ssh=params.SSH(authentication_method='publickey')) + if self.conn.hosts.add(HOST): + setChanged() + HOST = self.get_Host(host_name) + state = HOST.status.state + while (state != 'non_operational' and state != 'up'): + HOST = self.get_Host(host_name) + state = HOST.status.state + time.sleep(1) + if state == 'non_responsive': + setMsg('Failed to add host to RHEVM') + setFailed() + return False + + setMsg('status host: up') + time.sleep(5) + + HOST = self.get_Host(host_name) + state = HOST.status.state + setMsg('State before setting to maintenance: ' + str(state)) + HOST.deactivate() + while state != 'maintenance': + HOST = self.get_Host(host_name) + state = HOST.status.state + time.sleep(1) + setMsg('status host: maintenance') + + try: + HOST.nics.setupnetworks(params.Action( + force=True, + check_connectivity = False, + host_nics = params.HostNics(host_nic = networklist) + )) + setMsg('nics are set') + except Exception as e: + setMsg('Failed to apply networkconfig') + setFailed() + setMsg(str(e)) + return False + + try: + HOST.commitnetconfig() + setMsg('Network config is saved') + except Exception as e: + setMsg('Failed to save networkconfig') + setFailed() + setMsg(str(e)) + return False + except Exception as e: + if 'The Host name is already in use' in str(e): + setMsg("Host already exists") + else: + setMsg("Failed to add host") + setFailed() + setMsg(str(e)) + return False + + HOST.activate() + while state != 'up': + HOST = self.get_Host(host_name) + state = HOST.status.state + time.sleep(1) + if state == 'non_responsive': + setMsg('Failed to apply networkconfig.') + setFailed() + return False + setMsg('status host: up') + else: + setMsg("Host exists.") + + return True + + def del_NIC(self, vmname, nicname): + return self.get_NIC(vmname, nicname).delete() + + def remove_VM(self, vmname): + VM = self.get_VM(vmname) + try: + VM.delete() + except Exception as e: + setMsg("Failed to remove VM.") + setMsg(str(e)) + setFailed() + return False + return True + + def start_VM(self, vmname, timeout): + VM = self.get_VM(vmname) + try: + VM.start() + except Exception as e: + setMsg("Failed to start VM.") + setMsg(str(e)) + setFailed() + return False + return self.wait_VM(vmname, "up", timeout) + + def wait_VM(self, vmname, state, timeout): + VM = self.get_VM(vmname) + while VM.status.state != state: + VM = self.get_VM(vmname) + time.sleep(10) + if timeout is not False: + timeout -= 10 + if timeout <= 0: + setMsg("Timeout expired") + setFailed() + return False + return True + + def stop_VM(self, vmname, timeout): + VM = self.get_VM(vmname) + try: + VM.stop() + except Exception as e: + setMsg("Failed to stop VM.") + setMsg(str(e)) + setFailed() + return False + return self.wait_VM(vmname, "down", timeout) + + def set_CD(self, vmname, cd_drive): + VM = self.get_VM(vmname) + try: + if str(VM.status.state) == 'down': + cdrom = params.CdRom(file=cd_iso) + VM.cdroms.add(cdrom) + setMsg("Attached the image.") + setChanged() + else: + cdrom = VM.cdroms.get(id="00000000-0000-0000-0000-000000000000") + cdrom.set_file(cd_iso) + cdrom.update(current=True) + setMsg("Attached the image.") + setChanged() + except Exception as e: + setMsg("Failed to attach image.") + setMsg(str(e)) + setFailed() + return False + return True + + def set_VM_Host(self, vmname, vmhost): + VM = self.get_VM(vmname) + HOST = self.get_Host(vmhost) + try: + VM.placement_policy.host = HOST + VM.update() + setMsg("Set startup host to " + vmhost) + setChanged() + except Exception as e: + setMsg("Failed to set startup host.") + setMsg(str(e)) + setFailed() + return False + return True + + def migrate_VM(self, vmname, vmhost): + VM = self.get_VM(vmname) + + HOST = self.get_Host_byid(VM.host.id) + if str(HOST.name) != vmhost: + try: + vm.migrate( + action=params.Action( + host=params.Host( + name=vmhost, + ) + ), + ) + setChanged() + setMsg("VM migrated to " + vmhost) + except Exception as e: + setMsg("Failed to set startup host.") + setMsg(str(e)) + setFailed() + return False + return True + + def remove_CD(self, vmname): + VM = self.get_VM(vmname) + try: + VM.cdroms.get(id="00000000-0000-0000-0000-000000000000").delete() + setMsg("Removed the image.") + setChanged() + except Exception as e: + setMsg("Failed to remove the image.") + setMsg(str(e)) + setFailed() + return False + return True + + +class RHEV(object): + def __init__(self, module): + self.module = module + + def __get_conn(self): + self.conn = RHEVConn(self.module) + return self.conn + + def test(self): + self.__get_conn() + return "OK" + + def getVM(self, name): + self.__get_conn() + VM = self.conn.get_VM(name) + if VM: + vminfo = dict() + vminfo['uuid'] = VM.id + vminfo['name'] = VM.name + vminfo['status'] = VM.status.state + vminfo['cpu_cores'] = VM.cpu.topology.cores + vminfo['cpu_sockets'] = VM.cpu.topology.sockets + vminfo['cpu_shares'] = VM.cpu_shares + vminfo['memory'] = (int(VM.memory) / 1024 / 1024 / 1024) + vminfo['mem_pol'] = (int(VM.memory_policy.guaranteed) / 1024 / 1024 / 1024) + vminfo['os'] = VM.get_os().type_ + vminfo['del_prot'] = VM.delete_protected + try: + vminfo['host'] = str(self.conn.get_Host_byid(str(VM.host.id)).name) + except Exception as e: + vminfo['host'] = None + vminfo['boot_order'] = [] + for boot_dev in VM.os.get_boot(): + vminfo['boot_order'].append(str(boot_dev.dev)) + vminfo['disks'] = [] + for DISK in VM.disks.list(): + disk = dict() + disk['name'] = DISK.name + disk['size'] = (int(DISK.size) / 1024 / 1024 / 1024) + disk['domain'] = str((self.conn.get_domain_byid(DISK.get_storage_domains().get_storage_domain()[0].id)).name) + disk['interface'] = DISK.interface + vminfo['disks'].append(disk) + vminfo['ifaces'] = [] + for NIC in VM.nics.list(): + iface = dict() + iface['name'] = str(NIC.name) + iface['vlan'] = str(self.conn.get_network_byid(NIC.get_network().id).name) + iface['interface'] = NIC.interface + iface['mac'] = NIC.mac.address + vminfo['ifaces'].append(iface) + vminfo[str(NIC.name)] = NIC.mac.address + CLUSTER = self.conn.get_cluster_byid(VM.cluster.id) + if CLUSTER: + vminfo['cluster'] = CLUSTER.name + else: + vminfo = False + return vminfo + + def createVMimage(self, name, cluster, template, disks): + self.__get_conn() + return self.conn.createVMimage(name, cluster, template, disks) + + def createVM(self, name, cluster, os, actiontype): + self.__get_conn() + return self.conn.createVM(name, cluster, os, actiontype) + + def setMemory(self, name, memory): + self.__get_conn() + return self.conn.set_Memory(name, memory) + + def setMemoryPolicy(self, name, memory_policy): + self.__get_conn() + return self.conn.set_Memory_Policy(name, memory_policy) + + def setCPU(self, name, cpu): + self.__get_conn() + return self.conn.set_CPU(name, cpu) + + def setCPUShare(self, name, cpu_share): + self.__get_conn() + return self.conn.set_CPU_share(name, cpu_share) + + def setDisks(self, name, disks): + self.__get_conn() + counter = 0 + bootselect = False + for disk in disks: + if 'bootable' in disk: + if disk['bootable'] is True: + bootselect = True + + for disk in disks: + diskname = name + "_Disk" + str(counter) + "_" + disk.get('name', '').replace('/', '_') + disksize = disk.get('size', 1) + diskdomain = disk.get('domain', None) + if diskdomain is None: + setMsg("`domain` is a required disk key.") + setFailed() + return False + diskinterface = disk.get('interface', 'virtio') + diskformat = disk.get('format', 'raw') + diskallocationtype = disk.get('thin', False) + diskboot = disk.get('bootable', False) + + if bootselect is False and counter == 0: + diskboot = True + + DISK = self.conn.get_disk(diskname) + + if DISK is None: + self.conn.createDisk(name, diskname, disksize, diskdomain, diskinterface, diskformat, diskallocationtype, diskboot) + else: + self.conn.set_Disk(diskname, disksize, diskinterface, diskboot) + checkFail() + counter += 1 + + return True + + def setNetworks(self, vmname, ifaces): + self.__get_conn() + VM = self.conn.get_VM(vmname) + + counter = 0 + length = len(ifaces) + + for NIC in VM.nics.list(): + if counter < length: + iface = ifaces[counter] + name = iface.get('name', None) + if name is None: + setMsg("`name` is a required iface key.") + setFailed() + elif str(name) != str(NIC.name): + setMsg("ifaces are in the wrong order, rebuilding everything.") + for NIC in VM.nics.list(): + self.conn.del_NIC(vmname, NIC.name) + self.setNetworks(vmname, ifaces) + checkFail() + return True + vlan = iface.get('vlan', None) + if vlan is None: + setMsg("`vlan` is a required iface key.") + setFailed() + checkFail() + interface = iface.get('interface', 'virtio') + self.conn.set_NIC(vmname, str(NIC.name), name, vlan, interface) + else: + self.conn.del_NIC(vmname, NIC.name) + counter += 1 + checkFail() + + while counter < length: + iface = ifaces[counter] + name = iface.get('name', None) + if name is None: + setMsg("`name` is a required iface key.") + setFailed() + vlan = iface.get('vlan', None) + if vlan is None: + setMsg("`vlan` is a required iface key.") + setFailed() + if failed is True: + return False + interface = iface.get('interface', 'virtio') + self.conn.createNIC(vmname, name, vlan, interface) + + counter += 1 + checkFail() + return True + + def setDeleteProtection(self, vmname, del_prot): + self.__get_conn() + VM = self.conn.get_VM(vmname) + if bool(VM.delete_protected) != bool(del_prot): + self.conn.set_DeleteProtection(vmname, del_prot) + checkFail() + setMsg("`delete protection` has been updated.") + else: + setMsg("`delete protection` already has the right value.") + return True + + def setBootOrder(self, vmname, boot_order): + self.__get_conn() + VM = self.conn.get_VM(vmname) + bootorder = [] + for boot_dev in VM.os.get_boot(): + bootorder.append(str(boot_dev.dev)) + + if boot_order != bootorder: + self.conn.set_BootOrder(vmname, boot_order) + setMsg('The boot order has been set') + else: + setMsg('The boot order has already been set') + return True + + def removeVM(self, vmname): + self.__get_conn() + self.setPower(vmname, "down", 300) + return self.conn.remove_VM(vmname) + + def setPower(self, vmname, state, timeout): + self.__get_conn() + VM = self.conn.get_VM(vmname) + if VM is None: + setMsg("VM does not exist.") + setFailed() + return False + + if state == VM.status.state: + setMsg("VM state was already " + state) + else: + if state == "up": + setMsg("VM is going to start") + self.conn.start_VM(vmname, timeout) + setChanged() + elif state == "down": + setMsg("VM is going to stop") + self.conn.stop_VM(vmname, timeout) + setChanged() + elif state == "restarted": + self.setPower(vmname, "down", timeout) + checkFail() + self.setPower(vmname, "up", timeout) + checkFail() + setMsg("the vm state is set to " + state) + return True + + def setCD(self, vmname, cd_drive): + self.__get_conn() + if cd_drive: + return self.conn.set_CD(vmname, cd_drive) + else: + return self.conn.remove_CD(vmname) + + def setVMHost(self, vmname, vmhost): + self.__get_conn() + return self.conn.set_VM_Host(vmname, vmhost) + + VM = self.conn.get_VM(vmname) + HOST = self.conn.get_Host(vmhost) + + if VM.placement_policy.host is None: + self.conn.set_VM_Host(vmname, vmhost) + elif str(VM.placement_policy.host.id) != str(HOST.id): + self.conn.set_VM_Host(vmname, vmhost) + else: + setMsg("VM's startup host was already set to " + vmhost) + checkFail() + + if str(VM.status.state) == "up": + self.conn.migrate_VM(vmname, vmhost) + checkFail() + + return True + + def setHost(self, hostname, cluster, ifaces): + self.__get_conn() + return self.conn.set_Host(hostname, cluster, ifaces) + + +def checkFail(): + if failed: + module.fail_json(msg=msg) + else: + return True + + +def setFailed(): + global failed + failed = True + + +def setChanged(): + global changed + changed = True + + +def setMsg(message): + global failed + msg.append(message) + + +def core(module): + + r = RHEV(module) + + state = module.params.get('state', 'present') + + if state == 'ping': + r.test() + return RHEV_SUCCESS, {"ping": "pong"} + elif state == 'info': + name = module.params.get('name') + if not name: + setMsg("`name` is a required argument.") + return RHEV_FAILED, msg + vminfo = r.getVM(name) + return RHEV_SUCCESS, {'changed': changed, 'msg': msg, 'vm': vminfo} + elif state == 'present': + created = False + name = module.params.get('name') + if not name: + setMsg("`name` is a required argument.") + return RHEV_FAILED, msg + actiontype = module.params.get('type') + if actiontype == 'server' or actiontype == 'desktop': + vminfo = r.getVM(name) + if vminfo: + setMsg('VM exists') + else: + # Create VM + cluster = module.params.get('cluster') + if cluster is None: + setMsg("cluster is a required argument.") + setFailed() + template = module.params.get('image') + if template: + disks = module.params.get('disks') + if disks is None: + setMsg("disks is a required argument.") + setFailed() + checkFail() + if r.createVMimage(name, cluster, template, disks) is False: + return RHEV_FAILED, vminfo + else: + os = module.params.get('osver') + if os is None: + setMsg("osver is a required argument.") + setFailed() + checkFail() + if r.createVM(name, cluster, os, actiontype) is False: + return RHEV_FAILED, vminfo + created = True + + # Set MEMORY and MEMORY POLICY + vminfo = r.getVM(name) + memory = module.params.get('vmmem') + if memory is not None: + memory_policy = module.params.get('mempol') + if int(memory_policy) == 0: + memory_policy = memory + mem_pol_nok = True + if int(vminfo['mem_pol']) == int(memory_policy): + setMsg("Memory is correct") + mem_pol_nok = False + + mem_nok = True + if int(vminfo['memory']) == int(memory): + setMsg("Memory is correct") + mem_nok = False + + if memory_policy > memory: + setMsg('memory_policy cannot have a higher value than memory.') + return RHEV_FAILED, msg + + if mem_nok and mem_pol_nok: + if int(memory_policy) > int(vminfo['memory']): + r.setMemory(vminfo['name'], memory) + r.setMemoryPolicy(vminfo['name'], memory_policy) + else: + r.setMemoryPolicy(vminfo['name'], memory_policy) + r.setMemory(vminfo['name'], memory) + elif mem_nok: + r.setMemory(vminfo['name'], memory) + elif mem_pol_nok: + r.setMemoryPolicy(vminfo['name'], memory_policy) + checkFail() + + # Set CPU + cpu = module.params.get('vmcpu') + if int(vminfo['cpu_cores']) == int(cpu): + setMsg("Number of CPUs is correct") + else: + if r.setCPU(vminfo['name'], cpu) is False: + return RHEV_FAILED, msg + + # Set CPU SHARE + cpu_share = module.params.get('cpu_share') + if cpu_share is not None: + if int(vminfo['cpu_shares']) == int(cpu_share): + setMsg("CPU share is correct.") + else: + if r.setCPUShare(vminfo['name'], cpu_share) is False: + return RHEV_FAILED, msg + + # Set DISKS + disks = module.params.get('disks') + if disks is not None: + if r.setDisks(vminfo['name'], disks) is False: + return RHEV_FAILED, msg + + # Set NETWORKS + ifaces = module.params.get('ifaces', None) + if ifaces is not None: + if r.setNetworks(vminfo['name'], ifaces) is False: + return RHEV_FAILED, msg + + # Set Delete Protection + del_prot = module.params.get('del_prot') + if r.setDeleteProtection(vminfo['name'], del_prot) is False: + return RHEV_FAILED, msg + + # Set Boot Order + boot_order = module.params.get('boot_order') + if r.setBootOrder(vminfo['name'], boot_order) is False: + return RHEV_FAILED, msg + + # Set VM Host + vmhost = module.params.get('vmhost') + if vmhost is not False and vmhost is not "False": + if r.setVMHost(vminfo['name'], vmhost) is False: + return RHEV_FAILED, msg + + vminfo = r.getVM(name) + vminfo['created'] = created + return RHEV_SUCCESS, {'changed': changed, 'msg': msg, 'vm': vminfo} + + if actiontype == 'host': + cluster = module.params.get('cluster') + if cluster is None: + setMsg("cluster is a required argument.") + setFailed() + ifaces = module.params.get('ifaces') + if ifaces is None: + setMsg("ifaces is a required argument.") + setFailed() + if r.setHost(name, cluster, ifaces) is False: + return RHEV_FAILED, msg + return RHEV_SUCCESS, {'changed': changed, 'msg': msg} + + elif state == 'absent': + name = module.params.get('name') + if not name: + setMsg("`name` is a required argument.") + return RHEV_FAILED, msg + actiontype = module.params.get('type') + if actiontype == 'server' or actiontype == 'desktop': + vminfo = r.getVM(name) + if vminfo: + setMsg('VM exists') + + # Set Delete Protection + del_prot = module.params.get('del_prot') + if r.setDeleteProtection(vminfo['name'], del_prot) is False: + return RHEV_FAILED, msg + + # Remove VM + if r.removeVM(vminfo['name']) is False: + return RHEV_FAILED, msg + setMsg('VM has been removed.') + vminfo['state'] = 'DELETED' + else: + setMsg('VM was already removed.') + return RHEV_SUCCESS, {'changed': changed, 'msg': msg, 'vm': vminfo} + + elif state == 'up' or state == 'down' or state == 'restarted': + name = module.params.get('name') + if not name: + setMsg("`name` is a required argument.") + return RHEV_FAILED, msg + timeout = module.params.get('timeout') + if r.setPower(name, state, timeout) is False: + return RHEV_FAILED, msg + vminfo = r.getVM(name) + return RHEV_SUCCESS, {'changed': changed, 'msg': msg, 'vm': vminfo} + + elif state == 'cd': + name = module.params.get('name') + cd_drive = module.params.get('cd_drive') + if r.setCD(name, cd_drive) is False: + return RHEV_FAILED, msg + return RHEV_SUCCESS, {'changed': changed, 'msg': msg} + + +def main(): + global module + module = AnsibleModule( + argument_spec = dict( + state = dict(default='present', choices=['ping', 'present', 'absent', 'up', 'down', 'restarted', 'cd', 'info']), + user = dict(default="admin@internal"), + password = dict(required=True), + server = dict(default="127.0.0.1"), + port = dict(default="443"), + insecure_api = dict(default=False, type='bool'), + name = dict(), + image = dict(default=False), + datacenter = dict(default="Default"), + type = dict(default="server", choices=['server', 'desktop', 'host']), + cluster = dict(default=''), + vmhost = dict(default=False), + vmcpu = dict(default="2"), + vmmem = dict(default="1"), + disks = dict(), + osver = dict(default="rhel_6x64"), + ifaces = dict(aliases=['nics', 'interfaces']), + timeout = dict(default=False), + mempol = dict(default="1"), + vm_ha = dict(default=True), + cpu_share = dict(default="0"), + boot_order = dict(default=["network", "hd"]), + del_prot = dict(default=True, type="bool"), + cd_drive = dict(default=False) + ), + ) + + if not HAS_SDK: + module.fail_json( + msg='The `ovirtsdk` module is not importable. Check the requirements.' + ) + + rc = RHEV_SUCCESS + try: + rc, result = core(module) + except Exception as e: + module.fail_json(msg=str(e)) + + if rc != 0: # something went wrong emit the msg + module.fail_json(rc=rc, msg=result) + else: + module.exit_json(**result) + + +# import module snippets +from ansible.module_utils.basic import * + +if __name__ == '__main__': + main() diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/ovirt/ovirt_disks.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/ovirt/ovirt_disks.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/ovirt/ovirt_disks.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/ovirt/ovirt_disks.py 2017-01-16 17:04:03.000000000 +0000 @@ -278,7 +278,7 @@ ret = disks_module.remove() # If VM was passed attach/detach disks to/from the VM: - if 'vm_id' in module.params or 'vm_name' in module.params and state != 'absent': + if module.params.get('vm_id') is not None or module.params.get('vm_name') is not None and state != 'absent': vms_service = connection.system_service().vms_service() # If `vm_id` isn't specified, find VM by name: diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/rackspace/rax_clb_ssl.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/rackspace/rax_clb_ssl.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/rackspace/rax_clb_ssl.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/rackspace/rax_clb_ssl.py 2017-01-16 17:04:03.000000000 +0000 @@ -139,7 +139,7 @@ needs_change = False if existing_ssl: - for ssl_attr, value in ssl_attrs.iteritems(): + for ssl_attr, value in ssl_attrs.items(): if ssl_attr == 'privatekey': # The private key is not included in get_ssl_termination's # output (as it shouldn't be). Also, if you're changing the diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/rackspace/rax_mon_check.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/rackspace/rax_mon_check.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/rackspace/rax_mon_check.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/rackspace/rax_mon_check.py 2017-01-16 17:04:03.000000000 +0000 @@ -187,7 +187,7 @@ # Only force a recreation of the check if one of the *specified* # keys is missing or has a different value. if details: - for (key, value) in details.iteritems(): + for (key, value) in details.items(): if key not in check.details: should_delete = should_create = True elif value != check.details[key]: diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/vmware/vmware_guest.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/vmware/vmware_guest.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/vmware/vmware_guest.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/vmware/vmware_guest.py 2017-01-16 17:04:03.000000000 +0000 @@ -437,7 +437,7 @@ mac = device.macAddress ips = list(device.ipAddress) netDict[mac] = ips - for k,v in netDict.iteritems(): + for k,v in netDict.items(): for ipaddress in v: if ipaddress: if '::' in ipaddress: @@ -700,7 +700,7 @@ timeout=10, headers=None) # save all of the transfer data - for k,v in info.iteritems(): + for k,v in info.items(): result[k] = v # exit early if xfer failed @@ -766,7 +766,7 @@ result['msg'] = str(rsp.read()) # save all of the transfer data - for k,v in info.iteritems(): + for k,v in info.items(): result[k] = v return result diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/cloud/xenserver_facts.py ansible-2.2.1.0/lib/ansible/modules/extras/cloud/xenserver_facts.py --- ansible-2.2.0.0/lib/ansible/modules/extras/cloud/xenserver_facts.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/cloud/xenserver_facts.py 2017-01-16 17:04:03.000000000 +0000 @@ -89,7 +89,7 @@ recs = session.xenapi.network.get_all_records() xs_networks = {} networks = change_keys(recs, key='uuid') - for network in networks.itervalues(): + for network in networks.values(): xs_networks[network['name_label']] = network return xs_networks @@ -99,7 +99,7 @@ pifs = change_keys(recs, key='uuid') xs_pifs = {} devicenums = range(0, 7) - for pif in pifs.itervalues(): + for pif in pifs.values(): for eth in devicenums: interface_name = "eth%s" % (eth) bond_name = interface_name.replace('eth', 'bond') @@ -124,7 +124,7 @@ """ new_recs = {} - for ref, rec in recs.iteritems(): + for ref, rec in recs.items(): if filter_func is not None and not filter_func(rec): continue @@ -146,7 +146,7 @@ return None vms = change_keys(recs, key='uuid') - for vm in vms.itervalues(): + for vm in vms.values(): xs_vms[vm['name_label']] = vm return xs_vms @@ -157,7 +157,7 @@ if not recs: return None srs = change_keys(recs, key='uuid') - for sr in srs.itervalues(): + for sr in srs.values(): xs_srs[sr['name_label']] = sr return xs_srs diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/clustering/consul_acl.py ansible-2.2.1.0/lib/ansible/modules/extras/clustering/consul_acl.py --- ansible-2.2.0.0/lib/ansible/modules/extras/clustering/consul_acl.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/clustering/consul_acl.py 2017-01-16 17:04:03.000000000 +0000 @@ -213,7 +213,7 @@ if info and info['Rules']: rule_set = hcl.loads(to_ascii(info['Rules'])) for rule_type in rule_set: - for pattern, policy in rule_set[rule_type].iteritems(): + for pattern, policy in rule_set[rule_type].items(): rules.add_rule(rule_type, Rule(pattern, policy['policy'])) return rules except Exception as e: @@ -268,7 +268,7 @@ rules = "" for rule_type in RULE_TYPES: - for pattern, rule in self.rules[rule_type].iteritems(): + for pattern, rule in self.rules[rule_type].items(): rules += template % (rule_type, pattern, rule.policy) return to_ascii(rules) @@ -284,7 +284,7 @@ return False for rule_type in RULE_TYPES: - for name, other_rule in other.rules[rule_type].iteritems(): + for name, other_rule in other.rules[rule_type].items(): if not name in self.rules[rule_type]: return False rule = self.rules[rule_type][name] @@ -336,7 +336,7 @@ mgmt_token=dict(required=True, no_log=True), host=dict(default='localhost'), scheme=dict(required=False, default='http'), - validate_certs=dict(required=False, default=True), + validate_certs=dict(required=False, type='bool', default=True), name=dict(required=False), port=dict(default=8500, type='int'), rules=dict(default=None, required=False, type='list'), diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/clustering/consul_kv.py ansible-2.2.1.0/lib/ansible/modules/extras/clustering/consul_kv.py --- ansible-2.2.0.0/lib/ansible/modules/extras/clustering/consul_kv.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/clustering/consul_kv.py 2017-01-16 17:04:03.000000000 +0000 @@ -251,7 +251,7 @@ if not python_consul_installed: module.fail_json(msg="python-consul required for this module. "\ "see http://python-consul.readthedocs.org/en/latest/#installation") - + def main(): argument_spec = dict( @@ -260,12 +260,12 @@ key=dict(required=True), host=dict(default='localhost'), scheme=dict(required=False, default='http'), - validate_certs=dict(required=False, default=True), + validate_certs=dict(required=False, type='bool', default=True), port=dict(default=8500, type='int'), recurse=dict(required=False, type='bool'), - retrieve=dict(required=False, default=True), + retrieve=dict(required=False, type='bool', default=True), state=dict(default='present', choices=['present', 'absent', 'acquire', 'release']), - token=dict(required=False, default='anonymous', no_log=True), + token=dict(required=False, no_log=True), value=dict(required=False), session=dict(required=False) ) @@ -273,7 +273,7 @@ module = AnsibleModule(argument_spec, supports_check_mode=False) test_dependencies(module) - + try: execute(module) except ConnectionError as e: diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/clustering/consul.py ansible-2.2.1.0/lib/ansible/modules/extras/clustering/consul.py --- ansible-2.2.0.0/lib/ansible/modules/extras/clustering/consul.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/clustering/consul.py 2017-01-16 17:04:03.000000000 +0000 @@ -185,7 +185,7 @@ service_name: nginx service_port: 80 interval: 60s - http: /status + http: "http://localhost:80/status" - name: register external service nginx available at 10.1.5.23 consul: @@ -213,6 +213,14 @@ script: "/opt/disk_usage.py" interval: 5m + - name: register an http check against a service that's already registered + consul: + check_name: nginx-check2 + check_id: nginx-check2 + service_id: nginx + interval: 60s + http: "http://localhost:80/morestatus" + ''' try: @@ -265,7 +273,7 @@ retrieve the full metadata of an existing check through the consul api. Without this we can't compare to the supplied check and so we must assume a change. ''' - if not check.name: + if not check.name and not service_id: module.fail_json(msg='a check name is required for a node level check, one not attached to a service') consul_api = get_consul_api(module) @@ -278,7 +286,8 @@ interval=check.interval, ttl=check.ttl, http=check.http, - timeout=check.timeout) + timeout=check.timeout, + service_id=check.service_id) def remove_check(module, check_id): @@ -298,7 +307,7 @@ changed = False consul_api = get_consul_api(module) - existing = get_service_by_id(consul_api, service.id) + existing = get_service_by_id_or_name(consul_api, service.id) # there is no way to retrieve the details of checks so if a check is present # in the service it must be re-registered @@ -306,7 +315,7 @@ service.register(consul_api) # check that it registered correctly - registered = get_service_by_id(consul_api, service.id) + registered = get_service_by_id_or_name(consul_api, service.id) if registered: result = registered changed = True @@ -322,7 +331,7 @@ def remove_service(module, service_id): ''' deregister a service from the given agent using its service id ''' consul_api = get_consul_api(module) - service = get_service_by_id(consul_api, service_id) + service = get_service_by_id_or_name(consul_api, service_id) if service: consul_api.agent.service.deregister(service_id) module.exit_json(changed=True, id=service_id) @@ -338,10 +347,10 @@ token=module.params.get('token')) -def get_service_by_id(consul_api, service_id): +def get_service_by_id_or_name(consul_api, service_id_or_name): ''' iterate the registered services and find one with the given id ''' - for name, service in consul_api.agent.services().iteritems(): - if service['ID'] == service_id: + for name, service in consul_api.agent.services().items(): + if service['ID'] == service_id_or_name or service['Service'] == service_id_or_name: return ConsulService(loaded=service) @@ -363,7 +372,8 @@ module.params.get('ttl'), module.params.get('notes'), module.params.get('http'), - module.params.get('timeout') + module.params.get('timeout'), + module.params.get('service_id'), ) @@ -451,10 +461,11 @@ class ConsulCheck(): def __init__(self, check_id, name, node=None, host='localhost', - script=None, interval=None, ttl=None, notes=None, http=None, timeout=None): + script=None, interval=None, ttl=None, notes=None, http=None, timeout=None, service_id=None): self.check_id = self.name = name if check_id: self.check_id = check_id + self.service_id = service_id self.notes = notes self.node = node self.host = host @@ -488,13 +499,14 @@ return duration def register(self, consul_api): - consul_api.agent.check.register(self.name, check_id=self.check_id, + consul_api.agent.check.register(self.name, check_id=self.check_id, service_id=self.service_id, notes=self.notes, check=self.check) def __eq__(self, other): return (isinstance(other, self.__class__) and self.check_id == other.check_id + and self.service_id == other.service_id and self.name == other.name and self.script == script and self.interval == interval) @@ -514,6 +526,7 @@ self._add(data, 'ttl') self._add(data, 'http') self._add(data, 'timeout') + self._add(data, 'service_id') return data def _add(self, data, key, attr=None): diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/commands/expect.py ansible-2.2.1.0/lib/ansible/modules/extras/commands/expect.py --- ansible-2.2.0.0/lib/ansible/modules/extras/commands/expect.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/commands/expect.py 2017-01-16 17:04:03.000000000 +0000 @@ -142,7 +142,7 @@ echo = module.params['echo'] events = dict() - for key, value in responses.iteritems(): + for key, value in responses.items(): if isinstance(value, list): response = response_closure(module, key, value) else: diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/database/misc/mongodb_parameter.py ansible-2.2.1.0/lib/ansible/modules/extras/database/misc/mongodb_parameter.py --- ansible-2.2.0.0/lib/ansible/modules/extras/database/misc/mongodb_parameter.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/database/misc/mongodb_parameter.py 2017-01-16 17:04:03.000000000 +0000 @@ -212,7 +212,7 @@ db = client.admin try: - after_value = db.command("setParameter", **{param: int(value)}) + after_value = db.command("setParameter", **{param: value}) except OperationFailure: e = get_exception() module.fail_json(msg="unable to change parameter: %s" % str(e)) diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/database/misc/mongodb_user.py ansible-2.2.1.0/lib/ansible/modules/extras/database/misc/mongodb_user.py --- ansible-2.2.0.0/lib/ansible/modules/extras/database/misc/mongodb_user.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/database/misc/mongodb_user.py 2017-01-16 17:04:03.000000000 +0000 @@ -185,7 +185,7 @@ loose_srv_version = LooseVersion(client.server_info()['version']) loose_driver_version = LooseVersion(PyMongoVersion) - if loose_srv_version >= LooseVersion('3.2') and loose_driver_version <= LooseVersion('3.2'): + if loose_srv_version >= LooseVersion('3.2') and loose_driver_version < LooseVersion('3.2'): module.fail_json(msg=' (Note: you must use pymongo 3.2+ with MongoDB >= 3.2)') elif loose_srv_version >= LooseVersion('3.0') and loose_driver_version <= LooseVersion('2.8'): diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/database/postgresql/postgresql_ext.py ansible-2.2.1.0/lib/ansible/modules/extras/database/postgresql/postgresql_ext.py --- ansible-2.2.0.0/lib/ansible/modules/extras/database/postgresql/postgresql_ext.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/database/postgresql/postgresql_ext.py 2017-01-16 17:04:03.000000000 +0000 @@ -146,7 +146,7 @@ "login_password":"password", "port":"port" } - kw = dict( (params_map[k], v) for (k, v) in module.params.iteritems() + kw = dict( (params_map[k], v) for (k, v) in module.params.items() if k in params_map and v != '' ) try: db_connection = psycopg2.connect(database=db, **kw) diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/database/postgresql/postgresql_lang.py ansible-2.2.1.0/lib/ansible/modules/extras/database/postgresql/postgresql_lang.py --- ansible-2.2.0.0/lib/ansible/modules/extras/database/postgresql/postgresql_lang.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/database/postgresql/postgresql_lang.py 2017-01-16 17:04:03.000000000 +0000 @@ -217,7 +217,7 @@ "port":"port", "db":"database" } - kw = dict( (params_map[k], v) for (k, v) in module.params.iteritems() + kw = dict( (params_map[k], v) for (k, v) in module.params.items() if k in params_map and v != "" ) try: db_connection = psycopg2.connect(**kw) diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/files/blockinfile.py ansible-2.2.1.0/lib/ansible/modules/extras/files/blockinfile.py --- ansible-2.2.0.0/lib/ansible/modules/extras/files/blockinfile.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/files/blockinfile.py 2017-01-16 17:04:03.000000000 +0000 @@ -258,9 +258,9 @@ n0 = n1 = None for i, line in enumerate(lines): - if line.startswith(marker0): + if line == marker0: n0 = i - if line.startswith(marker1): + if line == marker1: n1 = i if None in (n0, n1): diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/monitoring/datadog_monitor.py ansible-2.2.1.0/lib/ansible/modules/extras/monitoring/datadog_monitor.py --- ansible-2.2.0.0/lib/ansible/modules/extras/monitoring/datadog_monitor.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/monitoring/datadog_monitor.py 2017-01-16 17:04:03.000000000 +0000 @@ -192,7 +192,9 @@ unmute_monitor(module) def _fix_template_vars(message): - return message.replace('[[', '{{').replace(']]', '}}') + if message: + return message.replace('[[', '{{').replace(']]', '}}') + return message def _get_monitor(module): diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/monitoring/logicmonitor.py ansible-2.2.1.0/lib/ansible/modules/extras/monitoring/logicmonitor.py --- ansible-2.2.0.0/lib/ansible/modules/extras/monitoring/logicmonitor.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/monitoring/logicmonitor.py 2017-01-16 17:04:03.000000000 +0000 @@ -1538,7 +1538,7 @@ if properties is not None and properties is not {}: self.module.debug("Properties hash exists") propnum = 0 - for key, value in properties.iteritems(): + for key, value in properties.items(): h["propName" + str(propnum)] = key h["propValue" + str(propnum)] = value propnum = propnum + 1 @@ -2030,7 +2030,7 @@ if properties != {}: self.module.debug("Properties hash exists") propnum = 0 - for key, value in properties.iteritems(): + for key, value in properties.items(): h["propName" + str(propnum)] = key h["propValue" + str(propnum)] = value propnum = propnum + 1 diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/network/f5/bigip_virtual_server.py ansible-2.2.1.0/lib/ansible/modules/extras/network/f5/bigip_virtual_server.py --- ansible-2.2.0.0/lib/ansible/modules/extras/network/f5/bigip_virtual_server.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/network/f5/bigip_virtual_server.py 2017-01-16 17:04:03.000000000 +0000 @@ -236,7 +236,7 @@ return False rules_list = list(enumerate(rules_list)) try: - current_rules = map(lambda x: (x['priority'], x['rule_name']), get_rules(api, name)) + current_rules = [(x['priority'], x['rule_name']) for x in get_rules(api, name)] to_add_rules = [] for i, x in rules_list: if (i, x) not in current_rules: diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/network/haproxy.py ansible-2.2.1.0/lib/ansible/modules/extras/network/haproxy.py --- ansible-2.2.0.0/lib/ansible/modules/extras/network/haproxy.py 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/network/haproxy.py 2017-01-16 17:04:03.000000000 +0000 @@ -211,7 +211,7 @@ """ data = self.execute('show stat', 200, False).lstrip('# ') r = csv.DictReader(data.splitlines()) - return map(lambda d: d['pxname'], filter(lambda d: d['svname'] == 'BACKEND', r)) + return tuple(map(lambda d: d['pxname'], filter(lambda d: d['svname'] == 'BACKEND', r))) def execute_for_backends(self, cmd, pxname, svname, wait_for_status = None): @@ -244,7 +244,7 @@ """ data = self.execute('show stat', 200, False).lstrip('# ') r = csv.DictReader(data.splitlines()) - state = map(lambda d: { 'status': d['status'], 'weight': d['weight'] }, filter(lambda d: (pxname is None or d['pxname'] == pxname) and d['svname'] == svname, r)) + state = tuple(map(lambda d: { 'status': d['status'], 'weight': d['weight'] }, filter(lambda d: (pxname is None or d['pxname'] == pxname) and d['svname'] == svname, r))) return state or None diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/packaging/elasticsearch_plugin.py ansible-2.2.1.0/lib/ansible/modules/extras/packaging/elasticsearch_plugin.py --- ansible-2.2.0.0/lib/ansible/modules/extras/packaging/elasticsearch_plugin.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/packaging/elasticsearch_plugin.py 2017-01-16 17:04:03.000000000 +0000 @@ -129,7 +129,7 @@ cmd_args = [plugin_bin, PACKAGE_STATE_MAP["present"], plugin_name] if version: - name = name + '/' + version + plugin_name = plugin_name + '/' + version if proxy_host and proxy_port: cmd_args.append("-DproxyHost=%s -DproxyPort=%s" % (proxy_host, proxy_port)) diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/packaging/language/composer.py ansible-2.2.1.0/lib/ansible/modules/extras/packaging/language/composer.py --- ansible-2.2.0.0/lib/ansible/modules/extras/packaging/language/composer.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/packaging/language/composer.py 2017-01-16 17:04:03.000000000 +0000 @@ -209,7 +209,7 @@ 'ignore_platform_reqs': 'ignore-platform-reqs', } - for param, option in option_params.iteritems(): + for param, option in option_params.items(): if module.params.get(param) and option in available_options: option = "--%s" % option options.append(option) diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/packaging/os/dnf.py ansible-2.2.1.0/lib/ansible/modules/extras/packaging/os/dnf.py --- ansible-2.2.0.0/lib/ansible/modules/extras/packaging/os/dnf.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/packaging/os/dnf.py 2017-01-16 17:04:03.000000000 +0000 @@ -179,7 +179,7 @@ base = dnf.Base() _configure_base(module, base, conf_file, disable_gpg_check) _specify_repositories(base, disablerepo, enablerepo) - base.fill_sack() + base.fill_sack(load_system_repo='auto') return base @@ -256,6 +256,9 @@ def ensure(module, base, state, names): + # Accumulate failures. Package management modules install what they can + # and fail with a message about what they can't. + failures = [] allow_erasing = False if names == ['*'] and state == 'latest': base.upgrade_all() @@ -264,34 +267,70 @@ if group_specs: base.read_comps() + pkg_specs = [p.strip() for p in pkg_specs] + filenames = [f.strip() for f in filenames] groups = [] - for group_spec in group_specs: + environments = [] + for group_spec in (g.strip() for g in group_specs): group = base.comps.group_by_pattern(group_spec) if group: groups.append(group) else: - module.fail_json( - msg="No group {} available.".format(group_spec)) + environment = base.comps.environment_by_pattern(group_spec) + if environment: + environments.append(environment.id) + else: + module.fail_json( + msg="No group {} available.".format(group_spec)) if state in ['installed', 'present']: # Install files. - _install_remote_rpms(base, (f.strip() for f in filenames)) + _install_remote_rpms(base, filenames) + # Install groups. - for group in (g.strip() for g in groups): - base.group_install(group, const.GROUP_PACKAGE_TYPES) + for group in groups: + try: + base.group_install(group, const.GROUP_PACKAGE_TYPES) + except exceptions.Error as e: + # In dnf 2.0 if all the mandatory packages in a group do + # not install, an error is raised. We want to capture + # this but still install as much as possible. + failures.append((group, e)) + + for environment in environments: + try: + base.environment_install(environment, const.GROUP_PACKAGE_TYPES) + except exceptions.Error as e: + failures.append((group, e)) + # Install packages. - for pkg_spec in (p.strip() for p in pkg_specs): + for pkg_spec in pkg_specs: _mark_package_install(module, base, pkg_spec) elif state == 'latest': # "latest" is same as "installed" for filenames. _install_remote_rpms(base, filenames) + for group in groups: try: - base.group_upgrade(group) - except exceptions.CompsError: - # If not already installed, try to install. - base.group_install(group, const.GROUP_PACKAGE_TYPES) + try: + base.group_upgrade(group) + except exceptions.CompsError: + # If not already installed, try to install. + base.group_install(group, const.GROUP_PACKAGE_TYPES) + except exceptions.Error as e: + failures.append((group, e)) + + for environment in environments: + try: + try: + base.environment_upgrade(environment) + except exceptions.CompsError: + # If not already installed, try to install. + base.environment_install(group, const.GROUP_PACKAGE_TYPES) + except exceptions.Error as e: + failures.append((group, e)) + for pkg_spec in pkg_specs: # best effort causes to install the latest package # even if not previously installed @@ -304,22 +343,41 @@ module.fail_json( msg="Cannot remove paths -- please specify package name.") - installed = base.sack.query().installed() for group in groups: - if installed.filter(name=group.name): + try: base.group_remove(group) + except dnf.exceptions.CompsError: + # Group is already uninstalled. + pass + + for envioronment in environments: + try: + base.environment_remove(environment) + except dnf.exceptions.CompsError: + # Environment is already uninstalled. + pass + + installed = base.sack.query().installed() for pkg_spec in pkg_specs: if installed.filter(name=pkg_spec): base.remove(pkg_spec) + # Like the dnf CLI we want to allow recursive removal of dependent # packages allow_erasing = True if not base.resolve(allow_erasing=allow_erasing): + if failures: + module.fail_json(msg='Failed to install some of the specified packages', + failures=failures) module.exit_json(msg="Nothing to do") else: if module.check_mode: + if failures: + module.fail_json(msg='Failed to install some of the specified packages', + failures=failures) module.exit_json(changed=True) + base.download_packages(base.transaction.install_set) base.do_transaction() response = {'changed': True, 'results': []} @@ -328,6 +386,9 @@ for package in base.transaction.remove_set: response['results'].append("Removed: {0}".format(package)) + if failures: + module.fail_json(msg='Failed to install some of the specified packages', + failures=failures) module.exit_json(**response) diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/packaging/os/homebrew_cask.py ansible-2.2.1.0/lib/ansible/modules/extras/packaging/os/homebrew_cask.py --- ansible-2.2.0.0/lib/ansible/modules/extras/packaging/os/homebrew_cask.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/packaging/os/homebrew_cask.py 2017-01-16 17:04:03.000000000 +0000 @@ -75,6 +75,8 @@ import os.path import re +from ansible.module_utils.six import iteritems + # exceptions -------------------------------------------------------------- {{{ class HomebrewCaskException(Exception): @@ -309,7 +311,7 @@ self.message = '' def _setup_instance_vars(self, **kwargs): - for key, val in kwargs.iteritems(): + for key, val in iteritems(kwargs): setattr(self, key, val) def _prep(self): diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/packaging/os/homebrew.py ansible-2.2.1.0/lib/ansible/modules/extras/packaging/os/homebrew.py --- ansible-2.2.0.0/lib/ansible/modules/extras/packaging/os/homebrew.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/packaging/os/homebrew.py 2017-01-16 17:04:03.000000000 +0000 @@ -101,6 +101,8 @@ import os.path import re +from ansible.module_utils.six import iteritems + # exceptions -------------------------------------------------------------- {{{ class HomebrewException(Exception): @@ -348,7 +350,7 @@ self.message = '' def _setup_instance_vars(self, **kwargs): - for key, val in kwargs.iteritems(): + for key, val in iteritems(kwargs): setattr(self, key, val) def _prep(self): diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/packaging/os/portage.py ansible-2.2.1.0/lib/ansible/modules/extras/packaging/os/portage.py --- ansible-2.2.0.0/lib/ansible/modules/extras/packaging/os/portage.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/packaging/os/portage.py 2017-01-16 17:04:03.000000000 +0000 @@ -45,22 +45,22 @@ description: - Update packages to the best version available (--update) required: false - default: null - choices: [ "yes" ] + default: no + choices: [ "yes", "no" ] deep: description: - Consider the entire dependency tree of packages (--deep) required: false - default: null - choices: [ "yes" ] + default: no + choices: [ "yes", "no" ] newuse: description: - Include installed packages where USE flags have changed (--newuse) required: false - default: null - choices: [ "yes" ] + default: no + choices: [ "yes", "no" ] changed_use: description: @@ -68,8 +68,8 @@ - flags that the user has not enabled are added or removed - (--changed-use) required: false - default: null - choices: [ "yes" ] + default: no + choices: [ "yes", "no" ] version_added: 1.8 oneshot: @@ -273,7 +273,7 @@ 'usepkgonly': '--usepkgonly', 'usepkg': '--usepkg', } - for flag, arg in emerge_flags.iteritems(): + for flag, arg in emerge_flags.items(): if p[flag]: args.append(arg) @@ -412,7 +412,7 @@ depclean=dict(default=False, type='bool'), quiet=dict(default=False, type='bool'), verbose=dict(default=False, type='bool'), - sync=dict(default=None, choices=['yes', 'web']), + sync=dict(default=None, choices=['yes', 'web', 'no']), getbinpkg=dict(default=False, type='bool'), usepkgonly=dict(default=False, type='bool'), usepkg=dict(default=False, type='bool'), @@ -427,7 +427,7 @@ p = module.params - if p['sync']: + if p['sync'] and p['sync'] != 'no': sync_repositories(module, webrsync=(p['sync'] == 'web')) if not p['package']: module.exit_json(msg='Sync successfully finished.') diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/packaging/os/yum_repository.py ansible-2.2.1.0/lib/ansible/modules/extras/packaging/os/yum_repository.py --- ansible-2.2.0.0/lib/ansible/modules/extras/packaging/os/yum_repository.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/packaging/os/yum_repository.py 2017-01-16 17:04:03.000000000 +0000 @@ -19,9 +19,9 @@ # along with Ansible. If not, see . -import ConfigParser import os from ansible.module_utils.pycompat24 import get_exception +from ansible.module_utils.six.moves import configparser DOCUMENTATION = ''' @@ -468,7 +468,7 @@ module = None params = None section = None - repofile = ConfigParser.RawConfigParser() + repofile = configparser.RawConfigParser() # List of parameters which will be allowed in the repo file output allowed_params = [ @@ -575,7 +575,7 @@ if len(self.repofile.sections()): # Write data into the file try: - fd = open(self.params['dest'], 'wb') + fd = open(self.params['dest'], 'w') except IOError: e = get_exception() self.module.fail_json( diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/system/gluster_volume.py ansible-2.2.1.0/lib/ansible/modules/extras/system/gluster_volume.py --- ansible-2.2.0.0/lib/ansible/modules/extras/system/gluster_volume.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/system/gluster_volume.py 2017-01-16 17:04:03.000000000 +0000 @@ -274,7 +274,7 @@ def probe(host, myhostname): global module out = run_gluster([ 'peer', 'probe', host ]) - if not out.find('localhost') and not wait_for_peer(host): + if out.find('localhost') == -1 and not wait_for_peer(host): module.fail_json(msg='failed to probe peer %s on %s' % (host, myhostname)) changed = True diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/system/known_hosts.py ansible-2.2.1.0/lib/ansible/modules/extras/system/known_hosts.py --- ansible-2.2.0.0/lib/ansible/modules/extras/system/known_hosts.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/system/known_hosts.py 2017-01-16 17:04:03.000000000 +0000 @@ -133,8 +133,8 @@ try: outf=tempfile.NamedTemporaryFile(dir=os.path.dirname(path)) if inf is not None: - for line_number, line in enumerate(inf, start=1): - if found_line==line_number and (replace_or_add or state=='absent'): + for line_number, line in enumerate(inf): + if found_line==(line_number + 1) and (replace_or_add or state=='absent'): continue # skip this line to replace its key outf.write(line) inf.close() diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/system/locale_gen.py ansible-2.2.1.0/lib/ansible/modules/extras/system/locale_gen.py --- ansible-2.2.0.0/lib/ansible/modules/extras/system/locale_gen.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/system/locale_gen.py 2017-01-16 17:04:03.000000000 +0000 @@ -49,8 +49,9 @@ from subprocess import Popen, PIPE, call import re -from ansible.module_utils.basic import * +from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.pycompat24 import get_exception +from ansible.module_utils._text import to_native LOCALE_NORMALIZATION = { ".utf8": ".UTF-8", @@ -93,12 +94,13 @@ def is_present(name): """Checks if the given locale is currently installed.""" output = Popen(["locale", "-a"], stdout=PIPE).communicate()[0] + output = to_native(output) return any(fix_case(name) == fix_case(line) for line in output.splitlines()) def fix_case(name): """locale -a might return the encoding in either lower or upper case. Passing through this function makes them uniform for comparisons.""" - for s, r in LOCALE_NORMALIZATION.iteritems(): + for s, r in LOCALE_NORMALIZATION.items(): name = name.replace(s, r) return name diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/system/make.py ansible-2.2.1.0/lib/ansible/modules/extras/system/make.py --- ansible-2.2.0.0/lib/ansible/modules/extras/system/make.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/system/make.py 2017-01-16 17:04:03.000000000 +0000 @@ -67,7 +67,7 @@ def format_params(params): - return [k + '=' + str(v) for k, v in params.iteritems()] + return [k + '=' + str(v) for k, v in params.items()] def push_arguments(cmd, args): diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/system/osx_defaults.py ansible-2.2.1.0/lib/ansible/modules/extras/system/osx_defaults.py --- ansible-2.2.0.0/lib/ansible/modules/extras/system/osx_defaults.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/system/osx_defaults.py 2017-01-16 17:04:03.000000000 +0000 @@ -84,7 +84,9 @@ ''' import datetime -from ansible.module_utils.basic import * +import re + +from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.pycompat24 import get_exception # exceptions --------------------------------------------------------------- {{{ @@ -105,7 +107,7 @@ self.current_value = None # Just set all given parameters - for key, val in kwargs.iteritems(): + for key, val in kwargs.items(): setattr(self, key, val) # Try to find the defaults executable @@ -347,6 +349,7 @@ value=dict( default=None, required=False, + type='raw' ), state=dict( default="present", diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/system/sefcontext.py ansible-2.2.1.0/lib/ansible/modules/extras/system/sefcontext.py --- ansible-2.2.0.0/lib/ansible/modules/extras/system/sefcontext.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/system/sefcontext.py 2017-01-16 17:04:03.000000000 +0000 @@ -81,6 +81,7 @@ from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.pycompat24 import get_exception +from ansible.module_utils._text import to_native try: import selinux @@ -94,21 +95,35 @@ except ImportError: HAVE_SEOBJECT=False +### Add missing entries (backward compatible) +seobject.file_types.update(dict( + a = seobject.SEMANAGE_FCONTEXT_ALL, + b = seobject.SEMANAGE_FCONTEXT_BLOCK, + c = seobject.SEMANAGE_FCONTEXT_CHAR, + d = seobject.SEMANAGE_FCONTEXT_DIR, + f = seobject.SEMANAGE_FCONTEXT_REG, + l = seobject.SEMANAGE_FCONTEXT_LINK, + p = seobject.SEMANAGE_FCONTEXT_PIPE, + s = seobject.SEMANAGE_FCONTEXT_SOCK, +)) + ### Make backward compatible -option_to_file_type_str = { - 'a': 'all files', - 'b': 'block device', - 'c': 'character device', - 'd': 'directory', - 'f': 'regular file', - 'l': 'symbolic link', - 's': 'socket file', - 'p': 'named pipe', -} +option_to_file_type_str = dict( + a = 'all files', + b = 'block device', + c = 'character device', + d = 'directory', + f = 'regular file', + l = 'symbolic link', + p = 'named pipe', + s = 'socket file', +) def semanage_fcontext_exists(sefcontext, target, ftype): ''' Get the SELinux file context mapping definition from policy. Return None if it does not exist. ''' - record = (target, ftype) + + # Beware that records comprise of a string representation of the file_type + record = (target, option_to_file_type_str[ftype]) records = sefcontext.get_all() try: return records[record] @@ -160,7 +175,7 @@ except Exception: e = get_exception() - module.fail_json(msg="%s: %s\n" % (e.__class__.__name__, str(e))) + module.fail_json(msg="%s: %s\n" % (e.__class__.__name__, to_native(e))) if module._diff and prepared_diff: result['diff'] = dict(prepared=prepared_diff) @@ -191,7 +206,7 @@ except Exception: e = get_exception() - module.fail_json(msg="%s: %s\n" % (e.__class__.__name__, str(e))) + module.fail_json(msg="%s: %s\n" % (e.__class__.__name__, to_native(e))) if module._diff and prepared_diff: result['diff'] = dict(prepared=prepared_diff) @@ -231,9 +246,6 @@ result = dict(target=target, ftype=ftype, setype=setype, state=state) - # Convert file types to (internally used) strings - ftype = option_to_file_type_str[ftype] - if state == 'present': semanage_fcontext_modify(module, result, target, ftype, setype, do_reload, serange, seuser) elif state == 'absent': diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/system/timezone.py ansible-2.2.1.0/lib/ansible/modules/extras/system/timezone.py --- ansible-2.2.0.0/lib/ansible/modules/extras/system/timezone.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/system/timezone.py 2017-01-16 17:04:03.000000000 +0000 @@ -223,6 +223,7 @@ tzfile = '/usr/share/zoneinfo/%s' % tz if not os.path.isfile(tzfile): self.abort('given timezone "%s" is not available' % tz) + return tzfile class SystemdTimezone(Timezone): @@ -301,7 +302,7 @@ super(NosystemdTimezone, self).__init__(module) # Validate given timezone if 'name' in self.value: - self._verify_timezone() + tzfile = self._verify_timezone() self.update_timezone = self.module.get_bin_path('cp', required=True) self.update_timezone += ' %s /etc/localtime' % tzfile self.update_hwclock = self.module.get_bin_path('hwclock', required=True) @@ -310,8 +311,8 @@ # Debian/Ubuntu self.update_timezone = self.module.get_bin_path('dpkg-reconfigure', required=True) self.update_timezone += ' --frontend noninteractive tzdata' - self.conf_files['name'] = '/etc/timezone', - self.conf_files['hwclock'] = '/etc/default/rcS', + self.conf_files['name'] = '/etc/timezone' + self.conf_files['hwclock'] = '/etc/default/rcS' self.regexps['name'] = re.compile(r'^([^\s]+)', re.MULTILINE) self.tzline_format = '%s\n' else: diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/system/ufw.py ansible-2.2.1.0/lib/ansible/modules/extras/system/ufw.py --- ansible-2.2.0.0/lib/ansible/modules/extras/system/ufw.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/system/ufw.py 2017-01-16 17:04:03.000000000 +0000 @@ -239,7 +239,7 @@ (_, pre_rules, _) = module.run_command("grep '^### tuple' /lib/ufw/user*.rules") # Execute commands - for (command, value) in commands.iteritems(): + for (command, value) in commands.items(): cmd = [[ufw_bin], [module.check_mode, '--dry-run']] if command == 'state': diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/system/zfs.py ansible-2.2.1.0/lib/ansible/modules/extras/system/zfs.py --- ansible-2.2.0.0/lib/ansible/modules/extras/system/zfs.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/system/zfs.py 2017-01-16 17:04:03.000000000 +0000 @@ -148,7 +148,7 @@ if volblocksize: cmd += ['-b', 'volblocksize'] if properties: - for prop, value in properties.iteritems(): + for prop, value in properties.items(): cmd += ['-o', '%s="%s"' % (prop, value)] if origin: cmd.append(origin) @@ -183,7 +183,7 @@ def set_properties_if_changed(self): current_properties = self.get_current_properties() - for prop, value in self.properties.iteritems(): + for prop, value in self.properties.items(): if current_properties.get(prop, None) != value: self.set_property(prop, value) @@ -222,7 +222,7 @@ # Get all valid zfs-properties properties = dict() - for prop, value in module.params.iteritems(): + for prop, value in module.params.items(): # All freestyle params are zfs properties if prop not in module.argument_spec: # Reverse the boolification of freestyle zfs properties diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/VERSION ansible-2.2.1.0/lib/ansible/modules/extras/VERSION --- ansible-2.2.0.0/lib/ansible/modules/extras/VERSION 2016-11-01 03:43:54.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/VERSION 2017-01-16 17:04:03.000000000 +0000 @@ -1 +1 @@ -2.2.0.0 1 +2.2.1.0 1 diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/web_infrastructure/jenkins_plugin.py ansible-2.2.1.0/lib/ansible/modules/extras/web_infrastructure/jenkins_plugin.py --- ansible-2.2.0.0/lib/ansible/modules/extras/web_infrastructure/jenkins_plugin.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/web_infrastructure/jenkins_plugin.py 2017-01-16 17:04:03.000000000 +0000 @@ -438,6 +438,13 @@ msg_exception="Plugin installation has failed.", data=data) + hpi_file = '%s/plugins/%s.hpi' % ( + self.params['jenkins_home'], + self.params['name']) + + if os.path.isfile(hpi_file): + os.remove(hpi_file) + changed = True else: # Check if the plugin directory exists @@ -563,20 +570,11 @@ msg_exception="Updates download failed.") # Write the updates file - updates_file = tempfile.mkstemp() - - try: - fd = open(updates_file, 'wb') - except IOError: - e = get_exception() - self.module.fail_json( - msg="Cannot open the tmp updates file %s." % updates_file, - details=str(e)) - - fd.write(r.read()) + update_fd, updates_file = tempfile.mkstemp() + os.write(update_fd, r.read()) try: - fd.close() + os.close(update_fd) except IOError: e = get_exception() self.module.fail_json( @@ -640,25 +638,15 @@ def _write_file(self, f, data): # Store the plugin into a temp file and then move it - tmp_f = tempfile.mkstemp() - - try: - fd = open(tmp_f, 'wb') - except IOError: - e = get_exception() - self.module.fail_json( - msg='Cannot open the temporal plugin file %s.' % tmp_f, - details=str(e)) + tmp_f_fd, tmp_f = tempfile.mkstemp() if isinstance(data, str): - d = data + os.write(tmp_f_fd, data) else: - d = data.read() - - fd.write(d) + os.write(tmp_f_fd, data.read()) try: - fd.close() + os.close(tmp_f_fd) except IOError: e = get_exception() self.module.fail_json( diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/web_infrastructure/jira.py ansible-2.2.1.0/lib/ansible/modules/extras/web_infrastructure/jira.py --- ansible-2.2.0.0/lib/ansible/modules/extras/web_infrastructure/jira.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/web_infrastructure/jira.py 2017-01-16 17:04:03.000000000 +0000 @@ -311,7 +311,7 @@ comment=dict(), status=dict(), assignee=dict(), - fields=dict(default={}) + fields=dict(default={}, type='dict') ), supports_check_mode=False ) diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/web_infrastructure/letsencrypt.py ansible-2.2.1.0/lib/ansible/modules/extras/web_infrastructure/letsencrypt.py --- ansible-2.2.0.0/lib/ansible/modules/extras/web_infrastructure/letsencrypt.py 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/web_infrastructure/letsencrypt.py 2017-01-16 17:04:03.000000000 +0000 @@ -20,6 +20,7 @@ import binascii import copy +import locale import textwrap from datetime import datetime @@ -120,8 +121,8 @@ # for example: # # - copy: -# dest: /var/www/html/{{ sample_com_http_challenge['challenge_data']['sample.com']['http-01']['resource'] }} -# content: "{{ sample_com_http_challenge['challenge_data']['sample.com']['http-01']['resource_value'] }}" +# dest: /var/www/html/{{ sample_com_challenge['challenge_data']['sample.com']['http-01']['resource'] }} +# content: "{{ sample_com_challenge['challenge_data']['sample.com']['http-01']['resource_value'] }}" # when: sample_com_challenge|changed - letsencrypt: @@ -767,6 +768,9 @@ ), supports_check_mode = True, ) + + # AnsibleModule() changes the locale, so change it back to C because we rely on time.strptime() when parsing certificate dates. + locale.setlocale(locale.LC_ALL, "C") cert_days = get_cert_days(module,module.params['dest']) if cert_days < module.params['remaining_days']: diff -Nru ansible-2.2.0.0/lib/ansible/modules/extras/windows/win_nssm.ps1 ansible-2.2.1.0/lib/ansible/modules/extras/windows/win_nssm.ps1 --- ansible-2.2.0.0/lib/ansible/modules/extras/windows/win_nssm.ps1 2016-11-01 03:43:55.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/modules/extras/windows/win_nssm.ps1 2017-01-16 17:04:03.000000000 +0000 @@ -42,6 +42,33 @@ $user = Get-Attr $params "user" -default $null $password = Get-Attr $params "password" -default $null + +#abstract the calling of nssm because some PowerShell environments +#mishandle its stdout(which is Unicode) as UTF8 +Function Nssm-Invoke +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string]$cmd + ) + Try { + $encodingWas = [System.Console]::OutputEncoding + [System.Console]::OutputEncoding = [System.Text.Encoding]::Unicode + + $nssmOutput = invoke-expression "nssm $cmd" + return $nssmOutput + } + Catch { + $ErrorMessage = $_.Exception.Message + Fail-Json $result "an exception occurred when invoking NSSM: $ErrorMessage" + } + Finally { + # Set the console encoding back to what it was + [System.Console]::OutputEncoding = $encodingWas + } +} + Function Service-Exists { [CmdletBinding()] @@ -63,11 +90,11 @@ if (Service-Exists -name $name) { - $cmd = "nssm stop ""$name""" - $results = invoke-expression $cmd + $cmd = "stop ""$name""" + $results = Nssm-Invoke $cmd - $cmd = "nssm remove ""$name"" confirm" - $results = invoke-expression $cmd + $cmd = "remove ""$name"" confirm" + $results = Nssm-Invoke $cmd if ($LastExitCode -ne 0) { @@ -76,6 +103,7 @@ Throw "Error removing service ""$name""" } + Set-Attr $result "changed_by" "remove_service" $result.changed = $true } } @@ -101,9 +129,7 @@ if (!(Service-Exists -name $name)) { - $cmd = "nssm install ""$name"" $application" - - $results = invoke-expression $cmd + $results = Nssm-Invoke "install ""$name"" $application" if ($LastExitCode -ne 0) { @@ -112,11 +138,11 @@ Throw "Error installing service ""$name""" } + Set-Attr $result "changed_by" "install_service" $result.changed = $true } else { - $cmd = "nssm get ""$name"" Application" - $results = invoke-expression $cmd + $results = Nssm-Invoke "get ""$name"" Application" if ($LastExitCode -ne 0) { @@ -125,11 +151,11 @@ Throw "Error installing service ""$name""" } - if ($results -ne $application) + if ($results -cnotlike $application) { - $cmd = "nssm set ""$name"" Application $application" + $cmd = "set ""$name"" Application $application" - $results = invoke-expression $cmd + $results = Nssm-Invoke $cmd if ($LastExitCode -ne 0) { @@ -137,7 +163,9 @@ Set-Attr $result "nssm_error_log" "$results" Throw "Error installing service ""$name""" } + Set-Attr $result "application" "$application" + Set-Attr $result "changed_by" "reinstall_service" $result.changed = $true } } @@ -184,8 +212,8 @@ [string]$appParameters ) - $cmd = "nssm get ""$name"" AppParameters" - $results = invoke-expression $cmd + $cmd = "get ""$name"" AppParameters" + $results = Nssm-Invoke $cmd if ($LastExitCode -ne 0) { @@ -228,11 +256,11 @@ { if ($appParameters) { - $cmd = "nssm set ""$name"" AppParameters $singleLineParams" + $cmd = "set ""$name"" AppParameters $singleLineParams" } else { - $cmd = "nssm set ""$name"" AppParameters '""""'" + $cmd = "set ""$name"" AppParameters '""""'" } - $results = invoke-expression $cmd + $results = Nssm-Invoke $cmd if ($LastExitCode -ne 0) { @@ -241,11 +269,12 @@ Throw "Error updating AppParameters for service ""$name""" } + Set-Attr $result "changed_by" "update_app_parameters" $result.changed = $true } } -Function Nssm-Set-Ouput-Files +Function Nssm-Set-Output-Files { [CmdletBinding()] param( @@ -255,8 +284,8 @@ [string]$stderr ) - $cmd = "nssm get ""$name"" AppStdout" - $results = invoke-expression $cmd + $cmd = "get ""$name"" AppStdout" + $results = Nssm-Invoke $cmd if ($LastExitCode -ne 0) { @@ -265,16 +294,16 @@ Throw "Error retrieving existing stdout file for service ""$name""" } - if ($results -ne $stdout) + if ($results -cnotlike $stdout) { if (!$stdout) { - $cmd = "nssm reset ""$name"" AppStdout" + $cmd = "reset ""$name"" AppStdout" } else { - $cmd = "nssm set ""$name"" AppStdout $stdout" + $cmd = "set ""$name"" AppStdout $stdout" } - $results = invoke-expression $cmd + $results = Nssm-Invoke $cmd if ($LastExitCode -ne 0) { @@ -283,11 +312,12 @@ Throw "Error setting stdout file for service ""$name""" } + Set-Attr $result "changed_by" "set_stdout" $result.changed = $true } - $cmd = "nssm get ""$name"" AppStderr" - $results = invoke-expression $cmd + $cmd = "get ""$name"" AppStderr" + $results = Nssm-Invoke $cmd if ($LastExitCode -ne 0) { @@ -296,12 +326,12 @@ Throw "Error retrieving existing stderr file for service ""$name""" } - if ($results -ne $stderr) + if ($results -cnotlike $stderr) { if (!$stderr) { - $cmd = "nssm reset ""$name"" AppStderr" - $results = invoke-expression $cmd + $cmd = "reset ""$name"" AppStderr" + $results = Nssm-Invoke $cmd if ($LastExitCode -ne 0) { @@ -310,8 +340,8 @@ Throw "Error clearing stderr file setting for service ""$name""" } } else { - $cmd = "nssm set ""$name"" AppStderr $stderr" - $results = invoke-expression $cmd + $cmd = "set ""$name"" AppStderr $stderr" + $results = Nssm-Invoke $cmd if ($LastExitCode -ne 0) { @@ -321,6 +351,7 @@ } } + Set-Attr $result "changed_by" "set_stderr" $result.changed = $true } @@ -329,28 +360,28 @@ ### #set files to overwrite - $cmd = "nssm set ""$name"" AppStdoutCreationDisposition 2" - $results = invoke-expression $cmd + $cmd = "set ""$name"" AppStdoutCreationDisposition 2" + $results = Nssm-Invoke $cmd - $cmd = "nssm set ""$name"" AppStderrCreationDisposition 2" - $results = invoke-expression $cmd + $cmd = "set ""$name"" AppStderrCreationDisposition 2" + $results = Nssm-Invoke $cmd #enable file rotation - $cmd = "nssm set ""$name"" AppRotateFiles 1" - $results = invoke-expression $cmd + $cmd = "set ""$name"" AppRotateFiles 1" + $results = Nssm-Invoke $cmd #don't rotate until the service restarts - $cmd = "nssm set ""$name"" AppRotateOnline 0" - $results = invoke-expression $cmd + $cmd = "set ""$name"" AppRotateOnline 0" + $results = Nssm-Invoke $cmd #both of the below conditions must be met before rotation will happen #minimum age before rotating - $cmd = "nssm set ""$name"" AppRotateSeconds 86400" - $results = invoke-expression $cmd + $cmd = "set ""$name"" AppRotateSeconds 86400" + $results = Nssm-Invoke $cmd #minimum size before rotating - $cmd = "nssm set ""$name"" AppRotateBytes 104858" - $results = invoke-expression $cmd + $cmd = "set ""$name"" AppRotateBytes 104858" + $results = Nssm-Invoke $cmd } Function Nssm-Update-Credentials @@ -365,8 +396,8 @@ [string]$password ) - $cmd = "nssm get ""$name"" ObjectName" - $results = invoke-expression $cmd + $cmd = "get ""$name"" ObjectName" + $results = Nssm-Invoke $cmd if ($LastExitCode -ne 0) { @@ -386,8 +417,8 @@ } If ($results -ne $fullUser) { - $cmd = "nssm set ""$name"" ObjectName $fullUser $password" - $results = invoke-expression $cmd + $cmd = "set ""$name"" ObjectName $fullUser $password" + $results = Nssm-Invoke $cmd if ($LastExitCode -ne 0) { @@ -396,6 +427,7 @@ Throw "Error updating credentials for service ""$name""" } + Set-Attr $result "changed_by" "update_credentials" $result.changed = $true } } @@ -412,8 +444,8 @@ [string]$dependencies ) - $cmd = "nssm get ""$name"" DependOnService" - $results = invoke-expression $cmd + $cmd = "get ""$name"" DependOnService" + $results = Nssm-Invoke $cmd if ($LastExitCode -ne 0) { @@ -423,8 +455,8 @@ } If (($dependencies) -and ($results.Tolower() -ne $dependencies.Tolower())) { - $cmd = "nssm set ""$name"" DependOnService $dependencies" - $results = invoke-expression $cmd + $cmd = "set ""$name"" DependOnService $dependencies" + $results = Nssm-Invoke $cmd if ($LastExitCode -ne 0) { @@ -433,6 +465,7 @@ Throw "Error updating dependencies for service ""$name""" } + Set-Attr $result "changed_by" "update-dependencies" $result.changed = $true } } @@ -447,8 +480,8 @@ [string]$mode ) - $cmd = "nssm get ""$name"" Start" - $results = invoke-expression $cmd + $cmd = "get ""$name"" Start" + $results = Nssm-Invoke $cmd if ($LastExitCode -ne 0) { @@ -459,9 +492,9 @@ $modes=@{"auto" = "SERVICE_AUTO_START"; "manual" = "SERVICE_DEMAND_START"; "disabled" = "SERVICE_DISABLED"} $mappedMode = $modes.$mode - if ($mappedMode -ne $results) { - $cmd = "nssm set ""$name"" Start $mappedMode" - $results = invoke-expression $cmd + if ($results -cnotlike $mappedMode) { + $cmd = "set ""$name"" Start $mappedMode" + $results = Nssm-Invoke $cmd if ($LastExitCode -ne 0) { @@ -470,6 +503,7 @@ Throw "Error updating start mode for service ""$name""" } + Set-Attr $result "changed_by" "start_mode" $result.changed = $true } } @@ -482,8 +516,8 @@ [string]$name ) - $cmd = "nssm status ""$name""" - $results = invoke-expression $cmd + $cmd = "status ""$name""" + $results = Nssm-Invoke $cmd return ,$results } @@ -526,9 +560,9 @@ [string]$name ) - $cmd = "nssm start ""$name""" + $cmd = "start ""$name""" - $results = invoke-expression $cmd + $results = Nssm-Invoke $cmd if ($LastExitCode -ne 0) { @@ -537,6 +571,7 @@ Throw "Error starting service ""$name""" } + Set-Attr $result "changed_by" "start_service" $result.changed = $true } @@ -548,9 +583,9 @@ [string]$name ) - $cmd = "nssm stop ""$name""" + $cmd = "stop ""$name""" - $results = invoke-expression $cmd + $results = Nssm-Invoke $cmd if ($LastExitCode -ne 0) { @@ -559,6 +594,7 @@ Throw "Error stopping service ""$name""" } + Set-Attr $result "changed_by" "stop_service_command" $result.changed = $true } @@ -581,9 +617,9 @@ if ($currentStatus -ne "SERVICE_STOPPED") { - $cmd = "nssm stop ""$name""" + $cmd = "stop ""$name""" - $results = invoke-expression $cmd + $results = Nssm-Invoke $cmd if ($LastExitCode -ne 0) { @@ -592,6 +628,7 @@ Throw "Error stopping service ""$name""" } + Set-Attr $result "changed_by" "stop_service" $result.changed = $true } } @@ -612,7 +649,7 @@ { Nssm-Install -name $name -application $application Nssm-Update-AppParameters -name $name -appParameters $appParameters - Nssm-Set-Ouput-Files -name $name -stdout $stdoutFile -stderr $stderrFile + Nssm-Set-Output-Files -name $name -stdout $stdoutFile -stderr $stderrFile Nssm-Update-Dependencies -name $name -dependencies $dependencies Nssm-Update-Credentials -name $name -user $user -password $password Nssm-Update-StartMode -name $name -mode $startMode diff -Nru ansible-2.2.0.0/lib/ansible/module_utils/asa.py ansible-2.2.1.0/lib/ansible/module_utils/asa.py --- ansible-2.2.0.0/lib/ansible/module_utils/asa.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/module_utils/asa.py 2017-01-16 17:03:54.000000000 +0000 @@ -62,8 +62,13 @@ def authorize(self, params, **kwargs): passwd = params['auth_pass'] + errors = self.shell.errors + # Disable errors (if already in enable mode) + self.shell.errors = [] cmd = Command('enable', prompt=self.NET_PASSWD_RE, response=passwd) self.execute([cmd, 'no terminal pager']) + # Reapply error handling + self.shell.errors = errors def change_context(self, params): context = params['context'] diff -Nru ansible-2.2.0.0/lib/ansible/module_utils/azure_rm_common.py ansible-2.2.1.0/lib/ansible/module_utils/azure_rm_common.py --- ansible-2.2.0.0/lib/ansible/module_utils/azure_rm_common.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/module_utils/azure_rm_common.py 2017-01-16 17:03:54.000000000 +0000 @@ -240,12 +240,12 @@ new_tags = copy.copy(tags) if isinstance(tags, dict) else dict() changed = False if isinstance(self.module.params.get('tags'), dict): - for key, value in self.module.params['tags'].iteritems(): + for key, value in self.module.params['tags'].items(): if not new_tags.get(key) or new_tags[key] != value: changed = True new_tags[key] = value if isinstance(tags, dict): - for key, value in tags.iteritems(): + for key, value in tags.items(): if not self.module.params['tags'].get(key): new_tags.pop(key) changed = True @@ -318,7 +318,7 @@ def _get_env_credentials(self): env_credentials = dict() - for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.iteritems(): + for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.items(): env_credentials[attribute] = os.environ.get(env_variable, None) if env_credentials['profile']: @@ -337,7 +337,7 @@ self.log('Getting credentials') arg_credentials = dict() - for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.iteritems(): + for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.items(): arg_credentials[attribute] = params.get(attribute, None) # try module params diff -Nru ansible-2.2.0.0/lib/ansible/module_utils/basic.py ansible-2.2.1.0/lib/ansible/module_utils/basic.py --- ansible-2.2.0.0/lib/ansible/module_utils/basic.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/module_utils/basic.py 2017-01-16 17:03:54.000000000 +0000 @@ -635,6 +635,7 @@ see library/* for examples ''' + self._name = os.path.basename(__file__) #initialize name until we can parse from options self.argument_spec = argument_spec self.supports_check_mode = supports_check_mode self.check_mode = False @@ -714,7 +715,7 @@ self._set_defaults(pre=False) - if not self.no_log and self._verbosity >= 3: + if not self.no_log: self._log_invocation() # finally, make sure we're in a sane working dir @@ -1926,18 +1927,6 @@ creating = not os.path.exists(b_dest) try: - login_name = os.getlogin() - except OSError: - # not having a tty can cause the above to fail, so - # just get the LOGNAME environment variable instead - login_name = os.environ.get('LOGNAME', None) - - # if the original login_name doesn't match the currently - # logged-in user, or if the SUDO_USER environment variable - # is set, then this user has switched their credentials - switched_user = login_name and login_name != pwd.getpwuid(os.getuid())[0] or os.environ.get('SUDO_USER') - - try: # Optimistically try a rename, solves some corner cases and can avoid useless work, throws exception if not atomic. os.rename(b_src, b_dest) except (IOError, OSError): @@ -1975,12 +1964,13 @@ # close tmp file handle before file operations to prevent text file busy errors on vboxfs synced folders (windows host) os.close(tmp_dest_fd) # leaves tmp file behind when sudo and not root - if switched_user and os.getuid() != 0: + try: + shutil.move(b_src, b_tmp_dest_name) + except OSError: # cleanup will happen by 'rm' of tempdir # copy2 will preserve some metadata shutil.copy2(b_src, b_tmp_dest_name) - else: - shutil.move(b_src, b_tmp_dest_name) + if self.selinux_enabled(): self.set_context_if_different( b_tmp_dest_name, context, False) @@ -1995,12 +1985,14 @@ try: os.rename(b_tmp_dest_name, b_dest) except (shutil.Error, OSError, IOError): + e = get_exception() if unsafe_writes: - self._unsafe_writes(b_tmp_dest_name, b_dest, get_exception()) + self._unsafe_writes(b_tmp_dest_name, b_dest, e) else: - self.fail_json(msg='Could not replace file: %s to %s: %s' % (src, dest, exception)) + self.fail_json(msg='Could not replace file: %s to %s: %s' % (src, dest, e)) except (shutil.Error, OSError, IOError): - self.fail_json(msg='Could not replace file: %s to %s: %s' % (src, dest, exception)) + e = get_exception() + self.fail_json(msg='Could not replace file: %s to %s: %s' % (src, dest, e)) finally: self.cleanup(b_tmp_dest_name) @@ -2010,8 +2002,12 @@ umask = os.umask(0) os.umask(umask) os.chmod(b_dest, DEFAULT_PERM & ~umask) - if switched_user: - os.chown(b_dest, os.getuid(), os.getgid()) + try: + os.chown(b_dest, os.geteuid(), os.getegid()) + except OSError: + # We're okay with trying our best here. If the user is not + # root (or old Unices) they won't be able to chown. + pass if self.selinux_enabled(): # rename might not preserve context @@ -2195,14 +2191,13 @@ stderr=subprocess.PIPE, ) - if cwd and os.path.isdir(cwd): - kwargs['cwd'] = cwd - # store the pwd prev_dir = os.getcwd() # make sure we're in the right working directory if cwd and os.path.isdir(cwd): + cwd = os.path.abspath(os.path.expanduser(cwd)) + kwargs['cwd'] = cwd try: os.chdir(cwd) except (OSError, IOError): @@ -2214,7 +2209,6 @@ old_umask = os.umask(umask) try: - if self._debug: self.log('Executing: ' + clean_args) cmd = subprocess.Popen(args, **kwargs) diff -Nru ansible-2.2.0.0/lib/ansible/module_utils/cloudstack.py ansible-2.2.1.0/lib/ansible/module_utils/cloudstack.py --- ansible-2.2.0.0/lib/ansible/module_utils/cloudstack.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/module_utils/cloudstack.py 2017-01-16 17:03:54.000000000 +0000 @@ -28,6 +28,7 @@ # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import time +from ansible.module_utils.six import iteritems try: from cs import CloudStack, CloudStackException, read_config @@ -148,7 +149,7 @@ def has_changed(self, want_dict, current_dict, only_keys=None): - for key, value in want_dict.iteritems(): + for key, value in want_dict.items(): # Optionally limit by a list of keys if only_keys and key not in only_keys: @@ -343,6 +344,9 @@ zone = self.module.params.get('zone') zones = self.cs.listZones() + if not zones: + self.module.fail_json(msg="No zones available. Please create a zone first") + # use the first zone if no zone param given if not zone: self.zone = zones['zone'][0] @@ -510,12 +514,12 @@ if resource: returns = self.common_returns.copy() returns.update(self.returns) - for search_key, return_key in returns.iteritems(): + for search_key, return_key in returns.items(): if search_key in resource: self.result[return_key] = resource[search_key] # Bad bad API does not always return int when it should. - for search_key, return_key in self.returns_to_int.iteritems(): + for search_key, return_key in self.returns_to_int.items(): if search_key in resource: self.result[return_key] = int(resource[search_key]) diff -Nru ansible-2.2.0.0/lib/ansible/module_utils/dellos10.py ansible-2.2.1.0/lib/ansible/module_utils/dellos10.py --- ansible-2.2.0.0/lib/ansible/module_utils/dellos10.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/module_utils/dellos10.py 2017-01-16 17:03:54.000000000 +0000 @@ -42,8 +42,9 @@ if not contents: contents = module.config.get_config() module.params['config'] = contents - - return NetworkConfig(indent=1, contents=contents[0]) + return NetworkConfig(indent=1, contents=contents[0]) + else: + return NetworkConfig(indent=1, contents=contents) def get_sublevel_config(running_config, module): @@ -54,11 +55,13 @@ contents = obj.children contents[:0] = module.params['parents'] + indent = 0 for c in contents: if isinstance(c, str): - current_config_contents.append(c) + current_config_contents.append(c.rjust(len(c) + indent, ' ')) if isinstance(c, ConfigLine): current_config_contents.append(c.raw) + indent = indent + 1 sublevel_config = '\n'.join(current_config_contents) return sublevel_config diff -Nru ansible-2.2.0.0/lib/ansible/module_utils/dellos9.py ansible-2.2.1.0/lib/ansible/module_utils/dellos9.py --- ansible-2.2.0.0/lib/ansible/module_utils/dellos9.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/module_utils/dellos9.py 2017-01-16 17:03:54.000000000 +0000 @@ -42,8 +42,9 @@ if not contents: contents = module.config.get_config() module.params['config'] = contents - - return NetworkConfig(indent=1, contents=contents[0]) + return NetworkConfig(indent=1, contents=contents[0]) + else: + return NetworkConfig(indent=1, contents=contents) def get_sublevel_config(running_config, module): @@ -54,11 +55,13 @@ contents = obj.children contents[:0] = module.params['parents'] + indent = 0 for c in contents: if isinstance(c, str): - current_config_contents.append(c) + current_config_contents.append(c.rjust(len(c) + indent, ' ')) if isinstance(c, ConfigLine): current_config_contents.append(c.raw) + indent = indent + 1 sublevel_config = '\n'.join(current_config_contents) return sublevel_config diff -Nru ansible-2.2.0.0/lib/ansible/module_utils/ec2.py ansible-2.2.1.0/lib/ansible/module_utils/ec2.py --- ansible-2.2.0.0/lib/ansible/module_utils/ec2.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/module_utils/ec2.py 2017-01-16 17:03:54.000000000 +0000 @@ -343,7 +343,7 @@ snake_dict = {} - for k, v in camel_dict.iteritems(): + for k, v in camel_dict.items(): if isinstance(v, dict): snake_dict[camel_to_snake(k)] = camel_dict_to_snake_dict(v) elif isinstance(v, list): @@ -378,7 +378,7 @@ """ filters_list = [] - for k,v in filters_dict.iteritems(): + for k,v in filters_dict.items(): filter_dict = {'Name': k} if isinstance(v, string_types): filter_dict['Values'] = [v] @@ -443,7 +443,7 @@ """ tags_list = [] - for k,v in tags_dict.iteritems(): + for k,v in tags_dict.items(): tags_list.append({'Key': k, 'Value': v}) return tags_list diff -Nru ansible-2.2.0.0/lib/ansible/module_utils/eos.py ansible-2.2.1.0/lib/ansible/module_utils/eos.py --- ansible-2.2.0.0/lib/ansible/module_utils/eos.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/module_utils/eos.py 2017-01-16 17:03:54.000000000 +0000 @@ -166,6 +166,7 @@ self.url_args.params['url_username'] = params['username'] self.url_args.params['url_password'] = params['password'] self.url_args.params['validate_certs'] = params['validate_certs'] + self.url_args.params['timeout'] = params['timeout'] if params['use_ssl']: proto = 'https' @@ -205,10 +206,11 @@ data = json.dumps(body) headers = {'Content-Type': 'application/json-rpc'} + timeout = self.url_args['timeout'] response, headers = fetch_url( self.url_args, self.url, data=data, headers=headers, - method='POST' + method='POST', timeout=timeout ) if headers['status'] != 200: diff -Nru ansible-2.2.0.0/lib/ansible/module_utils/exoscale.py ansible-2.2.1.0/lib/ansible/module_utils/exoscale.py --- ansible-2.2.0.0/lib/ansible/module_utils/exoscale.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/module_utils/exoscale.py 2017-01-16 17:03:54.000000000 +0000 @@ -143,7 +143,7 @@ def has_changed(self, want_dict, current_dict, only_keys=None): changed = False - for key, value in want_dict.iteritems(): + for key, value in want_dict.items(): # Optionally limit by a list of keys if only_keys and key not in only_keys: continue diff -Nru ansible-2.2.0.0/lib/ansible/module_utils/facts.py ansible-2.2.1.0/lib/ansible/module_utils/facts.py --- ansible-2.2.0.0/lib/ansible/module_utils/facts.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/module_utils/facts.py 2017-01-16 17:03:54.000000000 +0000 @@ -34,15 +34,8 @@ from ansible.module_utils.basic import get_all_subclasses from ansible.module_utils.six import PY3, iteritems -from ansible.module_utils.six.moves import configparser, StringIO -from ansible.module_utils._text import to_native - -try: - # python2 - from string import maketrans -except ImportError: - # python3 - maketrans = str.maketrans # TODO: is this really identical? +from ansible.module_utils.six.moves import configparser, StringIO, reduce +from ansible.module_utils._text import to_native, to_text try: import selinux @@ -335,11 +328,15 @@ else: proc_1 = os.path.basename(proc_1) + # The ps command above may return "COMMAND" if the user cannot read /proc, e.g. with grsecurity + if proc_1 == "COMMAND\n": + proc_1 = None + if proc_1 is not None: proc_1 = to_native(proc_1) proc_1 = proc_1.strip() - if proc_1 == 'init' or proc_1.endswith('sh'): + if proc_1 is not None and (proc_1 == 'init' or proc_1.endswith('sh')): # many systems return init, so this cannot be trusted, if it ends in 'sh' it probalby is a shell in a container proc_1 = None @@ -637,7 +634,7 @@ Archlinux = 'Archlinux', Manjaro = 'Archlinux', Mandriva = 'Mandrake', Mandrake = 'Mandrake', Altlinux = 'Altlinux', Solaris = 'Solaris', Nexenta = 'Solaris', OmniOS = 'Solaris', OpenIndiana = 'Solaris', SmartOS = 'Solaris', AIX = 'AIX', Alpine = 'Alpine', MacOSX = 'Darwin', - FreeBSD = 'FreeBSD', HPUX = 'HP-UX', openSUSE_Leap = 'Suse' + FreeBSD = 'FreeBSD', HPUX = 'HP-UX', openSUSE_Leap = 'Suse', Neon = 'Debian' ) def __init__(self, module): @@ -1213,7 +1210,11 @@ self.facts[k] = 'NA' def _run_lsblk(self, lsblk_path): - args = ['--list', '--noheadings', '--paths', '--output', 'NAME,UUID'] + # call lsblk and collect all uuids + # --exclude 2 makes lsblk ignore floppy disks, which are slower to answer than typical timeouts + # this uses the linux major device number + # for details see https://www.kernel.org/doc/Documentation/devices.txt + args = ['--list', '--noheadings', '--paths', '--output', 'NAME,UUID', '--exclude', '2'] cmd = [lsblk_path] + args rc, out, err = self.module.run_command(cmd) return rc, out, err @@ -1570,7 +1571,6 @@ - devices """ platform = 'OpenBSD' - DMESG_BOOT = '/var/run/dmesg.boot' def populate(self): self.sysctl = self.get_sysctl() @@ -1621,26 +1621,26 @@ # total: 69268k bytes allocated = 0k used, 69268k available rc, out, err = self.module.run_command("/sbin/swapctl -sk") if rc == 0: - swaptrans = maketrans(' ', ' ') - data = out.split() - self.facts['swapfree_mb'] = int(data[-2].translate(swaptrans, "kmg")) // 1024 - self.facts['swaptotal_mb'] = int(data[1].translate(swaptrans, "kmg")) // 1024 + swaptrans = { ord(u'k'): None, ord(u'm'): None, ord(u'g'): None} + data = to_text(out, errors='surrogate_or_strict').split() + self.facts['swapfree_mb'] = int(data[-2].translate(swaptrans)) // 1024 + self.facts['swaptotal_mb'] = int(data[1].translate(swaptrans)) // 1024 def get_processor_facts(self): processor = [] - dmesg_boot = get_file_content(OpenBSDHardware.DMESG_BOOT) - if not dmesg_boot: - rc, dmesg_boot, err = self.module.run_command("/sbin/dmesg") - i = 0 - for line in dmesg_boot.splitlines(): - if line.split(' ', 1)[0] == 'cpu%i:' % i: - processor.append(line.split(' ', 1)[1]) - i = i + 1 - processor_count = i + for i in range(int(self.sysctl['hw.ncpu'])): + processor.append(self.sysctl['hw.model']) + self.facts['processor'] = processor - self.facts['processor_count'] = processor_count - # I found no way to figure out the number of Cores per CPU in OpenBSD - self.facts['processor_cores'] = 'NA' + # The following is partly a lie because there is no reliable way to + # determine the number of physical CPUs in the system. We can only + # query the number of logical CPUs, which hides the number of cores. + # On amd64/i386 we could try to inspect the smt/core/package lines in + # dmesg, however even those have proven to be unreliable. + # So take a shortcut and report the logical number of processors in + # 'processor_count' and 'processor_cores' and leave it at that. + self.facts['processor_count'] = self.sysctl['hw.ncpu'] + self.facts['processor_cores'] = self.sysctl['hw.ncpu'] def get_device_facts(self): devices = [] diff -Nru ansible-2.2.0.0/lib/ansible/module_utils/junos.py ansible-2.2.1.0/lib/ansible/module_utils/junos.py --- ansible-2.2.0.0/lib/ansible/module_utils/junos.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/module_utils/junos.py 2017-01-16 17:03:54.000000000 +0000 @@ -111,6 +111,7 @@ try: self.device = Device(host, **kwargs) self.device.open() + self.device.timeout = params['timeout'] except ConnectError: exc = get_exception() self.raise_exc('unable to connect to %s: %s' % (host, str(exc))) @@ -183,8 +184,8 @@ merge = False overwrite = False elif overwrite: - merge = True - overwrite = False + merge = False + overwrite = True else: merge = True overwrite = False diff -Nru ansible-2.2.0.0/lib/ansible/module_utils/known_hosts.py ansible-2.2.1.0/lib/ansible/module_utils/known_hosts.py --- ansible-2.2.0.0/lib/ansible/module_utils/known_hosts.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/module_utils/known_hosts.py 2017-01-16 17:03:54.000000000 +0000 @@ -187,6 +187,9 @@ this_cmd = "%s -t %s %s" % (keyscan_cmd, key_type, fqdn) rc, out, err = module.run_command(this_cmd) + # ssh-keyscan gives a 0 exit code and prints nothins on timeout + if rc != 0 or not out: + module.fail_json(msg='failed to get the hostkey for %s' % fqdn) module.append_to_file(user_host_file, out) return rc, out, err diff -Nru ansible-2.2.0.0/lib/ansible/module_utils/openstack.py ansible-2.2.1.0/lib/ansible/module_utils/openstack.py --- ansible-2.2.0.0/lib/ansible/module_utils/openstack.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/module_utils/openstack.py 2017-01-16 17:03:54.000000000 +0000 @@ -28,6 +28,7 @@ import os +from ansible.module_utils.six import iteritems def openstack_argument_spec(): # DEPRECATED: This argument spec is only used for the deprecated old @@ -61,7 +62,7 @@ def openstack_find_nova_addresses(addresses, ext_tag, key_name=None): ret = [] - for (k, v) in addresses.iteritems(): + for (k, v) in iteritems(addresses): if key_name and k == key_name: ret.extend([addrs['addr'] for addrs in v]) else: diff -Nru ansible-2.2.0.0/lib/ansible/module_utils/service.py ansible-2.2.1.0/lib/ansible/module_utils/service.py --- ansible-2.2.0.0/lib/ansible/module_utils/service.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/module_utils/service.py 2017-01-16 17:03:54.000000000 +0000 @@ -27,4 +27,24 @@ # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # -# This file is a placeholder for common code for the future split 'service' modules. +import os +import glob + +def sysv_is_enabled(name): + return bool(glob.glob('/etc/rc?.d/S??%s' % name)) + +def get_sysv_script(name): + + if name.startswith('/'): + result = name + else: + result = '/etc/init.d/%s' % name + + return result + +def sysv_exists(name): + return os.path.exists(get_sysv_script(name)) + +def fail_if_missing(module, found, service, msg=''): + if not found: + module.fail_json(msg='Could not find the requested service %s: %s' % (service, msg)) diff -Nru ansible-2.2.0.0/lib/ansible/module_utils/six.py ansible-2.2.1.0/lib/ansible/module_utils/six.py --- ansible-2.2.0.0/lib/ansible/module_utils/six.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/module_utils/six.py 2017-01-16 17:03:54.000000000 +0000 @@ -38,6 +38,7 @@ class_types = type, text_type = str binary_type = bytes + cmp = lambda a, b: (a > b) - (a < b) MAXSIZE = sys.maxsize else: @@ -46,6 +47,7 @@ class_types = (type, types.ClassType) text_type = unicode binary_type = str + cmp = cmp if sys.platform.startswith("java"): # Jython always uses 32 bits. diff -Nru ansible-2.2.0.0/lib/ansible/module_utils/urls.py ansible-2.2.1.0/lib/ansible/module_utils/urls.py --- ansible-2.2.0.0/lib/ansible/module_utils/urls.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/module_utils/urls.py 2017-01-16 17:03:54.000000000 +0000 @@ -182,6 +182,8 @@ del libssl +LOADED_VERIFY_LOCATIONS = set() + HAS_MATCH_HOSTNAME = True try: from ssl import match_hostname, CertificateError @@ -307,7 +309,7 @@ # ca cert, regardless of validity, for Python on Mac OS to use the # keychain functionality in OpenSSL for validating SSL certificates. # See: http://mercurial.selenic.com/wiki/CACertificates#Mac_OS_X_10.6_and_higher -DUMMY_CA_CERT = """-----BEGIN CERTIFICATE----- +b_DUMMY_CA_CERT = b("""-----BEGIN CERTIFICATE----- MIICvDCCAiWgAwIBAgIJAO8E12S7/qEpMA0GCSqGSIb3DQEBBQUAMEkxCzAJBgNV BAYTAlVTMRcwFQYDVQQIEw5Ob3J0aCBDYXJvbGluYTEPMA0GA1UEBxMGRHVyaGFt MRAwDgYDVQQKEwdBbnNpYmxlMB4XDTE0MDMxODIyMDAyMloXDTI0MDMxNTIyMDAy @@ -324,7 +326,7 @@ qFy+aenWXsC0ZvrikFxbQnX8GVtDADtVznxOi7XzFw7JOxdsVrpXgSN0eh0aMzvV zKPZsZ2miVGclicJHzm5q080b1p/sZtuKIEZk6vZqEg= -----END CERTIFICATE----- -""" +""") # # Exceptions @@ -510,9 +512,15 @@ newheaders = dict((k,v) for k,v in req.headers.items() if k.lower() not in ("content-length", "content-type") ) + try: + # Python 2-3.3 + origin_req_host = req.get_origin_req_host() + except AttributeError: + # Python 3.4+ + origin_req_host = req.origin_req_host return urllib_request.Request(newurl, headers=newheaders, - origin_req_host=req.get_origin_req_host(), + origin_req_host=origin_req_host, unverifiable=True) else: raise urllib_error.HTTPError(req.get_full_url(), code, msg, hdrs, fp) @@ -590,10 +598,12 @@ paths_checked.append('/etc/ansible') tmp_fd, tmp_path = tempfile.mkstemp() + to_add_fd, to_add_path = tempfile.mkstemp() + to_add = False # Write the dummy ca cert if we are running on Mac OS X if system == 'Darwin': - os.write(tmp_fd, DUMMY_CA_CERT) + os.write(tmp_fd, b_DUMMY_CA_CERT) # Default Homebrew path for OpenSSL certs paths_checked.append('/usr/local/etc/openssl') @@ -608,13 +618,21 @@ if os.path.isfile(full_path) and os.path.splitext(f)[1] in ('.crt','.pem'): try: cert_file = open(full_path, 'rb') - os.write(tmp_fd, cert_file.read()) - os.write(tmp_fd, b('\n')) + cert = cert_file.read() cert_file.close() + os.write(tmp_fd, cert) + os.write(tmp_fd, b('\n')) + if full_path not in LOADED_VERIFY_LOCATIONS: + to_add = True + os.write(to_add_fd, cert) + os.write(to_add_fd, b('\n')) + LOADED_VERIFY_LOCATIONS.add(full_path) except (OSError, IOError): pass - return (tmp_path, paths_checked) + if not to_add: + to_add_path = None + return (tmp_path, to_add_path, paths_checked) def validate_proxy_response(self, response, valid_codes=[200]): ''' @@ -643,17 +661,18 @@ return False return True - def _make_context(self, tmp_ca_cert_path): + def _make_context(self, to_add_ca_cert_path): context = create_default_context() - context.load_verify_locations(tmp_ca_cert_path) + if to_add_ca_cert_path: + context.load_verify_locations(to_add_ca_cert_path) return context def http_request(self, req): - tmp_ca_cert_path, paths_checked = self.get_ca_certs() + tmp_ca_cert_path, to_add_ca_cert_path, paths_checked = self.get_ca_certs() https_proxy = os.environ.get('https_proxy') context = None if HAS_SSLCONTEXT: - context = self._make_context(tmp_ca_cert_path) + context = self._make_context(to_add_ca_cert_path) # Detect if 'no_proxy' environment variable is set and if our URL is included use_proxy = self.detect_no_proxy(req.get_full_url()) @@ -719,6 +738,14 @@ except: pass + try: + # cleanup the temp file created, don't worry + # if it fails for some reason + if to_add_ca_cert_path: + os.remove(to_add_ca_cert_path) + except: + pass + return req https_request = http_request @@ -798,9 +825,11 @@ # use this username/password combination for urls # for which `theurl` is a super-url authhandler = urllib_request.HTTPBasicAuthHandler(passman) + digest_authhandler = urllib_request.HTTPDigestAuthHandler(passman) # create the AuthHandler handlers.append(authhandler) + handlers.append(digest_authhandler) elif username and force_basic_auth: headers["Authorization"] = basic_auth_header(username, password) @@ -841,6 +870,7 @@ opener = urllib_request.build_opener(*handlers) urllib_request.install_opener(opener) + data = to_bytes(data, nonstring='passthru') if method: if method.upper() not in ('OPTIONS','GET','HEAD','POST','PUT','DELETE','TRACE','CONNECT','PATCH'): raise ConnectionError('invalid HTTP request method; %s' % method.upper()) @@ -853,13 +883,14 @@ if http_agent: request.add_header('User-agent', http_agent) - # if we're ok with getting a 304, set the timestamp in the - # header, otherwise make sure we don't get a cached copy - if last_mod_time and not force: + # Cache control + # Either we directly force a cache refresh + if force: + request.add_header('cache-control', 'no-cache') + # or we do it if the original is more recent than our copy + elif last_mod_time: tstamp = last_mod_time.strftime('%a, %d %b %Y %H:%M:%S +0000') request.add_header('If-Modified-Since', tstamp) - else: - request.add_header('cache-control', 'no-cache') # user defined headers now, which may override things we've set above if headers: diff -Nru ansible-2.2.0.0/lib/ansible/parsing/dataloader.py ansible-2.2.1.0/lib/ansible/parsing/dataloader.py --- ansible-2.2.0.0/lib/ansible/parsing/dataloader.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/parsing/dataloader.py 2017-01-16 17:03:54.000000000 +0000 @@ -31,7 +31,7 @@ from ansible.errors.yaml_strings import YAML_SYNTAX_ERROR from ansible.module_utils.basic import is_executable from ansible.module_utils._text import to_bytes, to_native, to_text -from ansible.parsing.vault import VaultLib, is_encrypted, is_encrypted_file +from ansible.parsing.vault import VaultLib, b_HEADER, is_encrypted, is_encrypted_file from ansible.parsing.quoting import unquote from ansible.parsing.yaml.loader import AnsibleLoader from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleUnicode @@ -116,7 +116,9 @@ parsed_data = self._FILE_CACHE[file_name] else: # read the file contents and load the data structure from them - (file_data, show_content) = self._get_file_contents(file_name) + (b_file_data, show_content) = self._get_file_contents(file_name) + + file_data = to_text(b_file_data, errors='surrogate_or_strict') parsed_data = self.load(data=file_data, file_name=file_name, show_content=show_content) # cache the file contents for next time @@ -178,7 +180,6 @@ data = self._vault.decrypt(data, filename=b_file_name) show_content = False - data = to_text(data, errors='surrogate_or_strict') return (data, show_content) except (IOError, OSError) as e: @@ -300,6 +301,7 @@ result = test_path else: search = [] + display.debug(u'evaluation_path:\n\t%s' % '\n\t'.join(paths)) for path in paths: upath = unfrackpath(path) b_upath = to_bytes(upath, errors='surrogate_or_strict') @@ -314,18 +316,20 @@ search.append(os.path.join(b_mydir, b_source)) else: # don't add dirname if user already is using it in source - if b_source.split(b'/')[0] == b_dirname: - search.append(os.path.join(b_upath, b_source)) - else: + if b_source.split(b'/')[0] != b_dirname: search.append(os.path.join(b_upath, b_dirname, b_source)) - search.append(os.path.join(b_upath, b'tasks', b_source)) + search.append(os.path.join(b_upath, b_source)) + elif b_dirname not in b_source.split(b'/'): # don't add dirname if user already is using it in source - search.append(os.path.join(b_upath, b_dirname, b_source)) + if b_source.split(b'/')[0] != dirname: + search.append(os.path.join(b_upath, b_dirname, b_source)) search.append(os.path.join(b_upath, b_source)) # always append basedir as last resort - search.append(os.path.join(to_bytes(self.get_basedir()), b_dirname, b_source)) + # don't add dirname if user already is using it in source + if b_source.split(b'/')[0] != dirname: + search.append(os.path.join(to_bytes(self.get_basedir()), b_dirname, b_source)) search.append(os.path.join(to_bytes(self.get_basedir()), b_source)) display.debug(u'search_path:\n\t%s' % to_text(b'\n\t'.join(search))) @@ -399,7 +403,10 @@ try: with open(to_bytes(real_path), 'rb') as f: - if is_encrypted_file(f): + # Limit how much of the file is read since we do not know + # whether this is a vault file and therefore it could be very + # large. + if is_encrypted_file(f, count=len(b_HEADER)): # if the file is encrypted and no password was specified, # the decrypt call would throw an error, but we check first # since the decrypt function doesn't know the file name diff -Nru ansible-2.2.0.0/lib/ansible/parsing/vault/__init__.py ansible-2.2.1.0/lib/ansible/parsing/vault/__init__.py --- ansible-2.2.0.0/lib/ansible/parsing/vault/__init__.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/parsing/vault/__init__.py 2017-01-16 17:03:54.000000000 +0000 @@ -398,18 +398,18 @@ self._shred_file(tmp_path) raise - tmpdata = self.read_data(tmp_path) + b_tmpdata = self.read_data(tmp_path) # Do nothing if the content has not changed - if existing_data == tmpdata and not force_save: + if existing_data == b_tmpdata and not force_save: self._shred_file(tmp_path) return # encrypt new data and write out to tmp # An existing vaultfile will always be UTF-8, # so decode to unicode here - enc_data = self.vault.encrypt(tmpdata.decode()) - self.write_data(enc_data, tmp_path) + b_ciphertext = self.vault.encrypt(b_tmpdata) + self.write_data(b_ciphertext, tmp_path) # shuffle tmp file into place self.shuffle_files(tmp_path, filename) @@ -420,9 +420,9 @@ # A file to be encrypted into a vaultfile could be any encoding # so treat the contents as a byte string. - plaintext = self.read_data(filename) - ciphertext = self.vault.encrypt(plaintext) - self.write_data(ciphertext, output_file or filename) + b_plaintext = self.read_data(filename) + b_ciphertext = self.vault.encrypt(b_plaintext) + self.write_data(b_ciphertext, output_file or filename) def decrypt_file(self, filename, output_file=None): @@ -486,6 +486,10 @@ except AnsibleError as e: raise AnsibleError("%s for %s" % (to_bytes(e),to_bytes(filename))) + # This is more or less an assert, see #18247 + if new_password is None: + raise AnsibleError('The value for the new_password to rekey %s with is not valid' % filename) + new_vault = VaultLib(new_password) new_ciphertext = new_vault.encrypt(plaintext) diff -Nru ansible-2.2.0.0/lib/ansible/parsing/yaml/objects.py ansible-2.2.1.0/lib/ansible/parsing/yaml/objects.py --- ansible-2.2.0.0/lib/ansible/parsing/yaml/objects.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/parsing/yaml/objects.py 2017-01-16 17:03:54.000000000 +0000 @@ -132,3 +132,6 @@ def __unicode__(self): return unicode(self.data) + + def encode(self, encoding=None, errors=None): + return self.data.encode(encoding, errors) diff -Nru ansible-2.2.0.0/lib/ansible/playbook/base.py ansible-2.2.1.0/lib/ansible/playbook/base.py --- ansible-2.2.0.0/lib/ansible/playbook/base.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/playbook/base.py 2017-01-16 17:03:54.000000000 +0000 @@ -111,7 +111,7 @@ method = "_get_attr_%s" % attr_name if method in src_dict or method in dst_dict: getter = partial(_generic_g_method, attr_name) - elif '_get_parent_attribute' in dst_dict and value.inherit: + elif ('_get_parent_attribute' in dst_dict or '_get_parent_attribute' in src_dict) and value.inherit: getter = partial(_generic_g_parent, attr_name) else: getter = partial(_generic_g, attr_name) @@ -131,7 +131,9 @@ for parent in parents: if hasattr(parent, '__dict__'): _create_attrs(parent.__dict__, dst_dict) - _process_parents(parent.__bases__, dst_dict) + new_dst_dict = parent.__dict__.copy() + new_dst_dict.update(dst_dict) + _process_parents(parent.__bases__, new_dst_dict) # create some additional class attributes dct['_attributes'] = dict() @@ -480,7 +482,7 @@ except TypeError as e: raise AnsibleParserError("Invalid variable name in vars specified for %s: %s" % (self.__class__.__name__, e), obj=ds) - def _extend_value(self, value, new_value): + def _extend_value(self, value, new_value, prepend=False): ''' Will extend the value given with new_value (and will turn both into lists if they are not so already). The values are run through @@ -492,7 +494,12 @@ if not isinstance(new_value, list): new_value = [ new_value ] - return [i for i,_ in itertools.groupby(value + new_value) if i is not None] + if prepend: + combined = new_value + value + else: + combined = value + new_value + + return [i for i,_ in itertools.groupby(combined) if i is not None] def serialize(self): ''' diff -Nru ansible-2.2.0.0/lib/ansible/playbook/block.py ansible-2.2.1.0/lib/ansible/playbook/block.py --- ansible-2.2.0.0/lib/ansible/playbook/block.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/playbook/block.py 2017-01-16 17:03:54.000000000 +0000 @@ -56,6 +56,9 @@ super(Block, self).__init__() + def __repr__(self): + return "BLOCK(uuid=%s)(id=%s)(parent=%s)" % (self._uuid, id(self), self._parent) + def get_vars(self): ''' Blocks do not store variables directly, however they may be a member @@ -255,17 +258,6 @@ self._parent = p self._dep_chain = self._parent.get_dep_chain() - def evaluate_conditional(self, templar, all_vars): - dep_chain = self.get_dep_chain() - if dep_chain: - for dep in dep_chain: - if not dep.evaluate_conditional(templar, all_vars): - return False - if self._parent is not None: - if not self._parent.evaluate_conditional(templar, all_vars): - return False - return super(Block, self).evaluate_conditional(templar, all_vars) - def set_loader(self, loader): self._loader = loader if self._parent: @@ -281,7 +273,7 @@ def _get_attr_environment(self): return self._get_parent_attribute('environment', extend=True) - def _get_parent_attribute(self, attr, extend=False): + def _get_parent_attribute(self, attr, extend=False, prepend=False): ''' Generic logic to get the attribute or parent attribute for a block value. ''' @@ -294,7 +286,7 @@ try: parent_value = getattr(self._parent, attr, None) if extend: - value = self._extend_value(value, parent_value) + value = self._extend_value(value, parent_value, prepend) else: value = parent_value except AttributeError: @@ -303,7 +295,7 @@ try: parent_value = getattr(self._role, attr, None) if extend: - value = self._extend_value(value, parent_value) + value = self._extend_value(value, parent_value, prepend) else: value = parent_value @@ -313,7 +305,7 @@ for dep in dep_chain: dep_value = getattr(dep, attr, None) if extend: - value = self._extend_value(value, dep_value) + value = self._extend_value(value, dep_value, prepend) else: value = dep_value @@ -325,7 +317,7 @@ try: parent_value = getattr(self._play, attr, None) if extend: - value = self._extend_value(value, parent_value) + value = self._extend_value(value, parent_value, prepend) else: value = parent_value except AttributeError: diff -Nru ansible-2.2.0.0/lib/ansible/playbook/conditional.py ansible-2.2.1.0/lib/ansible/playbook/conditional.py --- ansible-2.2.0.0/lib/ansible/playbook/conditional.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/playbook/conditional.py 2017-01-16 17:03:54.000000000 +0000 @@ -19,6 +19,9 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import ast +import re + from jinja2.exceptions import UndefinedError from ansible.compat.six import text_type @@ -27,6 +30,9 @@ from ansible.template import Templar from ansible.module_utils._text import to_native +LOOKUP_REGEX = re.compile(r'lookup\s*\(') +VALID_VAR_REGEX = re.compile("^[_A-Za-z][_a-zA-Z0-9]*$") + class Conditional: ''' @@ -51,6 +57,17 @@ if not isinstance(value, list): setattr(self, name, [ value ]) + def _get_attr_when(self): + ''' + Override for the 'tags' getattr fetcher, used from Base. + ''' + when = self._attributes['when'] + if when is None: + when = [] + if hasattr(self, '_get_parent_attribute'): + when = self._get_parent_attribute('when', extend=True, prepend=True) + return when + def evaluate_conditional(self, templar, all_vars): ''' Loops through the conditionals set on this object, returning @@ -88,21 +105,60 @@ if conditional is None or conditional == '': return True - if conditional in all_vars and '-' not in text_type(all_vars[conditional]): + # pull the "bare" var out, which allows for nested conditionals + # and things like: + # - assert: + # that: + # - item + # with_items: + # - 1 == 1 + if conditional in all_vars and VALID_VAR_REGEX.match(conditional): conditional = all_vars[conditional] # make sure the templar is using the variables specified with this method templar.set_available_variables(variables=all_vars) try: - conditional = templar.template(conditional) + # if the conditional is "unsafe", disable lookups + disable_lookups = hasattr(conditional, '__UNSAFE__') + conditional = templar.template(conditional, disable_lookups=disable_lookups) if not isinstance(conditional, text_type) or conditional == "": return conditional - # a Jinja2 evaluation that results in something Python can eval! + # update the lookups flag, as the string returned above may now be unsafe + # and we don't want future templating calls to do unsafe things + disable_lookups |= hasattr(conditional, '__UNSAFE__') + + # now we generated the "presented" string, which is a jinja2 if/else block + # used to evaluate the conditional. First, we do some low-level jinja2 parsing + # involving the AST format of the statement to ensure we don't do anything + # unsafe (using the disable_lookup flag above) + e = templar.environment.overlay() + e.filters.update(templar._get_filters()) + e.tests.update(templar._get_tests()) + presented = "{%% if %s %%} True {%% else %%} False {%% endif %%}" % conditional - conditional = templar.template(presented) - val = conditional.strip() + res = e._parse(presented, None, None) + res = e._generate(res, None, None, defer_init=True) + parsed = ast.parse(res, mode='exec') + + class CleansingNodeVisitor(ast.NodeVisitor): + def generic_visit(self, node, inside_call=False): + if isinstance(node, ast.Call): + inside_call = True + elif isinstance(node, ast.Str): + # calling things with a dunder is generally bad at this point... + if inside_call and disable_lookups and node.s.startswith("__"): + raise AnsibleError("Invalid access found in the presented conditional: '%s'" % conditional) + # iterate over all child nodes + for child_node in ast.iter_child_nodes(node): + self.generic_visit(child_node, inside_call=inside_call) + + cnv = CleansingNodeVisitor() + cnv.visit(parsed) + + # and finally we templated the presented string and look at the resulting string + val = templar.template(presented, disable_lookups=disable_lookups).strip() if val == "True": return True elif val == "False": @@ -114,10 +170,10 @@ # variable was undefined. If we happened to be # looking for an undefined variable, return True, # otherwise fail - if "is undefined" in original: + if "is undefined" in original or "is not defined" in original or "not is defined" in original: return True - elif "is defined" in original: + elif "is defined" in original or "is not undefined" in original or "not is undefined" in original: return False else: - raise AnsibleError("error while evaluating conditional (%s): %s" % (original, e)) + raise AnsibleUndefinedVariable("error while evaluating conditional (%s): %s" % (original, e)) diff -Nru ansible-2.2.0.0/lib/ansible/playbook/helpers.py ansible-2.2.1.0/lib/ansible/playbook/helpers.py --- ansible-2.2.0.0/lib/ansible/playbook/helpers.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/playbook/helpers.py 2017-01-16 17:03:54.000000000 +0000 @@ -40,6 +40,8 @@ # we import here to prevent a circular dependency with imports from ansible.playbook.block import Block + from ansible.playbook.task_include import TaskInclude + from ansible.playbook.role_include import IncludeRole assert isinstance(ds, (list, type(None))) @@ -54,14 +56,17 @@ task_include=task_include, use_handlers=use_handlers, variable_manager=variable_manager, - loader=loader + loader=loader, ) # Implicit blocks are created by bare tasks listed in a play without # an explicit block statement. If we have two implicit blocks in a row, # squash them down to a single block to save processing time later. if b._implicit and len(block_list) > 0 and block_list[-1]._implicit: for t in b.block: - t._block = block_list[-1] + if isinstance(t._parent, (TaskInclude, IncludeRole)): + t._parent._parent = block_list[-1] + else: + t._parent = block_list[-1] block_list[-1].block.extend(b.block) else: block_list.append(b) @@ -214,11 +219,13 @@ task_list.append(t) continue + ti_copy = t.copy(exclude_parent=True) + ti_copy._parent = block included_blocks = load_list_of_blocks( data, play=play, parent_block=None, - task_include=t.copy(), + task_include=ti_copy, role=role, use_handlers=use_handlers, loader=loader, @@ -228,12 +235,12 @@ # pop tags out of the include args, if they were specified there, and assign # them to the include. If the include already had tags specified, we raise an # error so that users know not to specify them both ways - tags = t.vars.pop('tags', []) + tags = ti_copy.vars.pop('tags', []) if isinstance(tags, string_types): tags = tags.split(',') if len(tags) > 0: - if len(t.tags) > 0: + if len(ti_copy.tags) > 0: raise AnsibleParserError( "Include tasks should not specify tags in more than one way (both via args and directly on the task). " \ "Mixing styles in which tags are specified is prohibited for whole import hierarchy, not only for single import statement", @@ -242,7 +249,7 @@ ) display.deprecated("You should not specify tags in the include parameters. All tags should be specified using the task-level option") else: - tags = t.tags[:] + tags = ti_copy.tags[:] # now we extend the tags on each of the included blocks for b in included_blocks: diff -Nru ansible-2.2.0.0/lib/ansible/playbook/__init__.py ansible-2.2.1.0/lib/ansible/playbook/__init__.py --- ansible-2.2.0.0/lib/ansible/playbook/__init__.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/playbook/__init__.py 2017-01-16 17:03:54.000000000 +0000 @@ -21,11 +21,12 @@ import os +from ansible import constants as C from ansible.errors import AnsibleParserError +from ansible.module_utils._text import to_text from ansible.playbook.play import Play from ansible.playbook.playbook_include import PlaybookInclude from ansible.plugins import get_all_plugin_loaders -from ansible import constants as C try: from __main__ import display @@ -43,7 +44,7 @@ # Entries in the datastructure of a playbook may # be either a play or an include statement self._entries = [] - self._basedir = os.getcwd() + self._basedir = to_text(os.getcwd(), errors='surrogate_or_strict') self._loader = loader self._file_name = None diff -Nru ansible-2.2.0.0/lib/ansible/playbook/playbook_include.py ansible-2.2.1.0/lib/ansible/playbook/playbook_include.py --- ansible-2.2.0.0/lib/ansible/playbook/playbook_include.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/playbook/playbook_include.py 2017-01-16 17:03:54.000000000 +0000 @@ -61,15 +61,6 @@ templar = Templar(loader=loader, variables=all_vars) - try: - forward_conditional = False - if not new_obj.evaluate_conditional(templar=templar, all_vars=all_vars): - return None - except AnsibleError: - # conditional evaluation raised an error, so we set a flag to indicate - # we need to forward the conditionals on to the included play(s) - forward_conditional = True - # then we use the object to load a Playbook pb = Playbook(loader=loader) @@ -95,9 +86,9 @@ # Check to see if we need to forward the conditionals on to the included # plays. If so, we can take a shortcut here and simply prepend them to # those attached to each block (if any) - if forward_conditional: - for task_block in entry.pre_tasks + entry.roles + entry.tasks + entry.post_tasks: - task_block.when = self.when[:] + task_block.when + if new_obj.when: + for task_block in (entry.pre_tasks + entry.roles + entry.tasks + entry.post_tasks): + task_block._attributes['when'] = new_obj.when[:] + task_block.when[:] return pb diff -Nru ansible-2.2.0.0/lib/ansible/playbook/play.py ansible-2.2.1.0/lib/ansible/playbook/play.py --- ansible-2.2.0.0/lib/ansible/playbook/play.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/playbook/play.py 2017-01-16 17:03:54.000000000 +0000 @@ -96,6 +96,7 @@ super(Play, self).__init__() self._included_path = None + self._removed_hosts = [] self.ROLE_CACHE = {} def __repr__(self): @@ -202,7 +203,7 @@ for prompt_data in new_ds: if 'name' not in prompt_data: display.deprecated("Using the 'short form' for vars_prompt has been deprecated") - for vname, prompt in prompt_data.iteritems(): + for vname, prompt in prompt_data.items(): vars_prompts.append(dict( name = vname, prompt = prompt, diff -Nru ansible-2.2.0.0/lib/ansible/playbook/role/definition.py ansible-2.2.1.0/lib/ansible/playbook/role/definition.py --- ansible-2.2.0.0/lib/ansible/playbook/role/definition.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/playbook/role/definition.py 2017-01-16 17:03:54.000000000 +0000 @@ -34,6 +34,12 @@ from ansible.template import Templar from ansible.utils.path import unfrackpath +try: + from __main__ import display +except ImportError: + from ansible.utils.display import Display + display = Display() + __all__ = ['RoleDefinition'] @@ -197,6 +203,11 @@ # or make this list more automatic in some way so we don't have to # remember to update it manually. if key not in base_attribute_names or key in ('connection', 'port', 'remote_user'): + if key in ('connection', 'port', 'remote_user'): + display.deprecated("Using '%s' as a role param has been deprecated. " % key + \ + "In the future, these values should be entered in the `vars:` " + \ + "section for roles, but for now we'll store it as both a param and an attribute.") + role_def[key] = value # this key does not match a field attribute, so it must be a role param role_params[key] = value else: diff -Nru ansible-2.2.0.0/lib/ansible/playbook/role/__init__.py ansible-2.2.1.0/lib/ansible/playbook/role/__init__.py --- ansible-2.2.0.0/lib/ansible/playbook/role/__init__.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/playbook/role/__init__.py 2017-01-16 17:03:54.000000000 +0000 @@ -19,10 +19,10 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from ansible.compat.six import iteritems - +import collections import os +from ansible.compat.six import iteritems, binary_type, text_type from ansible.errors import AnsibleError, AnsibleParserError from ansible.playbook.attribute import FieldAttribute from ansible.playbook.base import Base @@ -41,25 +41,54 @@ # the role due to the fact that it would require the use of self # in a static method. This is also used in the base class for # strategies (ansible/plugins/strategy/__init__.py) + def hash_params(params): - if not isinstance(params, dict): - if isinstance(params, list): - return frozenset(params) + """ + Construct a data structure of parameters that is hashable. + + This requires changing any mutable data structures into immutable ones. + We chose a frozenset because role parameters have to be unique. + + .. warning:: this does not handle unhashable scalars. Two things + mitigate that limitation: + + 1) There shouldn't be any unhashable scalars specified in the yaml + 2) Our only choice would be to return an error anyway. + """ + # Any container is unhashable if it contains unhashable items (for + # instance, tuple() is a Hashable subclass but if it contains a dict, it + # cannot be hashed) + if isinstance(params, collections.Container) and not isinstance(params, (text_type, binary_type)): + if isinstance(params, collections.Mapping): + try: + # Optimistically hope the contents are all hashable + new_params = frozenset(params.items()) + except TypeError: + new_params = set() + for k, v in params.items(): + # Hash each entry individually + new_params.update((k, hash_params(v))) + new_params = frozenset(new_params) + + elif isinstance(params, (collections.Set, collections.Sequence)): + try: + # Optimistically hope the contents are all hashable + new_params = frozenset(params) + except TypeError: + new_params = set() + for v in params: + # Hash each entry individually + new_params.update(hash_params(v)) + new_params = frozenset(new_params) else: - return params - else: - s = set() - for k,v in iteritems(params): - if isinstance(v, dict): - s.update((k, hash_params(v))) - elif isinstance(v, list): - things = [] - for item in v: - things.append(hash_params(item)) - s.update((k, tuple(things))) - else: - s.update((k, v)) - return frozenset(s) + # This is just a guess. + new_params = frozenset(params) + return new_params + + # Note: We do not handle unhashable scalars but our only choice would be + # to raise an error there anyway. + return frozenset((params,)) + class Role(Base, Become, Conditional, Taggable): diff -Nru ansible-2.2.0.0/lib/ansible/playbook/role_include.py ansible-2.2.1.0/lib/ansible/playbook/role_include.py --- ansible-2.2.0.0/lib/ansible/playbook/role_include.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/playbook/role_include.py 2017-01-16 17:03:54.000000000 +0000 @@ -48,8 +48,8 @@ # private as this is a 'module options' vs a task property _allow_duplicates = FieldAttribute(isa='bool', default=True, private=True) - _static = FieldAttribute(isa='bool', default=None, private=True) _private = FieldAttribute(isa='bool', default=None, private=True) + _static = FieldAttribute(isa='bool', default=None) def __init__(self, block=None, role=None, task_include=None): @@ -76,10 +76,16 @@ actual_role = Role.load(ri, myplay, parent_role=self._parent_role, from_files=self._from_files) actual_role._metadata.allow_duplicates = self.allow_duplicates - # compile role - blocks = actual_role.compile(play=myplay) + # compile role with parent roles as dependencies to ensure they inherit + # variables + if not self._parent_role: + dep_chain = [] + else: + dep_chain = list(self._parent_role._parents) + dep_chain.extend(self._parent_role.get_all_dependencies()) + dep_chain.append(self._parent_role) - # set parent to ensure proper inheritance + blocks = actual_role.compile(play=myplay, dep_chain=dep_chain) for b in blocks: b._parent = self @@ -107,7 +113,7 @@ #FIXME: find a way to make this list come from object ( attributes does not work as per below) # manual list as otherwise the options would set other task parameters we don't want. - for option in ['static', 'private', 'allow_duplicates']: + for option in ['private', 'allow_duplicates']: if option in ir.args: setattr(ir, option, ir.args.get(option)) diff -Nru ansible-2.2.0.0/lib/ansible/playbook/task.py ansible-2.2.1.0/lib/ansible/playbook/task.py --- ansible-2.2.0.0/lib/ansible/playbook/task.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/playbook/task.py 2017-01-16 17:03:54.000000000 +0000 @@ -376,12 +376,6 @@ super(Task, self).deserialize(data) - def evaluate_conditional(self, templar, all_vars): - if self._parent is not None: - if not self._parent.evaluate_conditional(templar, all_vars): - return False - return super(Task, self).evaluate_conditional(templar, all_vars) - def set_loader(self, loader): ''' Sets the loader on this object and recursively on parent, child objects. @@ -394,7 +388,7 @@ if self._parent: self._parent.set_loader(loader) - def _get_parent_attribute(self, attr, extend=False): + def _get_parent_attribute(self, attr, extend=False, prepend=False): ''' Generic logic to get the attribute or parent attribute for a task value. ''' @@ -405,7 +399,7 @@ if self._parent and (value is None or extend): parent_value = getattr(self._parent, attr, None) if extend: - value = self._extend_value(value, parent_value) + value = self._extend_value(value, parent_value, prepend) else: value = parent_value except KeyError: diff -Nru ansible-2.2.0.0/lib/ansible/plugins/action/assemble.py ansible-2.2.1.0/lib/ansible/plugins/action/assemble.py --- ansible-2.2.0.0/lib/ansible/plugins/action/assemble.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/action/assemble.py 2017-01-16 17:03:54.000000000 +0000 @@ -103,13 +103,8 @@ return result remote_user = task_vars.get('ansible_ssh_user') or self._play_context.remote_user - if not tmp: - tmp = self._make_tmp_path(remote_user) - self._cleanup_remote_tmp = True - if boolean(remote_src): - result.update(self._execute_module(tmp=tmp, task_vars=task_vars, delete_remote_tmp=False)) - self._remove_tmp_path(tmp) + result.update(self._execute_module(tmp=tmp, task_vars=task_vars)) return result else: try: @@ -119,6 +114,10 @@ result['msg'] = to_native(e) return result + if not tmp: + tmp = self._make_tmp_path(remote_user) + self._cleanup_remote_tmp = True + if not os.path.isdir(src): result['failed'] = True result['msg'] = u"Source (%s) is not a directory" % src diff -Nru ansible-2.2.0.0/lib/ansible/plugins/action/async.py ansible-2.2.1.0/lib/ansible/plugins/action/async.py --- ansible-2.2.0.0/lib/ansible/plugins/action/async.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/action/async.py 2017-01-16 17:03:54.000000000 +0000 @@ -91,7 +91,15 @@ async_limit = self._task.async async_jid = str(random.randint(0, 999999999999)) - async_cmd = [env_string, remote_async_module_path, async_jid, async_limit, remote_module_path] + # call the interpreter for async_wrapper directly + # this permits use of a script for an interpreter on non-Linux platforms + # TODO: re-implement async_wrapper as a regular module to avoid this special case + interpreter = shebang.replace('#!', '').strip() + async_cmd = [interpreter, remote_async_module_path, async_jid, async_limit, remote_module_path] + + if env_string: + async_cmd.insert(0, env_string) + if argsfile: async_cmd.append(argsfile) else: diff -Nru ansible-2.2.0.0/lib/ansible/plugins/action/copy.py ansible-2.2.1.0/lib/ansible/plugins/action/copy.py --- ansible-2.2.0.0/lib/ansible/plugins/action/copy.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/action/copy.py 2017-01-16 17:03:54.000000000 +0000 @@ -86,7 +86,7 @@ # if we have first_available_file in our vars # look up the files and use the first one we find as src elif remote_src: - result.update(self._execute_module(module_name='copy', module_args=self._task.args, task_vars=task_vars, delete_remote_tmp=False)) + result.update(self._execute_module(module_name='copy', module_args=self._task.args, task_vars=task_vars)) return result else: # find in expected paths try: diff -Nru ansible-2.2.0.0/lib/ansible/plugins/action/include_vars.py ansible-2.2.1.0/lib/ansible/plugins/action/include_vars.py --- ansible-2.2.0.0/lib/ansible/plugins/action/include_vars.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/action/include_vars.py 2017-01-16 17:03:54.000000000 +0000 @@ -22,7 +22,7 @@ import re from ansible.errors import AnsibleError -from ansible.module_utils._text import to_native +from ansible.module_utils._text import to_native, to_text from ansible.plugins.action import ActionBase @@ -137,8 +137,8 @@ results.update(updated_results) except AnsibleError as e: + failed = True err_msg = to_native(e) - raise AnsibleError(err_msg) if self.return_results_as_name: scope = dict() @@ -226,7 +226,7 @@ return success return success - def _load_files(self, filename): + def _load_files(self, filename, validate_extensions=False): """ Loads a file and converts the output into a valid Python dict. Args: filename (str): The source file. @@ -237,7 +237,7 @@ results = dict() failed = False err_msg = '' - if not self._is_valid_file_ext(filename): + if validate_extensions and not self._is_valid_file_ext(filename): failed = True err_msg = ( '{0} does not have a valid extension: {1}' @@ -245,7 +245,9 @@ ) return failed, err_msg, results - data, show_content = self._loader._get_file_contents(filename) + b_data, show_content = self._loader._get_file_contents(filename) + data = to_text(b_data, errors='surrogate_or_strict') + self.show_content = show_content data = self._loader.load(data, show_content) if not data: @@ -287,7 +289,7 @@ if not stop_iter and not failed: if path.exists(filepath) and not self._ignore_file(filename): - failed, err_msg, loaded_data = self._load_files(filepath) + failed, err_msg, loaded_data = self._load_files(filepath, validate_extensions=True) if not failed: results.update(loaded_data) diff -Nru ansible-2.2.0.0/lib/ansible/plugins/action/__init__.py ansible-2.2.1.0/lib/ansible/plugins/action/__init__.py --- ansible-2.2.0.0/lib/ansible/plugins/action/__init__.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/action/__init__.py 2017-01-16 17:03:54.000000000 +0000 @@ -30,9 +30,8 @@ import time from abc import ABCMeta, abstractmethod -from ansible.compat.six import binary_type, text_type, iteritems, with_metaclass - from ansible import constants as C +from ansible.compat.six import binary_type, string_types, text_type, iteritems, with_metaclass from ansible.errors import AnsibleError, AnsibleConnectionFailure from ansible.executor.module_common import modify_module from ansible.module_utils._text import to_bytes, to_native, to_text @@ -40,6 +39,7 @@ from ansible.parsing.utils.jsonify import jsonify from ansible.playbook.play_context import MAGIC_VARIABLE_MAPPING from ansible.release import __version__ +from ansible.vars.unsafe_proxy import wrap_var try: @@ -359,11 +359,16 @@ # Try to use file system acls to make the files readable for sudo'd # user if execute: - mode = 'rx' + chmod_mode = 'rx' + setfacl_mode = 'r-x' else: - mode = 'rX' + chmod_mode = 'rX' + ### Note: this form fails silently on freebsd. We currently + # never call _fixup_perms2() with execute=False but if we + # start to we'll have to fix this. + setfacl_mode = 'r-X' - res = self._remote_set_user_facl(remote_paths, self._play_context.become_user, mode) + res = self._remote_set_user_facl(remote_paths, self._play_context.become_user, setfacl_mode) if res['rc'] != 0: # File system acls failed; let's try to use chown next # Set executable bit first as on some systems an @@ -371,7 +376,7 @@ if execute: res = self._remote_chmod(remote_paths, 'u+x') if res['rc'] != 0: - raise AnsibleError('Failed to set file mode on remote temporary files (rc: {0}, err: {1})'.format(res['rc'], res['stderr'])) + raise AnsibleError('Failed to set file mode on remote temporary files (rc: {0}, err: {1})'.format(res['rc'], to_native(res['stderr']))) res = self._remote_chown(remote_paths, self._play_context.become_user) if res['rc'] != 0 and remote_user == 'root': @@ -385,20 +390,20 @@ display.warning('Using world-readable permissions for temporary files Ansible needs to create when becoming an unprivileged user.' ' This may be insecure. For information on securing this, see' ' https://docs.ansible.com/ansible/become.html#becoming-an-unprivileged-user') - res = self._remote_chmod(remote_paths, 'a+%s' % mode) + res = self._remote_chmod(remote_paths, 'a+%s' % chmod_mode) if res['rc'] != 0: - raise AnsibleError('Failed to set file mode on remote files (rc: {0}, err: {1})'.format(res['rc'], res['stderr'])) + raise AnsibleError('Failed to set file mode on remote files (rc: {0}, err: {1})'.format(res['rc'], to_native(res['stderr']))) else: raise AnsibleError('Failed to set permissions on the temporary files Ansible needs to create when becoming an unprivileged user' ' (rc: {0}, err: {1}). For information on working around this,' - ' see https://docs.ansible.com/ansible/become.html#becoming-an-unprivileged-user'.format(res['rc'], res['stderr'])) + ' see https://docs.ansible.com/ansible/become.html#becoming-an-unprivileged-user'.format(res['rc'], to_native(res['stderr']))) elif execute: # Can't depend on the file being transferred with execute # permissions. Only need user perms because no become was # used here res = self._remote_chmod(remote_paths, 'u+x') if res['rc'] != 0: - raise AnsibleError('Failed to set file mode on remote files (rc: {0}, err: {1})'.format(res['rc'], res['stderr'])) + raise AnsibleError('Failed to set file mode on remote files (rc: {0}, err: {1})'.format(res['rc'], to_native(res['stderr']))) return remote_paths @@ -449,6 +454,8 @@ # happens sometimes when it is a dir and not on bsd if 'checksum' not in mystat['stat']: mystat['stat']['checksum'] = '' + elif not isinstance(mystat['stat']['checksum'], string_types): + raise AnsibleError("Invalid checksum returned by stat: expected a string type but got %s" % type(mystat['stat']['checksum'])) return mystat['stat'] @@ -664,6 +671,39 @@ display.debug("done with _execute_module (%s, %s)" % (module_name, module_args)) return data + def _clean_returned_data(self, data): + remove_keys = set() + fact_keys = set(data.keys()) + # first we add all of our magic variable names to the set of + # keys we want to remove from facts + for magic_var in MAGIC_VARIABLE_MAPPING: + remove_keys.update(fact_keys.intersection(MAGIC_VARIABLE_MAPPING[magic_var])) + # next we remove any connection plugin specific vars + for conn_path in self._shared_loader_obj.connection_loader.all(path_only=True): + try: + conn_name = os.path.splitext(os.path.basename(conn_path))[0] + re_key = re.compile('^ansible_%s_' % conn_name) + for fact_key in fact_keys: + if re_key.match(fact_key): + remove_keys.add(fact_key) + except AttributeError: + pass + + # remove some KNOWN keys + for hard in ['ansible_rsync_path', 'ansible_playbook_python']: + if hard in fact_keys: + remove_keys.add(hard) + + # finally, we search for interpreter keys to remove + re_interp = re.compile('^ansible_.*_interpreter$') + for fact_key in fact_keys: + if re_interp.match(fact_key): + remove_keys.add(fact_key) + # then we remove them (except for ssh host keys) + for r_key in remove_keys: + if not r_key.startswith('ansible_ssh_host_key_'): + del data[r_key] + def _parse_returned_data(self, res): try: filtered_output, warnings = _filter_non_json_lines(res.get('stdout', u'')) @@ -672,31 +712,11 @@ data = json.loads(filtered_output) data['_ansible_parsed'] = True if 'ansible_facts' in data and isinstance(data['ansible_facts'], dict): - remove_keys = set() - fact_keys = set(data['ansible_facts'].keys()) - # first we add all of our magic variable names to the set of - # keys we want to remove from facts - for magic_var in MAGIC_VARIABLE_MAPPING: - remove_keys.update(fact_keys.intersection(MAGIC_VARIABLE_MAPPING[magic_var])) - # next we remove any connection plugin specific vars - for conn_path in self._shared_loader_obj.connection_loader.all(path_only=True): - try: - conn_name = os.path.splitext(os.path.basename(conn_path))[0] - re_key = re.compile('^ansible_%s_' % conn_name) - for fact_key in fact_keys: - if re_key.match(fact_key): - remove_keys.add(fact_key) - except AttributeError: - pass - # finally, we search for interpreter keys to remove - re_interp = re.compile('^ansible_.*_interpreter$') - for fact_key in fact_keys: - if re_interp.match(fact_key): - remove_keys.add(fact_key) - # then we remove them (except for ssh host keys) - for r_key in remove_keys: - if not r_key.startswith('ansible_ssh_host_key_'): - del data['ansible_facts'][r_key] + self._clean_returned_data(data['ansible_facts']) + data['ansible_facts'] = wrap_var(data['ansible_facts']) + if 'add_host' in data and isinstance(data['add_host'].get('host_vars', None), dict): + self._clean_returned_data(data['add_host']['host_vars']) + data['add_host'] = wrap_var(data['add_host']) except ValueError: # not valid json, lets try to capture error data = dict(failed=True, _ansible_parsed=False) diff -Nru ansible-2.2.0.0/lib/ansible/plugins/action/service.py ansible-2.2.1.0/lib/ansible/plugins/action/service.py --- ansible-2.2.0.0/lib/ansible/plugins/action/service.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/action/service.py 2017-01-16 17:03:54.000000000 +0000 @@ -26,7 +26,7 @@ TRANSFERS_FILES = False UNUSED_PARAMS = { - 'systemd': ['pattern', 'runlevels', 'sleep', 'arguments'], + 'systemd': ['pattern', 'runlevel', 'sleep', 'arguments', 'args'], } def run(self, tmp=None, task_vars=None): diff -Nru ansible-2.2.0.0/lib/ansible/plugins/action/set_stats.py ansible-2.2.1.0/lib/ansible/plugins/action/set_stats.py --- ansible-2.2.0.0/lib/ansible/plugins/action/set_stats.py 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/action/set_stats.py 2017-01-16 17:03:54.000000000 +0000 @@ -0,0 +1,73 @@ +# Copyright 2016 Ansible (RedHat, Inc) +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.compat.six import iteritems, string_types +from ansible.constants import mk_boolean as boolean +from ansible.plugins.action import ActionBase +from ansible.utils.vars import isidentifier + +class ActionModule(ActionBase): + + TRANSFERS_FILES = False + + #TODO: document this in non-empty set_stats.py module + def run(self, tmp=None, task_vars=None): + if task_vars is None: + task_vars = dict() + + result = super(ActionModule, self).run(tmp, task_vars) + + stats = {'data': {}, 'per_host': False, 'aggregate': True} + + if self._task.args: + data = self._task.args.get('data', {}) + + if not isinstance(data, dict): + data = self._templar.template(data, convert_bare=False, fail_on_undefined=True) + + if not isinstance(data, dict): + result['failed'] = True + result['msg'] = "The 'data' option needs to be a dictionary/hash" + return result + + # set boolean options, defaults are set above in stats init + for opt in ['per_host', 'aggregate']: + val = self._task.args.get(opt, None) + if val is not None: + if not isinstance(val, bool): + stats[opt] = boolean(self._templar.template(val)) + else: + stats[opt] = val + + for (k, v) in iteritems(data): + + k = self._templar.template(k) + + if not isidentifier(k): + result['failed'] = True + result['msg'] = "The variable name '%s' is not valid. Variables must start with a letter or underscore character, and contain only letters, numbers and underscores." % k + return result + + stats['data'][k] = self._templar.template(v) + + result['changed'] = False + result['ansible_stats'] = stats + + return result diff -Nru ansible-2.2.0.0/lib/ansible/plugins/action/template.py ansible-2.2.1.0/lib/ansible/plugins/action/template.py --- ansible-2.2.0.0/lib/ansible/plugins/action/template.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/action/template.py 2017-01-16 17:03:54.000000000 +0000 @@ -23,6 +23,7 @@ import time from ansible import constants as C +from ansible.compat.six import string_types from ansible.errors import AnsibleError from ansible.module_utils._text import to_bytes, to_native, to_text from ansible.plugins.action import ActionBase @@ -115,19 +116,28 @@ time.localtime(os.path.getmtime(b_source)) ) - # Create a new searchpath list to assign to the templar environment's file - # loader, so that it knows about the other paths to find template files - searchpath = [self._loader._basedir, os.path.dirname(source)] - if self._task._role is not None: - if C.DEFAULT_ROLES_PATH: - searchpath[:0] = C.DEFAULT_ROLES_PATH - searchpath.insert(1, self._task._role._role_path) + + searchpath = [] + # set jinja2 internal search path for includes + if 'ansible_search_path' in task_vars: + searchpath = task_vars['ansible_search_path'] + # our search paths aren't actually the proper ones for jinja includes. + + searchpath.extend([self._loader._basedir, os.path.dirname(source)]) + + # We want to search into the 'templates' subdir of each search path in + # addition to our original search paths. + newsearchpath = [] + for p in searchpath: + newsearchpath.append(os.path.join(p, 'templates')) + newsearchpath.append(p) + searchpath = newsearchpath self._templar.environment.loader.searchpath = searchpath old_vars = self._templar._available_variables self._templar.set_available_variables(temp_vars) - resultant = self._templar.template(template_data, preserve_trailing_newlines=True, escape_backslashes=False, convert_data=False) + resultant = self._templar.do_template(template_data, preserve_trailing_newlines=True, escape_backslashes=False) self._templar.set_available_variables(old_vars) except Exception as e: result['failed'] = True diff -Nru ansible-2.2.0.0/lib/ansible/plugins/callback/default.py ansible-2.2.1.0/lib/ansible/plugins/callback/default.py --- ansible-2.2.0.0/lib/ansible/plugins/callback/default.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/callback/default.py 2017-01-16 17:03:54.000000000 +0000 @@ -65,7 +65,7 @@ else: self._display.display("fatal: [%s]: FAILED! => %s" % (result._host.get_name(), self._dump_results(result._result)), color=C.COLOR_ERROR) - if result._task.ignore_errors: + if ignore_errors: self._display.display("...ignoring", color=C.COLOR_SKIP) def v2_runner_on_ok(self, result): diff -Nru ansible-2.2.0.0/lib/ansible/plugins/callback/profile_tasks.py ansible-2.2.1.0/lib/ansible/plugins/callback/profile_tasks.py --- ansible-2.2.0.0/lib/ansible/plugins/callback/profile_tasks.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/callback/profile_tasks.py 2017-01-16 17:03:54.000000000 +0000 @@ -28,6 +28,7 @@ import time from ansible.plugins.callback import CallbackBase +from ansible.compat.six.moves import reduce # define start time t0 = tn = time.time() @@ -123,7 +124,7 @@ # Sort the tasks by the specified sort if self.sort_order != 'none': results = sorted( - self.stats.iteritems(), + self.stats.items(), key=lambda x:x[1]['time'], reverse=self.sort_order, ) diff -Nru ansible-2.2.0.0/lib/ansible/plugins/connection/ssh.py ansible-2.2.1.0/lib/ansible/plugins/connection/ssh.py --- ansible-2.2.0.0/lib/ansible/plugins/connection/ssh.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/connection/ssh.py 2017-01-16 17:03:54.000000000 +0000 @@ -451,6 +451,14 @@ b_chunk = p.stdout.read() if b_chunk == b'': rpipes.remove(p.stdout) + # When ssh has ControlMaster (+ControlPath/Persist) enabled, the + # first connection goes into the background and we never see EOF + # on stderr. If we see EOF on stdout, lower the select timeout + # to reduce the time wasted selecting on stderr if we observe + # that the process has not yet existed after this EOF. Otherwise + # we may spend a long timeout period waiting for an EOF that is + # not going to arrive until the persisted connection closes. + timeout = 1 b_tmp_stdout += b_chunk display.debug("stdout chunk (state=%s):\n>>>%s<<<\n" % (state, to_text(b_chunk))) @@ -534,18 +542,11 @@ if p.poll() is not None: if not rpipes or not rfd: break - - # When ssh has ControlMaster (+ControlPath/Persist) enabled, the - # first connection goes into the background and we never see EOF - # on stderr. If we see EOF on stdout and the process has exited, - # we're probably done. We call select again with a zero timeout, - # just to make certain we don't miss anything that may have been - # written to stderr between the time we called select() and when - # we learned that the process had finished. - - if p.stdout not in rpipes: - timeout = 0 - continue + # We should not see further writes to the stdout/stderr file + # descriptors after the process has closed, set the select + # timeout to gather any last writes we may have missed. + timeout = 0 + continue # If the process has not yet exited, but we've already read EOF from # its stdout and stderr (and thus removed both from rpipes), we can @@ -627,7 +628,10 @@ cmd = self._build_command('sftp', to_bytes(host)) in_data = u"{0} {1} {2}\n".format(sftp_action, pipes.quote(in_path), pipes.quote(out_path)) elif method == 'scp': - cmd = self._build_command('scp', in_path, u'{0}:{1}'.format(host, pipes.quote(out_path))) + if sftp_action == 'get': + cmd = self._build_command('scp', u'{0}:{1}'.format(host, pipes.quote(in_path)), out_path) + else: + cmd = self._build_command('scp', in_path, u'{0}:{1}'.format(host, pipes.quote(out_path))) in_data = None in_data = to_bytes(in_data, nonstring='passthru') diff -Nru ansible-2.2.0.0/lib/ansible/plugins/__init__.py ansible-2.2.1.0/lib/ansible/plugins/__init__.py --- ansible-2.2.0.0/lib/ansible/plugins/__init__.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/__init__.py 2017-01-16 17:03:54.000000000 +0000 @@ -221,7 +221,7 @@ self._extra_dirs.append(directory) self._paths = None - def find_plugin(self, name, mod_type=''): + def find_plugin(self, name, mod_type='', ignore_deprecated=False): ''' Find a plugin named name ''' if mod_type: @@ -297,7 +297,7 @@ alias_name = '_' + name # We've already cached all the paths at this point if alias_name in pull_cache: - if not os.path.islink(pull_cache[alias_name]): + if not ignore_deprecated and not os.path.islink(pull_cache[alias_name]): display.deprecated('%s is kept for backwards compatibility ' 'but usage is discouraged. The module ' 'documentation details page may explain ' diff -Nru ansible-2.2.0.0/lib/ansible/plugins/lookup/file.py ansible-2.2.1.0/lib/ansible/plugins/lookup/file.py --- ansible-2.2.0.0/lib/ansible/plugins/lookup/file.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/lookup/file.py 2017-01-16 17:03:54.000000000 +0000 @@ -19,6 +19,7 @@ from ansible.errors import AnsibleError, AnsibleParserError from ansible.plugins.lookup import LookupBase +from ansible.module_utils._text import to_text try: from __main__ import display @@ -41,7 +42,8 @@ display.vvvv(u"File lookup using %s as file" % lookupfile) try: if lookupfile: - contents, show_data = self._loader._get_file_contents(lookupfile) + b_contents, show_data = self._loader._get_file_contents(lookupfile) + contents = to_text(b_contents, errors='surrogate_or_strict') ret.append(contents.rstrip()) else: raise AnsibleParserError() diff -Nru ansible-2.2.0.0/lib/ansible/plugins/lookup/ini.py ansible-2.2.1.0/lib/ansible/plugins/lookup/ini.py --- ansible-2.2.0.0/lib/ansible/plugins/lookup/ini.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/lookup/ini.py 2017-01-16 17:03:54.000000000 +0000 @@ -36,7 +36,7 @@ def _parse_params(term): '''Safely split parameter term to preserve spaces''' - keys = ['key', 'type', 'section', 'file', 're'] + keys = ['key', 'type', 'section', 'file', 're', 'default'] params = {} for k in keys: params[k] = '' diff -Nru ansible-2.2.0.0/lib/ansible/plugins/lookup/sequence.py ansible-2.2.1.0/lib/ansible/plugins/lookup/sequence.py --- ansible-2.2.0.0/lib/ansible/plugins/lookup/sequence.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/lookup/sequence.py 2017-01-16 17:03:54.000000000 +0000 @@ -100,8 +100,8 @@ "can't parse arg %s=%r as integer" % (arg, arg_raw) ) - if 'format' in args: - self.format = args.pop("format") + if 'format' in args: + self.format = args.pop("format") if args: raise AnsibleError( "unrecognized arguments to with_sequence: %r" diff -Nru ansible-2.2.0.0/lib/ansible/plugins/lookup/subelements.py ansible-2.2.1.0/lib/ansible/plugins/lookup/subelements.py --- ansible-2.2.0.0/lib/ansible/plugins/lookup/subelements.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/lookup/subelements.py 2017-01-16 17:03:54.000000000 +0000 @@ -51,7 +51,7 @@ # the registered result was completely skipped return [] elementlist = [] - for key in terms[0].iterkeys(): + for key in terms[0]: elementlist.append(terms[0][key]) else: elementlist = terms[0] diff -Nru ansible-2.2.0.0/lib/ansible/plugins/lookup/template.py ansible-2.2.1.0/lib/ansible/plugins/lookup/template.py --- ansible-2.2.0.0/lib/ansible/plugins/lookup/template.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/lookup/template.py 2017-01-16 17:03:54.000000000 +0000 @@ -49,6 +49,14 @@ # set jinja2 internal search path for includes if 'ansible_search_path' in variables: searchpath = variables['ansible_search_path'] + # our search paths aren't actually the proper ones for jinja includes. + # We want to search into the 'templates' subdir of each search path in + # addition to our original search paths. + newsearchpath = [] + for p in searchpath: + newsearchpath.append(os.path.join(p, 'templates')) + newsearchpath.append(p) + searchpath = newsearchpath else: searchpath = [self._loader._basedir, os.path.dirname(lookupfile)] self._templar.environment.loader.searchpath = searchpath diff -Nru ansible-2.2.0.0/lib/ansible/plugins/strategy/debug.py ansible-2.2.1.0/lib/ansible/plugins/strategy/debug.py --- ansible-2.2.0.0/lib/ansible/plugins/strategy/debug.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/strategy/debug.py 2017-01-16 17:03:54.000000000 +0000 @@ -7,6 +7,7 @@ import sys from ansible.plugins.strategy.linear import StrategyModule as LinearStrategyModule +from ansible.compat.six.moves import reduce try: from __main__ import display diff -Nru ansible-2.2.0.0/lib/ansible/plugins/strategy/__init__.py ansible-2.2.1.0/lib/ansible/plugins/strategy/__init__.py --- ansible-2.2.0.0/lib/ansible/plugins/strategy/__init__.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/strategy/__init__.py 2017-01-16 17:03:54.000000000 +0000 @@ -129,6 +129,12 @@ self._results_thread.join() def run(self, iterator, play_context, result=0): + # execute one more pass through the iterator without peeking, to + # make sure that all of the hosts are advanced to their final task. + # This should be safe, as everything should be ITERATING_COMPLETE by + # this point, though the strategy may not advance the hosts itself. + [iterator.get_next_task_for_host(host) for host in self._inventory.get_hosts(iterator._play.hosts) if host.name not in self._tqm._unreachable_hosts] + # save the failed/unreachable hosts, as the run_handlers() # method will clear that information during its execution failed_hosts = iterator.get_failed_hosts() @@ -223,6 +229,13 @@ return display.debug("exiting _queue_task() for %s/%s" % (host.name, task.action)) + def get_task_hosts(self, iterator, task_host, task): + if task.run_once: + host_list = [host for host in self._inventory.get_hosts(iterator._play.hosts) if host.name not in self._tqm._unreachable_hosts] + else: + host_list = [task_host] + return host_list + def _process_pending_results(self, iterator, one_pass=False, max_passes=None): ''' Reads results off the final queue and takes appropriate action @@ -238,29 +251,38 @@ else: return self._inventory.get_host(host_name) - def search_handler_blocks(handler_name, handler_blocks): + def search_handler_blocks_by_name(handler_name, handler_blocks): for handler_block in handler_blocks: for handler_task in handler_block.block: - handler_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, task=handler_task) - templar = Templar(loader=self._loader, variables=handler_vars) - try: - # first we check with the full result of get_name(), which may - # include the role name (if the handler is from a role). If that - # is not found, we resort to the simple name field, which doesn't - # have anything extra added to it. - target_handler_name = templar.template(handler_task.name) - if target_handler_name == handler_name: - return handler_task - else: - target_handler_name = templar.template(handler_task.get_name()) + if handler_task.name: + handler_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, task=handler_task) + templar = Templar(loader=self._loader, variables=handler_vars) + try: + # first we check with the full result of get_name(), which may + # include the role name (if the handler is from a role). If that + # is not found, we resort to the simple name field, which doesn't + # have anything extra added to it. + target_handler_name = templar.template(handler_task.name) if target_handler_name == handler_name: return handler_task - except (UndefinedError, AnsibleUndefinedVariable): - # We skip this handler due to the fact that it may be using - # a variable in the name that was conditionally included via - # set_fact or some other method, and we don't want to error - # out unnecessarily - continue + else: + target_handler_name = templar.template(handler_task.get_name()) + if target_handler_name == handler_name: + return handler_task + except (UndefinedError, AnsibleUndefinedVariable): + # We skip this handler due to the fact that it may be using + # a variable in the name that was conditionally included via + # set_fact or some other method, and we don't want to error + # out unnecessarily + continue + return None + + + def search_handler_blocks_by_uuid(handler_uuid, handler_blocks): + for handler_block in handler_blocks: + for handler_task in handler_block.block: + if handler_uuid == handler_task._uuid: + return handler_task return None def parent_handler_match(target_handler, handler_name): @@ -282,6 +304,11 @@ else: return False + # a Templar class to use for templating things later, as we're using + # original/non-validated objects here on the manager side. We set the + # variables in use later inside the loop below + templar = Templar(loader=self._loader) + cur_pass = 0 while True: try: @@ -292,11 +319,24 @@ finally: self._results_lock.release() + # get the original host and task. We then assign them to the TaskResult for use in callbacks/etc. original_host = get_original_host(task_result._host) original_task = iterator.get_original_task(original_host, task_result._task) + task_result._host = original_host task_result._task = original_task + # get the correct loop var for use later + if original_task.loop_control: + loop_var = original_task.loop_control.loop_var or 'item' + else: + loop_var = 'item' + + # get the vars for this task/host pair, make them the active set of vars for our templar above + task_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, host=original_host, task=original_task) + self.add_tqm_variables(task_vars, play=iterator._play) + templar.set_available_variables(task_vars) + # send callbacks for 'non final' results if '_ansible_retry' in task_result._result: self._tqm.send_callback('v2_runner_retry', task_result) @@ -313,11 +353,9 @@ self._tqm.send_callback('v2_runner_item_on_ok', task_result) continue + run_once = templar.template(original_task.run_once) if original_task.register: - if original_task.run_once: - host_list = [host for host in self._inventory.get_hosts(iterator._play.hosts) if host.name not in self._tqm._unreachable_hosts] - else: - host_list = [original_host] + host_list = self.get_task_hosts(iterator, original_host, original_task) clean_copy = strip_internal_keys(task_result._result) if 'invocation' in clean_copy: @@ -330,9 +368,10 @@ role_ran = False if task_result.is_failed(): role_ran = True - if not original_task.ignore_errors: + ignore_errors = templar.template(original_task.ignore_errors) + if not ignore_errors: display.debug("marking %s as failed" % original_host.name) - if original_task.run_once: + if run_once: # if we're using run_once, we have to fail every host here for h in self._inventory.get_hosts(iterator._play.hosts): if h.name not in self._tqm._unreachable_hosts: @@ -342,29 +381,33 @@ else: iterator.mark_host_failed(original_host) - # only add the host to the failed list officially if it has - # been failed by the iterator - if iterator.is_failed(original_host): + # increment the failed count for this host + self._tqm._stats.increment('failures', original_host.name) + + # grab the current state and if we're iterating on the rescue portion + # of a block then we save the failed task in a special var for use + # within the rescue/always + state, _ = iterator.get_next_task_for_host(original_host, peek=True) + + if iterator.is_failed(original_host) and state and state.run_state == iterator.ITERATING_COMPLETE: self._tqm._failed_hosts[original_host.name] = True - self._tqm._stats.increment('failures', original_host.name) - else: - # otherwise, we grab the current state and if we're iterating on - # the rescue portion of a block then we save the failed task in a - # special var for use within the rescue/always - state, _ = iterator.get_next_task_for_host(original_host, peek=True) - if state.run_state == iterator.ITERATING_RESCUE: - self._variable_manager.set_nonpersistent_facts( - original_host, - dict( - ansible_failed_task=original_task.serialize(), - ansible_failed_result=task_result._result, - ), - ) + + if state and state.run_state == iterator.ITERATING_RESCUE: + self._variable_manager.set_nonpersistent_facts( + original_host, + dict( + ansible_failed_task=original_task.serialize(), + ansible_failed_result=task_result._result, + ), + ) else: self._tqm._stats.increment('ok', original_host.name) - self._tqm.send_callback('v2_runner_on_failed', task_result, ignore_errors=original_task.ignore_errors) + if 'changed' in task_result._result and task_result._result['changed']: + self._tqm._stats.increment('changed', original_host.name) + self._tqm.send_callback('v2_runner_on_failed', task_result, ignore_errors=ignore_errors) elif task_result.is_unreachable(): self._tqm._unreachable_hosts[original_host.name] = True + iterator._play._removed_hosts.append(original_host.name) self._tqm._stats.increment('dark', original_host.name) self._tqm.send_callback('v2_runner_on_unreachable', task_result) elif task_result.is_skipped(): @@ -393,30 +436,33 @@ # dependency chain of the current task (if it's from a role), otherwise # we just look through the list of handlers in the current play/all # roles and use the first one that matches the notify name - target_handler = search_handler_blocks(handler_name, iterator._play.handlers) + target_handler = search_handler_blocks_by_name(handler_name, iterator._play.handlers) if target_handler is not None: found = True - if original_host not in self._notified_handlers[target_handler]: - self._notified_handlers[target_handler].append(original_host) + if original_host not in self._notified_handlers[target_handler._uuid]: + self._notified_handlers[target_handler._uuid].append(original_host) # FIXME: should this be a callback? display.vv("NOTIFIED HANDLER %s" % (handler_name,)) else: # As there may be more than one handler with the notified name as the # parent, so we just keep track of whether or not we found one at all - for target_handler in self._notified_handlers: - if parent_handler_match(target_handler, handler_name): - self._notified_handlers[target_handler].append(original_host) + for target_handler_uuid in self._notified_handlers: + target_handler = search_handler_blocks_by_uuid(target_handler_uuid, iterator._play.handlers) + if target_handler and parent_handler_match(target_handler, handler_name): + self._notified_handlers[target_handler._uuid].append(original_host) display.vv("NOTIFIED HANDLER %s" % (target_handler.get_name(),)) found = True if handler_name in self._listening_handlers: - for listening_handler_name in self._listening_handlers[handler_name]: - listening_handler = search_handler_blocks(listening_handler_name, iterator._play.handlers) + for listening_handler_uuid in self._listening_handlers[handler_name]: + listening_handler = search_handler_blocks_by_uuid(listening_handler_uuid, iterator._play.handlers) if listening_handler is not None: found = True - if original_host not in self._notified_handlers[listening_handler]: - self._notified_handlers[listening_handler].append(original_host) - display.vv("NOTIFIED HANDLER %s" % (listening_handler_name,)) + else: + continue + if original_host not in self._notified_handlers[listening_handler._uuid]: + self._notified_handlers[listening_handler._uuid].append(original_host) + display.vv("NOTIFIED HANDLER %s" % (listening_handler.get_name(),)) # and if none were found, then we raise an error if not found: @@ -435,23 +481,13 @@ # this task added a new group (group_by module) self._add_group(original_host, result_item) - elif 'ansible_facts' in result_item: - - # set correct loop var - if original_task.loop_control: - loop_var = original_task.loop_control.loop_var or 'item' - else: - loop_var = 'item' - - item = result_item.get(loop_var, None) + if 'ansible_facts' in result_item: # if delegated fact and we are delegating facts, we need to change target host for them if original_task.delegate_to is not None and original_task.delegate_facts: - task_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, host=original_host, task=original_task) - self.add_tqm_variables(task_vars, play=iterator._play) + item = result_item.get(loop_var, None) if item is not None: task_vars[loop_var] = item - templar = Templar(loader=self._loader, variables=task_vars) host_name = templar.template(original_task.delegate_to) actual_host = self._inventory.get_host(host_name) if actual_host is None: @@ -459,30 +495,37 @@ else: actual_host = original_host + host_list = self.get_task_hosts(iterator, actual_host, original_task) if original_task.action == 'include_vars': + for (var_name, var_value) in iteritems(result_item['ansible_facts']): # find the host we're actually refering too here, which may # be a host that is not really in inventory at all - - if original_task.run_once: - host_list = [host for host in self._inventory.get_hosts(iterator._play.hosts) if host.name not in self._tqm._unreachable_hosts] - else: - host_list = [actual_host] - for target_host in host_list: self._variable_manager.set_host_variable(target_host, var_name, var_value) else: - if original_task.run_once: - host_list = [host for host in self._inventory.get_hosts(iterator._play.hosts) if host.name not in self._tqm._unreachable_hosts] - else: - host_list = [actual_host] - for target_host in host_list: if original_task.action == 'set_fact': self._variable_manager.set_nonpersistent_facts(target_host, result_item['ansible_facts'].copy()) else: self._variable_manager.set_host_facts(target_host, result_item['ansible_facts'].copy()) + if 'ansible_stats' in result_item and 'data' in result_item['ansible_stats'] and result_item['ansible_stats']['data']: + + if 'per_host' not in result_item['ansible_stats'] or result_item['ansible_stats']['per_host']: + host_list = self.get_task_hosts(iterator, original_host, original_task) + else: + host_list = [None] + + data = result_item['ansible_stats']['data'] + aggregate = 'aggregate' in result_item['ansible_stats'] and result_item['ansible_stats']['aggregate'] + for myhost in host_list: + for k in data.keys(): + if aggregate: + self._tqm._stats.update_custom_stats(k, data[k], myhost) + else: + self._tqm._stats.set_custom_stats(k, data[k], myhost) + if 'diff' in task_result._result: if self._diff: self._tqm.send_callback('v2_on_file_diff', task_result) @@ -606,9 +649,6 @@ group_name = result_item.get('add_group') new_group = self._inventory.get_group(group_name) if not new_group: - # clear cache of group dict, which is used in magic host variables - self._inventory.clear_group_dict_cache() - # create the new group and add it to inventory new_group = Group(name=group_name) self._inventory.add_group(new_group) @@ -622,6 +662,10 @@ if group_name not in host.get_groups(): new_group.add_host(real_host) changed = True + + if changed: + # clear cache of group dict, which is used in magic host variables + self._inventory.clear_group_dict_cache() return changed @@ -653,6 +697,7 @@ obj=included_file._task._ds) display.deprecated("You should not specify tags in the include parameters. All tags should be specified using the task-level option") included_file._task.tags = tags + ti_copy.vars = temp_vars block_list = load_list_of_blocks( @@ -699,7 +744,7 @@ # but this may take some work in the iterator and gets tricky when # we consider the ability of meta tasks to flush handlers for handler in handler_block.block: - if handler in self._notified_handlers and len(self._notified_handlers[handler]): + if handler._uuid in self._notified_handlers and len(self._notified_handlers[handler._uuid]): result = self._do_handler_run(handler, handler.get_name(), iterator=iterator, play_context=play_context) if not result: break @@ -718,7 +763,7 @@ handler.name = saved_name if notified_hosts is None: - notified_hosts = self._notified_handlers[handler] + notified_hosts = self._notified_handlers[handler._uuid] run_once = False try: @@ -763,6 +808,7 @@ # of hosts which included the file to the notified_handlers dict for block in new_blocks: iterator._play.handlers.append(block) + iterator.cache_block_tasks(block) for task in block.block: result = self._do_handler_run( handler=task, @@ -781,7 +827,7 @@ continue # wipe the notification list - self._notified_handlers[handler] = [] + self._notified_handlers[handler._uuid] = [] display.debug("done running handlers, result is: %s" % result) return result diff -Nru ansible-2.2.0.0/lib/ansible/plugins/strategy/linear.py ansible-2.2.1.0/lib/ansible/plugins/strategy/linear.py --- ansible-2.2.0.0/lib/ansible/plugins/strategy/linear.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/plugins/strategy/linear.py 2017-01-16 17:03:54.000000000 +0000 @@ -287,7 +287,7 @@ loop_var = 'item' if hr._task.loop_control: loop_var = hr._task.loop_control.loop_var or 'item' - include_results = hr._result['results'] + include_results = hr._result.get('results', []) else: include_results = [ hr._result ] diff -Nru ansible-2.2.0.0/lib/ansible/release.py ansible-2.2.1.0/lib/ansible/release.py --- ansible-2.2.0.0/lib/ansible/release.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/release.py 2017-01-16 17:03:54.000000000 +0000 @@ -19,5 +19,5 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -__version__ = '2.2.0.0' +__version__ = '2.2.1.0' __author__ = 'Ansible, Inc.' diff -Nru ansible-2.2.0.0/lib/ansible/template/__init__.py ansible-2.2.1.0/lib/ansible/template/__init__.py --- ansible-2.2.0.0/lib/ansible/template/__init__.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/template/__init__.py 2017-01-16 17:03:54.000000000 +0000 @@ -31,8 +31,7 @@ from jinja2.loaders import FileSystemLoader from jinja2.exceptions import TemplateSyntaxError, UndefinedError from jinja2.utils import concat as j2_concat -from jinja2.runtime import StrictUndefined - +from jinja2.runtime import Context, StrictUndefined from ansible import constants as C from ansible.compat.six import string_types, text_type from ansible.errors import AnsibleError, AnsibleFilterError, AnsibleUndefinedVariable @@ -42,7 +41,6 @@ from ansible.template.vars import AnsibleJ2Vars from ansible.module_utils._text import to_native, to_text - try: from hashlib import sha1 except ImportError: @@ -126,6 +124,54 @@ # Uncommon cases: zero length string and string containing only newlines return i +class AnsibleContext(Context): + ''' + A custom context, which intercepts resolve() calls and sets a flag + internally if any variable lookup returns an AnsibleUnsafe value. This + flag is checked post-templating, and (when set) will result in the + final templated result being wrapped via UnsafeProxy. + ''' + def __init__(self, *args, **kwargs): + super(AnsibleContext, self).__init__(*args, **kwargs) + self.unsafe = False + + def _is_unsafe(self, val): + ''' + Our helper function, which will also recursively check dict and + list entries due to the fact that they may be repr'd and contain + a key or value which contains jinja2 syntax and would otherwise + lose the AnsibleUnsafe value. + ''' + if isinstance(val, dict): + for key in val.keys(): + if self._is_unsafe(key) or self._is_unsafe(val[key]): + return True + elif isinstance(val, list): + for item in val: + if self._is_unsafe(item): + return True + elif isinstance(val, string_types) and hasattr(val, '__UNSAFE__'): + return True + return False + + def resolve(self, key): + ''' + The intercepted resolve(), which uses the helper above to set the + internal flag whenever an unsafe variable value is returned. + ''' + val = super(AnsibleContext, self).resolve(key) + if val is not None and not self.unsafe: + if self._is_unsafe(val): + self.unsafe = True + return val + +class AnsibleEnvironment(Environment): + ''' + Our custom environment, which simply allows us to override the class-level + values for the Template and Context classes used by jinja2 internally. + ''' + context_class = AnsibleContext + template_class = AnsibleJ2Template class Templar: ''' @@ -159,14 +205,13 @@ self._fail_on_filter_errors = True self._fail_on_undefined_errors = C.DEFAULT_UNDEFINED_VAR_BEHAVIOR - self.environment = Environment( + self.environment = AnsibleEnvironment( trim_blocks=True, undefined=StrictUndefined, extensions=self._get_extensions(), finalize=self._finalize, loader=FileSystemLoader(self._basedir), ) - self.environment.template_class = AnsibleJ2Template self.SINGLE_VAR = re.compile(r"^%s\s*(\w*)\s*%s$" % (self.environment.variable_start_string, self.environment.variable_end_string)) @@ -229,7 +274,7 @@ def _clean_data(self, orig_data): ''' remove jinja2 template tags from a string ''' - if not isinstance(orig_data, string_types) or hasattr(orig_data, '__ENCRYPTED__'): + if not isinstance(orig_data, string_types) or hasattr(orig_data, '__ENCRYPTED__') or hasattr(orig_data, '__UNSAFE__'): return orig_data with contextlib.closing(StringIO(orig_data)) as data: @@ -279,7 +324,7 @@ self._available_variables = variables self._cached_result = {} - def template(self, variable, convert_bare=False, preserve_trailing_newlines=True, escape_backslashes=True, fail_on_undefined=None, overrides=None, convert_data=True, static_vars = [''], cache = True, bare_deprecated=True): + def template(self, variable, convert_bare=False, preserve_trailing_newlines=True, escape_backslashes=True, fail_on_undefined=None, overrides=None, convert_data=True, static_vars=[''], cache=True, bare_deprecated=True, disable_lookups=False): ''' Templates (possibly recursively) any given data as input. If convert_bare is set to True, the given data will be wrapped as a jinja2 variable ('{{foo}}') @@ -292,11 +337,12 @@ # Don't template unsafe variables, instead drop them back down to their constituent type. if hasattr(variable, '__UNSAFE__'): if isinstance(variable, text_type): - return self._clean_data(variable) + rval = self._clean_data(variable) else: # Do we need to convert these into text_type as well? # return self._clean_data(to_text(variable._obj, nonstring='passthru')) - return self._clean_data(variable._obj) + rval = self._clean_data(variable._obj) + return rval try: if convert_bare: @@ -328,7 +374,15 @@ if cache and sha1_hash in self._cached_result: result = self._cached_result[sha1_hash] else: - result = self._do_template(variable, preserve_trailing_newlines=preserve_trailing_newlines, escape_backslashes=escape_backslashes, fail_on_undefined=fail_on_undefined, overrides=overrides) + result = self.do_template( + variable, + preserve_trailing_newlines=preserve_trailing_newlines, + escape_backslashes=escape_backslashes, + fail_on_undefined=fail_on_undefined, + overrides=overrides, + disable_lookups=disable_lookups, + ) + unsafe = hasattr(result, '__UNSAFE__') if convert_data and not self._no_type_regex.match(variable): # if this looks like a dictionary or list, convert it to such using the safe_eval method if (result.startswith("{") and not result.startswith(self.environment.variable_start_string)) or \ @@ -336,6 +390,9 @@ eval_results = safe_eval(result, locals=self._available_variables, include_exceptions=True) if eval_results[1] is None: result = eval_results[0] + if unsafe: + from ansible.vars.unsafe_proxy import wrap_var + result = wrap_var(result) else: # FIXME: if the safe_eval raised an error, should we do something with it? pass @@ -349,14 +406,26 @@ return result elif isinstance(variable, (list, tuple)): - return [self.template(v, preserve_trailing_newlines=preserve_trailing_newlines, fail_on_undefined=fail_on_undefined, overrides=overrides) for v in variable] + return [self.template( + v, + preserve_trailing_newlines=preserve_trailing_newlines, + fail_on_undefined=fail_on_undefined, + overrides=overrides, + disable_lookups=disable_lookups, + ) for v in variable] elif isinstance(variable, dict): d = {} # we don't use iteritems() here to avoid problems if the underlying dict # changes sizes due to the templating, which can happen with hostvars for k in variable.keys(): if k not in static_vars: - d[k] = self.template(variable[k], preserve_trailing_newlines=preserve_trailing_newlines, fail_on_undefined=fail_on_undefined, overrides=overrides) + d[k] = self.template( + variable[k], + preserve_trailing_newlines=preserve_trailing_newlines, + fail_on_undefined=fail_on_undefined, + overrides=overrides, + disable_lookups=disable_lookups, + ) else: d[k] = variable[k] return d @@ -416,6 +485,9 @@ ''' return thing if thing is not None else '' + def _fail_lookup(self, name, *args, **kwargs): + raise AnsibleError("The lookup `%s` was found, however lookups were disabled from templating" % name) + def _lookup(self, name, *args, **kwargs): instance = self._lookup_loader.get(name.lower(), loader=self._loader, templar=self) @@ -431,7 +503,7 @@ raise AnsibleUndefinedVariable(e) except Exception as e: if self._fail_on_lookup_errors: - raise + raise AnsibleError("An unhandled exception occurred while running the lookup plugin '%s'. Error was a %s, original message: %s" % (name, type(e), e)) ran = None if ran: @@ -451,7 +523,7 @@ else: raise AnsibleError("lookup plugin (%s) not found" % name) - def _do_template(self, data, preserve_trailing_newlines=True, escape_backslashes=True, fail_on_undefined=None, overrides=None): + def do_template(self, data, preserve_trailing_newlines=True, escape_backslashes=True, fail_on_undefined=None, overrides=None, disable_lookups=False): # For preserving the number of input newlines in the output (used # later in this method) data_newlines = _count_newlines_from_end(data) @@ -495,7 +567,11 @@ else: return data - t.globals['lookup'] = self._lookup + if disable_lookups: + t.globals['lookup'] = self._fail_lookup + else: + t.globals['lookup'] = self._lookup + t.globals['finalize'] = self._finalize jvars = AnsibleJ2Vars(self, t.globals) @@ -505,6 +581,9 @@ try: res = j2_concat(rf) + if new_context.unsafe: + from ansible.vars.unsafe_proxy import wrap_var + res = wrap_var(res) except TypeError as te: if 'StrictUndefined' in to_native(te): errmsg = "Unable to look up a name or access an attribute in template string (%s).\n" % to_native(data) @@ -537,3 +616,6 @@ else: #TODO: return warning about undefined var return data + + # for backwards compatibility in case anyone is using old private method directly + _do_template = do_template diff -Nru ansible-2.2.0.0/lib/ansible/template/safe_eval.py ansible-2.2.1.0/lib/ansible/template/safe_eval.py --- ansible-2.2.0.0/lib/ansible/template/safe_eval.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/template/safe_eval.py 2017-01-16 17:03:54.000000000 +0000 @@ -26,12 +26,6 @@ from ansible import constants as C from ansible.plugins import filter_loader, test_loader -try: - from __main__ import display -except ImportError: - from ansible.utils.display import Display - display = Display() - def safe_eval(expr, locals={}, include_exceptions=False): ''' This is intended for allowing things like: @@ -75,6 +69,7 @@ ast.Name, ast.Str, ast.Sub, + ast.USub, ast.Tuple, ast.UnaryOp, ) @@ -143,7 +138,6 @@ return (expr, None) return expr except Exception as e: - display.warning('Exception in safe_eval() on expr: %s (%s)' % (expr, e)) if include_exceptions: return (expr, e) return expr diff -Nru ansible-2.2.0.0/lib/ansible/template/template.py ansible-2.2.1.0/lib/ansible/template/template.py --- ansible-2.2.0.0/lib/ansible/template/template.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/template/template.py 2017-01-16 17:03:54.000000000 +0000 @@ -33,5 +33,5 @@ ''' def new_context(self, vars=None, shared=False, locals=None): - return jinja2.runtime.Context(self.environment, vars.add_locals(locals), self.name, self.blocks) + return self.environment.context_class(self.environment, vars.add_locals(locals), self.name, self.blocks) diff -Nru ansible-2.2.0.0/lib/ansible/template/vars.py ansible-2.2.1.0/lib/ansible/template/vars.py --- ansible-2.2.0.0/lib/ansible/template/vars.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/template/vars.py 2017-01-16 17:03:54.000000000 +0000 @@ -82,7 +82,7 @@ # HostVars is special, return it as-is, as is the special variable # 'vars', which contains the vars structure from ansible.vars.hostvars import HostVars - if isinstance(variable, dict) and varname == "vars" or isinstance(variable, HostVars): + if isinstance(variable, dict) and varname == "vars" or isinstance(variable, HostVars) or hasattr(variable, '__UNSAFE__'): return variable else: value = None diff -Nru ansible-2.2.0.0/lib/ansible/utils/module_docs.py ansible-2.2.1.0/lib/ansible/utils/module_docs.py --- ansible-2.2.0.0/lib/ansible/utils/module_docs.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/utils/module_docs.py 2017-01-16 17:03:54.000000000 +0000 @@ -97,10 +97,10 @@ fragment_yaml = getattr(fragment_class, fragment_var, '{}') fragment = AnsibleLoader(fragment_yaml, file_name=filename).get_single_data() - if fragment.has_key('notes'): + if 'notes' in fragment: notes = fragment.pop('notes') if notes: - if not doc.has_key('notes'): + if 'notes' not in doc: doc['notes'] = [] doc['notes'].extend(notes) @@ -108,7 +108,7 @@ raise Exception("missing options in fragment, possibly misformatted?") for key, value in fragment.items(): - if not doc.has_key(key): + if key not in doc: doc[key] = value else: if isinstance(doc[key], MutableMapping): diff -Nru ansible-2.2.0.0/lib/ansible/utils/path.py ansible-2.2.1.0/lib/ansible/utils/path.py --- ansible-2.2.0.0/lib/ansible/utils/path.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/utils/path.py 2017-01-16 17:03:54.000000000 +0000 @@ -26,10 +26,9 @@ __all__ = ['unfrackpath', 'makedirs_safe'] -def unfrackpath(path): +def unfrackpath(path, follow=True): ''' - Returns a path that is free of symlinks, environment - variables, relative path traversals and symbols (~) + Returns a path that is free of symlinks (if follow=True), environment variables, relative path traversals and symbols (~) :arg path: A byte or text string representing a path to be canonicalized :raises UnicodeDecodeError: If the canonicalized version of the path @@ -41,9 +40,13 @@ example:: '$HOME/../../var/mail' becomes '/var/spool/mail' ''' - canonical_path = os.path.normpath(os.path.realpath(os.path.expanduser(os.path.expandvars(to_bytes(path, errors='surrogate_or_strict'))))) - return to_text(canonical_path, errors='surrogate_or_strict') + if follow: + final_path = os.path.normpath(os.path.realpath(os.path.expanduser(os.path.expandvars(to_bytes(path, errors='surrogate_or_strict'))))) + else: + final_path = os.path.normpath(os.path.abspath(os.path.expanduser(os.path.expandvars(to_bytes(path, errors='surrogate_or_strict'))))) + + return to_text(final_path, errors='surrogate_or_strict') def makedirs_safe(path, mode=None): '''Safe way to create dirs in muliprocess/thread environments. diff -Nru ansible-2.2.0.0/lib/ansible/vars/hostvars.py ansible-2.2.1.0/lib/ansible/vars/hostvars.py --- ansible-2.2.0.0/lib/ansible/vars/hostvars.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/vars/hostvars.py 2017-01-16 17:03:54.000000000 +0000 @@ -21,7 +21,7 @@ import collections -from jinja2 import Undefined as j2undefined +from jinja2.exceptions import UndefinedError from ansible import constants as C from ansible.template import Templar @@ -73,7 +73,7 @@ ''' host = self._find_host(host_name) if host is None: - raise j2undefined + raise UndefinedError("%s not found in hostvars" % host_name) return self._variable_manager.get_vars(loader=self._loader, host=host, include_hostvars=False) @@ -102,7 +102,7 @@ def __iter__(self): for host in self._inventory.get_hosts(ignore_limits=True, ignore_restrictions=True): - yield host + yield host.name def __len__(self): return len(self._inventory.get_hosts(ignore_limits=True, ignore_restrictions=True)) diff -Nru ansible-2.2.0.0/lib/ansible/vars/__init__.py ansible-2.2.1.0/lib/ansible/vars/__init__.py --- ansible-2.2.0.0/lib/ansible/vars/__init__.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/vars/__init__.py 2017-01-16 17:03:54.000000000 +0000 @@ -252,7 +252,7 @@ # we merge in vars from groups specified in the inventory (INI or script) all_vars = combine_vars(all_vars, host.get_group_vars()) - for group in sorted(host.get_groups(), key=lambda g: g.depth): + for group in sorted(host.get_groups(), key=lambda g: (g.depth, g.name)): if group.name in self._group_vars_files and group.name != 'all': for data in self._group_vars_files[group.name]: data = preprocess_vars(data) @@ -312,7 +312,10 @@ except AnsibleParserError as e: raise else: - raise AnsibleFileNotFound("vars file %s was not found" % vars_file_item) + # if include_delegate_to is set to False, we ignore the missing + # vars file here because we're working on a delegated host + if include_delegate_to: + raise AnsibleFileNotFound("vars file %s was not found" % vars_file_item) except (UndefinedError, AnsibleUndefinedVariable): if host is not None and self._fact_cache.get(host.name, dict()).get('module_setup') and task is not None: raise AnsibleUndefinedVariable("an undefined variable was found when attempting to template the vars_files item '%s'" % vars_file_item, obj=vars_file_item) @@ -410,12 +413,13 @@ variables['inventory_file'] = self._inventory.src() if play: # add the list of hosts in the play, as adjusted for limit/filters - # DEPRECATED: play_hosts should be deprecated in favor of ansible_play_hosts, - # however this would take work in the templating engine, so for now - # we'll add both so we can give users something transitional to use - variables['play_hosts'] = [x.name for x in self._inventory.get_hosts()] - variables['ansible_play_batch'] = [x.name for x in self._inventory.get_hosts()] - variables['ansible_play_hosts'] = [x.name for x in self._inventory.get_hosts(pattern=play.hosts or 'all', ignore_restrictions=True)] + variables['ansible_play_hosts_all'] = [x.name for x in self._inventory.get_hosts(pattern=play.hosts or 'all', ignore_restrictions=True)] + variables['ansible_play_hosts'] = [x for x in variables['ansible_play_hosts_all'] if x not in play._removed_hosts] + variables['ansible_play_batch'] = [x.name for x in self._inventory.get_hosts() if x.name not in play._removed_hosts] + + #DEPRECATED: play_hosts should be deprecated in favor of ansible_play_batch, + # however this would take work in the templating engine, so for now we'll add both + variables['play_hosts'] = variables['ansible_play_batch'] # the 'omit' value alows params to be left out if the variable they are based on is undefined variables['omit'] = self._omit_token diff -Nru ansible-2.2.0.0/lib/ansible/vars/unsafe_proxy.py ansible-2.2.1.0/lib/ansible/vars/unsafe_proxy.py --- ansible-2.2.0.0/lib/ansible/vars/unsafe_proxy.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible/vars/unsafe_proxy.py 2017-01-16 17:03:54.000000000 +0000 @@ -64,7 +64,6 @@ class AnsibleUnsafe(object): __UNSAFE__ = True - class AnsibleUnsafeText(text_type, AnsibleUnsafe): pass @@ -99,10 +98,14 @@ def _wrap_dict(v): + # Create new dict to get rid of the keys that are not wrapped. + new = {} for k in v.keys(): if v[k] is not None: - v[k] = wrap_var(v[k]) - return v + new[wrap_var(k)] = wrap_var(v[k]) + else: + new[wrap_var(k)] = None + return new def _wrap_list(v): diff -Nru ansible-2.2.0.0/lib/ansible.egg-info/PKG-INFO ansible-2.2.1.0/lib/ansible.egg-info/PKG-INFO --- ansible-2.2.0.0/lib/ansible.egg-info/PKG-INFO 2016-11-01 03:44:00.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible.egg-info/PKG-INFO 2017-01-16 17:04:27.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: ansible -Version: 2.2.0.0 +Version: 2.2.1.0 Summary: Radically simple IT automation Home-page: http://ansible.com/ Author: Ansible, Inc. diff -Nru ansible-2.2.0.0/lib/ansible.egg-info/requires.txt ansible-2.2.1.0/lib/ansible.egg-info/requires.txt --- ansible-2.2.0.0/lib/ansible.egg-info/requires.txt 2016-11-01 03:44:00.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible.egg-info/requires.txt 2017-01-16 17:04:27.000000000 +0000 @@ -1,5 +1,5 @@ paramiko -jinja2 +jinja2 < 2.9 PyYAML setuptools -pycrypto >= 2.6 \ No newline at end of file +pycrypto >= 2.6 diff -Nru ansible-2.2.0.0/lib/ansible.egg-info/SOURCES.txt ansible-2.2.1.0/lib/ansible.egg-info/SOURCES.txt --- ansible-2.2.0.0/lib/ansible.egg-info/SOURCES.txt 2016-11-01 03:44:00.000000000 +0000 +++ ansible-2.2.1.0/lib/ansible.egg-info/SOURCES.txt 2017-01-16 17:04:27.000000000 +0000 @@ -1241,6 +1241,7 @@ lib/ansible/plugins/action/script.py lib/ansible/plugins/action/service.py lib/ansible/plugins/action/set_fact.py +lib/ansible/plugins/action/set_stats.py lib/ansible/plugins/action/sros_config.py lib/ansible/plugins/action/synchronize.py lib/ansible/plugins/action/template.py @@ -1398,6 +1399,7 @@ lib/ansible/vars/unsafe_proxy.py packaging/arch/PKGBUILD packaging/arch/README.md +packaging/debian/Dockerfile packaging/debian/README.md packaging/debian/ansible.dirs packaging/debian/ansible.install diff -Nru ansible-2.2.0.0/Makefile ansible-2.2.1.0/Makefile --- ansible-2.2.0.0/Makefile 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/Makefile 2017-01-16 17:03:54.000000000 +0000 @@ -47,7 +47,7 @@ GITINFO = "" endif -ifeq ($(shell echo $(OS) | egrep -c 'Darwin|FreeBSD|OpenBSD'),1) +ifeq ($(shell echo $(OS) | egrep -c 'Darwin|FreeBSD|OpenBSD|DragonFly'),1) DATE := $(shell date -j -r $(shell git log -n 1 --format="%at") +%Y%m%d%H%M) else DATE := $(shell date --utc --date="$(GIT_DATE)" +%Y%m%d%H%M) @@ -77,6 +77,12 @@ # Choose the desired Ubuntu release: lucid precise saucy trusty DEB_DIST ?= unstable +# pbuilder parameters +PBUILDER_ARCH ?= amd64 +PBUILDER_CACHE_DIR = /var/cache/pbuilder +PBUILDER_BIN ?= pbuilder +PBUILDER_OPTS ?= --debootstrapopts --variant=buildd --architecture $(PBUILDER_ARCH) --debbuildopts -b + # RPM build parameters RPMSPECDIR= packaging/rpm RPMSPEC = $(RPMSPECDIR)/ansible.spec @@ -230,7 +236,23 @@ sed -ie "s|%VERSION%|$(VERSION)|g;s|%RELEASE%|$(DEB_RELEASE)|;s|%DIST%|$${DIST}|g;s|%DATE%|$(DEB_DATE)|g" deb-build/$${DIST}/$(NAME)-$(VERSION)/debian/changelog ; \ done -deb: debian +deb: deb-src + @for DIST in $(DEB_DIST) ; do \ + PBUILDER_OPTS="$(PBUILDER_OPTS) --distribution $${DIST} --basetgz $(PBUILDER_CACHE_DIR)/$${DIST}-$(PBUILDER_ARCH)-base.tgz --buildresult $(CURDIR)/deb-build/$${DIST}" ; \ + $(PBUILDER_BIN) create $${PBUILDER_OPTS} --othermirror "deb http://archive.ubuntu.com/ubuntu $${DIST} universe" ; \ + $(PBUILDER_BIN) update $${PBUILDER_OPTS} ; \ + $(PBUILDER_BIN) build $${PBUILDER_OPTS} deb-build/$${DIST}/$(NAME)_$(VERSION)-$(DEB_RELEASE)~$${DIST}.dsc ; \ + done + @echo "#############################################" + @echo "Ansible DEB artifacts:" + @for DIST in $(DEB_DIST) ; do \ + echo deb-build/$${DIST}/$(NAME)_$(VERSION)-$(DEB_RELEASE)~$${DIST}_amd64.changes ; \ + done + @echo "#############################################" + +# Build package outside of pbuilder, with locally installed dependencies. +# Install BuildRequires as noted in packaging/debian/control. +local_deb: debian @for DIST in $(DEB_DIST) ; do \ (cd deb-build/$${DIST}/$(NAME)-$(VERSION)/ && $(DEBUILD) -b) ; \ done diff -Nru ansible-2.2.0.0/packaging/debian/changelog ansible-2.2.1.0/packaging/debian/changelog --- ansible-2.2.0.0/packaging/debian/changelog 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/packaging/debian/changelog 2017-01-16 17:03:54.000000000 +0000 @@ -4,6 +4,13 @@ -- Ansible, Inc. %DATE% +ansible (2.2.1.0) unstable; urgency=low + + * 2.2.1.0 + + -- Ansible, Inc. Mon, 16 Jan 2017 10:13:29 -0600 + + ansible (2.2.0.0) unstable; urgency=low * 2.2.0.0 diff -Nru ansible-2.2.0.0/packaging/debian/Dockerfile ansible-2.2.1.0/packaging/debian/Dockerfile --- ansible-2.2.0.0/packaging/debian/Dockerfile 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.2.1.0/packaging/debian/Dockerfile 2017-01-16 17:03:54.000000000 +0000 @@ -0,0 +1,16 @@ +FROM ubuntu:xenial + +RUN apt-get update && apt-get install -y \ + asciidoc \ + cdbs \ + debootstrap \ + devscripts \ + make \ + pbuilder \ + python-setuptools + +VOLUME /ansible +WORKDIR /ansible + +ENTRYPOINT ["/bin/bash", "-c"] +CMD ["make deb"] diff -Nru ansible-2.2.0.0/packaging/debian/README.md ansible-2.2.1.0/packaging/debian/README.md --- ansible-2.2.0.0/packaging/debian/README.md 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/packaging/debian/README.md 2017-01-16 17:03:54.000000000 +0000 @@ -3,26 +3,39 @@ To create an Ansible DEB package: - sudo apt-get install python-paramiko python-yaml python-jinja2 python-httplib2 python-setuptools python-six sshpass - sudo apt-get install cdbs debhelper dpkg-dev git-core reprepro fakeroot asciidoc devscripts docbook-xml xsltproc libxml2-utils - sudo apt-get install dh-python - git clone git://github.com/ansible/ansible.git - cd ansible - make deb +__Note__: You must run this target as root or set `PBUILDER_BIN='sudo pbuilder'` -On older releases that do not have `dh-python` (like Ubuntu 12.04), install `python-support` instead: +``` +apt-get install asciidoc cdbs debootstrap devscripts make pbuilder python-setuptools +git clone git://github.com/ansible/ansible.git +cd ansible +git submodule update --init +DEB_DIST='xenial trusty precise' make deb +``` + +Building in Docker: + +``` +git clone git://github.com/ansible/ansible.git +cd ansible +git submodule update --init +docker build -t ansible-deb-builder -f packaging/debian/Dockerfile . +docker run --privileged -e DEB_DIST='trusty' -v $(pwd):/ansible ansible-deb-builder +``` - sudo apt-get install python-support - -The debian package file will be placed in the `../` directory. This can then be added to an APT repository or installed with `dpkg -i `. +The debian package file will be placed in the `deb-build` directory. This can then be added to an APT repository or installed with `dpkg -i `. Note that `dpkg -i` does not resolve dependencies. To install the Ansible DEB package and resolve dependencies: - sudo dpkg -i - sudo apt-get -fy install +``` +dpkg -i +apt-get -fy install +``` Or, if you are running Debian Stretch (or later) or Ubuntu Xenial (or later): - sudo apt install +``` +apt install /path/to/ +``` diff -Nru ansible-2.2.0.0/packaging/debian/rules ansible-2.2.1.0/packaging/debian/rules --- ansible-2.2.0.0/packaging/debian/rules 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/packaging/debian/rules 2017-01-16 17:03:54.000000000 +0000 @@ -2,9 +2,6 @@ # -- makefile -- DEB_PYTHON2_MODULE_PACKAGES=ansible -ifneq ($(shell dpkg-query -f '$${Version}' -W python-support 2>/dev/null),) -DEB_PYTHON_SYSTEM=pysupport -endif include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/class/python-distutils.mk diff -Nru ansible-2.2.0.0/packaging/release/templates/RELEASES.tmpl ansible-2.2.1.0/packaging/release/templates/RELEASES.tmpl --- ansible-2.2.0.0/packaging/release/templates/RELEASES.tmpl 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/packaging/release/templates/RELEASES.tmpl 2017-01-16 17:03:54.000000000 +0000 @@ -5,9 +5,9 @@ ++++++++++++++++++++++++++++++ {% for version in versions %} -{% for vkey, vdata in version.iteritems() %} +{% for vkey, vdata in version.items() %} {% for release in vdata.releases %} -{% for rkey, rdata in release.iteritems() %} +{% for rkey, rdata in release.items() %} {% set major_minor = vkey + "." + rkey %} {{"%-8s"|format(major_minor)}} {{"%-10s"|format(rdata)}} "{{vdata.code_name}}" {% endfor %} diff -Nru ansible-2.2.0.0/packaging/rpm/ansible.spec ansible-2.2.1.0/packaging/rpm/ansible.spec --- ansible-2.2.0.0/packaging/rpm/ansible.spec 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/packaging/rpm/ansible.spec 2017-01-16 17:03:54.000000000 +0000 @@ -125,6 +125,9 @@ %changelog +* Mon Jan 16 2017 Ansible, Inc. - 2.2.1.0-1 +- Release 2.2.1.0-1 + * Mon Oct 31 2016 Ansible, Inc. - 2.2.0.0-1 - Release 2.2.0.0-1 diff -Nru ansible-2.2.0.0/PKG-INFO ansible-2.2.1.0/PKG-INFO --- ansible-2.2.0.0/PKG-INFO 2016-11-01 03:44:01.000000000 +0000 +++ ansible-2.2.1.0/PKG-INFO 2017-01-16 17:04:28.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: ansible -Version: 2.2.0.0 +Version: 2.2.1.0 Summary: Radically simple IT automation Home-page: http://ansible.com/ Author: Ansible, Inc. diff -Nru ansible-2.2.0.0/setup.py ansible-2.2.1.0/setup.py --- ansible-2.2.0.0/setup.py 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/setup.py 2017-01-16 17:03:54.000000000 +0000 @@ -22,7 +22,7 @@ license='GPLv3', # Ansible will also make use of a system copy of python-six if installed but use a # Bundled copy if it's not. - install_requires=['paramiko', 'jinja2', "PyYAML", 'setuptools', 'pycrypto >= 2.6'], + install_requires=['paramiko', 'jinja2 < 2.9', "PyYAML", 'setuptools', 'pycrypto >= 2.6'], package_dir={ '': 'lib' }, packages=find_packages('lib'), package_data={ diff -Nru ansible-2.2.0.0/VERSION ansible-2.2.1.0/VERSION --- ansible-2.2.0.0/VERSION 2016-11-01 03:43:34.000000000 +0000 +++ ansible-2.2.1.0/VERSION 2017-01-16 17:03:54.000000000 +0000 @@ -1 +1 @@ -2.2.0.0 1 +2.2.1.0 1