mirror of
https://opendev.org/openstack/ansible-collections-openstack.git
synced 2026-03-27 05:53:02 +00:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e9a6f26c2 | ||
|
|
8dfcd17731 | ||
|
|
39bb4909ee | ||
|
|
ce60e71bde | ||
|
|
b579d03968 | ||
|
|
8743f24c4b | ||
|
|
7a9837dfb5 | ||
|
|
26b53e78b2 | ||
|
|
b8c2310963 | ||
|
|
915a78d7af | ||
|
|
60c39d495f | ||
|
|
2052a47324 | ||
|
|
3ecd3b6e64 | ||
|
|
c4a296c07c | ||
|
|
6b58d28a4e | ||
|
|
620956c61d | ||
|
|
970fb2489c | ||
|
|
8cc678acc1 | ||
|
|
75558c5c2e | ||
|
|
b4bde6af5c | ||
|
|
b935f21f44 | ||
|
|
3c047406dc | ||
|
|
be8965c7fc | ||
|
|
acf64a1f72 | ||
|
|
a4894337d4 | ||
|
|
0b0a80796f | ||
|
|
c63ff6fbc8 | ||
|
|
4f8f6ffaf4 | ||
|
|
0204bbeede | ||
|
|
5626a8d4c9 | ||
|
|
a3bb143f34 | ||
|
|
9c16ee4df3 | ||
|
|
a70a4c3424 | ||
|
|
708ed756ca | ||
|
|
c83884e5c8 | ||
|
|
655ed21ffa | ||
|
|
e64211213a | ||
|
|
39676b664a | ||
|
|
220f2b7dca | ||
|
|
0af7a252bd | ||
|
|
e8f9457893 | ||
|
|
29831685d8 | ||
|
|
82311440b5 | ||
|
|
57012cbaa3 | ||
|
|
5b453c62d1 | ||
|
|
8e1f1d6475 | ||
|
|
4b9e2295e0 |
263
.zuul.yaml
263
.zuul.yaml
@@ -1,9 +1,16 @@
|
||||
# yamllint disable
|
||||
---
|
||||
# Keep this file in sync between branches to avoid issues e.g. with job scheduling. Zuul CI will search in master branch
|
||||
# first when collecting job variants during job freeze which can have unwanted side effects. For example, when parent
|
||||
# job *-base has been changed in stable/1.0.0 branch, Zuul could still use *-base variants from master branch during job
|
||||
# freeze on child jobs such as *-ussuri-ansible-2.11 etc.
|
||||
# Keep parent jobs in sync between branches to avoid issues e.g. with job scheduling. Zuul CI will search in master
|
||||
# branch first when collecting job variants during job freeze which can have unwanted side effects. For example, when
|
||||
# parent job *-base has been changed in stable/1.0.0 branch, Zuul could still use *-base variants from master branch
|
||||
# during job freeze on child jobs such as *-ussuri-ansible-2.11 etc.
|
||||
#
|
||||
# Do not share job definitions with the job.branches attribute across multiple branches. Do not define jobs which are
|
||||
# specific to other branches, except for parent jobs which are shared across branches. For example, to not add a job
|
||||
# which is specific for the stable/1.0.0 branch to the .zuul.yaml in master branch. In particular do not use the
|
||||
# job.branches directive on a job which will be copied to multiple branches. When you have multiple copies of a job with
|
||||
# the job.branches attribute, Zuul CI could pick any of the job definitions which might not be the one you expected.
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-base
|
||||
parent: openstacksdk-functional-devstack
|
||||
@@ -46,14 +53,6 @@
|
||||
extensions_to_txt:
|
||||
log: true
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
branches: master
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
using master of openstacksdk with latest ansible release
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-octavia-base
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
@@ -92,15 +91,6 @@
|
||||
OCTAVIA_AMP_IMAGE_SIZE: 3
|
||||
OCTAVIA_AMP_IMAGE_NAME: "test-only-amphora-x64-haproxy-ubuntu-bionic"
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-octavia
|
||||
parent: ansible-collections-openstack-functional-devstack-octavia-base
|
||||
branches: master
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
with Octavia plugin enabled, using latest releases of openstacksdk
|
||||
and latest ansible release. Run it only on Load Balancer changes.
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-octavia
|
||||
parent: ansible-collections-openstack-functional-devstack-octavia-base
|
||||
@@ -119,20 +109,6 @@
|
||||
vars:
|
||||
tox_constraints_file: '{{ ansible_user_dir }}/{{ zuul.project.src_dir }}/tests/constraints-openstacksdk-0.x.x.txt'
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-releases
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
branches: master
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
using latest releases of openstacksdk and latest ansible release
|
||||
# OpenStack SDK 1.*.* has not been released to PyPI yet
|
||||
voting: false
|
||||
vars:
|
||||
# Uncomment once OpenStack SDK 1.*.* has been released to PyPI
|
||||
# tox_constraints_file: '{{ ansible_user_dir }}/{{ zuul.project.src_dir }}/tests/constraints-openstacksdk-1.x.x.txt'
|
||||
tox_install_siblings: false
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-releases
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
@@ -154,20 +130,6 @@
|
||||
tox_install_siblings: false
|
||||
|
||||
# Job with Ansible 2.9 for checking backward compatibility
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-ansible-2.9
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
branches: master
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
using master of openstacksdk and stable 2.9 branch of ansible
|
||||
voting: false
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.9
|
||||
vars:
|
||||
tox_envlist: ansible-2.9
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-ansible-2.9
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
@@ -187,20 +149,6 @@
|
||||
vars:
|
||||
tox_envlist: ansible-2.9
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-ansible-2.11
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
branches: master
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
using master of openstacksdk and stable 2.12 branch of ansible
|
||||
voting: false
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.11
|
||||
vars:
|
||||
tox_envlist: ansible-2.11
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-ansible-2.11
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
@@ -220,20 +168,6 @@
|
||||
vars:
|
||||
tox_envlist: ansible-2.11
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-ansible-2.12
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
branches: master
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
using master of openstacksdk and stable 2.12 branch of ansible
|
||||
voting: false
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.12
|
||||
vars:
|
||||
tox_envlist: ansible-2.12
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-ansible-2.12
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
@@ -253,18 +187,6 @@
|
||||
vars:
|
||||
tox_envlist: ansible-2.12
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-ansible-devel
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
branches: master
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
using master of openstacksdk and devel branch of ansible
|
||||
voting: false
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: devel
|
||||
|
||||
# Stable branches tests
|
||||
|
||||
- job:
|
||||
@@ -417,37 +339,6 @@
|
||||
vars:
|
||||
tox_envlist: ansible-2.11
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-queens-ansible-2.11
|
||||
# Do not inherit from any parent job which does not run for branch stable/1.0.0 because Zuul would dismiss this job
|
||||
# when collecting parent job variants. For example, when job.branches is set to master in a parent job, then Zuul
|
||||
# will not match that job when it collects job variants.
|
||||
#
|
||||
# Do not inherit from any parent job which sets job.required-projects.override-checkout on openstack/devstack
|
||||
# because Zuul would use that git ref instead of stable branch defined below to checkout projects of parent devstack
|
||||
# jobs when collecting variants for parent jobs.
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
description: |
|
||||
Run openstack collections functional tests against a queens devstack
|
||||
using train branch of openstacksdk and stable 2.11 branch of ansible
|
||||
branches: stable/1.0.0
|
||||
override-checkout: stable/queens
|
||||
# job.override-checkout will not override job.required-projects.override-checkout in parent jobs
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.11
|
||||
- # Choose parent devstack job from stable/1.0.0 branch instead of non-existing stable/queens branch
|
||||
name: openstack/ansible-collections-openstack
|
||||
override-checkout: stable/1.0.0
|
||||
- # Choose parent devstack job from stable/queens branch
|
||||
name: openstack/devstack
|
||||
override-checkout: stable/queens
|
||||
- name: openstack/openstacksdk
|
||||
# Run queens with highest possible py2 version of SDK
|
||||
override-checkout: stable/train
|
||||
vars:
|
||||
tox_envlist: ansible-2.11
|
||||
|
||||
# Linters
|
||||
- job:
|
||||
name: openstack-tox-linters-ansible
|
||||
@@ -488,109 +379,109 @@
|
||||
python_version: 3.8
|
||||
bindep_profile: test py38
|
||||
|
||||
- job:
|
||||
name: openstack-tox-linters-ansible-2.9
|
||||
parent: openstack-tox-linters-ansible
|
||||
nodeset: ubuntu-bionic
|
||||
description: |
|
||||
Run openstack collections linter tests using the 2.9 branch of ansible
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.9
|
||||
vars:
|
||||
tox_envlist: linters-2.9
|
||||
|
||||
# Cross-checks with other projects
|
||||
- job:
|
||||
name: bifrost-collections-src
|
||||
parent: bifrost-integration-tinyipa-ubuntu-focal
|
||||
override-checkout: stable/yoga
|
||||
# job.override-checkout will not override job.required-projects.override-checkout in parent jobs
|
||||
required-projects:
|
||||
- openstack/ansible-collections-openstack
|
||||
- # always use master branch when collecting parent job variants, refer to git blame for rationale.
|
||||
- # always use existing branch when collecting parent job variants, refer to git blame for rationale.
|
||||
name: openstack/bifrost
|
||||
override-checkout: master
|
||||
# Yoga has the latest SDK release of the 0.*.* series atm
|
||||
override-checkout: stable/yoga
|
||||
- job:
|
||||
name: bifrost-keystone-collections-src
|
||||
parent: bifrost-integration-tinyipa-keystone-ubuntu-focal
|
||||
override-checkout: stable/yoga
|
||||
# job.override-checkout will not override job.required-projects.override-checkout in parent jobs
|
||||
required-projects:
|
||||
- openstack/ansible-collections-openstack
|
||||
- # always use master branch when collecting parent job variants, refer to git blame for rationale.
|
||||
- # always use existing branch when collecting parent job variants, refer to git blame for rationale.
|
||||
name: openstack/bifrost
|
||||
override-checkout: master
|
||||
# Yoga has the latest SDK release of the 0.*.* series atm
|
||||
override-checkout: stable/yoga
|
||||
|
||||
# TripleO jobs
|
||||
- job:
|
||||
name: tripleo-ci-centos-8-standalone-osa
|
||||
parent: tripleo-ci-base-standalone-centos-8
|
||||
vars:
|
||||
featureset: '052'
|
||||
name: tripleo-ci-centos-8-standalone-osa-pre-zed
|
||||
parent: tripleo-ci-centos-8-standalone-build
|
||||
# Do not restrict branches in base jobs because else Zuul would not find a matching
|
||||
# parent job variant during job freeze when child jobs are on other branches.
|
||||
vars: &tripleo_vars
|
||||
consumer_job: false
|
||||
build_container_images: true
|
||||
required-projects:
|
||||
- openstack/ansible-collections-openstack
|
||||
# Define branch_override in child jobs, e.g. branch_override: stable/yoga
|
||||
force_non_periodic: true
|
||||
required-projects: &tripleo_required_projects
|
||||
- # Required for TripleO Quickstart to install current patchset of the collection
|
||||
# Ref.: https://opendev.org/openstack/tripleo-quickstart/src/commit/b48d869e14de40444d69a107a0b718b5f721e912/quickstart.sh#L123
|
||||
openstack/ansible-collections-openstack
|
||||
- # always use master branch when collecting parent job variants, refer to git blame for rationale.
|
||||
name: openstack/tripleo-ci
|
||||
override-checkout: master
|
||||
irrelevant-files: &irr_files
|
||||
irrelevant-files: &tripleo_irrelevant_files
|
||||
- .*molecule.*
|
||||
- ^.*\.md$
|
||||
- ^.*\.rst$
|
||||
- ^docs/.*$
|
||||
- ^contrib/.*$
|
||||
- ^changelogs/.*$
|
||||
- ^contrib/.*$
|
||||
- ^docs/.*$
|
||||
- ^meta/.*$
|
||||
- ^tests/.*$
|
||||
- ^tools/.*$
|
||||
- ^requirements.*$
|
||||
- ^setup.*$
|
||||
- ^tests/.*$
|
||||
- ^tools/.*$
|
||||
- tox.ini
|
||||
# Run only on files used in TripleO
|
||||
files: &ooo_files
|
||||
files: &tripleo_files
|
||||
- ^.zuul.yaml$
|
||||
- ^plugins/module_utils/openstack.*$
|
||||
- ^plugins/modules/catalog_service.*$
|
||||
- ^plugins/modules/compute_flavor.*$
|
||||
- ^plugins/modules/endpoint.*$
|
||||
- ^plugins/modules/identity_domain.*$
|
||||
- ^plugins/modules/identity_domain_info.*$
|
||||
- ^plugins/modules/identity_role.*$
|
||||
- ^plugins/modules/identity_user.*$
|
||||
- ^plugins/modules/image.*$
|
||||
- ^plugins/modules/keypair.*$
|
||||
- ^plugins/modules/network.*$
|
||||
- ^plugins/modules/project.*$
|
||||
- ^plugins/modules/role_assignment.*$
|
||||
- ^plugins/modules/router.*$
|
||||
- ^plugins/modules/stack.*$
|
||||
- ^plugins/module_utils/openstack.*$
|
||||
- ^plugins/modules/subnet.*$
|
||||
|
||||
- job:
|
||||
name: tripleo-ci-centos-9-standalone-osa
|
||||
parent: tripleo-ci-centos-8-standalone-osa
|
||||
nodeset: single-centos-9-node
|
||||
branches: ^(stable/1.0.0|master).*$
|
||||
override-checkout: master
|
||||
vars:
|
||||
containers_base_image: quay.io/centos/centos:stream9
|
||||
consumer_job: false
|
||||
build_container_images: true
|
||||
branch_override: master
|
||||
files: *ooo_files
|
||||
irrelevant-files: *irr_files
|
||||
name: tripleo-ci-centos-9-standalone-osa-pre-zed
|
||||
parent: tripleo-ci-centos-9-standalone-build
|
||||
# Do not restrict branches in base jobs because else Zuul would not find a matching
|
||||
# parent job variant during job freeze when child jobs are on other branches.
|
||||
vars: *tripleo_vars
|
||||
required-projects: *tripleo_required_projects
|
||||
irrelevant-files: *tripleo_irrelevant_files
|
||||
files: *tripleo_files
|
||||
|
||||
- job:
|
||||
name: tripleo-ci-centos-8-standalone-train-osa
|
||||
parent: tripleo-ci-centos-8-standalone-osa
|
||||
name: tripleo-ci-centos-8-standalone-osa-train
|
||||
parent: tripleo-ci-centos-8-standalone-osa-pre-zed
|
||||
branches: stable/1.0.0
|
||||
override-checkout: stable/train
|
||||
vars:
|
||||
branch_override: stable/train
|
||||
|
||||
- job:
|
||||
name: tripleo-ci-centos-8-standalone-wallaby-osa
|
||||
parent: tripleo-ci-centos-8-standalone-osa
|
||||
name: tripleo-ci-centos-8-standalone-osa-wallaby
|
||||
parent: tripleo-ci-centos-8-standalone-osa-pre-zed
|
||||
branches: stable/1.0.0
|
||||
override-checkout: stable/wallaby
|
||||
vars:
|
||||
branch_override: stable/wallaby
|
||||
|
||||
- job:
|
||||
name: tripleo-ci-centos-9-standalone-wallaby-osa
|
||||
parent: tripleo-ci-centos-9-standalone-osa
|
||||
name: tripleo-ci-centos-9-standalone-osa-wallaby
|
||||
parent: tripleo-ci-centos-9-standalone-osa-pre-zed
|
||||
branches: stable/1.0.0
|
||||
override-checkout: stable/wallaby
|
||||
vars:
|
||||
@@ -625,16 +516,10 @@
|
||||
- tox-pep8
|
||||
- openstack-tox-linters-ansible-devel
|
||||
- openstack-tox-linters-ansible-2.12
|
||||
- openstack-tox-linters-ansible-2.9
|
||||
- ansible-collections-openstack-functional-devstack:
|
||||
- ansible-collections-openstack-functional-devstack-releases:
|
||||
dependencies: &deps_unit_lint
|
||||
- tox-pep8
|
||||
- openstack-tox-linters-ansible-2.9
|
||||
- openstack-tox-linters-ansible-2.12
|
||||
voting: false
|
||||
|
||||
- ansible-collections-openstack-functional-devstack-releases:
|
||||
dependencies: *deps_unit_lint
|
||||
- ansible-collections-openstack-functional-devstack-ansible-2.9:
|
||||
dependencies: *deps_unit_lint
|
||||
- ansible-collections-openstack-functional-devstack-ansible-2.12:
|
||||
@@ -659,18 +544,9 @@
|
||||
dependencies: *deps_unit_lint
|
||||
irrelevant-files: *ignore_files
|
||||
|
||||
- tripleo-ci-centos-8-standalone-wallaby-osa:
|
||||
- tripleo-ci-centos-8-standalone-osa-wallaby:
|
||||
dependencies: *deps_unit_lint
|
||||
|
||||
- tripleo-ci-centos-8-standalone-train-osa:
|
||||
voting: false
|
||||
dependencies: *deps_unit_lint
|
||||
|
||||
- tripleo-ci-centos-9-standalone-osa:
|
||||
voting: false
|
||||
dependencies: *deps_unit_lint
|
||||
|
||||
- tripleo-ci-centos-9-standalone-wallaby-osa:
|
||||
- tripleo-ci-centos-9-standalone-osa-wallaby:
|
||||
voting: false
|
||||
dependencies: *deps_unit_lint
|
||||
|
||||
@@ -678,8 +554,6 @@
|
||||
jobs:
|
||||
- tox-pep8
|
||||
- openstack-tox-linters-ansible-2.12
|
||||
- openstack-tox-linters-ansible-2.9
|
||||
# - ansible-collections-openstack-functional-devstack
|
||||
- ansible-collections-openstack-functional-devstack-releases
|
||||
# - ansible-collections-openstack-functional-devstack-ansible-2.9
|
||||
# - ansible-collections-openstack-functional-devstack-ansible-2.12
|
||||
@@ -687,14 +561,12 @@
|
||||
- ansible-collections-openstack-functional-devstack-xena-ansible-2.12
|
||||
- ansible-collections-openstack-functional-devstack-train-ansible-2.11
|
||||
- ansible-collections-openstack-functional-devstack-octavia
|
||||
- tripleo-ci-centos-8-standalone-wallaby-osa
|
||||
- tripleo-ci-centos-8-standalone-osa-wallaby
|
||||
|
||||
periodic:
|
||||
jobs:
|
||||
- openstack-tox-linters-ansible-devel
|
||||
- openstack-tox-linters-ansible-2.12
|
||||
- openstack-tox-linters-ansible-2.9
|
||||
- ansible-collections-openstack-functional-devstack
|
||||
- ansible-collections-openstack-functional-devstack-releases
|
||||
- ansible-collections-openstack-functional-devstack-ansible-2.9
|
||||
- ansible-collections-openstack-functional-devstack-ansible-2.12
|
||||
@@ -703,21 +575,18 @@
|
||||
- ansible-collections-openstack-functional-devstack-wallaby-ansible-2.12
|
||||
- ansible-collections-openstack-functional-devstack-victoria-ansible-2.12
|
||||
- ansible-collections-openstack-functional-devstack-train-ansible-2.11
|
||||
- ansible-collections-openstack-functional-devstack-queens-ansible-2.11
|
||||
- bifrost-collections-src
|
||||
- bifrost-keystone-collections-src
|
||||
- ansible-collections-openstack-functional-devstack-octavia
|
||||
- tripleo-ci-centos-9-standalone-wallaby-osa
|
||||
- tripleo-ci-centos-9-standalone-osa
|
||||
- tripleo-ci-centos-8-standalone-train-osa
|
||||
- tripleo-ci-centos-8-standalone-wallaby-osa
|
||||
- tripleo-ci-centos-9-standalone-osa-wallaby
|
||||
- tripleo-ci-centos-8-standalone-osa-wallaby
|
||||
|
||||
experimental:
|
||||
jobs:
|
||||
- tripleo-ci-centos-8-standalone-osa-train
|
||||
- ansible-collections-openstack-functional-devstack-ansible-2.11
|
||||
- ansible-collections-openstack-functional-devstack-victoria-ansible-2.12
|
||||
- ansible-collections-openstack-functional-devstack-ussuri-ansible-2.11
|
||||
- ansible-collections-openstack-functional-devstack-queens-ansible-2.11
|
||||
|
||||
tag:
|
||||
jobs:
|
||||
|
||||
@@ -5,6 +5,68 @@ Openstack Cloud Ansilbe modules Release Notes
|
||||
.. contents:: Topics
|
||||
|
||||
|
||||
v1.9.1
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Bugfix in keypair module
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- Do not remove trailing spaces when reading public key in keypair module
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
|
||||
- For compatibility with OpenStack SDK >= 0.99.0 use Ansible OpenStack collection 2.0.0 or later which is currently under development.
|
||||
- Release series 1.x.x of this collection is compatible to OpenStack SDK prior to 0.99.0 only.
|
||||
|
||||
v1.9.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
This release will enforce openstacksdk<0.99.0, has a dozen modules refactored and several bugs fixed.
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- Added support for specifying a maximum version of the OpenStack SDK
|
||||
- Constrain filters in compute_service_info to SDK >= 0.53.0
|
||||
- Drop username from return values of identity_user_info
|
||||
- Fix logic in routers_info
|
||||
- Fixed return value disable{d,s}_reason in compute_service_info module
|
||||
- Fixed return values in compute_service_info module again
|
||||
- Follow up to bump of minimum required OpenStack SDK release to SDK 0.36.0 (Train)
|
||||
- Lowered maximum OpenStack SDK version to 0.98.999
|
||||
- Move dns zone info to use proxy layer
|
||||
- Refactored catalog_service module
|
||||
- Refactored endpoint module
|
||||
- Refactored host_aggregate module
|
||||
- Refactored identity_domain_info module
|
||||
- Refactored identity_group_info module
|
||||
- Refactored identity_role module
|
||||
- Refactored identity_role_info module
|
||||
- Refactored identity_user module
|
||||
- Refactored identity_user_info module
|
||||
- Refactored image_info module
|
||||
- Refactored keypair_info module
|
||||
- Refactored recordset module
|
||||
- Refactored role_assignment module
|
||||
- Set owner in image module
|
||||
- Support description in sg-rule creation
|
||||
- Warn users about us breaking backward compatibility
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
|
||||
- For compatibility with OpenStack SDK >= 0.99.0 use Ansible OpenStack collection 2.0.0 or later which is currently under development.
|
||||
- Release series 1.x.x of this collection is compatible to OpenStack SDK prior to 0.99.0 only.
|
||||
|
||||
v1.8.0
|
||||
======
|
||||
|
||||
|
||||
37
README.md
37
README.md
@@ -2,11 +2,44 @@
|
||||
|
||||
# Ansible Collection: openstack.cloud
|
||||
|
||||
|
||||
This repo hosts the `openstack.cloud` Ansible Collection.
|
||||
|
||||
The collection includes the Openstack modules and plugins supported by Openstack community to help the management of Openstack infrastructure.
|
||||
|
||||
## Breaking backward compatibility :warning:
|
||||
|
||||
Dear contributors and users of the Ansible OpenStack collection!
|
||||
Our codebase has been split into two separate release series:
|
||||
|
||||
* `2.x.x` releases of Ansible OpenStack collection are compatible with OpenStack SDK `1.x.x` and its release candidates
|
||||
`0.99.x` *only* (OpenStack Zed and later). Our `master` branch tracks our `2.x.x` releases.
|
||||
* `1.x.x` releases of Ansible OpenStack collection are compatible with OpenStack SDK `0.x.x` prior to `0.99.0` *only*
|
||||
(OpenStack Yoga and earlier). Our `stable/1.0.0` branch tracks our `1.x.x` releases.
|
||||
|
||||
Both branches will be developed in parallel for the time being. Patches from `master` will be backported to
|
||||
`stable/1.0.0` on a best effort basis but expect new features to be introduced in our `master` branch only.
|
||||
Contributions are welcome for both branches!
|
||||
Differences between both branches are mainly renamed and sometimes dropped module return values. We try to keep our
|
||||
module parameters backward compatible by offering aliases but e.g. the semantics of `filters` parameters in `*_info`
|
||||
modules have changed due to updates in the OpenStack SDK.
|
||||
|
||||
Our decision to break backward compatibility was not taken lightly. OpenStack SDK's first major release (`1.0.0` and its
|
||||
release candidates `0.99.x`) has streamlined and improved large parts of its codebase. For example, its Connection
|
||||
interface now consistently uses the Resource interfaces under the hood. This required breaking changes from older SDK
|
||||
releases though. The Ansible OpenStack collection is heavily based on OpenStack SDK. With OpenStack SDK becoming
|
||||
backward incompatible, so does our Ansible OpenStack collection. We simply lack the devpower to maintain a backward
|
||||
compatible interface in Ansible OpenStack collection across several SDK releases.
|
||||
|
||||
Our first `2.0.0` release is currently under development and we still have a long way to go. If you use modules of the
|
||||
Ansible OpenStack collection and want to join us in porting them to the upcoming OpenStack SDK, please contact us!
|
||||
Ping Jakob Meng <mail@jakobmeng.de> (jm1) or Rafael Castillo <rcastill@redhat.com> (rcastillo) and we will give you a
|
||||
quick introduction. We are also hanging around on `irc.oftc.net/#openstack-ansible-sig` and `irc.oftc.net/#oooq` 😎
|
||||
|
||||
We have extensive documentation on [why, what and how we are adopting and reviewing the new modules](
|
||||
https://hackmd.io/szgyWa5qSUOWw3JJBXLmOQ?view), [how to set up a working DevStack environment for hacking on the
|
||||
collection](https://hackmd.io/PI10x-iCTBuO09duvpeWgQ?view) and, most importantly, [a list of modules where we are
|
||||
coordinating our porting efforts](https://hackmd.io/7NtovjRkRn-tKraBXfz9jw?view).
|
||||
|
||||
## Installation and Usage
|
||||
|
||||
### Installing dependencies
|
||||
@@ -15,7 +48,7 @@ For using the Openstack Cloud collection firstly you need to install `ansible` a
|
||||
For example with pip:
|
||||
|
||||
```bash
|
||||
pip install "ansible>=2.9" "openstacksdk>=0.36"
|
||||
pip install "ansible>=2.9" "openstacksdk>=0.36,<0.99.0"
|
||||
```
|
||||
|
||||
OpenStackSDK has to be available to Ansible and to the Python interpreter on the host, where Ansible executes the module (target host).
|
||||
|
||||
@@ -332,7 +332,7 @@ releases:
|
||||
- Fix assertion after stack deletion
|
||||
- Handle aggregate host list set to None
|
||||
- Reenabled check-import.sh which tests imports to Ansible Galaxy
|
||||
- Remove old, unsupported parameters from documentation in image_info module
|
||||
- Remove old, unsupported parameters from documentation in image_info module
|
||||
- Router - Remove unneeded 'filter' parameter
|
||||
- Updated return value docs of compute_service_info module
|
||||
release_summary: Subnet pool module and bugfixes
|
||||
@@ -341,3 +341,51 @@ releases:
|
||||
name: subnet_pool
|
||||
namespace: ''
|
||||
release_date: '2022-04-08'
|
||||
1.9.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- Added support for specifying a maximum version of the OpenStack SDK
|
||||
- Constrain filters in compute_service_info to SDK >= 0.53.0
|
||||
- Drop username from return values of identity_user_info
|
||||
- Fix logic in routers_info
|
||||
- Fixed return value disable{d,s}_reason in compute_service_info module
|
||||
- Fixed return values in compute_service_info module again
|
||||
- Follow up to bump of minimum required OpenStack SDK release to SDK 0.36.0
|
||||
(Train)
|
||||
- Lowered maximum OpenStack SDK version to 0.98.999
|
||||
- Move dns zone info to use proxy layer
|
||||
- Refactored catalog_service module
|
||||
- Refactored endpoint module
|
||||
- Refactored host_aggregate module
|
||||
- Refactored identity_domain_info module
|
||||
- Refactored identity_group_info module
|
||||
- Refactored identity_role module
|
||||
- Refactored identity_role_info module
|
||||
- Refactored identity_user module
|
||||
- Refactored identity_user_info module
|
||||
- Refactored image_info module
|
||||
- Refactored keypair_info module
|
||||
- Refactored recordset module
|
||||
- Refactored role_assignment module
|
||||
- Set owner in image module
|
||||
- Support description in sg-rule creation
|
||||
- Warn users about us breaking backward compatibility
|
||||
known_issues:
|
||||
- For compatibility with OpenStack SDK >= 0.99.0 use Ansible OpenStack collection
|
||||
2.0.0 or later which is currently under development.
|
||||
- Release series 1.x.x of this collection is compatible to OpenStack SDK prior
|
||||
to 0.99.0 only.
|
||||
release_summary: 'This release will enforce openstacksdk<0.99.0, has a dozen
|
||||
modules refactored and several bugs fixed.'
|
||||
release_date: '2022-08-25'
|
||||
1.9.1:
|
||||
changes:
|
||||
bugfixes:
|
||||
- Do not remove trailing spaces when reading public key in keypair module
|
||||
known_issues:
|
||||
- For compatibility with OpenStack SDK >= 0.99.0 use Ansible OpenStack collection
|
||||
2.0.0 or later which is currently under development.
|
||||
- Release series 1.x.x of this collection is compatible to OpenStack SDK prior
|
||||
to 0.99.0 only.
|
||||
release_summary: Bugfix in keypair module
|
||||
release_date: '2022-09-08'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
- hosts: all
|
||||
vars:
|
||||
collection_path: "{{ ansible_user_dir }}/{{ zuul.projects['opendev.org/openstack/ansible-collections-openstack'].src_dir }}"
|
||||
collection_path: "{{ ansible_user_dir }}/{{ zuul.project.src_dir }}"
|
||||
build_collection_path: /tmp/collection_built/
|
||||
ansible_galaxy_path: "~/.local/bin/ansible-galaxy"
|
||||
|
||||
@@ -63,12 +63,21 @@
|
||||
url = {{ ansible_galaxy_info.url }}
|
||||
token = {{ ansible_galaxy_info.token }}
|
||||
|
||||
- name: Get content of galaxy.yml
|
||||
slurp:
|
||||
src: "{{ collection_path }}/galaxy.yml"
|
||||
register: galaxy_vars
|
||||
|
||||
- name: Parse yaml into variable
|
||||
set_fact:
|
||||
galaxy_yaml: "{{ galaxy_vars['content'] | b64decode | from_yaml }}"
|
||||
|
||||
- name: Publish collection to Ansible Galaxy / Automation Hub
|
||||
environment:
|
||||
ANSIBLE_CONFIG: "{{ _ansiblecfg_tmp.path }}"
|
||||
shell: >-
|
||||
{{ ansible_galaxy_path }} collection publish -vvv
|
||||
{{ build_collection_path }}/openstack-cloud-{{ version_tag }}.tar.gz
|
||||
{{ build_collection_path }}/{{ galaxy_yaml.namespace }}-{{ galaxy_yaml.name }}-{{ version_tag }}.tar.gz
|
||||
|
||||
always:
|
||||
- name: Shred ansible-galaxy credentials
|
||||
|
||||
94
ci/roles/catalog_service/tasks/main.yml
Normal file
94
ci/roles/catalog_service/tasks/main.yml
Normal file
@@ -0,0 +1,94 @@
|
||||
---
|
||||
- name: Delete service test
|
||||
openstack.cloud.catalog_service:
|
||||
cloud: "{{ cloud }}"
|
||||
service_type: test
|
||||
name: test
|
||||
state: absent
|
||||
register: service_delete
|
||||
|
||||
- name: Assert changed is set to false
|
||||
assert:
|
||||
that:
|
||||
- not service_delete.changed
|
||||
|
||||
- name: Create a service for test
|
||||
openstack.cloud.catalog_service:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "test_service"
|
||||
state: present
|
||||
service_type: test_type
|
||||
description: "Test service"
|
||||
register: service_test
|
||||
|
||||
- name: Verify returned values
|
||||
assert:
|
||||
that:
|
||||
- item in service_test.service
|
||||
loop:
|
||||
- description
|
||||
- id
|
||||
- enabled
|
||||
- name
|
||||
- service_type
|
||||
- type
|
||||
|
||||
- name: Check if the service test was created successfully
|
||||
openstack.cloud.catalog_service:
|
||||
cloud: "{{ cloud }}"
|
||||
service_type: test
|
||||
name: test
|
||||
register: service_created
|
||||
|
||||
- name: Verify returned values
|
||||
assert:
|
||||
that:
|
||||
- item in service_created.service
|
||||
loop:
|
||||
- description
|
||||
- id
|
||||
- enabled
|
||||
- name
|
||||
- type
|
||||
- service_type
|
||||
|
||||
- name: Update service test
|
||||
openstack.cloud.catalog_service:
|
||||
cloud: "{{ cloud }}"
|
||||
service_type: test
|
||||
description: "A new description"
|
||||
is_enabled: False
|
||||
name: test
|
||||
register: service_test
|
||||
|
||||
- name: Check if description and enabled were updated
|
||||
assert:
|
||||
that:
|
||||
- service_test.service.description == "A new description"
|
||||
- not (service_test.service.enabled|bool)
|
||||
|
||||
- name: Delete service test
|
||||
openstack.cloud.catalog_service:
|
||||
cloud: "{{ cloud }}"
|
||||
service_type: test
|
||||
name: test
|
||||
state: absent
|
||||
register: service_deleted
|
||||
|
||||
- name: Verify if service was deleted
|
||||
assert:
|
||||
that:
|
||||
- service_deleted.changed
|
||||
|
||||
- name: Delete service test again
|
||||
openstack.cloud.catalog_service:
|
||||
cloud: "{{ cloud }}"
|
||||
service_type: test
|
||||
name: test
|
||||
state: absent
|
||||
register: service_deleted
|
||||
|
||||
- name: Assert changed is set to false
|
||||
assert:
|
||||
that:
|
||||
- not service_deleted.changed
|
||||
@@ -18,53 +18,6 @@
|
||||
|
||||
- debug: var=updated_dns_zone
|
||||
|
||||
- name: Create a recordset
|
||||
openstack.cloud.recordset:
|
||||
cloud: "{{ cloud }}"
|
||||
zone: "{{ updated_dns_zone.zone.name }}"
|
||||
name: "{{ recordset_name }}"
|
||||
recordset_type: "a"
|
||||
records: "{{ records }}"
|
||||
register: recordset
|
||||
|
||||
- name: Verify recordset info
|
||||
assert:
|
||||
that:
|
||||
- recordset["recordset"].name == recordset_name
|
||||
- recordset["recordset"].zone_name == dns_zone.zone.name
|
||||
- recordset["recordset"].records == records
|
||||
|
||||
- name: Update a recordset
|
||||
openstack.cloud.recordset:
|
||||
cloud: "{{ cloud }}"
|
||||
zone: "{{ updated_dns_zone.zone.name }}"
|
||||
name: "{{ recordset_name }}"
|
||||
recordset_type: "a"
|
||||
records: "{{ updated_records }}"
|
||||
description: "new test recordset"
|
||||
register: updated_recordset
|
||||
|
||||
- name: Verify recordset info
|
||||
assert:
|
||||
that:
|
||||
- updated_recordset["recordset"].zone_name == dns_zone.zone.name
|
||||
- updated_recordset["recordset"].name == recordset_name
|
||||
- updated_recordset["recordset"].records == updated_records
|
||||
|
||||
- name: Delete recordset
|
||||
openstack.cloud.recordset:
|
||||
cloud: "{{ cloud }}"
|
||||
zone: "{{ updated_dns_zone.zone.name }}"
|
||||
name: "{{ recordset.recordset.name }}"
|
||||
state: absent
|
||||
register: deleted_recordset
|
||||
|
||||
- name: Verify recordset deletion
|
||||
assert:
|
||||
that:
|
||||
- deleted_recordset is successful
|
||||
- deleted_recordset is changed
|
||||
|
||||
- name: Delete dns zone
|
||||
openstack.cloud.dns_zone:
|
||||
cloud: "{{ cloud }}"
|
||||
|
||||
@@ -34,6 +34,14 @@
|
||||
- zone is not changed
|
||||
- zone.zones | length == 1
|
||||
|
||||
- name: Assert keys exist
|
||||
assert:
|
||||
that:
|
||||
- '["action", "attributes", "created_at", "description", "email",
|
||||
"links", "masters", "name", "pool_id", "project_id", "serial",
|
||||
"status", "ttl", "type", "updated_at", "id"] |
|
||||
difference(zone.zones.0.keys()) | length == 0'
|
||||
|
||||
- name: Drop created dns zone
|
||||
openstack.cloud.dns_zone:
|
||||
cloud: "{{ cloud }}"
|
||||
|
||||
65
ci/roles/endpoint/tasks/main.yml
Normal file
65
ci/roles/endpoint/tasks/main.yml
Normal file
@@ -0,0 +1,65 @@
|
||||
---
|
||||
- name: Create a service for compute
|
||||
openstack.cloud.endpoint:
|
||||
cloud: "{{ cloud }}"
|
||||
service: nova
|
||||
endpoint_interface: internal
|
||||
url: http://controller:9292
|
||||
region: RegionOne
|
||||
state: present
|
||||
register: endpoint_test
|
||||
|
||||
- name: Ensure service was created
|
||||
assert:
|
||||
that:
|
||||
- endpoint_test.endpoint.id is defined
|
||||
|
||||
- name: Ensure service have the proper endpoint
|
||||
assert:
|
||||
that:
|
||||
- endpoint_test.endpoint.url == "http://controller:9292"
|
||||
|
||||
- name: Create service for compute again
|
||||
openstack.cloud.endpoint:
|
||||
cloud: "{{ cloud }}"
|
||||
service: nova
|
||||
endpoint_interface: internal
|
||||
url: http://controller:9292
|
||||
region: RegionOne
|
||||
state: present
|
||||
register: endpoint_again
|
||||
|
||||
- name: Ensure changed is false
|
||||
assert:
|
||||
that:
|
||||
- not endpoint_again.changed
|
||||
|
||||
- name: Update endpoint url
|
||||
openstack.cloud.endpoint:
|
||||
cloud: "{{ cloud }}"
|
||||
service: nova
|
||||
endpoint_interface: internal
|
||||
url: http://controller:9393
|
||||
region: RegionOne
|
||||
state: present
|
||||
register: endpoint_updated
|
||||
|
||||
- name: Ensure endpoint was updated
|
||||
assert:
|
||||
that:
|
||||
- endpoint_updated.endpoint.url == "http://controller:9393"
|
||||
|
||||
- name: Delete endpoint
|
||||
openstack.cloud.endpoint:
|
||||
cloud: "{{ cloud }}"
|
||||
service: nova
|
||||
endpoint_interface: internal
|
||||
url: http://controller:9393
|
||||
region: RegionOne
|
||||
state: absent
|
||||
register: endpoint_deleted
|
||||
|
||||
- name: Ensure endpoint was deleted
|
||||
assert:
|
||||
that:
|
||||
- endpoint_deleted.changed
|
||||
10
ci/roles/host_aggregate/defaults/main.yml
Normal file
10
ci/roles/host_aggregate/defaults/main.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
# Parameter deleted has been renamed to is_deleted in openstacksdk 0.52.0,
|
||||
# hence we cannot test with this list here.
|
||||
# Ref.: https://github.com/openstack/openstacksdk/commit/b60915aab3ee0348f3e3cc8aa548f94d2a68b7eb
|
||||
expected_fields:
|
||||
- availability_zone
|
||||
- hosts
|
||||
- id
|
||||
- location
|
||||
- metadata
|
||||
- name
|
||||
99
ci/roles/host_aggregate/tasks/main.yml
Normal file
99
ci/roles/host_aggregate/tasks/main.yml
Normal file
@@ -0,0 +1,99 @@
|
||||
---
|
||||
- name: ensure aggregate doesn't exist before tests
|
||||
openstack.cloud.host_aggregate:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: test_aggregate
|
||||
register: aggregate
|
||||
|
||||
- block:
|
||||
- name: create aggregate
|
||||
openstack.cloud.host_aggregate:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: test_aggregate
|
||||
hosts:
|
||||
- "{{ ansible_hostname }}"
|
||||
register: aggregate
|
||||
|
||||
- name: assert aggregate is changed
|
||||
assert:
|
||||
that: aggregate is changed
|
||||
|
||||
- name: assert aggregate fields
|
||||
assert:
|
||||
that: item in aggregate.aggregate
|
||||
loop: "{{ expected_fields }}"
|
||||
|
||||
- block:
|
||||
- name: recreate aggregate
|
||||
openstack.cloud.host_aggregate:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: test_aggregate
|
||||
hosts:
|
||||
- "{{ ansible_hostname }}"
|
||||
register: aggregate
|
||||
|
||||
- name: assert aggregate is not changed
|
||||
assert:
|
||||
that: aggregate is not changed
|
||||
|
||||
- name: assert aggregate fields
|
||||
assert:
|
||||
that: item in aggregate.aggregate
|
||||
loop: "{{ expected_fields }}"
|
||||
|
||||
- block:
|
||||
- name: update aggregate
|
||||
openstack.cloud.host_aggregate:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: test_aggregate
|
||||
metadata:
|
||||
ssd: "true"
|
||||
hosts:
|
||||
- "{{ ansible_hostname }}"
|
||||
register: aggregate
|
||||
|
||||
- name: assert aggregate is changed
|
||||
assert:
|
||||
that: aggregate is changed
|
||||
|
||||
- name: assert aggregate fields
|
||||
assert:
|
||||
that: item in aggregate.aggregate
|
||||
loop: "{{ expected_fields }}"
|
||||
|
||||
- block:
|
||||
- name: purge hosts
|
||||
openstack.cloud.host_aggregate:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: test_aggregate
|
||||
hosts: []
|
||||
purge_hosts: true
|
||||
register: aggregate
|
||||
|
||||
- name: assert hosts were purged
|
||||
assert:
|
||||
that:
|
||||
- aggregate is changed
|
||||
- aggregate.aggregate.hosts | length == 0
|
||||
|
||||
- name: assert aggregate fields
|
||||
assert:
|
||||
that: item in aggregate.aggregate
|
||||
loop: "{{ expected_fields }}"
|
||||
|
||||
- block:
|
||||
- name: delete aggregate
|
||||
openstack.cloud.host_aggregate:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: test_aggregate
|
||||
register: aggregate
|
||||
|
||||
- name: assert aggregate is changed
|
||||
assert:
|
||||
that: aggregate is changed
|
||||
8
ci/roles/identity_domain_info/defaults/main.yml
Normal file
8
ci/roles/identity_domain_info/defaults/main.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
domain_name: domain_info_test_domain
|
||||
unexistent_domain_name: domain_info_unexistent_domain
|
||||
disabled_domain_name: test_domain_disabled
|
||||
domain_info_fields:
|
||||
- description
|
||||
- id
|
||||
- enabled
|
||||
- name
|
||||
72
ci/roles/identity_domain_info/tasks/main.yml
Normal file
72
ci/roles/identity_domain_info/tasks/main.yml
Normal file
@@ -0,0 +1,72 @@
|
||||
---
|
||||
- block:
|
||||
- name: Ensure domain does not exist
|
||||
openstack.cloud.identity_domain:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ unexistent_domain_name }}"
|
||||
- name: Get unexistent domain
|
||||
openstack.cloud.identity_domain_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ unexistent_domain_name }}"
|
||||
register: domain_info
|
||||
- name: Assert no results returned
|
||||
assert:
|
||||
that: not domain_info.openstack_domains
|
||||
|
||||
|
||||
- block:
|
||||
- name: Ensure domain exists
|
||||
openstack.cloud.identity_domain:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ domain_name }}"
|
||||
description: "test description"
|
||||
register: domain
|
||||
- name: Get domain
|
||||
openstack.cloud.identity_domain_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ domain_name }}"
|
||||
register: domain_info
|
||||
- name: Assert one result exists
|
||||
assert:
|
||||
that: domain_info.openstack_domains | length == 1
|
||||
- name: Assert fields are present
|
||||
assert:
|
||||
that: item in domain_info.openstack_domains[0]
|
||||
loop: "{{ domain_info_fields }}"
|
||||
- name: Assert returned value
|
||||
assert:
|
||||
that:
|
||||
- domain_info.openstack_domains[0].description == domain.domain.description
|
||||
|
||||
- block:
|
||||
- name: Get all domains
|
||||
openstack.cloud.identity_domain_info:
|
||||
cloud: "{{ cloud }}"
|
||||
register: domain_info
|
||||
|
||||
- block:
|
||||
- name: Ensure disabled domain exists
|
||||
openstack.cloud.identity_domain:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ disabled_domain_name }}"
|
||||
enabled: false
|
||||
description: "test description"
|
||||
register: domain
|
||||
- name: Get filtered domains
|
||||
openstack.cloud.identity_domain_info:
|
||||
cloud: "{{ cloud }}"
|
||||
filters:
|
||||
enabled: true
|
||||
register: domain_info
|
||||
- name: Assert at least one result
|
||||
assert:
|
||||
that: domain_info.openstack_domains | length >= 1
|
||||
- name: Assert returned value
|
||||
assert:
|
||||
that: item.enabled == true
|
||||
loop: "{{ domain_info.openstack_domains }}"
|
||||
|
||||
|
||||
74
ci/roles/identity_group_info/tasks/main.yml
Normal file
74
ci/roles/identity_group_info/tasks/main.yml
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
- name: List group by domain_id
|
||||
openstack.cloud.identity_group_info:
|
||||
cloud: "{{ cloud }}"
|
||||
domain: default
|
||||
register: group_domain
|
||||
|
||||
- name: Assert groups were returned
|
||||
assert:
|
||||
that:
|
||||
- group_domain.openstack_groups | length > 0
|
||||
- group_domain.openstack_groups[0].domain_id == 'default'
|
||||
- group_domain.openstack_groups[0].id is defined
|
||||
- group_domain.openstack_groups[0].description is defined
|
||||
- group_domain.openstack_groups[0].name is defined
|
||||
|
||||
- name: List group by domain_id and group
|
||||
openstack.cloud.identity_group_info:
|
||||
cloud: "{{ cloud }}"
|
||||
domain: default
|
||||
name: admins
|
||||
register: groups_info
|
||||
|
||||
- name: Assert groups by domain_id and grouph returned
|
||||
assert:
|
||||
that:
|
||||
- groups_info.openstack_groups | length > 0
|
||||
- groups_info.openstack_groups[0].domain_id == 'default'
|
||||
- groups_info.openstack_groups[0].id is defined
|
||||
- groups_info.openstack_groups[0].description is defined
|
||||
- groups_info.openstack_groups[0].name is defined
|
||||
|
||||
- name: List group by filter
|
||||
openstack.cloud.identity_group_info:
|
||||
cloud: "{{ cloud }}"
|
||||
domain: default
|
||||
filters:
|
||||
name: admins
|
||||
register: groups_filter
|
||||
|
||||
- name: Assert group by filter returned
|
||||
assert:
|
||||
that:
|
||||
- groups_filter.openstack_groups | length > 0
|
||||
- groups_filter.openstack_groups[0].domain_id == 'default'
|
||||
- groups_filter.openstack_groups[0].id is defined
|
||||
- groups_filter.openstack_groups[0].description is defined
|
||||
- groups_filter.openstack_groups[0].name is defined
|
||||
|
||||
- name: Verify returned values of group info
|
||||
assert:
|
||||
that:
|
||||
- item in groups_info.openstack_groups[0]
|
||||
loop:
|
||||
- description
|
||||
- domain_id
|
||||
- id
|
||||
- name
|
||||
|
||||
- name: List group by group name
|
||||
openstack.cloud.identity_group_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: admins
|
||||
register: groups_name
|
||||
|
||||
- name: Assert group by name returned
|
||||
assert:
|
||||
that:
|
||||
- groups_name.openstack_groups | length > 0
|
||||
- groups_name.openstack_groups[0].domain_id == 'default'
|
||||
- groups_name.openstack_groups[0].id is defined
|
||||
- groups_name.openstack_groups[0].description is defined
|
||||
- groups_name.openstack_groups[0].name is defined
|
||||
- groups_name.openstack_groups[0].name == 'admins'
|
||||
5
ci/roles/identity_role/defaults/main.yml
Normal file
5
ci/roles/identity_role/defaults/main.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
role_name: ansible_keystone_role
|
||||
expected_fields:
|
||||
- domain_id
|
||||
- id
|
||||
- name
|
||||
83
ci/roles/identity_role/tasks/main.yml
Normal file
83
ci/roles/identity_role/tasks/main.yml
Normal file
@@ -0,0 +1,83 @@
|
||||
---
|
||||
- name: Cleanup before tests
|
||||
block:
|
||||
- openstack.cloud.identity_role:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ role_name }}"
|
||||
|
||||
- block:
|
||||
- name: Delete unexistent role
|
||||
openstack.cloud.identity_role:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ role_name }}"
|
||||
register: role
|
||||
- name: Assert role didn't change
|
||||
assert:
|
||||
that: role is not changed
|
||||
|
||||
- block:
|
||||
- name: Create keystone role
|
||||
openstack.cloud.identity_role:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ role_name }}"
|
||||
register: role
|
||||
- name: Try to get role
|
||||
openstack.cloud.identity_role_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ role_name }}"
|
||||
register: roles
|
||||
- name: Assert role found
|
||||
assert:
|
||||
that:
|
||||
- roles.openstack_roles | length == 1
|
||||
- name: Assert role changed
|
||||
assert:
|
||||
that: role is changed
|
||||
- name: Assert return fields
|
||||
assert:
|
||||
that: item in role['role']
|
||||
loop: "{{ expected_fields }}"
|
||||
- name: Assert return value
|
||||
assert:
|
||||
that: role['role']['name'] == role_name
|
||||
- name: Assert retrieved values
|
||||
assert:
|
||||
that: roles.openstack_roles[0].name == role_name
|
||||
|
||||
- block:
|
||||
- name: Create existing keystone role
|
||||
openstack.cloud.identity_role:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ role_name }}"
|
||||
register: role
|
||||
- name: Assert role not changed
|
||||
assert:
|
||||
that: role is not changed
|
||||
- name: Assert return fields
|
||||
assert:
|
||||
that: item in role['role']
|
||||
loop: "{{ expected_fields }}"
|
||||
|
||||
- block:
|
||||
- name: Delete keystone role
|
||||
openstack.cloud.identity_role:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ role_name }}"
|
||||
register: role
|
||||
- name: Assert role changed
|
||||
assert:
|
||||
that: role is changed
|
||||
- name: Try to get role
|
||||
openstack.cloud.identity_role_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ role_name }}"
|
||||
register: roles
|
||||
- name: Assert no role found
|
||||
assert:
|
||||
that:
|
||||
- roles.openstack_roles | length == 0
|
||||
62
ci/roles/identity_role_info/tasks/main.yml
Normal file
62
ci/roles/identity_role_info/tasks/main.yml
Normal file
@@ -0,0 +1,62 @@
|
||||
- name: Ensure role does not exist before tests
|
||||
openstack.cloud.identity_role:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: test_role
|
||||
|
||||
- name: Get unexistent role
|
||||
openstack.cloud.identity_role_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: test_role
|
||||
register: roleinfo
|
||||
|
||||
- debug:
|
||||
var: roleinfo
|
||||
|
||||
- name: Assert that no results were returned
|
||||
assert:
|
||||
that: not roleinfo.openstack_roles
|
||||
|
||||
- name: Create keystone role
|
||||
openstack.cloud.identity_role:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: test_role
|
||||
|
||||
- name: Create second role
|
||||
openstack.cloud.identity_role:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: test_role2
|
||||
|
||||
- name: Get role by name
|
||||
openstack.cloud.identity_role_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: test_role
|
||||
register: roleinfo
|
||||
|
||||
- debug:
|
||||
var: roleinfo
|
||||
|
||||
- name: Assert that only one result was returned
|
||||
assert:
|
||||
that: roleinfo.openstack_roles | length == 1
|
||||
|
||||
- name: Assert that roleinfo has fields
|
||||
assert:
|
||||
that: item in roleinfo.openstack_roles[0]
|
||||
loop:
|
||||
- domain_id
|
||||
- id
|
||||
- name
|
||||
|
||||
- name: Post-test cleanup
|
||||
block:
|
||||
- name: Clean up roles
|
||||
openstack.cloud.identity_role:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ item }}"
|
||||
loop:
|
||||
- test_role
|
||||
- test_role2
|
||||
9
ci/roles/identity_user/defaults/main.yml
Normal file
9
ci/roles/identity_user/defaults/main.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
os_identity_user_fields:
|
||||
- default_project_id
|
||||
- description
|
||||
- domain_id
|
||||
- email
|
||||
- enabled
|
||||
- id
|
||||
- name
|
||||
- username
|
||||
197
ci/roles/identity_user/tasks/main.yml
Normal file
197
ci/roles/identity_user/tasks/main.yml
Normal file
@@ -0,0 +1,197 @@
|
||||
---
|
||||
- name: setup
|
||||
block:
|
||||
- name: Delete user before running tests
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ item }}"
|
||||
loop:
|
||||
- ansible_user
|
||||
- ansible_user2
|
||||
register: user
|
||||
|
||||
- block:
|
||||
- name: Delete unexistent user
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_user
|
||||
register: user
|
||||
|
||||
- name: Ensure user was not changed
|
||||
assert:
|
||||
that: user is not changed
|
||||
|
||||
- block:
|
||||
- name: Create a user without a password
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_user
|
||||
email: ansible.user@nowhere.net
|
||||
domain: default
|
||||
default_project: demo
|
||||
register: user
|
||||
|
||||
- name: Ensure user was changed
|
||||
assert:
|
||||
that: user is changed
|
||||
|
||||
- name: Ensure user has fields
|
||||
assert:
|
||||
that: item in user['user']
|
||||
loop: "{{ os_identity_user_fields }}"
|
||||
|
||||
- name: Fail when update_password is always but no password specified
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_user
|
||||
update_password: always
|
||||
email: ansible.user@nowhere.net
|
||||
domain: default
|
||||
default_project: demo
|
||||
register: user
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that: user.msg == "update_password is always but a password value is missing"
|
||||
|
||||
- name: Delete user
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_user
|
||||
|
||||
- block:
|
||||
- name: Create user with a password
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_user
|
||||
password: secret
|
||||
email: ansible.user@nowhere.net
|
||||
update_password: on_create
|
||||
domain: default
|
||||
default_project: demo
|
||||
register: user
|
||||
|
||||
- name: Assert user has fields
|
||||
assert:
|
||||
that: item in user['user']
|
||||
loop: "{{ os_identity_user_fields }}"
|
||||
|
||||
- block:
|
||||
- name: Create identical user
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_user
|
||||
password: secret
|
||||
email: ansible.user@nowhere.net
|
||||
update_password: on_create
|
||||
domain: default
|
||||
default_project: demo
|
||||
register: user
|
||||
|
||||
- name: Assert user was not changed
|
||||
assert:
|
||||
that: user is not changed
|
||||
|
||||
- name: Assert user has fields
|
||||
assert:
|
||||
that: item in user['user']
|
||||
loop: "{{ os_identity_user_fields }}"
|
||||
|
||||
- block:
|
||||
- name: Update user with password
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_user
|
||||
password: secret2
|
||||
email: updated.ansible.user@nowhere.net
|
||||
register: user
|
||||
|
||||
- name: Ensure user was changed
|
||||
assert:
|
||||
that: user is changed
|
||||
|
||||
- name: Ensure user has fields
|
||||
assert:
|
||||
that: item in user['user']
|
||||
loop: "{{ os_identity_user_fields }}"
|
||||
|
||||
- name: Update user without password and update_password set to always
|
||||
block:
|
||||
- openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_user
|
||||
update_password: always
|
||||
email: updated.ansible.user@nowhere.net
|
||||
register: user
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that: user.msg == "update_password is always but a password value is missing"
|
||||
|
||||
- block:
|
||||
- name: Ensure user with update_password set to on_create
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_user
|
||||
update_password: on_create
|
||||
password: secret3
|
||||
email: updated.ansible.user@nowhere.net
|
||||
register: user
|
||||
|
||||
- name: Ensure user was not changed
|
||||
assert:
|
||||
that: user is not changed
|
||||
|
||||
- block:
|
||||
- name: Ensure user with update_password set to always
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_user
|
||||
update_password: always
|
||||
password: secret3
|
||||
email: updated.ansible.user@nowhere.net
|
||||
register: user
|
||||
|
||||
- name: Ensure user was changed
|
||||
assert:
|
||||
that: user is changed
|
||||
|
||||
- block:
|
||||
- name: Create user without a password
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_user2
|
||||
password: secret
|
||||
email: ansible.user2@nowhere.net
|
||||
update_password: on_create
|
||||
domain: default
|
||||
default_project: demo
|
||||
register: user
|
||||
|
||||
- name: Assert user has fields
|
||||
assert:
|
||||
that: item in user['user']
|
||||
loop: "{{ os_identity_user_fields }}"
|
||||
|
||||
- block:
|
||||
- name: Delete user
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_user
|
||||
|
||||
- name: Ensure user was changed
|
||||
assert:
|
||||
that: user is changed
|
||||
9
ci/roles/identity_user_info/defaults/main.yml
Normal file
9
ci/roles/identity_user_info/defaults/main.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
os_expected_user_info_fields:
|
||||
- default_project_id
|
||||
- description
|
||||
- domain_id
|
||||
- email
|
||||
- enabled
|
||||
- id
|
||||
- name
|
||||
- username
|
||||
69
ci/roles/identity_user_info/tasks/main.yml
Normal file
69
ci/roles/identity_user_info/tasks/main.yml
Normal file
@@ -0,0 +1,69 @@
|
||||
- name: Ensure user does not exist before tests
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_user
|
||||
|
||||
- block:
|
||||
- name: Get unexistent user
|
||||
openstack.cloud.identity_user_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: ansible_user
|
||||
register: userinfo
|
||||
- name: Ensure nothing was returned
|
||||
assert:
|
||||
that: not userinfo.openstack_users
|
||||
|
||||
- block:
|
||||
- name: Create user
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_user
|
||||
password: secret
|
||||
email: ansible.user@nowhere.net
|
||||
domain: default
|
||||
default_project: demo
|
||||
register: user
|
||||
- name: Create second user
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_user2
|
||||
password: secret
|
||||
email: ansible.user2@nowhere.net
|
||||
domain: default
|
||||
default_project: demo
|
||||
register: user
|
||||
- name: Get first user info
|
||||
openstack.cloud.identity_user_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: ansible_user
|
||||
register: userinfo
|
||||
- name: Assert only one result exists
|
||||
assert:
|
||||
that: "{{ userinfo.openstack_users | length }} == 1"
|
||||
- name: Assert userinfo has fields
|
||||
assert:
|
||||
that: item in userinfo.openstack_users[0]
|
||||
loop: "{{ os_expected_user_info_fields }}"
|
||||
|
||||
- block:
|
||||
- name: Get all users
|
||||
openstack.cloud.identity_user_info:
|
||||
cloud: "{{ cloud }}"
|
||||
register: userinfo
|
||||
- name: Assert results were returned
|
||||
assert:
|
||||
that: "{{ userinfo.openstack_users | length }} > 0"
|
||||
|
||||
- name: Post-test cleanup
|
||||
block:
|
||||
- name: Ensure users do not exist
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ item }}"
|
||||
loop:
|
||||
- ansible_user
|
||||
- ansible_user2
|
||||
@@ -25,8 +25,8 @@
|
||||
- name: Verify image info
|
||||
assert:
|
||||
that:
|
||||
- "image_info_result.openstack_image.name == image_name"
|
||||
- "image_info_result.openstack_image.tags | sort == image_tags | sort"
|
||||
- "image_info_result.openstack_images[0].name == image_name"
|
||||
- "image_info_result.openstack_images[0].tags | sort == image_tags | sort"
|
||||
|
||||
- name: Delete raw image (defaults)
|
||||
openstack.cloud.image:
|
||||
@@ -57,11 +57,6 @@
|
||||
state: absent
|
||||
name: "{{ image_name }}"
|
||||
|
||||
- name: Delete test image file
|
||||
file:
|
||||
name: "{{ tmp_file.stdout }}"
|
||||
state: absent
|
||||
|
||||
- name: Try to get details of deleted image
|
||||
openstack.cloud.image_info:
|
||||
cloud: "{{ cloud }}"
|
||||
@@ -71,4 +66,83 @@
|
||||
- name: Verify image is deleted
|
||||
assert:
|
||||
that:
|
||||
- not deleted_image_info_result.openstack_image
|
||||
- not deleted_image_info_result.openstack_images
|
||||
|
||||
- name: Create owner project
|
||||
openstack.cloud.project:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: image_owner_project
|
||||
description: Project owning test image
|
||||
domain_id: default
|
||||
enabled: True
|
||||
register: owner_project
|
||||
|
||||
- name: Create raw image (owner by project name)
|
||||
openstack.cloud.image:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ image_name }}"
|
||||
filename: "{{ tmp_file.stdout }}"
|
||||
disk_format: raw
|
||||
tags: "{{ image_tags }}"
|
||||
project: image_owner_project
|
||||
register: image
|
||||
|
||||
- name: Get details of created image (owner by project name)
|
||||
openstack.cloud.image_info:
|
||||
cloud: "{{ cloud }}"
|
||||
image: "{{ image_name }}"
|
||||
register: image_info_result
|
||||
|
||||
- name: Verify image owner (owner by project name)
|
||||
assert:
|
||||
that:
|
||||
- image_info_result.openstack_images[0].owner == owner_project.project.id
|
||||
|
||||
- name: Delete raw image (owner by project name)
|
||||
openstack.cloud.image:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ image_name }}"
|
||||
|
||||
- name: Create raw image (owner by project name and domain name)
|
||||
openstack.cloud.image:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ image_name }}"
|
||||
filename: "{{ tmp_file.stdout }}"
|
||||
disk_format: raw
|
||||
tags: "{{ image_tags }}"
|
||||
project: image_owner_project
|
||||
project_domain: default
|
||||
register: image
|
||||
|
||||
- name: Get details of created image (owner by project name and domain name)
|
||||
openstack.cloud.image_info:
|
||||
cloud: "{{ cloud }}"
|
||||
image: "{{ image_name }}"
|
||||
register: image_info_result
|
||||
|
||||
- name: Verify image owner (owner by project name and domain name)
|
||||
assert:
|
||||
that:
|
||||
- image_info_result.openstack_images[0].owner == owner_project.project.id
|
||||
|
||||
- name: Delete raw image (owner by project name and domain name)
|
||||
openstack.cloud.image:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ image_name }}"
|
||||
|
||||
- name: Delete owner project
|
||||
openstack.cloud.project:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: image_owner_project
|
||||
domain_id: default
|
||||
|
||||
- name: Delete test image file
|
||||
file:
|
||||
name: "{{ tmp_file.stdout }}"
|
||||
state: absent
|
||||
|
||||
23
ci/roles/image_info/defaults/main.yml
Normal file
23
ci/roles/image_info/defaults/main.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
expected_fields:
|
||||
- checksum
|
||||
- container_format
|
||||
- created_at
|
||||
- direct_url
|
||||
- disk_format
|
||||
- file
|
||||
- id
|
||||
- locations
|
||||
- metadata
|
||||
- min_disk
|
||||
- min_ram
|
||||
- name
|
||||
- os_hidden
|
||||
- owner
|
||||
- properties
|
||||
- schema
|
||||
- size
|
||||
- status
|
||||
- tags
|
||||
- updated_at
|
||||
- virtual_size
|
||||
- visibility
|
||||
11
ci/roles/image_info/tasks/main.yml
Normal file
11
ci/roles/image_info/tasks/main.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: List all images # This will list at least the default cirros image of devstack
|
||||
openstack.cloud.image_info:
|
||||
cloud: "{{ cloud }}"
|
||||
register: image_list_result
|
||||
|
||||
- name: Assert fields
|
||||
assert:
|
||||
that:
|
||||
- item in image_list_result.openstack_images.0.keys()
|
||||
loop: "{{ expected_fields }}"
|
||||
@@ -1 +1,11 @@
|
||||
keypair_name: shade_keypair
|
||||
expected_fields:
|
||||
- created_at
|
||||
- fingerprint
|
||||
- id
|
||||
- is_deleted
|
||||
- name
|
||||
- private_key
|
||||
- public_key
|
||||
- type
|
||||
- user_id
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
---
|
||||
- name: Create keypair (non-existing)
|
||||
openstack.cloud.keypair:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ keypair_name }}"
|
||||
state: present
|
||||
register:
|
||||
keypair
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ keypair_name }}"
|
||||
state: present
|
||||
register: keypair
|
||||
|
||||
- name: Get list of keypairs
|
||||
- name: Get list of all keypairs
|
||||
openstack.cloud.keypair_info:
|
||||
cloud: "{{ cloud }}"
|
||||
register: keypairs_all
|
||||
|
||||
- name: Get list of keypairs with filter
|
||||
openstack.cloud.keypair_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ keypair_name }}"
|
||||
@@ -18,17 +22,35 @@
|
||||
that:
|
||||
- keypairs['openstack_keypairs']|length == 1
|
||||
|
||||
- name: Assert fields
|
||||
assert:
|
||||
that:
|
||||
- item in keypairs.openstack_keypairs.0.keys()
|
||||
loop: "{{ expected_fields }}"
|
||||
|
||||
# This assert verifies that Ansible is capable serializing data returned by SDK
|
||||
- name: Ensure private key is returned
|
||||
- name: Ensure public key is returned
|
||||
assert:
|
||||
that:
|
||||
- keypair.key.public_key is defined and keypair.key.public_key
|
||||
|
||||
- name: Create another keypair
|
||||
openstack.cloud.keypair:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ keypair_name }}-2"
|
||||
state: present
|
||||
|
||||
- name: Delete keypair (non-existing)
|
||||
openstack.cloud.keypair:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ keypair_name }}"
|
||||
state: absent
|
||||
cloud: "{{ cloud }}"
|
||||
name: "non-existing"
|
||||
state: absent
|
||||
|
||||
- name: Delete keypair
|
||||
openstack.cloud.keypair:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ keypair_name }}"
|
||||
state: absent
|
||||
|
||||
- name: Get list of keypairs
|
||||
openstack.cloud.keypair_info:
|
||||
@@ -41,18 +63,24 @@
|
||||
that:
|
||||
- keypairs['openstack_keypairs']|length == 0
|
||||
|
||||
- name: Delete another keypair
|
||||
openstack.cloud.keypair:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ keypair_name }}-2"
|
||||
state: absent
|
||||
|
||||
- name: Generate test key file
|
||||
user:
|
||||
name: "{{ ansible_env.USER }}"
|
||||
generate_ssh_key: yes
|
||||
ssh_key_file: .ssh/shade_id_rsa
|
||||
name: "{{ ansible_env.USER }}"
|
||||
generate_ssh_key: yes
|
||||
ssh_key_file: .ssh/shade_id_rsa
|
||||
|
||||
- name: Create keypair (file)
|
||||
openstack.cloud.keypair:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ keypair_name }}"
|
||||
state: present
|
||||
public_key_file: "{{ ansible_env.HOME }}/.ssh/shade_id_rsa.pub"
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ keypair_name }}"
|
||||
state: present
|
||||
public_key_file: "{{ ansible_env.HOME }}/.ssh/shade_id_rsa.pub"
|
||||
|
||||
- name: Get list of keypairs
|
||||
openstack.cloud.keypair_info:
|
||||
@@ -67,9 +95,9 @@
|
||||
|
||||
- name: Delete keypair (file)
|
||||
openstack.cloud.keypair:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ keypair_name }}"
|
||||
state: absent
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ keypair_name }}"
|
||||
state: absent
|
||||
|
||||
- name: Get list of keypairs
|
||||
openstack.cloud.keypair_info:
|
||||
@@ -84,10 +112,10 @@
|
||||
|
||||
- name: Create keypair (key)
|
||||
openstack.cloud.keypair:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ keypair_name }}"
|
||||
state: present
|
||||
public_key: "{{ lookup('file', '~/.ssh/shade_id_rsa.pub') }}"
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ keypair_name }}"
|
||||
state: present
|
||||
public_key: "{{ lookup('file', '~/.ssh/shade_id_rsa.pub') }}"
|
||||
|
||||
- name: Get list of keypairs
|
||||
openstack.cloud.keypair_info:
|
||||
@@ -102,9 +130,9 @@
|
||||
|
||||
- name: Delete keypair (key)
|
||||
openstack.cloud.keypair:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ keypair_name }}"
|
||||
state: absent
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ keypair_name }}"
|
||||
state: absent
|
||||
|
||||
- name: Get list of keypairs
|
||||
openstack.cloud.keypair_info:
|
||||
@@ -119,10 +147,10 @@
|
||||
|
||||
- name: Delete test key pub file
|
||||
file:
|
||||
name: "{{ ansible_env.HOME }}/.ssh/shade_id_rsa.pub"
|
||||
state: absent
|
||||
name: "{{ ansible_env.HOME }}/.ssh/shade_id_rsa.pub"
|
||||
state: absent
|
||||
|
||||
- name: Delete test key pvt file
|
||||
file:
|
||||
name: "{{ ansible_env.HOME }}/.ssh/shade_id_rsa"
|
||||
state: absent
|
||||
name: "{{ ansible_env.HOME }}/.ssh/shade_id_rsa"
|
||||
state: absent
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
role_name: ansible_keystone_role
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
- name: Create keystone role
|
||||
openstack.cloud.identity_role:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ role_name }}"
|
||||
|
||||
- name: List keystone roles
|
||||
openstack.cloud.identity_role_info:
|
||||
cloud: "{{ cloud }}"
|
||||
register: roles
|
||||
|
||||
- name: Check roles
|
||||
assert:
|
||||
that:
|
||||
- roles.openstack_roles | length > 0
|
||||
- "'{{ role_name }}' in (roles.openstack_roles | map(attribute='name') | list)"
|
||||
|
||||
- name: List keystone roles by name
|
||||
openstack.cloud.identity_role_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ role_name}}"
|
||||
register: roles1
|
||||
|
||||
- name: Check roles
|
||||
assert:
|
||||
that:
|
||||
- roles1.openstack_roles | length == 1
|
||||
- roles1.openstack_roles[0]['name'] == role_name
|
||||
|
||||
- name: Delete keystone role
|
||||
openstack.cloud.identity_role:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ role_name }}"
|
||||
@@ -3,20 +3,31 @@
|
||||
- name: Get nova compute services info
|
||||
openstack.cloud.compute_service_info:
|
||||
cloud: "{{ cloud }}"
|
||||
binary: "nova-compute"
|
||||
register: result
|
||||
failed_when: "result.openstack_compute_services | length <= 0"
|
||||
|
||||
- name: Assert fields
|
||||
- name: Assert fields on OpenStack SDK before 0.53
|
||||
when: sdk_version is version(0.53, '<')
|
||||
assert:
|
||||
that:
|
||||
- '["availability_zone", "binary", "disabled_reason", "is_forced_down",
|
||||
"host", "name", "state", "status", "updated_at", "id"] |
|
||||
difference(result.openstack_compute_services.0.keys()) | length == 0'
|
||||
- '["availability_zone", "binary", "disables_reason",
|
||||
"host", "name", "state", "status", "id"] |
|
||||
difference(result.openstack_compute_services.0.keys()) | length == 0'
|
||||
|
||||
- name: Get nova conductor services info
|
||||
openstack.cloud.compute_service_info:
|
||||
cloud: "{{ cloud }}"
|
||||
binary: "nova-conductor"
|
||||
register: result
|
||||
failed_when: "result.openstack_compute_services | length <= 0"
|
||||
- name: Assert fields on OpenStack SDK 0.53 and later
|
||||
when: sdk_version is version(0.53, '>=')
|
||||
assert:
|
||||
that:
|
||||
- '["availability_zone", "binary", "disabled_reason", "is_forced_down",
|
||||
"host", "name", "state", "status", "updated_at", "id"] |
|
||||
difference(result.openstack_compute_services.0.keys()) | length == 0'
|
||||
|
||||
- name: Filter compute services. Supported since OpenStack SDK 0.53.0 (Wallaby).
|
||||
when: sdk_version is version(0.53, '>=')
|
||||
block:
|
||||
- name: Get nova compute services info
|
||||
openstack.cloud.compute_service_info:
|
||||
cloud: "{{ cloud }}"
|
||||
binary: "nova-compute"
|
||||
register: result
|
||||
failed_when: "result.openstack_compute_services | length <= 0"
|
||||
|
||||
19
ci/roles/recordset/defaults/main.yml
Normal file
19
ci/roles/recordset/defaults/main.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
dns_zone_name: test.dns.zone.
|
||||
recordset_name: testrecordset.test.dns.zone.
|
||||
records: ['10.0.0.0']
|
||||
updated_records: ['10.1.1.1']
|
||||
|
||||
recordset_fields:
|
||||
- action
|
||||
- created_at
|
||||
- description
|
||||
- id
|
||||
- links
|
||||
- name
|
||||
- project_id
|
||||
- records
|
||||
- status
|
||||
- ttl
|
||||
- type
|
||||
- zone_id
|
||||
- zone_name
|
||||
112
ci/roles/recordset/tasks/main.yml
Normal file
112
ci/roles/recordset/tasks/main.yml
Normal file
@@ -0,0 +1,112 @@
|
||||
- name: Ensure DNS zone not present before tests
|
||||
openstack.cloud.dns_zone:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ dns_zone_name }}"
|
||||
zone_type: "primary"
|
||||
email: test@example.net
|
||||
state: absent
|
||||
|
||||
- name: Ensure dns zone
|
||||
openstack.cloud.dns_zone:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ dns_zone_name }}"
|
||||
zone_type: "primary"
|
||||
email: test@example.net
|
||||
register: dns_zone
|
||||
|
||||
- name: Create a recordset
|
||||
openstack.cloud.recordset:
|
||||
cloud: "{{ cloud }}"
|
||||
zone: "{{ dns_zone.zone.name }}"
|
||||
name: "{{ recordset_name }}"
|
||||
recordset_type: "a"
|
||||
records: "{{ records }}"
|
||||
register: recordset
|
||||
|
||||
- name: Verify recordset info
|
||||
assert:
|
||||
that:
|
||||
- recordset is changed
|
||||
- recordset["recordset"].name == recordset_name
|
||||
- recordset["recordset"].zone_name == dns_zone.zone.name
|
||||
- recordset["recordset"].records == records
|
||||
|
||||
- name: Assert recordset fields
|
||||
assert:
|
||||
that: item in recordset.recordset
|
||||
loop: "{{ recordset_fields }}"
|
||||
|
||||
- name: Create identical recordset
|
||||
openstack.cloud.recordset:
|
||||
cloud: "{{ cloud }}"
|
||||
zone: "{{ dns_zone.zone.name }}"
|
||||
name: "{{ recordset_name }}"
|
||||
recordset_type: "a"
|
||||
records: "{{ records }}"
|
||||
register: recordset
|
||||
|
||||
- name: Assert recordset not changed
|
||||
assert:
|
||||
that:
|
||||
- recordset is not changed
|
||||
|
||||
- name: Assert recordset fields
|
||||
assert:
|
||||
that: item in recordset.recordset
|
||||
loop: "{{ recordset_fields }}"
|
||||
|
||||
- name: Update a recordset
|
||||
openstack.cloud.recordset:
|
||||
cloud: "{{ cloud }}"
|
||||
zone: "{{ dns_zone.zone.name }}"
|
||||
name: "{{ recordset_name }}"
|
||||
recordset_type: "a"
|
||||
records: "{{ updated_records }}"
|
||||
description: "new test recordset"
|
||||
register: recordset
|
||||
|
||||
- name: Verify recordset info
|
||||
assert:
|
||||
that:
|
||||
- recordset is changed
|
||||
- recordset["recordset"].zone_name == dns_zone.zone.name
|
||||
- recordset["recordset"].name == recordset_name
|
||||
- recordset["recordset"].records == updated_records
|
||||
|
||||
- name: Assert recordset fields
|
||||
assert:
|
||||
that: item in recordset.recordset
|
||||
loop: "{{ recordset_fields }}"
|
||||
|
||||
- name: Delete recordset
|
||||
openstack.cloud.recordset:
|
||||
cloud: "{{ cloud }}"
|
||||
zone: "{{ dns_zone.zone.name }}"
|
||||
name: "{{ recordset.recordset.name }}"
|
||||
state: absent
|
||||
register: deleted_recordset
|
||||
|
||||
- name: Verify recordset deletion
|
||||
assert:
|
||||
that:
|
||||
- deleted_recordset is successful
|
||||
- deleted_recordset is changed
|
||||
|
||||
- name: Delete unexistent recordset
|
||||
openstack.cloud.recordset:
|
||||
cloud: "{{ cloud }}"
|
||||
zone: "{{ dns_zone.zone.name }}"
|
||||
name: "{{ recordset.recordset.name }}"
|
||||
state: absent
|
||||
register: deleted_recordset
|
||||
|
||||
- name: Verify recordset deletion
|
||||
assert:
|
||||
that:
|
||||
- deleted_recordset is not changed
|
||||
|
||||
- name: Delete dns zone
|
||||
openstack.cloud.dns_zone:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ dns_zone.zone.name }}"
|
||||
state: absent
|
||||
47
ci/roles/role_assignment/tasks/main.yml
Normal file
47
ci/roles/role_assignment/tasks/main.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
- name: Create project
|
||||
openstack.cloud.project:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_project
|
||||
description: dummy description
|
||||
domain_id: default
|
||||
enabled: True
|
||||
register: project
|
||||
|
||||
- name: Grant an admin role on the user admin in the project ansible_project
|
||||
openstack.cloud.role_assignment:
|
||||
cloud: "{{ cloud }}"
|
||||
domain: default
|
||||
project: ansible_project
|
||||
role: admin
|
||||
user: admin
|
||||
|
||||
- name: Grant an admin role on the user admin in the project ansible_project again
|
||||
openstack.cloud.role_assignment:
|
||||
cloud: "{{ cloud }}"
|
||||
domain: default
|
||||
project: ansible_project
|
||||
role: admin
|
||||
user: admin
|
||||
register: grant_again
|
||||
|
||||
- name: Ensure grant again doesn't change anything
|
||||
assert:
|
||||
that:
|
||||
- not grant_again.changed
|
||||
|
||||
- name: Revoke the admin role on the user admin in the project ansible_project
|
||||
openstack.cloud.role_assignment:
|
||||
cloud: "{{ cloud }}"
|
||||
domain: default
|
||||
project: ansible_project
|
||||
role: admin
|
||||
state: absent
|
||||
user: admin
|
||||
|
||||
- name: Delete project
|
||||
openstack.cloud.project:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_project
|
||||
@@ -124,12 +124,8 @@
|
||||
terminate_volume: true
|
||||
wait: true
|
||||
register: server
|
||||
tags:
|
||||
- object
|
||||
|
||||
- debug: var=server
|
||||
tags:
|
||||
- object
|
||||
|
||||
- name: Delete server with volume
|
||||
openstack.cloud.server:
|
||||
@@ -137,9 +133,6 @@
|
||||
state: absent
|
||||
name: "{{ server_name }}"
|
||||
wait: true
|
||||
tags:
|
||||
- object
|
||||
|
||||
- name: Create a minimal server
|
||||
openstack.cloud.server:
|
||||
cloud: "{{ cloud }}"
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
- name: Create user
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_user
|
||||
password: secret
|
||||
email: ansible.user@nowhere.net
|
||||
domain: default
|
||||
default_project: demo
|
||||
register: user
|
||||
|
||||
- debug: var=user
|
||||
|
||||
- name: Update user
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_user
|
||||
password: secret
|
||||
email: updated.ansible.user@nowhere.net
|
||||
register: updateduser
|
||||
|
||||
- debug: var=updateduser
|
||||
|
||||
- name: Delete user
|
||||
openstack.cloud.identity_user:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_user
|
||||
@@ -6,6 +6,7 @@
|
||||
roles:
|
||||
- { role: address_scope, tags: address_scope }
|
||||
- { role: auth, tags: auth }
|
||||
- { role: catalog_service, tags: catalog_service }
|
||||
- { role: client_config, tags: client_config }
|
||||
- { role: dns_zone_info, tags: dns_zone_info }
|
||||
- role: object_container
|
||||
@@ -15,8 +16,17 @@
|
||||
- role: dns
|
||||
tags: dns
|
||||
when: sdk_version is version(0.28, '>=')
|
||||
- { role: endpoint, tags: endpoint }
|
||||
- { role: floating_ip_info, tags: floating_ip_info }
|
||||
- { role: host_aggregate, tags: host_aggregate }
|
||||
- { role: identity_domain_info, tags: identity_domain_info }
|
||||
- { role: identity_group_info, tags: identity_group_info }
|
||||
- { role: identity_user, tags: identity_user }
|
||||
- { role: identity_user_info, tags: identity_user_info }
|
||||
- { role: identity_role, tags: identity_role }
|
||||
- { role: identity_role_info, tags: identity_role_info }
|
||||
- { role: image, tags: image }
|
||||
- { role: image_info, tags: image_info }
|
||||
- { role: keypair, tags: keypair }
|
||||
- { role: keystone_domain, tags: keystone_domain }
|
||||
- role: keystone_mapping
|
||||
@@ -28,7 +38,6 @@
|
||||
- role: keystone_federation_protocol
|
||||
tags: keystone_federation_protocol
|
||||
when: sdk_version is version(0.44, '>=')
|
||||
- { role: keystone_role, tags: keystone_role }
|
||||
- { role: network, tags: network }
|
||||
- role: neutron_rbac
|
||||
tags:
|
||||
@@ -43,12 +52,13 @@
|
||||
- { role: object, tags: object }
|
||||
- { role: port, tags: port }
|
||||
- { role: project, tags: project }
|
||||
- { role: recordset, tags: recordset }
|
||||
- { role: role_assignment, tags: role_assignment }
|
||||
- { role: router, tags: router }
|
||||
- { role: security_group, tags: security_group }
|
||||
- { role: server, tags: server }
|
||||
- { role: subnet, tags: subnet }
|
||||
- { role: subnet_pool, tags: subnet_pool }
|
||||
- { role: user, tags: user }
|
||||
- { role: user_group, tags: user_group }
|
||||
- { role: user_role, tags: user_role }
|
||||
- { role: volume, tags: volume }
|
||||
|
||||
@@ -33,4 +33,4 @@ build_ignore:
|
||||
- ansible_collections_openstack.egg-info
|
||||
- contrib
|
||||
- changelogs
|
||||
version: 1.8.0
|
||||
version: 1.9.1
|
||||
|
||||
@@ -91,7 +91,7 @@ options:
|
||||
type: str
|
||||
requirements:
|
||||
- python >= 3.6
|
||||
- openstacksdk >= 0.12.0
|
||||
- openstacksdk >= 0.36, < 0.99.0
|
||||
notes:
|
||||
- The standard OpenStack environment variables, such as C(OS_USERNAME)
|
||||
may be used instead of providing explicit values.
|
||||
|
||||
@@ -67,7 +67,8 @@ OVERRIDES = {'os_client_config': 'config',
|
||||
|
||||
CUSTOM_VAR_PARAMS = ['min_ver', 'max_ver']
|
||||
|
||||
MINIMUM_SDK_VERSION = '0.12.0'
|
||||
MINIMUM_SDK_VERSION = '0.36.0'
|
||||
MAXIMUM_SDK_VERSION = '0.98.999'
|
||||
|
||||
|
||||
def openstack_argument_spec():
|
||||
@@ -152,7 +153,7 @@ def openstack_module_kwargs(**kwargs):
|
||||
|
||||
|
||||
# for compatibility with old versions
|
||||
def openstack_cloud_from_module(module, min_version=None):
|
||||
def openstack_cloud_from_module(module, min_version=None, max_version=None):
|
||||
try:
|
||||
# Due to the name shadowing we should import other way
|
||||
sdk = importlib.import_module('openstack')
|
||||
@@ -160,18 +161,30 @@ def openstack_cloud_from_module(module, min_version=None):
|
||||
except ImportError:
|
||||
module.fail_json(msg='openstacksdk is required for this module')
|
||||
|
||||
if min_version:
|
||||
if min_version and MINIMUM_SDK_VERSION:
|
||||
min_version = max(StrictVersion(MINIMUM_SDK_VERSION),
|
||||
StrictVersion(min_version))
|
||||
else:
|
||||
elif MINIMUM_SDK_VERSION:
|
||||
min_version = StrictVersion(MINIMUM_SDK_VERSION)
|
||||
|
||||
if StrictVersion(sdk_version.__version__) < min_version:
|
||||
if max_version and MAXIMUM_SDK_VERSION:
|
||||
max_version = min(StrictVersion(MAXIMUM_SDK_VERSION),
|
||||
StrictVersion(max_version))
|
||||
elif MAXIMUM_SDK_VERSION:
|
||||
max_version = StrictVersion(MAXIMUM_SDK_VERSION)
|
||||
|
||||
if min_version and StrictVersion(sdk_version.__version__) < min_version:
|
||||
module.fail_json(
|
||||
msg="To utilize this module, the installed version of "
|
||||
"the openstacksdk library MUST be >={min_version}.".format(
|
||||
min_version=min_version))
|
||||
|
||||
if max_version and StrictVersion(sdk_version.__version__) > max_version:
|
||||
module.fail_json(
|
||||
msg="To utilize this module, the installed version of "
|
||||
"the openstacksdk library MUST be <={max_version}.".format(
|
||||
max_version=max_version))
|
||||
|
||||
cloud_config = module.params.pop('cloud', None)
|
||||
try:
|
||||
if isinstance(cloud_config, dict):
|
||||
@@ -244,6 +257,7 @@ class OpenStackModule:
|
||||
argument_spec = {}
|
||||
module_kwargs = {}
|
||||
module_min_sdk_version = None
|
||||
module_max_sdk_version = None
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize Openstack base class.
|
||||
@@ -306,19 +320,36 @@ class OpenStackModule:
|
||||
except ImportError:
|
||||
self.fail_json(msg='openstacksdk is required for this module')
|
||||
|
||||
# Fail if the available SDK version doesn't meet the minimum version
|
||||
# requirements
|
||||
if self.module_min_sdk_version:
|
||||
# Fail if the available SDK version doesn't meet the minimum
|
||||
# and maximum version requirements
|
||||
if self.module_min_sdk_version and MINIMUM_SDK_VERSION:
|
||||
min_version = max(StrictVersion(MINIMUM_SDK_VERSION),
|
||||
StrictVersion(self.module_min_sdk_version))
|
||||
else:
|
||||
elif MINIMUM_SDK_VERSION:
|
||||
min_version = StrictVersion(MINIMUM_SDK_VERSION)
|
||||
if StrictVersion(self.sdk_version) < min_version:
|
||||
else:
|
||||
min_version = None
|
||||
|
||||
if self.module_max_sdk_version and MAXIMUM_SDK_VERSION:
|
||||
max_version = min(StrictVersion(MAXIMUM_SDK_VERSION),
|
||||
StrictVersion(self.module_max_sdk_version))
|
||||
elif MAXIMUM_SDK_VERSION:
|
||||
max_version = StrictVersion(MAXIMUM_SDK_VERSION)
|
||||
else:
|
||||
max_version = None
|
||||
|
||||
if min_version and StrictVersion(self.sdk_version) < min_version:
|
||||
self.fail(
|
||||
msg="To utilize this module, the installed version of "
|
||||
"the openstacksdk library MUST be >={min_version}.".format(
|
||||
min_version=min_version))
|
||||
|
||||
if max_version and StrictVersion(self.sdk_version) > max_version:
|
||||
self.fail(
|
||||
msg="To utilize this module, the installed version of "
|
||||
"the openstacksdk library MUST be <={max_version}.".format(
|
||||
max_version=max_version))
|
||||
|
||||
# Fail if there are set unsupported for this version parameters
|
||||
# New parameters should NOT use 'default' but rely on SDK defaults
|
||||
for param in self.argument_spec:
|
||||
|
||||
@@ -26,11 +26,13 @@ options:
|
||||
- Is the service enabled
|
||||
type: bool
|
||||
default: 'yes'
|
||||
service_type:
|
||||
aliases: ['is_enabled']
|
||||
type:
|
||||
description:
|
||||
- The type of service
|
||||
required: true
|
||||
type: str
|
||||
aliases: ['service_type']
|
||||
state:
|
||||
description:
|
||||
- Should the resource be present or absent.
|
||||
@@ -51,14 +53,14 @@ EXAMPLES = '''
|
||||
cloud: mycloud
|
||||
state: present
|
||||
name: glance
|
||||
service_type: image
|
||||
type: image
|
||||
description: OpenStack Image Service
|
||||
# Delete a service
|
||||
- openstack.cloud.catalog_service:
|
||||
cloud: mycloud
|
||||
state: absent
|
||||
name: glance
|
||||
service_type: image
|
||||
type: image
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
@@ -75,6 +77,10 @@ service:
|
||||
description: Service name.
|
||||
type: str
|
||||
sample: "glance"
|
||||
type:
|
||||
description: Service type.
|
||||
type: str
|
||||
sample: "image"
|
||||
service_type:
|
||||
description: Service type.
|
||||
type: str
|
||||
@@ -100,9 +106,9 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O
|
||||
class IdentityCatalogServiceModule(OpenStackModule):
|
||||
argument_spec = dict(
|
||||
description=dict(default=None),
|
||||
enabled=dict(default=True, type='bool'),
|
||||
enabled=dict(default=True, aliases=['is_enabled'], type='bool'),
|
||||
name=dict(required=True),
|
||||
service_type=dict(required=True),
|
||||
type=dict(required=True, aliases=['service_type']),
|
||||
state=dict(default='present', choices=['absent', 'present']),
|
||||
)
|
||||
|
||||
@@ -111,11 +117,9 @@ class IdentityCatalogServiceModule(OpenStackModule):
|
||||
)
|
||||
|
||||
def _needs_update(self, service):
|
||||
if service.enabled != self.params['enabled']:
|
||||
return True
|
||||
if service.description is not None and \
|
||||
service.description != self.params['description']:
|
||||
return True
|
||||
for parameter in ('enabled', 'description', 'type'):
|
||||
if service[parameter] != self.params[parameter]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _system_state_change(self, service):
|
||||
@@ -135,33 +139,34 @@ class IdentityCatalogServiceModule(OpenStackModule):
|
||||
enabled = self.params['enabled']
|
||||
name = self.params['name']
|
||||
state = self.params['state']
|
||||
service_type = self.params['service_type']
|
||||
type = self.params['type']
|
||||
|
||||
services = self.conn.search_services(
|
||||
name_or_id=name, filters=dict(type=service_type))
|
||||
name_or_id=name, filters=(dict(type=type) if type else None))
|
||||
|
||||
service = None
|
||||
if len(services) > 1:
|
||||
self.fail_json(
|
||||
msg='Service name %s and type %s are not unique'
|
||||
% (name, service_type))
|
||||
% (name, type))
|
||||
elif len(services) == 1:
|
||||
service = services[0]
|
||||
else:
|
||||
service = None
|
||||
|
||||
if self.ansible.check_mode:
|
||||
self.exit_json(changed=self._system_state_change(service))
|
||||
|
||||
args = {'name': name, 'enabled': enabled, 'type': type}
|
||||
if description:
|
||||
args['description'] = description
|
||||
|
||||
if state == 'present':
|
||||
if service is None:
|
||||
service = self.conn.create_service(
|
||||
name=name, description=description, type=service_type, enabled=True)
|
||||
service = self.conn.create_service(**args)
|
||||
changed = True
|
||||
else:
|
||||
if self._needs_update(service):
|
||||
service = self.conn.update_service(
|
||||
service.id, name=name, type=service_type, enabled=enabled,
|
||||
description=description)
|
||||
service = self.conn.update_service(service,
|
||||
**args)
|
||||
changed = True
|
||||
else:
|
||||
changed = False
|
||||
@@ -171,7 +176,7 @@ class IdentityCatalogServiceModule(OpenStackModule):
|
||||
if service is None:
|
||||
changed = False
|
||||
else:
|
||||
self.conn.delete_service(service.id)
|
||||
self.conn.identity.delete_service(service.id)
|
||||
changed = True
|
||||
self.exit_json(changed=changed)
|
||||
|
||||
|
||||
@@ -12,11 +12,11 @@ description:
|
||||
options:
|
||||
binary:
|
||||
description:
|
||||
- Filter by service binary type
|
||||
- Filter by service binary type. Requires openstacksdk>=0.53.
|
||||
type: str
|
||||
host:
|
||||
description:
|
||||
- Filter by service host
|
||||
- Filter by service host. Requires openstacksdk>=0.53.
|
||||
type: str
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
@@ -61,7 +61,11 @@ openstack_compute_services:
|
||||
type: str
|
||||
disabled_reason:
|
||||
description: The reason why the service is disabled
|
||||
returned: success
|
||||
returned: success and OpenStack SDK >= 0.53
|
||||
type: str
|
||||
disables_reason:
|
||||
description: The reason why the service is disabled
|
||||
returned: success and OpenStack SDK < 0.53
|
||||
type: str
|
||||
availability_zone:
|
||||
description: The availability zone name.
|
||||
@@ -94,21 +98,16 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O
|
||||
|
||||
class ComputeServiceInfoModule(OpenStackModule):
|
||||
argument_spec = dict(
|
||||
binary=dict(required=False, default=None),
|
||||
host=dict(required=False, default=None),
|
||||
binary=dict(required=False, default=None, min_ver='0.53.0'),
|
||||
host=dict(required=False, default=None, min_ver='0.53.0'),
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
binary = self.params['binary']
|
||||
host = self.params['host']
|
||||
filters = {}
|
||||
if binary:
|
||||
filters['binary'] = binary
|
||||
if host:
|
||||
filters['host'] = host
|
||||
filters = self.check_versioned(binary=self.params['binary'], host=self.params['host'])
|
||||
filters = {k: v for k, v in filters.items() if v is not None}
|
||||
services = self.conn.compute.services(**filters)
|
||||
services = [service.to_dict(computed=True) for service in services]
|
||||
self.exit_json(changed=False, openstack_compute_services=services)
|
||||
|
||||
@@ -161,12 +161,8 @@ class DnsZoneInfoModule(OpenStackModule):
|
||||
if ttl:
|
||||
kwargs['ttl'] = ttl
|
||||
|
||||
data = []
|
||||
|
||||
for raw in self.conn.dns.zones(**kwargs):
|
||||
dt = raw.to_dict()
|
||||
dt.pop('location')
|
||||
data.append(dt)
|
||||
data = [zone.to_dict(computed=False) for zone in
|
||||
self.conn.dns.zones(**kwargs)]
|
||||
|
||||
self.exit_json(zones=data, changed=False)
|
||||
|
||||
|
||||
@@ -81,26 +81,34 @@ endpoint:
|
||||
description: Endpoint ID.
|
||||
type: str
|
||||
sample: 3292f020780b4d5baf27ff7e1d224c44
|
||||
interface:
|
||||
description: Endpoint Interface.
|
||||
type: str
|
||||
sample: public
|
||||
enabled:
|
||||
description: Service status.
|
||||
type: bool
|
||||
sample: True
|
||||
links:
|
||||
description: Links for the endpoint
|
||||
type: str
|
||||
sample: http://controller/identity/v3/endpoints/123
|
||||
region:
|
||||
description: Region Name.
|
||||
description: Same as C(region_id). Deprecated.
|
||||
type: str
|
||||
sample: RegionOne
|
||||
region_id:
|
||||
description: Region ID.
|
||||
type: str
|
||||
sample: RegionOne
|
||||
service_id:
|
||||
description: Service ID.
|
||||
type: str
|
||||
sample: b91f1318f735494a825a55388ee118f3
|
||||
interface:
|
||||
description: Endpoint Interface.
|
||||
type: str
|
||||
sample: public
|
||||
url:
|
||||
description: Service URL.
|
||||
type: str
|
||||
sample: http://controller:9292
|
||||
enabled:
|
||||
description: Service status.
|
||||
type: bool
|
||||
sample: True
|
||||
'''
|
||||
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
||||
@@ -148,10 +156,11 @@ class IdentityEndpointModule(OpenStackModule):
|
||||
state = self.params['state']
|
||||
|
||||
service = self.conn.get_service(service_name_or_id)
|
||||
|
||||
if service is None and state == 'absent':
|
||||
self.exit_json(changed=False)
|
||||
|
||||
elif service is None and state == 'present':
|
||||
if service is None and state == 'present':
|
||||
self.fail_json(msg='Service %s does not exist' % service_name_or_id)
|
||||
|
||||
filters = dict(service_id=service.id, interface=interface)
|
||||
@@ -159,24 +168,27 @@ class IdentityEndpointModule(OpenStackModule):
|
||||
filters['region'] = region
|
||||
endpoints = self.conn.search_endpoints(filters=filters)
|
||||
|
||||
endpoint = None
|
||||
if len(endpoints) > 1:
|
||||
self.fail_json(msg='Service %s, interface %s and region %s are '
|
||||
'not unique' %
|
||||
(service_name_or_id, interface, region))
|
||||
elif len(endpoints) == 1:
|
||||
endpoint = endpoints[0]
|
||||
else:
|
||||
endpoint = None
|
||||
|
||||
if self.ansible.check_mode:
|
||||
self.exit_json(changed=self._system_state_change(endpoint))
|
||||
|
||||
if state == 'present':
|
||||
if endpoint is None:
|
||||
result = self.conn.create_endpoint(
|
||||
service_name_or_id=service, url=url, interface=interface,
|
||||
region=region, enabled=enabled)
|
||||
endpoint = result[0]
|
||||
args = {'url': url, 'interface': interface,
|
||||
'service_name_or_id': service.id, 'enabled': enabled,
|
||||
'region': region}
|
||||
endpoints = self.conn.create_endpoint(**args)
|
||||
# safe because endpoints contains a single item when url is
|
||||
# given to self.conn.create_endpoint()
|
||||
endpoint = endpoints[0]
|
||||
|
||||
changed = True
|
||||
else:
|
||||
if self._needs_update(endpoint):
|
||||
@@ -185,7 +197,8 @@ class IdentityEndpointModule(OpenStackModule):
|
||||
changed = True
|
||||
else:
|
||||
changed = False
|
||||
self.exit_json(changed=changed, endpoint=endpoint)
|
||||
self.exit_json(changed=changed,
|
||||
endpoint=endpoint)
|
||||
|
||||
elif state == 'absent':
|
||||
if endpoint is None:
|
||||
|
||||
@@ -73,8 +73,36 @@ EXAMPLES = '''
|
||||
name: db_aggregate
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
|
||||
RETURN = r'''
|
||||
aggregate:
|
||||
description: A host aggregate resource.
|
||||
type: complex
|
||||
returned: On success, when I(state) is present
|
||||
contains:
|
||||
availability_zone:
|
||||
description: Availability zone of the aggregate
|
||||
type: str
|
||||
returned: always
|
||||
deleted:
|
||||
description: Whether or not the resource is deleted
|
||||
type: bool
|
||||
returned: always
|
||||
hosts:
|
||||
description: Hosts belonging to the aggregate
|
||||
type: str
|
||||
returned: always
|
||||
id:
|
||||
description: The UUID of the aggregate.
|
||||
type: str
|
||||
returned: always
|
||||
metadata:
|
||||
description: Metadata attached to the aggregate
|
||||
type: str
|
||||
returned: always
|
||||
name:
|
||||
description: Name of the aggregate
|
||||
type: str
|
||||
returned: always
|
||||
'''
|
||||
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
||||
@@ -94,14 +122,20 @@ class ComputeHostAggregateModule(OpenStackModule):
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def _find_aggregate(self, name_or_id):
|
||||
aggregates = self.conn.search_aggregates(name_or_id=name_or_id)
|
||||
if len(aggregates) == 1:
|
||||
return aggregates[0]
|
||||
elif len(aggregates) == 0:
|
||||
return None
|
||||
raise Exception("Aggregate is not unique, this should be impossible")
|
||||
|
||||
def _needs_update(self, aggregate):
|
||||
new_metadata = (self.params['metadata'] or {})
|
||||
new_metadata = self.params['metadata'] or {}
|
||||
|
||||
if self.params['availability_zone'] is not None:
|
||||
new_metadata['availability_zone'] = self.params['availability_zone']
|
||||
|
||||
if self.params['name'] != aggregate.name:
|
||||
return True
|
||||
if self.params['hosts'] is not None:
|
||||
if self.params['purge_hosts']:
|
||||
if set(self.params['hosts']) != set(aggregate.hosts):
|
||||
@@ -110,11 +144,10 @@ class ComputeHostAggregateModule(OpenStackModule):
|
||||
intersection = set(self.params['hosts']).intersection(set(aggregate.hosts))
|
||||
if set(self.params['hosts']) != intersection:
|
||||
return True
|
||||
if self.params['availability_zone'] is not None:
|
||||
if self.params['availability_zone'] != aggregate.availability_zone:
|
||||
return True
|
||||
if self.params['metadata'] is not None:
|
||||
if new_metadata != aggregate.metadata:
|
||||
|
||||
for param in ('availability_zone', 'metadata'):
|
||||
if self.params[param] is not None and \
|
||||
self.params[param] != aggregate[param]:
|
||||
return True
|
||||
|
||||
return False
|
||||
@@ -135,16 +168,16 @@ class ComputeHostAggregateModule(OpenStackModule):
|
||||
if hosts is None:
|
||||
return
|
||||
|
||||
hosts_to_add = set(hosts) - set(aggregate.get("hosts") or [])
|
||||
for i in hosts_to_add:
|
||||
self.conn.add_host_to_aggregate(aggregate.id, i)
|
||||
hosts_to_add = set(hosts) - set(aggregate['hosts'] or [])
|
||||
for host in hosts_to_add:
|
||||
self.conn.add_host_to_aggregate(aggregate.id, host)
|
||||
|
||||
if not purge_hosts:
|
||||
return
|
||||
|
||||
hosts_to_remove = set(aggregate.get("hosts") or []) - set(hosts)
|
||||
for i in hosts_to_remove:
|
||||
self.conn.remove_host_from_aggregate(aggregate.id, i)
|
||||
hosts_to_remove = set(aggregate["hosts"] or []) - set(hosts)
|
||||
for host in hosts_to_remove:
|
||||
self.conn.remove_host_from_aggregate(aggregate.id, host)
|
||||
|
||||
def run(self):
|
||||
name = self.params['name']
|
||||
@@ -157,18 +190,12 @@ class ComputeHostAggregateModule(OpenStackModule):
|
||||
if metadata is not None:
|
||||
metadata.pop('availability_zone', None)
|
||||
|
||||
aggregates = self.conn.search_aggregates(name_or_id=name)
|
||||
|
||||
if len(aggregates) == 1:
|
||||
aggregate = aggregates[0]
|
||||
elif len(aggregates) == 0:
|
||||
aggregate = None
|
||||
else:
|
||||
raise Exception("Should not happen")
|
||||
aggregate = self._find_aggregate(name)
|
||||
|
||||
if self.ansible.check_mode:
|
||||
self.exit_json(changed=self._system_state_change(aggregate))
|
||||
|
||||
changed = False
|
||||
if state == 'present':
|
||||
if aggregate is None:
|
||||
aggregate = self.conn.create_aggregate(
|
||||
@@ -177,32 +204,27 @@ class ComputeHostAggregateModule(OpenStackModule):
|
||||
if metadata:
|
||||
self.conn.set_aggregate_metadata(aggregate.id, metadata)
|
||||
changed = True
|
||||
else:
|
||||
if self._needs_update(aggregate):
|
||||
if availability_zone is not None:
|
||||
aggregate = self.conn.update_aggregate(
|
||||
aggregate.id, name=name,
|
||||
availability_zone=availability_zone)
|
||||
if metadata is not None:
|
||||
metas = metadata
|
||||
for i in (set(aggregate.metadata.keys()) - set(metadata.keys())):
|
||||
if i != 'availability_zone':
|
||||
metas[i] = None
|
||||
self.conn.set_aggregate_metadata(aggregate.id, metas)
|
||||
self._update_hosts(aggregate, hosts, purge_hosts)
|
||||
changed = True
|
||||
else:
|
||||
changed = False
|
||||
self.exit_json(changed=changed)
|
||||
|
||||
elif state == 'absent':
|
||||
if aggregate is None:
|
||||
changed = False
|
||||
else:
|
||||
self._update_hosts(aggregate, [], True)
|
||||
self.conn.delete_aggregate(aggregate.id)
|
||||
elif self._needs_update(aggregate):
|
||||
if availability_zone is not None:
|
||||
aggregate = self.conn.update_aggregate(
|
||||
aggregate.id, name=name,
|
||||
availability_zone=availability_zone)
|
||||
if metadata is not None:
|
||||
metas = metadata
|
||||
for i in set(aggregate.metadata.keys() - set(metadata.keys())):
|
||||
if i != 'availability_zone':
|
||||
metas[i] = None
|
||||
self.conn.set_aggregate_metadata(aggregate.id, metas)
|
||||
self._update_hosts(aggregate, hosts, purge_hosts)
|
||||
changed = True
|
||||
self.exit_json(changed=changed)
|
||||
aggregate = self._find_aggregate(name)
|
||||
self.exit_json(changed=changed, aggregate=aggregate)
|
||||
|
||||
elif state == 'absent' and aggregate is not None:
|
||||
self._update_hosts(aggregate, [], True)
|
||||
self.conn.delete_aggregate(aggregate.id)
|
||||
changed = True
|
||||
self.exit_json(changed=changed)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -18,7 +18,7 @@ options:
|
||||
type: str
|
||||
filters:
|
||||
description:
|
||||
- A dictionary of meta data to use for further filtering. Elements of
|
||||
- A dictionary of meta data to use for filtering. Elements of
|
||||
this dictionary may be additional dictionaries.
|
||||
type: dict
|
||||
requirements:
|
||||
@@ -61,7 +61,8 @@ RETURN = '''
|
||||
openstack_domains:
|
||||
description: has all the OpenStack information about domains
|
||||
returned: always, but can be null
|
||||
type: complex
|
||||
type: list
|
||||
elements: dict
|
||||
contains:
|
||||
id:
|
||||
description: Unique UUID.
|
||||
@@ -89,10 +90,8 @@ class IdentityDomainInfoModule(OpenStackModule):
|
||||
name=dict(required=False, default=None),
|
||||
filters=dict(required=False, type='dict', default=None),
|
||||
)
|
||||
|
||||
module_kwargs = dict(
|
||||
mutually_exclusive=[
|
||||
['name', 'filters'],
|
||||
],
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
@@ -100,18 +99,14 @@ class IdentityDomainInfoModule(OpenStackModule):
|
||||
|
||||
def run(self):
|
||||
name = self.params['name']
|
||||
filters = self.params['filters']
|
||||
filters = self.params['filters'] or {}
|
||||
|
||||
args = {}
|
||||
if name:
|
||||
# Let's suppose user is passing domain ID
|
||||
try:
|
||||
domains = self.conn.get_domain(name)
|
||||
except Exception:
|
||||
domains = self.conn.search_domains(filters={'name': name})
|
||||
|
||||
else:
|
||||
domains = self.conn.search_domains(filters)
|
||||
args['name_or_id'] = name
|
||||
args['filters'] = filters
|
||||
|
||||
domains = self.conn.search_domains(**args)
|
||||
self.exit_json(changed=False, openstack_domains=domains)
|
||||
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ EXAMPLES = '''
|
||||
RETURN = '''
|
||||
openstack_groups:
|
||||
description: Dictionary describing all the matching groups.
|
||||
returned: always, but can be null
|
||||
returned: always, but can be an empty list
|
||||
type: complex
|
||||
contains:
|
||||
name:
|
||||
@@ -126,27 +126,19 @@ class IdentityGroupInfoModule(OpenStackModule):
|
||||
def run(self):
|
||||
name = self.params['name']
|
||||
domain = self.params['domain']
|
||||
filters = self.params['filters']
|
||||
filters = self.params['filters'] or {}
|
||||
|
||||
args = {}
|
||||
if domain:
|
||||
try:
|
||||
# We assume admin is passing domain id
|
||||
dom = self.conn.get_domain(domain)['id']
|
||||
domain = dom
|
||||
except Exception:
|
||||
# If we fail, maybe admin is passing a domain name.
|
||||
# Note that domains have unique names, just like id.
|
||||
dom = self.conn.search_domains(filters={'name': domain})
|
||||
if dom:
|
||||
domain = dom[0]['id']
|
||||
else:
|
||||
self.fail_json(msg='Domain name or ID does not exist')
|
||||
dom = self.conn.identity.find_domain(domain)
|
||||
if dom:
|
||||
args['domain_id'] = dom['id']
|
||||
else:
|
||||
self.fail_json(msg='Domain name or ID does not exist')
|
||||
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
groups = self.conn.search_groups(name, filters, domain_id=domain)
|
||||
self.exit_json(changed=False, groups=groups)
|
||||
groups = self.conn.search_groups(name, filters, **args)
|
||||
# groups is for backward (and forward) compatibility
|
||||
self.exit_json(changed=False, groups=groups, openstack_groups=groups)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -49,6 +49,10 @@ role:
|
||||
returned: On success when I(state) is 'present'.
|
||||
type: complex
|
||||
contains:
|
||||
domain_id:
|
||||
description: Domain to which the role belongs
|
||||
type: str
|
||||
sample: default
|
||||
id:
|
||||
description: Unique role ID.
|
||||
type: str
|
||||
@@ -88,20 +92,16 @@ class IdentityRoleModule(OpenStackModule):
|
||||
if self.ansible.check_mode:
|
||||
self.exit_json(changed=self._system_state_change(state, role))
|
||||
|
||||
changed = False
|
||||
if state == 'present':
|
||||
if role is None:
|
||||
role = self.conn.create_role(name)
|
||||
role = self.conn.create_role(name=name)
|
||||
changed = True
|
||||
else:
|
||||
changed = False
|
||||
self.exit_json(changed=changed, role=role)
|
||||
elif state == 'absent':
|
||||
if role is None:
|
||||
changed = False
|
||||
else:
|
||||
self.conn.delete_role(name)
|
||||
changed = True
|
||||
self.exit_json(changed=changed)
|
||||
elif state == 'absent' and role is not None:
|
||||
self.conn.identity.delete_role(role['id'])
|
||||
changed = True
|
||||
self.exit_json(changed=changed)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -7,19 +7,19 @@
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: identity_role_info
|
||||
short_description: Retrive information about roles
|
||||
short_description: Retrieve information about roles
|
||||
author: OpenStack Ansible SIG
|
||||
description:
|
||||
- Get information about identity roles in Openstack
|
||||
options:
|
||||
domain_id:
|
||||
description:
|
||||
- List roles in specified domain only
|
||||
- Domain ID which owns the role
|
||||
type: str
|
||||
required: false
|
||||
name:
|
||||
description:
|
||||
- List role speficied by name
|
||||
- Name or ID of the role
|
||||
type: str
|
||||
required: false
|
||||
|
||||
@@ -37,21 +37,19 @@ openstack_roles:
|
||||
returned: always
|
||||
type: list
|
||||
elements: dict
|
||||
sample:
|
||||
- domain_id: None
|
||||
id: 19bf514fdda84f808ccee8463bd85c1a
|
||||
location:
|
||||
cloud: mycloud
|
||||
project:
|
||||
domain_id: None
|
||||
domain_name: None
|
||||
id: None
|
||||
name: None
|
||||
region_name: None
|
||||
zone: None
|
||||
name: member
|
||||
properties:
|
||||
|
||||
contains:
|
||||
id:
|
||||
description: Unique ID for the role
|
||||
returned: success
|
||||
type: str
|
||||
name:
|
||||
description: Unique role name, within the owning domain.
|
||||
returned: success
|
||||
type: str
|
||||
domain_id:
|
||||
description: References the domain ID which owns the role.
|
||||
returned: success
|
||||
type: str
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
@@ -75,23 +73,24 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O
|
||||
|
||||
|
||||
class IdentityRoleInfoModule(OpenStackModule):
|
||||
|
||||
argument_spec = dict(
|
||||
domain_id=dict(type='str', required=False),
|
||||
name=dict(type='str', required=False),
|
||||
)
|
||||
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
def run(self):
|
||||
roles = self.conn.list_roles(domain_id=self.params['domain_id'])
|
||||
# Dictionaries are supported from Train release
|
||||
roles = [item if isinstance(item, dict) else item.to_dict() for item in roles]
|
||||
# Filtering by name is supported from Wallaby release
|
||||
if self.params['name']:
|
||||
roles = [item for item in roles if self.params['name'] in (item['id'], item['name'])]
|
||||
self.results.update({'openstack_roles': roles})
|
||||
params = {
|
||||
'domain_id': self.params['domain_id'],
|
||||
'name_or_id': self.params['name'],
|
||||
}
|
||||
params = {k: v for k, v in params.items() if v is not None}
|
||||
|
||||
roles = self.conn.search_roles(**params)
|
||||
self.exit_json(changed=False, openstack_roles=roles)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -26,6 +26,7 @@ options:
|
||||
update_password:
|
||||
required: false
|
||||
choices: ['always', 'on_create']
|
||||
default: on_create
|
||||
description:
|
||||
- C(always) will attempt to update password. C(on_create) will only
|
||||
set the password for newly created users.
|
||||
@@ -108,28 +109,46 @@ RETURN = '''
|
||||
user:
|
||||
description: Dictionary describing the user.
|
||||
returned: On success when I(state) is 'present'
|
||||
type: complex
|
||||
type: dict
|
||||
contains:
|
||||
default_project_id:
|
||||
description: User default project ID. Only present with Keystone >= v3.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "4427115787be45f08f0ec22a03bfc735"
|
||||
description:
|
||||
description: The description of this user
|
||||
returned: success
|
||||
type: str
|
||||
sample: "a user"
|
||||
domain_id:
|
||||
description: User domain ID. Only present with Keystone >= v3.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "default"
|
||||
email:
|
||||
description: User email address
|
||||
returned: success
|
||||
type: str
|
||||
sample: "demo@example.com"
|
||||
id:
|
||||
description: User ID
|
||||
returned: success
|
||||
type: str
|
||||
sample: "f59382db809c43139982ca4189404650"
|
||||
enabled:
|
||||
description: Indicates whether the user is enabled
|
||||
type: bool
|
||||
name:
|
||||
description: User name
|
||||
description: Unique user name, within the owning domain
|
||||
returned: success
|
||||
type: str
|
||||
sample: "demouser"
|
||||
username:
|
||||
description: Username with Identity API v2 (OpenStack Pike or earlier) else Null
|
||||
returned: success
|
||||
type: str
|
||||
|
||||
'''
|
||||
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
||||
@@ -145,46 +164,37 @@ class IdentityUserModule(OpenStackModule):
|
||||
domain=dict(required=False, default=None),
|
||||
enabled=dict(default=True, type='bool'),
|
||||
state=dict(default='present', choices=['absent', 'present']),
|
||||
update_password=dict(default=None, choices=['always', 'on_create']),
|
||||
update_password=dict(default='on_create', choices=['always', 'on_create']),
|
||||
)
|
||||
|
||||
module_kwargs = dict()
|
||||
|
||||
def _needs_update(self, params_dict, user):
|
||||
for k in params_dict:
|
||||
if k not in ('password', 'update_password') and user[k] != params_dict[k]:
|
||||
# We don't get password back in the user object, so assume any supplied
|
||||
# password is a change.
|
||||
if k == 'password':
|
||||
return True
|
||||
if k == 'default_project':
|
||||
if user['default_project_id'] != params_dict['default_project']:
|
||||
return True
|
||||
else:
|
||||
continue
|
||||
if user[k] != params_dict[k]:
|
||||
return True
|
||||
|
||||
# We don't get password back in the user object, so assume any supplied
|
||||
# password is a change.
|
||||
if (
|
||||
params_dict['password'] is not None
|
||||
and params_dict['update_password'] == 'always'
|
||||
):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _get_domain_id(self, domain):
|
||||
try:
|
||||
# We assume admin is passing domain id
|
||||
domain_id = self.conn.get_domain(domain)['id']
|
||||
except Exception:
|
||||
# If we fail, maybe admin is passing a domain name.
|
||||
# Note that domains have unique names, just like id.
|
||||
try:
|
||||
domain_id = self.conn.search_domains(filters={'name': domain})[0]['id']
|
||||
except Exception:
|
||||
# Ok, let's hope the user is non-admin and passing a sane id
|
||||
domain_id = domain
|
||||
|
||||
return domain_id
|
||||
dom_obj = self.conn.identity.find_domain(domain)
|
||||
if dom_obj is None:
|
||||
# Ok, let's hope the user is non-admin and passing a sane id
|
||||
return domain
|
||||
return dom_obj.id
|
||||
|
||||
def _get_default_project_id(self, default_project, domain_id):
|
||||
project = self.conn.get_project(default_project, domain_id=domain_id)
|
||||
project = self.conn.identity.find_project(default_project, domain_id=domain_id)
|
||||
if not project:
|
||||
self.fail_json(msg='Default project %s is not valid' % default_project)
|
||||
|
||||
return project['id']
|
||||
|
||||
def run(self):
|
||||
@@ -198,84 +208,50 @@ class IdentityUserModule(OpenStackModule):
|
||||
update_password = self.params['update_password']
|
||||
description = self.params['description']
|
||||
|
||||
domain_id = None
|
||||
if domain:
|
||||
domain_id = self._get_domain_id(domain)
|
||||
user = self.conn.get_user(name, domain_id=domain_id)
|
||||
else:
|
||||
domain_id = None
|
||||
user = self.conn.get_user(name)
|
||||
|
||||
changed = False
|
||||
if state == 'present':
|
||||
if update_password in ('always', 'on_create'):
|
||||
if not password:
|
||||
msg = "update_password is %s but a password value is missing" % update_password
|
||||
self.fail_json(msg=msg)
|
||||
default_project_id = None
|
||||
user_args = {
|
||||
'name': name,
|
||||
'email': email,
|
||||
'domain_id': domain_id,
|
||||
'description': description,
|
||||
'enabled': enabled,
|
||||
}
|
||||
if default_project:
|
||||
default_project_id = self._get_default_project_id(
|
||||
default_project, domain_id)
|
||||
user_args['default_project'] = default_project_id
|
||||
user_args = {k: v for k, v in user_args.items() if v is not None}
|
||||
|
||||
changed = False
|
||||
if user is None:
|
||||
if description is not None:
|
||||
user = self.conn.create_user(
|
||||
name=name, password=password, email=email,
|
||||
default_project=default_project_id, domain_id=domain_id,
|
||||
enabled=enabled, description=description)
|
||||
else:
|
||||
user = self.conn.create_user(
|
||||
name=name, password=password, email=email,
|
||||
default_project=default_project_id, domain_id=domain_id,
|
||||
enabled=enabled)
|
||||
if password:
|
||||
user_args['password'] = password
|
||||
|
||||
user = self.conn.create_user(**user_args)
|
||||
changed = True
|
||||
else:
|
||||
params_dict = {'email': email, 'enabled': enabled,
|
||||
'password': password,
|
||||
'update_password': update_password}
|
||||
if description is not None:
|
||||
params_dict['description'] = description
|
||||
if domain_id is not None:
|
||||
params_dict['domain_id'] = domain_id
|
||||
if default_project_id is not None:
|
||||
params_dict['default_project_id'] = default_project_id
|
||||
if update_password == 'always':
|
||||
if not password:
|
||||
self.fail_json(msg="update_password is always but a password value is missing")
|
||||
user_args['password'] = password
|
||||
|
||||
if self._needs_update(params_dict, user):
|
||||
if update_password == 'always':
|
||||
if description is not None:
|
||||
user = self.conn.update_user(
|
||||
user['id'], password=password, email=email,
|
||||
default_project=default_project_id,
|
||||
domain_id=domain_id, enabled=enabled, description=description)
|
||||
else:
|
||||
user = self.conn.update_user(
|
||||
user['id'], password=password, email=email,
|
||||
default_project=default_project_id,
|
||||
domain_id=domain_id, enabled=enabled)
|
||||
else:
|
||||
if description is not None:
|
||||
user = self.conn.update_user(
|
||||
user['id'], email=email,
|
||||
default_project=default_project_id,
|
||||
domain_id=domain_id, enabled=enabled, description=description)
|
||||
else:
|
||||
user = self.conn.update_user(
|
||||
user['id'], email=email,
|
||||
default_project=default_project_id,
|
||||
domain_id=domain_id, enabled=enabled)
|
||||
if self._needs_update(user_args, user):
|
||||
user = self.conn.update_user(user['id'], **user_args)
|
||||
changed = True
|
||||
else:
|
||||
changed = False
|
||||
self.exit_json(changed=changed, user=user)
|
||||
|
||||
elif state == 'absent':
|
||||
if user is None:
|
||||
changed = False
|
||||
else:
|
||||
if domain:
|
||||
self.conn.delete_user(user['id'], domain_id=domain_id)
|
||||
else:
|
||||
self.conn.delete_user(user['id'])
|
||||
changed = True
|
||||
self.exit_json(changed=changed)
|
||||
self.exit_json(changed=changed, user=user)
|
||||
elif state == 'absent' and user is not None:
|
||||
self.conn.identity.delete_user(user['id'])
|
||||
changed = True
|
||||
self.exit_json(changed=changed)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -74,35 +74,40 @@ EXAMPLES = '''
|
||||
RETURN = '''
|
||||
openstack_users:
|
||||
description: has all the OpenStack information about users
|
||||
returned: always, but can be null
|
||||
type: complex
|
||||
returned: always
|
||||
type: list
|
||||
elements: dict
|
||||
contains:
|
||||
id:
|
||||
description: Unique UUID.
|
||||
returned: success
|
||||
type: str
|
||||
name:
|
||||
description: Name given to the user.
|
||||
returned: success
|
||||
type: str
|
||||
enabled:
|
||||
description: Flag to indicate if the user is enabled
|
||||
returned: success
|
||||
type: bool
|
||||
domain_id:
|
||||
description: Domain ID containing the user
|
||||
description: Username of the user.
|
||||
returned: success
|
||||
type: str
|
||||
default_project_id:
|
||||
description: Default project ID of the user
|
||||
returned: success
|
||||
type: str
|
||||
description:
|
||||
description: The description of this user
|
||||
returned: success
|
||||
type: str
|
||||
domain_id:
|
||||
description: Domain ID containing the user
|
||||
returned: success
|
||||
type: str
|
||||
email:
|
||||
description: Email of the user
|
||||
returned: success
|
||||
type: str
|
||||
enabled:
|
||||
description: Flag to indicate if the user is enabled
|
||||
returned: success
|
||||
type: bool
|
||||
username:
|
||||
description: Username of the user
|
||||
description: Username with Identity API v2 (OpenStack Pike or earlier) else Null
|
||||
returned: success
|
||||
type: str
|
||||
'''
|
||||
@@ -127,21 +132,15 @@ class IdentityUserInfoModule(OpenStackModule):
|
||||
domain = self.params['domain']
|
||||
filters = self.params['filters']
|
||||
|
||||
args = {}
|
||||
if domain:
|
||||
try:
|
||||
# We assume admin is passing domain id
|
||||
dom = self.conn.get_domain(domain)['id']
|
||||
domain = dom
|
||||
except Exception:
|
||||
# If we fail, maybe admin is passing a domain name.
|
||||
# Note that domains have unique names, just like id.
|
||||
dom = self.conn.search_domains(filters={'name': domain})
|
||||
if dom:
|
||||
domain = dom[0]['id']
|
||||
else:
|
||||
self.fail_json(msg='Domain name or ID does not exist')
|
||||
dom_obj = self.conn.identity.find_domain(domain)
|
||||
if dom_obj is None:
|
||||
self.fail_json(
|
||||
msg="Domain name or ID '{0}' does not exist".format(domain))
|
||||
args['domain_id'] = dom_obj.id
|
||||
|
||||
users = self.conn.search_users(name, filters, domain_id=domain)
|
||||
users = self.conn.search_users(name, filters, **args)
|
||||
self.exit_json(changed=False, openstack_users=users)
|
||||
|
||||
|
||||
|
||||
@@ -40,9 +40,15 @@ options:
|
||||
default: bare
|
||||
choices: ['ami', 'aki', 'ari', 'bare', 'ovf', 'ova', 'docker']
|
||||
type: str
|
||||
owner:
|
||||
project:
|
||||
description:
|
||||
- The owner of the image
|
||||
- The name or ID of the project owning the image
|
||||
type: str
|
||||
aliases: ['owner']
|
||||
project_domain:
|
||||
description:
|
||||
- The domain the project owning the image belongs to
|
||||
- May be used to identify a unique project when providing a name to the project argument and multiple projects with such name exist
|
||||
type: str
|
||||
min_disk:
|
||||
description:
|
||||
@@ -169,7 +175,8 @@ class ImageModule(OpenStackModule):
|
||||
disk_format=dict(default='qcow2',
|
||||
choices=['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2', 'vdi', 'iso', 'vhdx', 'ploop']),
|
||||
container_format=dict(default='bare', choices=['ami', 'aki', 'ari', 'bare', 'ovf', 'ova', 'docker']),
|
||||
owner=dict(type='str'),
|
||||
project=dict(type='str', aliases=['owner']),
|
||||
project_domain=dict(type='str'),
|
||||
min_disk=dict(type='int', default=0),
|
||||
min_ram=dict(type='int', default=0),
|
||||
is_public=dict(type='bool', default=False),
|
||||
@@ -202,6 +209,16 @@ class ImageModule(OpenStackModule):
|
||||
kwargs = {}
|
||||
if self.params['id'] is not None:
|
||||
kwargs['id'] = self.params['id']
|
||||
if self.params['project']:
|
||||
project_domain = {'id': None}
|
||||
if self.params['project_domain']:
|
||||
project_domain = self.conn.get_domain(name_or_id=self.params['project_domain'])
|
||||
if not project_domain or project_domain['id'] is None:
|
||||
self.fail(msg='Project domain %s could not be found' % self.params['project_domain'])
|
||||
project = self.conn.get_project(name_or_id=self.params['project'], domain_id=project_domain['id'])
|
||||
if not project:
|
||||
self.fail(msg='Project %s could not be found' % self.params['project'])
|
||||
kwargs['owner'] = project['id']
|
||||
image = self.conn.create_image(
|
||||
name=self.params['name'],
|
||||
filename=self.params['filename'],
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: image_info
|
||||
short_description: Retrieve information about an image within OpenStack.
|
||||
author: OpenStack Ansible SIG
|
||||
@@ -17,11 +18,12 @@ options:
|
||||
- Name or ID of the image
|
||||
required: false
|
||||
type: str
|
||||
properties:
|
||||
filters:
|
||||
description:
|
||||
- Dict of properties of the images used for query
|
||||
type: dict
|
||||
required: false
|
||||
aliases: ['properties']
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
- "openstacksdk"
|
||||
@@ -43,7 +45,7 @@ EXAMPLES = '''
|
||||
|
||||
- name: Show openstack information
|
||||
debug:
|
||||
msg: "{{ result.openstack_image }}"
|
||||
msg: "{{ result.image }}"
|
||||
|
||||
# Show all available Openstack images
|
||||
- name: Retrieve all available Openstack images
|
||||
@@ -52,22 +54,22 @@ EXAMPLES = '''
|
||||
|
||||
- name: Show images
|
||||
debug:
|
||||
msg: "{{ result.openstack_image }}"
|
||||
msg: "{{ result.image }}"
|
||||
|
||||
# Show images matching requested properties
|
||||
- name: Retrieve images having properties with desired values
|
||||
openstack.cloud.image_facts:
|
||||
properties:
|
||||
filters:
|
||||
some_property: some_value
|
||||
OtherProp: OtherVal
|
||||
|
||||
- name: Show images
|
||||
debug:
|
||||
msg: "{{ result.openstack_image }}"
|
||||
msg: "{{ result.image }}"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
openstack_image:
|
||||
openstack_images:
|
||||
description: has all the openstack information about the image
|
||||
returned: always, but can be null
|
||||
type: complex
|
||||
@@ -92,6 +94,10 @@ openstack_image:
|
||||
description: Container format of the image.
|
||||
returned: success
|
||||
type: str
|
||||
direct_url:
|
||||
description: URL to access the image file kept in external store.
|
||||
returned: success
|
||||
type: str
|
||||
min_ram:
|
||||
description: Min amount of RAM required for this image.
|
||||
returned: success
|
||||
@@ -100,19 +106,39 @@ openstack_image:
|
||||
description: Disk format of the image.
|
||||
returned: success
|
||||
type: str
|
||||
file:
|
||||
description: The URL for the virtual machine image file.
|
||||
returned: success
|
||||
type: str
|
||||
os_hidden:
|
||||
description: Controls whether an image is displayed in the default image-list response
|
||||
returned: success
|
||||
type: bool
|
||||
locations:
|
||||
description: A list of URLs to access the image file in external store.
|
||||
returned: success
|
||||
type: str
|
||||
metadata:
|
||||
description: The location metadata.
|
||||
returned: success
|
||||
type: str
|
||||
schema:
|
||||
description: URL for the schema describing a virtual machine image.
|
||||
returned: success
|
||||
type: str
|
||||
updated_at:
|
||||
description: Image updated at timestamp.
|
||||
returned: success
|
||||
type: str
|
||||
properties:
|
||||
description: Additional properties associated with the image.
|
||||
virtual_size:
|
||||
description: The virtual size of the image.
|
||||
returned: success
|
||||
type: dict
|
||||
type: str
|
||||
min_disk:
|
||||
description: Min amount of disk space required for this image.
|
||||
returned: success
|
||||
type: int
|
||||
protected:
|
||||
is_protected:
|
||||
description: Image protected flag.
|
||||
returned: success
|
||||
type: bool
|
||||
@@ -124,10 +150,10 @@ openstack_image:
|
||||
description: Owner for the image.
|
||||
returned: success
|
||||
type: str
|
||||
is_public:
|
||||
description: Is public flag of the image.
|
||||
visibility:
|
||||
description: Indicates who has access to the image.
|
||||
returned: success
|
||||
type: bool
|
||||
type: str
|
||||
size:
|
||||
description: Size of the image.
|
||||
returned: success
|
||||
@@ -137,7 +163,6 @@ openstack_image:
|
||||
returned: success
|
||||
type: list
|
||||
'''
|
||||
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
||||
|
||||
|
||||
@@ -147,20 +172,27 @@ class ImageInfoModule(OpenStackModule):
|
||||
|
||||
argument_spec = dict(
|
||||
image=dict(type='str', required=False),
|
||||
properties=dict(type='dict', required=False),
|
||||
filters=dict(type='dict', required=False, aliases=['properties']),
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
args = {
|
||||
'name_or_id': self.params['image'],
|
||||
'filters': self.params['filters'],
|
||||
}
|
||||
args = {k: v for k, v in args.items() if v is not None}
|
||||
images = self.conn.search_images(**args)
|
||||
|
||||
if self.params['image']:
|
||||
image = self.conn.get_image(self.params['image'])
|
||||
self.exit(changed=False, openstack_image=image)
|
||||
# for backward compatibility
|
||||
if 'name_or_id' in args:
|
||||
image = images[0] if images else None
|
||||
else:
|
||||
images = self.conn.search_images(filters=self.params['properties'])
|
||||
self.exit(changed=False, openstack_image=images)
|
||||
image = images
|
||||
|
||||
self.exit(changed=False, openstack_images=images, image=image)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -113,7 +113,7 @@ class KeyPairModule(OpenStackModule):
|
||||
|
||||
if self.params['public_key_file']:
|
||||
with open(self.params['public_key_file']) as public_key_fh:
|
||||
public_key = public_key_fh.read().rstrip()
|
||||
public_key = public_key_fh.read()
|
||||
|
||||
keypair = self.conn.get_keypair(name)
|
||||
|
||||
|
||||
@@ -54,7 +54,8 @@ RETURN = '''
|
||||
openstack_keypairs:
|
||||
description:
|
||||
- Lists keypairs that are associated with the account.
|
||||
type: complex
|
||||
type: list
|
||||
elements: dict
|
||||
returned: always
|
||||
contains:
|
||||
created_at:
|
||||
@@ -120,31 +121,15 @@ class KeyPairInfoModule(OpenStackModule):
|
||||
)
|
||||
|
||||
def run(self):
|
||||
name = self.params['name']
|
||||
user_id = self.params['user_id']
|
||||
limit = self.params['limit']
|
||||
marker = self.params['marker']
|
||||
|
||||
filters = {}
|
||||
data = []
|
||||
|
||||
if user_id:
|
||||
filters['user_id'] = user_id
|
||||
if limit:
|
||||
filters['limit'] = limit
|
||||
if marker:
|
||||
filters['marker'] = marker
|
||||
|
||||
result = self.conn.search_keypairs(name_or_id=name,
|
||||
filters=filters)
|
||||
raws = [raw if isinstance(raw, dict) else raw.to_dict()
|
||||
for raw in result]
|
||||
|
||||
for raw in raws:
|
||||
raw.pop('location')
|
||||
data.append(raw)
|
||||
|
||||
self.exit(changed=False, openstack_keypairs=data)
|
||||
filters = {k: self.params[k] for k in
|
||||
['user_id', 'name', 'limit', 'marker']
|
||||
if self.params[k] is not None}
|
||||
keypairs = self.conn.search_keypairs(name_or_id=self.params['name'],
|
||||
filters=filters)
|
||||
# self.conn.search_keypairs() returned munch.Munch objects before Train
|
||||
result = [raw if isinstance(raw, dict) else raw.to_dict(computed=False)
|
||||
for raw in keypairs]
|
||||
self.exit(changed=False, openstack_keypairs=result)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -12,42 +12,42 @@ description:
|
||||
updated. Only the I(records), I(description), and I(ttl) values
|
||||
can be updated.
|
||||
options:
|
||||
zone:
|
||||
description:
|
||||
description:
|
||||
- Zone managing the recordset
|
||||
required: true
|
||||
- Description of the recordset
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Name of the recordset. It must be ended with name of dns zone.
|
||||
required: true
|
||||
type: str
|
||||
recordset_type:
|
||||
description:
|
||||
- Recordset type
|
||||
- Required when I(state=present).
|
||||
choices: ['a', 'aaaa', 'mx', 'cname', 'txt', 'ns', 'srv', 'ptr', 'caa']
|
||||
type: str
|
||||
records:
|
||||
description:
|
||||
- List of recordset definitions.
|
||||
- Required when I(state=present).
|
||||
type: list
|
||||
elements: str
|
||||
description:
|
||||
recordset_type:
|
||||
description:
|
||||
- Description of the recordset
|
||||
- Recordset type
|
||||
- Required when I(state=present).
|
||||
choices: ['a', 'aaaa', 'mx', 'cname', 'txt', 'ns', 'srv', 'ptr', 'caa']
|
||||
type: str
|
||||
ttl:
|
||||
description:
|
||||
- TTL (Time To Live) value in seconds
|
||||
type: int
|
||||
state:
|
||||
description:
|
||||
- Should the resource be present or absent.
|
||||
choices: [present, absent]
|
||||
default: present
|
||||
type: str
|
||||
ttl:
|
||||
description:
|
||||
- TTL (Time To Live) value in seconds
|
||||
type: int
|
||||
zone:
|
||||
description:
|
||||
- Name or ID of the zone which manages the recordset
|
||||
required: true
|
||||
type: str
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
- "openstacksdk"
|
||||
@@ -90,36 +90,73 @@ RETURN = '''
|
||||
recordset:
|
||||
description: Dictionary describing the recordset.
|
||||
returned: On success when I(state) is 'present'.
|
||||
type: complex
|
||||
type: dict
|
||||
contains:
|
||||
id:
|
||||
description: Unique recordset ID
|
||||
action:
|
||||
description: Current action in progress on the resource
|
||||
type: str
|
||||
sample: "c1c530a3-3619-46f3-b0f6-236927b2618c"
|
||||
name:
|
||||
description: Recordset name
|
||||
returned: always
|
||||
created_at:
|
||||
description: Timestamp when the zone was created
|
||||
type: str
|
||||
sample: "www.example.net."
|
||||
zone_id:
|
||||
description: Zone id
|
||||
type: str
|
||||
sample: 9508e177-41d8-434e-962c-6fe6ca880af7
|
||||
type:
|
||||
description: Recordset type
|
||||
type: str
|
||||
sample: "A"
|
||||
returned: always
|
||||
description:
|
||||
description: Recordset description
|
||||
type: str
|
||||
sample: "Test description"
|
||||
ttl:
|
||||
description: Zone TTL value
|
||||
type: int
|
||||
sample: 3600
|
||||
returned: always
|
||||
id:
|
||||
description: Unique recordset ID
|
||||
type: str
|
||||
sample: "c1c530a3-3619-46f3-b0f6-236927b2618c"
|
||||
links:
|
||||
description: Links related to the resource
|
||||
type: dict
|
||||
returned: always
|
||||
name:
|
||||
description: Recordset name
|
||||
type: str
|
||||
sample: "www.example.net."
|
||||
returned: always
|
||||
project_id:
|
||||
description: ID of the proect to which the recordset belongs
|
||||
type: str
|
||||
returned: always
|
||||
records:
|
||||
description: Recordset records
|
||||
type: list
|
||||
sample: ['10.0.0.1']
|
||||
returned: always
|
||||
status:
|
||||
description:
|
||||
- Recordset status
|
||||
- Valid values include `PENDING_CREATE`, `ACTIVE`,`PENDING_DELETE`,
|
||||
`ERROR`
|
||||
type: str
|
||||
returned: always
|
||||
ttl:
|
||||
description: Zone TTL value
|
||||
type: int
|
||||
sample: 3600
|
||||
returned: always
|
||||
type:
|
||||
description:
|
||||
- Recordset type
|
||||
- Valid values include `A`, `AAAA`, `MX`, `CNAME`, `TXT`, `NS`,
|
||||
`SSHFP`, `SPF`, `SRV`, `PTR`
|
||||
type: str
|
||||
sample: "A"
|
||||
returned: always
|
||||
zone_id:
|
||||
description: The id of the Zone which this recordset belongs to
|
||||
type: str
|
||||
sample: 9508e177-41d8-434e-962c-6fe6ca880af7
|
||||
returned: always
|
||||
zone_name:
|
||||
description: The name of the Zone which this recordset belongs to
|
||||
type: str
|
||||
sample: "example.com."
|
||||
returned: always
|
||||
'''
|
||||
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
||||
@@ -127,13 +164,13 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O
|
||||
|
||||
class DnsRecordsetModule(OpenStackModule):
|
||||
argument_spec = dict(
|
||||
zone=dict(required=True),
|
||||
name=dict(required=True),
|
||||
recordset_type=dict(required=False, choices=['a', 'aaaa', 'mx', 'cname', 'txt', 'ns', 'srv', 'ptr', 'caa']),
|
||||
records=dict(required=False, type='list', elements='str'),
|
||||
description=dict(required=False, default=None),
|
||||
ttl=dict(required=False, type='int'),
|
||||
name=dict(required=True),
|
||||
records=dict(required=False, type='list', elements='str'),
|
||||
recordset_type=dict(required=False, choices=['a', 'aaaa', 'mx', 'cname', 'txt', 'ns', 'srv', 'ptr', 'caa']),
|
||||
state=dict(default='present', choices=['absent', 'present']),
|
||||
ttl=dict(required=False, type='int'),
|
||||
zone=dict(required=True),
|
||||
)
|
||||
|
||||
module_kwargs = dict(
|
||||
@@ -145,88 +182,73 @@ class DnsRecordsetModule(OpenStackModule):
|
||||
|
||||
module_min_sdk_version = '0.28.0'
|
||||
|
||||
def _system_state_change(self, state, records, description, ttl, recordset):
|
||||
def _needs_update(self, params, recordset):
|
||||
for k in ('description', 'records', 'ttl'):
|
||||
if k not in params:
|
||||
continue
|
||||
if params[k] is not None and params[k] != recordset[k]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _system_state_change(self, state, recordset):
|
||||
if state == 'present':
|
||||
if recordset is None:
|
||||
return True
|
||||
if records is not None and recordset['records'] != records:
|
||||
return True
|
||||
if description is not None and recordset['description'] != description:
|
||||
return True
|
||||
if ttl is not None and recordset['ttl'] != ttl:
|
||||
return True
|
||||
kwargs = self._build_params()
|
||||
return self._needs_update(kwargs, recordset)
|
||||
if state == 'absent' and recordset:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _build_params(self):
|
||||
recordset_type = self.params['recordset_type']
|
||||
records = self.params['records']
|
||||
description = self.params['description']
|
||||
ttl = self.params['ttl']
|
||||
params = {
|
||||
'description': description,
|
||||
'records': records,
|
||||
'type': recordset_type.upper(),
|
||||
'ttl': ttl,
|
||||
}
|
||||
return {k: v for k, v in params.items() if v is not None}
|
||||
|
||||
def run(self):
|
||||
zone = self.params.get('zone')
|
||||
name = self.params.get('name')
|
||||
state = self.params.get('state')
|
||||
ttl = self.params.get('ttl')
|
||||
|
||||
recordsets = self.conn.search_recordsets(zone, name_or_id=name)
|
||||
|
||||
recordset = None
|
||||
if recordsets:
|
||||
recordset = recordsets[0]
|
||||
try:
|
||||
recordset_id = recordset['id']
|
||||
except KeyError as e:
|
||||
self.fail_json(msg=str(e))
|
||||
else:
|
||||
# recordsets is filtered by type and should never be more than 1 return
|
||||
recordset = None
|
||||
|
||||
if self.ansible.check_mode:
|
||||
self.exit_json(changed=self._system_state_change(state, recordset))
|
||||
|
||||
changed = False
|
||||
if state == 'present':
|
||||
recordset_type = self.params.get('recordset_type').upper()
|
||||
records = self.params.get('records')
|
||||
description = self.params.get('description')
|
||||
ttl = self.params.get('ttl')
|
||||
|
||||
kwargs = {}
|
||||
if description:
|
||||
kwargs['description'] = description
|
||||
kwargs['records'] = records
|
||||
|
||||
if self.ansible.check_mode:
|
||||
self.exit_json(
|
||||
changed=self._system_state_change(
|
||||
state, records, description, ttl, recordset))
|
||||
|
||||
kwargs = self._build_params()
|
||||
if recordset is None:
|
||||
if ttl:
|
||||
kwargs['ttl'] = ttl
|
||||
else:
|
||||
kwargs['ttl'] = 300
|
||||
|
||||
recordset = self.conn.create_recordset(
|
||||
zone=zone, name=name, recordset_type=recordset_type,
|
||||
**kwargs)
|
||||
kwargs['ttl'] = ttl or 300
|
||||
type = kwargs.pop('type', None)
|
||||
if type is not None:
|
||||
kwargs['recordset_type'] = type
|
||||
recordset = self.conn.create_recordset(zone=zone, name=name,
|
||||
**kwargs)
|
||||
changed = True
|
||||
elif self._needs_update(kwargs, recordset):
|
||||
type = kwargs.pop('type', None)
|
||||
recordset = self.conn.update_recordset(zone, recordset['id'],
|
||||
**kwargs)
|
||||
changed = True
|
||||
else:
|
||||
|
||||
if ttl:
|
||||
kwargs['ttl'] = ttl
|
||||
|
||||
pre_update_recordset = recordset
|
||||
changed = self._system_state_change(
|
||||
state, records, description, ttl, pre_update_recordset)
|
||||
if changed:
|
||||
recordset = self.conn.update_recordset(
|
||||
zone=zone, name_or_id=recordset_id, **kwargs)
|
||||
|
||||
self.exit_json(changed=changed, recordset=recordset)
|
||||
|
||||
elif state == 'absent':
|
||||
if self.ansible.check_mode:
|
||||
self.exit_json(changed=self._system_state_change(
|
||||
state, None, None, None, recordset))
|
||||
|
||||
if recordset is None:
|
||||
changed = False
|
||||
else:
|
||||
self.conn.delete_recordset(zone, recordset_id)
|
||||
changed = True
|
||||
self.exit_json(changed=changed)
|
||||
elif state == 'absent' and recordset is not None:
|
||||
self.conn.delete_recordset(zone, recordset['id'])
|
||||
changed = True
|
||||
self.exit_json(changed=changed)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -120,51 +120,42 @@ class IdentityRoleAssignmentModule(OpenStackModule):
|
||||
state = self.params.get('state')
|
||||
|
||||
filters = {}
|
||||
find_filters = {}
|
||||
domain_id = None
|
||||
|
||||
r = self.conn.get_role(role)
|
||||
r = self.conn.identity.find_role(role)
|
||||
if r is None:
|
||||
self.fail_json(msg="Role %s is not valid" % role)
|
||||
filters['role'] = r['id']
|
||||
|
||||
if domain:
|
||||
d = self.conn.get_domain(name_or_id=domain)
|
||||
d = self.conn.identity.find_domain(domain)
|
||||
if d is None:
|
||||
self.fail_json(msg="Domain %s is not valid" % domain)
|
||||
filters['domain'] = d['id']
|
||||
domain_id = d['id']
|
||||
find_filters['domain_id'] = domain_id
|
||||
if user:
|
||||
if domain:
|
||||
u = self.conn.get_user(user, domain_id=filters['domain'])
|
||||
else:
|
||||
u = self.conn.get_user(user)
|
||||
|
||||
u = self.conn.identity.find_user(user, **find_filters)
|
||||
if u is None:
|
||||
self.fail_json(msg="User %s is not valid" % user)
|
||||
filters['user'] = u['id']
|
||||
|
||||
if group:
|
||||
if domain:
|
||||
g = self.conn.get_group(group, domain_id=filters['domain'])
|
||||
else:
|
||||
g = self.conn.get_group(group)
|
||||
# self.conn.identity.find_group() does not accept
|
||||
# a domain_id argument in Train's openstacksdk
|
||||
g = self.conn.get_group(group, **find_filters)
|
||||
if g is None:
|
||||
self.fail_json(msg="Group %s is not valid" % group)
|
||||
filters['group'] = g['id']
|
||||
if project:
|
||||
if domain:
|
||||
p = self.conn.get_project(project, domain_id=filters['domain'])
|
||||
# OpenStack won't allow us to use both a domain and project as
|
||||
# filter. Once we identified the project (using the domain as
|
||||
# a filter criteria), we need to remove the domain itself from
|
||||
# the filters list.
|
||||
domain_id = filters.pop('domain')
|
||||
else:
|
||||
p = self.conn.get_project(project)
|
||||
|
||||
p = self.conn.identity.find_project(project, **find_filters)
|
||||
if p is None:
|
||||
self.fail_json(msg="Project %s is not valid" % project)
|
||||
filters['project'] = p['id']
|
||||
|
||||
# Keeping the self.conn.list_role_assignments because it calls directly
|
||||
# the identity.role_assignments and there are some logics for the
|
||||
# filters that won't worth rewrite here.
|
||||
assignment = self.conn.list_role_assignments(filters=filters)
|
||||
|
||||
if self.ansible.check_mode:
|
||||
@@ -172,6 +163,9 @@ class IdentityRoleAssignmentModule(OpenStackModule):
|
||||
|
||||
changed = False
|
||||
|
||||
# Both grant_role and revoke_role calls directly the proxy layer, and
|
||||
# has some logic that won't worth to rewrite here so keeping it is a
|
||||
# good idea
|
||||
if state == 'present':
|
||||
if not assignment:
|
||||
kwargs = self._build_kwargs(user, group, project, domain_id)
|
||||
|
||||
@@ -179,7 +179,7 @@ class RouterInfoModule(OpenStackModule):
|
||||
'ip_address': ip_spec.get('ip_address'),
|
||||
'subnet_id': ip_spec.get('subnet_id')
|
||||
}
|
||||
interfaces_info.append(int_info)
|
||||
interfaces_info.append(int_info)
|
||||
router['interfaces_info'] = interfaces_info
|
||||
|
||||
self.exit(changed=False, openstack_routers=routers)
|
||||
|
||||
@@ -63,6 +63,11 @@ options:
|
||||
- Unique name or ID of the project.
|
||||
required: false
|
||||
type: str
|
||||
description:
|
||||
required: false
|
||||
description:
|
||||
- Description of the rule.
|
||||
type: str
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
- "openstacksdk"
|
||||
@@ -256,6 +261,7 @@ class SecurityGroupRuleModule(OpenStackModule):
|
||||
choices=['egress', 'ingress']),
|
||||
state=dict(default='present',
|
||||
choices=['absent', 'present']),
|
||||
description=dict(required=False, default=None),
|
||||
project=dict(default=None),
|
||||
)
|
||||
|
||||
@@ -349,10 +355,12 @@ class SecurityGroupRuleModule(OpenStackModule):
|
||||
kwargs = {}
|
||||
if project_id:
|
||||
kwargs['project_id'] = project_id
|
||||
rule = self.conn.create_security_group_rule(
|
||||
secgroup['id'],
|
||||
port_range_min=self.params['port_range_min'],
|
||||
port_range_max=self.params['port_range_max'],
|
||||
if self.params["description"] is not None:
|
||||
kwargs["description"] = self.params['description']
|
||||
rule = self.conn.network.create_security_group_rule(
|
||||
security_group_id=secgroup['id'],
|
||||
port_range_min=None if self.params['port_range_min'] == -1 else self.params['port_range_min'],
|
||||
port_range_max=None if self.params['port_range_max'] == -1 else self.params['port_range_max'],
|
||||
protocol=self.params['protocol'],
|
||||
remote_ip_prefix=self.params['remote_ip_prefix'],
|
||||
remote_group_id=remotegroup['id'],
|
||||
|
||||
@@ -1 +1 @@
|
||||
openstacksdk>=0.36
|
||||
openstacksdk>=0.36,<0.99.0
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
openstacksdk<1.0.0
|
||||
# 0.99.x are release candidates for the first major release of OpenStackSDK 1.x.x
|
||||
openstacksdk<0.99.0
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
openstacksdk>=1.0.0
|
||||
# 0.99.x are release candidates for the first major release of OpenStackSDK 1.x.x
|
||||
openstacksdk>=0.99.0
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
ansible-core
|
||||
# ansible.builtin.user module in ansible-core 2.13.0 and 2.13.1 is affected by #78017
|
||||
# Ref.: https://github.com/ansible/ansible/issues/78017
|
||||
#
|
||||
# TODO: Remove ansible-core constraint once issue #78017 has been fixed.
|
||||
ansible-core!=2.13.0,!=2.13.1
|
||||
flake8
|
||||
galaxy-importer
|
||||
openstacksdk
|
||||
|
||||
@@ -104,7 +104,7 @@ class TestNetworkArgs(object):
|
||||
nics: net-id=1234
|
||||
'''
|
||||
args = os_server._network_args(self.module, self.cloud)
|
||||
assert(args[0]['net-id'] == '1234')
|
||||
assert args[0]['net-id'] == '1234'
|
||||
|
||||
def test_nics_string_net_id_list(self):
|
||||
'''
|
||||
@@ -112,8 +112,8 @@ class TestNetworkArgs(object):
|
||||
nics: net-id=1234,net-id=4321
|
||||
'''
|
||||
args = os_server._network_args(self.module, self.cloud)
|
||||
assert(args[0]['net-id'] == '1234')
|
||||
assert(args[1]['net-id'] == '4321')
|
||||
assert args[0]['net-id'] == '1234'
|
||||
assert args[1]['net-id'] == '4321'
|
||||
|
||||
def test_nics_string_port_id(self):
|
||||
'''
|
||||
@@ -121,7 +121,7 @@ class TestNetworkArgs(object):
|
||||
nics: port-id=1234
|
||||
'''
|
||||
args = os_server._network_args(self.module, self.cloud)
|
||||
assert(args[0]['port-id'] == '1234')
|
||||
assert args[0]['port-id'] == '1234'
|
||||
|
||||
def test_nics_string_net_name(self):
|
||||
'''
|
||||
@@ -129,7 +129,7 @@ class TestNetworkArgs(object):
|
||||
nics: net-name=network1
|
||||
'''
|
||||
args = os_server._network_args(self.module, self.cloud)
|
||||
assert(args[0]['net-id'] == '5678')
|
||||
assert args[0]['net-id'] == '5678'
|
||||
|
||||
def test_nics_string_port_name(self):
|
||||
'''
|
||||
@@ -137,7 +137,7 @@ class TestNetworkArgs(object):
|
||||
nics: port-name=port1
|
||||
'''
|
||||
args = os_server._network_args(self.module, self.cloud)
|
||||
assert(args[0]['port-id'] == '1234')
|
||||
assert args[0]['port-id'] == '1234'
|
||||
|
||||
def test_nics_structured_net_id(self):
|
||||
'''
|
||||
@@ -146,7 +146,7 @@ class TestNetworkArgs(object):
|
||||
- net-id: '1234'
|
||||
'''
|
||||
args = os_server._network_args(self.module, self.cloud)
|
||||
assert(args[0]['net-id'] == '1234')
|
||||
assert args[0]['net-id'] == '1234'
|
||||
|
||||
def test_nics_structured_mixed(self):
|
||||
'''
|
||||
@@ -157,10 +157,10 @@ class TestNetworkArgs(object):
|
||||
- 'net-name=network1,port-id=4321'
|
||||
'''
|
||||
args = os_server._network_args(self.module, self.cloud)
|
||||
assert(args[0]['net-id'] == '1234')
|
||||
assert(args[1]['port-id'] == '1234')
|
||||
assert(args[2]['net-id'] == '5678')
|
||||
assert(args[3]['port-id'] == '4321')
|
||||
assert args[0]['net-id'] == '1234'
|
||||
assert args[1]['port-id'] == '1234'
|
||||
assert args[2]['net-id'] == '5678'
|
||||
assert args[3]['port-id'] == '4321'
|
||||
|
||||
|
||||
class TestCreateServer(object):
|
||||
@@ -190,10 +190,10 @@ class TestCreateServer(object):
|
||||
with pytest.raises(AnsibleExit):
|
||||
os_server._create_server(self.module, self.cloud)
|
||||
|
||||
assert(self.cloud.create_server.call_count == 1)
|
||||
assert(self.cloud.create_server.call_args[1]['image'] == self.cloud.get_image_id('cirros'))
|
||||
assert(self.cloud.create_server.call_args[1]['flavor'] == self.cloud.get_flavor('m1.tiny')['id'])
|
||||
assert(self.cloud.create_server.call_args[1]['nics'][0]['net-id'] == self.cloud.get_network('network1')['id'])
|
||||
assert self.cloud.create_server.call_count == 1
|
||||
assert self.cloud.create_server.call_args[1]['image'] == self.cloud.get_image_id('cirros')
|
||||
assert self.cloud.create_server.call_args[1]['flavor'] == self.cloud.get_flavor('m1.tiny')['id']
|
||||
assert self.cloud.create_server.call_args[1]['nics'][0]['net-id'] == self.cloud.get_network('network1')['id']
|
||||
|
||||
def test_create_server_bad_flavor(self):
|
||||
'''
|
||||
@@ -206,8 +206,7 @@ class TestCreateServer(object):
|
||||
with pytest.raises(AnsibleFail):
|
||||
os_server._create_server(self.module, self.cloud)
|
||||
|
||||
assert('missing_flavor' in
|
||||
self.module.fail_json.call_args[1]['msg'])
|
||||
assert 'missing_flavor' in self.module.fail_json.call_args[1]['msg']
|
||||
|
||||
def test_create_server_bad_nic(self):
|
||||
'''
|
||||
@@ -220,5 +219,4 @@ class TestCreateServer(object):
|
||||
with pytest.raises(AnsibleFail):
|
||||
os_server._create_server(self.module, self.cloud)
|
||||
|
||||
assert('missing_network' in
|
||||
self.module.fail_json.call_args[1]['msg'])
|
||||
assert 'missing_network' in self.module.fail_json.call_args[1]['msg']
|
||||
|
||||
@@ -15,5 +15,10 @@
|
||||
|
||||
set -e
|
||||
|
||||
if python -c 'import sys; sys.exit(0 if sys.version_info[0:2] < (3, 6) else 1)'; then
|
||||
echo "Skipped Ansible Galaxy content importer check because it requires Python 3.6 or later" 2>&1
|
||||
exit
|
||||
fi
|
||||
|
||||
TOXDIR="${1:-.}"
|
||||
python -m galaxy_importer.main "$TOXDIR/build_artifact/"*
|
||||
|
||||
Reference in New Issue
Block a user