├── tests ├── unit │ ├── __init__.py │ ├── mock │ │ ├── __init__.py │ │ ├── path.py │ │ ├── vault_helper.py │ │ └── procenv.py │ ├── modules │ │ ├── __init__.py │ │ ├── cloud │ │ │ ├── __init__.py │ │ │ └── openstack │ │ │ │ └── __init__.py │ │ ├── conftest.py │ │ └── utils.py │ ├── plugins │ │ ├── __init__.py │ │ └── inventory │ │ │ ├── __init__.py │ │ │ └── test_openstack.py │ └── requirements.txt ├── .gitignore ├── sanity │ ├── ignore-2.11.txt │ ├── ignore-2.12.txt │ ├── ignore-2.13.txt │ └── ignore-2.9.txt ├── constraints-none.txt ├── constraints-openstacksdk-0.x.x.txt ├── constraints-openstacksdk-1.x.x.txt ├── requirements-ansible-2.11.txt ├── requirements-ansible-2.12.txt ├── requirements-ansible-2.16.txt ├── requirements-ansible-2.18.txt ├── requirements-ansible-2.9.txt └── requirements.txt ├── plugins ├── modules │ ├── __init__.py │ ├── auth.py │ ├── federation_idp_info.py │ ├── federation_mapping_info.py │ ├── config.py │ ├── catalog_service_info.py │ ├── group_assignment.py │ ├── identity_role_info.py │ ├── volume_service_info.py │ ├── identity_domain_info.py │ ├── trait.py │ ├── keystone_federation_protocol_info.py │ ├── compute_service_info.py │ ├── identity_group_info.py │ └── project_info.py ├── doc_fragments │ └── __init__.py ├── inventory │ └── __init__.py └── module_utils │ ├── __init__.py │ └── ironic.py ├── requirements.txt ├── ci ├── roles │ ├── auth │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── trait │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── resource │ │ └── defaults │ │ │ └── main.yml │ ├── resources │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── logging │ │ ├── defaults │ │ │ └── main.yaml │ │ └── tasks │ │ │ └── main.yaml │ ├── inventory │ │ ├── files │ │ │ └── ansible.cfg │ │ └── templates │ │ │ └── openstack.yaml.j2 │ ├── keystone_federation_protocol │ │ └── defaults │ │ │ └── main.yml │ ├── identity_group │ │ └── defaults │ │ │ └── main.yml │ ├── identity_role │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── identity_domain │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── catalog_service │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── baremetal_deploy_template │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── endpoint │ │ ├── defaults │ │ │ └── main.yaml │ │ └── tasks │ │ │ └── main.yml │ ├── address_scope │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── application_credential │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── project │ │ └── defaults │ │ │ └── main.yml │ ├── volume_service │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── server_group │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── share_type │ │ └── defaults │ │ │ └── main.yml │ ├── compute_service │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── host_aggregate │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── neutron_rbac_policy │ │ └── defaults │ │ │ └── main.yml │ ├── keypair │ │ └── defaults │ │ │ └── main.yml │ ├── identity_user │ │ └── defaults │ │ │ └── main.yml │ ├── stack │ │ ├── files │ │ │ └── hello-world.yaml │ │ ├── defaults │ │ │ └── main.yaml │ │ └── tasks │ │ │ └── main.yaml │ ├── security_group │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── volume_snapshot │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── compute_flavor │ │ └── defaults │ │ │ └── main.yml │ ├── baremetal_port │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── dns_zone │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── config │ │ └── tasks │ │ │ └── main.yml │ ├── volume_type │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ ├── volume_encryption.yml │ │ │ └── main.yml │ ├── recordset │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── security_group_rule │ │ └── defaults │ │ │ └── main.yml │ ├── floating_ip │ │ └── defaults │ │ │ └── main.yml │ ├── volume_backup │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── keystone_idp │ │ └── defaults │ │ │ └── main.yml │ ├── loadbalancer │ │ └── defaults │ │ │ └── main.yml │ ├── object_container │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── baremetal_inspect │ │ ├── tasks │ │ │ └── main.yml │ │ └── defaults │ │ │ └── main.yml │ ├── coe_cluster │ │ └── defaults │ │ │ └── main.yml │ ├── trunk │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── subnet_pool │ │ └── defaults │ │ │ └── main.yml │ ├── subnet │ │ └── defaults │ │ │ └── main.yml │ ├── federation_mapping │ │ └── defaults │ │ │ └── main.yml │ ├── server_volume │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── object │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── coe_cluster_template │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── volume_manage │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── volume │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ ├── main.yml │ │ │ └── volume_info.yml │ ├── object_containers_info │ │ └── defaults │ │ │ └── main.yml │ ├── quota │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── network │ │ └── defaults │ │ │ └── main.yml │ ├── port │ │ └── defaults │ │ │ └── main.yml │ ├── router │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ ├── shared_ext_network.yml │ │ │ └── shared_network.yml │ ├── baremetal_node │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── image │ │ └── defaults │ │ │ └── main.yml │ ├── server │ │ └── defaults │ │ │ └── main.yml │ ├── group_assignment │ │ └── tasks │ │ │ └── main.yml │ ├── compute_flavor_access │ │ └── tasks │ │ │ └── main.yml │ ├── volume_type_access │ │ └── tasks │ │ │ └── main.yml │ └── server_metadata │ │ └── tasks │ │ └── main.yml ├── playbooks │ ├── devstack │ │ └── post.yaml │ └── postlog.yaml ├── requirements.yml ├── run-collection.yml └── publish │ └── publish_collection.yml ├── .gitignore ├── .gitreview ├── setup.py ├── bindep.txt ├── changelogs └── config.yaml ├── galaxy.yml.in ├── galaxy.yml ├── tools ├── check-import.sh ├── build.py └── run-ansible-sanity.sh ├── setup.cfg ├── meta └── runtime.yml └── tox.ini /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/modules/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | output/ 2 | -------------------------------------------------------------------------------- /tests/sanity/ignore-2.11.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/sanity/ignore-2.12.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/sanity/ignore-2.13.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/sanity/ignore-2.9.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/mock/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/doc_fragments/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/inventory/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/module_utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/modules/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/modules/cloud/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | openstacksdk>=1.0.0 2 | -------------------------------------------------------------------------------- /tests/unit/plugins/inventory/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/modules/cloud/openstack/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ci/roles/auth/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - auth_token 3 | -------------------------------------------------------------------------------- /ci/roles/trait/defaults/main.yml: -------------------------------------------------------------------------------- 1 | trait_name: CUSTOM_ANSIBLE_TRAIT 2 | -------------------------------------------------------------------------------- /tests/constraints-none.txt: -------------------------------------------------------------------------------- 1 | # No constraints are defined by default 2 | -------------------------------------------------------------------------------- /ci/roles/resource/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | expected_fields: 3 | - resource 4 | -------------------------------------------------------------------------------- /ci/roles/resources/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | expected_fields: 3 | - resources 4 | -------------------------------------------------------------------------------- /tests/unit/requirements.txt: -------------------------------------------------------------------------------- 1 | # requirements for openstack collection 2 | munch 3 | -------------------------------------------------------------------------------- /ci/playbooks/devstack/post.yaml: -------------------------------------------------------------------------------- 1 | - hosts: all 2 | roles: 3 | - fetch-tox-output 4 | -------------------------------------------------------------------------------- /ci/roles/logging/defaults/main.yaml: -------------------------------------------------------------------------------- 1 | sdk_log_file_path: "{{ playbook_dir }}/sdk.log" 2 | -------------------------------------------------------------------------------- /ci/roles/inventory/files/ansible.cfg: -------------------------------------------------------------------------------- 1 | [inventory] 2 | enable_plugins=openstack.cloud.openstack 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .tox 2 | build_artifact 3 | ansible_collections 4 | FILES.json 5 | MANIFEST.json 6 | importer_result.json 7 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=review.opendev.org 3 | port=29418 4 | project=openstack/ansible-collections-openstack.git 5 | -------------------------------------------------------------------------------- /ci/roles/keystone_federation_protocol/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - id 3 | - mapping_id 4 | - name 5 | -------------------------------------------------------------------------------- /ci/roles/identity_group/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - description 3 | - domain_id 4 | - id 5 | - name 6 | -------------------------------------------------------------------------------- /ci/roles/identity_role/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - description 3 | - domain_id 4 | - id 5 | - links 6 | - name 7 | -------------------------------------------------------------------------------- /ci/roles/identity_domain/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - description 3 | - id 4 | - is_enabled 5 | - name 6 | - links 7 | -------------------------------------------------------------------------------- /ci/roles/catalog_service/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - description 3 | - id 4 | - is_enabled 5 | - links 6 | - name 7 | - type 8 | -------------------------------------------------------------------------------- /ci/roles/baremetal_deploy_template/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - created_at 3 | - extra 4 | - id 5 | - name 6 | - steps 7 | - updated_at 8 | -------------------------------------------------------------------------------- /tests/constraints-openstacksdk-0.x.x.txt: -------------------------------------------------------------------------------- 1 | # 0.99.0 and later are release candidates for the first major release of OpenStackSDK 1.x.x 2 | openstacksdk<0.99.0 3 | -------------------------------------------------------------------------------- /tests/constraints-openstacksdk-1.x.x.txt: -------------------------------------------------------------------------------- 1 | # 0.99.0 and later are release candidates for the first major release of OpenStackSDK 1.x.x 2 | openstacksdk>=1.0.0 3 | -------------------------------------------------------------------------------- /ci/roles/endpoint/defaults/main.yaml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - id 3 | - interface 4 | - is_enabled 5 | - links 6 | - name 7 | - region_id 8 | - service_id 9 | - url 10 | -------------------------------------------------------------------------------- /ci/roles/address_scope/defaults/main.yml: -------------------------------------------------------------------------------- 1 | address_scope_name: "address_scope" 2 | expected_fields: 3 | - id 4 | - ip_version 5 | - is_shared 6 | - name 7 | - project_id 8 | - tenant_id 9 | -------------------------------------------------------------------------------- /ci/roles/application_credential/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - description 3 | - expires_at 4 | - id 5 | - name 6 | - project_id 7 | - roles 8 | - secret 9 | - unrestricted 10 | -------------------------------------------------------------------------------- /ci/roles/project/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - description 3 | - domain_id 4 | - id 5 | - is_domain 6 | - is_enabled 7 | - name 8 | - options 9 | - parent_id 10 | - tags 11 | -------------------------------------------------------------------------------- /ci/roles/volume_service/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - availability_zone 3 | - binary 4 | - disabled_reason 5 | - host 6 | - name 7 | - state 8 | - status 9 | - updated_at 10 | -------------------------------------------------------------------------------- /ci/roles/server_group/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - id 3 | - name 4 | - policy 5 | - policies 6 | - member_ids 7 | - metadata 8 | - project_id 9 | - rules 10 | - user_id 11 | 12 | -------------------------------------------------------------------------------- /tests/requirements-ansible-2.11.txt: -------------------------------------------------------------------------------- 1 | ansible-core>=2.11.0,<2.12.0 2 | flake8 3 | galaxy-importer 4 | openstacksdk 5 | pycodestyle 6 | pylint 7 | rstcheck 8 | ruamel.yaml 9 | tox 10 | voluptuous 11 | yamllint 12 | -------------------------------------------------------------------------------- /tests/requirements-ansible-2.12.txt: -------------------------------------------------------------------------------- 1 | ansible-core>=2.12.0,<2.13.0 2 | flake8 3 | galaxy-importer 4 | openstacksdk 5 | pycodestyle 6 | pylint 7 | rstcheck 8 | ruamel.yaml 9 | tox 10 | voluptuous 11 | yamllint 12 | -------------------------------------------------------------------------------- /tests/unit/mock/path.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | from ansible.utils.path import unfrackpath 4 | 5 | 6 | mock_unfrackpath_noop = MagicMock(spec_set=unfrackpath, side_effect=lambda x, *args, **kwargs: x) 7 | -------------------------------------------------------------------------------- /ci/roles/share_type/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | share_backend_name: GENERIC_BACKEND 3 | share_type_name: test_share_type 4 | share_type_description: Test share type for CI 5 | share_type_alt_description: Changed test share type 6 | -------------------------------------------------------------------------------- /tests/requirements-ansible-2.16.txt: -------------------------------------------------------------------------------- 1 | ansible-core>=2.16.0,<2.17.0 2 | flake8 3 | galaxy-importer 4 | openstacksdk 5 | pycodestyle 6 | pylint 7 | rstcheck 8 | ruamel.yaml 9 | tox 10 | voluptuous 11 | yamllint 12 | setuptools 13 | -------------------------------------------------------------------------------- /tests/requirements-ansible-2.18.txt: -------------------------------------------------------------------------------- 1 | ansible-core>=2.18.0,<2.19.0 2 | flake8 3 | galaxy-importer 4 | openstacksdk 5 | pycodestyle 6 | pylint 7 | rstcheck 8 | ruamel.yaml 9 | tox 10 | voluptuous 11 | yamllint 12 | setuptools 13 | -------------------------------------------------------------------------------- /ci/playbooks/postlog.yaml: -------------------------------------------------------------------------------- 1 | - hosts: all 2 | tasks: 3 | - zuul_return: 4 | data: 5 | zuul: 6 | artifacts: 7 | - name: Test log 8 | url: controller/logs/test_output_log.txt 9 | -------------------------------------------------------------------------------- /ci/roles/compute_service/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - availability_zone 3 | - binary 4 | - disabled_reason 5 | - host 6 | - id 7 | - is_forced_down 8 | - name 9 | - state 10 | - status 11 | - updated_at 12 | -------------------------------------------------------------------------------- /ci/roles/host_aggregate/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - availability_zone 3 | - created_at 4 | - deleted_at 5 | - hosts 6 | - id 7 | - is_deleted 8 | - metadata 9 | - name 10 | - updated_at 11 | - uuid 12 | -------------------------------------------------------------------------------- /ci/roles/neutron_rbac_policy/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - action 3 | - id 4 | - name 5 | - object_id 6 | - object_type 7 | - project_id 8 | - target_project_id 9 | - tenant_id 10 | all_project_symbol: '*' 11 | -------------------------------------------------------------------------------- /ci/roles/keypair/defaults/main.yml: -------------------------------------------------------------------------------- 1 | keypair_name: shade_keypair 2 | expected_fields: 3 | - created_at 4 | - fingerprint 5 | - id 6 | - is_deleted 7 | - name 8 | - private_key 9 | - public_key 10 | - type 11 | - user_id 12 | -------------------------------------------------------------------------------- /ci/roles/identity_user/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - default_project_id 3 | - description 4 | - domain_id 5 | - email 6 | - id 7 | - is_enabled 8 | - links 9 | - name 10 | - password 11 | - password_expires_at 12 | -------------------------------------------------------------------------------- /ci/roles/stack/files/hello-world.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Minimal HOT template defining a single compute server. 3 | # 4 | heat_template_version: 2013-05-23 5 | 6 | description: > 7 | Minimal HOT template for stack 8 | 9 | parameters: 10 | resources: 11 | outputs: 12 | -------------------------------------------------------------------------------- /ci/roles/security_group/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - created_at 3 | - description 4 | - name 5 | - project_id 6 | - security_group_rules 7 | - stateful 8 | - tenant_id 9 | - updated_at 10 | - revision_number 11 | - id 12 | - tags 13 | -------------------------------------------------------------------------------- /ci/roles/volume_snapshot/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - created_at 3 | - description 4 | - id 5 | - is_forced 6 | - metadata 7 | - name 8 | - progress 9 | - project_id 10 | - size 11 | - status 12 | - updated_at 13 | - volume_id 14 | -------------------------------------------------------------------------------- /ci/roles/compute_flavor/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - description 3 | - disk 4 | - ephemeral 5 | - extra_specs 6 | - id 7 | - is_disabled 8 | - is_public 9 | - name 10 | - original_name 11 | - ram 12 | - rxtx_factor 13 | - swap 14 | - vcpus 15 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright Red Hat, Inc. All Rights Reserved. 2 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 3 | 4 | import setuptools 5 | 6 | setuptools.setup( 7 | setup_requires=['pbr', 'setuptools'], 8 | pbr=True, 9 | py_modules=[]) 10 | -------------------------------------------------------------------------------- /ci/roles/baremetal_port/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - address 3 | - created_at 4 | - extra 5 | - id 6 | - internal_info 7 | - is_pxe_enabled 8 | - links 9 | - local_link_connection 10 | - name 11 | - node_id 12 | - physical_network 13 | - port_group_id 14 | - updated_at 15 | -------------------------------------------------------------------------------- /ci/roles/dns_zone/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - action 3 | - attributes 4 | - created_at 5 | - description 6 | - email 7 | - id 8 | - links 9 | - masters 10 | - name 11 | - pool_id 12 | - project_id 13 | - serial 14 | - status 15 | - ttl 16 | - type 17 | - updated_at 18 | -------------------------------------------------------------------------------- /ci/roles/inventory/templates/openstack.yaml.j2: -------------------------------------------------------------------------------- 1 | plugin: openstack.cloud.openstack 2 | 3 | all_projects: true 4 | compose: 5 | ci_compose_id: openstack.id 6 | ci_compose_project_id: openstack.project_id 7 | expand_hostvars: true 8 | fail_on_errors: true 9 | only_clouds: 10 | - "{{ cloud }}" 11 | strict: true 12 | -------------------------------------------------------------------------------- /ci/requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | collections: 3 | - ansible.posix 4 | - ansible.utils 5 | - name: community.general 6 | version: 4.8.8 7 | # 5.0.0 dropped compatibility with ansible 2.9 and ansible-base 2.10 8 | # Ref.: https://github.com/ansible-collections/community.general/commit/1a9b3214fdf1eaccba5cc9ee210cbc5b5070fe4b 9 | -------------------------------------------------------------------------------- /ci/roles/config/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: List all cloud profiles 3 | openstack.cloud.config: 4 | register: config 5 | # WARNING: This will output sensitive authentication information!!!! 6 | 7 | - name: Assert config module 8 | assert: 9 | that: 10 | - cloud in (config.clouds | map(attribute='name') | list) 11 | -------------------------------------------------------------------------------- /ci/roles/volume_type/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | volume_backend_name: LVM_iSCSI 3 | volume_type_name: test_type 4 | volume_type_description: Test volume type 5 | 6 | enc_provider_name: nova.volume.encryptors.luks.LuksEncryptor 7 | enc_cipher: aes-xts-plain64 8 | enc_control_location: front-end 9 | enc_control_alt_location: back-end 10 | enc_key_size: 256 11 | -------------------------------------------------------------------------------- /ci/roles/auth/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Authenticate to the cloud 3 | openstack.cloud.auth: 4 | cloud={{ cloud }} 5 | register: auth 6 | 7 | - name: Assert return values of auth module 8 | assert: 9 | that: 10 | # allow new fields to be introduced but prevent fields from being removed 11 | - expected_fields|difference(auth.keys())|length == 0 12 | -------------------------------------------------------------------------------- /tests/requirements-ansible-2.9.txt: -------------------------------------------------------------------------------- 1 | ansible>=2.9.0,<2.10.0 2 | flake8 3 | # galaxy-importer 0.3.2 moved from ansible 2.9 to ansible-core 2.11 4 | # Ref.: https://github.com/ansible/galaxy-importer/commit/98933547831922c45243f39d85eefe150b55fc36 5 | galaxy-importer==0.3.1 6 | openstacksdk 7 | pycodestyle 8 | pylint 9 | rstcheck 10 | ruamel.yaml 11 | tox 12 | voluptuous 13 | yamllint 14 | -------------------------------------------------------------------------------- /bindep.txt: -------------------------------------------------------------------------------- 1 | # This is a cross-platform list tracking distribution packages needed by tests; 2 | # see https://docs.openstack.org/infra/bindep/ for additional information. 3 | 4 | gcc [compile platform:centos-8 platform:rhel-8] 5 | python38-cryptography [platform:centos-8 platform:rhel-8] 6 | python38-devel [compile platform:centos-8 platform:rhel-8] 7 | python38-requests [platform:centos-8 platform:rhel-8] 8 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | # ansible.builtin.user module in ansible-core 2.13.0 and 2.13.1 is affected by #78017 2 | # Ref.: https://github.com/ansible/ansible/issues/78017 3 | # 4 | # TODO: Remove ansible-core constraint once issue #78017 has been fixed. 5 | ansible-core!=2.13.0,!=2.13.1 6 | flake8 7 | galaxy-importer 8 | openstacksdk 9 | pycodestyle 10 | pylint 11 | rstcheck 12 | ruamel.yaml 13 | tox 14 | voluptuous 15 | yamllint 16 | -------------------------------------------------------------------------------- /ci/roles/recordset/defaults/main.yml: -------------------------------------------------------------------------------- 1 | dns_zone_name: test.dns.zone. 2 | recordset_name: testrecordset.test.dns.zone. 3 | records: ['10.0.0.0', '10.0.0.2'] 4 | updated_records: ['10.1.1.1', '10.0.0.2'] 5 | 6 | recordset_fields: 7 | - action 8 | - created_at 9 | - description 10 | - id 11 | - links 12 | - name 13 | - project_id 14 | - records 15 | - status 16 | - ttl 17 | - type 18 | - zone_id 19 | - zone_name 20 | -------------------------------------------------------------------------------- /ci/roles/security_group_rule/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - created_at 3 | - description 4 | - direction 5 | - ether_type 6 | - id 7 | - name 8 | - port_range_max 9 | - port_range_min 10 | - project_id 11 | - protocol 12 | - remote_address_group_id 13 | - remote_group_id 14 | - remote_ip_prefix 15 | - revision_number 16 | - security_group_id 17 | - tags 18 | - tenant_id 19 | - updated_at 20 | -------------------------------------------------------------------------------- /ci/roles/floating_ip/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | expected_fields: 3 | - created_at 4 | - description 5 | - dns_domain 6 | - dns_name 7 | - fixed_ip_address 8 | - floating_ip_address 9 | - floating_network_id 10 | - id 11 | - name 12 | - port_details 13 | - port_id 14 | - project_id 15 | - qos_policy_id 16 | - revision_number 17 | - router_id 18 | - status 19 | - subnet_id 20 | - tags 21 | - updated_at 22 | -------------------------------------------------------------------------------- /ci/roles/volume_backup/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - availability_zone 3 | - container 4 | - created_at 5 | - data_timestamp 6 | - description 7 | - fail_reason 8 | - force 9 | - has_dependent_backups 10 | - id 11 | - is_incremental 12 | - links 13 | - metadata 14 | - name 15 | - object_count 16 | - project_id 17 | - size 18 | - snapshot_id 19 | - status 20 | - updated_at 21 | - user_id 22 | - volume_id 23 | -------------------------------------------------------------------------------- /ci/roles/keystone_idp/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - description 3 | - domain_id 4 | - id 5 | - is_enabled 6 | - name 7 | - remote_ids 8 | remote_ids_1: 9 | - 'https://auth.example.com/auth/realms/ExampleRealm' 10 | - 'https://auth.stage.example.com/auth/realms/ExampleRealm' 11 | remote_ids_2: 12 | - 'https://auth.example.com/auth/realms/ExampleRealm' 13 | remote_ids_3: 14 | - 'https://auth.stage.example.com/auth/realms/ExampleRealm' 15 | -------------------------------------------------------------------------------- /ci/roles/loadbalancer/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - additional_vips 3 | - availability_zone 4 | - created_at 5 | - description 6 | - flavor_id 7 | - id 8 | - is_admin_state_up 9 | - listeners 10 | - name 11 | - operating_status 12 | - pools 13 | - project_id 14 | - provider 15 | - provisioning_status 16 | - tags 17 | - updated_at 18 | - vip_address 19 | - vip_network_id 20 | - vip_port_id 21 | - vip_qos_policy_id 22 | - vip_subnet_id 23 | -------------------------------------------------------------------------------- /ci/roles/object_container/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - bytes 3 | - bytes_used 4 | - content_type 5 | - count 6 | - history_location 7 | - id 8 | - if_none_match 9 | - is_content_type_detected 10 | - is_newest 11 | - meta_temp_url_key 12 | - meta_temp_url_key_2 13 | - metadata 14 | - name 15 | - object_count 16 | - read_ACL 17 | - storage_policy 18 | - sync_key 19 | - sync_to 20 | - timestamp 21 | - versions_location 22 | - write_ACL 23 | -------------------------------------------------------------------------------- /ci/roles/logging/tasks/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Trigger flavor listing to create logs 3 | openstack.cloud.compute_flavor_info: 4 | cloud: "{{ cloud }}" 5 | sdk_log_path: "{{ sdk_log_file_path }}" 6 | sdk_log_level: "DEBUG" 7 | 8 | - name: Read openstacksdk's log file 9 | ansible.builtin.slurp: 10 | src: "{{ sdk_log_file_path }}" 11 | register: log 12 | 13 | - name: Print contents of openstacksdk's log 14 | ansible.builtin.debug: 15 | msg: "{{ log['content'] | b64decode }}" 16 | -------------------------------------------------------------------------------- /ci/roles/baremetal_inspect/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # TODO: Actually run this role in CI. Atm we do not have DevStack's ironic plugin enabled. 3 | - name: Introspect node 4 | openstack.cloud.baremetal_inspect: 5 | cloud: "{{ cloud }}" 6 | name: node-1 7 | register: inspect 8 | 9 | - debug: var=inspect 10 | 11 | - name: assert return values of baremetal_inspect module 12 | assert: 13 | that: 14 | # allow new fields to be introduced but prevent fields from being removed 15 | - expected_fields|difference(inspect.node.keys())|length == 0 16 | -------------------------------------------------------------------------------- /ci/roles/coe_cluster/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - api_address 3 | - cluster_template_id 4 | - coe_version 5 | - create_timeout 6 | - created_at 7 | - discovery_url 8 | - fixed_network 9 | - fixed_subnet 10 | - flavor_id 11 | - id 12 | - is_floating_ip_enabled 13 | - is_master_lb_enabled 14 | - keypair 15 | - labels 16 | - master_addresses 17 | - master_count 18 | - master_flavor_id 19 | - name 20 | - node_addresses 21 | - node_count 22 | - stack_id 23 | - status 24 | - status_reason 25 | - updated_at 26 | - uuid 27 | -------------------------------------------------------------------------------- /ci/roles/trunk/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - created_at 3 | - description 4 | - id 5 | - is_admin_state_up 6 | - name 7 | - port_id 8 | - project_id 9 | - revision_number 10 | - status 11 | - sub_ports 12 | - tags 13 | - tenant_id 14 | - updated_at 15 | trunk_name: ansible_trunk 16 | parent_network_name: ansible_parent_port_network 17 | parent_subnet_name: ansible_parent_port_subnet 18 | parent_port_name: ansible_parent_port 19 | subport_network_name: ansible_subport_network 20 | subport_subnet_name: ansible_subport_subnet 21 | subport_name: ansible_subport 22 | -------------------------------------------------------------------------------- /ci/roles/subnet_pool/defaults/main.yml: -------------------------------------------------------------------------------- 1 | address_scope_name: "ansible_address_scope" 2 | default_prefix_length: 24 3 | expected_fields: 4 | - address_scope_id 5 | - created_at 6 | - default_prefix_length 7 | - default_quota 8 | - description 9 | - id 10 | - ip_version 11 | - is_default 12 | - is_shared 13 | - maximum_prefix_length 14 | - minimum_prefix_length 15 | - name 16 | - prefixes 17 | - project_id 18 | - revision_number 19 | - tags 20 | - tenant_id 21 | - updated_at 22 | maximum_prefix_length: 30 23 | minimum_prefix_length: 10 24 | subnet_pool_name: "ansible_subnet_pool" 25 | -------------------------------------------------------------------------------- /ci/roles/subnet/defaults/main.yml: -------------------------------------------------------------------------------- 1 | enable_subnet_dhcp: false 2 | expected_fields: 3 | - allocation_pools 4 | - cidr 5 | - created_at 6 | - description 7 | - dns_nameservers 8 | - dns_publish_fixed_ip 9 | - gateway_ip 10 | - host_routes 11 | - id 12 | - ip_version 13 | - ipv6_address_mode 14 | - ipv6_ra_mode 15 | - is_dhcp_enabled 16 | - name 17 | - network_id 18 | - prefix_length 19 | - project_id 20 | - revision_number 21 | - segment_id 22 | - service_types 23 | - subnet_pool_id 24 | - tags 25 | - updated_at 26 | - use_default_subnet_pool 27 | subnet_name: shade_subnet 28 | -------------------------------------------------------------------------------- /ci/roles/federation_mapping/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - id 3 | - name 4 | - rules 5 | mapping_name: 'ansible-test-mapping' 6 | mapping_name_2: 'ansible-test-mapping-2' 7 | mapping_rules_1: 8 | - local: 9 | - group: 10 | domain: 11 | name: example_domain 12 | name: example-group 13 | remote: 14 | - type: HTTP_OIDC_GROUPS 15 | any_one_of: 16 | - group1 17 | - group2 18 | mapping_rules_2: 19 | - local: 20 | - group: 21 | domain: 22 | name: example_domain 23 | name: example_group 24 | remote: 25 | - type: HTTP_OIDC_GROUPS 26 | any_one_of: 27 | - group1 28 | -------------------------------------------------------------------------------- /ci/roles/trait/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create trait 3 | openstack.cloud.trait: 4 | cloud: "{{ cloud }}" 5 | state: present 6 | id: "{{ trait_name }}" 7 | until: result is success 8 | retries: 5 9 | delay: 20 10 | register: result 11 | 12 | - name: Assert trait 13 | assert: 14 | that: 15 | - "'name' in result.trait" 16 | - "result.trait.id == trait_name" 17 | 18 | - name: Remove trait 19 | openstack.cloud.trait: 20 | cloud: "{{ cloud }}" 21 | state: absent 22 | id: "{{ trait_name }}" 23 | register: result1 24 | 25 | - name: Assert trait removed 26 | assert: 27 | that: 28 | - "'trait' not in result1" 29 | -------------------------------------------------------------------------------- /ci/roles/stack/defaults/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | stack_name: "test-stack" 3 | expected_fields: 4 | - added 5 | - capabilities 6 | - created_at 7 | - deleted 8 | - deleted_at 9 | - description 10 | - environment 11 | - environment_files 12 | - files 13 | - files_container 14 | - id 15 | - is_rollback_disabled 16 | - links 17 | - name 18 | - notification_topics 19 | - outputs 20 | - owner_id 21 | - parameters 22 | - parent_id 23 | - replaced 24 | - status 25 | - status_reason 26 | - tags 27 | - template 28 | - template_description 29 | - template_url 30 | - timeout_mins 31 | - unchanged 32 | - updated 33 | - updated_at 34 | - user_project_id 35 | -------------------------------------------------------------------------------- /ci/roles/server_volume/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - attachments 3 | - availability_zone 4 | - consistency_group_id 5 | - created_at 6 | - description 7 | - extended_replication_status 8 | - group_id 9 | - host 10 | - id 11 | - image_id 12 | - is_bootable 13 | - is_encrypted 14 | - metadata 15 | - migration_id 16 | - migration_status 17 | - name 18 | - project_id 19 | - replication_driver_data 20 | - replication_status 21 | - scheduler_hints 22 | - size 23 | - snapshot_id 24 | - source_volume_id 25 | - status 26 | - updated_at 27 | - user_id 28 | - volume_image_metadata 29 | - volume_type 30 | flavor: m1.tiny 31 | server_name: ansible_server 32 | server_network: private 33 | -------------------------------------------------------------------------------- /ci/roles/object/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - accept_ranges 3 | - access_control_allow_origin 4 | - content_disposition 5 | - content_encoding 6 | - content_length 7 | - content_type 8 | - copy_from 9 | - delete_after 10 | - delete_at 11 | - etag 12 | - expires_at 13 | - id 14 | - if_match 15 | - if_modified_since 16 | - if_none_match 17 | - if_unmodified_since 18 | - is_content_type_detected 19 | - is_newest 20 | - is_static_large_object 21 | - last_modified_at 22 | - manifest 23 | - metadata 24 | - multipart_manifest 25 | - name 26 | - object_manifest 27 | - range 28 | - signature 29 | - symlink_target 30 | - symlink_target_account 31 | - timestamp 32 | - transfer_encoding 33 | -------------------------------------------------------------------------------- /ci/roles/coe_cluster_template/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - apiserver_port 3 | - cluster_distro 4 | - coe 5 | - created_at 6 | - dns_nameserver 7 | - docker_storage_driver 8 | - docker_volume_size 9 | - external_network_id 10 | - fixed_network 11 | - fixed_subnet 12 | - flavor_id 13 | - http_proxy 14 | - https_proxy 15 | - id 16 | - image_id 17 | - insecure_registry 18 | - is_floating_ip_enabled 19 | - is_hidden 20 | - is_master_lb_enabled 21 | - is_public 22 | - is_registry_enabled 23 | - is_tls_disabled 24 | - keypair_id 25 | - labels 26 | - master_flavor_id 27 | - name 28 | - network_driver 29 | - no_proxy 30 | - server_type 31 | - updated_at 32 | - uuid 33 | - volume_driver 34 | -------------------------------------------------------------------------------- /ci/roles/volume_manage/defaults/main.yml: -------------------------------------------------------------------------------- 1 | test_volume: ansible_test_volume 2 | managed_volume: managed_test_volume 3 | expected_fields: 4 | - attachments 5 | - availability_zone 6 | - consistency_group_id 7 | - created_at 8 | - updated_at 9 | - description 10 | - extended_replication_status 11 | - group_id 12 | - host 13 | - image_id 14 | - is_bootable 15 | - is_encrypted 16 | - is_multiattach 17 | - migration_id 18 | - migration_status 19 | - project_id 20 | - replication_driver_data 21 | - replication_status 22 | - scheduler_hints 23 | - size 24 | - snapshot_id 25 | - source_volume_id 26 | - status 27 | - user_id 28 | - volume_image_metadata 29 | - volume_type 30 | - id 31 | - name 32 | - metadata 33 | -------------------------------------------------------------------------------- /ci/roles/volume/defaults/main.yml: -------------------------------------------------------------------------------- 1 | test_volume_image: ansible_test_volume_image 2 | test_volume_shared_image: ansible_test_volume_shared_image 3 | expected_fields: 4 | - attachments 5 | - availability_zone 6 | - consistency_group_id 7 | - created_at 8 | - updated_at 9 | - description 10 | - extended_replication_status 11 | - group_id 12 | - host 13 | - image_id 14 | - is_bootable 15 | - is_encrypted 16 | - is_multiattach 17 | - migration_id 18 | - migration_status 19 | - project_id 20 | - replication_driver_data 21 | - replication_status 22 | - scheduler_hints 23 | - size 24 | - snapshot_id 25 | - source_volume_id 26 | - status 27 | - user_id 28 | - volume_image_metadata 29 | - volume_type 30 | - id 31 | - name 32 | - metadata 33 | -------------------------------------------------------------------------------- /ci/roles/object_containers_info/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | test_container_unprefixed_name: ansible-test-container 4 | test_container_prefixed_prefix: ansible-prefixed-test-container 5 | test_container_prefixed_num: 2 6 | 7 | test_object_data: "Hello, world!" 8 | 9 | expected_fields_single: 10 | - bytes 11 | - bytes_used 12 | - content_type 13 | - count 14 | - history_location 15 | - id 16 | - if_none_match 17 | - is_content_type_detected 18 | - is_newest 19 | - meta_temp_url_key 20 | - meta_temp_url_key_2 21 | - name 22 | - object_count 23 | - read_ACL 24 | - storage_policy 25 | - sync_key 26 | - sync_to 27 | - timestamp 28 | - versions_location 29 | - write_ACL 30 | 31 | expected_fields_multiple: 32 | - bytes 33 | - bytes_used 34 | - count 35 | - id 36 | - name 37 | - object_count 38 | -------------------------------------------------------------------------------- /ci/roles/quota/defaults/main.yml: -------------------------------------------------------------------------------- 1 | test_project: ansible_project 2 | test_network_quota: 3 | floating_ips: 5 4 | networks: 50 5 | ports: 300 6 | rbac_policies: 5 7 | routers: 5 8 | security_group_rules: 5 9 | security_groups: 5 10 | subnet_pools: 5 11 | subnets: 5 12 | test_volume_quota: 13 | backup_gigabytes: 500 14 | backups: 5 15 | gigabytes: 500 16 | groups: 1 17 | per_volume_gigabytes: 10 18 | snapshots: 5 19 | volumes: 5 20 | test_compute_quota: 21 | cores: 5 22 | injected_file_content_bytes: 5 23 | injected_file_path_bytes: 5 24 | injected_files: 5 25 | instances: 5 26 | key_pairs: 5 27 | metadata_items: 5 28 | ram: 5 29 | server_group_members: 5 30 | server_groups: 5 31 | test_load_balancer_quota: 32 | load_balancers: 5 33 | health_monitors: 5 34 | listeners: 5 35 | pools: 5 36 | members: 5 37 | -------------------------------------------------------------------------------- /ci/roles/volume_service/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Fetch volume services 3 | openstack.cloud.volume_service_info: 4 | cloud: "{{ cloud }}" 5 | register: volume_services 6 | 7 | - name: Assert return values of volume_service_info module 8 | assert: 9 | that: 10 | - volume_services.volume_services | length > 0 11 | # allow new fields to be introduced but prevent fields from being removed 12 | - expected_fields|difference(volume_services.volume_services[0].keys())|length == 0 13 | 14 | - name: Fetch volume services with filters 15 | openstack.cloud.volume_service_info: 16 | cloud: "{{ cloud }}" 17 | binary: "cinder-volume" 18 | register: volume_services 19 | 20 | - name: Assert return values of volume_service_info module 21 | assert: 22 | that: 23 | - volume_services.volume_services | length > 0 24 | -------------------------------------------------------------------------------- /ci/roles/compute_service/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Fetch compute services 3 | openstack.cloud.compute_service_info: 4 | cloud: "{{ cloud }}" 5 | register: compute_services 6 | 7 | - name: Assert return values of compute_service_info module 8 | assert: 9 | that: 10 | - compute_services.compute_services | length > 0 11 | # allow new fields to be introduced but prevent fields from being removed 12 | - expected_fields|difference(compute_services.compute_services[0].keys())|length == 0 13 | 14 | - name: Fetch compute services with filters 15 | openstack.cloud.compute_service_info: 16 | cloud: "{{ cloud }}" 17 | binary: "nova-compute" 18 | register: compute_services 19 | 20 | - name: Assert return values of compute_service_info module 21 | assert: 22 | that: 23 | - compute_services.compute_services | length > 0 24 | -------------------------------------------------------------------------------- /ci/roles/network/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - availability_zone_hints 3 | - availability_zones 4 | - created_at 5 | - description 6 | - dns_domain 7 | - id 8 | - ipv4_address_scope_id 9 | - ipv6_address_scope_id 10 | - is_admin_state_up 11 | - is_default 12 | - is_port_security_enabled 13 | - is_router_external 14 | - is_shared 15 | - is_vlan_transparent 16 | - mtu 17 | - name 18 | - project_id 19 | - provider_network_type 20 | - provider_physical_network 21 | - provider_segmentation_id 22 | - qos_policy_id 23 | - revision_number 24 | - segments 25 | - status 26 | - subnet_ids 27 | - tags 28 | - updated_at 29 | dns_domain: example.opendev.org 30 | mtu: 1250 31 | network_name: shade_network 32 | network_name_newparams: newparams_network 33 | network_name_updates: update_network 34 | network_shared: false 35 | port_security_enabled: false 36 | -------------------------------------------------------------------------------- /changelogs/config.yaml: -------------------------------------------------------------------------------- 1 | changelog_filename_template: ../CHANGELOG.rst 2 | changelog_filename_version_depth: 0 3 | changes_file: changelog.yaml 4 | changes_format: combined 5 | ignore_other_fragment_extensions: true 6 | keep_fragments: false 7 | mention_ancestor: true 8 | new_plugins_after_name: removed_features 9 | notes_dir: fragments 10 | prelude_name: release_summary 11 | prelude_title: Release Summary 12 | sections: 13 | - - major_changes 14 | - Major Changes 15 | - - minor_changes 16 | - Minor Changes 17 | - - breaking_changes 18 | - Breaking Changes / Porting Guide 19 | - - deprecated_features 20 | - Deprecated Features 21 | - - removed_features 22 | - Removed Features (previously deprecated) 23 | - - security_fixes 24 | - Security Fixes 25 | - - bugfixes 26 | - Bugfixes 27 | - - known_issues 28 | - Known Issues 29 | title: Ansible OpenStack Collection 30 | trivial_section_name: trivial 31 | use_fqcn: true 32 | -------------------------------------------------------------------------------- /galaxy.yml.in: -------------------------------------------------------------------------------- 1 | namespace: openstack 2 | name: cloud 3 | readme: README.md 4 | authors: Openstack 5 | description: Openstack Ansible modules 6 | license: GPL-3.0-or-later 7 | tags: 8 | - cloud 9 | - openstack 10 | dependencies: {} 11 | repository: https://opendev.org/openstack/ansible-collections-openstack 12 | documentation: https://docs.ansible.com/ansible/latest/collections/openstack/cloud/index.html 13 | homepage: https://opendev.org/openstack/ansible-collections-openstack 14 | issues: https://bugs.launchpad.net/ansible-collections-openstack 15 | build_ignore: 16 | - "*.tar.gz" 17 | - build_artifact 18 | - ci 19 | - galaxy.yml.in 20 | - setup.cfg 21 | - test-requirements* 22 | - tests 23 | - tools 24 | - tox.ini 25 | - .gitignore 26 | - .gitreview 27 | - .zuul.yaml 28 | - .pytest_cache 29 | - importer_result.json 30 | - .tox 31 | - .env 32 | - .vscode 33 | - ansible_collections_openstack.egg-info 34 | - changelogs 35 | -------------------------------------------------------------------------------- /galaxy.yml: -------------------------------------------------------------------------------- 1 | namespace: openstack 2 | name: cloud 3 | readme: README.md 4 | authors: Openstack 5 | description: Openstack Ansible modules 6 | license: GPL-3.0-or-later 7 | tags: 8 | - cloud 9 | - openstack 10 | dependencies: {} 11 | repository: https://opendev.org/openstack/ansible-collections-openstack 12 | documentation: https://docs.ansible.com/ansible/latest/collections/openstack/cloud/index.html 13 | homepage: https://opendev.org/openstack/ansible-collections-openstack 14 | issues: https://bugs.launchpad.net/ansible-collections-openstack 15 | build_ignore: 16 | - "*.tar.gz" 17 | - build_artifact 18 | - ci 19 | - galaxy.yml.in 20 | - setup.cfg 21 | - test-requirements* 22 | - tests 23 | - tools 24 | - tox.ini 25 | - .gitignore 26 | - .gitreview 27 | - .zuul.yaml 28 | - .pytest_cache 29 | - importer_result.json 30 | - .tox 31 | - .env 32 | - .vscode 33 | - ansible_collections_openstack.egg-info 34 | - changelogs 35 | version: 2.5.0 36 | -------------------------------------------------------------------------------- /tools/check-import.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2020 Red Hat, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | set -e 17 | 18 | if python -c 'import sys; sys.exit(0 if sys.version_info[0:2] < (3, 6) else 1)'; then 19 | echo "Skipped Ansible Galaxy content importer check because it requires Python 3.6 or later" 2>&1 20 | exit 21 | fi 22 | 23 | TOXDIR="${1:-.}" 24 | python -m galaxy_importer.main "$TOXDIR/build_artifact/"* 25 | -------------------------------------------------------------------------------- /ci/roles/port/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - allowed_address_pairs 3 | - binding_host_id 4 | - binding_profile 5 | - binding_vif_details 6 | - binding_vif_type 7 | - binding_vnic_type 8 | - created_at 9 | - data_plane_status 10 | - description 11 | - device_id 12 | - device_owner 13 | - device_profile 14 | - dns_assignment 15 | - dns_domain 16 | - dns_name 17 | - extra_dhcp_opts 18 | - fixed_ips 19 | - id 20 | - ip_allocation 21 | - is_admin_state_up 22 | - is_port_security_enabled 23 | - mac_address 24 | - name 25 | - network_id 26 | - numa_affinity_policy 27 | - project_id 28 | - propagate_uplink_status 29 | - qos_network_policy_id 30 | - qos_policy_id 31 | - resource_request 32 | - revision_number 33 | - security_group_ids 34 | - status 35 | - tags 36 | - tenant_id 37 | - trunk_details 38 | - updated_at 39 | network_name: ansible_port_network 40 | no_security_groups: True 41 | port_name: ansible_port 42 | subnet_name: ansible_port_subnet 43 | -------------------------------------------------------------------------------- /ci/roles/router/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - availability_zone_hints 3 | - availability_zones 4 | - created_at 5 | - description 6 | - external_gateway_info 7 | - flavor_id 8 | - id 9 | - is_admin_state_up 10 | - is_distributed 11 | - is_ha 12 | - name 13 | - project_id 14 | - revision_number 15 | - routes 16 | - status 17 | - tags 18 | - tenant_id 19 | - updated_at 20 | network_name: ansible_net 21 | external_network_name: ansible_external_net 22 | router_name: ansible_router 23 | test_subnets: 24 | - cloud: "{{ cloud }}" 25 | state: present 26 | network_name: "{{ network_name }}" 27 | name: shade_subnet1 28 | cidr: 10.7.7.0/24 29 | - cloud: "{{ cloud }}" 30 | state: present 31 | network_name: "{{ network_name }}" 32 | name: shade_subnet2 33 | cidr: 10.8.8.0/24 34 | - cloud: "{{ cloud }}" 35 | state: present 36 | network_name: "{{ network_name }}" 37 | name: shade_subnet3 38 | cidr: 10.9.9.0/24 39 | - cloud: "{{ cloud }}" 40 | state: present 41 | network_name: "{{ network_name }}" 42 | name: shade_subnet4 43 | cidr: 10.10.10.0/24 44 | -------------------------------------------------------------------------------- /plugins/modules/auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2015 Hewlett-Packard Development Company, L.P. 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | DOCUMENTATION = r''' 8 | --- 9 | module: auth 10 | short_description: Retrieve auth token from OpenStack cloud 11 | author: OpenStack Ansible SIG 12 | description: 13 | - Retrieve auth token from OpenStack cloud 14 | extends_documentation_fragment: 15 | - openstack.cloud.openstack 16 | ''' 17 | 18 | EXAMPLES = r''' 19 | - name: Authenticate to cloud and return auth token 20 | openstack.cloud.auth: 21 | cloud: rax-dfw 22 | ''' 23 | 24 | RETURN = r''' 25 | auth_token: 26 | description: Openstack API Auth Token 27 | returned: success 28 | type: str 29 | ''' 30 | 31 | from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule 32 | 33 | 34 | class AuthModule(OpenStackModule): 35 | def run(self): 36 | self.exit_json(changed=False, 37 | auth_token=self.conn.auth_token) 38 | 39 | 40 | def main(): 41 | module = AuthModule() 42 | module() 43 | 44 | 45 | if __name__ == '__main__': 46 | main() 47 | -------------------------------------------------------------------------------- /ci/roles/baremetal_node/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - allocation_id 3 | - bios_interface 4 | - boot_interface 5 | - boot_mode 6 | - chassis_id 7 | - clean_step 8 | - conductor 9 | - conductor_group 10 | - console_interface 11 | - created_at 12 | - deploy_interface 13 | - deploy_step 14 | - driver 15 | - driver_info 16 | - driver_internal_info 17 | - extra 18 | - fault 19 | - id 20 | - inspect_interface 21 | - instance_id 22 | - instance_info 23 | - is_automated_clean_enabled 24 | - is_console_enabled 25 | - is_maintenance 26 | - is_protected 27 | - is_retired 28 | - is_secure_boot 29 | - last_error 30 | - links 31 | - maintenance_reason 32 | - management_interface 33 | - name 34 | - network_interface 35 | - owner 36 | - port_groups 37 | - ports 38 | - power_interface 39 | - power_state 40 | - properties 41 | - protected_reason 42 | - provision_state 43 | - raid_config 44 | - raid_interface 45 | - rescue_interface 46 | - reservation 47 | - resource_class 48 | - retired_reason 49 | - states 50 | - storage_interface 51 | - target_power_state 52 | - target_provision_state 53 | - target_raid_config 54 | - traits 55 | - updated_at 56 | - vendor_interface 57 | -------------------------------------------------------------------------------- /ci/roles/baremetal_inspect/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - allocation_id 3 | - bios_interface 4 | - boot_interface 5 | - boot_mode 6 | - chassis_id 7 | - clean_step 8 | - conductor 9 | - conductor_group 10 | - console_interface 11 | - created_at 12 | - deploy_interface 13 | - deploy_step 14 | - driver 15 | - driver_info 16 | - driver_internal_info 17 | - extra 18 | - fault 19 | - id 20 | - inspect_interface 21 | - instance_id 22 | - instance_info 23 | - is_automated_clean_enabled 24 | - is_console_enabled 25 | - is_maintenance 26 | - is_protected 27 | - is_retired 28 | - is_secure_boot 29 | - last_error 30 | - links 31 | - maintenance_reason 32 | - management_interface 33 | - name 34 | - network_interface 35 | - owner 36 | - port_groups 37 | - ports 38 | - power_interface 39 | - power_state 40 | - properties 41 | - protected_reason 42 | - provision_state 43 | - raid_config 44 | - raid_interface 45 | - rescue_interface 46 | - reservation 47 | - resource_class 48 | - retired_reason 49 | - states 50 | - storage_interface 51 | - target_power_state 52 | - target_provision_state 53 | - target_raid_config 54 | - traits 55 | - updated_at 56 | - vendor_interface 57 | -------------------------------------------------------------------------------- /tests/unit/modules/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Ansible Project 2 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 3 | 4 | import json 5 | 6 | import pytest 7 | 8 | from ansible.module_utils.six import string_types 9 | from ansible.module_utils._text import to_bytes 10 | from ansible.module_utils.common._collections_compat import MutableMapping 11 | 12 | 13 | @pytest.fixture 14 | def patch_ansible_module(request, mocker): 15 | if isinstance(request.param, string_types): 16 | args = request.param 17 | elif isinstance(request.param, MutableMapping): 18 | if 'ANSIBLE_MODULE_ARGS' not in request.param: 19 | request.param = {'ANSIBLE_MODULE_ARGS': request.param} 20 | if '_ansible_remote_tmp' not in request.param['ANSIBLE_MODULE_ARGS']: 21 | request.param['ANSIBLE_MODULE_ARGS']['_ansible_remote_tmp'] = '/tmp' 22 | if '_ansible_keep_remote_files' not in request.param['ANSIBLE_MODULE_ARGS']: 23 | request.param['ANSIBLE_MODULE_ARGS']['_ansible_keep_remote_files'] = False 24 | args = json.dumps(request.param) 25 | else: 26 | raise Exception('Malformed data to the patch_ansible_module pytest fixture') 27 | 28 | mocker.patch('ansible.module_utils.basic._ANSIBLE_ARGS', to_bytes(args)) 29 | -------------------------------------------------------------------------------- /tests/unit/modules/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import unittest 3 | from unittest.mock import patch 4 | 5 | from ansible.module_utils import basic 6 | from ansible.module_utils._text import to_bytes 7 | 8 | 9 | def set_module_args(args): 10 | if '_ansible_remote_tmp' not in args: 11 | args['_ansible_remote_tmp'] = '/tmp' 12 | if '_ansible_keep_remote_files' not in args: 13 | args['_ansible_keep_remote_files'] = False 14 | 15 | args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) 16 | basic._ANSIBLE_ARGS = to_bytes(args) 17 | 18 | 19 | class AnsibleExitJson(Exception): 20 | pass 21 | 22 | 23 | class AnsibleFailJson(Exception): 24 | pass 25 | 26 | 27 | def exit_json(*args, **kwargs): 28 | if 'changed' not in kwargs: 29 | kwargs['changed'] = False 30 | raise AnsibleExitJson(kwargs) 31 | 32 | 33 | def fail_json(*args, **kwargs): 34 | kwargs['failed'] = True 35 | raise AnsibleFailJson(kwargs) 36 | 37 | 38 | class ModuleTestCase(unittest.TestCase): 39 | 40 | def setUp(self): 41 | self.mock_module = patch.multiple(basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json) 42 | self.mock_module.start() 43 | self.mock_sleep = patch('time.sleep') 44 | self.mock_sleep.start() 45 | set_module_args({}) 46 | self.addCleanup(self.mock_module.stop) 47 | self.addCleanup(self.mock_sleep.stop) 48 | -------------------------------------------------------------------------------- /ci/roles/image/defaults/main.yml: -------------------------------------------------------------------------------- 1 | expected_fields: 2 | - architecture 3 | - checksum 4 | - container_format 5 | - created_at 6 | - direct_url 7 | - disk_format 8 | - file 9 | - has_auto_disk_config 10 | - hash_algo 11 | - hash_value 12 | - hw_cpu_cores 13 | - hw_cpu_policy 14 | - hw_cpu_sockets 15 | - hw_cpu_thread_policy 16 | - hw_cpu_threads 17 | - hw_disk_bus 18 | - hw_machine_type 19 | - hw_qemu_guest_agent 20 | - hw_rng_model 21 | - hw_scsi_model 22 | - hw_serial_port_count 23 | - hw_video_model 24 | - hw_video_ram 25 | - hw_vif_model 26 | - hw_watchdog_action 27 | - hypervisor_type 28 | - id 29 | - instance_type_rxtx_factor 30 | - instance_uuid 31 | - is_hidden 32 | - is_hw_boot_menu_enabled 33 | - is_hw_vif_multiqueue_enabled 34 | - is_protected 35 | - kernel_id 36 | - locations 37 | - metadata 38 | - min_disk 39 | - min_ram 40 | - name 41 | - needs_config_drive 42 | - needs_secure_boot 43 | - os_admin_user 44 | - os_command_line 45 | - os_distro 46 | - os_require_quiesce 47 | - os_shutdown_timeout 48 | - os_type 49 | - os_version 50 | - owner 51 | - owner_id 52 | - properties 53 | - ramdisk_id 54 | - schema 55 | - size 56 | - status 57 | - store 58 | - tags 59 | - updated_at 60 | - url 61 | - virtual_size 62 | - visibility 63 | - vm_mode 64 | - vmware_adaptertype 65 | - vmware_ostype 66 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = ansible-collections-openstack.cloud 3 | summary = Ansible collections for Openstack cloud 4 | description_file = 5 | README.md 6 | 7 | author = OpenStack 8 | author_email = openstack-discuss@lists.openstack.org 9 | home_page = https://opendev.org/openstack/ansible-collections-openstack/ 10 | classifier = 11 | License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) 12 | Development Status :: 5 - Production/Stable 13 | Intended Audience :: Developers 14 | Intended Audience :: System Administrators 15 | Intended Audience :: Information Technology 16 | Topic :: System :: Systems Administration 17 | Topic :: Utilities 18 | 19 | [global] 20 | setup_hooks = 21 | pbr.hooks.setup_hook 22 | 23 | [files] 24 | data_files = 25 | share/ansible/collections/ansible_collections/openstack/cloud/ = README.md 26 | share/ansible/collections/ansible_collections/openstack/cloud/roles/ = roles/* 27 | share/ansible/collections/ansible_collections/openstack/cloud/plugins/ = plugins/* 28 | share/ansible/collections/ansible_collections/openstack/cloud/playbooks/ = playbooks/* 29 | share/ansible/collections/ansible_collections/openstack/cloud/docs/ = docs/* 30 | share/ansible/collections/ansible_collections/openstack/cloud/meta/ = meta/* 31 | 32 | [wheel] 33 | universal = 1 34 | 35 | [pbr] 36 | skip_authors = True 37 | skip_changelog = True 38 | -------------------------------------------------------------------------------- /tools/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2019 Red Hat, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import pbr.version 17 | 18 | from ruamel.yaml import YAML 19 | 20 | import os 21 | import shutil 22 | 23 | 24 | def generate_version_info(): 25 | version_info = pbr.version.VersionInfo('openstack-cloud') 26 | semantic_version = version_info.semantic_version() 27 | release_string = semantic_version._long_version('-') 28 | 29 | yaml = YAML() 30 | yaml.explicit_start = True 31 | yaml.indent(sequence=4, offset=2) 32 | 33 | config = yaml.load(open('galaxy.yml.in')) 34 | config['version'] = release_string 35 | 36 | with open('galaxy.yml', 'w') as fp: 37 | yaml.dump(config, fp) 38 | 39 | 40 | def main(): 41 | generate_version_info() 42 | shutil.rmtree('build_artifact', ignore_errors=True) 43 | if os.path.exists('MANIFEST.json'): 44 | os.unlink('MANIFEST.json') 45 | 46 | 47 | if __name__ == '__main__': 48 | main() 49 | -------------------------------------------------------------------------------- /ci/roles/address_scope/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create address_scope 3 | openstack.cloud.address_scope: 4 | cloud: "{{ cloud }}" 5 | name: "{{ address_scope_name }}" 6 | shared: False 7 | ip_version: "4" 8 | register: create_address_scope 9 | 10 | - name: Verify returned values 11 | assert: 12 | that: 13 | - item in create_address_scope.address_scope 14 | loop: "{{ expected_fields }}" 15 | 16 | - name: Verify address scope 17 | assert: 18 | that: 19 | - create_address_scope is successful 20 | - create_address_scope is changed 21 | - create_address_scope.address_scope.name == address_scope_name 22 | - create_address_scope.address_scope.is_shared == False 23 | - create_address_scope.address_scope.ip_version == 4 24 | 25 | - name: Update address scope 26 | openstack.cloud.address_scope: 27 | cloud: "{{ cloud }}" 28 | name: "{{ address_scope_name }}" 29 | shared: True 30 | ip_version: "4" 31 | register: update_address_scope 32 | 33 | - name: Verify updated IPv4 address scope 34 | assert: 35 | that: 36 | - update_address_scope is successful 37 | - update_address_scope is changed 38 | - update_address_scope.address_scope.name == address_scope_name 39 | - update_address_scope.address_scope.is_shared == True 40 | - update_address_scope.address_scope.ip_version == 4 41 | 42 | - name: Delete created address scope 43 | openstack.cloud.address_scope: 44 | cloud: "{{ cloud }}" 45 | name: "{{ address_scope_name }}" 46 | state: absent 47 | -------------------------------------------------------------------------------- /ci/roles/server/defaults/main.yml: -------------------------------------------------------------------------------- 1 | boot_volume_size: 5 2 | expected_fields: 3 | - access_ipv4 4 | - access_ipv6 5 | - addresses 6 | - admin_password 7 | - attached_volumes 8 | - availability_zone 9 | - block_device_mapping 10 | - compute_host 11 | - config_drive 12 | - created_at 13 | - description 14 | - disk_config 15 | - flavor 16 | - flavor_id 17 | - has_config_drive 18 | - host_id 19 | - host_status 20 | - hostname 21 | - hypervisor_hostname 22 | - id 23 | - image 24 | - image_id 25 | - instance_name 26 | - is_locked 27 | - kernel_id 28 | - key_name 29 | - launch_index 30 | - launched_at 31 | - links 32 | - max_count 33 | - metadata 34 | - min_count 35 | - name 36 | - networks 37 | - power_state 38 | - progress 39 | - project_id 40 | - ramdisk_id 41 | - reservation_id 42 | - root_device_name 43 | - scheduler_hints 44 | - security_groups 45 | - server_groups 46 | - status 47 | - tags 48 | - task_state 49 | - terminated_at 50 | - trusted_image_certificates 51 | - updated_at 52 | - user_data 53 | - user_id 54 | - vm_state 55 | - volumes 56 | flavor_name: m1.tiny 57 | floating_ip_pool_name: public 58 | server_alt_name: ansible_server_alt 59 | server_alt_network: ansible_server_network_alt 60 | server_alt_security_group: ansible_server_security_group_alt 61 | server_alt_subnet: ansible_server_subnet_alt 62 | server_name: ansible_server 63 | server_network: ansible_server_network 64 | server_port: ansible_server_port 65 | server_security_group: ansible_server_security_group 66 | server_subnet: ansible_server_subnet 67 | -------------------------------------------------------------------------------- /tests/unit/mock/vault_helper.py: -------------------------------------------------------------------------------- 1 | # Ansible is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # Ansible is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with Ansible. If not, see . 13 | 14 | # Make coding more python3-ish 15 | 16 | from ansible.module_utils._text import to_bytes 17 | 18 | from ansible.parsing.vault import VaultSecret 19 | 20 | 21 | class TextVaultSecret(VaultSecret): 22 | '''A secret piece of text. ie, a password. Tracks text encoding. 23 | 24 | The text encoding of the text may not be the default text encoding so 25 | we keep track of the encoding so we encode it to the same bytes.''' 26 | 27 | def __init__(self, text, encoding=None, errors=None, _bytes=None): 28 | super(TextVaultSecret, self).__init__() 29 | self.text = text 30 | self.encoding = encoding or 'utf-8' 31 | self._bytes = _bytes 32 | self.errors = errors or 'strict' 33 | 34 | @property 35 | def bytes(self): 36 | '''The text encoded with encoding, unless we specifically set _bytes.''' 37 | return self._bytes or to_bytes(self.text, encoding=self.encoding, errors=self.errors) 38 | -------------------------------------------------------------------------------- /ci/roles/volume_manage/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create volume 3 | openstack.cloud.volume: 4 | cloud: "{{ cloud }}" 5 | state: present 6 | size: 1 7 | name: "{{ test_volume }}" 8 | description: Test volume 9 | register: vol 10 | 11 | - assert: 12 | that: item in vol.volume 13 | loop: "{{ expected_fields }}" 14 | 15 | - name: Unmanage volume 16 | openstack.cloud.volume_manage: 17 | cloud: "{{ cloud }}" 18 | state: absent 19 | name: "{{ vol.volume.id }}" 20 | 21 | - name: Unmanage volume again 22 | openstack.cloud.volume_manage: 23 | cloud: "{{ cloud }}" 24 | state: absent 25 | name: "{{ vol.volume.id }}" 26 | register: unmanage_idempotency 27 | 28 | - assert: 29 | that: 30 | - unmanage_idempotency is not changed 31 | 32 | - name: Manage volume 33 | openstack.cloud.volume_manage: 34 | cloud: "{{ cloud }}" 35 | state: present 36 | source_name: volume-{{ vol.volume.id }} 37 | host: "{{ vol.volume.host }}" 38 | name: "{{ managed_volume }}" 39 | register: new_vol 40 | 41 | - assert: 42 | that: 43 | - new_vol.volume.name == managed_volume 44 | 45 | - name: Manage volume again 46 | openstack.cloud.volume_manage: 47 | cloud: "{{ cloud }}" 48 | state: present 49 | source_name: volume-{{ vol.volume.id }} 50 | host: "{{ vol.volume.host }}" 51 | name: "{{ managed_volume }}" 52 | register: vol_idempotency 53 | 54 | - assert: 55 | that: 56 | - vol_idempotency is not changed 57 | 58 | - pause: 59 | seconds: 10 60 | 61 | - name: Delete volume 62 | openstack.cloud.volume: 63 | cloud: "{{ cloud }}" 64 | state: absent 65 | name: "{{ managed_volume }}" 66 | -------------------------------------------------------------------------------- /ci/roles/resources/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - module_defaults: 3 | group/openstack.cloud.openstack: 4 | cloud: "{{ cloud }}" 5 | # Listing modules individually is required for 6 | # backward compatibility with Ansible 2.9 only 7 | openstack.cloud.resources: 8 | cloud: "{{ cloud }}" 9 | block: 10 | - name: List images 11 | openstack.cloud.resources: 12 | service: image 13 | type: image 14 | register: images 15 | 16 | - name: Identify CirrOS image id 17 | set_fact: 18 | image_id: "{{ images.resources|community.general.json_query(query)|first }}" 19 | vars: 20 | query: "[?starts_with(name, 'cirros')].id" 21 | 22 | - name: Assert return values of resources module 23 | assert: 24 | that: 25 | - images is not changed 26 | # allow new fields to be introduced but prevent fields from being removed 27 | - expected_fields|difference(images.keys())|length == 0 28 | 29 | - name: List compute flavors 30 | openstack.cloud.resources: 31 | service: compute 32 | type: flavor 33 | register: flavors 34 | 35 | - name: Identify m1.tiny flavor id 36 | set_fact: 37 | flavor_id: "{{ flavors.resources|community.general.json_query(query)|first }}" 38 | vars: 39 | query: "[?name == 'm1.tiny'].id" 40 | 41 | - name: List public network 42 | openstack.cloud.resources: 43 | service: network 44 | type: network 45 | parameters: 46 | name: public 47 | register: networks 48 | 49 | - name: Assert public network 50 | assert: 51 | that: 52 | - networks.resources|length == 1 53 | - networks.resources.0.name == 'public' 54 | -------------------------------------------------------------------------------- /ci/roles/group_assignment/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create user 3 | openstack.cloud.identity_user: 4 | cloud: "{{ cloud }}" 5 | state: present 6 | name: ansible_user 7 | password: secret 8 | email: ansible.user@nowhere.net 9 | domain: default 10 | default_project: demo 11 | 12 | - name: Assign user to nonadmins group 13 | openstack.cloud.group_assignment: 14 | cloud: "{{ cloud }}" 15 | state: present 16 | user: ansible_user 17 | group: nonadmins 18 | register: group_assignment 19 | 20 | - name: Assert group assignment 21 | assert: 22 | that: 23 | - group_assignment is changed 24 | 25 | - name: Assign user to nonadmins group again 26 | openstack.cloud.group_assignment: 27 | cloud: "{{ cloud }}" 28 | state: present 29 | user: ansible_user 30 | group: nonadmins 31 | register: group_assignment 32 | 33 | - name: Assert group assignment 34 | assert: 35 | that: 36 | - group_assignment is not changed 37 | 38 | - name: Remove user from nonadmins group 39 | openstack.cloud.group_assignment: 40 | cloud: "{{ cloud }}" 41 | state: absent 42 | user: ansible_user 43 | group: nonadmins 44 | register: group_assignment 45 | 46 | - name: Assert group assignment 47 | assert: 48 | that: 49 | - group_assignment is changed 50 | 51 | - name: Remove user from nonadmins group again 52 | openstack.cloud.group_assignment: 53 | cloud: "{{ cloud }}" 54 | state: absent 55 | user: ansible_user 56 | group: nonadmins 57 | register: group_assignment 58 | 59 | - name: Assert group assignment 60 | assert: 61 | that: 62 | - group_assignment is not changed 63 | 64 | - name: Delete user 65 | openstack.cloud.identity_user: 66 | cloud: "{{ cloud }}" 67 | state: absent 68 | name: ansible_user 69 | -------------------------------------------------------------------------------- /ci/roles/server_group/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Create server group 2 | openstack.cloud.server_group: 3 | cloud: "{{ cloud }}" 4 | name: ansible_group 5 | policy: affinity 6 | register: server_group 7 | 8 | - name: Assert changed 9 | assert: 10 | that: server_group is changed 11 | 12 | - name: Assert return values 13 | assert: 14 | that: item in server_group.server_group 15 | loop: "{{ expected_fields }}" 16 | 17 | - name: Create server group again 18 | openstack.cloud.server_group: 19 | cloud: "{{ cloud }}" 20 | name: ansible_group 21 | policy: affinity 22 | register: server_group 23 | 24 | - name: Assert not changed 25 | assert: 26 | that: server_group is not changed 27 | 28 | - name: Delete server group 29 | openstack.cloud.server_group: 30 | cloud: "{{ cloud }}" 31 | name: ansible_group 32 | state: absent 33 | register: server_group 34 | 35 | - name: Assert changed 36 | assert: 37 | that: server_group is changed 38 | 39 | - name: Delete server group again 40 | openstack.cloud.server_group: 41 | cloud: "{{ cloud }}" 42 | name: ansible_group 43 | state: absent 44 | register: server_group 45 | 46 | - name: Assert not changed 47 | assert: 48 | that: server_group is not changed 49 | 50 | - name: Create server group with rules 51 | openstack.cloud.server_group: 52 | cloud: "{{ cloud }}" 53 | name: ansible_group 54 | policy: anti-affinity 55 | rules: 56 | max_server_per_host: 2 57 | register: server_group 58 | 59 | - name: Assert changed 60 | assert: 61 | that: server_group is changed 62 | 63 | - name: Assert return values 64 | assert: 65 | that: item in server_group.server_group 66 | loop: "{{ expected_fields }}" 67 | 68 | - name: Delete server group 69 | openstack.cloud.server_group: 70 | cloud: "{{ cloud }}" 71 | name: ansible_group 72 | state: absent 73 | -------------------------------------------------------------------------------- /ci/roles/baremetal_deploy_template/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # TODO: Actually run this role in CI. Atm we do not have DevStack's ironic plugin enabled. 3 | - name: Create baremetal deploy template 4 | openstack.cloud.baremetal_deploy_template: 5 | cloud: "{{ cloud }}" 6 | state: present 7 | name: CUSTOM_ANSIBLE_DEPLOY_TEMPLATE 8 | steps: 9 | - interface: bios 10 | step: apply_configuration 11 | args: 12 | settings: 13 | - name: some-setting 14 | value: some-value 15 | priority: 110 16 | register: template 17 | 18 | - debug: var=template 19 | 20 | - name: Assert return values of baremetal_deploy_template module 21 | assert: 22 | that: 23 | # allow new fields to be introduced but prevent fields from being removed 24 | - expected_fields|difference(template.template.keys())|length == 0 25 | 26 | - name: Update baremetal deploy template 27 | openstack.cloud.baremetal_deploy_template: 28 | cloud: "{{ cloud }}" 29 | state: present 30 | id: "{{ template.template.id }}" 31 | extra: 32 | foo: bar 33 | register: updated_template 34 | 35 | - name: Assert return values of updated baremetal deploy template 36 | assert: 37 | that: 38 | - updated_template is changed 39 | - updated_template.template.id == template.template.id 40 | 41 | - name: Update baremetal deploy template again 42 | openstack.cloud.baremetal_deploy_template: 43 | cloud: "{{ cloud }}" 44 | state: present 45 | id: "{{ template.template.id }}" 46 | register: updated_template 47 | 48 | - name: Assert return values of updated baremetal deploy template 49 | assert: 50 | that: 51 | - updated_template is not changed 52 | - updated_template.template.id == template.template.id 53 | 54 | - name: Delete Bare Metal deploy template 55 | openstack.cloud.baremetal_deploy_template: 56 | cloud: "{{ cloud }}" 57 | state: absent 58 | id: "{{ template.template.id }}" 59 | -------------------------------------------------------------------------------- /ci/roles/endpoint/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create a service endpoint for compute 3 | openstack.cloud.endpoint: 4 | cloud: "{{ cloud }}" 5 | service: nova 6 | endpoint_interface: internal 7 | url: http://controller:9292 8 | region: RegionOne 9 | state: present 10 | register: endpoint_test 11 | 12 | - debug: var=endpoint_test 13 | 14 | - name: Assert return values of endpoint module 15 | assert: 16 | that: 17 | # allow new fields to be introduced but prevent fields from being removed 18 | - expected_fields|difference(endpoint_test.endpoint.keys())|length == 0 19 | 20 | - name: Ensure service have the proper endpoint 21 | assert: 22 | that: 23 | - endpoint_test.endpoint.url == "http://controller:9292" 24 | 25 | - name: Create service endpoint for compute again 26 | openstack.cloud.endpoint: 27 | cloud: "{{ cloud }}" 28 | service: nova 29 | endpoint_interface: internal 30 | url: http://controller:9292 31 | region: RegionOne 32 | state: present 33 | register: endpoint_again 34 | 35 | - name: Ensure changed is false 36 | assert: 37 | that: 38 | - not endpoint_again.changed 39 | 40 | - name: Update service endpoint url 41 | openstack.cloud.endpoint: 42 | cloud: "{{ cloud }}" 43 | service: nova 44 | endpoint_interface: internal 45 | url: http://controller:9393 46 | region: RegionOne 47 | state: present 48 | register: endpoint_updated 49 | 50 | - name: Ensure service endpoint was updated 51 | assert: 52 | that: 53 | - endpoint_updated.endpoint.url == "http://controller:9393" 54 | 55 | - name: Delete service endpoint 56 | openstack.cloud.endpoint: 57 | cloud: "{{ cloud }}" 58 | service: nova 59 | endpoint_interface: internal 60 | url: http://controller:9393 61 | region: RegionOne 62 | state: absent 63 | register: endpoint_deleted 64 | 65 | - name: Ensure service endpoint was deleted 66 | assert: 67 | that: 68 | - endpoint_deleted.changed 69 | -------------------------------------------------------------------------------- /ci/roles/dns_zone/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create dns zone 3 | openstack.cloud.dns_zone: 4 | cloud: "{{ cloud }}" 5 | name: ansible.test.zone. 6 | type: primary 7 | email: test@example.net 8 | register: dns_zone 9 | 10 | - name: Assert return values of dns_zone module 11 | assert: 12 | that: 13 | - dns_zone.zone.name == "ansible.test.zone." 14 | - dns_zone.zone.type|lower == "primary" 15 | - dns_zone.zone.email == "test@example.net" 16 | # allow new fields to be introduced but prevent fields from being removed 17 | - expected_fields|difference(dns_zone.zone.keys())|length == 0 18 | 19 | - name: Update dns zone 20 | openstack.cloud.dns_zone: 21 | cloud: "{{ cloud }}" 22 | name: ansible.test.zone. 23 | description: "Another description" 24 | register: dns_zone 25 | 26 | - name: Assert return values of dns_zone module 27 | assert: 28 | that: 29 | - dns_zone.zone.description == "Another description" 30 | 31 | - name: Fetch all dns zones 32 | openstack.cloud.dns_zone_info: 33 | cloud: "{{ cloud }}" 34 | register: dns_zones 35 | 36 | - name: Assert return values of dns_zone_info module 37 | assert: 38 | that: 39 | - dns_zones is not changed 40 | - dns_zones | length > 0 41 | # allow new fields to be introduced but prevent fields from being removed 42 | - expected_fields|difference(dns_zones.zones[0].keys())|length == 0 43 | 44 | - name: Fetch a dns zone by name 45 | openstack.cloud.dns_zone_info: 46 | cloud: "{{ cloud }}" 47 | name: ansible.test.zone. 48 | register: dns_zones 49 | 50 | - name: Assert return values of dns_zone_info module 51 | assert: 52 | that: 53 | - dns_zones is not changed 54 | - dns_zones.zones | length == 1 55 | - dns_zones.zones[0].id == dns_zone.zone.id 56 | 57 | - name: Delete dns zone 58 | openstack.cloud.dns_zone: 59 | cloud: "{{ cloud }}" 60 | name: ansible.test.zone. 61 | state: absent 62 | register: dns_zone 63 | 64 | - name: Verify dns zone 65 | assert: 66 | that: 67 | - dns_zone is changed 68 | -------------------------------------------------------------------------------- /ci/roles/application_credential/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Create application credentials 4 | openstack.cloud.application_credential: 5 | cloud: "{{ cloud }}" 6 | state: present 7 | name: ansible_creds 8 | description: dummy description 9 | register: appcred 10 | 11 | - name: Assert return values of application_credential module 12 | assert: 13 | that: 14 | - appcred is changed 15 | # allow new fields to be introduced but prevent fields from being removed 16 | - expected_fields|difference(appcred.application_credential.keys())|length == 0 17 | 18 | - name: Create the application credential again 19 | openstack.cloud.application_credential: 20 | cloud: "{{ cloud }}" 21 | state: present 22 | name: ansible_creds 23 | description: dummy description 24 | register: appcred 25 | 26 | - name: Assert return values of ansible_credential module 27 | assert: 28 | that: 29 | # credentials are immutable so creating twice will cause delete and create 30 | - appcred is changed 31 | # allow new fields to be introduced but prevent fields from being removed 32 | - expected_fields|difference(appcred.application_credential.keys())|length == 0 33 | 34 | - name: Update the application credential again 35 | openstack.cloud.application_credential: 36 | cloud: "{{ cloud }}" 37 | state: present 38 | name: ansible_creds 39 | description: new description 40 | register: appcred 41 | 42 | - name: Assert application credential changed 43 | assert: 44 | that: 45 | - appcred is changed 46 | - appcred.application_credential.description == 'new description' 47 | 48 | - name: Get list of all keypairs using application credential 49 | openstack.cloud.keypair_info: 50 | cloud: "{{ appcred.cloud }}" 51 | 52 | - name: Delete application credential 53 | openstack.cloud.application_credential: 54 | cloud: "{{ cloud }}" 55 | state: absent 56 | name: ansible_creds 57 | register: appcred 58 | 59 | - name: Assert application credential changed 60 | assert: 61 | that: appcred is changed 62 | -------------------------------------------------------------------------------- /tools/run-ansible-sanity.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2020 Red Hat, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | TOXDIR=${1:-.} 17 | ANSIBLE_COLLECTIONS_PATH=$(mktemp -d) 18 | echo "Executing ansible-test sanity checks in ${ANSIBLE_COLLECTIONS_PATH}" 19 | 20 | trap "rm -rf ${ANSIBLE_COLLECTIONS_PATH}" err exit 21 | 22 | PY_VER=$(python3 -c "from platform import python_version;print(python_version())" | cut -f 1,2 -d".") 23 | echo "Running test with Python version ${PY_VER}" 24 | 25 | rm -rf "${ANSIBLE_COLLECTIONS_PATH}" 26 | mkdir -p ${ANSIBLE_COLLECTIONS_PATH}/ansible_collections/openstack/cloud 27 | cp -a ${TOXDIR}/{plugins,meta,tests,docs,galaxy.yml} ${ANSIBLE_COLLECTIONS_PATH}/ansible_collections/openstack/cloud 28 | cd ${ANSIBLE_COLLECTIONS_PATH}/ansible_collections/openstack/cloud/ 29 | echo "Running ansible-test with version:" 30 | ansible --version 31 | # Ansible-core 2.17 dropped support for the metaclass-boilerplate and future-import-boilerplate tests. 32 | # TODO(mgoddard): Drop this workaround when ansible-core 2.16 is EOL. 33 | ANSIBLE_VER=$(python3 -m pip show ansible-core | awk '$1 == "Version:" { print $2 }') 34 | ANSIBLE_MAJOR_VER=$(echo "$ANSIBLE_VER" | sed 's/^\([0-9]\)\..*/\1/g') 35 | SKIP_TESTS="" 36 | if [[ $ANSIBLE_MAJOR_VER -eq 2 ]]; then 37 | ANSIBLE_MINOR_VER=$(echo "$ANSIBLE_VER" | sed 's/^2\.\([^\.]*\)\..*/\1/g') 38 | if [[ $ANSIBLE_MINOR_VER -le 16 ]]; then 39 | SKIP_TESTS="--skip-test metaclass-boilerplate --skip-test future-import-boilerplate" 40 | fi 41 | fi 42 | ansible-test sanity -v \ 43 | --venv \ 44 | --python ${PY_VER} \ 45 | $SKIP_TESTS \ 46 | plugins/ docs/ meta/ 47 | -------------------------------------------------------------------------------- /ci/roles/object/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create container 3 | openstack.cloud.object_container: 4 | cloud: "{{ cloud }}" 5 | state: present 6 | name: ansible_container 7 | 8 | - name: Create object from data 9 | openstack.cloud.object: 10 | cloud: "{{ cloud }}" 11 | state: present 12 | name: ansible_object 13 | data: "this is a test" 14 | container: ansible_container 15 | register: object 16 | 17 | - name: Assert return values of object module 18 | assert: 19 | that: 20 | - object.object.id == "ansible_object" 21 | # allow new fields to be introduced but prevent fields from being removed 22 | - expected_fields|difference(object.object.keys())|length == 0 23 | 24 | - name: Delete object 25 | openstack.cloud.object: 26 | cloud: "{{ cloud }}" 27 | state: absent 28 | name: ansible_object 29 | container: ansible_container 30 | 31 | - name: Create object from file 32 | block: 33 | - name: Create temporary data file 34 | ansible.builtin.tempfile: 35 | register: tmp_file 36 | 37 | - name: Populate data file 38 | ansible.builtin.copy: 39 | content: "this is a test" 40 | dest: "{{ tmp_file.path }}" 41 | 42 | - name: Create object from data file 43 | openstack.cloud.object: 44 | cloud: "{{ cloud }}" 45 | state: present 46 | name: ansible_object 47 | filename: "{{ tmp_file.path }}" 48 | container: ansible_container 49 | register: object 50 | 51 | always: 52 | - name: Remove temporary data file 53 | ansible.builtin.file: 54 | path: "{{ tmp_file.path }}" 55 | state: absent 56 | when: tmp_file is defined and 'path' in tmp_file 57 | 58 | - name: Assert return values of object module 59 | assert: 60 | that: 61 | - object.object.id == "ansible_object" 62 | # allow new fields to be introduced but prevent fields from being removed 63 | - expected_fields|difference(object.object.keys())|length == 0 64 | 65 | - name: Delete object 66 | openstack.cloud.object: 67 | cloud: "{{ cloud }}" 68 | state: absent 69 | name: ansible_object 70 | container: ansible_container 71 | 72 | - name: Delete container 73 | openstack.cloud.object_container: 74 | cloud: "{{ cloud }}" 75 | state: absent 76 | name: ansible_container 77 | -------------------------------------------------------------------------------- /ci/roles/baremetal_node/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # TODO: Actually run this role in CI. Atm we do not have DevStack's ironic plugin enabled. 3 | - name: Create baremetal node 4 | openstack.cloud.baremetal_node: 5 | cloud: "{{ cloud }}" 6 | driver_info: 7 | ipmi_address: "1.2.3.4" 8 | ipmi_username: "admin" 9 | ipmi_password: "secret" 10 | name: ansible_baremetal_node 11 | nics: 12 | - mac: "aa:bb:cc:aa:bb:cc" 13 | state: present 14 | register: node 15 | 16 | - debug: var=node 17 | 18 | - name: assert return values of baremetal_node module 19 | assert: 20 | that: 21 | # allow new fields to be introduced but prevent fields from being removed 22 | - expected_fields|difference(node.node.keys())|length == 0 23 | 24 | - name: Fetch baremetal nodes 25 | openstack.cloud.baremetal_node_info: 26 | cloud: "{{ cloud }}" 27 | register: nodes 28 | 29 | - name: assert module results of baremetal_node_info module 30 | assert: 31 | that: 32 | - nodes.nodes|list|length > 0 33 | 34 | - name: assert return values of baremetal_node_info module 35 | assert: 36 | that: 37 | # allow new fields to be introduced but prevent fields from being removed 38 | - expected_fields|difference(nodes.nodes.0.keys())|length == 0 39 | 40 | - name: Fetch baremetal node by name 41 | openstack.cloud.baremetal_node_info: 42 | cloud: "{{ cloud }}" 43 | name: ansible_baremetal_node 44 | register: nodes 45 | 46 | - name: assert module results of baremetal_node_info module 47 | assert: 48 | that: 49 | - nodes.nodes|list|length == 1 50 | - nodes.nodes.0.id == node.node.id 51 | - nodes.nodes.0.name == "ansible_baremetal_node" 52 | 53 | - name: Delete baremetal node 54 | openstack.cloud.baremetal_node: 55 | cloud: "{{ cloud }}" 56 | driver_info: 57 | ipmi_address: "1.2.3.4" 58 | ipmi_username: "admin" 59 | ipmi_password: "secret" 60 | name: ansible_baremetal_node 61 | nics: 62 | - mac: "aa:bb:cc:aa:bb:cc" 63 | state: absent 64 | 65 | - name: Fetch baremetal node by name 66 | openstack.cloud.baremetal_node_info: 67 | cloud: "{{ cloud }}" 68 | name: ansible_baremetal_node 69 | register: nodes 70 | 71 | - name: Assert that baremetal node has been deleted 72 | assert: 73 | that: 74 | - nodes.nodes|list|length == 0 75 | -------------------------------------------------------------------------------- /ci/roles/identity_role/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create identity role 3 | openstack.cloud.identity_role: 4 | cloud: "{{ cloud }}" 5 | state: present 6 | name: ansible_role 7 | description: "ansible role" 8 | register: role 9 | 10 | - name: Assert return values of identity_role module 11 | assert: 12 | that: 13 | - role.role.name == 'ansible_role' 14 | - role.role.description == "ansible role" 15 | # allow new fields to be introduced but prevent fields from being removed 16 | - expected_fields|difference(role.role.keys())|length == 0 17 | 18 | - name: Try to get role 19 | openstack.cloud.identity_role_info: 20 | cloud: "{{ cloud }}" 21 | name: ansible_role 22 | register: roles 23 | 24 | - name: Assert role found 25 | assert: 26 | that: 27 | - roles.roles | length == 1 28 | - roles.roles.0.name == 'ansible_role' 29 | 30 | - name: Fetch all roles 31 | openstack.cloud.identity_role_info: 32 | cloud: "{{ cloud }}" 33 | register: roles 34 | 35 | - name: Assert return values of identity_role_info module 36 | assert: 37 | that: 38 | - roles.roles | length > 0 39 | # allow new fields to be introduced but prevent fields from being removed 40 | - expected_fields|difference(roles.roles.0.keys())|length == 0 41 | 42 | - name: Create identity role again 43 | openstack.cloud.identity_role: 44 | cloud: "{{ cloud }}" 45 | state: present 46 | name: ansible_role 47 | description: "ansible role" 48 | register: role 49 | 50 | - name: Assert role did not change 51 | assert: 52 | that: 53 | - role is not changed 54 | 55 | - name: Delete identity role 56 | openstack.cloud.identity_role: 57 | cloud: "{{ cloud }}" 58 | state: absent 59 | name: ansible_role 60 | register: role 61 | 62 | - name: Assert role changed 63 | assert: 64 | that: 65 | - role is changed 66 | 67 | - name: Try to get role 68 | openstack.cloud.identity_role_info: 69 | cloud: "{{ cloud }}" 70 | name: ansible_role 71 | register: roles 72 | 73 | - name: Assert no role found 74 | assert: 75 | that: 76 | - roles.roles | length == 0 77 | 78 | - name: Delete role again 79 | openstack.cloud.identity_role: 80 | cloud: "{{ cloud }}" 81 | state: absent 82 | name: ansible_role 83 | register: role 84 | 85 | - name: Assert role did not change 86 | assert: 87 | that: 88 | - role is not changed 89 | -------------------------------------------------------------------------------- /meta/runtime.yml: -------------------------------------------------------------------------------- 1 | requires_ansible: ">=2.8" 2 | action_groups: 3 | openstack: 4 | - address_scope 5 | - application_credential 6 | - auth 7 | - baremetal_deploy_template 8 | - baremetal_inspect 9 | - baremetal_node 10 | - baremetal_node_action 11 | - baremetal_node_info 12 | - baremetal_port 13 | - baremetal_port_info 14 | - catalog_service 15 | - catalog_service_info 16 | - coe_cluster 17 | - coe_cluster_template 18 | - compute_flavor 19 | - compute_flavor_access 20 | - compute_flavor_info 21 | - compute_service_info 22 | - config 23 | - dns_zone 24 | - dns_zone_info 25 | - endpoint 26 | - federation_idp 27 | - federation_idp_info 28 | - federation_mapping 29 | - federation_mapping_info 30 | - floating_ip 31 | - floating_ip_info 32 | - group_assignment 33 | - host_aggregate 34 | - identity_domain 35 | - identity_domain_info 36 | - identity_group 37 | - identity_group_info 38 | - identity_role 39 | - identity_role_info 40 | - identity_user 41 | - identity_user_info 42 | - image 43 | - image_info 44 | - keypair 45 | - keypair_info 46 | - keystone_federation_protocol 47 | - keystone_federation_protocol_info 48 | - lb_health_monitor 49 | - lb_listener 50 | - lb_member 51 | - lb_pool 52 | - loadbalancer 53 | - network 54 | - networks_info 55 | - neutron_rbac_policies_info 56 | - neutron_rbac_policy 57 | - object 58 | - object_container 59 | - object_containers_info 60 | - port 61 | - port_info 62 | - project 63 | - project_info 64 | - quota 65 | - recordset 66 | - resource 67 | - resources 68 | - role_assignment 69 | - router 70 | - routers_info 71 | - security_group 72 | - security_group_info 73 | - security_group_rule 74 | - security_group_rule_info 75 | - server 76 | - server_action 77 | - server_group 78 | - server_info 79 | - server_metadata 80 | - server_volume 81 | - share_type 82 | - share_type_info 83 | - stack 84 | - stack_info 85 | - subnet 86 | - subnet_pool 87 | - subnets_info 88 | - trunk 89 | - volume 90 | - volume_manage 91 | - volume_backup 92 | - volume_backup_info 93 | - volume_info 94 | - volume_service_info 95 | - volume_snapshot 96 | - volume_snapshot_info 97 | - volume_type_access 98 | -------------------------------------------------------------------------------- /ci/roles/compute_flavor_access/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create flavor 3 | openstack.cloud.compute_flavor: 4 | cloud: devstack-admin 5 | state: present 6 | name: ansible_flavor 7 | is_public: False 8 | ram: 1024 9 | vcpus: 1 10 | disk: 10 11 | ephemeral: 10 12 | swap: 1 13 | register: flavor 14 | 15 | - name: Fetch demo project 16 | openstack.cloud.project_info: 17 | cloud: devstack-admin 18 | name: demo 19 | register: projects 20 | 21 | - name: Verify demo project 22 | assert: 23 | that: 24 | - projects.projects|length == 1 25 | - projects.projects.0.name == "demo" 26 | 27 | - name: Grant access to flavor 28 | openstack.cloud.compute_flavor_access: 29 | cloud: devstack-admin 30 | name: ansible_flavor 31 | project: demo 32 | state: present 33 | register: access 34 | 35 | - name: Verify access 36 | assert: 37 | that: 38 | - access is changed 39 | - access.flavor.id == flavor.flavor.id 40 | 41 | # TODO: Replace with appropriate Ansible module once available 42 | - name: Get compute flavor 43 | command: openstack --os-cloud=devstack-admin flavor show ansible_flavor -f json 44 | register: flavor_show 45 | 46 | - name: Verify volume type access 47 | assert: 48 | that: 49 | - (flavor_show.stdout | from_json).name == 'ansible_flavor' 50 | - projects.projects.0.id in (flavor_show.stdout | from_json).access_project_ids 51 | 52 | - name: Grant access to flavor again 53 | openstack.cloud.compute_flavor_access: 54 | cloud: devstack-admin 55 | name: ansible_flavor 56 | project: demo 57 | state: present 58 | register: access 59 | 60 | - name: Verify access did not change 61 | assert: 62 | that: 63 | - access is not changed 64 | 65 | - name: Revoke access to flavor 66 | openstack.cloud.compute_flavor_access: 67 | cloud: devstack-admin 68 | name: ansible_flavor 69 | project: demo 70 | state: absent 71 | register: access 72 | 73 | - name: Verify revoked access 74 | assert: 75 | that: 76 | - access is changed 77 | - access.flavor.id == flavor.flavor.id 78 | 79 | - name: Revoke access to flavor again 80 | openstack.cloud.compute_flavor_access: 81 | cloud: devstack-admin 82 | name: ansible_flavor 83 | project: demo 84 | state: absent 85 | register: access 86 | 87 | - name: Verify access did not change 88 | assert: 89 | that: 90 | - access is not changed 91 | 92 | - name: Delete flavor 93 | openstack.cloud.compute_flavor: 94 | cloud: devstack-admin 95 | state: absent 96 | name: ansible_flavor 97 | -------------------------------------------------------------------------------- /ci/roles/volume_type/tasks/volume_encryption.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test, Volume type has no encryption 3 | openstack.cloud.volume_type_info: 4 | cloud: "{{ cloud }}" 5 | name: "{{ volume_type_name }}" 6 | register: the_result 7 | - name: Check volume type has no encryption 8 | ansible.builtin.assert: 9 | that: 10 | - the_result.encryption.id == None 11 | success_msg: >- 12 | Success: Volume type has no encryption at the moment 13 | 14 | - name: Test, create volume type encryption 15 | openstack.cloud.volume_type_encryption: 16 | cloud: "{{ cloud }}" 17 | volume_type: "{{ volume_type_name }}" 18 | state: present 19 | encryption_provider: "{{ enc_provider_name }}" 20 | encryption_cipher: "{{ enc_cipher }}" 21 | encryption_control_location: "{{ enc_control_location }}" 22 | encryption_key_size: "{{ enc_key_size }}" 23 | register: the_result 24 | - name: Check volume type encryption 25 | ansible.builtin.assert: 26 | that: 27 | - the_result.encryption.cipher == enc_cipher 28 | - the_result.encryption.control_location == enc_control_location 29 | - the_result.encryption.key_size == enc_key_size 30 | - the_result.encryption.provider == enc_provider_name 31 | success_msg: >- 32 | Success: {{ the_result.encryption.encryption_id }} 33 | 34 | - name: Test, update volume type encryption 35 | openstack.cloud.volume_type_encryption: 36 | cloud: "{{ cloud }}" 37 | volume_type: "{{ volume_type_name }}" 38 | state: present 39 | encryption_provider: "{{ enc_provider_name }}" 40 | encryption_cipher: "{{ enc_cipher }}" 41 | encryption_control_location: "{{ enc_control_alt_location }}" 42 | encryption_key_size: "{{ enc_key_size }}" 43 | register: the_result 44 | - name: Check volume type encryption change 45 | ansible.builtin.assert: 46 | that: 47 | - the_result.encryption.control_location == enc_control_alt_location 48 | success_msg: >- 49 | New location: {{ the_result.encryption.control_location }} 50 | 51 | - name: Test, delete volume type encryption 52 | openstack.cloud.volume_type_encryption: 53 | cloud: "{{ cloud }}" 54 | volume_type: "{{ volume_type_name }}" 55 | state: absent 56 | register: the_result 57 | - name: Get volume type details 58 | openstack.cloud.volume_type_info: 59 | cloud: "{{ cloud }}" 60 | name: "{{ volume_type_name }}" 61 | register: the_result 62 | - name: Check volume type has no encryption 63 | ansible.builtin.assert: 64 | that: 65 | - the_result.encryption.id == None 66 | success_msg: >- 67 | Success: Volume type has no encryption 68 | -------------------------------------------------------------------------------- /ci/roles/coe_cluster_template/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create keypair 3 | openstack.cloud.keypair: 4 | cloud: "{{ cloud }}" 5 | name: ansible_keypair 6 | state: present 7 | register: keypair 8 | 9 | - name: List all images 10 | openstack.cloud.image_info: 11 | cloud: "{{ cloud }}" 12 | register: images 13 | 14 | - name: Identify Fedora CoreOS image id 15 | set_fact: 16 | image_id: "{{ images.images|community.general.json_query(query)|first }}" 17 | vars: 18 | query: "[?starts_with(name, 'fedora-coreos')].id" 19 | 20 | - name: Create Kubernetes cluster template 21 | openstack.cloud.coe_cluster_template: 22 | cloud: "{{ cloud }}" 23 | coe: kubernetes 24 | is_floating_ip_enabled: false 25 | image_id: '{{ image_id }}' 26 | keypair_id: '{{ keypair.keypair.id }}' 27 | name: k8s 28 | state: present 29 | labels: 30 | docker_volume_size: 10 31 | cloud_provider_tag: v1.23.1 32 | register: coe_cluster_template 33 | 34 | - name: Assert return values of coe_cluster_template module 35 | assert: 36 | that: 37 | # allow new fields to be introduced but prevent fields from being removed 38 | - expected_fields|difference(coe_cluster_template.cluster_template.keys())|length == 0 39 | 40 | - name: Create Kubernetes cluster template again 41 | openstack.cloud.coe_cluster_template: 42 | cloud: "{{ cloud }}" 43 | coe: kubernetes 44 | is_floating_ip_enabled: false 45 | image_id: '{{ image_id }}' 46 | keypair_id: '{{ keypair.keypair.id }}' 47 | name: k8s 48 | state: present 49 | labels: 50 | docker_volume_size: 10 51 | cloud_provider_tag: v1.23.1 52 | register: coe_cluster_template 53 | 54 | - name: Assert return values of coe_cluster_template module 55 | assert: 56 | that: 57 | - coe_cluster_template is not changed 58 | 59 | - name: Delete Kubernetes cluster template 60 | openstack.cloud.coe_cluster_template: 61 | cloud: "{{ cloud }}" 62 | name: k8s 63 | state: absent 64 | register: coe_cluster_template 65 | 66 | - name: Assert return values of coe_cluster_template module 67 | assert: 68 | that: 69 | - coe_cluster_template is changed 70 | 71 | - name: Delete Kubernetes cluster template again 72 | openstack.cloud.coe_cluster_template: 73 | cloud: "{{ cloud }}" 74 | name: k8s 75 | state: absent 76 | register: coe_cluster_template 77 | 78 | - name: Assert return values of coe_cluster_template module 79 | assert: 80 | that: 81 | - coe_cluster_template is not changed 82 | 83 | - name: Delete keypair 84 | openstack.cloud.keypair: 85 | cloud: "{{ cloud }}" 86 | name: ansible_keypair 87 | state: absent 88 | -------------------------------------------------------------------------------- /ci/roles/stack/tasks/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create minimal stack 3 | openstack.cloud.stack: 4 | cloud: "{{ cloud }}" 5 | template: "roles/stack/files/hello-world.yaml" 6 | name: "{{ stack_name }}" 7 | tags: "tag1,tag2" 8 | register: stack 9 | 10 | - name: Assert fields returned by create stack 11 | assert: 12 | that: item in stack.stack 13 | loop: "{{ expected_fields }}" 14 | 15 | - name: List stacks 16 | openstack.cloud.stack_info: 17 | cloud: "{{ cloud }}" 18 | register: stacks 19 | 20 | - name: Assert stack_info module return values 21 | assert: 22 | that: 23 | - stacks.stacks|length > 0 24 | 25 | - name: Assert fields returned by stack info 26 | assert: 27 | that: item in stacks.stacks[0] 28 | loop: "{{ expected_fields }}" 29 | 30 | - name: Get single stack 31 | openstack.cloud.stack_info: 32 | cloud: "{{ cloud }}" 33 | name: "{{ stack_name }}" 34 | register: stacks 35 | 36 | - name: Assert single stack 37 | assert: 38 | that: 39 | - stacks.stacks|length == 1 40 | - stacks.stacks.0.name == stack_name 41 | - stacks.stacks.0.id == stack.stack.id 42 | # Older openstacksdk releases use datatype list instead of str for tags 43 | # Ref.: https://review.opendev.org/c/openstack/openstacksdk/+/860534 44 | - stacks.stacks.0.tags|string in ["tag1,tag2", "['tag1', 'tag2']"] 45 | 46 | - name: Update stack 47 | openstack.cloud.stack: 48 | cloud: "{{ cloud }}" 49 | template: "roles/stack/files/hello-world.yaml" 50 | name: "{{ stack_name }}" 51 | tags: "tag1,tag2,tag3" 52 | register: stack_updated 53 | 54 | - name: Assert updated stack 55 | assert: 56 | that: 57 | - stack_updated.stack.id == stack.stack.id 58 | - stack_updated is changed 59 | 60 | - name: Get updated stack 61 | openstack.cloud.stack_info: 62 | cloud: "{{ cloud }}" 63 | name: "{{ stack_name }}" 64 | register: stacks 65 | 66 | - name: Assert updated stack 67 | assert: 68 | that: 69 | - stacks.stacks|length == 1 70 | - stacks.stacks.0.id == stack.stack.id 71 | # Older openstacksdk releases use datatype list instead of str for tags 72 | # Ref.: https://review.opendev.org/c/openstack/openstacksdk/+/860534 73 | - stacks.stacks.0.tags|string in ["tag1,tag2,tag3", "['tag1', 'tag2', 'tag3']"] 74 | 75 | - name: Delete stack 76 | openstack.cloud.stack: 77 | cloud: "{{ cloud }}" 78 | name: "{{ stack_name }}" 79 | state: absent 80 | 81 | - name: Get single stack 82 | openstack.cloud.stack_info: 83 | cloud: "{{ cloud }}" 84 | name: "{{ stack_name }}" 85 | register: stacks 86 | 87 | - assert: 88 | that: 89 | - (stacks.stacks|length == 0) or (stacks.stacks.0.status == 'DELETE_COMPLETE') 90 | -------------------------------------------------------------------------------- /ci/roles/host_aggregate/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ensure aggregate doesn't exist before tests 3 | openstack.cloud.host_aggregate: 4 | cloud: "{{ cloud }}" 5 | state: absent 6 | name: test_aggregate 7 | register: aggregate 8 | 9 | - block: 10 | - name: create aggregate 11 | openstack.cloud.host_aggregate: 12 | cloud: "{{ cloud }}" 13 | state: present 14 | name: test_aggregate 15 | hosts: 16 | - "{{ ansible_hostname }}" 17 | register: aggregate 18 | 19 | - name: assert aggregate is changed 20 | assert: 21 | that: aggregate is changed 22 | 23 | - name: assert aggregate fields 24 | assert: 25 | that: item in aggregate.aggregate 26 | loop: "{{ expected_fields }}" 27 | 28 | - block: 29 | - name: recreate aggregate 30 | openstack.cloud.host_aggregate: 31 | cloud: "{{ cloud }}" 32 | state: present 33 | name: test_aggregate 34 | hosts: 35 | - "{{ ansible_hostname }}" 36 | register: aggregate 37 | 38 | - name: assert aggregate is not changed 39 | assert: 40 | that: aggregate is not changed 41 | 42 | - name: assert aggregate fields 43 | assert: 44 | that: item in aggregate.aggregate 45 | loop: "{{ expected_fields }}" 46 | 47 | - block: 48 | - name: update aggregate 49 | openstack.cloud.host_aggregate: 50 | cloud: "{{ cloud }}" 51 | state: present 52 | name: test_aggregate 53 | metadata: 54 | ssd: "true" 55 | hosts: 56 | - "{{ ansible_hostname }}" 57 | register: aggregate 58 | 59 | - name: assert aggregate is changed 60 | assert: 61 | that: aggregate is changed 62 | 63 | - name: assert aggregate fields 64 | assert: 65 | that: item in aggregate.aggregate 66 | loop: "{{ expected_fields }}" 67 | 68 | - block: 69 | - name: purge hosts 70 | openstack.cloud.host_aggregate: 71 | cloud: "{{ cloud }}" 72 | state: present 73 | name: test_aggregate 74 | hosts: [] 75 | purge_hosts: true 76 | register: aggregate 77 | 78 | - name: assert hosts were purged 79 | assert: 80 | that: 81 | - aggregate is changed 82 | - aggregate.aggregate.hosts | length == 0 83 | 84 | - name: assert aggregate fields 85 | assert: 86 | that: item in aggregate.aggregate 87 | loop: "{{ expected_fields }}" 88 | 89 | - block: 90 | - name: delete aggregate 91 | openstack.cloud.host_aggregate: 92 | cloud: "{{ cloud }}" 93 | state: absent 94 | name: test_aggregate 95 | register: aggregate 96 | 97 | - name: assert aggregate is changed 98 | assert: 99 | that: aggregate is changed 100 | -------------------------------------------------------------------------------- /tests/unit/plugins/inventory/test_openstack.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright 2018 Lars Kellogg-Stedman 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | 20 | # Make coding more python3-ish 21 | 22 | import pytest 23 | 24 | from ansible_collections.openstack.cloud.plugins.inventory.openstack import InventoryModule 25 | from ansible.inventory.data import InventoryData 26 | from ansible.template import Templar 27 | 28 | 29 | config_data = { 30 | 'plugin': 'openstack', 31 | 'compose': { 32 | 'composed_var': '"testvar-" + testvar', 33 | }, 34 | 'groups': { 35 | 'testgroup': '"host" in inventory_hostname', 36 | }, 37 | 'keyed_groups': 38 | [{ 39 | 'prefix': 'keyed', 40 | 'key': 'testvar', 41 | }] 42 | } 43 | 44 | hostvars = { 45 | 'host0': { 46 | 'inventory_hostname': 'host0', 47 | 'testvar': '0', 48 | }, 49 | 'host1': { 50 | 'inventory_hostname': 'host1', 51 | 'testvar': '1', 52 | }, 53 | } 54 | 55 | 56 | @pytest.fixture(scope="module") 57 | def inventory(): 58 | inventory = InventoryModule() 59 | inventory._config_data = config_data 60 | inventory.inventory = InventoryData() 61 | inventory.templar = Templar(loader=None) 62 | 63 | for host in hostvars: 64 | inventory.inventory.add_host(host) 65 | 66 | return inventory 67 | 68 | 69 | def test_simple_groups(inventory): 70 | inventory._set_variables(hostvars, {}) 71 | groups = inventory.inventory.get_groups_dict() 72 | assert 'testgroup' in groups 73 | assert len(groups['testgroup']) == len(hostvars) 74 | 75 | 76 | def test_keyed_groups(inventory): 77 | inventory._set_variables(hostvars, {}) 78 | assert 'keyed_0' in inventory.inventory.groups 79 | assert 'keyed_1' in inventory.inventory.groups 80 | 81 | 82 | def test_composed_vars(inventory): 83 | inventory._set_variables(hostvars, {}) 84 | 85 | for host in hostvars: 86 | assert host in inventory.inventory.hosts 87 | host = inventory.inventory.get_host(host) 88 | assert host.vars['composed_var'] == 'testvar-{testvar}'.format(**hostvars[host.name]) 89 | -------------------------------------------------------------------------------- /plugins/modules/federation_idp_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: Ansible Project 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | DOCUMENTATION = r''' 8 | module: federation_idp_info 9 | short_description: Fetch OpenStack federation identity providers 10 | author: OpenStack Ansible SIG 11 | description: 12 | - Fetch OpenStack federation identity providers. 13 | options: 14 | id: 15 | description: 16 | - The ID (and name) of the identity provider to fetch. 17 | type: str 18 | aliases: ['name'] 19 | extends_documentation_fragment: 20 | - openstack.cloud.openstack 21 | ''' 22 | 23 | EXAMPLES = r''' 24 | - name: Fetch a specific identity provider 25 | openstack.cloud.federation_idp_info: 26 | cloud: example_cloud 27 | name: example_provider 28 | 29 | - name: Fetch all providers 30 | openstack.cloud.federation_idp_info: 31 | cloud: example_cloud 32 | ''' 33 | 34 | RETURN = r''' 35 | identity_providers: 36 | description: Dictionary describing the identity providers 37 | returned: always 38 | type: list 39 | elements: dict 40 | contains: 41 | description: 42 | description: Identity provider description 43 | type: str 44 | sample: "demodescription" 45 | domain_id: 46 | description: Domain to which the identity provider belongs 47 | type: str 48 | sample: "default" 49 | id: 50 | description: Identity provider ID 51 | type: str 52 | sample: "test-idp" 53 | is_enabled: 54 | description: Indicates whether the identity provider is enabled 55 | type: bool 56 | name: 57 | description: Name of the identity provider, equals its ID. 58 | type: str 59 | sample: "test-idp" 60 | remote_ids: 61 | description: Remote IDs associated with the identity provider 62 | type: list 63 | ''' 64 | 65 | from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule 66 | 67 | 68 | class IdentityFederationIdpInfoModule(OpenStackModule): 69 | argument_spec = dict( 70 | id=dict(aliases=['name']), 71 | ) 72 | module_kwargs = dict( 73 | supports_check_mode=True 74 | ) 75 | 76 | def run(self): 77 | kwargs = dict((k, self.params[k]) 78 | for k in ['id'] 79 | if self.params[k] is not None) 80 | identity_providers = self.conn.identity.identity_providers(**kwargs) 81 | self.exit_json( 82 | changed=False, 83 | identity_providers=[i.to_dict(computed=False) 84 | for i in identity_providers]) 85 | 86 | 87 | def main(): 88 | module = IdentityFederationIdpInfoModule() 89 | module() 90 | 91 | 92 | if __name__ == '__main__': 93 | main() 94 | -------------------------------------------------------------------------------- /ci/roles/volume_snapshot/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Get existing snapshots 3 | openstack.cloud.volume_snapshot_info: 4 | cloud: "{{ cloud }}" 5 | register: info 6 | 7 | - name: Assert volume_snapshot_info 8 | assert: 9 | that: 10 | - info.volume_snapshots|length == 0 11 | 12 | - name: Get non-existing snapshot 13 | openstack.cloud.volume_snapshot_info: 14 | cloud: "{{ cloud }}" 15 | name: non-existing-snapshot 16 | register: info 17 | 18 | - name: Assert volume_snapshot_info 19 | assert: 20 | that: 21 | - info.volume_snapshots|length == 0 22 | 23 | - name: Create volume 24 | openstack.cloud.volume: 25 | cloud: "{{ cloud }}" 26 | state: present 27 | size: 1 28 | name: ansible_volume 29 | description: Test volume 30 | register: volume 31 | 32 | - name: Create volume snapshot 33 | openstack.cloud.volume_snapshot: 34 | cloud: "{{ cloud }}" 35 | state: present 36 | name: ansible_volume_snapshot 37 | volume: ansible_volume 38 | register: snapshot 39 | 40 | - name: Assert volume_snapshot 41 | assert: 42 | that: 43 | - snapshot.volume_snapshot.name == "ansible_volume_snapshot" 44 | 45 | - name: Assert return values of volume_snapshot module 46 | assert: 47 | that: 48 | # allow new fields to be introduced but prevent fields from being removed 49 | - expected_fields|difference(snapshot.volume_snapshot.keys())|length == 0 50 | 51 | - name: Get snapshot info 52 | openstack.cloud.volume_snapshot_info: 53 | cloud: "{{ cloud }}" 54 | name: ansible_volume_snapshot 55 | register: info 56 | 57 | - name: Assert volume_snapshot_info 58 | assert: 59 | that: 60 | - info.volume_snapshots|length == 1 61 | - info.volume_snapshots[0].id == snapshot.volume_snapshot.id 62 | - info.volume_snapshots[0].volume_id == volume.volume.id 63 | 64 | - name: Assert return values of volume_info module 65 | assert: 66 | that: 67 | # allow new fields to be introduced but prevent fields from being removed 68 | - expected_fields|difference(info.volume_snapshots[0].keys())|length == 0 69 | 70 | - name: Create volume from snapshot 71 | openstack.cloud.volume: 72 | cloud: "{{ cloud }}" 73 | state: present 74 | size: 1 75 | snapshot: ansible_volume_snapshot 76 | name: ansible_volume2 77 | description: Test volume 78 | 79 | - name: Delete volume snapshot 80 | openstack.cloud.volume_snapshot: 81 | cloud: "{{ cloud }}" 82 | name: ansible_volume_snapshot 83 | volume: ansible_volume 84 | state: absent 85 | 86 | - name: Delete volume 87 | openstack.cloud.volume: 88 | cloud: "{{ cloud }}" 89 | state: absent 90 | name: ansible_volume2 91 | 92 | - name: Delete volume 93 | openstack.cloud.volume: 94 | cloud: "{{ cloud }}" 95 | state: absent 96 | name: ansible_volume 97 | -------------------------------------------------------------------------------- /ci/roles/volume_backup/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Get existing backups 3 | openstack.cloud.volume_backup_info: 4 | cloud: "{{ cloud }}" 5 | register: info 6 | 7 | - name: Assert volume_backup_info 8 | assert: 9 | that: 10 | - info.volume_backups|length == 0 11 | 12 | - name: Get non-existing backup 13 | openstack.cloud.volume_backup_info: 14 | cloud: "{{ cloud }}" 15 | name: non-existing-backup 16 | register: info 17 | 18 | - name: Assert volume_backup_info 19 | assert: 20 | that: 21 | - info.volume_backups|length == 0 22 | 23 | - name: Create volume 24 | openstack.cloud.volume: 25 | cloud: "{{ cloud }}" 26 | state: present 27 | size: 1 28 | name: ansible_volume 29 | register: volume 30 | 31 | - name: Create volume backup 32 | openstack.cloud.volume_backup: 33 | cloud: "{{ cloud }}" 34 | state: present 35 | name: ansible_volume_backup 36 | volume: ansible_volume 37 | # TODO: Uncomment code when https://storyboard.openstack.org/#!/story/2010395 has been solved. 38 | #metadata: 39 | # key1: value1 40 | # key2: value2 41 | register: backup 42 | 43 | - name: Assert volume_backup 44 | assert: 45 | that: 46 | - backup.volume_backup.name == "ansible_volume_backup" 47 | - backup.volume_backup.volume_id == volume.volume.id 48 | # TODO: Uncomment code when https://storyboard.openstack.org/#!/story/2010395 has been solved. 49 | #- backup.volume_backup.metadata.keys()|sort == ['key1', 'key2'] 50 | #- backup.volume_backup.metadata['key1'] == 'value1' 51 | #- backup.volume_backup.metadata['key2'] == 'value2' 52 | 53 | - name: Assert return values of volume_backup module 54 | assert: 55 | that: 56 | # allow new fields to be introduced but prevent fields from being removed 57 | - expected_fields|difference(backup.volume_backup.keys())|length == 0 58 | 59 | - name: Get backup info 60 | openstack.cloud.volume_backup_info: 61 | cloud: "{{ cloud }}" 62 | name: ansible_volume_backup 63 | register: info 64 | 65 | - name: Assert volume_backup_info 66 | assert: 67 | that: 68 | - info.volume_backups|length == 1 69 | - info.volume_backups[0].id == backup.backup.id 70 | - info.volume_backups[0].volume_id == volume.volume.id 71 | 72 | - name: Assert return values of volume_info module 73 | assert: 74 | that: 75 | # allow new fields to be introduced but prevent fields from being removed 76 | - expected_fields|difference(info.volume_backups[0].keys())|length == 0 77 | 78 | - name: Delete volume backup 79 | openstack.cloud.volume_backup: 80 | cloud: "{{ cloud }}" 81 | name: ansible_volume_backup 82 | wait: false 83 | state: absent 84 | 85 | - name: Delete volume 86 | openstack.cloud.volume: 87 | cloud: "{{ cloud }}" 88 | state: absent 89 | name: ansible_volume 90 | -------------------------------------------------------------------------------- /plugins/modules/federation_mapping_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: Ansible Project 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | DOCUMENTATION = r''' 8 | --- 9 | module: federation_mapping_info 10 | short_description: Fetch Keystone federation mappings 11 | author: OpenStack Ansible SIG 12 | description: 13 | - Fetch Keystone federation mappings. 14 | options: 15 | name: 16 | description: 17 | - ID or name of the federation mapping. 18 | type: str 19 | aliases: ['id'] 20 | notes: 21 | - Name equals the ID of a federation mapping. 22 | extends_documentation_fragment: 23 | - openstack.cloud.openstack 24 | ''' 25 | 26 | EXAMPLES = r''' 27 | - name: Fetch all federation mappings 28 | openstack.cloud.federation_mapping_info: 29 | cloud: example_cloud 30 | 31 | - name: Fetch federation mapping by name 32 | openstack.cloud.federation_mapping_info: 33 | cloud: example_cloud 34 | name: example_mapping 35 | ''' 36 | 37 | RETURN = r''' 38 | mappings: 39 | description: List of federation mapping dictionaries. 40 | returned: always 41 | type: list 42 | elements: dict 43 | contains: 44 | id: 45 | description: The id of the mapping 46 | type: str 47 | sample: "ansible-test-mapping" 48 | name: 49 | description: Name of the mapping. Equal to C(id). 50 | type: str 51 | sample: "ansible-test-mapping" 52 | rules: 53 | description: List of rules for the mapping 54 | type: list 55 | ''' 56 | 57 | from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule 58 | 59 | 60 | class IdentityFederationMappingInfoModule(OpenStackModule): 61 | argument_spec = dict( 62 | name=dict(aliases=['id']), 63 | ) 64 | 65 | module_kwargs = dict( 66 | supports_check_mode=True 67 | ) 68 | 69 | def run(self): 70 | # name is id for federation mappings 71 | id = self.params['name'] 72 | 73 | if id: 74 | # handle id parameter separately because self.conn.identity.\ 75 | # mappings() does not allow to filter by id 76 | # Ref.: https://review.opendev.org/c/openstack/ 77 | # openstacksdk/+/858522 78 | mapping = self.conn.identity.find_mapping(name_or_id=id, 79 | ignore_missing=True) 80 | mappings = [mapping] if mapping else [] 81 | else: 82 | mappings = self.conn.identity.mappings() 83 | 84 | self.exit_json(changed=False, 85 | mappings=[mapping.to_dict(computed=False) 86 | for mapping in mappings]) 87 | 88 | 89 | def main(): 90 | module = IdentityFederationMappingInfoModule() 91 | module() 92 | 93 | 94 | if __name__ == '__main__': 95 | main() 96 | -------------------------------------------------------------------------------- /plugins/modules/config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2015 Hewlett-Packard Development Company, L.P. 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | DOCUMENTATION = r''' 8 | --- 9 | module: config 10 | short_description: Get OpenStack Client config 11 | author: OpenStack Ansible SIG 12 | description: 13 | - Get OpenStack cloud credentials and configuration, 14 | e.g. from clouds.yaml and environment variables. 15 | options: 16 | clouds: 17 | description: 18 | - List of clouds to limit the return list to. 19 | - When I(clouds) is not defined, then data 20 | is returned for all configured clouds. 21 | default: [] 22 | type: list 23 | elements: str 24 | requirements: 25 | - "python >= 3.6" 26 | - "openstacksdk >= 1.0.0" 27 | ''' 28 | 29 | RETURN = r''' 30 | clouds: 31 | description: List of OpenStack cloud configurations. 32 | returned: always 33 | type: list 34 | elements: dict 35 | contains: 36 | name: 37 | description: Name of the cloud. 38 | type: str 39 | config: 40 | description: A dict of configuration values for the CloudRegion and 41 | its services. The key for a ${config_option} for a 42 | specific ${service} should be ${service}_${config_option}. 43 | type: dict 44 | ''' 45 | 46 | EXAMPLES = r''' 47 | - name: Read configuration of all defined clouds 48 | openstack.cloud.config: 49 | register: config 50 | 51 | - name: Print clouds which do not support security groups 52 | loop: "{{ config.clouds }}" 53 | when: item.config.secgroup_source|default(None) != None 54 | debug: 55 | var: item 56 | 57 | - name: Read configuration of a two specific clouds 58 | openstack.cloud.config: 59 | clouds: 60 | - devstack 61 | - mordred 62 | ''' 63 | 64 | from ansible.module_utils.basic import AnsibleModule 65 | 66 | try: 67 | import openstack.config 68 | from openstack import exceptions 69 | HAS_OPENSTACKSDK = True 70 | except ImportError: 71 | HAS_OPENSTACKSDK = False 72 | 73 | 74 | def main(): 75 | module = AnsibleModule( 76 | argument_spec=dict( 77 | clouds=dict(type='list', default=[], elements='str'), 78 | ) 79 | ) 80 | 81 | if not HAS_OPENSTACKSDK: 82 | module.fail_json(msg='openstacksdk is required for this module') 83 | 84 | try: 85 | clouds = [dict(name=cloud.name, config=cloud.config) 86 | for cloud in openstack.config.OpenStackConfig().get_all() 87 | if not module.params['clouds'] 88 | or cloud.name in module.params['clouds']] 89 | 90 | module.exit_json(changed=False, clouds=clouds) 91 | 92 | except exceptions.SDKException as e: 93 | module.fail_json(msg=str(e)) 94 | 95 | 96 | if __name__ == "__main__": 97 | main() 98 | -------------------------------------------------------------------------------- /ci/roles/volume/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create volume 3 | openstack.cloud.volume: 4 | cloud: "{{ cloud }}" 5 | state: present 6 | size: 1 7 | name: ansible_volume 8 | description: Test volume 9 | register: vol 10 | 11 | - assert: 12 | that: item in vol.volume 13 | loop: "{{ expected_fields }}" 14 | 15 | - name: Create volume from existing volume 16 | openstack.cloud.volume: 17 | cloud: "{{ cloud }}" 18 | state: present 19 | size: 1 20 | volume: "{{ vol.volume.id }}" 21 | name: ansible_volume1 22 | description: Test volume 23 | 24 | - name: Delete volume 25 | openstack.cloud.volume: 26 | cloud: "{{ cloud }}" 27 | state: absent 28 | name: ansible_volume1 29 | 30 | - name: Delete volume 31 | openstack.cloud.volume: 32 | cloud: "{{ cloud }}" 33 | state: absent 34 | name: ansible_volume 35 | 36 | - name: Test images 37 | block: 38 | - name: Ensure clean environment 39 | ansible.builtin.set_fact: 40 | tmp_file: !!null 41 | 42 | - name: Create a test image file 43 | ansible.builtin.tempfile: 44 | register: tmp_file 45 | 46 | - name: Fill test image file to 1MB 47 | community.general.filesize: 48 | path: '{{ tmp_file.path }}' 49 | size: 1M 50 | 51 | - name: Create test image 52 | openstack.cloud.image: 53 | cloud: "{{ cloud }}" 54 | state: present 55 | name: "{{ test_volume_image }}" 56 | filename: "{{ tmp_file.path }}" 57 | disk_format: raw 58 | 59 | - name: Create volume from image 60 | openstack.cloud.volume: 61 | cloud: "{{ cloud }}" 62 | state: present 63 | size: 1 64 | image: "{{ test_volume_image }}" 65 | name: ansible_volume2 66 | description: Test volume 67 | 68 | - name: Delete volume from image 69 | openstack.cloud.volume: 70 | cloud: "{{ cloud }}" 71 | name: ansible_volume2 72 | state: absent 73 | 74 | - name: Create test shared image 75 | openstack.cloud.image: 76 | cloud: "{{ cloud }}" 77 | state: present 78 | name: ansible_image 79 | filename: "{{ tmp_file.path }}" 80 | is_public: true 81 | disk_format: raw 82 | 83 | - name: Delete test shared image 84 | openstack.cloud.image: 85 | cloud: "{{ cloud }}" 86 | state: absent 87 | name: ansible_image 88 | filename: "{{ tmp_file.path }}" 89 | is_public: true 90 | disk_format: raw 91 | 92 | always: 93 | - name: Remove temporary image file 94 | ansible.builtin.file: 95 | path: "{{ tmp_file.path }}" 96 | state: absent 97 | when: tmp_file is defined and 'path' in tmp_file 98 | 99 | - include_tasks: volume_info.yml 100 | -------------------------------------------------------------------------------- /ci/roles/server_volume/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: List all images 3 | openstack.cloud.image_info: 4 | cloud: "{{ cloud }}" 5 | register: images 6 | 7 | - name: Identify CirrOS image name 8 | set_fact: 9 | image_name: "{{ images.images|community.general.json_query(query)|first }}" 10 | vars: 11 | query: "[?starts_with(name, 'cirros')].name" 12 | 13 | - name: Create server 14 | openstack.cloud.server: 15 | cloud: "{{ cloud }}" 16 | state: present 17 | name: "{{ server_name }}" 18 | image: "{{ image_name }}" 19 | flavor: "{{ flavor_name }}" 20 | network: "{{ server_network }}" 21 | auto_ip: false 22 | wait: true 23 | register: server 24 | 25 | - name: Create volume 26 | openstack.cloud.volume: 27 | cloud: "{{ cloud }}" 28 | state: present 29 | size: 1 30 | name: ansible_volume 31 | wait: true 32 | register: volume 33 | 34 | - name: Attach volume to server 35 | openstack.cloud.server_volume: 36 | cloud: "{{ cloud }}" 37 | server: "{{ server.server.id }}" 38 | volume: "{{ volume.volume.id }}" 39 | wait: true 40 | register: server_volume 41 | 42 | - name: Assert changed 43 | assert: 44 | that: server_volume is changed 45 | 46 | - name: Assert return values of server_volume module 47 | assert: 48 | that: 49 | # allow new fields to be introduced but prevent fields from being removed 50 | - expected_fields|difference(server_volume.volume.keys())|length == 0 51 | 52 | - name: Attach volume to server again 53 | openstack.cloud.server_volume: 54 | cloud: "{{ cloud }}" 55 | server: "{{ server.server.id }}" 56 | volume: "{{ volume.volume.id }}" 57 | wait: true 58 | register: server_volume 59 | 60 | - name: Assert not changed 61 | assert: 62 | that: server_volume is not changed 63 | 64 | - name: Detach volume to server 65 | openstack.cloud.server_volume: 66 | cloud: "{{ cloud }}" 67 | state: absent 68 | server: "{{ server.server.id }}" 69 | volume: "{{ volume.volume.id }}" 70 | wait: true 71 | register: server_volume 72 | 73 | - name: Assert changed 74 | assert: 75 | that: server_volume is changed 76 | 77 | - name: Detach volume to server again 78 | openstack.cloud.server_volume: 79 | cloud: "{{ cloud }}" 80 | state: absent 81 | server: "{{ server.server.id }}" 82 | volume: "{{ volume.volume.id }}" 83 | wait: true 84 | register: server_volume 85 | 86 | - name: Assert not changed 87 | assert: 88 | that: server_volume is not changed 89 | 90 | - name: Delete volume 91 | openstack.cloud.volume: 92 | cloud: "{{ cloud }}" 93 | state: absent 94 | name: ansible_volume 95 | wait: true 96 | 97 | - name: Delete server 98 | openstack.cloud.server: 99 | cloud: "{{ cloud }}" 100 | state: absent 101 | name: "{{ server_name }}" 102 | wait: true 103 | -------------------------------------------------------------------------------- /plugins/modules/catalog_service_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2022 by Red Hat, Inc. 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | 8 | DOCUMENTATION = r''' 9 | module: catalog_service_info 10 | short_description: Retrieve information about services from OpenStack 11 | author: OpenStack Ansible SIG 12 | description: 13 | - Retrieve information about services from OpenStack. 14 | options: 15 | name: 16 | description: 17 | - Name or ID of the service. 18 | type: str 19 | extends_documentation_fragment: 20 | - openstack.cloud.openstack 21 | ''' 22 | 23 | EXAMPLES = r''' 24 | - name: Fetch all services 25 | openstack.cloud.catalog_service_info: 26 | cloud: devstack 27 | 28 | - name: Fetch a single service 29 | openstack.cloud.catalog_service_info: 30 | cloud: devstack 31 | name: heat 32 | ''' 33 | 34 | RETURN = r''' 35 | services: 36 | description: List of dictionaries the services. 37 | returned: always 38 | type: list 39 | elements: dict 40 | contains: 41 | id: 42 | description: Service ID. 43 | type: str 44 | sample: "3292f020780b4d5baf27ff7e1d224c44" 45 | name: 46 | description: Service name. 47 | type: str 48 | sample: "glance" 49 | type: 50 | description: Service type. 51 | type: str 52 | sample: "image" 53 | description: 54 | description: Service description. 55 | type: str 56 | sample: "OpenStack Image Service" 57 | is_enabled: 58 | description: Service status. 59 | type: bool 60 | sample: True 61 | links: 62 | description: Link of the service 63 | type: str 64 | sample: http://10.0.0.1/identity/v3/services/0ae87 65 | ''' 66 | 67 | from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( 68 | OpenStackModule 69 | ) 70 | 71 | 72 | class CatalogServiceInfoModule(OpenStackModule): 73 | argument_spec = dict( 74 | name=dict(), 75 | ) 76 | 77 | module_kwargs = dict( 78 | supports_check_mode=True, 79 | ) 80 | 81 | def run(self): 82 | name_or_id = self.params['name'] 83 | 84 | if name_or_id: 85 | service = self.conn.identity.find_service(name_or_id) 86 | services = [service] if service else [] 87 | else: 88 | services = self.conn.identity.services() 89 | 90 | self.exit_json(changed=False, 91 | services=[s.to_dict(computed=False) for s in services]) 92 | 93 | 94 | def main(): 95 | module = CatalogServiceInfoModule() 96 | module() 97 | 98 | 99 | if __name__ == "__main__": 100 | main() 101 | -------------------------------------------------------------------------------- /tests/unit/mock/procenv.py: -------------------------------------------------------------------------------- 1 | # (c) 2016, Matt Davis 2 | # (c) 2016, Toshio Kuratomi 3 | # 4 | # This file is part of Ansible 5 | # 6 | # Ansible is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Ansible is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with Ansible. If not, see . 18 | 19 | # Make coding more python3-ish 20 | 21 | import sys 22 | import json 23 | import unittest 24 | 25 | from contextlib import contextmanager 26 | from io import BytesIO, StringIO 27 | from ansible.module_utils.six import PY3 28 | from ansible.module_utils._text import to_bytes 29 | 30 | 31 | @contextmanager 32 | def swap_stdin_and_argv(stdin_data='', argv_data=tuple()): 33 | """ 34 | context manager that temporarily masks the test runner's values for stdin and argv 35 | """ 36 | real_stdin = sys.stdin 37 | real_argv = sys.argv 38 | 39 | if PY3: 40 | fake_stream = StringIO(stdin_data) 41 | fake_stream.buffer = BytesIO(to_bytes(stdin_data)) 42 | else: 43 | fake_stream = BytesIO(to_bytes(stdin_data)) 44 | 45 | try: 46 | sys.stdin = fake_stream 47 | sys.argv = argv_data 48 | 49 | yield 50 | finally: 51 | sys.stdin = real_stdin 52 | sys.argv = real_argv 53 | 54 | 55 | @contextmanager 56 | def swap_stdout(): 57 | """ 58 | context manager that temporarily replaces stdout for tests that need to verify output 59 | """ 60 | old_stdout = sys.stdout 61 | 62 | if PY3: 63 | fake_stream = StringIO() 64 | else: 65 | fake_stream = BytesIO() 66 | 67 | try: 68 | sys.stdout = fake_stream 69 | 70 | yield fake_stream 71 | finally: 72 | sys.stdout = old_stdout 73 | 74 | 75 | class ModuleTestCase(unittest.TestCase): 76 | def setUp(self, module_args=None): 77 | if module_args is None: 78 | module_args = {'_ansible_remote_tmp': '/tmp', '_ansible_keep_remote_files': False} 79 | 80 | args = json.dumps(dict(ANSIBLE_MODULE_ARGS=module_args)) 81 | 82 | # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually 83 | self.stdin_swap = swap_stdin_and_argv(stdin_data=args) 84 | self.stdin_swap.__enter__() 85 | 86 | def tearDown(self): 87 | # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually 88 | self.stdin_swap.__exit__(None, None, None) 89 | -------------------------------------------------------------------------------- /plugins/modules/group_assignment.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2015 Hewlett-Packard Development Company, L.P. 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | DOCUMENTATION = r''' 8 | --- 9 | module: group_assignment 10 | short_description: Assign OpenStack identity users to groups 11 | author: OpenStack Ansible SIG 12 | description: 13 | - Add and remove OpenStack identity (Keystone) users to/from groups. 14 | options: 15 | group: 16 | description: 17 | - Name or ID for the group. 18 | required: true 19 | type: str 20 | state: 21 | description: 22 | - Should the user be present or absent in the group. 23 | choices: [present, absent] 24 | default: present 25 | type: str 26 | user: 27 | description: 28 | - Name or ID for the user. 29 | required: true 30 | type: str 31 | extends_documentation_fragment: 32 | - openstack.cloud.openstack 33 | ''' 34 | 35 | EXAMPLES = r''' 36 | - name: Add demo_user user to demo_group group 37 | openstack.cloud.group_assignment: 38 | cloud: mycloud 39 | user: demo_user 40 | group: demo_group 41 | ''' 42 | 43 | from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule 44 | 45 | 46 | class IdentityGroupAssignment(OpenStackModule): 47 | argument_spec = dict( 48 | group=dict(required=True), 49 | state=dict(default='present', choices=['absent', 'present']), 50 | user=dict(required=True), 51 | ) 52 | 53 | module_kwargs = dict( 54 | supports_check_mode=True 55 | ) 56 | 57 | def run(self): 58 | user_name_or_id = self.params['user'] 59 | user = self.conn.identity.find_user(user_name_or_id, 60 | ignore_missing=False) 61 | 62 | group_name_or_id = self.params['group'] 63 | group = self.conn.identity.find_group(group_name_or_id, 64 | ignore_missing=False) 65 | 66 | is_user_in_group = \ 67 | self.conn.identity.check_user_in_group(user, group) 68 | 69 | state = self.params['state'] 70 | if self.ansible.check_mode: 71 | self.exit_json( 72 | changed=( 73 | (state == 'present' and not is_user_in_group) 74 | or (state == 'absent' and is_user_in_group))) 75 | 76 | if state == 'present' and not is_user_in_group: 77 | self.conn.identity.add_user_to_group(user, group) 78 | self.exit_json(changed=True) 79 | elif state == 'absent' and is_user_in_group: 80 | self.conn.identity.remove_user_from_group(user, group) 81 | self.exit_json(changed=True) 82 | else: 83 | self.exit_json(changed=False) 84 | 85 | 86 | def main(): 87 | module = IdentityGroupAssignment() 88 | module() 89 | 90 | 91 | if __name__ == '__main__': 92 | main() 93 | -------------------------------------------------------------------------------- /plugins/modules/identity_role_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2020, Sagi Shnaidman 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | DOCUMENTATION = r''' 8 | --- 9 | module: identity_role_info 10 | short_description: Fetch OpenStack identity (Keystone) roles 11 | author: OpenStack Ansible SIG 12 | description: 13 | - Fetch OpenStack identity (Keystone) roles. 14 | options: 15 | domain_id: 16 | description: 17 | - Domain ID which owns the role. 18 | type: str 19 | required: false 20 | name: 21 | description: 22 | - Name or ID of the role. 23 | type: str 24 | required: false 25 | extends_documentation_fragment: 26 | - openstack.cloud.openstack 27 | ''' 28 | 29 | RETURN = r''' 30 | roles: 31 | description: List of dictionaries describing matching identity roles. 32 | returned: always 33 | type: list 34 | elements: dict 35 | contains: 36 | description: 37 | description: User-facing description of the role. 38 | type: str 39 | domain_id: 40 | description: References the domain ID which owns the role. 41 | type: str 42 | id: 43 | description: Unique ID for the role 44 | type: str 45 | links: 46 | description: The links for the service resources 47 | type: dict 48 | name: 49 | description: Unique role name, within the owning domain. 50 | type: str 51 | ''' 52 | 53 | EXAMPLES = r''' 54 | - name: Retrieve info about all roles 55 | openstack.cloud.identity_role_info: 56 | cloud: mycloud 57 | 58 | - name: Retrieve info about all roles in specific domain 59 | openstack.cloud.identity_role_info: 60 | cloud: mycloud 61 | domain_id: some_domain_id 62 | 63 | - name: Retrieve info about role 'admin' 64 | openstack.cloud.identity_role_info: 65 | cloud: mycloud 66 | name: admin 67 | ''' 68 | 69 | from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule 70 | 71 | 72 | class IdentityRoleInfoModule(OpenStackModule): 73 | argument_spec = dict( 74 | domain_id=dict(), 75 | name=dict(), 76 | ) 77 | 78 | module_kwargs = dict( 79 | supports_check_mode=True, 80 | ) 81 | 82 | def run(self): 83 | kwargs = dict((k, self.params[k]) 84 | for k in ['domain_id'] 85 | if self.params[k] is not None) 86 | 87 | name_or_id = self.params['name'] 88 | if name_or_id is not None: 89 | kwargs['name_or_id'] = name_or_id 90 | 91 | self.exit_json(changed=False, 92 | roles=[r.to_dict(computed=False) 93 | for r in self.conn.search_roles(**kwargs)]) 94 | 95 | 96 | def main(): 97 | module = IdentityRoleInfoModule() 98 | module() 99 | 100 | 101 | if __name__ == '__main__': 102 | main() 103 | -------------------------------------------------------------------------------- /ci/roles/volume_type/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create volume type 3 | openstack.cloud.volume_type: 4 | name: "{{ volume_type_name }}" 5 | cloud: "{{ cloud }}" 6 | state: present 7 | extra_specs: 8 | volume_backend_name: "{{ volume_backend_name }}" 9 | description: "{{ volume_type_description }}" 10 | is_public: true 11 | register: the_result 12 | - name: Check created volume type 13 | vars: 14 | the_volume: "{{ the_result.volume_type }}" 15 | ansible.builtin.assert: 16 | that: 17 | - "'id' in the_result.volume_type" 18 | - the_volume.description == volume_type_description 19 | - the_volume.is_public == True 20 | - the_volume.name == volume_type_name 21 | - the_volume.extra_specs['volume_backend_name'] == volume_backend_name 22 | success_msg: >- 23 | Created volume: {{ the_result.volume_type.id }}, 24 | Name: {{ the_result.volume_type.name }}, 25 | Description: {{ the_result.volume_type.description }} 26 | 27 | - name: Test, check idempotency 28 | openstack.cloud.volume_type: 29 | name: "{{ volume_type_name }}" 30 | cloud: "{{ cloud }}" 31 | state: present 32 | extra_specs: 33 | volume_backend_name: "{{ volume_backend_name }}" 34 | description: "{{ volume_type_description }}" 35 | is_public: true 36 | register: the_result 37 | - name: Check result.changed is false 38 | ansible.builtin.assert: 39 | that: 40 | - the_result.changed == false 41 | success_msg: "Request with the same details lead to no changes" 42 | 43 | - name: Add extra spec 44 | openstack.cloud.volume_type: 45 | cloud: "{{ cloud }}" 46 | name: "{{ volume_type_name }}" 47 | state: present 48 | extra_specs: 49 | volume_backend_name: "{{ volume_backend_name }}" 50 | some_spec: fake_spec 51 | description: "{{ volume_type_description }}" 52 | is_public: true 53 | register: the_result 54 | - name: Check volume type extra spec 55 | ansible.builtin.assert: 56 | that: 57 | - "'some_spec' in the_result.volume_type.extra_specs" 58 | - the_result.volume_type.extra_specs["some_spec"] == "fake_spec" 59 | success_msg: >- 60 | New extra specs: {{ the_result.volume_type.extra_specs }} 61 | 62 | # is_public update attempt using openstacksdk result in unexpected attribute 63 | # error... TODO: Find solution 64 | # 65 | # - name: Make volume type private 66 | # openstack.cloud.volume_type: 67 | # cloud: "{{ cloud }}" 68 | # name: "{{ volume_type_alt_name }}" 69 | # state: present 70 | # extra_specs: 71 | # volume_backend_name: "{{ volume_backend_name }}" 72 | # # some_other_spec: test 73 | # description: Changed 3rd time test volume type 74 | # is_public: true 75 | # register: the_result 76 | 77 | - name: Volume encryption tests 78 | ansible.builtin.include_tasks: volume_encryption.yml 79 | 80 | - name: Delete volume type 81 | openstack.cloud.volume_type: 82 | cloud: "{{ cloud }}" 83 | name: "{{ volume_type_name }}" 84 | state: absent 85 | register: the_result 86 | -------------------------------------------------------------------------------- /ci/roles/catalog_service/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Delete service test 3 | openstack.cloud.catalog_service: 4 | cloud: "{{ cloud }}" 5 | service_type: test 6 | name: test 7 | state: absent 8 | register: service_delete 9 | 10 | - name: Assert changed is set to false 11 | assert: 12 | that: 13 | - not service_delete.changed 14 | 15 | - name: Create a service for test 16 | openstack.cloud.catalog_service: 17 | cloud: "{{ cloud }}" 18 | name: "test_service" 19 | state: present 20 | service_type: test_type 21 | description: "Test service" 22 | register: service_test 23 | 24 | - name: Verify returned values 25 | assert: 26 | that: item in service_test.service 27 | loop: "{{ expected_fields }}" 28 | 29 | - name: Check if the service test was created successfully 30 | openstack.cloud.catalog_service: 31 | cloud: "{{ cloud }}" 32 | service_type: test 33 | name: test 34 | register: service_created 35 | 36 | - name: Verify returned values 37 | assert: 38 | that: item in service_created.service 39 | loop: "{{ expected_fields }}" 40 | 41 | - name: Update service test 42 | openstack.cloud.catalog_service: 43 | cloud: "{{ cloud }}" 44 | service_type: test 45 | description: "A new description" 46 | is_enabled: False 47 | name: test 48 | register: service_test 49 | 50 | - name: Check if description and is_enabled were updated 51 | assert: 52 | that: 53 | - service_test.service.description == "A new description" 54 | - not (service_test.service.is_enabled|bool) 55 | 56 | - name: Get all services 57 | openstack.cloud.catalog_service_info: 58 | cloud: "{{ cloud }}" 59 | register: services 60 | 61 | - name: Assert return values of catalog_service_info module 62 | assert: 63 | that: 64 | # allow new fields to be introduced but prevent fields from being removed 65 | - expected_fields|difference(services.services[0].keys())|length == 0 66 | 67 | - name: Get service by name 68 | openstack.cloud.catalog_service_info: 69 | cloud: "{{ cloud }}" 70 | name: test 71 | register: services 72 | 73 | - name: Assert services returned by catalog_service_info module 74 | assert: 75 | that: 76 | - services.services|length == 1 77 | - services.services[0].id == service_test.service.id 78 | 79 | - name: Delete service test 80 | openstack.cloud.catalog_service: 81 | cloud: "{{ cloud }}" 82 | service_type: test 83 | name: test 84 | state: absent 85 | register: service_deleted 86 | 87 | - name: Verify if service was deleted 88 | assert: 89 | that: 90 | - service_deleted.changed 91 | 92 | - name: Delete service test again 93 | openstack.cloud.catalog_service: 94 | cloud: "{{ cloud }}" 95 | service_type: test 96 | name: test 97 | state: absent 98 | register: service_deleted 99 | 100 | - name: Assert changed is set to false 101 | assert: 102 | that: 103 | - not service_deleted.changed 104 | -------------------------------------------------------------------------------- /plugins/modules/volume_service_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2023 Bitswalk, inc. 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | DOCUMENTATION = r''' 8 | --- 9 | module: volume_service_info 10 | short_description: Fetch OpenStack Volume (Cinder) services 11 | author: OpenStack Ansible SIG 12 | description: 13 | - Fetch OpenStack Volume (Cinder) services. 14 | options: 15 | binary: 16 | description: 17 | - Filter the service list result by binary name of the service. 18 | type: str 19 | host: 20 | description: 21 | - Filter the service list result by the host name. 22 | type: str 23 | extends_documentation_fragment: 24 | - openstack.cloud.openstack 25 | ''' 26 | 27 | EXAMPLES = r''' 28 | - name: Fetch all OpenStack Volume (Cinder) services 29 | openstack.cloud.volume_service_info: 30 | cloud: awesomecloud 31 | 32 | - name: Fetch a subset of OpenStack Volume (Cinder) services 33 | openstack.cloud.volume_service_info: 34 | cloud: awesomecloud 35 | binary: "cinder-volume" 36 | host: "localhost" 37 | ''' 38 | 39 | RETURN = r''' 40 | volume_services: 41 | description: List of dictionaries describing Volume (Cinder) services. 42 | returned: always 43 | type: list 44 | elements: dict 45 | contains: 46 | availability_zone: 47 | description: The availability zone name. 48 | type: str 49 | binary: 50 | description: The binary name of the service. 51 | type: str 52 | disabled_reason: 53 | description: The reason why the service is disabled 54 | type: str 55 | host: 56 | description: The name of the host. 57 | type: str 58 | name: 59 | description: Service name 60 | type: str 61 | state: 62 | description: The state of the service. One of up or down. 63 | type: str 64 | status: 65 | description: The status of the service. One of enabled or disabled. 66 | type: str 67 | update_at: 68 | description: The date and time when the resource was updated 69 | type: str 70 | ''' 71 | 72 | from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule 73 | 74 | 75 | class VolumeServiceInfoModule(OpenStackModule): 76 | 77 | argument_spec = dict( 78 | binary=dict(), 79 | host=dict(), 80 | ) 81 | 82 | module_kwargs = dict( 83 | supports_check_mode=True 84 | ) 85 | 86 | def run(self): 87 | kwargs = {k: self.params[k] 88 | for k in ['binary', 'host'] 89 | if self.params[k] is not None} 90 | volume_services = self.conn.block_storage.services(**kwargs) 91 | 92 | self.exit_json(changed=False, 93 | volume_services=[s.to_dict(computed=False) 94 | for s in volume_services]) 95 | 96 | 97 | def main(): 98 | module = VolumeServiceInfoModule() 99 | module() 100 | 101 | 102 | if __name__ == '__main__': 103 | main() 104 | -------------------------------------------------------------------------------- /ci/roles/volume_type_access/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # TODO: Replace with appropriate Ansible module once available 3 | - name: Get volume types 4 | command: openstack --os-cloud=devstack-admin volume type list -f json 5 | register: volume_types 6 | 7 | # TODO: Replace with appropriate Ansible module once available 8 | - name: Create volume type 9 | command: openstack --os-cloud=devstack-admin volume type create ansible_volume_type --private 10 | when: "'ansible_volume_type' not in (volume_types.stdout | from_json) | map(attribute='Name') | list" 11 | 12 | # TODO: Replace with appropriate Ansible module once available 13 | - name: Get volume types 14 | command: openstack --os-cloud=devstack-admin volume type show ansible_volume_type -f json 15 | register: volume_type 16 | 17 | - name: Fetch demo project 18 | openstack.cloud.project_info: 19 | cloud: devstack-admin 20 | name: demo 21 | register: projects 22 | 23 | - name: Verify demo project 24 | assert: 25 | that: 26 | - projects.projects|length == 1 27 | - projects.projects.0.name == "demo" 28 | 29 | - name: Grant access to volume type 30 | openstack.cloud.volume_type_access: 31 | cloud: devstack-admin 32 | name: ansible_volume_type 33 | project: demo 34 | state: present 35 | register: access 36 | 37 | - name: Verify access 38 | assert: 39 | that: 40 | - access is changed 41 | - access.volume_type.id == (volume_type.stdout | from_json).id 42 | 43 | # TODO: Replace with appropriate Ansible module once available 44 | - name: Get volume types 45 | command: openstack --os-cloud=devstack-admin volume type show ansible_volume_type -f json 46 | register: volume_type 47 | 48 | - name: Verify volume type access 49 | assert: 50 | that: 51 | - (volume_type.stdout | from_json).name == 'ansible_volume_type' 52 | - projects.projects.0.id in (volume_type.stdout | from_json).access_project_ids 53 | 54 | - name: Grant access to volume type again 55 | openstack.cloud.volume_type_access: 56 | cloud: devstack-admin 57 | name: ansible_volume_type 58 | project: demo 59 | state: present 60 | register: access 61 | 62 | - name: Verify access did not change 63 | assert: 64 | that: 65 | - access is not changed 66 | 67 | - name: Revoke access to volume type 68 | openstack.cloud.volume_type_access: 69 | cloud: devstack-admin 70 | name: ansible_volume_type 71 | project: demo 72 | state: absent 73 | register: access 74 | 75 | - name: Verify revoked access 76 | assert: 77 | that: 78 | - access is changed 79 | - access.volume_type.id == (volume_type.stdout | from_json).id 80 | 81 | - name: Revoke access to volume type again 82 | openstack.cloud.volume_type_access: 83 | cloud: devstack-admin 84 | name: ansible_volume_type 85 | project: demo 86 | state: absent 87 | register: access 88 | 89 | - name: Verify access did not change 90 | assert: 91 | that: 92 | - access is not changed 93 | 94 | # TODO: Replace with appropriate Ansible module once available 95 | - name: Delete volume type 96 | command: openstack --os-cloud=devstack-admin volume type delete ansible_volume_type 97 | -------------------------------------------------------------------------------- /plugins/modules/identity_domain_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2016 Hewlett-Packard Enterprise Corporation 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | DOCUMENTATION = r''' 8 | --- 9 | module: identity_domain_info 10 | short_description: Fetch identity (Keystone) domains from OpenStack cloud 11 | author: OpenStack Ansible SIG 12 | description: 13 | - Fetch identity (Keystone) domains from OpenStack cloud 14 | options: 15 | filters: 16 | description: 17 | - A dictionary of meta data to use for filtering. 18 | - Elements of this dictionary may be additional dictionaries. 19 | type: dict 20 | name: 21 | description: 22 | - Name or ID of the domain 23 | type: str 24 | extends_documentation_fragment: 25 | - openstack.cloud.openstack 26 | ''' 27 | 28 | EXAMPLES = r''' 29 | - name: Gather information about previously created domain 30 | openstack.cloud.identity_domain_info: 31 | cloud: awesomecloud 32 | 33 | - name: Gather information about a previously created domain by name 34 | openstack.cloud.identity_domain_info: 35 | cloud: awesomecloud 36 | name: demodomain 37 | 38 | - name: Gather information about a previously created domain with filter 39 | openstack.cloud.identity_domain_info: 40 | cloud: awesomecloud 41 | name: demodomain 42 | filters: 43 | is_enabled: false 44 | ''' 45 | 46 | RETURN = r''' 47 | domains: 48 | description: List of dictionaries describing OpenStack domains 49 | returned: always 50 | type: list 51 | elements: dict 52 | contains: 53 | description: 54 | description: Description of the domain. 55 | type: str 56 | id: 57 | description: Unique UUID. 58 | type: str 59 | is_enabled: 60 | description: Flag to indicate if the domain is enabled. 61 | type: bool 62 | links: 63 | description: The links related to the domain resource 64 | type: list 65 | name: 66 | description: Name given to the domain. 67 | type: str 68 | ''' 69 | 70 | from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule 71 | 72 | 73 | class IdentityDomainInfoModule(OpenStackModule): 74 | argument_spec = dict( 75 | filters=dict(type='dict'), 76 | name=dict(), 77 | ) 78 | 79 | module_kwargs = dict( 80 | supports_check_mode=True 81 | ) 82 | 83 | def run(self): 84 | kwargs = {} 85 | name = self.params['name'] 86 | if name is not None: 87 | kwargs['name_or_id'] = name 88 | 89 | filters = self.params['filters'] 90 | if filters is not None: 91 | kwargs['filters'] = filters 92 | 93 | self.exit_json(changed=False, 94 | domains=[d.to_dict(computed=False) 95 | for d in self.conn.search_domains(**kwargs)]) 96 | 97 | 98 | def main(): 99 | module = IdentityDomainInfoModule() 100 | module() 101 | 102 | 103 | if __name__ == '__main__': 104 | main() 105 | -------------------------------------------------------------------------------- /plugins/modules/trait.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2025, ScaleUp Technologies GmbH & Co. KG 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | DOCUMENTATION = ''' 8 | --- 9 | module: trait 10 | short_description: Add/Delete a trait from OpenStack 11 | author: OpenStack Ansible SIG 12 | description: 13 | - Add or Delete a trait from OpenStack 14 | options: 15 | id: 16 | description: 17 | - ID/Name of this trait 18 | required: true 19 | type: str 20 | state: 21 | description: 22 | - Should the resource be present or absent. 23 | choices: [present, absent] 24 | default: present 25 | type: str 26 | extends_documentation_fragment: 27 | - openstack.cloud.openstack 28 | ''' 29 | 30 | EXAMPLES = ''' 31 | # Creates a trait with the ID CUSTOM_WINDOWS_SPLA 32 | - openstack.cloud.trait: 33 | cloud: openstack 34 | state: present 35 | id: CUSTOM_WINDOWS_SPLA 36 | ''' 37 | 38 | RETURN = ''' 39 | trait: 40 | description: Dictionary describing the trait. 41 | returned: On success when I(state) is 'present' 42 | type: dict 43 | contains: 44 | id: 45 | description: ID of the trait. 46 | returned: success 47 | type: str 48 | ''' 49 | 50 | from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( 51 | OpenStackModule) 52 | 53 | 54 | class TraitModule(OpenStackModule): 55 | 56 | argument_spec = dict( 57 | id=dict(required=True), 58 | state=dict(default='present', 59 | choices=['absent', 'present']), 60 | ) 61 | 62 | module_kwargs = dict( 63 | supports_check_mode=True, 64 | ) 65 | 66 | def _system_state_change(self, trait): 67 | state = self.params['state'] 68 | if state == 'present' and not trait: 69 | return True 70 | if state == 'absent' and trait: 71 | return True 72 | return False 73 | 74 | def run(self): 75 | 76 | state = self.params['state'] 77 | id = self.params['id'] 78 | 79 | try: 80 | trait = self.conn.placement.get_trait(id) 81 | except self.sdk.exceptions.NotFoundException: 82 | trait = None 83 | 84 | if self.ansible.check_mode: 85 | self.exit_json(changed=self._system_state_change(trait), trait=trait) 86 | 87 | changed = False 88 | if state == 'present': 89 | if not trait: 90 | trait = self.conn.placement.create_trait(id) 91 | changed = True 92 | 93 | self.exit_json( 94 | changed=changed, trait=trait.to_dict(computed=False)) 95 | 96 | elif state == 'absent': 97 | if trait: 98 | self.conn.placement.delete_trait(id, ignore_missing=False) 99 | self.exit_json(changed=True) 100 | 101 | self.exit_json(changed=False) 102 | 103 | 104 | def main(): 105 | module = TraitModule() 106 | module() 107 | 108 | 109 | if __name__ == '__main__': 110 | main() 111 | -------------------------------------------------------------------------------- /ci/roles/server_metadata/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: List all images 3 | openstack.cloud.image_info: 4 | cloud: "{{ cloud }}" 5 | register: images 6 | 7 | - name: Identify CirrOS image name 8 | set_fact: 9 | image_name: "{{ images.images|community.general.json_query(query)|first }}" 10 | vars: 11 | query: "[?starts_with(name, 'cirros')].name" 12 | 13 | - name: Create test server 14 | openstack.cloud.server: 15 | cloud: "{{ cloud }}" 16 | state: present 17 | name: ansible_server 18 | image: "{{ image_name }}" 19 | flavor: m1.tiny 20 | network: private 21 | auto_ip: false 22 | wait: true 23 | register: server 24 | 25 | - name: Set server metadata by id 26 | openstack.cloud.server_metadata: 27 | cloud: "{{ cloud }}" 28 | name: "{{ server.server.id }}" 29 | meta: 30 | test_key: test_value 31 | second_key: second_value 32 | register: server_metadata 33 | 34 | - name: Assert updated metadata 35 | assert: 36 | that: 37 | - server_metadata is changed 38 | - "server_metadata.server.metadata == {'test_key': 'test_value', 'second_key': 39 | 'second_value'}" 40 | 41 | - name: Set server metadata by name 42 | openstack.cloud.server_metadata: 43 | cloud: "{{ cloud }}" 44 | name: "{{ server.server.name }}" 45 | meta: 46 | test_key: test_value_2 47 | register: server_metadata 48 | 49 | - name: Assert updated metadata 50 | assert: 51 | that: 52 | - server_metadata is changed 53 | - "server_metadata.server.metadata == {'test_key': 'test_value_2', 'second_key': 54 | 'second_value'}" 55 | 56 | - name: Update metadata again 57 | openstack.cloud.server_metadata: 58 | cloud: "{{ cloud }}" 59 | name: "{{ server.server.id }}" 60 | meta: 61 | test_key: test_value_2 62 | register: server_metadata 63 | 64 | - name: Assert not changed 65 | assert: 66 | that: 67 | - server_metadata is not changed 68 | - "server_metadata.server.metadata == {'test_key': 'test_value_2', 'second_key': 69 | 'second_value'}" 70 | 71 | - name: Delete server metadata 72 | openstack.cloud.server_metadata: 73 | cloud: "{{ cloud }}" 74 | name: "{{ server.server.id }}" 75 | state: absent 76 | meta: 77 | test_key: 78 | register: server_metadata 79 | 80 | - name: Assert updated metadata 81 | assert: 82 | that: 83 | - server_metadata is changed 84 | - "server_metadata.server.metadata == {'second_key': 'second_value'}" 85 | 86 | - name: Delete server metadata again 87 | openstack.cloud.server_metadata: 88 | cloud: "{{ cloud }}" 89 | name: "{{ server.server.id }}" 90 | state: absent 91 | meta: 92 | test_key: 93 | register: server_metadata 94 | 95 | - name: Assert not changed 96 | assert: 97 | that: 98 | - server_metadata is not changed 99 | - "server_metadata.server.metadata == {'second_key': 'second_value'}" 100 | 101 | - name: Delete test server 102 | openstack.cloud.server: 103 | cloud: "{{ cloud }}" 104 | name: "{{ server.server.id }}" 105 | state: absent 106 | wait: true 107 | register: server 108 | -------------------------------------------------------------------------------- /ci/roles/router/tasks/shared_ext_network.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Test the case where we have a shared external network in one project used as 3 | # the gateway on a router in a second project. 4 | # See https://bugs.launchpad.net/ansible-collections-openstack/+bug/2049658 5 | 6 | - name: Create the first project 7 | openstack.cloud.project: 8 | cloud: "{{ cloud }}" 9 | state: present 10 | name: "shared_ext_net_test_1" 11 | description: "Project that contains the external network to be shared" 12 | domain: default 13 | is_enabled: True 14 | register: project_1 15 | 16 | - name: Create the external network to be shared 17 | openstack.cloud.network: 18 | cloud: "{{ cloud }}" 19 | state: present 20 | name: "{{ external_network_name }}" 21 | project: "shared_ext_net_test_1" 22 | external: true 23 | shared: true 24 | register: shared_ext_network 25 | 26 | - name: Create subnet on external network 27 | openstack.cloud.subnet: 28 | cloud: "{{ cloud }}" 29 | state: present 30 | network_name: "{{ shared_ext_network.id }}" 31 | name: "shared_ext_subnet" 32 | project: "shared_ext_net_test_1" 33 | cidr: "10.6.6.0/24" 34 | register: shared_subnet 35 | 36 | - name: Create the second project 37 | openstack.cloud.project: 38 | cloud: "{{ cloud }}" 39 | state: present 40 | name: "shared_ext_net_test_2" 41 | description: "Project that contains the subnet to be shared" 42 | domain: default 43 | is_enabled: True 44 | register: project_2 45 | 46 | - name: Create router with gateway on shared external network 47 | openstack.cloud.router: 48 | cloud: "{{ cloud }}" 49 | state: present 50 | name: "shared_ext_net_test2_router" 51 | project: "shared_ext_net_test_2" 52 | network: "{{ external_network_name }}" 53 | register: router 54 | 55 | - name: Gather routers info 56 | openstack.cloud.routers_info: 57 | cloud: "{{ cloud }}" 58 | name: "shared_ext_net_test2_router" 59 | register: routers 60 | 61 | - name: Verify routers info 62 | assert: 63 | that: 64 | - routers.routers.0.id == router.router.id 65 | - routers.routers.0.external_gateway_info.external_fixed_ips|length == 1 66 | 67 | - name: Delete router 68 | openstack.cloud.router: 69 | cloud: "{{ cloud }}" 70 | state: absent 71 | name: "shared_ext_net_test2_router" 72 | project: "shared_ext_net_test_2" 73 | 74 | - name: Delete subnet 75 | openstack.cloud.subnet: 76 | cloud: "{{ cloud }}" 77 | state: absent 78 | network_name: "{{ shared_ext_network.id }}" 79 | name: "shared_ext_subnet" 80 | project: "shared_ext_net_test_1" 81 | 82 | - name: Delete network 83 | openstack.cloud.network: 84 | cloud: "{{ cloud }}" 85 | state: absent 86 | name: "{{ external_network_name }}" 87 | project: "shared_ext_net_test_1" 88 | 89 | - name: Delete project 2 90 | openstack.cloud.project: 91 | cloud: "{{ cloud }}" 92 | state: absent 93 | name: "shared_ext_net_test_2" 94 | 95 | - name: Delete project 1 96 | openstack.cloud.project: 97 | cloud: "{{ cloud }}" 98 | state: absent 99 | name: "shared_ext_net_test_1" 100 | -------------------------------------------------------------------------------- /ci/run-collection.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | connection: local 4 | gather_facts: true 5 | 6 | roles: 7 | - { role: address_scope, tags: address_scope } 8 | - { role: application_credential, tags: application_credential } 9 | - { role: auth, tags: auth } 10 | - { role: catalog_service, tags: catalog_service } 11 | - { role: coe_cluster, tags: coe_cluster } 12 | - { role: coe_cluster_template, tags: coe_cluster_template } 13 | - { role: compute_flavor, tags: compute_flavor } 14 | - { role: compute_flavor_access, tags: compute_flavor_access } 15 | - { role: compute_service, tags: compute_service } 16 | - { role: config, tags: config } 17 | - { role: dns_zone, tags: dns_zone } 18 | - { role: endpoint, tags: endpoint } 19 | - { role: federation_mapping, tags: federation_mapping } 20 | - { role: floating_ip, tags: floating_ip } 21 | - { role: group_assignment, tags: group_assignment } 22 | - { role: host_aggregate, tags: host_aggregate } 23 | - { role: identity_domain, tags: identity_domain } 24 | - { role: identity_group, tags: identity_group } 25 | - { role: identity_role, tags: identity_role } 26 | - { role: identity_user, tags: identity_user } 27 | - { role: image, tags: image } 28 | - { role: inventory, tags: inventory } 29 | - { role: keypair, tags: keypair } 30 | - { role: keystone_federation_protocol, tags: keystone_federation_protocol } 31 | - { role: keystone_idp, tags: keystone_idp } 32 | - { role: loadbalancer, tags: loadbalancer } 33 | - { role: logging, tags: logging } 34 | - { role: network, tags: network } 35 | - { role: neutron_rbac_policy, tags: neutron_rbac_policy } 36 | - { role: object, tags: object } 37 | - { role: object_container, tags: object_container } 38 | - { role: object_containers_info, tags: object_containers_info } 39 | - { role: port, tags: port } 40 | - { role: trait, tags: trait } 41 | - { role: trunk, tags: trunk } 42 | - { role: project, tags: project } 43 | - { role: quota, tags: quota } 44 | - { role: recordset, tags: recordset } 45 | - { role: resource, tags: resource } 46 | - { role: resources, tags: resources } 47 | - { role: role_assignment, tags: role_assignment } 48 | - { role: router, tags: router } 49 | - { role: security_group, tags: security_group } 50 | - { role: security_group_rule, tags: security_group_rule } 51 | - { role: server, tags: server } 52 | - { role: server_action, tags: server_action } 53 | - { role: server_group, tags: server_group } 54 | - { role: server_metadata, tags: server_metadata } 55 | - { role: server_volume, tags: server_volume } 56 | - { role: share_type, tags: share_type } 57 | - { role: stack, tags: stack } 58 | - { role: subnet, tags: subnet } 59 | - { role: subnet_pool, tags: subnet_pool } 60 | - { role: volume, tags: volume } 61 | - { role: volume_type, tags: volume_type } 62 | - { role: volume_backup, tags: volume_backup } 63 | - { role: volume_manage, tags: volume_manage } 64 | - { role: volume_service, tags: volume_service } 65 | - { role: volume_snapshot, tags: volume_snapshot } 66 | - { role: volume_type_access, tags: volume_type_access } 67 | -------------------------------------------------------------------------------- /ci/publish/publish_collection.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars: 4 | collection_path: "{{ ansible_user_dir }}/{{ zuul.project.src_dir }}" 5 | build_collection_path: /tmp/collection_built/ 6 | ansible_virtualenv_path: /tmp/ansible_venv 7 | ansible_galaxy_path: "{{ ansible_virtualenv_path }}/bin/ansible-galaxy" 8 | 9 | tasks: 10 | 11 | - name: Include role for pip 12 | include_role: 13 | name: ensure-pip 14 | 15 | - name: Install Ansible in virtualenv 16 | pip: 17 | name: ansible-core<2.19 18 | virtualenv: "{{ ansible_virtualenv_path }}" 19 | virtualenv_command: "{{ ensure_pip_virtualenv_command }}" 20 | 21 | - name: Detect ansible version 22 | command: "{{ ansible_virtualenv_path }}/bin/ansible --version" 23 | register: ansible_version 24 | 25 | - name: Discover tag version 26 | set_fact: 27 | version_tag: "{{ zuul.tag|default('no_version', true) }}" 28 | 29 | - name: Fail if no tag version found 30 | fail: 31 | msg: "No tag was found in Zuul vars!" 32 | when: version_tag == 'no_version' 33 | 34 | - name: Create a directory for collection 35 | file: 36 | state: "{{ item }}" 37 | path: "{{ build_collection_path }}" 38 | loop: 39 | - absent 40 | - directory 41 | 42 | - name: Set galaxy.yml for right version from tag 43 | lineinfile: 44 | path: '{{ collection_path }}/galaxy.yml' 45 | regexp: '^version:.*' 46 | line: 'version: {{ version_tag }}' 47 | 48 | - name: Build collection 49 | command: "{{ ansible_galaxy_path }} collection build --output-path {{ build_collection_path }} --force" 50 | args: 51 | chdir: "{{ collection_path }}" 52 | 53 | - name: Publish content to Ansible Galaxy 54 | block: 55 | - name: Create ansible.cfg configuration file tempfile 56 | tempfile: 57 | state: file 58 | suffix: .cfg 59 | register: _ansiblecfg_tmp 60 | 61 | - name: Create ansible.cfg configuration file 62 | copy: 63 | dest: "{{ _ansiblecfg_tmp.path }}" 64 | mode: 0600 65 | content: | 66 | [galaxy] 67 | server_list = release_galaxy 68 | 69 | [galaxy_server.release_galaxy] 70 | url = {{ ansible_galaxy_info.url }} 71 | token = {{ ansible_galaxy_info.token }} 72 | 73 | - name: Get content of galaxy.yml 74 | slurp: 75 | src: "{{ collection_path }}/galaxy.yml" 76 | register: galaxy_vars 77 | 78 | - name: Parse yaml into variable 79 | set_fact: 80 | galaxy_yaml: "{{ galaxy_vars['content'] | b64decode | from_yaml }}" 81 | 82 | - name: Publish collection to Ansible Galaxy / Automation Hub 83 | environment: 84 | ANSIBLE_CONFIG: "{{ _ansiblecfg_tmp.path }}" 85 | shell: >- 86 | {{ ansible_galaxy_path }} collection publish -vvv 87 | {{ build_collection_path }}/{{ galaxy_yaml.namespace }}-{{ galaxy_yaml.name }}-{{ version_tag }}.tar.gz 88 | 89 | always: 90 | - name: Shred ansible-galaxy credentials 91 | command: "shred {{ _ansiblecfg_tmp.path }}" 92 | -------------------------------------------------------------------------------- /ci/roles/volume/tasks/volume_info.yml: -------------------------------------------------------------------------------- 1 | - name: Create volume 2 | openstack.cloud.volume: 3 | cloud: "{{ cloud }}" 4 | state: present 5 | size: 1 6 | name: ansible_test 7 | description: testci 8 | register: vol 9 | 10 | - name: Get info about volumes 11 | openstack.cloud.volume_info: 12 | cloud: "{{ cloud }}" 13 | details: true 14 | all_projects: true 15 | register: info 16 | 17 | - name: Check info 18 | assert: 19 | that: 20 | - info.volumes | selectattr("description", "equalto", "testci") | list | length == 1 21 | - info.volumes.0.name == 'ansible_test' 22 | - info.volumes.0.status == 'available' 23 | 24 | - name: Assert return values of volume_info module 25 | assert: 26 | that: 27 | # allow new fields to be introduced but prevent fields from being removed 28 | - expected_fields|difference(info.volumes[0].keys())|length == 0 29 | 30 | - name: Get not detailed info about volumes 31 | openstack.cloud.volume_info: 32 | cloud: "{{ cloud }}" 33 | details: false 34 | all_projects: true 35 | register: info1 36 | 37 | - name: Check info 38 | assert: 39 | that: 40 | - info1.volumes | selectattr("id", "equalto", info.volumes.0.id) | list | length == 1 41 | - info1.volumes.0.name == 'ansible_test' 42 | - info1.volumes.0.status == None 43 | 44 | - name: Get info about volumes with name 45 | openstack.cloud.volume_info: 46 | cloud: "{{ cloud }}" 47 | details: false 48 | name: ansible_test 49 | all_projects: true 50 | register: info2 51 | 52 | - name: Check info 53 | assert: 54 | that: 55 | - info2.volumes | length == 1 56 | - info2.volumes.0.name == 'ansible_test' 57 | 58 | - name: Get info about volumes with non-existent name 59 | openstack.cloud.volume_info: 60 | cloud: "{{ cloud }}" 61 | details: false 62 | name: nothing_here 63 | all_projects: true 64 | register: info3 65 | 66 | - name: Check info 67 | assert: 68 | that: 69 | - info3.volumes | length == 0 70 | 71 | - name: Get info about volumes 72 | openstack.cloud.volume_info: 73 | cloud: "{{ cloud }}" 74 | details: false 75 | name: ansible_test 76 | all_projects: true 77 | register: info4 78 | 79 | - name: Check info 80 | assert: 81 | that: 82 | - info4.volumes | length == 1 83 | - info4.volumes.0.name == 'ansible_test' 84 | 85 | - name: Get info about volumes not from all projects 86 | openstack.cloud.volume_info: 87 | cloud: "{{ cloud }}" 88 | details: false 89 | name: ansible_test 90 | register: info4a 91 | 92 | - name: Check info 93 | assert: 94 | that: 95 | - info4a.volumes | length == 1 96 | - info4a.volumes.0.name == 'ansible_test' 97 | 98 | - name: Delete volume 99 | openstack.cloud.volume: 100 | cloud: "{{ cloud }}" 101 | state: absent 102 | name: ansible_test 103 | 104 | - name: Get info when no volumes 105 | openstack.cloud.volume_info: 106 | cloud: "{{ cloud }}" 107 | all_projects: true 108 | register: info5 109 | 110 | - name: Check info 111 | assert: 112 | that: 113 | - info5.volumes | selectattr("name", "equalto", "ansible_test") | list | length == 0 114 | -------------------------------------------------------------------------------- /plugins/modules/keystone_federation_protocol_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright: Ansible Project 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | DOCUMENTATION = r''' 8 | --- 9 | module: keystone_federation_protocol_info 10 | short_description: Fetch Keystone federation protocols 11 | author: OpenStack Ansible SIG 12 | description: 13 | - Fetch Keystone federation protocols. 14 | options: 15 | name: 16 | description: 17 | - ID or name of the federation protocol. 18 | type: str 19 | aliases: ['id'] 20 | idp: 21 | description: 22 | - ID or name of the identity provider this protocol is associated with. 23 | aliases: ['idp_id', 'idp_name'] 24 | required: true 25 | type: str 26 | notes: 27 | - Name equals the ID of a federation protocol. 28 | - Name equals the ID of an identity provider. 29 | extends_documentation_fragment: 30 | - openstack.cloud.openstack 31 | ''' 32 | 33 | EXAMPLES = r''' 34 | - name: Fetch all federation protocols attached to an identity provider 35 | openstack.cloud.keystone_federation_protocol_info: 36 | cloud: example_cloud 37 | idp: example_idp 38 | 39 | - name: Fetch federation protocol by name 40 | openstack.cloud.keystone_federation_protocol_info: 41 | cloud: example_cloud 42 | idp: example_idp 43 | name: example_protocol 44 | ''' 45 | 46 | RETURN = r''' 47 | protocols: 48 | description: List of federation protocol dictionaries. 49 | returned: always 50 | type: list 51 | elements: dict 52 | contains: 53 | id: 54 | description: ID of the federation protocol. 55 | returned: success 56 | type: str 57 | mapping_id: 58 | description: The definition of the federation protocol. 59 | returned: success 60 | type: str 61 | name: 62 | description: Name of the protocol. Equal to C(id). 63 | returned: success 64 | type: str 65 | ''' 66 | 67 | from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule 68 | 69 | 70 | class IdentityFederationProtocolInfoModule(OpenStackModule): 71 | argument_spec = dict( 72 | name=dict(aliases=['id']), 73 | idp=dict(required=True, aliases=['idp_id', 'idp_name']), 74 | ) 75 | 76 | module_kwargs = dict( 77 | supports_check_mode=True 78 | ) 79 | 80 | def run(self): 81 | # name is id for federation protocols 82 | id = self.params['name'] 83 | 84 | # name is id for identity providers 85 | idp_id = self.params['idp'] 86 | 87 | if id: 88 | protocol = self.conn.identity.find_federation_protocol(idp_id, id) 89 | protocols = [protocol] if protocol else [] 90 | else: 91 | protocols = self.conn.identity.federation_protocols(idp_id) 92 | 93 | self.exit_json(changed=False, 94 | protocols=[p.to_dict(computed=False) 95 | for p in protocols]) 96 | 97 | 98 | def main(): 99 | module = IdentityFederationProtocolInfoModule() 100 | module() 101 | 102 | 103 | if __name__ == '__main__': 104 | main() 105 | -------------------------------------------------------------------------------- /plugins/modules/compute_service_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2016 Hewlett-Packard Enterprise Corporation 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | DOCUMENTATION = r''' 8 | --- 9 | module: compute_service_info 10 | short_description: Fetch OpenStack Compute (Nova) services 11 | author: OpenStack Ansible SIG 12 | description: 13 | - Fetch OpenStack Compute (Nova) services. 14 | options: 15 | binary: 16 | description: 17 | - Filter the service list result by binary name of the service. 18 | type: str 19 | host: 20 | description: 21 | - Filter the service list result by the host name. 22 | type: str 23 | extends_documentation_fragment: 24 | - openstack.cloud.openstack 25 | ''' 26 | 27 | EXAMPLES = r''' 28 | - name: Fetch all OpenStack Compute (Nova) services 29 | openstack.cloud.compute_service_info: 30 | cloud: awesomecloud 31 | 32 | - name: Fetch a subset of OpenStack Compute (Nova) services 33 | openstack.cloud.compute_service_info: 34 | cloud: awesomecloud 35 | binary: "nova-compute" 36 | host: "localhost" 37 | ''' 38 | 39 | RETURN = r''' 40 | compute_services: 41 | description: List of dictionaries describing Compute (Nova) services. 42 | returned: always 43 | type: list 44 | elements: dict 45 | contains: 46 | availability_zone: 47 | description: The availability zone name. 48 | type: str 49 | binary: 50 | description: The binary name of the service. 51 | type: str 52 | disabled_reason: 53 | description: The reason why the service is disabled 54 | type: str 55 | id: 56 | description: Unique UUID. 57 | type: str 58 | is_forced_down: 59 | description: If the service has been forced down or nova-compute 60 | type: bool 61 | host: 62 | description: The name of the host. 63 | type: str 64 | name: 65 | description: Service name 66 | type: str 67 | state: 68 | description: The state of the service. One of up or down. 69 | type: str 70 | status: 71 | description: The status of the service. One of enabled or disabled. 72 | type: str 73 | update_at: 74 | description: The date and time when the resource was updated 75 | type: str 76 | ''' 77 | 78 | from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule 79 | 80 | 81 | class ComputeServiceInfoModule(OpenStackModule): 82 | argument_spec = dict( 83 | binary=dict(), 84 | host=dict(), 85 | ) 86 | 87 | module_kwargs = dict( 88 | supports_check_mode=True 89 | ) 90 | 91 | def run(self): 92 | kwargs = {k: self.params[k] 93 | for k in ['binary', 'host'] 94 | if self.params[k] is not None} 95 | compute_services = self.conn.compute.services(**kwargs) 96 | 97 | self.exit_json(changed=False, 98 | compute_services=[s.to_dict(computed=False) 99 | for s in compute_services]) 100 | 101 | 102 | def main(): 103 | module = ComputeServiceInfoModule() 104 | module() 105 | 106 | 107 | if __name__ == '__main__': 108 | main() 109 | -------------------------------------------------------------------------------- /plugins/module_utils/ironic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # This code is part of Ansible, but is an independent component. 5 | # This particular file snippet, and this file snippet only, is BSD licensed. 6 | # Modules you write using this snippet, which is embedded dynamically by Ansible 7 | # still belong to the author of the module, and may assign their own license 8 | # to the complete work. 9 | # 10 | # Redistribution and use in source and binary forms, with or without modification, 11 | # are permitted provided that the following conditions are met: 12 | # 13 | # * Redistributions of source code must retain the above copyright 14 | # notice, this list of conditions and the following disclaimer. 15 | # * Redistributions in binary form must reproduce the above copyright notice, 16 | # this list of conditions and the following disclaimer in the documentation 17 | # and/or other materials provided with the distribution. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 24 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 27 | # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | 30 | from ansible.module_utils.basic import AnsibleModule 31 | from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec 32 | 33 | 34 | def ironic_argument_spec(**kwargs): 35 | spec = dict( 36 | auth_type=dict(), 37 | ironic_url=dict(), 38 | ) 39 | spec.update(kwargs) 40 | return openstack_full_argument_spec(**spec) 41 | 42 | 43 | # TODO(dtantsur): inherit the collection's base module 44 | class IronicModule(AnsibleModule): 45 | 46 | def __init__(self, *args, **kwargs): 47 | super().__init__(*args, **kwargs) 48 | self._update_ironic_auth() 49 | 50 | def _update_ironic_auth(self): 51 | """Validate and update authentication parameters for ironic.""" 52 | if ( 53 | self.params['auth_type'] in [None, 'None', 'none'] 54 | and self.params['ironic_url'] is None 55 | and not self.params['cloud'] 56 | and not (self.params['auth'] 57 | and self.params['auth'].get('endpoint')) 58 | ): 59 | self.fail_json(msg=("Authentication appears to be disabled, " 60 | "Please define either ironic_url, or cloud, " 61 | "or auth.endpoint")) 62 | 63 | if ( 64 | self.params['ironic_url'] 65 | and self.params['auth_type'] in [None, 'None', 'none'] 66 | and not (self.params['auth'] 67 | and self.params['auth'].get('endpoint')) 68 | ): 69 | self.params['auth'] = dict( 70 | endpoint=self.params['ironic_url'] 71 | ) 72 | -------------------------------------------------------------------------------- /ci/roles/security_group/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create security group 3 | openstack.cloud.security_group: 4 | cloud: "{{ cloud }}" 5 | name: ansible_security_group 6 | state: present 7 | description: 'Created from Ansible playbook' 8 | register: security_group 9 | 10 | - name: Assert return values of security_group module 11 | assert: 12 | that: 13 | - security_group.security_group.name == 'ansible_security_group' 14 | - security_group.security_group.description == 'Created from Ansible playbook' 15 | # allow new fields to be introduced but prevent fields from being removed 16 | - expected_fields|difference(security_group.security_group.keys())|length == 0 17 | 18 | - name: List all security groups 19 | openstack.cloud.security_group_info: 20 | cloud: "{{ cloud }}" 21 | register: security_groups 22 | 23 | - name: Assert return values of security_group_info module 24 | assert: 25 | that: 26 | - security_groups.security_groups | length > 0 27 | # allow new fields to be introduced but prevent fields from being removed 28 | - expected_fields|difference(security_groups.security_groups[0].keys())|length == 0 29 | 30 | - name: Find security group by name 31 | openstack.cloud.security_group_info: 32 | cloud: "{{ cloud }}" 33 | name: ansible_security_group 34 | register: security_groups 35 | 36 | - name: Check filter security group by name 37 | assert: 38 | that: 39 | - security_groups.security_groups | length == 1 40 | - security_groups.security_groups.0.id == security_group.security_group.id 41 | 42 | - name: Filter security group by description 43 | openstack.cloud.security_group_info: 44 | cloud: "{{ cloud }}" 45 | description: 'Created from Ansible playbook' 46 | register: security_groups 47 | 48 | - name: Check filter security group by description 49 | assert: 50 | that: 51 | - security_groups.security_groups | length == 1 52 | - security_groups.security_groups.0.id == security_group.security_group.id 53 | 54 | - name: Filter security group by not_tags 55 | openstack.cloud.security_group_info: 56 | cloud: "{{ cloud }}" 57 | name: ansible_security_group 58 | not_tags: 59 | - ansibletag1 60 | - ansibletag2 61 | register: security_groups 62 | 63 | - name: Check filter security group by not_tags 64 | assert: 65 | that: 66 | - security_groups.security_groups | length == 1 67 | - security_groups.security_groups.0.id == security_group.security_group.id 68 | 69 | - name: Delete security group 70 | openstack.cloud.security_group: 71 | cloud: "{{ cloud }}" 72 | name: ansible_security_group 73 | state: absent 74 | 75 | - name: Create stateless security group 76 | openstack.cloud.security_group: 77 | cloud: "{{ cloud }}" 78 | name: ansible_security_group_stateless 79 | stateful: false 80 | state: present 81 | description: 'Created from Ansible playbook' 82 | register: security_group_stateless 83 | 84 | - name: Assert return values of security_group module 85 | assert: 86 | that: 87 | - security_group_stateless.security_group.name == 'ansible_security_group_stateless' 88 | - security_group_stateless.security_group.stateful == False 89 | 90 | - name: Delete stateless security group 91 | openstack.cloud.security_group: 92 | cloud: "{{ cloud }}" 93 | name: ansible_security_group_stateless 94 | state: absent 95 | 96 | - include_tasks: rules.yml 97 | -------------------------------------------------------------------------------- /plugins/modules/identity_group_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2019, Phillipe Smith 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | DOCUMENTATION = r''' 8 | --- 9 | module: identity_group_info 10 | short_description: Fetch OpenStack identity (Keystone) groups 11 | author: OpenStack Ansible SIG 12 | description: 13 | - Fetch OpenStack identity (Keystone) groups. 14 | options: 15 | domain: 16 | description: 17 | - Name or ID of the domain containing the group. 18 | type: str 19 | filters: 20 | description: 21 | - A dictionary of meta data to use for further filtering. Elements of 22 | this dictionary may be additional dictionaries. 23 | type: dict 24 | name: 25 | description: 26 | - Name or ID of the group. 27 | type: str 28 | extends_documentation_fragment: 29 | - openstack.cloud.openstack 30 | ''' 31 | 32 | EXAMPLES = r''' 33 | - name: Gather previously created groups 34 | openstack.cloud.identity_group_info: 35 | cloud: awesomecloud 36 | 37 | - name: Gather previously created groups by name 38 | openstack.cloud.identity_group_info: 39 | cloud: awesomecloud 40 | name: demogroup 41 | 42 | - name: Gather previously created groups in a specific domain 43 | openstack.cloud.identity_group_info: 44 | cloud: awesomecloud 45 | domain: admindomain 46 | 47 | - name: Gather and filter previously created groups 48 | openstack.cloud.identity_group_info: 49 | cloud: awesomecloud 50 | name: demogroup 51 | domain: admindomain 52 | filters: 53 | is_enabled: False 54 | ''' 55 | 56 | RETURN = r''' 57 | groups: 58 | description: Dictionary describing all matching identity groups. 59 | returned: always 60 | type: list 61 | elements: dict 62 | contains: 63 | name: 64 | description: Name given to the group. 65 | type: str 66 | description: 67 | description: Description of the group. 68 | type: str 69 | id: 70 | description: Unique UUID. 71 | type: str 72 | domain_id: 73 | description: Domain ID containing the group (keystone v3 clouds only) 74 | type: bool 75 | ''' 76 | 77 | from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule 78 | 79 | 80 | class IdentityGroupInfoModule(OpenStackModule): 81 | argument_spec = dict( 82 | domain=dict(), 83 | filters=dict(type='dict'), 84 | name=dict(), 85 | ) 86 | module_kwargs = dict( 87 | supports_check_mode=True 88 | ) 89 | 90 | def run(self): 91 | name = self.params['name'] 92 | filters = self.params['filters'] or {} 93 | 94 | kwargs = {} 95 | domain_name_or_id = self.params['domain'] 96 | if domain_name_or_id: 97 | domain = self.conn.identity.find_domain(domain_name_or_id) 98 | if domain is None: 99 | self.exit_json(changed=False, groups=[]) 100 | kwargs['domain_id'] = domain['id'] 101 | 102 | groups = self.conn.search_groups(name, filters, **kwargs) 103 | self.exit_json(changed=False, 104 | groups=[g.to_dict(computed=False) for g in groups]) 105 | 106 | 107 | def main(): 108 | module = IdentityGroupInfoModule() 109 | module() 110 | 111 | 112 | if __name__ == '__main__': 113 | main() 114 | -------------------------------------------------------------------------------- /ci/roles/baremetal_port/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # TODO: Actually run this role in CI. Atm we do not have DevStack's ironic plugin enabled. 3 | - name: Create baremetal node 4 | openstack.cloud.baremetal_node: 5 | cloud: "{{ cloud }}" 6 | driver_info: 7 | ipmi_address: "1.2.3.4" 8 | ipmi_username: "admin" 9 | ipmi_password: "secret" 10 | name: ansible_baremetal_node 11 | nics: 12 | - mac: "aa:bb:cc:aa:bb:cc" 13 | state: present 14 | register: node 15 | 16 | - name: Create baremetal port 17 | openstack.cloud.baremetal_port: 18 | cloud: "{{ cloud }}" 19 | state: present 20 | node: ansible_baremetal_node 21 | address: fa:16:3e:aa:aa:aa 22 | is_pxe_enabled: False 23 | register: port 24 | 25 | - debug: var=port 26 | 27 | - name: Assert return values of baremetal_port module 28 | assert: 29 | that: 30 | - not port.port.is_pxe_enabled 31 | # allow new fields to be introduced but prevent fields from being removed 32 | - expected_fields|difference(port.port.keys())|length == 0 33 | 34 | - name: Fetch baremetal ports 35 | openstack.cloud.baremetal_port_info: 36 | cloud: "{{ cloud }}" 37 | register: ports 38 | 39 | - name: Assert module results of baremetal_port_info module 40 | assert: 41 | that: 42 | - ports.ports|list|length > 0 43 | 44 | - name: assert return values of baremetal_port_info module 45 | assert: 46 | that: 47 | # allow new fields to be introduced but prevent fields from being removed 48 | - expected_fields|difference(ports.ports.0.keys())|length == 0 49 | 50 | - name: Fetch baremetal port by id 51 | openstack.cloud.baremetal_port_info: 52 | cloud: "{{ cloud }}" 53 | id: "{{ port.port.id }}" 54 | register: ports 55 | 56 | - name: assert module results of baremetal_port_info module 57 | assert: 58 | that: 59 | - ports.ports|list|length == 1 60 | - ports.ports.0.id == port.port.id 61 | 62 | - name: Update baremetal port 63 | openstack.cloud.baremetal_port: 64 | cloud: "{{ cloud }}" 65 | state: present 66 | id: "{{ port.port.id }}" 67 | is_pxe_enabled: True 68 | register: updated_port 69 | 70 | - name: Assert return values of updated baremetal port 71 | assert: 72 | that: 73 | - update_port is changed 74 | - update_port.port.id == port.port.id 75 | - update_port.port.address == port.port.address 76 | - update_port.port.is_pxe_enabled 77 | 78 | - name: Update baremetal port again 79 | openstack.cloud.baremetal_port: 80 | cloud: "{{ cloud }}" 81 | state: present 82 | id: "{{ port.port.id }}" 83 | is_pxe_enabled: True 84 | register: updated_port 85 | 86 | - name: Assert return values of updated baremetal port 87 | assert: 88 | that: 89 | - update_port is not changed 90 | - update_port.port.id == port.port.id 91 | 92 | - name: Delete Bare Metal port 93 | openstack.cloud.baremetal_port: 94 | cloud: "{{ cloud }}" 95 | state: absent 96 | id: "{{ port.port.id }}" 97 | 98 | - name: Fetch baremetal ports 99 | openstack.cloud.baremetal_port_info: 100 | cloud: "{{ cloud }}" 101 | register: ports 102 | 103 | - name: Assert no baremetal port is left 104 | assert: 105 | that: 106 | - ports.ports|list|length == 0 107 | 108 | - name: Delete baremetal node 109 | openstack.cloud.baremetal_node: 110 | cloud: "{{ cloud }}" 111 | name: ansible_baremetal_node 112 | state: absent 113 | -------------------------------------------------------------------------------- /ci/roles/object_container/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create an empty container with public access 3 | openstack.cloud.object_container: 4 | cloud: "{{ cloud }}" 5 | name: ansible_container 6 | read_ACL: ".r:*,.rlistings" 7 | register: container 8 | 9 | - name: Assert return values of container module 10 | assert: 11 | that: 12 | - container is changed 13 | - container.container.name == "ansible_container" 14 | - container.container.read_ACL == ".r:*,.rlistings" 15 | # allow new fields to be introduced but prevent fields from being removed 16 | - expected_fields|difference(container.container.keys())|length == 0 17 | 18 | - name: Set container metadata aka container properties 19 | openstack.cloud.object_container: 20 | cloud: "{{ cloud }}" 21 | name: ansible_container 22 | metadata: 23 | 'Cache-Control': 'no-cache' 24 | 'foo': 'bar' 25 | register: container 26 | 27 | - name: Verify container metadata was set 28 | assert: 29 | that: 30 | - container is changed 31 | - ('cache-control' in container.container.metadata.keys()|map('lower')) 32 | - container.container.metadata['foo'] == 'bar' 33 | 34 | - name: Update container metadata 35 | openstack.cloud.object_container: 36 | cloud: "{{ cloud }}" 37 | name: ansible_container 38 | metadata: 39 | 'foo': 'baz' 40 | register: container 41 | 42 | - name: Verify container metadata was updated 43 | assert: 44 | that: 45 | - container is changed 46 | - ('cache-control' in container.container.metadata.keys()|map('lower')) 47 | - container.container.metadata['foo'] == 'baz' 48 | 49 | - name: Update a container 50 | openstack.cloud.object_container: 51 | cloud: "{{ cloud }}" 52 | name: ansible_container 53 | delete_metadata_keys: 54 | - 'Cache-Control' 55 | read_ACL: "" 56 | register: container 57 | 58 | - name: Verify updated container 59 | assert: 60 | that: 61 | - container is changed 62 | - ('cache-control' not in container.container.metadata.keys()|map('lower')) 63 | - "container.container.metadata == {'foo': 'baz'}" 64 | - container.container.read_ACL is none or container.container.read_ACL == "" 65 | 66 | - name: Delete container 67 | openstack.cloud.object_container: 68 | cloud: "{{ cloud }}" 69 | name: ansible_container 70 | state: absent 71 | register: container 72 | 73 | - name: Verify container was deleted 74 | assert: 75 | that: 76 | - container is changed 77 | 78 | - name: Delete container again 79 | openstack.cloud.object_container: 80 | cloud: "{{ cloud }}" 81 | name: ansible_container 82 | state: absent 83 | register: container 84 | 85 | - name: Verify container was not deleted again 86 | assert: 87 | that: 88 | - container is not changed 89 | 90 | - name: Create another container for recursive deletion 91 | openstack.cloud.object_container: 92 | cloud: "{{ cloud }}" 93 | name: ansible_container2 94 | 95 | - name: Load an object into container 96 | openstack.cloud.object: 97 | cloud: "{{ cloud }}" 98 | state: present 99 | name: ansible_object 100 | data: "this is another test" 101 | container: ansible_container2 102 | 103 | - name: Delete container recursively 104 | openstack.cloud.object_container: 105 | cloud: "{{ cloud }}" 106 | state: absent 107 | name: ansible_container2 108 | delete_with_all_objects: true 109 | -------------------------------------------------------------------------------- /ci/roles/identity_domain/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create keystone domain 3 | openstack.cloud.identity_domain: 4 | cloud: "{{ cloud }}" 5 | state: present 6 | name: ansible_domain 7 | description: "test description" 8 | register: domain 9 | 10 | - name: Assert return values of identity_domain module 11 | assert: 12 | that: 13 | - domain.domain.name == 'ansible_domain' 14 | - domain.domain.description == "test description" 15 | # allow new fields to be introduced but prevent fields from being removed 16 | - expected_fields|difference(domain.domain.keys())|length == 0 17 | 18 | - name: Update keystone domain 19 | openstack.cloud.identity_domain: 20 | cloud: "{{ cloud }}" 21 | name: ansible_domain 22 | description: "updated description" 23 | register: domain 24 | 25 | - name: Assert updated domain 26 | assert: 27 | that: 28 | - domain.domain.description == "updated description" 29 | 30 | - name: Fetch domains 31 | openstack.cloud.identity_domain_info: 32 | cloud: "{{ cloud }}" 33 | register: domains 34 | 35 | - name: Assert return values of identity_domain_info module 36 | assert: 37 | that: 38 | - domains.domains | length > 0 39 | # allow new fields to be introduced but prevent fields from being removed 40 | - expected_fields|difference(domains.domains.0.keys())|length == 0 41 | 42 | - name: Fetch domain by name 43 | openstack.cloud.identity_domain_info: 44 | cloud: "{{ cloud }}" 45 | name: ansible_domain 46 | register: domains 47 | 48 | - name: Assert named domain 49 | assert: 50 | that: 51 | - domains.domains | length == 1 52 | 53 | - name: Create disabled domain 54 | openstack.cloud.identity_domain: 55 | cloud: "{{ cloud }}" 56 | state: present 57 | name: ansible_domain_disabled 58 | is_enabled: false 59 | description: "test description" 60 | register: domain 61 | 62 | - name: Fetch all domains 63 | openstack.cloud.identity_domain_info: 64 | cloud: "{{ cloud }}" 65 | register: domains 66 | 67 | - name: Assert both ansible domains exist 68 | assert: 69 | that: 70 | - domains.domains | length >= 2 71 | 72 | - name: Fetch disabled domains 73 | openstack.cloud.identity_domain_info: 74 | cloud: "{{ cloud }}" 75 | filters: 76 | is_enabled: false 77 | register: domains 78 | 79 | - name: Assert at least one disabled domain exists 80 | assert: 81 | that: 82 | - domains.domains | length >= 1 83 | 84 | - name: Fetch enabled domains 85 | openstack.cloud.identity_domain_info: 86 | cloud: "{{ cloud }}" 87 | filters: 88 | is_enabled: true 89 | register: domains 90 | 91 | - name: Assert returned value 92 | assert: 93 | that: 94 | - item.is_enabled 95 | loop: "{{ domains.domains }}" 96 | 97 | - name: Delete disabled domain 98 | openstack.cloud.identity_domain: 99 | cloud: "{{ cloud }}" 100 | state: absent 101 | name: ansible_domain_disabled 102 | 103 | - name: Assert domain is disabled 104 | assert: 105 | that: 106 | - not domain.domain.is_enabled 107 | 108 | - name: Delete domain 109 | openstack.cloud.identity_domain: 110 | cloud: "{{ cloud }}" 111 | state: absent 112 | name: ansible_domain 113 | 114 | - name: Get non-existing domain 115 | openstack.cloud.identity_domain_info: 116 | cloud: "{{ cloud }}" 117 | name: ansible_domain 118 | register: domains 119 | 120 | - name: Assert no results returned 121 | assert: 122 | that: 123 | - domains.domains | length == 0 124 | -------------------------------------------------------------------------------- /ci/roles/trunk/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create parent network 3 | openstack.cloud.network: 4 | cloud: "{{ cloud }}" 5 | state: present 6 | name: "{{ parent_network_name }}" 7 | external: true 8 | register: parent_network 9 | 10 | - name: Create parent subnet 11 | openstack.cloud.subnet: 12 | cloud: "{{ cloud }}" 13 | state: present 14 | name: "{{ parent_subnet_name }}" 15 | network_name: "{{ parent_network_name }}" 16 | cidr: 10.5.5.0/24 17 | register: parent_subnet 18 | 19 | - name: Create parent port 20 | openstack.cloud.port: 21 | cloud: "{{ cloud }}" 22 | state: present 23 | name: "{{ parent_port_name }}" 24 | network: "{{ parent_network_name }}" 25 | fixed_ips: 26 | - ip_address: 10.5.5.69 27 | register: parent_port 28 | 29 | - name: Create subport network 30 | openstack.cloud.network: 31 | cloud: "{{ cloud }}" 32 | state: present 33 | name: "{{ subport_network_name }}" 34 | external: true 35 | register: subport_network 36 | 37 | - name: Create subport subnet 38 | openstack.cloud.subnet: 39 | cloud: "{{ cloud }}" 40 | state: present 41 | name: "{{ subport_subnet_name }}" 42 | network_name: "{{ subport_network_name }}" 43 | cidr: 10.5.6.0/24 44 | register: subport_subnet 45 | 46 | - name: Create subport 47 | openstack.cloud.port: 48 | cloud: "{{ cloud }}" 49 | state: present 50 | name: "{{ subport_name }}" 51 | network: "{{ subport_network_name }}" 52 | fixed_ips: 53 | - ip_address: 10.5.6.55 54 | register: subport 55 | 56 | - name: Create trunk 57 | openstack.cloud.trunk: 58 | cloud: "{{ cloud }}" 59 | state: present 60 | name: "{{ trunk_name }}" 61 | port: "{{ parent_port_name }}" 62 | register: trunk 63 | 64 | - debug: var=trunk 65 | 66 | - name: assert return values of trunk module 67 | assert: 68 | that: 69 | # allow new fields to be introduced but prevent fields from being removed 70 | - expected_fields|difference(trunk.trunk.keys())|length == 0 71 | 72 | - name: Add subport to trunk 73 | openstack.cloud.trunk: 74 | cloud: "{{ cloud }}" 75 | state: present 76 | name: "{{ trunk_name }}" 77 | port: "{{ parent_port_name }}" 78 | sub_ports: 79 | - port: "{{ subport_name }}" 80 | segmentation_type: vlan 81 | segmentation_id: 123 82 | 83 | - name: Update subport from trunk 84 | openstack.cloud.trunk: 85 | cloud: "{{ cloud }}" 86 | state: present 87 | name: "{{ trunk_name }}" 88 | port: "{{ parent_port_name }}" 89 | sub_ports: [] 90 | 91 | - name: Delete trunk 92 | openstack.cloud.trunk: 93 | cloud: "{{ cloud }}" 94 | state: absent 95 | name: "{{ trunk_name }}" 96 | 97 | - name: Delete subport 98 | openstack.cloud.port: 99 | cloud: "{{ cloud }}" 100 | state: absent 101 | name: "{{ subport_name }}" 102 | 103 | - name: Delete subport subnet 104 | openstack.cloud.subnet: 105 | cloud: "{{ cloud }}" 106 | state: absent 107 | name: "{{ subport_subnet_name }}" 108 | 109 | - name: Delete subport network 110 | openstack.cloud.network: 111 | cloud: "{{ cloud }}" 112 | state: absent 113 | name: "{{ subport_network_name }}" 114 | 115 | - name: Delete parent port 116 | openstack.cloud.port: 117 | cloud: "{{ cloud }}" 118 | state: absent 119 | name: "{{ parent_port_name }}" 120 | 121 | - name: Delete parent subnet 122 | openstack.cloud.subnet: 123 | cloud: "{{ cloud }}" 124 | state: absent 125 | name: "{{ parent_subnet_name }}" 126 | 127 | - name: Delete parent network 128 | openstack.cloud.network: 129 | cloud: "{{ cloud }}" 130 | state: absent 131 | name: "{{ parent_network_name }}" 132 | -------------------------------------------------------------------------------- /ci/roles/recordset/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Ensure DNS zone not present before tests 2 | openstack.cloud.dns_zone: 3 | cloud: "{{ cloud }}" 4 | name: "{{ dns_zone_name }}" 5 | zone_type: "primary" 6 | email: test@example.net 7 | state: absent 8 | 9 | - name: Ensure dns zone 10 | openstack.cloud.dns_zone: 11 | cloud: "{{ cloud }}" 12 | name: "{{ dns_zone_name }}" 13 | zone_type: "primary" 14 | email: test@example.net 15 | register: dns_zone 16 | 17 | - name: Ensure recordset not present 18 | openstack.cloud.recordset: 19 | cloud: "{{ cloud }}" 20 | zone: "{{ dns_zone.zone.name }}" 21 | name: "{{ recordset_name }}" 22 | recordset_type: "a" 23 | records: "{{ records }}" 24 | state: absent 25 | 26 | - name: Create a recordset 27 | openstack.cloud.recordset: 28 | cloud: "{{ cloud }}" 29 | zone: "{{ dns_zone.zone.name }}" 30 | name: "{{ recordset_name }}" 31 | recordset_type: "a" 32 | records: "{{ records }}" 33 | register: recordset 34 | until: '"PENDING" not in recordset["recordset"].status' 35 | retries: 10 36 | delay: 5 37 | 38 | - name: Verify recordset info 39 | assert: 40 | that: 41 | - recordset["recordset"].name == recordset_name 42 | - recordset["recordset"].zone_name == dns_zone.zone.name 43 | - recordset["recordset"].records | list | sort == records | list | sort 44 | 45 | - name: Assert recordset fields 46 | assert: 47 | that: item in recordset.recordset 48 | loop: "{{ recordset_fields }}" 49 | 50 | - name: Create identical recordset 51 | openstack.cloud.recordset: 52 | cloud: "{{ cloud }}" 53 | zone: "{{ dns_zone.zone.name }}" 54 | name: "{{ recordset_name }}" 55 | recordset_type: "a" 56 | records: "{{ records }}" 57 | register: recordset 58 | 59 | - name: Assert recordset not changed 60 | assert: 61 | that: 62 | - recordset is not changed 63 | 64 | - name: Assert recordset fields 65 | assert: 66 | that: item in recordset.recordset 67 | loop: "{{ recordset_fields }}" 68 | 69 | - name: Update a recordset 70 | openstack.cloud.recordset: 71 | cloud: "{{ cloud }}" 72 | zone: "{{ dns_zone.zone.name }}" 73 | name: "{{ recordset_name }}" 74 | recordset_type: "a" 75 | records: "{{ updated_records }}" 76 | description: "new test recordset" 77 | register: recordset 78 | 79 | - name: Verify recordset info 80 | assert: 81 | that: 82 | - recordset is changed 83 | - recordset["recordset"].zone_name == dns_zone.zone.name 84 | - recordset["recordset"].name == recordset_name 85 | - recordset["recordset"].records | list | sort == updated_records | list | sort 86 | 87 | - name: Assert recordset fields 88 | assert: 89 | that: item in recordset.recordset 90 | loop: "{{ recordset_fields }}" 91 | 92 | - name: Delete recordset 93 | openstack.cloud.recordset: 94 | cloud: "{{ cloud }}" 95 | zone: "{{ dns_zone.zone.name }}" 96 | name: "{{ recordset.recordset.name }}" 97 | state: absent 98 | register: deleted_recordset 99 | 100 | - name: Verify recordset deletion 101 | assert: 102 | that: 103 | - deleted_recordset is successful 104 | - deleted_recordset is changed 105 | 106 | - name: Delete unexistent recordset 107 | openstack.cloud.recordset: 108 | cloud: "{{ cloud }}" 109 | zone: "{{ dns_zone.zone.name }}" 110 | name: "{{ recordset.recordset.name }}" 111 | state: absent 112 | register: deleted_recordset 113 | 114 | - name: Verify recordset deletion 115 | assert: 116 | that: 117 | - deleted_recordset is not changed 118 | 119 | - name: Delete dns zone 120 | openstack.cloud.dns_zone: 121 | cloud: "{{ cloud }}" 122 | name: "{{ dns_zone.zone.name }}" 123 | state: absent 124 | -------------------------------------------------------------------------------- /ci/roles/router/tasks/shared_network.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create the first project 3 | openstack.cloud.project: 4 | cloud: "{{ cloud }}" 5 | state: present 6 | name: "shared_net_test_1" 7 | description: "Project that contains the subnet to be shared" 8 | domain: default 9 | is_enabled: True 10 | register: project_1 11 | 12 | - name: Create the network to be shared 13 | openstack.cloud.network: 14 | cloud: "{{ cloud }}" 15 | state: present 16 | name: "my_shared_network" 17 | project: "shared_net_test_1" 18 | external: False 19 | provider_network_type: vxlan 20 | register: shared_network 21 | 22 | - name: Create ipv4 subnet 23 | openstack.cloud.subnet: 24 | cloud: "{{ cloud }}" 25 | state: present 26 | network_name: "{{ shared_network.id }}" 27 | name: "my_shared_subnet" 28 | project: "shared_net_test_1" 29 | ip_version: "4" 30 | cidr: "10.0.0.0/24" 31 | gateway_ip: "10.0.0.1" 32 | register: shared_subnet 33 | 34 | - name: Create the second project 35 | openstack.cloud.project: 36 | cloud: "{{ cloud }}" 37 | state: present 38 | name: "shared_net_test_2" 39 | description: "Project that contains the subnet to be shared" 40 | domain: default 41 | is_enabled: True 42 | register: project_2 43 | 44 | - name: Share the network with the second project 45 | openstack.cloud.neutron_rbac_policy: 46 | cloud: "{{ cloud }}" 47 | action: 'access_as_shared' 48 | object_id: "{{ shared_network.id }}" 49 | object_type: 'network' 50 | target_project_id: "{{ project_2.project.id }}" 51 | project_id: "{{ project_1.project.id }}" 52 | register: rbac_rule 53 | 54 | - name: Create router with interface in shared network 55 | openstack.cloud.router: 56 | cloud: "{{ cloud }}" 57 | state: present 58 | name: "shared_net_test2_router" 59 | project: "shared_net_test_2" 60 | interfaces: 61 | - net: "{{ shared_network.id }}" 62 | portip: "10.0.0.42" 63 | subnet: "{{ shared_subnet.id }}" 64 | register: router 65 | 66 | - name: Gather routers info 67 | openstack.cloud.routers_info: 68 | cloud: "{{ cloud }}" 69 | name: "shared_net_test2_router" 70 | register: routers 71 | 72 | - name: List ports of first router 73 | openstack.cloud.port_info: 74 | cloud: "{{ cloud }}" 75 | filters: 76 | device_id: "{{ routers.routers.0.id }}" 77 | register: ports 78 | 79 | - name: Verify routers info 80 | assert: 81 | that: 82 | - routers.routers.0.id == router.router.id 83 | - ports.ports 84 | |rejectattr('device_owner', 'equalto', 'network:router_gateway') 85 | |sum(attribute='fixed_ips', start=[]) 86 | |map(attribute='ip_address') 87 | |sort|list == ["10.0.0.42"] 88 | 89 | - name: delete router 90 | openstack.cloud.router: 91 | cloud: "{{ cloud }}" 92 | state: absent 93 | name: "shared_net_test2_router" 94 | project: "shared_net_test_2" 95 | 96 | - name: delete rbac rule 97 | openstack.cloud.neutron_rbac_policy: 98 | cloud: "{{ cloud }}" 99 | policy_id: "{{ rbac_rule.policy.id }}" 100 | state: absent 101 | 102 | - name: delete subnet 103 | openstack.cloud.subnet: 104 | cloud: "{{ cloud }}" 105 | state: absent 106 | network_name: "{{ shared_network.id }}" 107 | name: "my_shared_subnet" 108 | project: "shared_net_test_1" 109 | 110 | - name: delete network 111 | openstack.cloud.network: 112 | cloud: "{{ cloud }}" 113 | state: absent 114 | name: "my_shared_network" 115 | project: "shared_net_test_1" 116 | 117 | - name: delete project 2 118 | openstack.cloud.project: 119 | cloud: "{{ cloud }}" 120 | state: absent 121 | name: "shared_net_test_2" 122 | 123 | - name: delete project 1 124 | openstack.cloud.project: 125 | cloud: "{{ cloud }}" 126 | state: absent 127 | name: "shared_net_test_1" 128 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 3.18.0 3 | envlist = linters_latest,ansible_latest 4 | skipsdist = True 5 | ignore_basepython_conflict = True 6 | 7 | [testenv] 8 | skip_install = True 9 | install_command = python3 -m pip install {opts} {packages} 10 | basepython = python3 11 | passenv = 12 | OS_* 13 | setenv = 14 | VIRTUAL_ENV={envdir} 15 | LANG=en_US.UTF-8 16 | LANGUAGE=en_US:en 17 | LC_ALL=en_US.utf-8 18 | OS_LOG_CAPTURE={env:OS_LOG_CAPTURE:true} 19 | OS_STDOUT_CAPTURE={env:OS_STDOUT_CAPTURE:true} 20 | OS_STDERR_CAPTURE={env:OS_STDERR_CAPTURE:true} 21 | commands = stestr run {posargs} 22 | stestr slowest 23 | 24 | [testenv:pep8] 25 | # for Zuul CI job tox-pep8 26 | commands = 27 | flake8 28 | deps = 29 | -c{env:TOX_CONSTRAINTS_FILE:{toxinidir}/tests/constraints-none.txt} 30 | -r{toxinidir}/tests/requirements.txt 31 | 32 | [testenv:build] 33 | allowlist_externals = bash 34 | deps = 35 | ansible-core 36 | galaxy-importer 37 | pbr 38 | ruamel.yaml 39 | setuptools 40 | commands = 41 | python {toxinidir}/tools/build.py 42 | ansible --version 43 | ansible-galaxy collection build --force {toxinidir} --output-path {toxinidir}/build_artifact 44 | bash {toxinidir}/tools/check-import.sh {toxinidir} 45 | 46 | [testenv:linters_{2_9,2_11,2_12,2_16,2_18,latest}] 47 | allowlist_externals = bash 48 | commands = 49 | {[testenv:build]commands} 50 | flake8 51 | ansible --version 52 | bash {toxinidir}/tools/run-ansible-sanity.sh {toxinidir} 53 | deps = 54 | -c{env:TOX_CONSTRAINTS_FILE:{toxinidir}/tests/constraints-none.txt} 55 | {[testenv:build]deps} 56 | linters_latest: -r{toxinidir}/tests/requirements.txt 57 | linters_2_9: -r{toxinidir}/tests/requirements-ansible-2.9.txt 58 | linters_2_11: -r{toxinidir}/tests/requirements-ansible-2.11.txt 59 | linters_2_12: -r{toxinidir}/tests/requirements-ansible-2.12.txt 60 | linters_2_16: -r{toxinidir}/tests/requirements-ansible-2.16.txt 61 | linters_2_18: -r{toxinidir}/tests/requirements-ansible-2.18.txt 62 | passenv = * 63 | 64 | [flake8] 65 | # W503 Is supposed to be off by default but in the latest pycodestyle isn't. 66 | # Also, both openstacksdk and Donald Knuth disagree with the rule. Line 67 | # breaks should occur before the binary operator for readability. 68 | # H4 are rules for docstrings. Maybe we should clean them? 69 | # E501,E402,H301 are ignored so we can import the existing 70 | # modules unchanged and then clean them in subsequent patches. 71 | ignore = W503,H4,E501,E402,H301 72 | show-source = True 73 | exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,ansible_collections 74 | 75 | [testenv:ansible_{2_9,2_11,2_12,2_16,2_18,latest}] 76 | allowlist_externals = bash 77 | commands = 78 | bash {toxinidir}/ci/run-ansible-tests-collection.sh -e {envdir} {posargs} 79 | deps = 80 | -c{env:TOX_CONSTRAINTS_FILE:{toxinidir}/tests/constraints-none.txt} 81 | ansible_latest: -r{toxinidir}/tests/requirements.txt 82 | ansible_2_9: -r{toxinidir}/tests/requirements-ansible-2.9.txt 83 | ansible_2_11: -r{toxinidir}/tests/requirements-ansible-2.11.txt 84 | ansible_2_12: -r{toxinidir}/tests/requirements-ansible-2.12.txt 85 | ansible_2_16: -r{toxinidir}/tests/requirements-ansible-2.16.txt 86 | ansible_2_18: -r{toxinidir}/tests/requirements-ansible-2.18.txt 87 | # Need to pass some env vars for the Ansible playbooks 88 | passenv = 89 | HOME 90 | USER 91 | ANSIBLE_* 92 | 93 | [testenv:galaxy_release] 94 | allowlist_externals = mkdir rm sed 95 | commands = 96 | rm -rf /tmp/collection_built/ 97 | mkdir -p /tmp/collection_built/ 98 | sed -i "s/version:.*/version: {env:VERSION_TAG}/" {toxinidir}/galaxy.yml 99 | ansible-galaxy collection build {toxinidir} --output-path /tmp/collection_built/ --force 100 | ansible-galaxy collection publish /tmp/collection_built/openstack-cloud-{env:VERSION_TAG}.tar.gz --token {env:API_GALAXY_TOKEN} 101 | deps = 102 | ansible-core 103 | -------------------------------------------------------------------------------- /plugins/modules/project_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2016 Hewlett-Packard Enterprise Corporation 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | DOCUMENTATION = r''' 8 | --- 9 | module: project_info 10 | short_description: Retrieve information about one or more OpenStack projects 11 | author: OpenStack Ansible SIG 12 | description: 13 | - Retrieve information about a one or more OpenStack projects 14 | options: 15 | name: 16 | description: 17 | - Name or ID of the project. 18 | type: str 19 | domain: 20 | description: 21 | - Name or ID of the domain containing the project. 22 | type: str 23 | filters: 24 | description: 25 | - A dictionary of meta data to use for filtering projects. 26 | - Elements of I(filters) are passed as query parameters to 27 | OpenStack Identity API. 28 | type: dict 29 | extends_documentation_fragment: 30 | - openstack.cloud.openstack 31 | ''' 32 | 33 | EXAMPLES = r''' 34 | - name: Fetch all Identity (Keystone) projects 35 | openstack.cloud.project_info: 36 | cloud: awesomecloud 37 | 38 | - name: Fetch all projects with a name 39 | openstack.cloud.project_info: 40 | cloud: awesomecloud 41 | name: demoproject 42 | 43 | - name: Fetch all projects with a name in a domain 44 | openstack.cloud.project_info: 45 | cloud: awesomecloud 46 | name: demoproject 47 | domain: admindomain 48 | 49 | - name: Fetch all disabled projects 50 | openstack.cloud.project_info: 51 | cloud: awesomecloud 52 | filters: 53 | is_enabled: false 54 | ''' 55 | 56 | RETURN = r''' 57 | projects: 58 | description: List of dictionaries describing Identity (Keystone) projects. 59 | elements: dict 60 | returned: always, but can be empty 61 | type: list 62 | contains: 63 | description: 64 | description: Project description 65 | type: str 66 | sample: "demodescription" 67 | domain_id: 68 | description: Domain ID to which the project belongs 69 | type: str 70 | sample: "default" 71 | id: 72 | description: Project ID 73 | type: str 74 | sample: "f59382db809c43139982ca4189404650" 75 | is_domain: 76 | description: Indicates whether the project also acts as a domain. 77 | type: bool 78 | is_enabled: 79 | description: Indicates whether the project is enabled 80 | type: bool 81 | name: 82 | description: Project name 83 | type: str 84 | sample: "demoproject" 85 | options: 86 | description: The resource options for the project 87 | type: dict 88 | parent_id: 89 | description: The ID of the parent of the project 90 | type: str 91 | tags: 92 | description: A list of associated tags 93 | type: list 94 | elements: str 95 | ''' 96 | 97 | from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule 98 | 99 | 100 | class IdentityProjectInfoModule(OpenStackModule): 101 | argument_spec = dict( 102 | domain=dict(), 103 | name=dict(), 104 | filters=dict(type='dict'), 105 | ) 106 | module_kwargs = dict( 107 | supports_check_mode=True 108 | ) 109 | 110 | def run(self): 111 | filters = self.params['filters'] or {} 112 | 113 | domain_name_or_id = self.params['domain'] 114 | if domain_name_or_id is not None: 115 | domain = self.conn.identity.find_domain(domain_name_or_id) 116 | 117 | if not domain: 118 | self.exit_json(changed=False, projects=[]) 119 | 120 | filters['domain_id'] = domain.id 121 | 122 | projects = self.conn.search_projects(name_or_id=self.params['name'], 123 | filters=filters) 124 | 125 | self.exit_json(changed=False, 126 | projects=[p.to_dict(computed=False) for p in projects]) 127 | 128 | 129 | def main(): 130 | module = IdentityProjectInfoModule() 131 | module() 132 | 133 | 134 | if __name__ == '__main__': 135 | main() 136 | -------------------------------------------------------------------------------- /ci/roles/quota/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - module_defaults: 3 | group/openstack.cloud.openstack: 4 | cloud: "{{ cloud }}" 5 | name: "{{ test_project }}" 6 | # Backward compatibility with Ansible 2.9 7 | openstack.cloud.project: 8 | cloud: "{{ cloud }}" 9 | name: "{{ test_project }}" 10 | openstack.cloud.quota: 11 | cloud: "{{ cloud }}" 12 | name: "{{ test_project }}" 13 | block: 14 | - name: Create test project 15 | openstack.cloud.project: 16 | state: present 17 | 18 | - name: Clear quotas before tests 19 | openstack.cloud.quota: 20 | state: absent 21 | register: default_quotas 22 | 23 | - name: Set network quota 24 | openstack.cloud.quota: "{{ test_network_quota }}" 25 | register: quotas 26 | 27 | - name: Assert changed 28 | assert: 29 | that: quotas is changed 30 | 31 | - name: Assert field values 32 | assert: 33 | that: quotas.quotas.network[item.key] == item.value 34 | loop: "{{ test_network_quota | dict2items }}" 35 | 36 | - name: Set network quota again 37 | openstack.cloud.quota: "{{ test_network_quota }}" 38 | register: quotas 39 | 40 | - name: Assert not changed 41 | assert: 42 | that: quotas is not changed 43 | 44 | - name: Set volume quotas 45 | openstack.cloud.quota: "{{ test_volume_quota }}" 46 | register: quotas 47 | 48 | - name: Assert changed 49 | assert: 50 | that: quotas is changed 51 | 52 | - name: Assert field values 53 | assert: 54 | that: quotas.quotas.volume[item.key] == item.value 55 | loop: "{{ test_volume_quota | dict2items }}" 56 | 57 | - name: Set volume quotas again 58 | openstack.cloud.quota: "{{ test_volume_quota }}" 59 | register: quotas 60 | 61 | - name: Assert not changed 62 | assert: 63 | that: quotas is not changed 64 | 65 | - name: Set compute quotas 66 | openstack.cloud.quota: "{{ test_compute_quota }}" 67 | register: quotas 68 | 69 | - name: Assert changed 70 | assert: 71 | that: quotas is changed 72 | 73 | - name: Assert field values 74 | assert: 75 | that: quotas.quotas.compute[item.key] == item.value 76 | loop: "{{ test_compute_quota | dict2items }}" 77 | 78 | - name: Set compute quotas again 79 | openstack.cloud.quota: "{{ test_compute_quota }}" 80 | register: quotas 81 | 82 | - name: Unset all quotas 83 | openstack.cloud.quota: 84 | state: absent 85 | register: quotas 86 | 87 | - name: Assert defaults restore 88 | assert: 89 | that: quotas.quotas == default_quotas.quotas 90 | 91 | - name: Set all quotas at once 92 | openstack.cloud.quota: 93 | "{{ [test_network_quota, test_volume_quota, test_compute_quota] | combine }}" 94 | register: quotas 95 | 96 | - name: Assert changed 97 | assert: 98 | that: quotas is changed 99 | 100 | - name: Assert volume values 101 | assert: 102 | that: quotas.quotas.volume[item.key] == item.value 103 | loop: "{{ test_volume_quota | dict2items }}" 104 | 105 | - name: Assert network values 106 | assert: 107 | that: quotas.quotas.network[item.key] == item.value 108 | loop: "{{ test_network_quota | dict2items }}" 109 | 110 | - name: Assert compute values 111 | assert: 112 | that: quotas.quotas.compute[item.key] == item.value 113 | loop: "{{ test_compute_quota | dict2items }}" 114 | 115 | - name: Set all quotas at once again 116 | openstack.cloud.quota: 117 | "{{ [test_network_quota, test_volume_quota, test_compute_quota] | combine }}" 118 | register: quotas 119 | 120 | - name: Assert not changed 121 | assert: 122 | that: quotas is not changed 123 | 124 | - name: Unset all quotas 125 | openstack.cloud.quota: 126 | state: absent 127 | register: quotas 128 | 129 | - name: Delete test project 130 | openstack.cloud.project: 131 | state: absent 132 | 133 | - import_tasks: loadbalancer.yml 134 | tags: 135 | - loadbalancer 136 | 137 | --------------------------------------------------------------------------------