From a23c95023b3e4d79b971b75cc8c5878c62ebf501 Mon Sep 17 00:00:00 2001 From: John R Barker Date: Tue, 30 Jan 2018 12:23:52 +0000 Subject: [PATCH] Module deprecation: docs, scheme and tests (#34100) Enforce module deprecation. After module has reached the end of it's deprecation cycle we will replace it with a docs stub. * Replace deprecated modules with docs-only sub * Use of deprecated past deprecation cycle gives meaningful message (see examples below) * Enforce documentation.deprecation dict via `schema.py` * Update `ansible-doc` and web docs to display documentation.deprecation * Document that structure in `dev_guide` * Ensure that all modules starting with `_` have a `deprecation:` block * Ensure `deprecation:` block is only used on modules that start with `_` * `removed_in` A string which represents when this module needs **deleting** * CHANGELOG.md and porting_guide_2.5.rst list removed modules as well as alternatives * CHANGELOG.md links to porting guide index To ensure that meaningful messages are given to the user if they try to use a module at the end of it's deprecation cycle we enforce the module to contain: ```python if __name__ == '__main__': removed_module() ``` --- CHANGELOG.md | 19 +- .../developing_modules_documenting.rst | 14 +- .../dev_guide/testing_validate-modules.rst | 4 +- docs/docsite/rst/porting_guide_2.5.rst | 12 +- docs/templates/plugin.rst.j2 | 2 +- lib/ansible/cli/doc.py | 2 +- .../modules/cloud/amazon/_ec2_ami_find.py | 5 +- .../modules/cloud/amazon/_ec2_ami_search.py | 126 +- .../modules/cloud/amazon/_ec2_remote_facts.py | 5 +- lib/ansible/modules/cloud/amazon/_ec2_vpc.py | 611 +------ lib/ansible/modules/cloud/azure/_azure.py | 5 +- .../modules/cloud/cloudstack/_cs_nic.py | 5 +- lib/ansible/modules/cloud/docker/_docker.py | 1465 +---------------- .../modules/clustering/k8s/_kubernetes.py | 5 +- .../modules/clustering/openshift/_oc.py | 5 +- .../modules/network/citrix/_netscaler.py | 5 +- .../modules/network/cumulus/_cl_bond.py | 283 +--- .../modules/network/cumulus/_cl_bridge.py | 260 +-- .../network/cumulus/_cl_img_install.py | 215 +-- .../modules/network/cumulus/_cl_interface.py | 251 +-- .../network/cumulus/_cl_interface_policy.py | 83 +- .../modules/network/cumulus/_cl_license.py | 47 +- .../modules/network/cumulus/_cl_ports.py | 140 +- .../network/nxos/_nxos_ip_interface.py | 5 +- lib/ansible/modules/network/nxos/_nxos_mtu.py | 265 +-- .../modules/network/nxos/_nxos_portchannel.py | 5 +- .../modules/network/nxos/_nxos_switchport.py | 5 +- ...nos_nat_policy.py => _panos_nat_policy.py} | 7 +- ...ty_policy.py => _panos_security_policy.py} | 7 +- .../modules/utilities/helper/_accelerate.py | 14 +- .../modules/utilities/logic/_include.py | 5 +- lib/ansible/modules/windows/_win_msi.py | 5 +- test/integration/nxos.yaml | 9 - test/integration/targets/docker/aliases | 2 - .../targets/docker/files/devdockerCA.crt | 23 - .../targets/docker/files/devdockerCA.key | 27 - .../targets/docker/files/devdockerCA.srl | 1 - .../docker/files/docker-registry.htpasswd | 1 - .../docker/files/dockertest.ansible.com.crt | 21 - .../docker/files/dockertest.ansible.com.csr | 17 - .../docker/files/dockertest.ansible.com.key | 27 - .../docker/files/nginx-docker-registry.conf | 40 - test/integration/targets/docker/meta/main.yml | 20 - .../docker/tasks/docker-setup-debian.yml | 5 - .../targets/docker/tasks/docker-setup-rht.yml | 16 - .../targets/docker/tasks/docker-tests.yml | 58 - .../integration/targets/docker/tasks/main.yml | 22 - .../targets/docker/tasks/registry-tests.yml | 188 --- test/integration/targets/ec2_vpc/aliases | 2 - .../targets/ec2_vpc/defaults/main.yml | 2 - .../integration/targets/ec2_vpc/meta/main.yml | 3 - .../targets/ec2_vpc/tasks/main.yml | 2 - .../integration/targets/ec2_vpc/vars/main.yml | 2 - .../targets/nxos_mtu/defaults/main.yaml | 2 - .../targets/nxos_mtu/meta/main.yml | 2 - .../targets/nxos_mtu/tasks/cli.yaml | 33 - .../targets/nxos_mtu/tasks/main.yaml | 3 - .../targets/nxos_mtu/tasks/nxapi.yaml | 27 - .../nxos_mtu/tests/common/set_mtu.yaml | 70 - .../nxos_mtu/tests/common/set_sysmtu.yaml | 60 - test/sanity/validate-modules/ignore.txt | 2 +- test/sanity/validate-modules/main.py | 30 +- test/sanity/validate-modules/schema.py | 54 +- test/sanity/validate-modules/skip.txt | 1 - test/units/modules/cloud/docker/__init__.py | 0 .../units/modules/cloud/docker/test_docker.py | 20 - 66 files changed, 241 insertions(+), 4438 deletions(-) rename lib/ansible/modules/network/panos/{panos_nat_policy.py => _panos_nat_policy.py} (98%) rename lib/ansible/modules/network/panos/{panos_security_policy.py => _panos_security_policy.py} (98%) delete mode 100644 test/integration/targets/docker/aliases delete mode 100644 test/integration/targets/docker/files/devdockerCA.crt delete mode 100644 test/integration/targets/docker/files/devdockerCA.key delete mode 100644 test/integration/targets/docker/files/devdockerCA.srl delete mode 100644 test/integration/targets/docker/files/docker-registry.htpasswd delete mode 100644 test/integration/targets/docker/files/dockertest.ansible.com.crt delete mode 100644 test/integration/targets/docker/files/dockertest.ansible.com.csr delete mode 100644 test/integration/targets/docker/files/dockertest.ansible.com.key delete mode 100644 test/integration/targets/docker/files/nginx-docker-registry.conf delete mode 100644 test/integration/targets/docker/meta/main.yml delete mode 100644 test/integration/targets/docker/tasks/docker-setup-debian.yml delete mode 100644 test/integration/targets/docker/tasks/docker-setup-rht.yml delete mode 100644 test/integration/targets/docker/tasks/docker-tests.yml delete mode 100644 test/integration/targets/docker/tasks/main.yml delete mode 100644 test/integration/targets/docker/tasks/registry-tests.yml delete mode 100644 test/integration/targets/ec2_vpc/aliases delete mode 100644 test/integration/targets/ec2_vpc/defaults/main.yml delete mode 100644 test/integration/targets/ec2_vpc/meta/main.yml delete mode 100644 test/integration/targets/ec2_vpc/tasks/main.yml delete mode 100644 test/integration/targets/ec2_vpc/vars/main.yml delete mode 100644 test/integration/targets/nxos_mtu/defaults/main.yaml delete mode 100644 test/integration/targets/nxos_mtu/meta/main.yml delete mode 100644 test/integration/targets/nxos_mtu/tasks/cli.yaml delete mode 100644 test/integration/targets/nxos_mtu/tasks/main.yaml delete mode 100644 test/integration/targets/nxos_mtu/tasks/nxapi.yaml delete mode 100644 test/integration/targets/nxos_mtu/tests/common/set_mtu.yaml delete mode 100644 test/integration/targets/nxos_mtu/tests/common/set_sysmtu.yaml delete mode 100644 test/units/modules/cloud/docker/__init__.py delete mode 100644 test/units/modules/cloud/docker/test_docker.py diff --git a/CHANGELOG.md b/CHANGELOG.md index e19616cf6c8..d7e5b625e1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ Ansible Changes By Release ## 2.5 "TBD" - ACTIVE DEVELOPMENT +[Porting Guide](http://docs.ansible.com/ansible/devel/porting_guides.html) + ### Major Changes * Removed the previously deprecated 'accelerate' mode and all associated keywords and code. * New simpler and more intuitive 'loop' keyword for task loops. The ``with_`` loops will be deprecated in the near future and eventually removed. @@ -13,7 +15,7 @@ Ansible Changes By Release currently on by default, in the future it will be off. * Add a configuration file to filter modules that a site administrator wants to exclude from being used. -### Deprecations +### Deprecations (to be removed in 2.9) * Previously deprecated 'hostfile' config settings have been 're-deprecated' as previously code did not warn about deprecated configuration settings. * Using Ansible provided Jinja tests as filters is deprecated and will be removed in Ansible 2.9 * `stat` and `win_stat` have deprecated `get_md5` and the `md5` return value @@ -36,6 +38,9 @@ Ansible Changes By Release * nxos_ip_interface module is deprecated in Ansible 2.5. Use nxos_l3_interface module instead. * nxos_portchannel module is deprecated in Ansible 2.5. Use nxos_linkagg module instead. * nxos_switchport module is deprecated in Ansible 2.5. Use nxos_l2_interface module instead. +* ec2_ami_find has been deprecated, use ec2_ami_facts. + +See [Porting Guide](http://docs.ansible.com/ansible/devel/porting_guides.html) for more information ### Minor Changes * added a few new magic vars corresponding to configuration/command line options: @@ -57,14 +62,16 @@ Ansible Changes By Release * Task debugger functionality was moved into `StrategyBase`, and extended to allow explicit invocation from use of the `debugger` keyword. The `debug` strategy is still functional, and is now just a trigger to enable this functionality -#### Deprecated Modules (to be removed in 2.9): -* ec2_ami_find: replaced by ec2_ami_facts - #### Removed Modules (previously deprecated): -* accelerate +* accelerate. * boundary_meter: There was no deprecation period for this but the hosted service it relied on has gone away so the module has been removed. - https://github.com/ansible/ansible/issues/29387 + [#29387](https://github.com/ansible/ansible/issues/29387) +* cl_ : cl_interface, cl_interface_policy, cl_bridge, cl_img_install, cl_ports, cl_license, cl_bond. Use `nclu` instead +* docker, use docker_container and docker_image instead. +* ec2_vpc. +* ec2_ami_search, use ec2_ami_facts instead. +* nxos_mtu, use nxos_system's `system_mtu` option. To specify an interfaces MTU use nxos_interface. ### New Plugins diff --git a/docs/docsite/rst/dev_guide/developing_modules_documenting.rst b/docs/docsite/rst/dev_guide/developing_modules_documenting.rst index 8961b8b9738..3198e866aeb 100644 --- a/docs/docsite/rst/dev_guide/developing_modules_documenting.rst +++ b/docs/docsite/rst/dev_guide/developing_modules_documenting.rst @@ -201,9 +201,17 @@ The following fields can be used and are all required unless specified otherwise :author: Name of the module author in the form ``First Last (@GitHubID)``. Use a multi-line list if there is more than one author. :deprecated: - If this module is deprecated, detail when that happened, and what to use instead, e.g. - `Deprecated in 2.3. Use M(whatmoduletouseinstead) instead.` - Ensure `CHANGELOG.md` is updated to reflect this. + If a module is deprecated it must be: + + * Mentioned in ``CHANGELOG`` + * Referenced in the ``porting_guide_x.y.rst`` + * File should be renamed to start with an ``_`` + * ``ANSIBLE_METADATA`` must contain ``status: ['deprecated']`` + * Following values must be set: + + :removed_in: A `string`, such as ``"2.9"``, which represents the version of Ansible this module will replaced with docs only module stub. + :why: Optional string that used to detail why this has been removed. + :alternative: Inform users they should do instead, i.e. ``Use M(whatmoduletouseinstead) instead.``. :options: One per module argument: diff --git a/docs/docsite/rst/dev_guide/testing_validate-modules.rst b/docs/docsite/rst/dev_guide/testing_validate-modules.rst index 5b034fb0767..0caff15f7b1 100644 --- a/docs/docsite/rst/dev_guide/testing_validate-modules.rst +++ b/docs/docsite/rst/dev_guide/testing_validate-modules.rst @@ -63,8 +63,8 @@ Errors **1xx** **Locations** 101 Interpreter line is not ``#!/usr/bin/python`` 102 Interpreter line is not ``#!powershell`` - 103 Did not find a call to ``main()`` - 104 Call to ``main()`` not the last line + 103 Did not find a call to ``main()`` (or ``removed_module()`` in the case of deprecated & docs only modules) + 104 Call to ``main()`` not the last line (or ``removed_module()`` in the case of deprecated & docs only modules) 105 GPLv3 license header not found 106 Import found before documentation variables. All imports must appear below ``DOCUMENTATION``/``EXAMPLES``/``RETURN``/``ANSIBLE_METADATA`` diff --git a/docs/docsite/rst/porting_guide_2.5.rst b/docs/docsite/rst/porting_guide_2.5.rst index eb625fe5732..109d7634d3c 100644 --- a/docs/docsite/rst/porting_guide_2.5.rst +++ b/docs/docsite/rst/porting_guide_2.5.rst @@ -76,7 +76,17 @@ Modules removed The following modules no longer exist: -* None +* :ref:`nxos_mtu ` use :ref:`nxos_system `'s ``system_mtu`` option or :ref:`nxos_interface ` instead +* :ref:`cl_interface_policy ` use :ref:`nclu ` instead +* :ref:`cl_bridge ` use :ref:`nclu ` instead +* :ref:`cl_img_install ` use :ref:`nclu ` instead +* :ref:`cl_ports ` use :ref:`nclu ` instead +* :ref:`cl_license ` use :ref:`nclu ` instead +* :ref:`cl_interface ` use :ref:`nclu ` instead +* :ref:`cl_bond ` use :ref:`nclu ` instead +* :ref:`ec2_vpc ` use :ref:`ec2_vpc_net ` along with supporting modules :ref:`ec2_vpc_igw `, :ref:`ec2_vpc_route_table `, :ref:`ec2_vpc_subnet `, :ref:`ec2_vpc_dhcp_options `, :ref:`ec2_vpc_nat_gateway `, :ref:`ec2_vpc_nacl ` instead. +* :ref:`ec2_ami_search ` instead +* :ref:`docker ` use :ref:`docker_container ` and :ref:`docker_image ` instead Deprecation notices ------------------- diff --git a/docs/templates/plugin.rst.j2 b/docs/templates/plugin.rst.j2 index c04ee4aa63e..190feaba98a 100644 --- a/docs/templates/plugin.rst.j2 +++ b/docs/templates/plugin.rst.j2 @@ -34,7 +34,7 @@ DEPRECATED ---------- {# use unknown here? skip the fields? #} -:In: version: @{ deprecated['version'] | default('') | string | convert_symbols_to_format }@ +:Removed in Ansible: version: @{ deprecated['removed_in'] | default('') | string | convert_symbols_to_format }@ :Why: @{ deprecated['why'] | default('') | convert_symbols_to_format }@ :Alternative: @{ deprecated['alternative'] | default('')| convert_symbols_to_format }@ diff --git a/lib/ansible/cli/doc.py b/lib/ansible/cli/doc.py index 99cf388721a..dc001ee05fb 100644 --- a/lib/ansible/cli/doc.py +++ b/lib/ansible/cli/doc.py @@ -468,7 +468,7 @@ class DocCLI(CLI): if 'deprecated' in doc and doc['deprecated'] is not None and len(doc['deprecated']) > 0: text.append("DEPRECATED: \n") if isinstance(doc['deprecated'], dict): - text.append("\tReason: %(why)s\n\tScheduled removal: Ansible %(version)s\n\tAlternatives: %(alternative)s" % doc.pop('deprecated')) + text.append("\tReason: %(why)s\n\tWill be removed in: Ansible %(removed_in)s\n\tAlternatives: %(alternative)s" % doc.pop('deprecated')) else: text.append("%s" % doc.pop('deprecated')) text.append("\n") diff --git a/lib/ansible/modules/cloud/amazon/_ec2_ami_find.py b/lib/ansible/modules/cloud/amazon/_ec2_ami_find.py index 1ae5f081f1f..6bd6e3436a8 100644 --- a/lib/ansible/modules/cloud/amazon/_ec2_ami_find.py +++ b/lib/ansible/modules/cloud/amazon/_ec2_ami_find.py @@ -16,7 +16,10 @@ DOCUMENTATION = r''' module: ec2_ami_find version_added: '2.0' short_description: Searches for AMIs to obtain the AMI ID and other information -deprecated: Deprecated in 2.5. Use M(ec2_ami_facts) instead. +deprecated: + removed_in: "2.9" + why: Various AWS modules have been combined and replaced with M(ec2_ami_facts). + alternative: Use M(ec2_ami_facts) instead. description: - Returns list of matching AMIs with AMI ID, along with other useful information - Can search AMIs with different owners diff --git a/lib/ansible/modules/cloud/amazon/_ec2_ami_search.py b/lib/ansible/modules/cloud/amazon/_ec2_ami_search.py index d270e159c37..c17a3727bbc 100644 --- a/lib/ansible/modules/cloud/amazon/_ec2_ami_search.py +++ b/lib/ansible/modules/cloud/amazon/_ec2_ami_search.py @@ -16,7 +16,10 @@ DOCUMENTATION = ''' --- module: ec2_ami_search short_description: Retrieve AWS AMI information for a given operating system. -deprecated: "Use M(ec2_ami_find) instead." +deprecated: + removed_in: "2.2" + why: Various AWS modules have been combined and replaced with M(ec2_ami_facts). + alternative: Use M(ec2_ami_find) instead. version_added: "1.6" description: - Look up the most recent AMI on AWS for a given operating system. @@ -84,124 +87,7 @@ EXAMPLES = ''' key_name: mykey ''' -import csv - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.urls import fetch_url - - -SUPPORTED_DISTROS = ['ubuntu'] - -AWS_REGIONS = ['ap-northeast-1', - 'ap-southeast-1', - 'ap-northeast-2', - 'ap-southeast-2', - 'ap-south-1', - 'ca-central-1', - 'eu-central-1', - 'eu-west-1', - 'eu-west-2', - 'sa-east-1', - 'us-east-1', - 'us-east-2', - 'us-west-1', - 'us-west-2', - "us-gov-west-1"] - - -def get_url(module, url): - """ Get url and return response """ - - r, info = fetch_url(module, url) - if info['status'] != 200: - # Backwards compat - info['status_code'] = info['status'] - module.fail_json(**info) - return r - - -def ubuntu(module): - """ Get the ami for ubuntu """ - - release = module.params['release'] - stream = module.params['stream'] - store = module.params['store'] - arch = module.params['arch'] - region = module.params['region'] - virt = module.params['virt'] - - url = get_ubuntu_url(release, stream) - - req = get_url(module, url) - reader = csv.reader(req, delimiter='\t') - try: - ami, aki, ari, tag, serial = lookup_ubuntu_ami(reader, release, stream, - store, arch, region, virt) - module.exit_json(changed=False, ami=ami, aki=aki, ari=ari, tag=tag, - serial=serial) - except KeyError: - module.fail_json(msg="No matching AMI found") - - -def lookup_ubuntu_ami(table, release, stream, store, arch, region, virt): - """ Look up the Ubuntu AMI that matches query given a table of AMIs - - table: an iterable that returns a row of - (release, stream, tag, serial, region, ami, aki, ari, virt) - release: ubuntu release name - stream: 'server' or 'desktop' - store: 'ebs', 'ebs-io1', 'ebs-ssd' or 'instance-store' - arch: 'i386' or 'amd64' - region: EC2 region - virt: 'paravirtual' or 'hvm' - - Returns (ami, aki, ari, tag, serial)""" - expected = (release, stream, store, arch, region, virt) - - for row in table: - (actual_release, actual_stream, tag, serial, - actual_store, actual_arch, actual_region, ami, aki, ari, - actual_virt) = row - actual = (actual_release, actual_stream, actual_store, actual_arch, - actual_region, actual_virt) - if actual == expected: - # aki and ari are sometimes blank - if aki == '': - aki = None - if ari == '': - ari = None - return (ami, aki, ari, tag, serial) - - raise KeyError() - - -def get_ubuntu_url(release, stream): - url = "https://cloud-images.ubuntu.com/query/%s/%s/released.current.txt" - return url % (release, stream) - - -def main(): - arg_spec = dict( - distro=dict(required=True, choices=SUPPORTED_DISTROS), - release=dict(required=True), - stream=dict(required=False, default='server', - choices=['desktop', 'server']), - store=dict(required=False, default='ebs', - choices=['ebs', 'ebs-io1', 'ebs-ssd', 'instance-store']), - arch=dict(required=False, default='amd64', - choices=['i386', 'amd64']), - region=dict(required=False, default='us-east-1', choices=AWS_REGIONS), - virt=dict(required=False, default='paravirtual', - choices=['paravirtual', 'hvm']), - ) - module = AnsibleModule(argument_spec=arg_spec) - distro = module.params['distro'] - - if distro == 'ubuntu': - ubuntu(module) - else: - module.fail_json(msg="Unsupported distro: %s" % distro) - +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module() diff --git a/lib/ansible/modules/cloud/amazon/_ec2_remote_facts.py b/lib/ansible/modules/cloud/amazon/_ec2_remote_facts.py index 57e342e3d77..a3aece7a15d 100644 --- a/lib/ansible/modules/cloud/amazon/_ec2_remote_facts.py +++ b/lib/ansible/modules/cloud/amazon/_ec2_remote_facts.py @@ -15,7 +15,10 @@ DOCUMENTATION = ''' --- module: ec2_remote_facts short_description: Gather facts about ec2 instances in AWS -deprecated: Deprecated in 2.4. Use M(ec2_instance_facts) instead. +deprecated: + removed_in: "2.8" + why: Replaced with boto3 version. + alternative: Use M(ec2_instance_facts) instead. description: - Gather facts about ec2 instances in AWS version_added: "2.0" diff --git a/lib/ansible/modules/cloud/amazon/_ec2_vpc.py b/lib/ansible/modules/cloud/amazon/_ec2_vpc.py index 2b1b88862e2..f6765f7ef10 100644 --- a/lib/ansible/modules/cloud/amazon/_ec2_vpc.py +++ b/lib/ansible/modules/cloud/amazon/_ec2_vpc.py @@ -18,10 +18,11 @@ short_description: configure AWS virtual private clouds description: - Create or terminates AWS virtual private clouds. This module has a dependency on python-boto. version_added: "1.4" -deprecated: >- - Deprecated in 2.3. Use M(ec2_vpc_net) along with supporting modules including - M(ec2_vpc_igw), M(ec2_vpc_route_table), M(ec2_vpc_subnet), M(ec2_vpc_dhcp_options), - M(ec2_vpc_nat_gateway), M(ec2_vpc_nacl). +deprecated: + removed_in: "2.5" + why: Replaced by dedicated modules. + alternative: Use M(ec2_vpc_net) along with supporting modules including M(ec2_vpc_igw), M(ec2_vpc_route_table), M(ec2_vpc_subnet), + M(ec2_vpc_dhcp_options), M(ec2_vpc_nat_gateway), M(ec2_vpc_nacl). options: cidr_block: description: @@ -159,605 +160,7 @@ EXAMPLES = ''' # the delete will fail until those dependencies are removed. ''' -import time - -try: - import boto - import boto.ec2 - import boto.vpc - from boto.exception import EC2ResponseError - - HAS_BOTO = True -except ImportError: - HAS_BOTO = False - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.ec2 import connect_to_aws, ec2_argument_spec, get_aws_connection_info - - -def get_vpc_info(vpc): - """ - Retrieves vpc information from an instance - ID and returns it as a dictionary - """ - - return({ - 'id': vpc.id, - 'cidr_block': vpc.cidr_block, - 'dhcp_options_id': vpc.dhcp_options_id, - 'region': vpc.region.name, - 'state': vpc.state, - }) - - -def find_vpc(module, vpc_conn, vpc_id=None, cidr=None): - """ - Finds a VPC that matches a specific id or cidr + tags - - module : AnsibleModule object - vpc_conn: authenticated VPCConnection connection object - - Returns: - A VPC object that matches either an ID or CIDR and one or more tag values - """ - - if vpc_id is None and cidr is None: - module.fail_json( - msg='You must specify either a vpc_id or a cidr block + list of unique tags, aborting' - ) - - found_vpcs = [] - - resource_tags = module.params.get('resource_tags') - - # Check for existing VPC by cidr_block or id - if vpc_id is not None: - found_vpcs = vpc_conn.get_all_vpcs(None, {'vpc-id': vpc_id, 'state': 'available', }) - - else: - previous_vpcs = vpc_conn.get_all_vpcs(None, {'cidr': cidr, 'state': 'available'}) - - for vpc in previous_vpcs: - # Get all tags for each of the found VPCs - vpc_tags = dict((t.name, t.value) for t in vpc_conn.get_all_tags(filters={'resource-id': vpc.id})) - - # If the supplied list of ID Tags match a subset of the VPC Tags, we found our VPC - if resource_tags and set(resource_tags.items()).issubset(set(vpc_tags.items())): - found_vpcs.append(vpc) - - found_vpc = None - - if len(found_vpcs) == 1: - found_vpc = found_vpcs[0] - - if len(found_vpcs) > 1: - module.fail_json(msg='Found more than one vpc based on the supplied criteria, aborting') - - return (found_vpc) - - -def routes_match(rt_list=None, rt=None, igw=None): - """ - Check if the route table has all routes as in given list - - rt_list : A list if routes provided in the module - rt : The Remote route table object - igw : The internet gateway object for this vpc - - Returns: - True when there provided routes and remote routes are the same. - False when provided routes and remote routes are different. - """ - - local_routes = [] - remote_routes = [] - for route in rt_list: - route_kwargs = { - 'gateway_id': None, - 'instance_id': None, - 'interface_id': None, - 'vpc_peering_connection_id': None, - 'state': 'active' - } - if route['gw'] == 'igw': - route_kwargs['gateway_id'] = igw.id - elif route['gw'].startswith('i-'): - route_kwargs['instance_id'] = route['gw'] - elif route['gw'].startswith('eni-'): - route_kwargs['interface_id'] = route['gw'] - elif route['gw'].startswith('pcx-'): - route_kwargs['vpc_peering_connection_id'] = route['gw'] - else: - route_kwargs['gateway_id'] = route['gw'] - route_kwargs['destination_cidr_block'] = route['dest'] - local_routes.append(route_kwargs) - for j in rt.routes: - remote_routes.append(j.__dict__) - match = [] - for i in local_routes: - change = "false" - for j in remote_routes: - if set(i.items()).issubset(set(j.items())): - change = "true" - match.append(change) - if 'false' in match: - return False - else: - return True - - -def rtb_changed(route_tables=None, vpc_conn=None, module=None, vpc=None, igw=None): - """ - Checks if the remote routes match the local routes. - - route_tables : Route_tables parameter in the module - vpc_conn : The VPC connection object - module : The module object - vpc : The vpc object for this route table - igw : The internet gateway object for this vpc - - Returns: - True when there is difference between the provided routes and remote routes and if subnet associations are different. - False when both routes and subnet associations matched. - - """ - # We add a one for the main table - rtb_len = len(route_tables) + 1 - remote_rtb_len = len(vpc_conn.get_all_route_tables(filters={'vpc_id': vpc.id})) - if remote_rtb_len != rtb_len: - return True - for rt in route_tables: - rt_id = None - for sn in rt['subnets']: - rsn = vpc_conn.get_all_subnets(filters={'cidr': sn, 'vpc_id': vpc.id}) - if len(rsn) != 1: - module.fail_json( - msg='The subnet {0} to associate with route_table {1} ' - 'does not exist, aborting'.format(sn, rt) - ) - nrt = vpc_conn.get_all_route_tables(filters={'vpc_id': vpc.id, 'association.subnet-id': rsn[0].id}) - if not nrt: - return True - else: - nrt = nrt[0] - if not rt_id: - rt_id = nrt.id - if not routes_match(rt['routes'], nrt, igw): - return True - continue - else: - if rt_id == nrt.id: - continue - else: - return True - return True - return False - - -def create_vpc(module, vpc_conn): - """ - Creates a new or modifies an existing VPC. - - module : AnsibleModule object - vpc_conn: authenticated VPCConnection connection object - - Returns: - A dictionary with information - about the VPC and subnets that were launched - """ - - id = module.params.get('vpc_id') - cidr_block = module.params.get('cidr_block') - instance_tenancy = module.params.get('instance_tenancy') - dns_support = module.params.get('dns_support') - dns_hostnames = module.params.get('dns_hostnames') - subnets = module.params.get('subnets') - internet_gateway = module.params.get('internet_gateway') - route_tables = module.params.get('route_tables') - vpc_spec_tags = module.params.get('resource_tags') - wait = module.params.get('wait') - wait_timeout = int(module.params.get('wait_timeout')) - changed = False - - # Check for existing VPC by cidr_block + tags or id - previous_vpc = find_vpc(module, vpc_conn, id, cidr_block) - - if previous_vpc is not None: - changed = False - vpc = previous_vpc - else: - changed = True - try: - vpc = vpc_conn.create_vpc(cidr_block, instance_tenancy) - # wait here until the vpc is available - pending = True - wait_timeout = time.time() + wait_timeout - while wait and wait_timeout > time.time() and pending: - try: - pvpc = vpc_conn.get_all_vpcs(vpc.id) - if hasattr(pvpc, 'state'): - if pvpc.state == "available": - pending = False - elif hasattr(pvpc[0], 'state'): - if pvpc[0].state == "available": - pending = False - # sometimes vpc_conn.create_vpc() will return a vpc that can't be found yet by vpc_conn.get_all_vpcs() - # when that happens, just wait a bit longer and try again - except boto.exception.BotoServerError as e: - if e.error_code != 'InvalidVpcID.NotFound': - raise - if pending: - time.sleep(5) - if wait and wait_timeout <= time.time(): - # waiting took too long - module.fail_json(msg="wait for vpc availability timeout on %s" % time.asctime()) - - except boto.exception.BotoServerError as e: - module.fail_json(msg="%s: %s" % (e.error_code, e.error_message)) - - # Done with base VPC, now change to attributes and features. - - # Add resource tags - vpc_tags = dict((t.name, t.value) for t in vpc_conn.get_all_tags(filters={'resource-id': vpc.id})) - - if not set(vpc_spec_tags.items()).issubset(set(vpc_tags.items())): - new_tags = {} - - for (key, value) in set(vpc_spec_tags.items()): - if (key, value) not in set(vpc_tags.items()): - new_tags[key] = value - - if new_tags: - vpc_conn.create_tags(vpc.id, new_tags) - - # boto doesn't appear to have a way to determine the existing - # value of the dns attributes, so we just set them. - # It also must be done one at a time. - vpc_conn.modify_vpc_attribute(vpc.id, enable_dns_support=dns_support) - vpc_conn.modify_vpc_attribute(vpc.id, enable_dns_hostnames=dns_hostnames) - - # Process all subnet properties - if subnets is not None: - if not isinstance(subnets, list): - module.fail_json(msg='subnets needs to be a list of cidr blocks') - - current_subnets = vpc_conn.get_all_subnets(filters={'vpc_id': vpc.id}) - - # First add all new subnets - for subnet in subnets: - add_subnet = True - subnet_tags_current = True - new_subnet_tags = subnet.get('resource_tags', {}) - subnet_tags_delete = [] - - for csn in current_subnets: - if subnet['cidr'] == csn.cidr_block: - add_subnet = False - - # Check if AWS subnet tags are in playbook subnet tags - existing_tags_subset_of_new_tags = (set(csn.tags.items()).issubset(set(new_subnet_tags.items()))) - # Check if subnet tags in playbook are in AWS subnet tags - new_tags_subset_of_existing_tags = (set(new_subnet_tags.items()).issubset(set(csn.tags.items()))) - - if existing_tags_subset_of_new_tags is False: - try: - for item in csn.tags.items(): - if item not in new_subnet_tags.items(): - subnet_tags_delete.append(item) - - subnet_tags_delete = [key[0] for key in subnet_tags_delete] - delete_subnet_tag = vpc_conn.delete_tags(csn.id, subnet_tags_delete) - changed = True - except EC2ResponseError as e: - module.fail_json(msg='Unable to delete resource tag, error {0}'.format(e)) - # Add new subnet tags if not current - - if new_tags_subset_of_existing_tags is False: - try: - changed = True - create_subnet_tag = vpc_conn.create_tags(csn.id, new_subnet_tags) - - except EC2ResponseError as e: - module.fail_json(msg='Unable to create resource tag, error: {0}'.format(e)) - - if add_subnet: - try: - new_subnet = vpc_conn.create_subnet(vpc.id, subnet['cidr'], subnet.get('az', None)) - new_subnet_tags = subnet.get('resource_tags', {}) - if new_subnet_tags: - # Sometimes AWS takes its time to create a subnet and so using new subnets's id - # to create tags results in exception. - # boto doesn't seem to refresh 'state' of the newly created subnet, i.e.: it's always 'pending' - # so i resorted to polling vpc_conn.get_all_subnets with the id of the newly added subnet - while len(vpc_conn.get_all_subnets(filters={'subnet-id': new_subnet.id})) == 0: - time.sleep(0.1) - - vpc_conn.create_tags(new_subnet.id, new_subnet_tags) - - changed = True - except EC2ResponseError as e: - module.fail_json(msg='Unable to create subnet {0}, error: {1}'.format(subnet['cidr'], e)) - - # Now delete all absent subnets - for csubnet in current_subnets: - delete_subnet = True - for subnet in subnets: - if csubnet.cidr_block == subnet['cidr']: - delete_subnet = False - if delete_subnet: - try: - vpc_conn.delete_subnet(csubnet.id) - changed = True - except EC2ResponseError as e: - module.fail_json(msg='Unable to delete subnet {0}, error: {1}'.format(csubnet.cidr_block, e)) - - # Handle Internet gateway (create/delete igw) - igw = None - igw_id = None - igws = vpc_conn.get_all_internet_gateways(filters={'attachment.vpc-id': vpc.id}) - if len(igws) > 1: - module.fail_json(msg='EC2 returned more than one Internet Gateway for id %s, aborting' % vpc.id) - if internet_gateway: - if len(igws) != 1: - try: - igw = vpc_conn.create_internet_gateway() - vpc_conn.attach_internet_gateway(igw.id, vpc.id) - changed = True - except EC2ResponseError as e: - module.fail_json(msg='Unable to create Internet Gateway, error: {0}'.format(e)) - else: - # Set igw variable to the current igw instance for use in route tables. - igw = igws[0] - else: - if len(igws) > 0: - try: - vpc_conn.detach_internet_gateway(igws[0].id, vpc.id) - vpc_conn.delete_internet_gateway(igws[0].id) - changed = True - except EC2ResponseError as e: - module.fail_json(msg='Unable to delete Internet Gateway, error: {0}'.format(e)) - - if igw is not None: - igw_id = igw.id - - # Handle route tables - this may be worth splitting into a - # different module but should work fine here. The strategy to stay - # idempotent is to basically build all the route tables as - # defined, track the route table ids, and then run through the - # remote list of route tables and delete any that we didn't - # create. This shouldn't interrupt traffic in theory, but is the - # only way to really work with route tables over time that I can - # think of without using painful aws ids. Hopefully boto will add - # the replace-route-table API to make this smoother and - # allow control of the 'main' routing table. - if route_tables is not None: - rtb_needs_change = rtb_changed(route_tables, vpc_conn, module, vpc, igw) - if route_tables is not None and rtb_needs_change: - if not isinstance(route_tables, list): - module.fail_json(msg='route tables need to be a list of dictionaries') - - # Work through each route table and update/create to match dictionary array - all_route_tables = [] - for rt in route_tables: - try: - new_rt = vpc_conn.create_route_table(vpc.id) - new_rt_tags = rt.get('resource_tags', None) - if new_rt_tags: - vpc_conn.create_tags(new_rt.id, new_rt_tags) - for route in rt['routes']: - route_kwargs = {} - if route['gw'] == 'igw': - if not internet_gateway: - module.fail_json( - msg='You asked for an Internet Gateway ' - '(igw) route, but you have no Internet Gateway' - ) - route_kwargs['gateway_id'] = igw.id - elif route['gw'].startswith('i-'): - route_kwargs['instance_id'] = route['gw'] - elif route['gw'].startswith('eni-'): - route_kwargs['interface_id'] = route['gw'] - elif route['gw'].startswith('pcx-'): - route_kwargs['vpc_peering_connection_id'] = route['gw'] - else: - route_kwargs['gateway_id'] = route['gw'] - vpc_conn.create_route(new_rt.id, route['dest'], **route_kwargs) - - # Associate with subnets - for sn in rt['subnets']: - rsn = vpc_conn.get_all_subnets(filters={'cidr': sn, 'vpc_id': vpc.id}) - if len(rsn) != 1: - module.fail_json( - msg='The subnet {0} to associate with route_table {1} ' - 'does not exist, aborting'.format(sn, rt) - ) - rsn = rsn[0] - - # Disassociate then associate since we don't have replace - old_rt = vpc_conn.get_all_route_tables( - filters={'association.subnet_id': rsn.id, 'vpc_id': vpc.id} - ) - old_rt = [x for x in old_rt if x.id is not None] - if len(old_rt) == 1: - old_rt = old_rt[0] - association_id = None - for a in old_rt.associations: - if a.subnet_id == rsn.id: - association_id = a.id - vpc_conn.disassociate_route_table(association_id) - - vpc_conn.associate_route_table(new_rt.id, rsn.id) - - all_route_tables.append(new_rt) - changed = True - except EC2ResponseError as e: - module.fail_json( - msg='Unable to create and associate route table {0}, error: ' - '{1}'.format(rt, e) - ) - - # Now that we are good to go on our new route tables, delete the - # old ones except the 'main' route table as boto can't set the main - # table yet. - all_rts = vpc_conn.get_all_route_tables(filters={'vpc-id': vpc.id}) - for rt in all_rts: - if rt.id is None: - continue - delete_rt = True - for newrt in all_route_tables: - if newrt.id == rt.id: - delete_rt = False - break - if delete_rt: - rta = rt.associations - is_main = False - for a in rta: - if a.main: - is_main = True - break - try: - if not is_main: - vpc_conn.delete_route_table(rt.id) - changed = True - except EC2ResponseError as e: - module.fail_json(msg='Unable to delete old route table {0}, error: {1}'.format(rt.id, e)) - - vpc_dict = get_vpc_info(vpc) - - created_vpc_id = vpc.id - returned_subnets = [] - current_subnets = vpc_conn.get_all_subnets(filters={'vpc_id': vpc.id}) - - for sn in current_subnets: - returned_subnets.append({ - 'resource_tags': dict((t.name, t.value) for t in vpc_conn.get_all_tags(filters={'resource-id': sn.id})), - 'cidr': sn.cidr_block, - 'az': sn.availability_zone, - 'id': sn.id, - }) - - if subnets is not None: - # Sort subnets by the order they were listed in the play - order = {} - for idx, val in enumerate(subnets): - order[val['cidr']] = idx - - # Number of subnets in the play - subnets_in_play = len(subnets) - returned_subnets.sort(key=lambda x: order.get(x['cidr'], subnets_in_play)) - - return (vpc_dict, created_vpc_id, returned_subnets, igw_id, changed) - - -def terminate_vpc(module, vpc_conn, vpc_id=None, cidr=None): - """ - Terminates a VPC - - module: Ansible module object - vpc_conn: authenticated VPCConnection connection object - vpc_id: a vpc id to terminate - cidr: The cidr block of the VPC - can be used in lieu of an ID - - Returns a dictionary of VPC information - about the VPC terminated. - - If the VPC to be terminated is available - "changed" will be set to True. - - """ - vpc_dict = {} - terminated_vpc_id = '' - changed = False - - vpc = find_vpc(module, vpc_conn, vpc_id, cidr) - - if vpc is not None: - if vpc.state == 'available': - terminated_vpc_id = vpc.id - vpc_dict = get_vpc_info(vpc) - try: - subnets = vpc_conn.get_all_subnets(filters={'vpc_id': vpc.id}) - for sn in subnets: - vpc_conn.delete_subnet(sn.id) - - igws = vpc_conn.get_all_internet_gateways( - filters={'attachment.vpc-id': vpc.id} - ) - for igw in igws: - vpc_conn.detach_internet_gateway(igw.id, vpc.id) - vpc_conn.delete_internet_gateway(igw.id) - - rts = vpc_conn.get_all_route_tables(filters={'vpc_id': vpc.id}) - for rt in rts: - rta = rt.associations - is_main = False - for a in rta: - if a.main: - is_main = True - if not is_main: - vpc_conn.delete_route_table(rt.id) - - vpc_conn.delete_vpc(vpc.id) - except EC2ResponseError as e: - module.fail_json( - msg='Unable to delete VPC {0}, error: {1}'.format(vpc.id, e) - ) - changed = True - vpc_dict['state'] = "terminated" - - return (changed, vpc_dict, terminated_vpc_id) - - -def main(): - argument_spec = ec2_argument_spec() - argument_spec.update(dict( - cidr_block=dict(), - instance_tenancy=dict(choices=['default', 'dedicated'], default='default'), - wait=dict(type='bool', default=False), - wait_timeout=dict(default=300), - dns_support=dict(type='bool', default=True), - dns_hostnames=dict(type='bool', default=True), - subnets=dict(type='list'), - vpc_id=dict(), - internet_gateway=dict(type='bool', default=False), - resource_tags=dict(type='dict', required=True), - route_tables=dict(type='list'), - state=dict(choices=['present', 'absent'], default='present'), - ) - ) - - module = AnsibleModule( - argument_spec=argument_spec, - ) - - if not HAS_BOTO: - module.fail_json(msg='boto required for this module') - - state = module.params.get('state') - - region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module) - - # If we have a region specified, connect to its endpoint. - if region: - try: - vpc_conn = connect_to_aws(boto.vpc, region, **aws_connect_kwargs) - except boto.exception.NoAuthHandlerFound as e: - module.fail_json(msg=str(e)) - else: - module.fail_json(msg="region must be specified") - - igw_id = None - if module.params.get('state') == 'absent': - vpc_id = module.params.get('vpc_id') - cidr = module.params.get('cidr_block') - (changed, vpc_dict, new_vpc_id) = terminate_vpc(module, vpc_conn, vpc_id, cidr) - subnets_changed = None - elif module.params.get('state') == 'present': - # Changed is always set to true when provisioning a new VPC - (vpc_dict, new_vpc_id, subnets_changed, igw_id, changed) = create_vpc(module, vpc_conn) - - module.exit_json(changed=changed, vpc_id=new_vpc_id, vpc=vpc_dict, igw_id=igw_id, subnets=subnets_changed) - +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module() diff --git a/lib/ansible/modules/cloud/azure/_azure.py b/lib/ansible/modules/cloud/azure/_azure.py index bc40cb5d26b..1b380ef9c72 100644 --- a/lib/ansible/modules/cloud/azure/_azure.py +++ b/lib/ansible/modules/cloud/azure/_azure.py @@ -19,7 +19,10 @@ short_description: create or terminate a virtual machine in azure description: - Creates or terminates azure instances. When created optionally waits for it to be 'running'. version_added: "1.7" -deprecated: "Use M(azure_rm_virtualmachine) instead." +deprecated: + removed_in: "2.8" + why: Replaced with various dedicated Azure modules. + alternative: M(azure_rm_virtualmachine) options: name: description: diff --git a/lib/ansible/modules/cloud/cloudstack/_cs_nic.py b/lib/ansible/modules/cloud/cloudstack/_cs_nic.py index 5a457ea8a0e..9bc433de466 100644 --- a/lib/ansible/modules/cloud/cloudstack/_cs_nic.py +++ b/lib/ansible/modules/cloud/cloudstack/_cs_nic.py @@ -20,7 +20,10 @@ description: version_added: "2.3" author: - René Moser (@resmo) -deprecated: Deprecated in 2.4. Use M(cs_instance_nic_secondaryip) instead. +deprecated: + removed_in: "2.8" + why: New module created. + alternative: Use M(cs_instance_nic_secondaryip) instead. options: vm: description: diff --git a/lib/ansible/modules/cloud/docker/_docker.py b/lib/ansible/modules/cloud/docker/_docker.py index 152475a54d4..98bf6e1bbc2 100644 --- a/lib/ansible/modules/cloud/docker/_docker.py +++ b/lib/ansible/modules/cloud/docker/_docker.py @@ -18,7 +18,10 @@ DOCUMENTATION = ''' module: docker version_added: "1.4" short_description: manage docker containers -deprecated: In 2.2 use M(docker_container) and M(docker_image) instead. +deprecated: + removed_in: "2.4" + why: Replaced by dedicated modules. + alternative: Use M(docker_container) and M(docker_image) instead. description: - This is the original Ansible module for managing the Docker container life cycle. - NOTE - Additional and newer modules are available. For the latest on orchestrating containers with Ansible @@ -478,1463 +481,7 @@ EXAMPLES = ''' syslog-tag: myservice ''' -import json -import os -import shlex -from ansible.module_utils.six.moves.urllib.parse import urlparse - -try: - import docker.client - import docker.utils - import docker.errors - from requests.exceptions import RequestException - HAS_DOCKER_PY = True -except ImportError: - HAS_DOCKER_PY = False - -DEFAULT_DOCKER_API_VERSION = None -DEFAULT_TIMEOUT_SECONDS = 60 -if HAS_DOCKER_PY: - try: - from docker.errors import APIError as DockerAPIError - except ImportError: - from docker.client import APIError as DockerAPIError - try: - # docker-py 1.2+ - import docker.constants - DEFAULT_DOCKER_API_VERSION = docker.constants.DEFAULT_DOCKER_API_VERSION - DEFAULT_TIMEOUT_SECONDS = docker.constants.DEFAULT_TIMEOUT_SECONDS - except (ImportError, AttributeError): - # docker-py less than 1.2 - DEFAULT_DOCKER_API_VERSION = docker.client.DEFAULT_DOCKER_API_VERSION - DEFAULT_TIMEOUT_SECONDS = docker.client.DEFAULT_TIMEOUT_SECONDS - -from ansible.module_utils.basic import AnsibleModule - - -def _human_to_bytes(number): - suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] - - if isinstance(number, int): - return number - if number.isdigit(): - return int(number) - if number[-1] == suffixes[0] and number[-2].isdigit(): - return number[:-1] - - i = 1 - for each in suffixes[1:]: - if number[-len(each):] == suffixes[i]: - return int(number[:-len(each)]) * (1024 ** i) - i = i + 1 - - raise ValueError('Could not convert %s to integer' % (number,)) - - -def _ansible_facts(container_list): - return {"docker_containers": container_list} - - -def _docker_id_quirk(inspect): - # XXX: some quirk in docker - if 'ID' in inspect: - inspect['Id'] = inspect['ID'] - del inspect['ID'] - return inspect - - -def get_split_image_tag(image): - # If image contains a host or org name, omit that from our check - if '/' in image: - registry, resource = image.rsplit('/', 1) - else: - registry, resource = None, image - - # now we can determine if image has a tag or a digest - for s in ['@', ':']: - if s in resource: - resource, tag = resource.split(s, 1) - if registry: - resource = '/'.join((registry, resource)) - break - else: - tag = "latest" - resource = image - - return resource, tag - - -def normalize_image(image): - """ - Normalize a Docker image name to include the implied :latest tag. - """ - - return ":".join(get_split_image_tag(image)) - - -def is_running(container): - '''Return True if an inspected container is in a state we consider "running."''' - - return container['State']['Running'] is True and not container['State'].get('Ghost', False) - - -def get_docker_py_versioninfo(): - if hasattr(docker, '__version__'): - # a '__version__' attribute was added to the module but not until - # after 0.3.0 was pushed to pypi. If it's there, use it. - version = [] - for part in docker.__version__.split('.'): - try: - version.append(int(part)) - except ValueError: - for idx, char in enumerate(part): - if not char.isdigit(): - nondigit = part[idx:] - digit = part[:idx] - break - if digit: - version.append(int(digit)) - if nondigit: - version.append(nondigit) - elif hasattr(docker.Client, '_get_raw_response_socket'): - # HACK: if '__version__' isn't there, we check for the existence of - # `_get_raw_response_socket` in the docker.Client class, which was - # added in 0.3.0 - version = (0, 3, 0) - else: - # This is untrue but this module does not function with a version less - # than 0.3.0 so it's okay to lie here. - version = (0,) - - return tuple(version) - - -def check_dependencies(module): - """ - Ensure `docker-py` >= 0.3.0 is installed, and call module.fail_json with a - helpful error message if it isn't. - """ - if not HAS_DOCKER_PY: - module.fail_json(msg="`docker-py` doesn't seem to be installed, but is required for the Ansible Docker module.") - else: - versioninfo = get_docker_py_versioninfo() - if versioninfo < (0, 3, 0): - module.fail_json(msg="The Ansible Docker module requires `docker-py` >= 0.3.0.") - - -class DockerManager(object): - - counters = dict( - created=0, started=0, stopped=0, killed=0, removed=0, restarted=0, pulled=0 - ) - reload_reasons = [] - _capabilities = set() - - # Map optional parameters to minimum (docker-py version, server APIVersion) - # docker-py version is a tuple of ints because we have to compare them - # server APIVersion is passed to a docker-py function that takes strings - _cap_ver_req = dict( - devices=((0, 7, 0), '1.2'), - dns=((0, 3, 0), '1.10'), - volumes_from=((0, 3, 0), '1.10'), - restart_policy=((0, 5, 0), '1.14'), - extra_hosts=((0, 7, 0), '1.3.1'), - pid=((1, 0, 0), '1.17'), - log_driver=((1, 2, 0), '1.18'), - log_opt=((1, 2, 0), '1.18'), - host_config=((0, 7, 0), '1.15'), - cpu_set=((0, 6, 0), '1.14'), - cap_add=((0, 5, 0), '1.14'), - cap_drop=((0, 5, 0), '1.14'), - read_only=((1, 0, 0), '1.17'), - labels=((1, 2, 0), '1.18'), - stop_timeout=((0, 5, 0), '1.0'), - ulimits=((1, 2, 0), '1.18'), - # Clientside only - insecure_registry=((0, 5, 0), '0.0'), - env_file=((1, 4, 0), '0.0'), - ) - - def __init__(self, module): - self.module = module - - self.binds = None - self.volumes = None - if self.module.params.get('volumes'): - self.binds = [] - self.volumes = [] - vols = self.module.params.get('volumes') - for vol in vols: - parts = vol.split(":") - # regular volume - if len(parts) == 1: - self.volumes.append(parts[0]) - # host mount (e.g. /mnt:/tmp, bind mounts host's /tmp to /mnt in the container) - elif 2 <= len(parts) <= 3: - # default to read-write - mode = 'rw' - # with supplied bind mode - if len(parts) == 3: - if parts[2] not in ["rw", "rw,Z", "rw,z", "z,rw", "Z,rw", "Z", "z", "ro", "ro,Z", "ro,z", "z,ro", "Z,ro"]: - self.module.fail_json(msg='invalid bind mode ' + parts[2]) - else: - mode = parts[2] - self.binds.append("%s:%s:%s" % (parts[0], parts[1], mode)) - else: - self.module.fail_json(msg='volumes support 1 to 3 arguments') - - self.lxc_conf = None - if self.module.params.get('lxc_conf'): - self.lxc_conf = [] - options = self.module.params.get('lxc_conf') - for option in options: - parts = option.split(':', 1) - self.lxc_conf.append({"Key": parts[0], "Value": parts[1]}) - - self.exposed_ports = None - if self.module.params.get('expose'): - self.exposed_ports = self.get_exposed_ports(self.module.params.get('expose')) - - self.port_bindings = None - if self.module.params.get('ports'): - self.port_bindings = self.get_port_bindings(self.module.params.get('ports')) - - self.links = None - if self.module.params.get('links'): - self.links = self.get_links(self.module.params.get('links')) - - self.ulimits = None - if self.module.params.get('ulimits'): - self.ulimits = [] - ulimits = self.module.params.get('ulimits') - for ulimit in ulimits: - parts = ulimit.split(":") - if len(parts) == 2: - self.ulimits.append({'name': parts[0], 'soft': int(parts[1]), 'hard': int(parts[1])}) - elif len(parts) == 3: - self.ulimits.append({'name': parts[0], 'soft': int(parts[1]), 'hard': int(parts[2])}) - else: - self.module.fail_json(msg='ulimits support 2 to 3 arguments') - - # Connect to the docker server using any configured host and TLS settings. - - env_host = os.getenv('DOCKER_HOST') - env_docker_verify = os.getenv('DOCKER_TLS_VERIFY') - env_cert_path = os.getenv('DOCKER_CERT_PATH') - env_docker_hostname = os.getenv('DOCKER_TLS_HOSTNAME') - - docker_url = module.params.get('docker_url') - if not docker_url: - if env_host: - docker_url = env_host - else: - docker_url = 'unix://var/run/docker.sock' - - docker_api_version = module.params.get('docker_api_version') - timeout = module.params.get('timeout') - - tls_client_cert = module.params.get('tls_client_cert', None) - if not tls_client_cert and env_cert_path: - tls_client_cert = os.path.join(env_cert_path, 'cert.pem') - - tls_client_key = module.params.get('tls_client_key', None) - if not tls_client_key and env_cert_path: - tls_client_key = os.path.join(env_cert_path, 'key.pem') - - tls_ca_cert = module.params.get('tls_ca_cert') - if not tls_ca_cert and env_cert_path: - tls_ca_cert = os.path.join(env_cert_path, 'ca.pem') - - tls_hostname = module.params.get('tls_hostname') - if tls_hostname is None: - if env_docker_hostname: - tls_hostname = env_docker_hostname - else: - parsed_url = urlparse(docker_url) - if ':' in parsed_url.netloc: - tls_hostname = parsed_url.netloc[:parsed_url.netloc.rindex(':')] - else: - tls_hostname = parsed_url - if not tls_hostname: - tls_hostname = True - - # use_tls can be one of four values: - # no: Do not use tls - # encrypt: Use tls. We may do client auth. We will not verify the server - # verify: Use tls. We may do client auth. We will verify the server - # None: Only use tls if the parameters for client auth were specified - # or tls_ca_cert (which requests verifying the server with - # a specific ca certificate) - use_tls = module.params.get('use_tls') - if use_tls is None and env_docker_verify is not None: - use_tls = 'verify' - - tls_config = None - if use_tls != 'no': - params = {} - - # Setup client auth - if tls_client_cert and tls_client_key: - params['client_cert'] = (tls_client_cert, tls_client_key) - - # We're allowed to verify the connection to the server - if use_tls == 'verify' or (use_tls is None and tls_ca_cert): - if tls_ca_cert: - params['ca_cert'] = tls_ca_cert - params['verify'] = True - params['assert_hostname'] = tls_hostname - else: - params['verify'] = True - params['assert_hostname'] = tls_hostname - elif use_tls == 'encrypt': - params['verify'] = False - - if params: - # See https://github.com/docker/docker-py/blob/d39da11/docker/utils/utils.py#L279-L296 - docker_url = docker_url.replace('tcp://', 'https://') - tls_config = docker.tls.TLSConfig(**params) - - self.client = docker.Client(base_url=docker_url, - version=docker_api_version, - tls=tls_config, - timeout=timeout) - - self.docker_py_versioninfo = get_docker_py_versioninfo() - - env = self.module.params.get('env', None) - env_file = self.module.params.get('env_file', None) - self.environment = self.get_environment(env, env_file) - - def _check_capabilities(self): - """ - Create a list of available capabilities - """ - api_version = self.client.version()['ApiVersion'] - for cap, req_vers in self._cap_ver_req.items(): - if (self.docker_py_versioninfo >= req_vers[0] and - docker.utils.compare_version(req_vers[1], api_version) >= 0): - self._capabilities.add(cap) - - def ensure_capability(self, capability, fail=True): - """ - Some of the functionality this ansible module implements are only - available in newer versions of docker. Ensure that the capability - is available here. - - If fail is set to False then return True or False depending on whether - we have the capability. Otherwise, simply fail and exit the module if - we lack the capability. - """ - if not self._capabilities: - self._check_capabilities() - - if capability in self._capabilities: - return True - - if not fail: - return False - - api_version = self.client.version()['ApiVersion'] - self.module.fail_json(msg='Specifying the `%s` parameter requires' - ' docker-py: %s, docker server apiversion %s; found' - ' docker-py: %s, server: %s' % (capability, - '.'.join(map(str, self._cap_ver_req[capability][0])), - self._cap_ver_req[capability][1], - '.'.join(map(str, self.docker_py_versioninfo)), - api_version)) - - def get_environment(self, env, env_file): - """ - If environment files are combined with explicit environment variables, the explicit environment variables will override the key from the env file. - """ - final_env = {} - - if env_file: - self.ensure_capability('env_file') - parsed_env_file = docker.utils.parse_env_file(env_file) - - for name, value in parsed_env_file.items(): - final_env[name] = str(value) - - if env: - for name, value in env.items(): - final_env[name] = str(value) - - return final_env - - def get_links(self, links): - """ - Parse the links passed, if a link is specified without an alias then just create the alias of the same name as the link - """ - processed_links = {} - - for link in links: - parsed_link = link.split(':', 1) - if(len(parsed_link) == 2): - processed_links[parsed_link[0]] = parsed_link[1] - else: - processed_links[parsed_link[0]] = parsed_link[0] - - return processed_links - - def get_exposed_ports(self, expose_list): - """ - Parse the ports and protocols (TCP/UDP) to expose in the docker-py `create_container` call from the docker CLI-style syntax. - """ - if expose_list: - exposed = [] - for port in expose_list: - port = str(port).strip() - if port.endswith('/tcp') or port.endswith('/udp'): - port_with_proto = tuple(port.split('/')) - else: - # assume tcp protocol if not specified - port_with_proto = (port, 'tcp') - exposed.append(port_with_proto) - return exposed - else: - return None - - def get_start_params(self): - """ - Create start params - """ - params = { - 'lxc_conf': self.lxc_conf, - 'binds': self.binds, - 'port_bindings': self.port_bindings, - 'publish_all_ports': self.module.params.get('publish_all_ports'), - 'privileged': self.module.params.get('privileged'), - 'links': self.links, - 'network_mode': self.module.params.get('net'), - } - - optionals = {} - for optional_param in ('devices', 'dns', 'volumes_from', 'restart_policy', 'restart_policy_retry', - 'pid', 'extra_hosts', 'log_driver', 'cap_add', 'cap_drop', 'read_only', 'log_opt'): - optionals[optional_param] = self.module.params.get(optional_param) - - if optionals['devices'] is not None: - self.ensure_capability('devices') - params['devices'] = optionals['devices'] - - if optionals['dns'] is not None: - self.ensure_capability('dns') - params['dns'] = optionals['dns'] - - if optionals['volumes_from'] is not None: - self.ensure_capability('volumes_from') - params['volumes_from'] = optionals['volumes_from'] - - if optionals['restart_policy'] is not None: - self.ensure_capability('restart_policy') - params['restart_policy'] = dict(Name=optionals['restart_policy']) - if params['restart_policy']['Name'] == 'on-failure': - params['restart_policy']['MaximumRetryCount'] = optionals['restart_policy_retry'] - - # docker_py only accepts 'host' or None - if 'pid' in optionals and not optionals['pid']: - optionals['pid'] = None - - if optionals['pid'] is not None: - self.ensure_capability('pid') - params['pid_mode'] = optionals['pid'] - - if optionals['extra_hosts'] is not None: - self.ensure_capability('extra_hosts') - params['extra_hosts'] = optionals['extra_hosts'] - - if optionals['log_driver'] is not None: - 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'].items(): - log_config.set_config_value(k, v) - log_config.type = optionals['log_driver'] - params['log_config'] = log_config - - if optionals['cap_add'] is not None: - self.ensure_capability('cap_add') - params['cap_add'] = optionals['cap_add'] - - if optionals['cap_drop'] is not None: - self.ensure_capability('cap_drop') - params['cap_drop'] = optionals['cap_drop'] - - if optionals['read_only'] is not None: - self.ensure_capability('read_only') - params['read_only'] = optionals['read_only'] - - return params - - def create_host_config(self): - """ - Create HostConfig object - """ - params = self.get_start_params() - return docker.utils.create_host_config(**params) - - def get_port_bindings(self, ports): - """ - Parse the `ports` string into a port bindings dict for the `start_container` call. - """ - binds = {} - for port in ports: - # ports could potentially be an array like [80, 443], so we make sure they're strings - # before splitting - parts = str(port).split(':') - container_port = parts[-1] - if '/' not in container_port: - container_port = int(parts[-1]) - - p_len = len(parts) - if p_len == 1: - # Bind `container_port` of the container to a dynamically - # allocated TCP port on all available interfaces of the host - # machine. - bind = ('0.0.0.0',) - elif p_len == 2: - # Bind `container_port` of the container to port `parts[0]` on - # all available interfaces of the host machine. - bind = ('0.0.0.0', int(parts[0])) - elif p_len == 3: - # Bind `container_port` of the container to port `parts[1]` on - # IP `parts[0]` of the host machine. If `parts[1]` empty bind - # to a dynamically allocated port of IP `parts[0]`. - bind = (parts[0], int(parts[1])) if parts[1] else (parts[0],) - - if container_port in binds: - old_bind = binds[container_port] - if isinstance(old_bind, list): - # append to list if it already exists - old_bind.append(bind) - else: - # otherwise create list that contains the old and new binds - binds[container_port] = [binds[container_port], bind] - else: - binds[container_port] = bind - - return binds - - def get_summary_message(self): - ''' - Generate a message that briefly describes the actions taken by this - task, in English. - ''' - - parts = [] - for k, v in self.counters.items(): - if v == 0: - continue - - if v == 1: - plural = "" - else: - plural = "s" - parts.append("%s %d container%s" % (k, v, plural)) - - if parts: - return ", ".join(parts) + "." - else: - return "No action taken." - - def get_reload_reason_message(self): - ''' - Generate a message describing why any reloaded containers were reloaded. - ''' - - if self.reload_reasons: - return ", ".join(self.reload_reasons) - else: - return None - - def get_summary_counters_msg(self): - msg = "" - for k, v in self.counters.items(): - msg = msg + "%s %d " % (k, v) - - return msg - - def increment_counter(self, name): - self.counters[name] = self.counters[name] + 1 - - def has_changed(self): - for k, v in self.counters.items(): - if v > 0: - return True - - return False - - def get_inspect_image(self): - try: - return self.client.inspect_image(self.module.params.get('image')) - except DockerAPIError as e: - if e.response.status_code == 404: - return None - else: - raise e - - def get_image_repo_tags(self): - image, tag = get_split_image_tag(self.module.params.get('image')) - if tag is None: - tag = 'latest' - resource = '%s:%s' % (image, tag) - - for image in self.client.images(name=image): - if resource in image.get('RepoTags', []): - return image['RepoTags'] - return [] - - def get_inspect_containers(self, containers): - inspect = [] - for i in containers: - details = self.client.inspect_container(i['Id']) - details = _docker_id_quirk(details) - inspect.append(details) - - return inspect - - def get_differing_containers(self): - """ - Inspect all matching, running containers, and return those that were - started with parameters that differ from the ones that are provided - during this module run. A list containing the differing - containers will be returned, and a short string describing the specific - difference encountered in each container will be appended to - reload_reasons. - - This generates the set of containers that need to be stopped and - started with new parameters with state=reloaded. - """ - - running = self.get_running_containers() - current = self.get_inspect_containers(running) - defaults = self.client.info() - - # Get API version - api_version = self.client.version()['ApiVersion'] - - image = self.get_inspect_image() - if image is None: - # The image isn't present. Assume that we're about to pull a new - # tag and *everything* will be restarted. - # - # This will give false positives if you untag an image on the host - # and there's nothing more to pull. - return current - - differing = [] - - for container in current: - - # IMAGE - # Compare the image by ID rather than name, so that containers - # will be restarted when new versions of an existing image are - # pulled. - if container['Image'] != image['Id']: - self.reload_reasons.append('image ({0} => {1})'.format(container['Image'], image['Id'])) - differing.append(container) - continue - - # ENTRYPOINT - - expected_entrypoint = self.module.params.get('entrypoint') - if expected_entrypoint: - expected_entrypoint = shlex.split(expected_entrypoint) - actual_entrypoint = container["Config"]["Entrypoint"] - - if actual_entrypoint != expected_entrypoint: - self.reload_reasons.append( - 'entrypoint ({0} => {1})' - .format(actual_entrypoint, expected_entrypoint) - ) - differing.append(container) - continue - - # COMMAND - - expected_command = self.module.params.get('command') - if expected_command: - expected_command = shlex.split(expected_command) - actual_command = container["Config"]["Cmd"] - - if actual_command != expected_command: - self.reload_reasons.append('command ({0} => {1})'.format(actual_command, expected_command)) - differing.append(container) - continue - - # EXPOSED PORTS - expected_exposed_ports = set((image['ContainerConfig'].get('ExposedPorts') or {}).keys()) - for p in (self.exposed_ports or []): - expected_exposed_ports.add("/".join(p)) - - actually_exposed_ports = set((container["Config"].get("ExposedPorts") or {}).keys()) - - if actually_exposed_ports != expected_exposed_ports: - self.reload_reasons.append('exposed_ports ({0} => {1})'.format(actually_exposed_ports, expected_exposed_ports)) - differing.append(container) - continue - - # VOLUMES - - expected_volume_keys = set((image['ContainerConfig']['Volumes'] or {}).keys()) - if self.volumes: - expected_volume_keys.update(self.volumes) - - actual_volume_keys = set((container['Config']['Volumes'] or {}).keys()) - - if actual_volume_keys != expected_volume_keys: - self.reload_reasons.append('volumes ({0} => {1})'.format(actual_volume_keys, expected_volume_keys)) - differing.append(container) - continue - - # ULIMITS - - expected_ulimit_keys = set(map(lambda x: '%s:%s:%s' % (x['name'], x['soft'], x['hard']), self.ulimits or [])) - actual_ulimit_keys = set(map(lambda x: '%s:%s:%s' % (x['Name'], x['Soft'], x['Hard']), (container['HostConfig']['Ulimits'] or []))) - - if actual_ulimit_keys != expected_ulimit_keys: - self.reload_reasons.append('ulimits ({0} => {1})'.format(actual_ulimit_keys, expected_ulimit_keys)) - differing.append(container) - continue - - # CPU_SHARES - - expected_cpu_shares = self.module.params.get('cpu_shares') - actual_cpu_shares = container['HostConfig']['CpuShares'] - - if expected_cpu_shares and actual_cpu_shares != expected_cpu_shares: - self.reload_reasons.append('cpu_shares ({0} => {1})'.format(actual_cpu_shares, expected_cpu_shares)) - differing.append(container) - continue - - # MEM_LIMIT - - try: - expected_mem = _human_to_bytes(self.module.params.get('memory_limit')) - except ValueError as e: - self.module.fail_json(msg=str(e)) - - # For v1.19 API and above use HostConfig, otherwise use Config - if docker.utils.compare_version('1.19', api_version) >= 0: - actual_mem = container['HostConfig']['Memory'] - else: - actual_mem = container['Config']['Memory'] - - if expected_mem and actual_mem != expected_mem: - self.reload_reasons.append('memory ({0} => {1})'.format(actual_mem, expected_mem)) - differing.append(container) - continue - - # ENVIRONMENT - # actual_env is likely to include environment variables injected by - # the Dockerfile. - - expected_env = {} - - for image_env in image['ContainerConfig']['Env'] or []: - name, value = image_env.split('=', 1) - expected_env[name] = value - - if self.environment: - for name, value in self.environment.items(): - expected_env[name] = str(value) - - actual_env = {} - for container_env in container['Config']['Env'] or []: - name, value = container_env.split('=', 1) - actual_env[name] = value - - if actual_env != expected_env: - # Don't include the environment difference in the output. - self.reload_reasons.append('environment {0} => {1}'.format(actual_env, expected_env)) - differing.append(container) - continue - - # LABELS - - expected_labels = {} - for name, value in self.module.params.get('labels').items(): - expected_labels[name] = str(value) - - if isinstance(container['Config']['Labels'], dict): - actual_labels = container['Config']['Labels'] - else: - for container_label in container['Config']['Labels'] or []: - name, value = container_label.split('=', 1) - actual_labels[name] = value - - if actual_labels != expected_labels: - self.reload_reasons.append('labels {0} => {1}'.format(actual_labels, expected_labels)) - differing.append(container) - continue - - # HOSTNAME - - expected_hostname = self.module.params.get('hostname') - actual_hostname = container['Config']['Hostname'] - if expected_hostname and actual_hostname != expected_hostname: - self.reload_reasons.append('hostname ({0} => {1})'.format(actual_hostname, expected_hostname)) - differing.append(container) - continue - - # DOMAINNAME - - expected_domainname = self.module.params.get('domainname') - actual_domainname = container['Config']['Domainname'] - if expected_domainname and actual_domainname != expected_domainname: - self.reload_reasons.append('domainname ({0} => {1})'.format(actual_domainname, expected_domainname)) - differing.append(container) - continue - - # DETACH - - # We don't have to check for undetached containers. If it wasn't - # detached, it would have stopped before the playbook continued! - - # NAME - - # We also don't have to check name, because this is one of the - # criteria that's used to determine which container(s) match in - # the first place. - - # STDIN_OPEN - - expected_stdin_open = self.module.params.get('stdin_open') - actual_stdin_open = container['Config']['OpenStdin'] - if actual_stdin_open != expected_stdin_open: - self.reload_reasons.append('stdin_open ({0} => {1})'.format(actual_stdin_open, expected_stdin_open)) - differing.append(container) - continue - - # TTY - - expected_tty = self.module.params.get('tty') - actual_tty = container['Config']['Tty'] - if actual_tty != expected_tty: - self.reload_reasons.append('tty ({0} => {1})'.format(actual_tty, expected_tty)) - differing.append(container) - continue - - # -- "start" call differences -- - - # LXC_CONF - - if self.lxc_conf: - expected_lxc = set(self.lxc_conf) - actual_lxc = set(container['HostConfig']['LxcConf'] or []) - if actual_lxc != expected_lxc: - self.reload_reasons.append('lxc_conf ({0} => {1})'.format(actual_lxc, expected_lxc)) - differing.append(container) - continue - - # BINDS - - expected_binds = set() - if self.binds: - for bind in self.binds: - expected_binds.add(bind) - - actual_binds = set() - for bind in (container['HostConfig']['Binds'] or []): - if len(bind.split(':')) == 2: - actual_binds.add(bind + ":rw") - else: - actual_binds.add(bind) - - if actual_binds != expected_binds: - self.reload_reasons.append('binds ({0} => {1})'.format(actual_binds, expected_binds)) - differing.append(container) - continue - - # PORT BINDINGS - - expected_bound_ports = {} - if self.port_bindings: - 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: - expected_bound_ports[container_port] = [dict(HostIp="0.0.0.0", HostPort="")] - elif isinstance(config[0], tuple): - expected_bound_ports[container_port] = [] - for hostip, hostport in config: - expected_bound_ports[container_port].append(dict(HostIp=hostip, HostPort=str(hostport))) - else: - expected_bound_ports[container_port] = [{'HostIp': config[0], 'HostPort': str(config[1])}] - - actual_bound_ports = container['HostConfig']['PortBindings'] or {} - - if actual_bound_ports != expected_bound_ports: - self.reload_reasons.append('port bindings ({0} => {1})'.format(actual_bound_ports, expected_bound_ports)) - differing.append(container) - continue - - # PUBLISHING ALL PORTS - - # What we really care about is the set of ports that is actually - # published. That should be caught above. - - # PRIVILEGED - - expected_privileged = self.module.params.get('privileged') - actual_privileged = container['HostConfig']['Privileged'] - if actual_privileged != expected_privileged: - self.reload_reasons.append('privileged ({0} => {1})'.format(actual_privileged, expected_privileged)) - differing.append(container) - continue - - # LINKS - - expected_links = set() - for link, alias in (self.links or {}).items(): - expected_links.add("/{0}:{1}/{2}".format(link, container["Name"], alias)) - - actual_links = set() - for link in (container['HostConfig']['Links'] or []): - actual_links.add(link) - - if actual_links != expected_links: - self.reload_reasons.append('links ({0} => {1})'.format(actual_links, expected_links)) - differing.append(container) - continue - - # NETWORK MODE - - expected_netmode = self.module.params.get('net') or 'bridge' - actual_netmode = container['HostConfig']['NetworkMode'] or 'bridge' - if actual_netmode != expected_netmode: - self.reload_reasons.append('net ({0} => {1})'.format(actual_netmode, expected_netmode)) - differing.append(container) - continue - - # DEVICES - - expected_devices = set() - for device in (self.module.params.get('devices') or []): - if len(device.split(':')) == 2: - expected_devices.add(device + ":rwm") - else: - expected_devices.add(device) - - actual_devices = set() - for device in (container['HostConfig']['Devices'] or []): - actual_devices.add("{PathOnHost}:{PathInContainer}:{CgroupPermissions}".format(**device)) - - if actual_devices != expected_devices: - self.reload_reasons.append('devices ({0} => {1})'.format(actual_devices, expected_devices)) - differing.append(container) - continue - - # DNS - - expected_dns = set(self.module.params.get('dns') or []) - actual_dns = set(container['HostConfig']['Dns'] or []) - if actual_dns != expected_dns: - self.reload_reasons.append('dns ({0} => {1})'.format(actual_dns, expected_dns)) - differing.append(container) - continue - - # VOLUMES_FROM - - expected_volumes_from = set(self.module.params.get('volumes_from') or []) - actual_volumes_from = set(container['HostConfig']['VolumesFrom'] or []) - if actual_volumes_from != expected_volumes_from: - self.reload_reasons.append('volumes_from ({0} => {1})'.format(actual_volumes_from, expected_volumes_from)) - differing.append(container) - - # LOG_DRIVER - - if self.ensure_capability('log_driver', False): - expected_log_driver = self.module.params.get('log_driver') or defaults['LoggingDriver'] - actual_log_driver = container['HostConfig']['LogConfig']['Type'] - if actual_log_driver != expected_log_driver: - self.reload_reasons.append('log_driver ({0} => {1})'.format(actual_log_driver, expected_log_driver)) - differing.append(container) - continue - - if self.ensure_capability('log_opt', False): - expected_logging_opts = self.module.params.get('log_opt') or {} - actual_log_opts = container['HostConfig']['LogConfig']['Config'] - if len(set(expected_logging_opts.items()) - set(actual_log_opts.items())) != 0: - log_opt_reasons = { - 'added': dict(set(expected_logging_opts.items()) - set(actual_log_opts.items())), - 'removed': dict(set(actual_log_opts.items()) - set(expected_logging_opts.items())) - } - self.reload_reasons.append('log_opt ({0})'.format(log_opt_reasons)) - differing.append(container) - - return differing - - def get_deployed_containers(self): - """ - Return any matching containers that are already present. - """ - - entrypoint = self.module.params.get('entrypoint') - if entrypoint is not None: - entrypoint = shlex.split(entrypoint) - command = self.module.params.get('command') - if command is not None: - command = shlex.split(command) - name = self.module.params.get('name') - if name and not name.startswith('/'): - name = '/' + name - deployed = [] - - # "images" will be a collection of equivalent "name:tag" image names - # that map to the same Docker image. - inspected = self.get_inspect_image() - if inspected: - repo_tags = self.get_image_repo_tags() - else: - repo_tags = [normalize_image(self.module.params.get('image'))] - - for container in self.client.containers(all=True): - details = None - - if name: - name_list = container.get('Names') - if name_list is None: - name_list = [] - matches = name in name_list - else: - details = self.client.inspect_container(container['Id']) - details = _docker_id_quirk(details) - - running_image = normalize_image(details['Config']['Image']) - - image_matches = running_image in repo_tags - - if command is None: - command_matches = True - else: - command_matches = (command == details['Config']['Cmd']) - - if entrypoint is None: - entrypoint_matches = True - else: - entrypoint_matches = ( - entrypoint == details['Config']['Entrypoint'] - ) - - matches = (image_matches and command_matches and - entrypoint_matches) - - if matches: - if not details: - details = self.client.inspect_container(container['Id']) - details = _docker_id_quirk(details) - - deployed.append(details) - - return deployed - - def get_running_containers(self): - return [c for c in self.get_deployed_containers() if is_running(c)] - - def pull_image(self): - extra_params = {} - if self.module.params.get('insecure_registry'): - if self.ensure_capability('insecure_registry', fail=False): - extra_params['insecure_registry'] = self.module.params.get('insecure_registry') - - resource = self.module.params.get('image') - image, tag = get_split_image_tag(resource) - if self.module.params.get('username'): - try: - self.client.login( - self.module.params.get('username'), - password=self.module.params.get('password'), - email=self.module.params.get('email'), - registry=self.module.params.get('registry') - ) - except Exception as e: - self.module.fail_json(msg="failed to login to the remote registry, check your username/password.", error=repr(e)) - try: - changes = list(self.client.pull(image, tag=tag, stream=True, **extra_params)) - pull_success = False - for change in changes: - status = json.loads(change).get('status', '') - if status.startswith('Status: Image is up to date for'): - # Image is already up to date. Don't increment the counter. - pull_success = True - break - elif (status.startswith('Status: Downloaded newer image for') or - status.startswith('Download complete')): - # Image was updated. Increment the pull counter. - self.increment_counter('pulled') - pull_success = True - break - if not pull_success: - # Unrecognized status string. - self.module.fail_json(msg="Unrecognized status from pull.", status=status, changes=changes) - except Exception as e: - self.module.fail_json(msg="Failed to pull the specified image: %s" % resource, error=repr(e)) - - def create_containers(self, count=1): - try: - mem_limit = _human_to_bytes(self.module.params.get('memory_limit')) - except ValueError as e: - self.module.fail_json(msg=str(e)) - api_version = self.client.version()['ApiVersion'] - - params = dict( - image=self.module.params.get('image'), - entrypoint=self.module.params.get('entrypoint'), - command=self.module.params.get('command'), - ports=self.exposed_ports, - volumes=self.volumes, - environment=self.environment, - labels=self.module.params.get('labels'), - hostname=self.module.params.get('hostname'), - domainname=self.module.params.get('domainname'), - detach=self.module.params.get('detach'), - name=self.module.params.get('name'), - stdin_open=self.module.params.get('stdin_open'), - tty=self.module.params.get('tty'), - cpuset=self.module.params.get('cpu_set'), - cpu_shares=self.module.params.get('cpu_shares'), - user=self.module.params.get('docker_user'), - ) - if self.ensure_capability('host_config', fail=False): - params['host_config'] = self.create_host_config() - - # For v1.19 API and above use HostConfig, otherwise use Config - if docker.utils.compare_version('1.19', api_version) < 0: - params['mem_limit'] = mem_limit - else: - params['host_config']['Memory'] = mem_limit - - if self.ulimits is not None: - self.ensure_capability('ulimits') - params['host_config']['ulimits'] = self.ulimits - - def do_create(count, params): - results = [] - for _ in range(count): - result = self.client.create_container(**params) - self.increment_counter('created') - results.append(result) - - return results - - try: - containers = do_create(count, params) - except docker.errors.APIError as e: - if e.response.status_code != 404: - raise - - self.pull_image() - containers = do_create(count, params) - - return containers - - def start_containers(self, containers): - params = {} - - if not self.ensure_capability('host_config', fail=False): - params = self.get_start_params() - - for i in containers: - self.client.start(i) - self.increment_counter('started') - - if not self.module.params.get('detach'): - status = self.client.wait(i['Id']) - if status != 0: - output = self.client.logs(i['Id'], stdout=True, stderr=True, - stream=False, timestamps=False) - self.module.fail_json(status=status, msg=output) - - def stop_containers(self, containers): - for i in containers: - self.client.stop(i['Id'], self.module.params.get('stop_timeout')) - self.increment_counter('stopped') - - return [self.client.wait(i['Id']) for i in containers] - - def remove_containers(self, containers): - for i in containers: - self.client.remove_container(i['Id']) - self.increment_counter('removed') - - def kill_containers(self, containers): - for i in containers: - self.client.kill(i['Id'], self.module.params.get('signal')) - self.increment_counter('killed') - - def restart_containers(self, containers): - for i in containers: - self.client.restart(i['Id']) - self.increment_counter('restarted') - - -class ContainerSet: - - def __init__(self, manager): - self.manager = manager - self.running = [] - self.deployed = [] - self.changed = [] - - def refresh(self): - ''' - Update our view of the matching containers from the Docker daemon. - ''' - - self.deployed = self.manager.get_deployed_containers() - self.running = [c for c in self.deployed if is_running(c)] - - def notice_changed(self, containers): - ''' - Record a collection of containers as "changed". - ''' - - self.changed.extend(containers) - - -def present(manager, containers, count, name): - '''Ensure that exactly `count` matching containers exist in any state.''' - - containers.refresh() - delta = count - len(containers.deployed) - - if delta > 0: - created = manager.create_containers(delta) - containers.notice_changed(manager.get_inspect_containers(created)) - - if delta < 0: - # If both running and stopped containers exist, remove - # stopped containers first. - # Use key param for python 2/3 compatibility. - containers.deployed.sort(key=is_running) - - to_stop = [] - to_remove = [] - for c in containers.deployed[0:-delta]: - if is_running(c): - to_stop.append(c) - to_remove.append(c) - - manager.stop_containers(to_stop) - containers.notice_changed(manager.get_inspect_containers(to_remove)) - manager.remove_containers(to_remove) - - -def started(manager, containers, count, name): - '''Ensure that exactly `count` matching containers exist and are running.''' - - containers.refresh() - delta = count - len(containers.running) - - if delta > 0: - if name and containers.deployed: - # A stopped container exists with the requested name. - # Clean it up before attempting to start a new one. - manager.remove_containers(containers.deployed) - - created = manager.create_containers(delta) - manager.start_containers(created) - containers.notice_changed(manager.get_inspect_containers(created)) - - if delta < 0: - excess = containers.running[0:-delta] - containers.notice_changed(manager.get_inspect_containers(excess)) - manager.stop_containers(excess) - manager.remove_containers(excess) - - -def reloaded(manager, containers, count, name): - ''' - Ensure that exactly `count` matching containers exist and are - running. If any associated settings have been changed (volumes, - ports or so on), restart those containers. - ''' - - containers.refresh() - - for container in manager.get_differing_containers(): - manager.stop_containers([container]) - manager.remove_containers([container]) - - started(manager, containers, count, name) - - -def restarted(manager, containers, count, name): - ''' - Ensure that exactly `count` matching containers exist and are - running. Unconditionally restart any that were already running. - ''' - - containers.refresh() - - for container in manager.get_differing_containers(): - manager.stop_containers([container]) - manager.remove_containers([container]) - - containers.refresh() - - manager.restart_containers(containers.running) - started(manager, containers, count, name) - - -def stopped(manager, containers, count, name): - '''Stop any matching containers that are running.''' - - containers.refresh() - - manager.stop_containers(containers.running) - containers.notice_changed(manager.get_inspect_containers(containers.running)) - - -def killed(manager, containers, count, name): - '''Kill any matching containers that are running.''' - - containers.refresh() - - manager.kill_containers(containers.running) - containers.notice_changed(manager.get_inspect_containers(containers.running)) - - -def absent(manager, containers, count, name): - '''Stop and remove any matching containers.''' - - containers.refresh() - - manager.stop_containers(containers.running) - containers.notice_changed(manager.get_inspect_containers(containers.deployed)) - manager.remove_containers(containers.deployed) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - count=dict(type='int', default=1), - image=dict(type='str', required=True), - pull=dict(ttpe='str', default='missing', choices=['always', 'missing']), - entrypoint=dict(type='str'), - command=dict(type='str'), - expose=dict(type='list'), - ports=dict(type='list'), - publish_all_ports=dict(type='bool', default=False), - volumes=dict(type='list'), - volumes_from=dict(type='list'), - links=dict(type='list'), - devices=dict(type='list'), - memory_limit=dict(type='str', default=0), - memory_swap=dict(type='int', default=0), - cpu_shares=dict(type='int', default=0), - docker_url=dict(type='str'), - use_tls=dict(type='str', choices=['encrypt', 'no', 'verify']), - tls_client_cert=dict(type='path'), - tls_client_key=dict(type='path'), - tls_ca_cert=dict(type='path'), - tls_hostname=dict(type='str'), - docker_api_version=dict(type='str', default=DEFAULT_DOCKER_API_VERSION), - docker_user=dict(type='str'), - username=dict(type='str',), - password=dict(type='str', no_log=True), - email=dict(type='str'), - registry=dict(type='str'), - hostname=dict(type='str'), - domainname=dict(type='str'), - env=dict(type='dict'), - env_file=dict(type='str'), - dns=dict(type='list'), - detach=dict(type='bool', default=True), - state=dict(type='str', default='started', choices=['absent', 'killed', 'present', 'reloaded', 'restarted', 'running', 'started', 'stopped']), - signal=dict(type='str'), - restart_policy=dict(type='str', choices=['always', 'no', 'on-failure', 'unless-stopped']), - restart_policy_retry=dict(type='int', default=0), - extra_hosts=dict(type='dict'), - debug=dict(type='bool', default=False), - privileged=dict(type='bool', default=False), - stdin_open=dict(type='bool', default=False), - tty=dict(type='bool', default=False), - lxc_conf=dict(type='list'), - name=dict(type='str'), - net=dict(type='str'), - pid=dict(type='str'), - insecure_registry=dict(type='bool', default=False), - log_driver=dict(type='str', choices=['awslogs', 'fluentd', 'gelf', 'journald', 'json-file', 'none', 'syslog']), - log_opt=dict(type='dict'), - cpu_set=dict(type='str'), - cap_add=dict(type='list'), - cap_drop=dict(type='list'), - read_only=dict(type='bool'), - labels=dict(type='dict', default={}), - stop_timeout=dict(type='int', default=10), - timeout=dict(type='int', default=DEFAULT_TIMEOUT_SECONDS), - ulimits=dict(type='list'), - ), - required_together=( - ['tls_client_cert', 'tls_client_key'], - ), - ) - - check_dependencies(module) - - try: - manager = DockerManager(module) - count = module.params.get('count') - name = module.params.get('name') - pull = module.params.get('pull') - - state = module.params.get('state') - if state == 'running': - # Renamed running to started in 1.9 - state = 'started' - - if count < 0: - module.fail_json(msg="Count must be greater than zero") - - if count > 1 and name: - module.fail_json(msg="Count and name must not be used together") - - # Explicitly pull new container images, if requested. Do this before - # noticing running and deployed containers so that the image names - # will differ if a newer image has been pulled. - # Missing images should be pulled first to avoid downtime when old - # container is stopped, but image for new one is now downloaded yet. - # It also prevents removal of running container before realizing - # that requested image cannot be retrieved. - if pull == "always" or (state == 'reloaded' and manager.get_inspect_image() is None): - manager.pull_image() - - containers = ContainerSet(manager) - - if state == 'present': - present(manager, containers, count, name) - elif state == 'started': - started(manager, containers, count, name) - elif state == 'reloaded': - reloaded(manager, containers, count, name) - elif state == 'restarted': - restarted(manager, containers, count, name) - elif state == 'stopped': - stopped(manager, containers, count, name) - elif state == 'killed': - killed(manager, containers, count, name) - elif state == 'absent': - absent(manager, containers, count, name) - else: - module.fail_json(msg='Unrecognized state %s. Must be one of: ' - 'present; started; reloaded; restarted; ' - 'stopped; killed; absent.' % state) - - module.exit_json(changed=manager.has_changed(), - msg=manager.get_summary_message(), - summary=manager.counters, - reload_reasons=manager.get_reload_reason_message(), - ansible_facts=_ansible_facts(containers.changed)) - - except DockerAPIError as e: - module.fail_json(changed=manager.has_changed(), msg="Docker API Error: %s" % e.explanation) - - except RequestException as e: - module.fail_json(changed=manager.has_changed(), msg=repr(e)) - +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module() diff --git a/lib/ansible/modules/clustering/k8s/_kubernetes.py b/lib/ansible/modules/clustering/k8s/_kubernetes.py index 41063994822..5e57e69e71b 100755 --- a/lib/ansible/modules/clustering/k8s/_kubernetes.py +++ b/lib/ansible/modules/clustering/k8s/_kubernetes.py @@ -14,7 +14,10 @@ DOCUMENTATION = ''' --- module: kubernetes version_added: "2.1" -deprecated: In 2.5 use M(k8s_raw) instead. +deprecated: + removed_in: "2.9" + why: This module used the oc command line tool, where as M(k8s_raw) goes over the REST API. + alternative: Use M(k8s_raw) instead. short_description: Manage Kubernetes resources description: - This module can manage Kubernetes resources on an existing cluster using diff --git a/lib/ansible/modules/clustering/openshift/_oc.py b/lib/ansible/modules/clustering/openshift/_oc.py index c76ae8ac5b2..3dd8d5e1ee1 100644 --- a/lib/ansible/modules/clustering/openshift/_oc.py +++ b/lib/ansible/modules/clustering/openshift/_oc.py @@ -17,7 +17,10 @@ ANSIBLE_METADATA = { DOCUMENTATION = """ author: - "Kenneth D. Evensen (@kevensen)" -deprecated: In 2.5 use M(openshift_raw) instead. +deprecated: + removed_in: "2.9" + why: This module used the oc command line tool, where as M(openshift_raw) goes over the REST API. + alternative: Use M(openshift_raw) instead. description: - This module allows management of resources in an OpenShift cluster. The inventory host can be any host with network connectivity to the OpenShift diff --git a/lib/ansible/modules/network/citrix/_netscaler.py b/lib/ansible/modules/network/citrix/_netscaler.py index c069d6cd245..b8bf36bf347 100644 --- a/lib/ansible/modules/network/citrix/_netscaler.py +++ b/lib/ansible/modules/network/citrix/_netscaler.py @@ -18,7 +18,10 @@ version_added: "1.1" short_description: Manages Citrix NetScaler entities description: - Manages Citrix NetScaler server and service entities. -deprecated: In 2.4 use M(netscaler_service) and M(netscaler_server) instead. +deprecated: + removed_in: "2.8" + why: Replaced with Citrix maintained version. + alternative: Use M(netscaler_service) and M(netscaler_server) instead. options: nsc_host: description: diff --git a/lib/ansible/modules/network/cumulus/_cl_bond.py b/lib/ansible/modules/network/cumulus/_cl_bond.py index d2db8306aff..fed468d30d6 100644 --- a/lib/ansible/modules/network/cumulus/_cl_bond.py +++ b/lib/ansible/modules/network/cumulus/_cl_bond.py @@ -19,7 +19,10 @@ module: cl_bond version_added: "2.1" author: "Cumulus Networks (@CumulusNetworks)" short_description: Configures a bond port on Cumulus Linux -deprecated: Deprecated in 2.3. Use M(nclu) instead. +deprecated: + removed_in: "2.5" + why: The M(nclu) module is designed to be easier to use for individuals who are new to Cumulus Linux by exposing the NCLU interface in an automatable way. + alternative: Use M(nclu) instead. description: - Configures a bond interface on Cumulus Linux To configure a bridge port use the cl_bridge module. To configure any other type of interface use the @@ -209,281 +212,7 @@ msg: sample: "interface bond0 config updated" ''' -import os -import re -import tempfile - -from ansible.module_utils.basic import AnsibleModule - - -# handy helper for calling system calls. -# calls AnsibleModule.run_command and prints a more appropriate message -# exec_path - path to file to execute, with all its arguments. -# E.g "/sbin/ip -o link show" -# failure_msg - what message to print on failure -def run_cmd(module, exec_path): - (_rc, out, _err) = module.run_command(exec_path) - if _rc > 0: - if re.search('cannot find interface', _err): - return '[{}]' - failure_msg = "Failed; %s Error: %s" % (exec_path, _err) - module.fail_json(msg=failure_msg) - else: - return out - - -def current_iface_config(module): - # due to a bug in ifquery, have to check for presence of interface file - # and not rely solely on ifquery. when bug is fixed, this check can be - # removed - _ifacename = module.params.get('name') - _int_dir = module.params.get('location') - module.custom_current_config = {} - if os.path.exists(_int_dir + '/' + _ifacename): - _cmd = "/sbin/ifquery -o json %s" % (module.params.get('name')) - module.custom_current_config = module.from_json( - run_cmd(module, _cmd))[0] - - -def build_address(module): - # if addr_method == 'dhcp', don't add IP address - if module.params.get('addr_method') == 'dhcp': - return - _ipv4 = module.params.get('ipv4') - _ipv6 = module.params.get('ipv6') - _addresslist = [] - if _ipv4 and len(_ipv4) > 0: - _addresslist += _ipv4 - - if _ipv6 and len(_ipv6) > 0: - _addresslist += _ipv6 - if len(_addresslist) > 0: - module.custom_desired_config['config']['address'] = ' '.join( - _addresslist) - - -def build_vids(module): - _vids = module.params.get('vids') - if _vids and len(_vids) > 0: - module.custom_desired_config['config']['bridge-vids'] = ' '.join(_vids) - - -def build_pvid(module): - _pvid = module.params.get('pvid') - if _pvid: - module.custom_desired_config['config']['bridge-pvid'] = str(_pvid) - - -def conv_bool_to_str(_value): - if isinstance(_value, bool): - if _value is True: - return 'yes' - else: - return 'no' - return _value - - -def conv_array_to_str(_value): - if isinstance(_value, list): - return ' '.join(_value) - return _value - - -def build_generic_attr(module, _attr): - _value = module.params.get(_attr) - _value = conv_bool_to_str(_value) - _value = conv_array_to_str(_value) - if _value: - module.custom_desired_config['config'][ - re.sub('_', '-', _attr)] = str(_value) - - -def build_alias_name(module): - alias_name = module.params.get('alias_name') - if alias_name: - module.custom_desired_config['config']['alias'] = alias_name - - -def build_addr_method(module): - _addr_method = module.params.get('addr_method') - if _addr_method: - module.custom_desired_config['addr_family'] = 'inet' - module.custom_desired_config['addr_method'] = _addr_method - - -def build_vrr(module): - _virtual_ip = module.params.get('virtual_ip') - _virtual_mac = module.params.get('virtual_mac') - vrr_config = [] - if _virtual_ip: - vrr_config.append(_virtual_mac) - vrr_config.append(_virtual_ip) - module.custom_desired_config.get('config')['address-virtual'] = \ - ' '.join(vrr_config) - - -def add_glob_to_array(_bondmems): - """ - goes through each bond member if it sees a dash add glob - before it - """ - result = [] - if isinstance(_bondmems, list): - for _entry in _bondmems: - if re.search('-', _entry): - _entry = 'glob ' + _entry - result.append(_entry) - return ' '.join(result) - return _bondmems - - -def build_bond_attr(module, _attr): - _value = module.params.get(_attr) - _value = conv_bool_to_str(_value) - _value = add_glob_to_array(_value) - if _value: - module.custom_desired_config['config'][ - 'bond-' + re.sub('_', '-', _attr)] = str(_value) - - -def build_desired_iface_config(module): - """ - take parameters defined and build ifupdown2 compatible hash - """ - module.custom_desired_config = { - 'addr_family': None, - 'auto': True, - 'config': {}, - 'name': module.params.get('name') - } - - for _attr in ['slaves', 'mode', 'xmit_hash_policy', - 'miimon', 'lacp_rate', 'lacp_bypass_allow', - 'lacp_bypass_period', 'lacp_bypass_all_active', - 'min_links']: - build_bond_attr(module, _attr) - - build_addr_method(module) - build_address(module) - build_vids(module) - build_pvid(module) - build_alias_name(module) - build_vrr(module) - - for _attr in ['mtu', 'mstpctl_portnetwork', 'mstpctl_portadminedge' - 'mstpctl_bpduguard', 'clag_id', - 'lacp_bypass_priority']: - build_generic_attr(module, _attr) - - -def config_dict_changed(module): - """ - return true if 'config' dict in hash is different - between desired and current config - """ - current_config = module.custom_current_config.get('config') - desired_config = module.custom_desired_config.get('config') - return current_config != desired_config - - -def config_changed(module): - """ - returns true if config has changed - """ - if config_dict_changed(module): - return True - # check if addr_method is changed - return module.custom_desired_config.get('addr_method') != \ - module.custom_current_config.get('addr_method') - - -def replace_config(module): - temp = tempfile.NamedTemporaryFile() - desired_config = module.custom_desired_config - # by default it will be something like /etc/network/interfaces.d/swp1 - final_location = module.params.get('location') + '/' + \ - module.params.get('name') - final_text = '' - _fh = open(final_location, 'w') - # make sure to put hash in array or else ifquery will fail - # write to temp file - try: - temp.write(module.jsonify([desired_config])) - # need to seek to 0 so that data is written to tempfile. - temp.seek(0) - _cmd = "/sbin/ifquery -a -i %s -t json" % (temp.name) - final_text = run_cmd(module, _cmd) - finally: - temp.close() - - try: - _fh.write(final_text) - finally: - _fh.close() - - -def main(): - module = AnsibleModule( - argument_spec=dict( - slaves=dict(required=True, type='list'), - name=dict(required=True, type='str'), - ipv4=dict(type='list'), - ipv6=dict(type='list'), - alias_name=dict(type='str'), - addr_method=dict(type='str', - choices=['', 'dhcp']), - mtu=dict(type='str'), - virtual_ip=dict(type='str'), - virtual_mac=dict(type='str'), - vids=dict(type='list'), - pvid=dict(type='str'), - mstpctl_portnetwork=dict(type='bool'), - mstpctl_portadminedge=dict(type='bool'), - mstpctl_bpduguard=dict(type='bool'), - clag_id=dict(type='str'), - min_links=dict(type='int', default=1), - mode=dict(type='str', default='802.3ad'), - miimon=dict(type='int', default=100), - xmit_hash_policy=dict(type='str', default='layer3+4'), - lacp_rate=dict(type='int', default=1), - lacp_bypass_allow=dict(type='int', choices=[0, 1]), - lacp_bypass_all_active=dict(type='int', choices=[0, 1]), - lacp_bypass_priority=dict(type='list'), - lacp_bypass_period=dict(type='int'), - location=dict(type='str', - default='/etc/network/interfaces.d') - ), - mutually_exclusive=[['lacp_bypass_priority', 'lacp_bypass_all_active']], - required_together=[['virtual_ip', 'virtual_mac']] - ) - - # if using the jinja default filter, this resolves to - # create an list with an empty string ['']. The following - # 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.items(): - if isinstance(_param, list): - module.params[k] = [x for x in _param if x] - - _location = module.params.get('location') - if not os.path.exists(_location): - _msg = "%s does not exist." % (_location) - module.fail_json(msg=_msg) - return # for testing purposes only - - ifacename = module.params.get('name') - _changed = False - _msg = "interface %s config not changed" % (ifacename) - current_iface_config(module) - build_desired_iface_config(module) - if config_changed(module): - replace_config(module) - _msg = "interface %s config updated" % (ifacename) - _changed = True - - module.exit_json(changed=_changed, msg=_msg) - +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module() diff --git a/lib/ansible/modules/network/cumulus/_cl_bridge.py b/lib/ansible/modules/network/cumulus/_cl_bridge.py index 5c9af56b2e5..00b498d5e93 100644 --- a/lib/ansible/modules/network/cumulus/_cl_bridge.py +++ b/lib/ansible/modules/network/cumulus/_cl_bridge.py @@ -19,7 +19,10 @@ module: cl_bridge version_added: "2.1" author: "Cumulus Networks (@CumulusNetworks)" short_description: Configures a bridge port on Cumulus Linux -deprecated: Deprecated in 2.3. Use M(nclu) instead. +deprecated: + removed_in: "2.5" + why: The M(nclu) module is designed to be easier to use for individuals who are new to Cumulus Linux by exposing the NCLU interface in an automatable way. + alternative: Use M(nclu) instead. description: - Configures a bridge interface on Cumulus Linux To configure a bond port use the cl_bond module. To configure any other type of interface use the @@ -157,258 +160,7 @@ msg: sample: "interface bond0 config updated" ''' -import os -import re -import tempfile - -from ansible.module_utils.basic import AnsibleModule - - -# handy helper for calling system calls. -# calls AnsibleModule.run_command and prints a more appropriate message -# exec_path - path to file to execute, with all its arguments. -# E.g "/sbin/ip -o link show" -# failure_msg - what message to print on failure -def run_cmd(module, exec_path): - (_rc, out, _err) = module.run_command(exec_path) - if _rc > 0: - if re.search('cannot find interface', _err): - return '[{}]' - failure_msg = "Failed; %s Error: %s" % (exec_path, _err) - module.fail_json(msg=failure_msg) - else: - return out - - -def current_iface_config(module): - # due to a bug in ifquery, have to check for presence of interface file - # and not rely solely on ifquery. when bug is fixed, this check can be - # removed - _ifacename = module.params.get('name') - _int_dir = module.params.get('location') - module.custom_current_config = {} - if os.path.exists(_int_dir + '/' + _ifacename): - _cmd = "/sbin/ifquery -o json %s" % (module.params.get('name')) - module.custom_current_config = module.from_json( - run_cmd(module, _cmd))[0] - - -def build_address(module): - # if addr_method == 'dhcp', don't add IP address - if module.params.get('addr_method') == 'dhcp': - return - _ipv4 = module.params.get('ipv4') - _ipv6 = module.params.get('ipv6') - _addresslist = [] - if _ipv4 and len(_ipv4) > 0: - _addresslist += _ipv4 - - if _ipv6 and len(_ipv6) > 0: - _addresslist += _ipv6 - if len(_addresslist) > 0: - module.custom_desired_config['config']['address'] = ' '.join( - _addresslist) - - -def build_vids(module): - _vids = module.params.get('vids') - if _vids and len(_vids) > 0: - module.custom_desired_config['config']['bridge-vids'] = ' '.join(_vids) - - -def build_pvid(module): - _pvid = module.params.get('pvid') - if _pvid: - module.custom_desired_config['config']['bridge-pvid'] = str(_pvid) - - -def conv_bool_to_str(_value): - if isinstance(_value, bool): - if _value is True: - return 'yes' - else: - return 'no' - return _value - - -def build_generic_attr(module, _attr): - _value = module.params.get(_attr) - _value = conv_bool_to_str(_value) - if _value: - module.custom_desired_config['config'][ - re.sub('_', '-', _attr)] = str(_value) - - -def build_alias_name(module): - alias_name = module.params.get('alias_name') - if alias_name: - module.custom_desired_config['config']['alias'] = alias_name - - -def build_addr_method(module): - _addr_method = module.params.get('addr_method') - if _addr_method: - module.custom_desired_config['addr_family'] = 'inet' - module.custom_desired_config['addr_method'] = _addr_method - - -def build_vrr(module): - _virtual_ip = module.params.get('virtual_ip') - _virtual_mac = module.params.get('virtual_mac') - vrr_config = [] - if _virtual_ip: - vrr_config.append(_virtual_mac) - vrr_config.append(_virtual_ip) - module.custom_desired_config.get('config')['address-virtual'] = \ - ' '.join(vrr_config) - - -def add_glob_to_array(_bridgemems): - """ - goes through each bridge member if it sees a dash add glob - before it - """ - result = [] - if isinstance(_bridgemems, list): - for _entry in _bridgemems: - if re.search('-', _entry): - _entry = 'glob ' + _entry - result.append(_entry) - return ' '.join(result) - return _bridgemems - - -def build_bridge_attr(module, _attr): - _value = module.params.get(_attr) - _value = conv_bool_to_str(_value) - _value = add_glob_to_array(_value) - if _value: - module.custom_desired_config['config'][ - 'bridge-' + re.sub('_', '-', _attr)] = str(_value) - - -def build_desired_iface_config(module): - """ - take parameters defined and build ifupdown2 compatible hash - """ - module.custom_desired_config = { - 'addr_family': None, - 'auto': True, - 'config': {}, - 'name': module.params.get('name') - } - - for _attr in ['vlan_aware', 'pvid', 'ports', 'stp']: - build_bridge_attr(module, _attr) - - build_addr_method(module) - build_address(module) - build_vids(module) - build_alias_name(module) - build_vrr(module) - for _attr in ['mtu', 'mstpctl_treeprio']: - build_generic_attr(module, _attr) - - -def config_dict_changed(module): - """ - return true if 'config' dict in hash is different - between desired and current config - """ - current_config = module.custom_current_config.get('config') - desired_config = module.custom_desired_config.get('config') - return current_config != desired_config - - -def config_changed(module): - """ - returns true if config has changed - """ - if config_dict_changed(module): - return True - # check if addr_method is changed - return module.custom_desired_config.get('addr_method') != \ - module.custom_current_config.get('addr_method') - - -def replace_config(module): - temp = tempfile.NamedTemporaryFile() - desired_config = module.custom_desired_config - # by default it will be something like /etc/network/interfaces.d/swp1 - final_location = module.params.get('location') + '/' + \ - module.params.get('name') - final_text = '' - _fh = open(final_location, 'w') - # make sure to put hash in array or else ifquery will fail - # write to temp file - try: - temp.write(module.jsonify([desired_config])) - # need to seek to 0 so that data is written to tempfile. - temp.seek(0) - _cmd = "/sbin/ifquery -a -i %s -t json" % (temp.name) - final_text = run_cmd(module, _cmd) - finally: - temp.close() - - try: - _fh.write(final_text) - finally: - _fh.close() - - -def main(): - module = AnsibleModule( - argument_spec=dict( - ports=dict(required=True, type='list'), - name=dict(required=True, type='str'), - ipv4=dict(type='list'), - ipv6=dict(type='list'), - alias_name=dict(type='str'), - addr_method=dict(type='str', - choices=['', 'dhcp']), - mtu=dict(type='str'), - virtual_ip=dict(type='str'), - virtual_mac=dict(type='str'), - vids=dict(type='list'), - pvid=dict(type='str'), - mstpctl_treeprio=dict(type='str'), - vlan_aware=dict(type='bool'), - stp=dict(type='bool', default='yes'), - location=dict(type='str', - default='/etc/network/interfaces.d') - ), - required_together=[ - ['virtual_ip', 'virtual_mac'] - ] - ) - - # if using the jinja default filter, this resolves to - # create an list with an empty string ['']. The following - # 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.items(): - if isinstance(_param, list): - module.params[k] = [x for x in _param if x] - - _location = module.params.get('location') - if not os.path.exists(_location): - _msg = "%s does not exist." % (_location) - module.fail_json(msg=_msg) - return # for testing purposes only - - ifacename = module.params.get('name') - _changed = False - _msg = "interface %s config not changed" % (ifacename) - current_iface_config(module) - build_desired_iface_config(module) - if config_changed(module): - replace_config(module) - _msg = "interface %s config updated" % (ifacename) - _changed = True - - module.exit_json(changed=_changed, msg=_msg) - +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module() diff --git a/lib/ansible/modules/network/cumulus/_cl_img_install.py b/lib/ansible/modules/network/cumulus/_cl_img_install.py index 22658475370..7342c70abb9 100644 --- a/lib/ansible/modules/network/cumulus/_cl_img_install.py +++ b/lib/ansible/modules/network/cumulus/_cl_img_install.py @@ -19,7 +19,10 @@ module: cl_img_install version_added: "2.1" author: "Cumulus Networks (@CumulusNetworks)" short_description: Install a different Cumulus Linux version. -deprecated: Deprecated in 2.3. The image slot system no longer exists in Cumulus Linux. +deprecated: + removed_in: "2.5" + why: The image slot system no longer exists in Cumulus Linux. + alternative: n/a description: - install a different version of Cumulus Linux in the inactive slot. For more details go the Image Management User Guide at @@ -103,213 +106,7 @@ msg: sample: "interface bond0 config updated" ''' -import re - -from ansible.module_utils.basic import AnsibleModule, platform -from ansible.module_utils.six.moves.urllib import parse as urlparse - - -def check_url(module, url): - parsed_url = urlparse(url) - if len(parsed_url.path) > 0: - sch = parsed_url.scheme - if (sch == 'http' or sch == 'https' or len(parsed_url.scheme) == 0): - return True - module.fail_json(msg="Image Path URL. Wrong Format %s" % (url)) - return False - - -def run_cl_cmd(module, cmd, check_rc=True): - try: - (rc, out, err) = module.run_command(cmd, check_rc=check_rc) - except Exception as e: - module.fail_json(msg=e.strerror) - # trim last line as it is always empty - ret = out.splitlines() - return ret - - -def get_slot_info(module): - slots = {} - slots['1'] = {} - slots['2'] = {} - active_slotnum = get_active_slot(module) - primary_slotnum = get_primary_slot_num(module) - for _num in range(1, 3): - slot = slots[str(_num)] - slot['version'] = get_slot_version(module, str(_num)) - if _num == int(active_slotnum): - slot['active'] = True - if _num == int(primary_slotnum): - slot['primary'] = True - return slots - - -def get_slot_version(module, slot_num): - lsb_release = check_mnt_root_lsb_release(slot_num) - switch_firm_ver = check_fw_print_env(module, slot_num) - _version = module.sw_version - if lsb_release == _version or switch_firm_ver == _version: - return _version - elif lsb_release: - return lsb_release - else: - return switch_firm_ver - - -def check_mnt_root_lsb_release(slot_num): - _path = '/mnt/root-rw/config%s/etc/lsb-release' % (slot_num) - try: - lsb_release = open(_path) - lines = lsb_release.readlines() - for line in lines: - _match = re.search('DISTRIB_RELEASE=([0-9a-zA-Z.]+)', line) - if _match: - return _match.group(1).split('-')[0] - except: - pass - return None - - -def check_fw_print_env(module, slot_num): - cmd = None - if platform.machine() == 'ppc': - cmd = "/usr/sbin/fw_printenv -n cl.ver%s" % (slot_num) - fw_output = run_cl_cmd(module, cmd) - return fw_output[0].split('-')[0] - elif platform.machine() == 'x86_64': - cmd = "/usr/bin/grub-editenv list" - grub_output = run_cl_cmd(module, cmd) - for _line in grub_output: - _regex_str = re.compile('cl.ver' + slot_num + r'=([\w.]+)-') - m0 = re.match(_regex_str, _line) - if m0: - return m0.group(1) - - -def get_primary_slot_num(module): - cmd = None - if platform.machine() == 'ppc': - cmd = "/usr/sbin/fw_printenv -n cl.active" - return ''.join(run_cl_cmd(module, cmd)) - elif platform.machine() == 'x86_64': - cmd = "/usr/bin/grub-editenv list" - grub_output = run_cl_cmd(module, cmd) - for _line in grub_output: - _regex_str = re.compile(r'cl.active=(\d)') - m0 = re.match(_regex_str, _line) - if m0: - return m0.group(1) - - -def get_active_slot(module): - try: - cmdline = open('/proc/cmdline').readline() - except: - module.fail_json(msg='Failed to open /proc/cmdline. ' + - 'Unable to determine active slot') - - _match = re.search(r'active=(\d+)', cmdline) - if _match: - return _match.group(1) - return None - - -def install_img(module): - src = module.params.get('src') - _version = module.sw_version - app_path = '/usr/cumulus/bin/cl-img-install -f %s' % (src) - run_cl_cmd(module, app_path) - perform_switch_slot = module.params.get('switch_slot') - if perform_switch_slot is True: - check_sw_version(module) - else: - _changed = True - _msg = "Cumulus Linux Version " + _version + " successfully" + \ - " installed in alternate slot" - module.exit_json(changed=_changed, msg=_msg) - - -def switch_slot(module, slotnum): - _switch_slot = module.params.get('switch_slot') - if _switch_slot is True: - app_path = '/usr/cumulus/bin/cl-img-select %s' % (slotnum) - run_cl_cmd(module, app_path) - - -def determine_sw_version(module): - _version = module.params.get('version') - _filename = '' - # Use _version if user defines it - if _version: - module.sw_version = _version - return - else: - _filename = module.params.get('src').split('/')[-1] - _match = re.search(r'\d+\W\d+\W\w+', _filename) - if _match: - module.sw_version = re.sub(r'\W', '.', _match.group()) - return - _msg = 'Unable to determine version from file %s' % (_filename) - module.exit_json(changed=False, msg=_msg) - - -def check_sw_version(module): - slots = get_slot_info(module) - _version = module.sw_version - perform_switch_slot = module.params.get('switch_slot') - for _num, slot in slots.items(): - if slot['version'] == _version: - if 'active' in slot: - _msg = "Version %s is installed in the active slot" \ - % (_version) - module.exit_json(changed=False, msg=_msg) - else: - _msg = "Version " + _version + \ - " is installed in the alternate slot. " - if 'primary' not in slot: - if perform_switch_slot is True: - switch_slot(module, _num) - _msg = _msg + \ - "cl-img-select has made the alternate " + \ - "slot the primary slot. " +\ - "Next reboot, switch will load " + _version + "." - module.exit_json(changed=True, msg=_msg) - else: - _msg = _msg + \ - "Next reboot will not load " + _version + ". " + \ - "switch_slot keyword set to 'no'." - module.exit_json(changed=False, msg=_msg) - else: - if perform_switch_slot is True: - _msg = _msg + \ - "Next reboot, switch will load " + _version + "." - module.exit_json(changed=False, msg=_msg) - else: - _msg = _msg + \ - 'switch_slot set to "no". ' + \ - 'No further action to take' - module.exit_json(changed=False, msg=_msg) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - src=dict(required=True, type='str'), - version=dict(type='str'), - switch_slot=dict(type='bool', default=False), - ), - ) - - determine_sw_version(module) - _url = module.params.get('src') - - check_sw_version(module) - - check_url(module, _url) - - install_img(module) - +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module() diff --git a/lib/ansible/modules/network/cumulus/_cl_interface.py b/lib/ansible/modules/network/cumulus/_cl_interface.py index e6499685c41..153529716e1 100644 --- a/lib/ansible/modules/network/cumulus/_cl_interface.py +++ b/lib/ansible/modules/network/cumulus/_cl_interface.py @@ -20,7 +20,10 @@ version_added: "2.1" author: "Cumulus Networks (@CumulusNetworks)" short_description: Configures a front panel port, loopback or management port on Cumulus Linux. -deprecated: Deprecated in 2.3. Use M(nclu) instead. +deprecated: + removed_in: "2.5" + why: The M(nclu) module is designed to be easier to use for individuals who are new to Cumulus Linux by exposing the NCLU interface in an automatable way. + alternative: Use M(nclu) instead. description: - Configures a front panel, sub-interface, SVI, management or loopback port on a Cumulus Linux switch. For bridge ports use the cl_bridge module. For @@ -202,249 +205,7 @@ msg: sample: "interface bond0 config updated" ''' -import os -import re -import tempfile - -from ansible.module_utils.basic import AnsibleModule - - -# handy helper for calling system calls. -# calls AnsibleModule.run_command and prints a more appropriate message -# exec_path - path to file to execute, with all its arguments. -# E.g "/sbin/ip -o link show" -# failure_msg - what message to print on failure -def run_cmd(module, exec_path): - (_rc, out, _err) = module.run_command(exec_path) - if _rc > 0: - if re.search('cannot find interface', _err): - return '[{}]' - failure_msg = "Failed; %s Error: %s" % (exec_path, _err) - module.fail_json(msg=failure_msg) - else: - return out - - -def current_iface_config(module): - # due to a bug in ifquery, have to check for presence of interface file - # and not rely solely on ifquery. when bug is fixed, this check can be - # removed - _ifacename = module.params.get('name') - _int_dir = module.params.get('location') - module.custom_current_config = {} - if os.path.exists(_int_dir + '/' + _ifacename): - _cmd = "/sbin/ifquery -o json %s" % (module.params.get('name')) - module.custom_current_config = module.from_json( - run_cmd(module, _cmd))[0] - - -def build_address(module): - # if addr_method == 'dhcp', don't add IP address - if module.params.get('addr_method') == 'dhcp': - return - _ipv4 = module.params.get('ipv4') - _ipv6 = module.params.get('ipv6') - _addresslist = [] - if _ipv4 and len(_ipv4) > 0: - _addresslist += _ipv4 - if _ipv6 and len(_ipv6) > 0: - _addresslist += _ipv6 - if len(_addresslist) > 0: - module.custom_desired_config['config']['address'] = ' '.join( - _addresslist) - - -def build_vids(module): - _vids = module.params.get('vids') - if _vids and len(_vids) > 0: - module.custom_desired_config['config']['bridge-vids'] = ' '.join(_vids) - - -def build_pvid(module): - _pvid = module.params.get('pvid') - if _pvid: - module.custom_desired_config['config']['bridge-pvid'] = str(_pvid) - - -def build_speed(module): - _speed = module.params.get('speed') - if _speed: - module.custom_desired_config['config']['link-speed'] = str(_speed) - module.custom_desired_config['config']['link-duplex'] = 'full' - - -def conv_bool_to_str(_value): - if isinstance(_value, bool): - if _value is True: - return 'yes' - else: - return 'no' - return _value - - -def build_generic_attr(module, _attr): - _value = module.params.get(_attr) - _value = conv_bool_to_str(_value) - if _value: - module.custom_desired_config['config'][ - re.sub('_', '-', _attr)] = str(_value) - - -def build_alias_name(module): - alias_name = module.params.get('alias_name') - if alias_name: - module.custom_desired_config['config']['alias'] = alias_name - - -def build_addr_method(module): - _addr_method = module.params.get('addr_method') - if _addr_method: - module.custom_desired_config['addr_family'] = 'inet' - module.custom_desired_config['addr_method'] = _addr_method - - -def build_vrr(module): - _virtual_ip = module.params.get('virtual_ip') - _virtual_mac = module.params.get('virtual_mac') - vrr_config = [] - if _virtual_ip: - vrr_config.append(_virtual_mac) - vrr_config.append(_virtual_ip) - module.custom_desired_config.get('config')['address-virtual'] = \ - ' '.join(vrr_config) - - -def build_desired_iface_config(module): - """ - take parameters defined and build ifupdown2 compatible hash - """ - module.custom_desired_config = { - 'addr_family': None, - 'auto': True, - 'config': {}, - 'name': module.params.get('name') - } - - build_addr_method(module) - build_address(module) - build_vids(module) - build_pvid(module) - build_speed(module) - build_alias_name(module) - build_vrr(module) - for _attr in ['mtu', 'mstpctl_portnetwork', 'mstpctl_portadminedge', - 'mstpctl_bpduguard', 'clagd_enable', - 'clagd_priority', 'clagd_peer_ip', - 'clagd_sys_mac', 'clagd_args']: - build_generic_attr(module, _attr) - - -def config_dict_changed(module): - """ - return true if 'config' dict in hash is different - between desired and current config - """ - current_config = module.custom_current_config.get('config') - desired_config = module.custom_desired_config.get('config') - return current_config != desired_config - - -def config_changed(module): - """ - returns true if config has changed - """ - if config_dict_changed(module): - return True - # check if addr_method is changed - return module.custom_desired_config.get('addr_method') != \ - module.custom_current_config.get('addr_method') - - -def replace_config(module): - temp = tempfile.NamedTemporaryFile() - desired_config = module.custom_desired_config - # by default it will be something like /etc/network/interfaces.d/swp1 - final_location = module.params.get('location') + '/' + \ - module.params.get('name') - final_text = '' - _fh = open(final_location, 'w') - # make sure to put hash in array or else ifquery will fail - # write to temp file - try: - temp.write(module.jsonify([desired_config])) - # need to seek to 0 so that data is written to tempfile. - temp.seek(0) - _cmd = "/sbin/ifquery -a -i %s -t json" % (temp.name) - final_text = run_cmd(module, _cmd) - finally: - temp.close() - - try: - _fh.write(final_text) - finally: - _fh.close() - - -def main(): - module = AnsibleModule( - argument_spec=dict( - name=dict(required=True, type='str'), - ipv4=dict(type='list'), - ipv6=dict(type='list'), - alias_name=dict(type='str'), - addr_method=dict(type='str', - choices=['', 'loopback', 'dhcp']), - speed=dict(type='str'), - mtu=dict(type='str'), - virtual_ip=dict(type='str'), - virtual_mac=dict(type='str'), - vids=dict(type='list'), - pvid=dict(type='str'), - mstpctl_portnetwork=dict(type='bool'), - mstpctl_portadminedge=dict(type='bool'), - mstpctl_bpduguard=dict(type='bool'), - clagd_enable=dict(type='bool'), - clagd_priority=dict(type='str'), - clagd_peer_ip=dict(type='str'), - clagd_sys_mac=dict(type='str'), - clagd_args=dict(type='str'), - location=dict(type='str', - default='/etc/network/interfaces.d') - ), - required_together=[ - ['virtual_ip', 'virtual_mac'], - ['clagd_enable', 'clagd_priority', - 'clagd_peer_ip', 'clagd_sys_mac'] - ] - ) - - # if using the jinja default filter, this resolves to - # create an list with an empty string ['']. The following - # 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.items(): - if isinstance(_param, list): - module.params[k] = [x for x in _param if x] - - _location = module.params.get('location') - if not os.path.exists(_location): - _msg = "%s does not exist." % (_location) - module.fail_json(msg=_msg) - return # for testing purposes only - - ifacename = module.params.get('name') - _changed = False - _msg = "interface %s config not changed" % (ifacename) - current_iface_config(module) - build_desired_iface_config(module) - if config_changed(module): - replace_config(module) - _msg = "interface %s config updated" % (ifacename) - _changed = True - - module.exit_json(changed=_changed, msg=_msg) - +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module() diff --git a/lib/ansible/modules/network/cumulus/_cl_interface_policy.py b/lib/ansible/modules/network/cumulus/_cl_interface_policy.py index bb2e32bc5ad..e1049cfc016 100644 --- a/lib/ansible/modules/network/cumulus/_cl_interface_policy.py +++ b/lib/ansible/modules/network/cumulus/_cl_interface_policy.py @@ -19,7 +19,10 @@ module: cl_interface_policy version_added: "2.1" author: "Cumulus Networks (@CumulusNetworks)" short_description: Configure interface enforcement policy on Cumulus Linux -deprecated: Deprecated in 2.3. Use M(nclu) instead. +deprecated: + removed_in: "2.5" + why: The M(nclu) module is designed to be easier to use for individuals who are new to Cumulus Linux by exposing the NCLU interface in an automatable way. + alternative: Use M(nclu) instead. description: - This module affects the configuration files located in the interfaces folder defined by ifupdown2. Interfaces port and port ranges listed in the @@ -64,82 +67,8 @@ msg: type: string sample: "interface bond0 config updated" ''' -import os -import re - -from ansible.module_utils.basic import AnsibleModule - - -# get list of interface files that are currently "configured". -# doesn't mean actually applied to the system, but most likely are -def read_current_int_dir(module): - module.custom_currentportlist = os.listdir(module.params.get('location')) - - -# take the allowed list and convert it to into a list -# of ports. -def convert_allowed_list_to_port_range(module): - allowedlist = module.params.get('allowed') - for portrange in allowedlist: - module.custom_allowedportlist += breakout_portrange(portrange) - - -def breakout_portrange(prange): - _m0 = re.match(r'(\w+[a-z.])(\d+)?-?(\d+)?(\w+)?', prange.strip()) - # no range defined - if _m0.group(3) is None: - return [_m0.group(0)] - else: - portarray = [] - intrange = range(int(_m0.group(2)), int(_m0.group(3)) + 1) - for _int in intrange: - portarray.append(''.join([_m0.group(1), - str(_int), - str(_m0.group(4) or '') - ] - ) - ) - return portarray - - -# deletes the interface files -def unconfigure_interfaces(module): - currentportset = set(module.custom_currentportlist) - allowedportset = set(module.custom_allowedportlist) - remove_list = currentportset.difference(allowedportset) - fileprefix = module.params.get('location') - module.msg = "remove config for interfaces %s" % (', '.join(remove_list)) - for _file in remove_list: - os.unlink(fileprefix + _file) - - -# check to see if policy should be enforced -# returns true if policy needs to be enforced -# that is delete interface files -def int_policy_enforce(module): - currentportset = set(module.custom_currentportlist) - allowedportset = set(module.custom_allowedportlist) - return not currentportset.issubset(allowedportset) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - allowed=dict(type='list', required=True), - location=dict(type='str', default='/etc/network/interfaces.d/') - ), - ) - module.custom_currentportlist = [] - module.custom_allowedportlist = [] - module.changed = False - module.msg = 'configured port list is part of allowed port list' - read_current_int_dir(module) - convert_allowed_list_to_port_range(module) - if int_policy_enforce(module): - module.changed = True - unconfigure_interfaces(module) - module.exit_json(changed=module.changed, msg=module.msg) +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module() diff --git a/lib/ansible/modules/network/cumulus/_cl_license.py b/lib/ansible/modules/network/cumulus/_cl_license.py index 175e79fb68a..d88d4ad9e19 100644 --- a/lib/ansible/modules/network/cumulus/_cl_license.py +++ b/lib/ansible/modules/network/cumulus/_cl_license.py @@ -18,8 +18,11 @@ DOCUMENTATION = ''' module: cl_license version_added: "2.1" author: "Cumulus Networks (@CumulusNetworks)" -short_description: Install licenses fo Cumulus Linux -deprecated: Deprecated in 2.3. +short_description: Install licenses for Cumulus Linux +deprecated: + why: The M(nclu) module is designed to be easier to use for individuals who are new to Cumulus Linux by exposing the NCLU interface in an automatable way. + removed_in: "2.5" + alternative: Use M(nclu) instead. description: - Installs a Cumulus Linux license. The module reports no change of status when a license is installed. @@ -100,43 +103,7 @@ msg: sample: "interface bond0 config updated" ''' -from ansible.module_utils.basic import AnsibleModule - - -CL_LICENSE_PATH = '/usr/cumulus/bin/cl-license' - - -def install_license(module): - # license is not installed, install it - _url = module.params.get('src') - (_rc, out, _err) = module.run_command("%s -i %s" % (CL_LICENSE_PATH, _url)) - if _rc > 0: - module.fail_json(msg=_err) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - src=dict(required=True, type='str'), - force=dict(type='bool', default=False) - ), - ) - - # check if license is installed - # if force is enabled then set return code to nonzero - if module.params.get('force') is True: - _rc = 10 - else: - (_rc, out, _err) = module.run_command(CL_LICENSE_PATH) - if _rc == 0: - module.msg = "No change. License already installed" - module.changed = False - else: - install_license(module) - module.msg = "License installation completed" - module.changed = True - module.exit_json(changed=module.changed, msg=module.msg) - +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module() diff --git a/lib/ansible/modules/network/cumulus/_cl_ports.py b/lib/ansible/modules/network/cumulus/_cl_ports.py index 41e329ae298..e1bf209343b 100644 --- a/lib/ansible/modules/network/cumulus/_cl_ports.py +++ b/lib/ansible/modules/network/cumulus/_cl_ports.py @@ -19,7 +19,10 @@ module: cl_ports version_added: "2.1" author: "Cumulus Networks (@CumulusNetworks)" short_description: Configure Cumulus Switch port attributes (ports.conf) -deprecated: Deprecated in 2.3. Use M(nclu) instead. +deprecated: + removed_in: "2.5" + why: The M(nclu) module is designed to be easier to use for individuals who are new to Cumulus Linux by exposing the NCLU interface in an automatable way. + alternative: Use M(nclu) instead. description: - Set the initial port attribute defined in the Cumulus Linux ports.conf, file. This module does not do any error checking at the moment. Be careful @@ -77,139 +80,8 @@ msg: type: string sample: "interface bond0 config updated" ''' -import os -import re -import tempfile -import shutil - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_native - - -PORTS_CONF = '/etc/cumulus/ports.conf' - - -def hash_existing_ports_conf(module): - module.ports_conf_hash = {} - if not os.path.exists(PORTS_CONF): - return False - - try: - existing_ports_conf = open(PORTS_CONF).readlines() - except IOError as e: - _msg = "Failed to open %s: %s" % (PORTS_CONF, to_native(e)) - module.fail_json(msg=_msg) - return # for testing only should return on module.fail_json - - for _line in existing_ports_conf: - _m0 = re.match(r'^(\d+)=(\w+)', _line) - if _m0: - _portnum = int(_m0.group(1)) - _speed = _m0.group(2) - module.ports_conf_hash[_portnum] = _speed - - -def generate_new_ports_conf_hash(module): - new_ports_conf_hash = {} - convert_hash = { - 'speed_40g_div_4': '40G/4', - 'speed_4_by_10g': '4x10G', - 'speed_10g': '10G', - 'speed_40g': '40G' - } - for k in module.params.keys(): - port_range = module.params[k] - port_setting = convert_hash[k] - if port_range: - port_range = [x for x in port_range if x] - for port_str in port_range: - port_range_str = port_str.replace('swp', '').split('-') - if len(port_range_str) == 1: - new_ports_conf_hash[int(port_range_str[0])] = \ - port_setting - else: - int_range = map(int, port_range_str) - portnum_range = range(int_range[0], int_range[1] + 1) - for i in portnum_range: - new_ports_conf_hash[i] = port_setting - module.new_ports_hash = new_ports_conf_hash - - -def compare_new_and_old_port_conf_hash(module): - ports_conf_hash_copy = module.ports_conf_hash.copy() - module.ports_conf_hash.update(module.new_ports_hash) - port_num_length = len(module.ports_conf_hash.keys()) - orig_port_num_length = len(ports_conf_hash_copy.keys()) - if port_num_length != orig_port_num_length: - module.fail_json(msg="Port numbering is wrong. \ -Too many or two few ports configured") - return False - elif ports_conf_hash_copy == module.ports_conf_hash: - return False - return True - - -def make_copy_of_orig_ports_conf(module): - if os.path.exists(PORTS_CONF + '.orig'): - return - - try: - shutil.copyfile(PORTS_CONF, PORTS_CONF + '.orig') - except IOError as e: - _msg = "Failed to save the original %s: %s" % (PORTS_CONF, to_native(e)) - module.fail_json(msg=_msg) - return # for testing only - - -def write_to_ports_conf(module): - """ - use tempfile to first write out config in temp file - then write to actual location. may help prevent file - corruption. Ports.conf is a critical file for Cumulus. - Don't want to corrupt this file under any circumstance. - """ - temp = tempfile.NamedTemporaryFile() - try: - try: - temp.write('# Managed By Ansible\n') - for k in sorted(module.ports_conf_hash.keys()): - port_setting = module.ports_conf_hash[k] - _str = "%s=%s\n" % (k, port_setting) - temp.write(_str) - temp.seek(0) - shutil.copyfile(temp.name, PORTS_CONF) - except IOError as e: - module.fail_json(msg="Failed to write to %s: %s" % (PORTS_CONF, to_native(e))) - finally: - temp.close() - - -def main(): - module = AnsibleModule( - argument_spec=dict( - speed_40g_div_4=dict(type='list'), - speed_4_by_10g=dict(type='list'), - speed_10g=dict(type='list'), - speed_40g=dict(type='list') - ), - required_one_of=[['speed_40g_div_4', - 'speed_4_by_10g', - 'speed_10g', - 'speed_40g']] - ) - - _changed = False - hash_existing_ports_conf(module) - generate_new_ports_conf_hash(module) - if compare_new_and_old_port_conf_hash(module): - make_copy_of_orig_ports_conf(module) - write_to_ports_conf(module) - _changed = True - _msg = "/etc/cumulus/ports.conf changed" - else: - _msg = 'No change in /etc/ports.conf' - module.exit_json(changed=_changed, msg=_msg) +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module() diff --git a/lib/ansible/modules/network/nxos/_nxos_ip_interface.py b/lib/ansible/modules/network/nxos/_nxos_ip_interface.py index 1d9d9f496a3..18600b2fd61 100644 --- a/lib/ansible/modules/network/nxos/_nxos_ip_interface.py +++ b/lib/ansible/modules/network/nxos/_nxos_ip_interface.py @@ -24,7 +24,10 @@ DOCUMENTATION = ''' --- module: nxos_ip_interface version_added: "2.1" -deprecated: Deprecated in 2.5. Use M(nxos_l3_interface) instead. +deprecated: + removed_in: "2.9" + why: Replaced with common C(*_l3_interface) network modules. + alternative: Use M(nxos_l3_interface) instead. short_description: Manages L3 attributes for IPv4 and IPv6 interfaces. description: - Manages Layer 3 attributes for IPv4 and IPv6 interfaces. diff --git a/lib/ansible/modules/network/nxos/_nxos_mtu.py b/lib/ansible/modules/network/nxos/_nxos_mtu.py index f2a52127a65..6fe6707013f 100644 --- a/lib/ansible/modules/network/nxos/_nxos_mtu.py +++ b/lib/ansible/modules/network/nxos/_nxos_mtu.py @@ -25,7 +25,10 @@ DOCUMENTATION = ''' module: nxos_mtu extends_documentation_fragment: nxos version_added: "2.2" -deprecated: Deprecated in 2.3 use M(nxos_system)'s C(mtu) option. +deprecated: + removed_in: "2.5" + why: Replaced with common C(*_system) network modules. + alternative: Use M(nxos_system)'s C(system_mtu) option. To specify an interfaces MTU use M(nxos_interface). short_description: Manages MTU settings on Nexus switch. description: - Manages MTU settings on Nexus switch. @@ -121,264 +124,8 @@ changed: type: boolean sample: true ''' -from ansible.module_utils.network.nxos.nxos import load_config, run_commands -from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, check_args -from ansible.module_utils.basic import AnsibleModule - - -def execute_show_command(command, module): - if 'show run' not in command: - output = 'json' - else: - output = 'text' - cmds = [{ - 'command': command, - 'output': output, - }] - - body = run_commands(module, cmds) - return body - - -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_mtu(interface, module): - command = 'show interface {0}'.format(interface) - mtu = {} - - body = execute_show_command(command, module) - - try: - mtu_table = body[0]['TABLE_interface']['ROW_interface'] - mtu['mtu'] = str( - mtu_table.get('eth_mtu', - mtu_table.get('svi_mtu', 'unreadable_via_api'))) - mtu['sysmtu'] = get_system_mtu(module)['sysmtu'] - except KeyError: - mtu = {} - - return mtu - - -def get_system_mtu(module): - command = 'show run all | inc jumbomtu' - sysmtu = '' - - body = execute_show_command(command, module) - - if body: - sysmtu = str(body[0].split(' ')[-1]) - try: - sysmtu = int(sysmtu) - except: - sysmtu = "" - - return dict(sysmtu=str(sysmtu)) - - -def get_commands_config_mtu(delta, interface): - CONFIG_ARGS = { - 'mtu': 'mtu {mtu}', - 'sysmtu': 'system jumbomtu {sysmtu}', - } - - commands = [] - for param, value in delta.items(): - command = CONFIG_ARGS.get(param, 'DNE').format(**delta) - if command and command != 'DNE': - commands.append(command) - command = None - mtu_check = delta.get('mtu', None) - if mtu_check: - commands.insert(0, 'interface {0}'.format(interface)) - return commands - - -def get_commands_remove_mtu(delta, interface): - CONFIG_ARGS = { - 'mtu': 'no mtu {mtu}', - 'sysmtu': 'no system jumbomtu {sysmtu}', - } - commands = [] - for param, value in delta.items(): - command = CONFIG_ARGS.get(param, 'DNE').format(**delta) - if command and command != 'DNE': - commands.append(command) - command = None - mtu_check = delta.get('mtu', None) - if mtu_check: - commands.insert(0, 'interface {0}'.format(interface)) - return commands - - -def get_interface_type(interface): - if interface.upper().startswith('ET'): - return 'ethernet' - elif interface.upper().startswith('VL'): - return 'svi' - elif interface.upper().startswith('LO'): - return 'loopback' - elif interface.upper().startswith('MG'): - return 'management' - elif interface.upper().startswith('MA'): - return 'management' - elif interface.upper().startswith('PO'): - return 'portchannel' - else: - return 'unknown' - - -def is_default(interface, module): - command = 'show run interface {0}'.format(interface) - - try: - body = execute_show_command(command, module)[0] - if body == 'DNE': - return 'DNE' - else: - raw_list = body.split('\n') - if raw_list[-1].startswith('interface'): - return True - else: - return False - except (KeyError): - return 'DNE' - - -def get_interface_mode(interface, intf_type, module): - command = 'show interface {0}'.format(interface) - mode = 'unknown' - interface_table = {} - body = execute_show_command(command, module) - - try: - interface_table = body[0]['TABLE_interface']['ROW_interface'] - except (KeyError, AttributeError, IndexError): - return mode - - if intf_type in ['ethernet', 'portchannel']: - mode = str(interface_table.get('eth_mode', 'layer3')) - if mode in ['access', 'trunk']: - mode = 'layer2' - elif mode == 'routed': - mode = 'layer3' - elif intf_type in ['loopback', 'svi']: - mode = 'layer3' - return mode - - -def main(): - argument_spec = dict( - mtu=dict(type='str'), - interface=dict(type='str'), - sysmtu=dict(type='str'), - state=dict(choices=['absent', 'present'], default='present'), - ) - - argument_spec.update(nxos_argument_spec) - - module = AnsibleModule(argument_spec=argument_spec, - required_together=[['mtu', 'interface']], - supports_check_mode=True) - - warnings = list() - check_args(module, warnings) - - interface = module.params['interface'] - mtu = module.params['mtu'] - sysmtu = module.params['sysmtu'] - state = module.params['state'] - - if sysmtu and (interface or mtu): - module.fail_json(msg='Proper usage-- either just use the sysmtu param ' - 'or use interface AND mtu params') - - if interface: - intf_type = get_interface_type(interface) - if intf_type != 'ethernet': - if is_default(interface, module) == 'DNE': - module.fail_json(msg='Invalid interface. It does not exist ' - 'on the switch.') - - existing = get_mtu(interface, module) - else: - existing = get_system_mtu(module) - - if interface and mtu: - if intf_type == 'loopback': - module.fail_json(msg='Cannot set MTU for loopback interface.') - mode = get_interface_mode(interface, intf_type, module) - if mode == 'layer2': - if intf_type in ['ethernet', 'portchannel']: - if mtu not in [existing['sysmtu'], '1500']: - module.fail_json(msg='MTU on L2 interfaces can only be set' - ' to the system default (1500) or ' - 'existing sysmtu value which is ' - ' {0}'.format(existing['sysmtu'])) - elif mode == 'layer3': - if intf_type in ['ethernet', 'portchannel', 'svi']: - if ((int(mtu) < 576 or int(mtu) > 9216) or - ((int(mtu) % 2) != 0)): - module.fail_json(msg='Invalid MTU for Layer 3 interface' - 'needs to be an even number between' - '576 and 9216') - if sysmtu: - if ((int(sysmtu) < 576 or int(sysmtu) > 9216 or - ((int(sysmtu) % 2) != 0))): - module.fail_json(msg='Invalid MTU- needs to be an even ' - 'number between 576 and 9216') - - args = dict(mtu=mtu, sysmtu=sysmtu) - 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 - commands = [] - - if state == 'present': - if delta: - command = get_commands_config_mtu(delta, interface) - commands.append(command) - - elif state == 'absent': - common = set(proposed.items()).intersection(existing.items()) - if common: - command = get_commands_remove_mtu(dict(common), interface) - commands.append(command) - - cmds = flatten_list(commands) - if cmds: - if module.check_mode: - module.exit_json(changed=True, commands=cmds) - else: - changed = True - load_config(module, cmds) - if interface: - end_state = get_mtu(interface, module) - else: - end_state = get_system_mtu(module) - if 'configure' in cmds: - cmds.pop(0) - - results = {} - results['proposed'] = proposed - results['existing'] = existing - results['end_state'] = end_state - results['updates'] = cmds - results['changed'] = changed - results['warnings'] = warnings - - module.exit_json(**results) +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module() diff --git a/lib/ansible/modules/network/nxos/_nxos_portchannel.py b/lib/ansible/modules/network/nxos/_nxos_portchannel.py index 1d113f382ce..575dfe8de1c 100644 --- a/lib/ansible/modules/network/nxos/_nxos_portchannel.py +++ b/lib/ansible/modules/network/nxos/_nxos_portchannel.py @@ -25,7 +25,10 @@ DOCUMENTATION = ''' module: nxos_portchannel extends_documentation_fragment: nxos version_added: "2.2" -deprecated: Deprecated in 2.5. Use M(nxos_linkagg) instead. +deprecated: + removed_in: "2.9" + why: Replaced with common C(*_linkagg) network modules. + alternative: Use M(nxos_linkagg) instead. short_description: Manages port-channel interfaces. description: - Manages port-channel specific configuration parameters. diff --git a/lib/ansible/modules/network/nxos/_nxos_switchport.py b/lib/ansible/modules/network/nxos/_nxos_switchport.py index d736d97b87b..3a726b49644 100644 --- a/lib/ansible/modules/network/nxos/_nxos_switchport.py +++ b/lib/ansible/modules/network/nxos/_nxos_switchport.py @@ -25,7 +25,10 @@ DOCUMENTATION = ''' module: nxos_switchport extends_documentation_fragment: nxos version_added: "2.1" -deprecated: Use M(nxos_l2_interface) instead. +deprecated: + removed_in: "2.9" + why: Replaced with generic version. + alternative: Use M(nxos_l2_interface) instead. short_description: Manages Layer 2 switchport interfaces. description: - Manages Layer 2 interfaces diff --git a/lib/ansible/modules/network/panos/panos_nat_policy.py b/lib/ansible/modules/network/panos/_panos_nat_policy.py similarity index 98% rename from lib/ansible/modules/network/panos/panos_nat_policy.py rename to lib/ansible/modules/network/panos/_panos_nat_policy.py index 67cdefbf0bd..a476af0b962 100644 --- a/lib/ansible/modules/network/panos/panos_nat_policy.py +++ b/lib/ansible/modules/network/panos/_panos_nat_policy.py @@ -30,7 +30,10 @@ author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" version_added: "2.3" requirements: - pan-python -deprecated: In 2.4 use M(panos_nat_rule) instead. +deprecated: + removed_in: "2.8" + why: M(panos_nat_rule) uses next generation SDK (PanDevice). + alternative: Use M(panos_nat_rule) instead. options: ip_address: description: @@ -143,7 +146,7 @@ RETURN = ''' ''' ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], + 'status': ['deprecated'], 'supported_by': 'community'} diff --git a/lib/ansible/modules/network/panos/panos_security_policy.py b/lib/ansible/modules/network/panos/_panos_security_policy.py similarity index 98% rename from lib/ansible/modules/network/panos/panos_security_policy.py rename to lib/ansible/modules/network/panos/_panos_security_policy.py index 3d35964e132..34fb151cbe1 100644 --- a/lib/ansible/modules/network/panos/panos_security_policy.py +++ b/lib/ansible/modules/network/panos/_panos_security_policy.py @@ -20,7 +20,7 @@ # along with Ansible. If not, see . ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], + 'status': ['deprecated'], 'supported_by': 'community'} @@ -35,7 +35,10 @@ description: traffic is applied, the more specific rules must precede the more general ones. author: "Ivan Bojer (@ivanbojer)" version_added: "2.3" -deprecated: In 2.4 use M(panos_security_rule) instead. +deprecated: + removed_in: "2.8" + why: Renamed to M(panos_security_rule) in order to align with API calls and UI object references, which also has extra support for PanDevice SDK. + alternative: Use M(panos_security_rule) instead. requirements: - pan-python can be obtained from PyPi U(https://pypi.python.org/pypi/pan-python) - pandevice can be obtained from PyPi U(https://pypi.python.org/pypi/pandevice) diff --git a/lib/ansible/modules/utilities/helper/_accelerate.py b/lib/ansible/modules/utilities/helper/_accelerate.py index e14535810c0..e0fbe8c068e 100644 --- a/lib/ansible/modules/utilities/helper/_accelerate.py +++ b/lib/ansible/modules/utilities/helper/_accelerate.py @@ -16,11 +16,14 @@ ANSIBLE_METADATA = {'metadata_version': '1.1', DOCUMENTATION = ''' --- module: accelerate -removed: True short_description: Enable accelerated mode on remote node -deprecated: "Use SSH with ControlPersist instead." +deprecated: + removed_in: "2.4" + why: Replaced by ControlPersist + alternative: Use SSH with ControlPersist instead. + removed: True description: - - This module has been removed, this file is kept for historicaly documentation purposes + - This module has been removed, this file is kept for historical documentation purposes. - This modules launches an ephemeral I(accelerate) daemon on the remote node which Ansible can use to communicate with nodes at high speed. - The daemon listens on a configurable port for a configurable amount of time. @@ -77,3 +80,8 @@ EXAMPLES = ''' tasks: - command: /usr/bin/anything ''' + +from ansible.module_utils.common.removed import removed_module + +if __name__ == '__main__': + removed_module() diff --git a/lib/ansible/modules/utilities/logic/_include.py b/lib/ansible/modules/utilities/logic/_include.py index 9afc17ad980..025b792184a 100644 --- a/lib/ansible/modules/utilities/logic/_include.py +++ b/lib/ansible/modules/utilities/logic/_include.py @@ -20,8 +20,9 @@ author: Ansible Core Team (@ansible) module: include short_description: Include a play or task list deprecated: - The include action was too confusing, dealing with both plays and tasks, being both dynamic and static. This module - will be removed in version 2.8. As alternatives use M(include_tasks), M(import_playbook), M(import_tasks). + removed_in: "2.8" + why: The include action was too confusing, dealing with both plays and tasks, being both dynamic and static. This module will be removed in version 2.8. + alternative: Use M(include_tasks), M(import_playbook), M(import_tasks). description: - Includes a file with a list of plays or tasks to be executed in the current playbook. - Files with a list of plays can only be included at the top level. Lists of tasks can only be included where tasks diff --git a/lib/ansible/modules/windows/_win_msi.py b/lib/ansible/modules/windows/_win_msi.py index aeed7556a02..d1cb6898260 100644 --- a/lib/ansible/modules/windows/_win_msi.py +++ b/lib/ansible/modules/windows/_win_msi.py @@ -29,7 +29,10 @@ DOCUMENTATION = r''' --- module: win_msi version_added: '1.7' -deprecated: In 2.4 and will be removed in 2.8, use M(win_package) instead. +deprecated: + removed_in: "2.8" + why: The win_msi module has a number of issues, the M(win_package) module is easier to maintain and use. + alternative: Use M(win_package) instead. short_description: Installs and uninstalls Windows MSI files description: - Installs or uninstalls a Windows MSI file that is already located on the diff --git a/test/integration/nxos.yaml b/test/integration/nxos.yaml index ace802dbd6a..49c8cb0a2ae 100644 --- a/test/integration/nxos.yaml +++ b/test/integration/nxos.yaml @@ -105,15 +105,6 @@ failed_modules: "{{ failed_modules }} + [ 'nxos_vxlan_vtep_vni' ]" test_failed: true - - block: - - include_role: - name: nxos_mtu - when: "limit_to in ['*', 'nxos_mtu']" - rescue: - - set_fact: - failed_modules: "{{ failed_modules }} + [ 'nxos_mtu' ]" - test_failed: true - - block: - include_role: name: nxos_system diff --git a/test/integration/targets/docker/aliases b/test/integration/targets/docker/aliases deleted file mode 100644 index 8e7d715f9c0..00000000000 --- a/test/integration/targets/docker/aliases +++ /dev/null @@ -1,2 +0,0 @@ -destructive -posix/ci/group1 diff --git a/test/integration/targets/docker/files/devdockerCA.crt b/test/integration/targets/docker/files/devdockerCA.crt deleted file mode 100644 index 14f1b2f7ee6..00000000000 --- a/test/integration/targets/docker/files/devdockerCA.crt +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID3TCCAsWgAwIBAgIJAPczDjnFOjH/MA0GCSqGSIb3DQEBCwUAMIGEMQswCQYD -VQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBkR1cmhhbTEQMA4GA1UECgwH -QW5zaWJsZTEfMB0GA1UEAwwWZG9ja2VydGVzdC5hbnNpYmxlLmNvbTEkMCIGCSqG -SIb3DQEJARYVdGt1cmF0b21pQGFuc2libGUuY29tMB4XDTE1MDMxNzIyMjc1OVoX -DTQyMDgwMjIyMjc1OVowgYQxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEPMA0G -A1UEBwwGRHVyaGFtMRAwDgYDVQQKDAdBbnNpYmxlMR8wHQYDVQQDDBZkb2NrZXJ0 -ZXN0LmFuc2libGUuY29tMSQwIgYJKoZIhvcNAQkBFhV0a3VyYXRvbWlAYW5zaWJs -ZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIk4D0+QY3obQM -I/BPmI4pFFu734HHz98ce6Qat7WYiGUHsnt3LHw2a6zMsgP3siD1zqGHtk1IipWR -IwZbXm1spww/8YNUEE8wbXlLGI8IPUpg2J7NS2SdYIuN/TrQMqCUt7fFb+7OQjaH -RtR0LtXhP96al3E8BR9G6AiS67XuwdTL4vrXLUWISjNyF2Vj7xQsp8KRrq0qnXhq -pefeBi1fD9DG5f76j3s8lqGiOg9FHegvfodonNGcqE16T/vBhQcf+NjenlFvR2Lh -3wb/RCo/b1IhZHKNx32fJ/WpiKXkrLYFvwtIWtLw6XIwwarc+n7AfGqKnt4h4bAG -a+5aNnlFAgMBAAGjUDBOMB0GA1UdDgQWBBRZpu6oomSlpCvy2VgOHbWwDwVl1jAf -BgNVHSMEGDAWgBRZpu6oomSlpCvy2VgOHbWwDwVl1jAMBgNVHRMEBTADAQH/MA0G -CSqGSIb3DQEBCwUAA4IBAQCqOSFzTgQDww5bkNRCQrg7lTKzXW9bJpJ5NZdTLwh6 -b+e+XouRH+lBe7Cnn2RTtuFYVfm8hQ1Ra7GDM3v2mJns/s3zDkRINZMMVXddzl5S -M8QxsFJK41PaL9wepizslkcg19yQkdWJQYPDeFurlFvwtakhZE7ttawYi5bFkbCd -4fchMNBBmcigpSfoWb/L2lK2vVKBcfOdUl+V6k49lpf8u7WZD0Xi2cbBhw17tPj4 -ulKZaVNdzj0GFfhpQe/MtDoqxStRpHamdk0Y6fN+CvoW7RPDeVsqkIgCu30MOFuG -A53ZtOc3caYRyGYJtIIl0Rd5uIApscec/6RGiFX6Gab8 ------END CERTIFICATE----- diff --git a/test/integration/targets/docker/files/devdockerCA.key b/test/integration/targets/docker/files/devdockerCA.key deleted file mode 100644 index 0c8c0ee7b0c..00000000000 --- a/test/integration/targets/docker/files/devdockerCA.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEAyJOA9PkGN6G0DCPwT5iOKRRbu9+Bx8/fHHukGre1mIhlB7J7 -dyx8NmuszLID97Ig9c6hh7ZNSIqVkSMGW15tbKcMP/GDVBBPMG15SxiPCD1KYNie -zUtknWCLjf060DKglLe3xW/uzkI2h0bUdC7V4T/empdxPAUfRugIkuu17sHUy+L6 -1y1FiEozchdlY+8ULKfCka6tKp14aqXn3gYtXw/QxuX++o97PJahojoPRR3oL36H -aJzRnKhNek/7wYUHH/jY3p5Rb0di4d8G/0QqP29SIWRyjcd9nyf1qYil5Ky2Bb8L -SFrS8OlyMMGq3Pp+wHxqip7eIeGwBmvuWjZ5RQIDAQABAoIBAQCVOumfWgf+LBlB -TxvknKRoe/Ukes6cU1S0ZGlcV4KM0i4Y4/poWHiyJLqUMX4yNB3BxNL5nfEyH6nY -Ki74m/Dd/gtnJ9GGIfxJE6pC7Sq9/pvwIjtEkutxC/vI0LeJX6GKBIZ+JyGN5EWd -sF0xdAc9Z7+/VR2ygj0bDFgUt7rMv6fLaXh6i5Ms0JV7I/HkIi0Lmy9FncJPOTjP -/Wb3Rj5twDppBqSiqU2JNQHysWzNbp8nzBGeR0+WU6xkWjjGzVyQZJq4XJQhqqot -t+v+/lF+jObujcRxPRStaA5IoQdmls3l+ubkoFeNp3j6Nigz40wjTJArMu/Q9xQ5 -A+kHYNgBAoGBAPVNku0eyz1SyMM8FNoB+AfSpkslTnqfmehn1GCOOS9JPimGWS3A -UlAs/PAPW/H/FTM38eC89GsKKVV8zvwkERNwf+PIGzkQrJgYLxGwoflAKsvFoQi9 -PVbIn0TBDZ3TWyNfGul62fEgNen4B46d7kG6l/C3p9eKKCo3sCBgWl8FAoGBANFS -n9YWyAYmHQAWy5R0YeTsdtiRpZWkB0Is9Jr8Zm/DQDNnsKgvXw//qxuWYMi68teK -6o8t5mgDQNWBu3rXrU73f8mMVJNmzSHFbyQEyFOJ9yvI5qMRbJfvdURUje6d3ZUw -G7olKjX0fec4cAG7hbT8sMDvIbnATdhh3VppiEVBAoGBAJKidJnaNpPJ0MkkOTK4 -ypOikFWLT4ZtsYsDxiiR3A0wM0CPVu/Kb2oN+oVmKQhX+0xKvQQi79iskljP6ss+ -pBaCwXBgRiWumf2xNzHT7H8apHp7APBAb1JZSxvGa2VU2r4iM+wty+of3xqlcZ8H -OU2BRSJYJrTpmWjjMR2pe1whAoGAfMTbMSlzIPcm4h60SlD06Rdp370xDfkvumpB -gwBfrs6bPgjYa+eQqmCjBValagDFL2VGWwHpDKajxqAFuDtGuoMcUG6tGw9zxmWA -0d9n6SObiSW/FAQWzpmVNJ2R3GGM6pg6bsIoXvDU+zXQzbeRA0h7swTW/Xl67Teo -UXQGHgECgYEAjckqv2e39AgBvjxvj9SylVbFNSERrbpmiIRH31MnAHpTXbxRf7K+ -/79vUsRfQun9F/+KVfjUyMqRj0PE2tS4ATIjqQsa18RCB4mAE3sNsKz8HbJfzIFq -eEqAWmURm6gRmLmaTMlXS0ZtZaw/A2Usa/DJumu9CsfBu7ZJbDnrQIY= ------END RSA PRIVATE KEY----- diff --git a/test/integration/targets/docker/files/devdockerCA.srl b/test/integration/targets/docker/files/devdockerCA.srl deleted file mode 100644 index 78f0162afec..00000000000 --- a/test/integration/targets/docker/files/devdockerCA.srl +++ /dev/null @@ -1 +0,0 @@ -D96F3E552F279F46 diff --git a/test/integration/targets/docker/files/docker-registry.htpasswd b/test/integration/targets/docker/files/docker-registry.htpasswd deleted file mode 100644 index 7cee295817c..00000000000 --- a/test/integration/targets/docker/files/docker-registry.htpasswd +++ /dev/null @@ -1 +0,0 @@ -testdocker:$apr1$6cYd3tA9$4Dc9/I5Z.bl8/br8O/6B41 diff --git a/test/integration/targets/docker/files/dockertest.ansible.com.crt b/test/integration/targets/docker/files/dockertest.ansible.com.crt deleted file mode 100644 index e89327c3faf..00000000000 --- a/test/integration/targets/docker/files/dockertest.ansible.com.crt +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDYTCCAkkCCQDZbz5VLyefRjANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMC -VVMxCzAJBgNVBAgMAk5DMQ8wDQYDVQQHDAZEdXJoYW0xEDAOBgNVBAoMB0Fuc2li -bGUxHzAdBgNVBAMMFmRvY2tlcnRlc3QuYW5zaWJsZS5jb20xJDAiBgkqhkiG9w0B -CQEWFXRrdXJhdG9taUBhbnNpYmxlLmNvbTAgFw0xNTAzMTcyMjMxNTBaGA8yMjg4 -MTIzMDIyMzE1MFowXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5DMQ8wDQYDVQQH -DAZEdXJoYW0xEDAOBgNVBAoMB0Fuc2libGUxHzAdBgNVBAMMFmRvY2tlcnRlc3Qu -YW5zaWJsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7WpI3 -QuuARgPufAA0JkGCGIUNWqFyTEngOWvBVEuk5TnDB4x78OCE9j7rr75OxZaSc6Y7 -oFTl+hhlgt6sqj+GXehgCHLA97CCc8eUqGv3bwdIIg/hahCPjEWfYzocX1xmUdzN -6klbV9lSO7FGSuk7W4DNga/weRfZmVoPi6jqTvx0tFsGrHVb1evholUKpxaOEYQZ -2NJ22+UXpUyVzN/mw5TAGNG0/yR7sIgCjKYCsYF8k79SfNDMJ1VcCPy3aag45jaz -WoA+OIJJFRkAaPSM5VtnbGBv/slpDVaKfl2ei7Ey3mKx1b7jYMzRz07Gw+zqr1gJ -kBWvfjR7ioxXcN7jAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAJyF24tCq5R8SJto -EMln0m9dMoJTC5usaBYBUMMe6hV2ikUGaXVDIqY+Yypt1sIcjGnLRmehJbej8iS7 -4aypuLc8Fgb4CvW+gY3I3W1iF7ZxIN/4yr237Z9KH1d1uGi+066Sk94OCXlqgsb+ -RzU6XOg+PMIjYC/us5VRv8a2qfjIA8getR+19nP+hR6NgIQcEyRKG2FmhkUSAwd8 -60FhpW4UmPQmn0ErZmRwdp2hNPj5g3my5iOSi7DzdK4CwZJAASOoWsbQIxP0k4JE -PMo7Ad1YxXlOvNWIA8FLMkRsq3li6KJ17WBdEYgFeuxWpf1/x1WA+WpwEIfC5cuR -A5LkaNI= ------END CERTIFICATE----- diff --git a/test/integration/targets/docker/files/dockertest.ansible.com.csr b/test/integration/targets/docker/files/dockertest.ansible.com.csr deleted file mode 100644 index 62b1f8535ac..00000000000 --- a/test/integration/targets/docker/files/dockertest.ansible.com.csr +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIICozCCAYsCAQAwXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5DMQ8wDQYDVQQH -DAZEdXJoYW0xEDAOBgNVBAoMB0Fuc2libGUxHzAdBgNVBAMMFmRvY2tlcnRlc3Qu -YW5zaWJsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7WpI3 -QuuARgPufAA0JkGCGIUNWqFyTEngOWvBVEuk5TnDB4x78OCE9j7rr75OxZaSc6Y7 -oFTl+hhlgt6sqj+GXehgCHLA97CCc8eUqGv3bwdIIg/hahCPjEWfYzocX1xmUdzN -6klbV9lSO7FGSuk7W4DNga/weRfZmVoPi6jqTvx0tFsGrHVb1evholUKpxaOEYQZ -2NJ22+UXpUyVzN/mw5TAGNG0/yR7sIgCjKYCsYF8k79SfNDMJ1VcCPy3aag45jaz -WoA+OIJJFRkAaPSM5VtnbGBv/slpDVaKfl2ei7Ey3mKx1b7jYMzRz07Gw+zqr1gJ -kBWvfjR7ioxXcN7jAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAoPgw9dlA3Ys2 -oahtr2KMNFnHnab6hUr/CuDIygkOft+MCX1cPXY1c0R72NQq42TjAFO5UnriJ0Jg -rcWgBAw8TCOHH77ZWawQFjWWoxNTy+bfXNJ002tzc4S/A4s8ytcFQN7E2irbGtUB -ratVaE+c6RvD/o48N4YLUyJbJK84FZ1xMnJI0z5R6XzDWEqYbobzkM/aUWvDTT9F -+F9H5W/3sIhNFVGLygSKbhgrb6eaC8R36fcmTRfYYdT4GrpXFePoZ4LJGCKiiaGV -p8gZzYQ9xjRYDP2OUMacBDlX1Mu5IJ2SCfjavD1hMhB54tWiiw3CRMJcNMql7ob/ -ZHH8UDMqgA== ------END CERTIFICATE REQUEST----- diff --git a/test/integration/targets/docker/files/dockertest.ansible.com.key b/test/integration/targets/docker/files/dockertest.ansible.com.key deleted file mode 100644 index bda2bb61262..00000000000 --- a/test/integration/targets/docker/files/dockertest.ansible.com.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAu1qSN0LrgEYD7nwANCZBghiFDVqhckxJ4DlrwVRLpOU5wweM -e/DghPY+66++TsWWknOmO6BU5foYZYLerKo/hl3oYAhywPewgnPHlKhr928HSCIP -4WoQj4xFn2M6HF9cZlHczepJW1fZUjuxRkrpO1uAzYGv8HkX2ZlaD4uo6k78dLRb -Bqx1W9Xr4aJVCqcWjhGEGdjSdtvlF6VMlczf5sOUwBjRtP8ke7CIAoymArGBfJO/ -UnzQzCdVXAj8t2moOOY2s1qAPjiCSRUZAGj0jOVbZ2xgb/7JaQ1Win5dnouxMt5i -sdW+42DM0c9OxsPs6q9YCZAVr340e4qMV3De4wIDAQABAoIBABjczxSIS+pM4E6w -o/JHtV/HUzjPcydQ2mjoFdWlExjB1qV8BfeYoqLibr0mKFIZxH6Q3FmDUGDojH5E -HLq7KQzyv1inJltXQ1Q8exrOMu22DThUVNksEyCJk9+v8lE7km59pJiq46s8gDl6 -dG8Il+TporEi6a820qRsxlfTx8m4EUbyPIhf2e2wYdqiscLwj49ZzMs3TFJxN3j4 -lLP3QDHz9n8q+XXpUT9+rsePe4D4DVVRLhg8w35zkys36xfvBZrI+9SytSs+r1/e -X4gVhxeX9q3FkvXiw1IDGPr0l5X7SH+5zk7JWuLfFbNBK02zR/Bd2OIaYAOmyIFk -ZzsVfokCgYEA8Cj04S32Tga7lOAAUEuPjgXbCtGYqBUJ/9mlMHJBtyl4vaBRm1Z3 -1YQqlL3yGM1F6ZStPWs86vsVaScypr7+RnmQ/uPjz1g2jNI9vomqRkzpzd8/bBwW -J3FCaKFIfl9uQx4ac7piAYdhNXswjQ7Kzn5xgG24i8EkUm6+UxarA38CgYEAx7X+ -qOVT+kA5WU1EDIc2x3Au0PhNIXiHOGRLW0MC7Vy1xBrgxfVrz6J8flBXOxmWYjRq -3dFiHA9S7WPQStkgTjzE91sthLefJ8DKXE4IrRkvYXIIX8DqkcFxTHS/OzckTcK/ -z79jNOPYA1s+z2jzgd24sslXbqxNz1LqZ/PlRp0CgYEAik8cEF72/aK0/x0uMRAD -IcjPiGCDKTHMq3M9xjPXEtQofBTLSsm2g9n05+qodY4qmEYOq1OKJs3pW8C+U/ek -2xOB5Ll75lqoN9uQwZ3o2UnMUMskbG+UdqyskTNpW5Y8Gx1IIKQTc0vzOOi0YlhF -hjydw1ftM1dNQsgShimE3aMCgYEAwITwFk7kcoTBBBZY+B7Mrtu1Ndt3N0HiUHlW -r4Zc5waNbptefVbF9GY1zuqR/LYA43CWaHj1NAmNrqye2diPrPwmADHUInGEqqTO -LsdG099Ibo6oBe6J8bJiDwsoYeQZSiDoGVPtRcoyraGjXfxVaaac6zTu5RCS/b53 -m3hhWH0CgYAqi3x10NpJHInU/zNa1GhI9UVJzabE2APdbPHvoE/yyfpCGhExiXZw -MDImUzc59Ro0pCZ9Bk7pd5LwdjjeJXih7jaRZQlPD1BeM6dKdmJps1KMaltOOJ4J -W0FE34E+Kt5JeIix8zmhxgaAU9NVilaNx5tI/D65Y0inMBZpqedrtg== ------END RSA PRIVATE KEY----- diff --git a/test/integration/targets/docker/files/nginx-docker-registry.conf b/test/integration/targets/docker/files/nginx-docker-registry.conf deleted file mode 100644 index 99c7802e1bf..00000000000 --- a/test/integration/targets/docker/files/nginx-docker-registry.conf +++ /dev/null @@ -1,40 +0,0 @@ -# For versions of Nginx > 1.3.9 that include chunked transfer encoding support -# Replace with appropriate values where necessary - -upstream docker-registry { - server localhost:5000; -} - -server { - listen 8080; - server_name dockertest.ansible.com; - - ssl on; - ssl_certificate /etc/pki/tls/certs/dockertest.ansible.com.crt; - ssl_certificate_key /etc/pki/tls/private/dockertest.ansible.com.key; - - proxy_set_header Host $http_host; # required for Docker client sake - proxy_set_header X-Real-IP $remote_addr; # pass on real client IP - - client_max_body_size 0; # disable any limits to avoid HTTP 413 for large image uploads - - # required to avoid HTTP 411: see Issue #1486 (https://github.com/dotcloud/docker/issues/1486) - chunked_transfer_encoding on; - - location / { - # let Nginx know about our auth file - auth_basic "Restricted"; - auth_basic_user_file /etc/nginx/docker-registry.htpasswd; - - proxy_pass http://docker-registry; - } - location /_ping { - auth_basic off; - proxy_pass http://docker-registry; - } - location /v1/_ping { - auth_basic off; - proxy_pass http://docker-registry; - } - -} diff --git a/test/integration/targets/docker/meta/main.yml b/test/integration/targets/docker/meta/main.yml deleted file mode 100644 index 399f3fb6e77..00000000000 --- a/test/integration/targets/docker/meta/main.yml +++ /dev/null @@ -1,20 +0,0 @@ -# test code for the service module -# (c) 2014, James Cammarata - -# 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 . - -dependencies: - - prepare_tests diff --git a/test/integration/targets/docker/tasks/docker-setup-debian.yml b/test/integration/targets/docker/tasks/docker-setup-debian.yml deleted file mode 100644 index afe4b046dc6..00000000000 --- a/test/integration/targets/docker/tasks/docker-setup-debian.yml +++ /dev/null @@ -1,5 +0,0 @@ -- name: Install docker packages (apt) - apt: - state: present - # Note: add docker-registry when available - name: docker.io,python-docker,netcat-openbsd,nginx diff --git a/test/integration/targets/docker/tasks/docker-setup-rht.yml b/test/integration/targets/docker/tasks/docker-setup-rht.yml deleted file mode 100644 index b8c9e3816e3..00000000000 --- a/test/integration/targets/docker/tasks/docker-setup-rht.yml +++ /dev/null @@ -1,16 +0,0 @@ -- name: Install docker packages (rht family) - package: - state: present - name: docker-io,docker-registry,python-docker-py,nginx - -- name: Install netcat (Fedora) - package: - state: present - name: nmap-ncat - when: ansible_distribution == 'Fedora' or (ansible_os_family == 'RedHat' and ansible_distribution_version is version(7, '>=')) - -- name: Install netcat (RHEL) - package: - state: present - name: nc - when: ansible_distribution != 'Fedora' and (ansible_os_family == 'RedHat' and ansible_distribution_version is version(7, '<')) diff --git a/test/integration/targets/docker/tasks/docker-tests.yml b/test/integration/targets/docker/tasks/docker-tests.yml deleted file mode 100644 index 14e23f72dd5..00000000000 --- a/test/integration/targets/docker/tasks/docker-tests.yml +++ /dev/null @@ -1,58 +0,0 @@ -- name: Start docker daemon - service: - name: docker - state: started - -- name: Download busybox image - docker: - image: busybox - state: present - pull: missing - -- name: Run a small script in busybox - docker: - image: busybox - state: reloaded - pull: always - command: "nc -l -p 2000 -e xargs -n1 echo hello" - detach: True - -- name: Get the docker container ip - set_fact: container_ip="{{docker_containers[0].NetworkSettings.IPAddress}}" - -- name: Try to access the server - shell: "echo 'world' | nc {{ container_ip }} 2000" - register: docker_output - -- name: check that the script ran - assert: - that: - - "'hello world' in docker_output.stdout_lines" - -- name: Run a script that sets environment in busybox - docker: - image: busybox - state: reloaded - pull: always - env: - TEST: hello - command: '/bin/sh -c "nc -l -p 2000 -e xargs -n1 echo $TEST"' - detach: True - -- name: Get the docker container ip - set_fact: container_ip="{{docker_containers[0].NetworkSettings.IPAddress}}" - -- name: Try to access the server - shell: "echo 'world' | nc {{ container_ip }} 2000" - register: docker_output - -- name: check that the script ran - assert: - that: - - "'hello world' in docker_output.stdout_lines" - -- name: Remove containers - shell: "docker rm -f $(docker ps -aq)" - -- name: Remove all images from the local docker - shell: "docker rmi -f $(docker images -q)" diff --git a/test/integration/targets/docker/tasks/main.yml b/test/integration/targets/docker/tasks/main.yml deleted file mode 100644 index 31e42c83e66..00000000000 --- a/test/integration/targets/docker/tasks/main.yml +++ /dev/null @@ -1,22 +0,0 @@ -#- include: docker-setup-rht.yml -# when: ansible_distribution in ['Fedora'] -#- include: docker-setup-rht.yml - # Packages on RHEL and CentOS 7 are broken, broken, broken. Revisit when - # they've got that sorted out - # CentOS 6 currently broken by conflicting files in python-backports and python-backports-ssl_match_hostname - #when: ansible_distribution in ['RedHat', 'CentOS'] and ansible_lsb.major_release|int == 6 - -# python-docker isn't available until 14.10. Revist at the next Ubuntu LTS -#- include: docker-setup-debian.yml -# when: ansible_distribution in ['Ubuntu'] - -#- include: docker-tests.yml - # Add other distributions as the proper packages become available -# when: ansible_distribution in ['Fedora'] - -#- include: docker-tests.yml -# when: ansible_distribution in ['RedHat', 'CentOS'] and ansible_lsb.major_release|int == 6 - -#- include: registry-tests.yml - # Add other distributions as the proper packages become available -# when: ansible_distribution in ['Fedora'] diff --git a/test/integration/targets/docker/tasks/registry-tests.yml b/test/integration/targets/docker/tasks/registry-tests.yml deleted file mode 100644 index e17c383a095..00000000000 --- a/test/integration/targets/docker/tasks/registry-tests.yml +++ /dev/null @@ -1,188 +0,0 @@ -- name: Configure a private docker registry - service: - name: docker-registry - state: started - -- name: Retrieve busybox image from docker hub - docker: - image: busybox - state: present - pull: missing - -- name: Get busybox image id - shell: "docker images | grep busybox | awk '{ print $3 }'" - register: image_id - -- name: Tag docker image into the local registry - command: "docker tag {{ image_id.stdout_lines[0] }} localhost:5000/mine" - -- name: Push docker image into the private registry - command: "docker push localhost:5000/mine" - -- name: Remove all images from the local docker - shell: "docker rmi -f {{image_id.stdout_lines[0]}}" - -- name: Get number of images in docker - command: "docker images" - register: docker_output - -# docker prints a header so the header should be all that's present -- name: Check that there are no images in docker - assert: - that: - - "{{ docker_output.stdout_lines| length }} <= 1 " - -- name: Retrieve the image from private docker registry - docker: - image: "localhost:5000/mine" - state: present - pull: missing - insecure_registry: True - -- name: Run a small script in the new image - docker: - image: "localhost:5000/mine" - state: reloaded - pull: always - command: "nc -l -p 2000 -e xargs -n1 echo hello" - detach: True - insecure_registry: True - -- name: Get the docker container id - shell: "docker ps | grep mine | awk '{ print $1 }'" - register: container_id - -- name: Get the docker container ip - shell: "docker inspect {{ container_id.stdout_lines[0] }} | grep IPAddress | awk -F '\"' '{ print $4 }'" - register: container_ip - -- name: Pause a few moments because docker is not reliable - pause: - seconds: 40 - -- name: Try to access the server - shell: "echo 'world' | nc {{ container_ip.stdout_lines[0] }} 2000" - register: docker_output - -- name: check that the script ran - assert: - that: - - "'hello world' in docker_output.stdout_lines" - - -- name: Remove containers - shell: "docker rm -f $(docker ps -aq)" - -- shell: docker images -q -- name: Remove all images from the local docker - shell: "docker rmi -f $(docker images -q)" - -- name: Get number of images in docker - command: "docker images" - register: docker_output - -- name: Check that there are no images in docker - assert: - that: - - "{{ docker_output.stdout_lines| length }} <= 1" - -# -# Private registry secured with an SSL proxy -# - -- name: Set selinux to allow docker to connect to nginx - seboolean: - name: docker_connect_any - state: yes - -- name: Set selinux to allow nginx to connect to docker - seboolean: - name: httpd_can_network_connect - state: yes - -- name: Setup nginx with a user/password - copy: - src: docker-registry.htpasswd - dest: /etc/nginx/docker-registry.htpasswd - -- name: Setup nginx with a config file - copy: - src: nginx-docker-registry.conf - dest: /etc/nginx/conf.d/nginx-docker-registry.conf - -- name: Setup nginx docker cert - copy: - src: dockertest.ansible.com.crt - dest: /etc/pki/tls/certs/dockertest.ansible.com.crt - -- name: Setup nginx docker key - copy: - src: dockertest.ansible.com.key - dest: /etc/pki/tls/private/dockertest.ansible.com.key - -- name: Setup the ca keys - copy: - src: devdockerCA.crt - dest: /etc/pki/ca-trust/source/anchors/devdockerCA.crt - -- name: Update the ca bundle - command: update-ca-trust extract - -- name: Restart docker daemon - service: - name: docker - state: restarted - -- name: Start nginx - service: - name: nginx - state: restarted - -- name: Add domain name to hosts - lineinfile: - line: "127.0.0.1 dockertest.ansible.com" - dest: /etc/hosts - state: present - -- name: Start a container after getting it from a secured private registry - docker: - image: dockertest.ansible.com:8080/mine - registry: dockertest.ansible.com:8080 - username: "testdocker" - password: "testdocker" - state: running - command: "nc -l -p 2000 -e xargs -n1 echo hello" - detach: True - -- name: Get the docker container id - shell: "docker ps | grep mine | awk '{ print $1 }'" - register: container_id - -- name: Get the docker container ip - shell: "docker inspect {{ container_id.stdout_lines[0] }} | grep IPAddress | awk -F '\"' '{ print $4 }'" - register: container_ip - -- name: Pause a few moments because docker is not reliable - pause: - seconds: 40 - -- name: Try to access the server - shell: "echo 'world' | nc {{ container_ip.stdout_lines[0] }} 2000" - register: docker_output - -- name: check that the script ran - assert: - that: - - "'hello world' in docker_output.stdout_lines" - -- name: Remove containers - shell: "docker rm $(docker ps -aq)" - -- name: Remove all images from the local docker - shell: "docker rmi -f $(docker images -q)" - -- name: Remove domain name to hosts - lineinfile: - line: "127.0.0.1 dockertest.ansible.com" - dest: /etc/hosts - state: absent diff --git a/test/integration/targets/ec2_vpc/aliases b/test/integration/targets/ec2_vpc/aliases deleted file mode 100644 index d6ae2f116bc..00000000000 --- a/test/integration/targets/ec2_vpc/aliases +++ /dev/null @@ -1,2 +0,0 @@ -cloud/aws -posix/ci/cloud/group4/aws diff --git a/test/integration/targets/ec2_vpc/defaults/main.yml b/test/integration/targets/ec2_vpc/defaults/main.yml deleted file mode 100644 index 4487c4a8d8a..00000000000 --- a/test/integration/targets/ec2_vpc/defaults/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -# defaults file for test_ec2_vpc diff --git a/test/integration/targets/ec2_vpc/meta/main.yml b/test/integration/targets/ec2_vpc/meta/main.yml deleted file mode 100644 index 1f64f1169a9..00000000000 --- a/test/integration/targets/ec2_vpc/meta/main.yml +++ /dev/null @@ -1,3 +0,0 @@ -dependencies: - - prepare_tests - - setup_ec2 diff --git a/test/integration/targets/ec2_vpc/tasks/main.yml b/test/integration/targets/ec2_vpc/tasks/main.yml deleted file mode 100644 index 28cad8e5228..00000000000 --- a/test/integration/targets/ec2_vpc/tasks/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -# tasks file for test_ec2_vpc diff --git a/test/integration/targets/ec2_vpc/vars/main.yml b/test/integration/targets/ec2_vpc/vars/main.yml deleted file mode 100644 index e0fe3ae8e4d..00000000000 --- a/test/integration/targets/ec2_vpc/vars/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -# vars file for test_ec2_vpc diff --git a/test/integration/targets/nxos_mtu/defaults/main.yaml b/test/integration/targets/nxos_mtu/defaults/main.yaml deleted file mode 100644 index 5f709c5aac1..00000000000 --- a/test/integration/targets/nxos_mtu/defaults/main.yaml +++ /dev/null @@ -1,2 +0,0 @@ ---- -testcase: "*" diff --git a/test/integration/targets/nxos_mtu/meta/main.yml b/test/integration/targets/nxos_mtu/meta/main.yml deleted file mode 100644 index ae741cbdc71..00000000000 --- a/test/integration/targets/nxos_mtu/meta/main.yml +++ /dev/null @@ -1,2 +0,0 @@ -dependencies: - - prepare_nxos_tests diff --git a/test/integration/targets/nxos_mtu/tasks/cli.yaml b/test/integration/targets/nxos_mtu/tasks/cli.yaml deleted file mode 100644 index edbff7dfafb..00000000000 --- a/test/integration/targets/nxos_mtu/tasks/cli.yaml +++ /dev/null @@ -1,33 +0,0 @@ ---- -- name: collect common cli test cases - find: - paths: "{{ role_path }}/tests/common" - patterns: "{{ testcase }}.yaml" - connection: local - register: test_cases - -- name: collect cli test cases - find: - paths: "{{ role_path }}/tests/cli" - patterns: "{{ testcase }}.yaml" - connection: local - register: cli_cases - -- set_fact: - test_cases: - files: "{{ test_cases.files }} + {{ cli_cases.files }}" - -- name: set test_items - set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" - -- name: run test cases (connection=network_cli) - include: "{{ test_case_to_run }} ansible_connection=network_cli connection={}" - with_items: "{{ test_items }}" - loop_control: - loop_var: test_case_to_run - -- name: run test case (connection=local) - include: "{{ test_case_to_run }} ansible_connection=local connection={{ cli }}" - with_first_found: "{{ test_items }}" - loop_control: - loop_var: test_case_to_run diff --git a/test/integration/targets/nxos_mtu/tasks/main.yaml b/test/integration/targets/nxos_mtu/tasks/main.yaml deleted file mode 100644 index 4b0f8c64d90..00000000000 --- a/test/integration/targets/nxos_mtu/tasks/main.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -- { include: cli.yaml, tags: ['cli'] } -- { include: nxapi.yaml, tags: ['nxapi'] } diff --git a/test/integration/targets/nxos_mtu/tasks/nxapi.yaml b/test/integration/targets/nxos_mtu/tasks/nxapi.yaml deleted file mode 100644 index 68e96a29420..00000000000 --- a/test/integration/targets/nxos_mtu/tasks/nxapi.yaml +++ /dev/null @@ -1,27 +0,0 @@ ---- -- name: collect common nxapi test cases - find: - paths: "{{ role_path }}/tests/common" - patterns: "{{ testcase }}.yaml" - connection: local - register: test_cases - -- name: collect nxapi test cases - find: - paths: "{{ role_path }}/tests/nxapi" - patterns: "{{ testcase }}.yaml" - connection: local - register: nxapi_cases - -- set_fact: - test_cases: - files: "{{ test_cases.files }} + {{ nxapi_cases.files }}" - -- name: set test_items - set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" - -- name: run test cases (connection=local) - include: "{{ test_case_to_run }} ansible_connection=local connection={{ nxapi }}" - with_items: "{{ test_items }}" - loop_control: - loop_var: test_case_to_run diff --git a/test/integration/targets/nxos_mtu/tests/common/set_mtu.yaml b/test/integration/targets/nxos_mtu/tests/common/set_mtu.yaml deleted file mode 100644 index c8240e1c820..00000000000 --- a/test/integration/targets/nxos_mtu/tests/common/set_mtu.yaml +++ /dev/null @@ -1,70 +0,0 @@ ---- -- debug: msg="START connection={{ ansible_connection }}/set_mtu.yaml" -- debug: msg="Using provider={{ connection.transport }}" - when: ansible_connection == "local" - -- set_fact: testint="{{ nxos_int1 }}" - -- name: setup - nxos_config: - lines: - - no switchport - - no mtu - parents: "interface {{ testint }}" - match: none - provider: "{{ connection }}" - -- name: configure interface mtu - nxos_mtu: - interface: "{{ testint }}" - mtu: 2000 - provider: "{{ connection }}" - register: result - -- assert: - that: - - "result.changed == true" - -- name: verify interface mtu - nxos_mtu: - interface: "{{ testint }}" - mtu: 2000 - provider: "{{ connection }}" - register: result - -- assert: - that: - - "result.changed == false" - -- name: configure invalid (odd) interface mtu - nxos_mtu: - interface: "{{ testint }}" - mtu: 2001 - provider: "{{ connection }}" - register: result - ignore_errors: yes - -- assert: - that: - - "result.failed == true" - -- name: configure invalid (large) mtu setting - nxos_mtu: - interface: "{{ testint }}" - mtu: 100000 - provider: "{{ connection }}" - register: result - ignore_errors: yes - -- assert: - that: - - "result.failed == true" - -- name: teardown - nxos_config: - lines: no mtu - parents: "interface {{ testint }}" - match: none - provider: "{{ connection }}" - -- debug: msg="END connection={{ ansible_connection }}/set_mtu.yaml" diff --git a/test/integration/targets/nxos_mtu/tests/common/set_sysmtu.yaml b/test/integration/targets/nxos_mtu/tests/common/set_sysmtu.yaml deleted file mode 100644 index d16ab8255bd..00000000000 --- a/test/integration/targets/nxos_mtu/tests/common/set_sysmtu.yaml +++ /dev/null @@ -1,60 +0,0 @@ ---- -- debug: msg="START connection={{ ansible_connection }}/sysmtu.yaml" -- debug: msg="Using provider={{ connection.transport }}" - when: ansible_connection == "local" - -- name: setup - nxos_config: - lines: no system jumbomtu - match: none - provider: "{{ connection }}" - -- name: configure system mtu - nxos_mtu: - sysmtu: 2000 - provider: "{{ connection }}" - register: result - -- assert: - that: - - "result.changed == true" - -- name: verify system mtu - nxos_mtu: - sysmtu: 2000 - provider: "{{ connection }}" - register: result - -- assert: - that: - - "result.changed == false" - -- name: configure invalid (odd) system mtu - nxos_mtu: - sysmtu: 2001 - provider: "{{ connection }}" - register: result - ignore_errors: yes - -- assert: - that: - - "result.failed == true" - -- name: configure invalid (large) system mtu setting - nxos_mtu: - sysmtu: 10000 - provider: "{{ connection }}" - register: result - ignore_errors: yes - -- assert: - that: - - "result.failed == true" - -- name: teardown - nxos_config: - lines: no system jumbomtu - match: none - provider: "{{ connection }}" - -- debug: msg="END connection={{ ansible_connection }}/sysmtu.yaml" diff --git a/test/sanity/validate-modules/ignore.txt b/test/sanity/validate-modules/ignore.txt index 3670cedbcd0..8b2e443e0cb 100644 --- a/test/sanity/validate-modules/ignore.txt +++ b/test/sanity/validate-modules/ignore.txt @@ -427,7 +427,7 @@ lib/ansible/modules/network/ordnance/ordnance_facts.py E322 lib/ansible/modules/network/panos/panos_nat_rule.py E322 lib/ansible/modules/network/panos/panos_sag.py E322 lib/ansible/modules/network/panos/panos_sag.py E323 -lib/ansible/modules/network/panos/panos_security_policy.py E322 +lib/ansible/modules/network/panos/_panos_security_policy.py E322 lib/ansible/modules/network/panos/panos_security_rule.py E322 lib/ansible/modules/network/radware/vdirect_commit.py E321 lib/ansible/modules/network/radware/vdirect_file.py E321 diff --git a/test/sanity/validate-modules/main.py b/test/sanity/validate-modules/main.py index d9fe4baf7fb..71a881a67c5 100755 --- a/test/sanity/validate-modules/main.py +++ b/test/sanity/validate-modules/main.py @@ -505,7 +505,14 @@ class ModuleValidator(Validator): return min(linenos) - def _find_main_call(self): + def _find_main_call(self, look_for="main"): + """ Ensure that the module ends with: + if __name__ == '__main__': + main() + OR, in the case of modules that are in the docs-only deprecation phase + if __name__ == '__main__': + removed_module() + """ lineno = False if_bodies = [] for child in self.ast.body: @@ -547,13 +554,13 @@ class ModuleValidator(Validator): if isinstance(child, ast.Expr): if isinstance(child.value, ast.Call): if (isinstance(child.value.func, ast.Name) and - child.value.func.id == 'main'): + child.value.func.id == look_for): lineno = child.lineno if lineno < self.length - 1: self.reporter.error( path=self.object_path, code=104, - msg='Call to main() not the last line', + msg=('Call to %s() not the last line' % look_for), line=lineno ) @@ -561,7 +568,7 @@ class ModuleValidator(Validator): self.reporter.error( path=self.object_path, code=103, - msg='Did not find a call to main' + msg=('Did not find a call to %s()' % look_for) ) return lineno or 0 @@ -1220,10 +1227,18 @@ class ModuleValidator(Validator): ) return + end_of_deprecation_should_be_docs_only = False if self._python_module(): doc_info, docs = self._validate_docs() - if self._python_module() and not self._just_docs(): + # See if current version => deprecated.removed_in, ie, should be docs only + if 'deprecated' in docs and docs['deprecated'] is not None: + removed_in = docs.get('deprecated')['removed_in'] + strict_ansible_version = StrictVersion('.'.join(ansible_version.split('.')[:2])) + end_of_deprecation_should_be_docs_only = strict_ansible_version >= removed_in + # FIXME if +2 then file should be empty? - maybe add this only in the future + + if self._python_module() and not self._just_docs() and not end_of_deprecation_should_be_docs_only: self._validate_argument_spec(docs) self._check_for_sys_exit() self._find_blacklist_imports() @@ -1239,11 +1254,14 @@ class ModuleValidator(Validator): self._find_ps_docs_py_file() self._check_gpl3_header() - if not self._just_docs(): + if not self._just_docs() and not end_of_deprecation_should_be_docs_only: self._check_interpreter(powershell=self._powershell_module()) self._check_type_instead_of_isinstance( powershell=self._powershell_module() ) + if end_of_deprecation_should_be_docs_only: + # Ensure that `if __name__ == '__main__':` calls `removed_module()` which ensure that the module has no code in + main = self._find_main_call('removed_module') class PythonPackageValidator(Validator): diff --git a/test/sanity/validate-modules/schema.py b/test/sanity/validate-modules/schema.py index 8dbcf62a2b2..565b99b4783 100644 --- a/test/sanity/validate-modules/schema.py +++ b/test/sanity/validate-modules/schema.py @@ -83,23 +83,51 @@ def return_schema(data): ) +def deprecation_schema(): + + deprecation_schema_dict = { + # Only list branches that are deprecated or may have docs stubs in + # Deprecation cycle changed at 2.4 (though not retroactively) + # 2.3 -> removed_in: "2.5" + n for docs stub + # 2.4 -> removed_in: "2.8" + n for docs stub + Required('removed_in'): Any("2.2", "2.3", "2.4", "2.5", "2.8", "2.9"), + Required('why'): Any(*string_types), + Required('alternative'): Any(*string_types), + 'removed': Any(True), + } + return Schema( + deprecation_schema_dict, + extra=PREVENT_EXTRA + ) + + def doc_schema(module_name): + deprecated_module = False + if module_name.startswith('_'): module_name = module_name[1:] + deprecated_module = True + doc_schema_dict = { + Required('module'): module_name, + Required('short_description'): Any(*string_types), + Required('description'): Any(list_string_types, *string_types), + Required('version_added'): Any(float, *string_types), + Required('author'): Any(None, list_string_types, *string_types), + 'notes': Any(None, list_string_types), + 'requirements': list_string_types, + 'todo': Any(None, list_string_types, *string_types), + 'options': Any(None, *list_dict_option_schema), + 'extends_documentation_fragment': Any(list_string_types, *string_types) + } + + if deprecated_module: + deprecation_required_scheme = { + Required('deprecated'): Any(deprecation_schema()), + } + + doc_schema_dict.update(deprecation_required_scheme) return Schema( - { - Required('module'): module_name, - 'deprecated': Any(*string_types), - Required('short_description'): Any(*string_types), - Required('description'): Any(list_string_types, *string_types), - Required('version_added'): Any(float, *string_types), - Required('author'): Any(None, list_string_types, *string_types), - 'notes': Any(None, list_string_types), - 'requirements': list_string_types, - 'todo': Any(None, list_string_types, *string_types), - 'options': Any(None, *list_dict_option_schema), - 'extends_documentation_fragment': Any(list_string_types, *string_types) - }, + doc_schema_dict, extra=PREVENT_EXTRA ) diff --git a/test/sanity/validate-modules/skip.txt b/test/sanity/validate-modules/skip.txt index b06240bf137..10fe3c1b55a 100644 --- a/test/sanity/validate-modules/skip.txt +++ b/test/sanity/validate-modules/skip.txt @@ -1,2 +1 @@ lib/ansible/modules/utilities/logic/async_status.py -lib/ansible/modules/utilities/helper/_accelerate.py diff --git a/test/units/modules/cloud/docker/__init__.py b/test/units/modules/cloud/docker/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/test/units/modules/cloud/docker/test_docker.py b/test/units/modules/cloud/docker/test_docker.py deleted file mode 100644 index 3264e7dfb1d..00000000000 --- a/test/units/modules/cloud/docker/test_docker.py +++ /dev/null @@ -1,20 +0,0 @@ -import collections -import os -import unittest - -from ansible.modules.cloud.docker._docker import get_split_image_tag - - -class DockerSplitImageTagTestCase(unittest.TestCase): - - def test_trivial(self): - self.assertEqual(get_split_image_tag('test'), ('test', 'latest')) - - def test_with_org_name(self): - self.assertEqual(get_split_image_tag('ansible/centos7-ansible'), ('ansible/centos7-ansible', 'latest')) - - def test_with_tag(self): - self.assertEqual(get_split_image_tag('test:devel'), ('test', 'devel')) - - def test_with_tag_and_org_name(self): - self.assertEqual(get_split_image_tag('ansible/centos7-ansible:devel'), ('ansible/centos7-ansible', 'devel'))