27 Commits
2.0.0 ... 2.2.0

Author SHA1 Message Date
gtema
fff978d273 Prepare release 2.2.0
Prepare data for the v2.2.0 release with few new modules and bugfixes.

Change-Id: Id593b623b389cedb140fb05e8063f48ef7eacc36
2023-12-01 17:29:42 +01:00
Zuul
9fb544d94a Merge "Fix port_security_enabled key for port module" 2023-10-17 09:28:52 +00:00
Simon Hensel
94ed95c8b6 Fix port_security_enabled key for port module
Changes to the port_security_enabled parameter are not applied due to
mismatching key names.
In the port module, the input parameter is called `port_security_enabled`,
while the OpenStackSDK is using a field called `is_port_security_enabled`.

When updating an existing port, the port module is comparing the dictionary
keys of the Ansible module parameters with those of the port object
returned by the OpenStackSDK.
Since these keys different, they will not match and changes to
port security are not applied.

Story: 2010687
Task: 47789
Change-Id: I838e9d6ebf1a281269add91724eac240abe35fd4
2023-10-17 08:56:21 +02:00
Zuul
4ab054790c Merge "Prevent routers to be always updated if no shared public network" 2023-10-16 15:31:30 +00:00
Zuul
2c68080758 Merge "Added module for volume type encription" 2023-10-16 14:42:15 +00:00
Zuul
6e680d594b Merge "Add volume_type related plugins/modules" 2023-10-16 14:29:57 +00:00
Dmitriy Rabotyagov
b25e93dbdd Prevent routers to be always updated if no shared public network
Current logic assumes that external_fixed_ips should be always defined,
otherwise `req_fip_map` is an empty sequence, which makes _needs_update
to return True.
With that not having external_fixed_ips is a vaild case whenever
deployment does not have shared public network. This usually
the case when public network is not passed to computes and public
network is used only for routers and floating IPs.

Patch changes logic by addind a `is not None` support to only compare
external_fip configration when user explicitly passed something (passing
an empty dict is equal to requesting "empty" configuration).

Co-Authored-by: Artem Goncharov
Change-Id: Id0f69fe4c985c4c38b493577250cad4e589b9d24
2023-10-16 12:04:50 +02:00
Will Szumski
0aedc268f1 Adds stateful parameter to security groups
This is a missing option.

Change-Id: Ic7b43093d9c35de8962978e9ee108cf7b5379fcd
2023-09-01 17:53:32 +00:00
Dmitriy Rabotyagov
9b47cb4b59 Fix usage of subnet_id key for router
At the moment `subnet` is an alias of `subnet_id`. The way, how aliases
work in ansible modules, is that ansible does add intended key to param
in case alias is used. When riginal key is used, aliases are not
populated.

Right now in case user define `subnet_id` instead of its alias `subnet`
module will fail with KeyError.

Change-Id: I5ce547352097ea821be4c9bbc18147575986c740
2023-09-01 07:23:05 +00:00
Zuul
8612171af3 Merge "Image filters should be dict not set" 2023-08-28 11:04:30 +00:00
arddennis
8f321eaeb2 Added module for volume type encription
New module to manipulate volume type encryption. Including simple CI
task to verify functionality.

Change-Id: I7380a5d258c3df1f9bd512aa4295868294391e31
2023-08-21 08:43:23 +02:00
Denys Mishchenko
147ad6c452 Add volume_type related plugins/modules
Added 2 new modules to manipulate volume types in OpenStack
* volume_type is used to create, delete and modify volume type
* volume_type_info is used to show volume_type details, including
  encryption information

ci tests extended with additional role to test basic module behaviour

It is currently impossible to update is_public volume type attribute
as it is being changed to "os-volume-type-access:is_public" which is not
expected by api. Which expects just "is_public"
https://docs.openstack.org/api-ref/block-storage/v3/?expanded=update-a-volume-type-detail#update-a-volume-type
Which results in "'os-volume-type-access:is_public' was unexpected"
reply. I guess the change is required by openstacksdk or on the API side

Change-Id: Idc26a5240b5f3314c8384c7326d8a82dcc8c6171
2023-08-16 16:35:51 +02:00
Dmitriy Rabotyagov
407369da6e Fix linters for mocking
Right now linters test fail due to a trivial issue in mock loader.
This aims to fix CI for the repo.

Change-Id: Ib58e70d3a54b75ca4cb9ad86b761db1ded157143
2023-08-15 20:16:31 +02:00
Dmitriy Rabotyagov
0a371445eb Image filters should be dict not set
At the moment we generate a set as a filter for image checksums which
lead to AttributeError in SDK:
'set' object has no attribute 'keys'

With that we ensure that supllying checksum does not cause
module crash.

Change-Id: I490f51950592f62c9ad81806593340779bf6dbdb
2023-07-25 12:49:28 +02:00
Joker 234
2808d1c155 fix(inventory): bug when using clouds_yaml_path
Before this fix the current implementation in combination with the most
recent openstacksdk (1.2.0) resulted in a list containing the default
values and another list inside this list containing the value of
clouds_yaml_path. The clouds_yaml_path value gets now added directly to
the list only if it was set.

Change-Id: I3c3b6f59393928d098e9b80c55b87fc6ee1e9912
2023-06-01 19:41:20 +02:00
Jakob Meng
c30e4db77c Publish 2.1.0 release
Change-Id: I142f14dd35720557539b9cf3489a3c12c31f342a
2023-04-18 08:46:14 +02:00
Jakob Meng
ab6f2e45c6 Change security group rules only when instructed to do so
Security group rules in module openstack.cloud.security_group
are changed/updated only when option 'security_group_rules' was
defined explicitly. This follows our policy of "apply no change"
when module options in our Ansible modules have not been set.

Story: 2010691
Task: 47795
Change-Id: I4a0cda46cb160b5321913b63ff1123d8b8a19705
2023-04-18 08:39:57 +02:00
Zuul
568adcb890 Merge "Add baremetal_deploy_template module" 2023-04-17 23:37:52 +00:00
Jakob Meng
18cf839db4 Highlight our mode of operation more prominently
Change-Id: I23d9f390ffbe2c67ebca4e114c9d417a8bd2065e
2023-03-30 10:02:46 +02:00
Mark Goddard
454a05452b Add baremetal_deploy_template module
This module supports managing deploy template resources in OpenStack
Ironic.

https: //docs.openstack.org/ironic/latest/admin/node-deployment.html#deploy-templates

Change-Id: I2d1b89e7cbd1a7e847f54ffd62778f953ba65863
2023-03-28 12:05:38 +00:00
Dmitriy Rabotyagov
ad9594dcd7 Fix mistake in compute_flavor_access notes
In order to execute addTenantAccess or removeTenantAccess
a flavor should have have is_public set to false, which means it must
be private.

Change-Id: Iea1c4e7167b7134a4f70a4fb44fc0a8676265419
2023-03-14 18:43:35 +01:00
Jakob Meng
62c0169e64 Fixed private option in inventory plugin
Story: 2010614
Task: 47538
Change-Id: I64e1b3ce5323ca8e351ee9faef4bddbef53dfd5d
2023-03-06 21:07:23 +01:00
Christian Kueppers
497f020100 Fix for AttributeError: 'dict' object has no attribute 'status'.
Story: 2010610
Task: 47505
Change-Id: I9e138d8f282de9adfb3d7e1142c10ab77c22578e
2023-03-05 09:41:42 +00:00
Jakob Meng
92c3e87467 Respect description option and delete security group rules first
The description option of security group rules will now be used properly
when creating new rules.

Security group rules have to be deleted first before new ones get
created, because if one changes one rule attribute such as its
description, then the old rule must be deleted before recreating it,
as rules cannot be updated.

Story: 2010605
Task: 47486

Change-Id: I75b900e6675f7ec33532089738a6c2bfc10a898b
2023-02-23 21:20:51 +01:00
Jakob Meng
f73a0e385e Use true and false instead of yes and no for boolean values
Story: 2010586
Task: 47380
Change-Id: I1b88aa925d823d74b2d012153dfe26d35c93dfd5
2023-02-21 13:07:29 +01:00
Jakob Meng
b6b5f63877 Do not hardcode image name and properly delete mock images in CI tests
Change-Id: I2962dd1d1c27989cbd135b051cfb3e9f8c9e3823
2023-02-21 13:06:44 +01:00
Sagi Shnaidman
edd4e1b2e9 Fix issue with multiple records in recordset
Story: #2010527
Task: #47136

Sort records in recordset so it can be compared to existing ones
and not to trigger update in case of a different order.

Change-Id: Ib5d2af56616532174c29ec2be86827ccd0a17940
2023-02-01 21:26:26 +02:00
52 changed files with 1418 additions and 149 deletions

View File

@@ -5,6 +5,52 @@ Ansible OpenStack Collection Release Notes
.. contents:: Topics
v2.2.0
======
Release Summary
---------------
New module for volume_type and bugfixes
Minor Changes
-------------
- Add volume_encryption_type modules
- Add volume_type modules
Bugfixes
--------
- Fix image module filter
- Fix port module idempotency
- Fix router module idempotency
v2.1.0
======
Release Summary
---------------
New module for Ironic and bugfixes
Minor Changes
-------------
- Add baremetal_deploy_template module
- Highlight our mode of operation more prominently
Bugfixes
--------
- Change security group rules only when instructed to do so
- Fix for AttributeError: 'dict' object has no attribute 'status'
- Fix issue with multiple records in recordset
- Fix mistake in compute_flavor_access notes
- Fixed private option in inventory plugin
- Respect description option and delete security group rules first
- Use true and false instead of yes and no for boolean values
v2.0.0
======

View File

@@ -6,6 +6,12 @@ http://zuul.opendev.org/t/openstack/builds?project=openstack%2Fansible-collectio
Ansible OpenStack collection aka `openstack.cloud` provides Ansible modules and Ansible plugins for managing OpenStack
clouds. It is supported and maintained by the OpenStack community.
**NOTE:** We need and value your contributions! Maintaining this collection is a community effort. We are all both users
and developers of this collection at the same time. If you find a bug, please report it. If you have fixed a bug, please
submit a patch. If you need new functionality which is not covered by this collection yet, please extend an existing
module or submit a new one. Our [Contributing](#contributing) section below has tons of docs to check out. Please get in
touch!
## Branches and Non Backward Compatibility ⚠️
Our codebase has been split into two separate release series, `2.x.x` and `1.x.x`:
@@ -216,8 +222,7 @@ docs/reviewing.md) (⚠️) before sending your first patch. Pull requests submi
## Communication
We have a Special Interest Group for the Ansible OpenStack collection. Join us in `#openstack-ansible-sig` on
[OFTC IRC](https://www.oftc.net/) and ping Artem Goncharov <artem.goncharov@gmail.com> (gtema), Jakob Meng
<mail@jakobmeng.de> (jm1) or Sagi Shnaidman <sshnaidm@redhat.com> (sshnaidm) 🍪
[OFTC IRC](https://www.oftc.net/) 🍪
## License

View File

@@ -500,3 +500,29 @@ releases:
directory which could be used to generate and develop new Ansible modules
for this collection have been removed.
release_date: '2023-01-31'
2.1.0:
changes:
bugfixes:
- Change security group rules only when instructed to do so
- 'Fix for AttributeError: ''dict'' object has no attribute ''status'''
- Fix issue with multiple records in recordset
- Fix mistake in compute_flavor_access notes
- Fixed private option in inventory plugin
- Respect description option and delete security group rules first
- Use true and false instead of yes and no for boolean values
minor_changes:
- Add baremetal_deploy_template module
- Highlight our mode of operation more prominently
release_summary: New module for Ironic and bugfixes
release_date: '2023-04-19'
2.2.0:
changes:
bugfixes:
- Fix image module filter
- Fix port module idempotency
- Fix router module idempotency
minor_changes:
- Add volume_encryption_type modules
- Add volume_type modules
release_summary: New module for volume_type and bugfixes
release_date: '2023-12-01'

View File

@@ -0,0 +1,7 @@
expected_fields:
- created_at
- extra
- id
- name
- steps
- updated_at

View File

@@ -0,0 +1,58 @@
---
# TODO: Actually run this role in CI. Atm we do not have DevStack's ironic plugin enabled.
- name: Create baremetal deploy template
openstack.cloud.baremetal_deploy_template:
cloud: "{{ cloud }}"
state: present
name: CUSTOM_ANSIBLE_DEPLOY_TEMPLATE
steps:
- interface: bios
step: apply_configuration
args:
settings:
- name: some-setting
value: some-value
priority: 110
register: template
- debug: var=template
- name: Assert return values of baremetal_deploy_template module
assert:
that:
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(template.template.keys())|length == 0
- name: Update baremetal deploy template
openstack.cloud.baremetal_deploy_template:
cloud: "{{ cloud }}"
state: present
id: "{{ template.template.id }}"
extra:
foo: bar
register: updated_template
- name: Assert return values of updated baremetal deploy template
assert:
that:
- updated_template is changed
- updated_template.template.id == template.template.id
- name: Update baremetal deploy template again
openstack.cloud.baremetal_deploy_template:
cloud: "{{ cloud }}"
state: present
id: "{{ template.template.id }}"
register: updated_template
- name: Assert return values of updated baremetal deploy template
assert:
that:
- updated_template is not changed
- updated_template.template.id == template.template.id
- name: Delete Bare Metal deploy template
openstack.cloud.baremetal_deploy_template:
cloud: "{{ cloud }}"
state: absent
id: "{{ template.template.id }}"

View File

@@ -19,7 +19,7 @@
name: '{{ mapping_name }}'
rules: '{{ mapping_rules_1 }}'
register: create_mapping
check_mode: yes
check_mode: true
- assert:
that:
@@ -91,7 +91,7 @@
name: '{{ mapping_name }}'
rules: '{{ mapping_rules_1 }}'
register: create_mapping
check_mode: yes
check_mode: true
- assert:
that:
@@ -117,7 +117,7 @@
name: '{{ mapping_name }}'
rules: '{{ mapping_rules_2 }}'
register: update_mapping
check_mode: yes
check_mode: true
- assert:
that:
@@ -196,7 +196,7 @@
state: 'absent'
name: '{{ mapping_name }}'
register: delete_mapping
check_mode: yes
check_mode: true
- assert:
that:
@@ -217,7 +217,7 @@
state: 'absent'
name: '{{ mapping_name }}'
register: delete_mapping
check_mode: yes
check_mode: true
- assert:
that:
@@ -257,10 +257,10 @@
openstack.cloud.federation_mapping:
state: 'absent'
name: '{{ mapping_name }}'
ignore_errors: yes
ignore_errors: true
- name: 'Delete second mapping'
openstack.cloud.federation_mapping:
state: 'absent'
name: '{{ mapping_name_2 }}'
ignore_errors: yes
ignore_errors: true

View File

@@ -214,7 +214,7 @@
cloud: "{{ cloud }}"
state: present
server: ansible_server1
wait: yes
wait: true
- name: Get floating ip attached to server 1
openstack.cloud.floating_ip_info:
@@ -302,7 +302,7 @@
openstack.cloud.floating_ip:
cloud: "{{ cloud }}"
state: present
reuse: no # else fixed_address will be ignored
reuse: false # else fixed_address will be ignored
server: ansible_server2
network: public
fixed_address: "{{ port2.port.fixed_ips[0].ip_address }}"
@@ -347,12 +347,12 @@
openstack.cloud.floating_ip:
cloud: "{{ cloud }}"
state: present
reuse: no # else fixed_address will be ignored
reuse: false # else fixed_address will be ignored
server: ansible_server2
network: ansible_external
fixed_address: "{{ port3.port.fixed_ips[0].ip_address }}"
floating_ip_address: "10.6.6.150"
wait: no # does not work anyway and causes issues in local testing
wait: false # does not work anyway and causes issues in local testing
- name: Get floating ip attached to server 2
openstack.cloud.floating_ip_info:

View File

@@ -28,7 +28,7 @@
domain: default
default_project: demo
register: user
ignore_errors: yes
ignore_errors: true
- name: Assert that update failed
assert:
@@ -93,7 +93,7 @@
update_password: always
email: updated.ansible.user@nowhere.net
register: user
ignore_errors: yes
ignore_errors: true
- name: Assert user update failed
assert:

View File

@@ -24,6 +24,13 @@
path: '{{ tmp_file.path }}'
size: 1M
- name: Calculating file checksum
ansible.builtin.stat:
path: "{{ tmp_file.path }}"
checksum_algorithm: sha512
get_checksum: true
register: image_details
- name: Ensure mock kernel and ramdisk images (defaults)
openstack.cloud.image:
cloud: "{{ cloud }}"
@@ -42,6 +49,7 @@
name: ansible_image
filename: "{{ tmp_file.path }}"
is_protected: true
checksum: "{{ image_details.stat.checksum }}"
disk_format: raw
tags:
- test
@@ -318,6 +326,15 @@
name: ansible_project
domain: default
- name: Delete mock kernel and ramdisk images
openstack.cloud.image:
cloud: "{{ cloud }}"
state: absent
name: "{{ item }}"
loop:
- cirros-vmlinuz
- cirros-initrd
- name: Delete test image file
file:
name: "{{ tmp_file.path }}"

View File

@@ -78,7 +78,7 @@
- name: Generate test key file
user:
name: "{{ ansible_env.USER }}"
generate_ssh_key: yes
generate_ssh_key: true
ssh_key_file: .ssh/shade_id_rsa
- name: Create keypair (file)

View File

@@ -78,7 +78,7 @@
# Creation
- name: Create protocol - CHECK MODE
check_mode: yes
check_mode: true
openstack.cloud.keystone_federation_protocol:
state: present
name: ansible_protocol1
@@ -119,7 +119,7 @@
- expected_fields|difference(protocol.protocol.keys())|length == 0
- name: Create protocol (retry - no change) - CHECK MODE
check_mode: yes
check_mode: true
openstack.cloud.keystone_federation_protocol:
state: present
name: ansible_protocol1
@@ -148,7 +148,7 @@
# Update
- name: Update protocol - CHECK MODE
check_mode: yes
check_mode: true
openstack.cloud.keystone_federation_protocol:
state: present
name: ansible_protocol1
@@ -174,7 +174,7 @@
- protocol.protocol.mapping_id == 'ansible_mapping2'
- name: Update protocol (retry - no change) - CHECK MODE
check_mode: yes
check_mode: true
openstack.cloud.keystone_federation_protocol:
state: present
name: ansible_protocol1
@@ -258,7 +258,7 @@
# Deletion
- name: Delete protocol - CHECK MODE
check_mode: yes
check_mode: true
openstack.cloud.keystone_federation_protocol:
state: absent
name: ansible_protocol1
@@ -279,7 +279,7 @@
- protocol is changed
- name: Delete protocol (retry - no change) - CHECK MODE
check_mode: yes
check_mode: true
openstack.cloud.keystone_federation_protocol:
state: absent
name: ansible_protocol1
@@ -307,35 +307,35 @@
state: absent
name: ansible_protocol1
idp_id: ansible_idp
ignore_errors: yes
ignore_errors: true
- name: Delete protocol (2)
openstack.cloud.keystone_federation_protocol:
state: absent
name: ansible_protocol2
idp_id: ansible_idp
ignore_errors: yes
ignore_errors: true
- name: Delete mapping 1
openstack.cloud.federation_mapping:
state: absent
name: ansible_mapping1
ignore_errors: yes
ignore_errors: true
- name: Delete mapping 2
openstack.cloud.federation_mapping:
state: absent
name: ansible_mapping2
ignore_errors: yes
ignore_errors: true
- name: Delete idp
openstack.cloud.federation_idp:
state: absent
name: ansible_idp
ignore_errors: yes
ignore_errors: true
- name: Delete domain
openstack.cloud.identity_domain:
state: absent
name: ansible_domain
ignore_errors: yes
ignore_errors: true

View File

@@ -116,7 +116,7 @@
_idps: '{{ idps.identity_providers }}'
- name: 'Create identity_provider (retry - no change) - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -150,7 +150,7 @@
# Update (simple cases)
- name: 'Update IDP set description - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -181,7 +181,7 @@
_idp: '{{ idp.identity_provider }}'
- name: 'Update IDP set description (retry - no change) - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -213,7 +213,7 @@
- name: 'Update IDP set Remote IDs - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -244,7 +244,7 @@
_idp: '{{ idp.identity_provider }}'
- name: 'Update IDP set Remote IDs (retry - no change) - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -275,7 +275,7 @@
_idp: '{{ idp.identity_provider }}'
- name: 'Update IDP set Enabled - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -306,7 +306,7 @@
_idp: '{{ idp.identity_provider }}'
- name: 'Update IDP set Enabled (retry - no change) - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -338,7 +338,7 @@
# If we don't specify anything to change, then nothing should change...
- name: 'Minimal call to IDP (no change) - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -371,7 +371,7 @@
# Update (mass-update)
- name: 'Update all updatable IDP parameters - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -406,7 +406,7 @@
_idp: '{{ idp.identity_provider }}'
- name: 'Update all updatable IDP parameters (no change) - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -444,7 +444,7 @@
# Create complex IDP
- name: 'Create complex IDP - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider2'
@@ -481,7 +481,7 @@
_idp: '{{ idp.identity_provider }}'
- name: 'Create complex IDP (retry - no change) - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider2'
@@ -558,7 +558,7 @@
- False in (idps.identity_providers | map(attribute='is_enabled'))
- name: 'Delete identity_provider - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: absent
name: 'ansible_identity_provider'
@@ -579,7 +579,7 @@
- idp is changed
- name: 'Delete identity_provider (retry - no change) - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: absent
name: 'ansible_identity_provider'

View File

@@ -23,7 +23,7 @@
- name: Check output of network info
# TODO: Remove ignore_errors once SDK's search_networks() (re)implemented searching by id
ignore_errors: yes
ignore_errors: true
assert:
that:
- result.networks|length == 1

View File

@@ -90,4 +90,4 @@
cloud: "{{ cloud }}"
state: absent
name: ansible_container2
delete_with_all_objects: yes
delete_with_all_objects: true

View File

@@ -169,7 +169,7 @@
- ip_address: 10.5.5.69
name: "{{ port_name }}"
network: "{{ network_name }}"
no_security_groups: yes
no_security_groups: true
state: present
register: port
@@ -192,7 +192,7 @@
subnet_id: "{{ subnet.subnet.id }}"
name: "{{ port_name }}"
network: "{{ network_name }}"
no_security_groups: yes
no_security_groups: true
state: present
register: port_again

View File

@@ -1,7 +1,7 @@
dns_zone_name: test.dns.zone.
recordset_name: testrecordset.test.dns.zone.
records: ['10.0.0.0']
updated_records: ['10.1.1.1']
records: ['10.0.0.0', '10.0.0.2']
updated_records: ['10.1.1.1', '10.0.0.2']
recordset_fields:
- action

View File

@@ -29,7 +29,7 @@
- recordset is changed
- recordset["recordset"].name == recordset_name
- recordset["recordset"].zone_name == dns_zone.zone.name
- recordset["recordset"].records == records
- recordset["recordset"].records | list | sort == records | list | sort
- name: Assert recordset fields
assert:
@@ -71,7 +71,7 @@
- recordset is changed
- recordset["recordset"].zone_name == dns_zone.zone.name
- recordset["recordset"].name == recordset_name
- recordset["recordset"].records == updated_records
- recordset["recordset"].records | list | sort == updated_records | list | sort
- name: Assert recordset fields
assert:

View File

@@ -384,7 +384,7 @@
- shade_subnet1
network: "{{ external_network_name }}"
external_fixed_ips:
- subnet: shade_subnet5
- subnet_id: shade_subnet5
ip: 10.6.6.100
- name: Gather routers info
@@ -412,7 +412,7 @@
external_gateway_info:
network: "{{ external_network_name }}"
external_fixed_ips:
- subnet: shade_subnet5
- subnet_id: shade_subnet5
ip: 10.6.6.100
- subnet: shade_subnet5
ip: 10.6.6.101
@@ -426,7 +426,7 @@
- shade_subnet1
network: "{{ external_network_name }}"
external_fixed_ips:
- subnet: shade_subnet5
- subnet_id: shade_subnet5
ip: 10.6.6.100
- subnet: shade_subnet5
ip: 10.6.6.101
@@ -461,7 +461,7 @@
- shade_subnet1
network: "{{ external_network_name }}"
external_fixed_ips:
- subnet: shade_subnet5
- subnet_id: shade_subnet5
ip: 10.6.6.101
- name: Update router (remove external fixed ips) again
@@ -473,7 +473,7 @@
- shade_subnet1
network: "{{ external_network_name }}"
external_fixed_ips:
- subnet: shade_subnet5
- subnet_id: shade_subnet5
ip: 10.6.6.101
register: router
@@ -506,7 +506,7 @@
- shade_subnet1
network: "{{ external_network_name }}"
external_fixed_ips:
- subnet: shade_subnet5
- subnet_id: shade_subnet5
ip: 10.6.6.101
- name: Gather routers info
@@ -533,7 +533,7 @@
- shade_subnet1
network: "{{ external_network_name }}"
external_fixed_ips:
- subnet: shade_subnet5
- subnet_id: shade_subnet5
ip: 10.6.6.101
register: router

View File

@@ -72,4 +72,25 @@
name: ansible_security_group
state: absent
- name: Create stateless security group
openstack.cloud.security_group:
cloud: "{{ cloud }}"
name: ansible_security_group_stateless
stateful: false
state: present
description: 'Created from Ansible playbook'
register: security_group_stateless
- name: Assert return values of security_group module
assert:
that:
- security_group_stateless.security_group.name == 'ansible_security_group_stateless'
- security_group_stateless.security_group.stateful == False
- name: Delete stateless security group
openstack.cloud.security_group:
cloud: "{{ cloud }}"
name: ansible_security_group_stateless
state: absent
- include_tasks: rules.yml

View File

@@ -32,7 +32,9 @@
- name: Assert return values of security_group_rule_info module
assert:
that:
- security_group_rules.security_group_rules | length == 0
- security_group_rules.security_group_rules | length in [1, 2]
- security_group_rules.security_group_rules | map(attribute='ether_type') | list | sort in
[['IPv4'], ['IPv6'], ['IPv4', 'IPv6']]
- name: Delete security group
openstack.cloud.security_group:
@@ -58,6 +60,47 @@
that:
- security_group is not changed
- name: Create security group without security group rules
openstack.cloud.security_group:
cloud: "{{ cloud }}"
name: ansible_security_group
security_group_rules: []
register: security_group
- name: Assert return values of security_group module
assert:
that:
- security_group is changed
- name: Create security group without security group rules again
openstack.cloud.security_group:
cloud: "{{ cloud }}"
name: ansible_security_group
security_group_rules: []
register: security_group
- name: Assert return values of security_group module
assert:
that:
- security_group is not changed
- name: Fetch security group rules
openstack.cloud.security_group_rule_info:
cloud: "{{ cloud }}"
security_group: ansible_security_group
register: security_group_rules
- name: Assert return values of security_group_rule_info module
assert:
that:
- security_group_rules.security_group_rules | length == 0
- name: Delete security group without security group rules
openstack.cloud.security_group:
cloud: "{{ cloud }}"
name: ansible_security_group
state: absent
- name: Create security group including security group rules
openstack.cloud.security_group:
cloud: "{{ cloud }}"
@@ -263,10 +306,11 @@
name: ansible_security_group
state: absent
- name: Create security group
- name: Create security group without security group rules
openstack.cloud.security_group:
cloud: "{{ cloud }}"
name: ansible_security_group
security_group_rules: []
state: present
register: security_group

View File

@@ -224,7 +224,7 @@
register: info
# TODO: Drop ignore_errors once openstacksdk's bug #2010135 has been solved.
# Ref.: https://storyboard.openstack.org/#!/story/2010135
ignore_errors: yes
ignore_errors: true
- name: Check info about server image name
assert:
@@ -232,7 +232,7 @@
- info.servers[0].image.name == image_name
# TODO: Drop ignore_errors once openstacksdk's bug #2010135 has been solved.
# Ref.: https://storyboard.openstack.org/#!/story/2010135
ignore_errors: yes
ignore_errors: true
- name: Delete server (FIP from pool/network)
openstack.cloud.server:
@@ -375,7 +375,7 @@
state: present
name: "{{ server_port }}"
network: "{{ server_network }}"
no_security_groups: yes
no_security_groups: true
fixed_ips:
- ip_address: 192.168.0.42
register: port
@@ -555,7 +555,7 @@
- name: Delete updated server
openstack.cloud.server:
cloud: "{{ cloud }}"
delete_ips: yes
delete_ips: true
name: "{{ server_name }}"
state: absent
wait: true

View File

@@ -1,9 +1,21 @@
---
- name: List all images
openstack.cloud.image_info:
cloud: "{{ cloud }}"
register: images
- name: Identify CirrOS image name
set_fact:
image_name: "{{ images.images|community.general.json_query(query)|first }}"
vars:
query: "[?starts_with(name, 'cirros')].name"
- name: Create server
openstack.cloud.server:
cloud: "{{ cloud }}"
state: present
name: "{{ server_name }}"
image: "cirros-0.5.2-x86_64-disk"
image: "{{ image_name }}"
flavor: "{{ flavor_name }}"
network: "{{ server_network }}"
auto_ip: false

View File

@@ -0,0 +1,10 @@
---
volume_backend_name: LVM_iSCSI
volume_type_name: test_type
volume_type_description: Test volume type
enc_provider_name: nova.volume.encryptors.luks.LuksEncryptor
enc_cipher: aes-xts-plain64
enc_control_location: front-end
enc_control_alt_location: back-end
enc_key_size: 256

View File

@@ -0,0 +1,85 @@
---
- name: Create volume type
openstack.cloud.volume_type:
name: "{{ volume_type_name }}"
cloud: "{{ cloud }}"
state: present
extra_specs:
volume_backend_name: "{{ volume_backend_name }}"
description: "{{ volume_type_description }}"
is_public: true
register: the_result
- name: Check created volume type
vars:
the_volume: "{{ the_result.volume_type }}"
ansible.builtin.assert:
that:
- "'id' in the_result.volume_type"
- the_volume.description == volume_type_description
- the_volume.is_public == True
- the_volume.name == volume_type_name
- the_volume.extra_specs['volume_backend_name'] == volume_backend_name
success_msg: >-
Created volume: {{ the_result.volume_type.id }},
Name: {{ the_result.volume_type.name }},
Description: {{ the_result.volume_type.description }}
- name: Test, check idempotency
openstack.cloud.volume_type:
name: "{{ volume_type_name }}"
cloud: "{{ cloud }}"
state: present
extra_specs:
volume_backend_name: "{{ volume_backend_name }}"
description: "{{ volume_type_description }}"
is_public: true
register: the_result
- name: Check result.changed is false
ansible.builtin.assert:
that:
- the_result.changed == false
success_msg: "Request with the same details lead to no changes"
- name: Add extra spec
openstack.cloud.volume_type:
cloud: "{{ cloud }}"
name: "{{ volume_type_name }}"
state: present
extra_specs:
volume_backend_name: "{{ volume_backend_name }}"
some_spec: fake_spec
description: "{{ volume_type_description }}"
is_public: true
register: the_result
- name: Check volume type extra spec
ansible.builtin.assert:
that:
- "'some_spec' in the_result.volume_type.extra_specs"
- the_result.volume_type.extra_specs["some_spec"] == "fake_spec"
success_msg: >-
New extra specs: {{ the_result.volume_type.extra_specs }}
# is_public update attempt using openstacksdk result in unexpected attribute
# error... TODO: Find solution
#
# - name: Make volume type private
# openstack.cloud.volume_type:
# cloud: "{{ cloud }}"
# name: "{{ volume_type_alt_name }}"
# state: present
# extra_specs:
# volume_backend_name: "{{ volume_backend_name }}"
# # some_other_spec: test
# description: Changed 3rd time test volume type
# is_public: true
# register: the_result
- name: Volume encryption tests
ansible.builtin.include_tasks: volume_encryption.yml
- name: Delete volume type
openstack.cloud.volume_type:
cloud: "{{ cloud }}"
name: "{{ volume_type_name }}"
state: absent
register: the_result

View File

@@ -0,0 +1,67 @@
---
- name: Test, Volume type has no encryption
openstack.cloud.volume_type_info:
cloud: "{{ cloud }}"
name: "{{ volume_type_name }}"
register: the_result
- name: Check volume type has no encryption
ansible.builtin.assert:
that:
- the_result.encryption.id == None
success_msg: >-
Success: Volume type has no encryption at the moment
- name: Test, create volume type encryption
openstack.cloud.volume_type_encryption:
cloud: "{{ cloud }}"
volume_type: "{{ volume_type_name }}"
state: present
encryption_provider: "{{ enc_provider_name }}"
encryption_cipher: "{{ enc_cipher }}"
encryption_control_location: "{{ enc_control_location }}"
encryption_key_size: "{{ enc_key_size }}"
register: the_result
- name: Check volume type encryption
ansible.builtin.assert:
that:
- the_result.encryption.cipher == enc_cipher
- the_result.encryption.control_location == enc_control_location
- the_result.encryption.key_size == enc_key_size
- the_result.encryption.provider == enc_provider_name
success_msg: >-
Success: {{ the_result.encryption.encryption_id }}
- name: Test, update volume type encryption
openstack.cloud.volume_type_encryption:
cloud: "{{ cloud }}"
volume_type: "{{ volume_type_name }}"
state: present
encryption_provider: "{{ enc_provider_name }}"
encryption_cipher: "{{ enc_cipher }}"
encryption_control_location: "{{ enc_control_alt_location }}"
encryption_key_size: "{{ enc_key_size }}"
register: the_result
- name: Check volume type encryption change
ansible.builtin.assert:
that:
- the_result.encryption.control_location == enc_control_alt_location
success_msg: >-
New location: {{ the_result.encryption.control_location }}
- name: Test, delete volume type encryption
openstack.cloud.volume_type_encryption:
cloud: "{{ cloud }}"
volume_type: "{{ volume_type_name }}"
state: absent
register: the_result
- name: Get volume type details
openstack.cloud.volume_type_info:
cloud: "{{ cloud }}"
name: "{{ volume_type_name }}"
register: the_result
- name: Check volume type has no encryption
ansible.builtin.assert:
that:
- the_result.encryption.id == None
success_msg: >-
Success: Volume type has no encryption

View File

@@ -53,6 +53,7 @@
- { role: subnet, tags: subnet }
- { role: subnet_pool, tags: subnet_pool }
- { role: volume, tags: volume }
- { role: volume_type, tags: volume_type }
- { role: volume_backup, tags: volume_backup }
- { role: volume_snapshot, tags: volume_snapshot }
- { role: volume_type_access, tags: volume_type_access }

View File

@@ -32,4 +32,4 @@ build_ignore:
- .vscode
- ansible_collections_openstack.egg-info
- changelogs
version: 2.0.0
version: 2.2.0

View File

@@ -3,6 +3,7 @@ action_groups:
openstack:
- address_scope
- auth
- baremetal_deploy_template
- baremetal_inspect
- baremetal_node
- baremetal_node_action

View File

@@ -46,7 +46,7 @@ options:
description:
- Should ansible wait until the requested resource is complete.
type: bool
default: yes
default: true
timeout:
description:
- How long should ansible wait for the requested resource.
@@ -60,7 +60,7 @@ options:
validate_certs:
description:
- Whether or not SSL API requests should be verified.
- Before Ansible 2.3 this defaulted to C(yes).
- Before Ansible 2.3 this defaulted to C(true).
type: bool
aliases: [ verify ]
ca_cert:

View File

@@ -271,9 +271,9 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
if not attempt_to_read_cache or cache_needs_update:
self.display.vvvv('Retrieving servers from Openstack clouds')
clouds_yaml_path = self.get_option('clouds_yaml_path')
config_files = (
openstack.config.loader.CONFIG_FILES
+ ([clouds_yaml_path] if clouds_yaml_path else []))
config_files = openstack.config.loader.CONFIG_FILES
if clouds_yaml_path:
config_files += clouds_yaml_path
config = openstack.config.loader.OpenStackConfig(
config_files=config_files)
@@ -293,10 +293,6 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
clouds = [openstack.connection.Connection(config=cloud_region)
for cloud_region in cloud_regions]
if self.get_option('private'):
for cloud in self.clouds:
cloud.private = True
self.display.vvvv(
'Found {0} OpenStack cloud(s)'
.format(len(clouds)))
@@ -393,7 +389,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
if address['OS-EXT-IPS:type'] == 'fixed'),
None)
ip = floating_ip if floating_ip is not None else fixed_ip
ip = floating_ip if floating_ip is not None and not self.get_option('private') else fixed_ip
if ip is not None:
host_vars['ansible_ssh_host'] = ip

View File

@@ -40,7 +40,7 @@ options:
description:
- Whether this address scope is shared or not.
type: bool
default: 'no'
default: 'false'
aliases: ['shared']
extra_specs:
description:

View File

@@ -0,0 +1,198 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2023 StackHPC Ltd.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = r'''
module: baremetal_deploy_template
short_description: Create/Delete Bare Metal deploy template Resources from OpenStack
author: OpenStack Ansible SIG
description:
- Create, Update and Remove ironic deploy templates from OpenStack.
options:
extra:
description:
- A set of one or more arbitrary metadata key and value pairs.
type: dict
id:
description:
- ID of the deploy template.
- Will be auto-generated if not specified.
type: str
aliases: ['uuid']
name:
description:
- Name of the deploy template.
- Must be formatted as a trait name (see API reference).
- Required when the deploy template is created, after which the
name or ID may be used.
type: str
steps:
description:
- List of deploy steps to apply.
- Required when the deploy template is created.
type: list
elements: dict
state:
description:
- Indicates desired state of the resource
choices: ['present', 'absent']
default: present
type: str
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = r'''
- name: Create Bare Metal deploy template
openstack.cloud.baremetal_deploy_template:
cloud: devstack
state: present
name: CUSTOM_FOO
steps:
- interface: bios
step: apply_configuration
args:
settings:
- name: LogicalProc
value: Enabled
priority: 110
extra:
something: extra
register: result
- name: Delete Bare Metal deploy template
openstack.cloud.baremetal_deploy_template:
cloud: devstack
state: absent
id: 1a85ebca-22bf-42eb-ad9e-f640789b8098
register: result
- name: Update Bare Metal deploy template
openstack.cloud.baremetal_deploy_template:
cloud: devstack
state: present
id: 1a85ebca-22bf-42eb-ad9e-f640789b8098
extra:
something: new
'''
RETURN = r'''
template:
description: A deploy template dictionary, subset of the dictionary keys
listed below may be returned, depending on your cloud
provider.
returned: success
type: dict
contains:
created_at:
description: Bare Metal deploy template created at timestamp.
returned: success
type: str
extra:
description: A set of one or more arbitrary metadata key and value
pairs.
returned: success
type: dict
id:
description: The UUID for the Baremetal Deploy Template resource.
returned: success
type: str
links:
description: A list of relative links, including the self and
bookmark links.
returned: success
type: list
location:
description: Cloud location of this resource (cloud, project,
region, zone)
returned: success
type: dict
name:
description: Bare Metal deploy template name.
returned: success
type: str
steps:
description: A list of deploy steps.
returned: success
type: list
elements: dict
updated_at:
description: Bare Metal deploy template updated at timestamp.
returned: success
type: str
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (
OpenStackModule
)
class BaremetalDeployTemplateModule(OpenStackModule):
argument_spec = dict(
extra=dict(type='dict'),
id=dict(aliases=['uuid']),
name=dict(),
steps=dict(type='list', elements='dict'),
state=dict(default='present', choices=['present', 'absent']),
)
module_kwargs = dict(
required_one_of=[
('id', 'name'),
],
)
def run(self):
template = self._find_deploy_template()
state = self.params['state']
if state == 'present':
# create or update deploy template
kwargs = {}
for k in ['extra', 'id', 'name', 'steps']:
if self.params[k] is not None:
kwargs[k] = self.params[k]
changed = True
if not template:
# create deploy template
template = self.conn.baremetal.create_deploy_template(**kwargs)
else:
# update deploy template
updates = dict((k, v)
for k, v in kwargs.items()
if v != template[k])
if updates:
template = \
self.conn.baremetal.update_deploy_template(template['id'], **updates)
else:
changed = False
self.exit_json(changed=changed, template=template.to_dict(computed=False))
if state == 'absent':
# remove deploy template
if not template:
self.exit_json(changed=False)
template = self.conn.baremetal.delete_deploy_template(template['id'])
self.exit_json(changed=True)
def _find_deploy_template(self):
id_or_name = self.params['id'] if self.params['id'] else self.params['name']
try:
return self.conn.baremetal.get_deploy_template(id_or_name)
except self.sdk.exceptions.ResourceNotFound:
return None
def main():
module = BaremetalDeployTemplateModule()
module()
if __name__ == "__main__":
main()

View File

@@ -93,10 +93,10 @@ EXAMPLES = r'''
image_checksum: "356a6b55ecc511a20c33c946c4e678af"
image_disk_format: "qcow"
delegate_to: localhost
deploy: yes
deploy: true
cloud: "openstack"
config_drive: "http://192.168.1.1/host-configdrive.iso"
maintenance: no
maintenance: false
power: present
uuid: "d44666e1-35b3-4f6b-acb0-88ab7052da69"
state: present

View File

@@ -317,7 +317,7 @@ EXAMPLES = r'''
image_id: 2a8c9888-9054-4b06-a1ca-2bb61f9adb72
keypair_id: mykey
name: k8s
is_public: no
is_public: false
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule

View File

@@ -34,7 +34,7 @@ options:
type: str
choices: ['present', 'absent']
notes:
- A compute flavor must not be private to manage project access.
- A compute flavor must be private to manage project access.
extends_documentation_fragment:
- openstack.cloud.openstack
'''

View File

@@ -42,7 +42,7 @@ options:
floating IP completely, or only detach it from the server.
Default is to detach only.
type: bool
default: 'no'
default: 'false'
reuse:
description:
- When I(state) is present, and I(floating_ip_address) is not present,
@@ -52,7 +52,7 @@ options:
I(floating_ip_address) is undefined, then C(nat_destination) and
C(fixed_address) will be ignored.
type: bool
default: 'no'
default: 'false'
server:
description:
- The name or ID of the server to which the IP address
@@ -83,7 +83,7 @@ EXAMPLES = '''
- openstack.cloud.floating_ip:
cloud: dguerri
state: present
reuse: yes
reuse: true
server: cattle001
network: ext_net
fixed_address: 192.0.2.3
@@ -244,7 +244,7 @@ class NetworkingFloatingIPModule(OpenStackModule):
else: # ip
# Requested floating ip address exists already
if ip.port_details and (ip.port_details.status == 'ACTIVE') \
if ip.port_details and (ip.port_details['status'] == 'ACTIVE') \
and (floating_ip_address not in self._filter_ips(
self.server)):
# Floating ip address exists and has been attached

View File

@@ -32,7 +32,7 @@ options:
description:
- Whether the user is enabled or not.
type: bool
default: 'yes'
default: 'true'
aliases: ['enabled']
name:
description:

View File

@@ -485,7 +485,7 @@ class ImageModule(OpenStackModule):
if image_name_or_id:
image = self.conn.get_image(
image_name_or_id,
filters={(k, self.params[k])
filters={k: self.params[k]
for k in ['checksum'] if self.params[k] is not None})
changed = False

View File

@@ -258,7 +258,7 @@ EXAMPLES = r'''
- name: Delete a load balancer, its related resources and its floating ip
openstack.cloud.loadbalancer:
cloud: devstack
delete_floating_ip: yes
delete_floating_ip: true
name: my_lb
state: absent
'''

View File

@@ -135,13 +135,14 @@ options:
description:
- Do not associate a security group with this port.
- "Deprecated. Use I(security_groups): C([]) instead
of I(no_security_groups): C(yes)."
of I(no_security_groups): C(true)."
type: bool
default: 'no'
port_security_enabled:
default: 'false'
is_port_security_enabled:
description:
- Whether to enable or disable the port security on the network.
type: bool
aliases: ['port_security_enabled']
security_groups:
description:
- Security group(s) ID(s) or name(s) associated with the port.
@@ -479,7 +480,7 @@ class PortModule(OpenStackModule):
name=dict(required=True),
network=dict(),
no_security_groups=dict(default=False, type='bool'),
port_security_enabled=dict(type='bool'),
is_port_security_enabled=dict(type='bool', aliases=['port_security_enabled']),
security_groups=dict(type='list', elements='str'),
state=dict(default='present', choices=['absent', 'present']),
)
@@ -655,7 +656,7 @@ class PortModule(OpenStackModule):
'extra_dhcp_opts',
'is_admin_state_up',
'mac_address',
'port_security_enabled',
'is_port_security_enabled',
'fixed_ips',
'name']:
if self.params[k] is not None:

View File

@@ -181,6 +181,10 @@ class DnsRecordsetModule(OpenStackModule):
module_min_sdk_version = '0.28.0'
def _needs_update(self, params, recordset):
if params['records'] is not None:
params['records'] = sorted(params['records'])
if recordset['records'] is not None:
recordset['records'] = sorted(recordset['records'])
for k in ('description', 'records', 'ttl', 'type'):
if k not in params:
continue

View File

@@ -87,7 +87,7 @@ options:
description:
- Desired admin state of the created or existing router.
type: bool
default: 'yes'
default: 'true'
aliases: ['admin_state_up']
name:
description:
@@ -366,38 +366,36 @@ class RouterModule(OpenStackModule):
if 'ip_address' in p:
cur_fip_map[p['subnet_id']].add(p['ip_address'])
req_fip_map = defaultdict(set)
for p in external_fixed_ips:
if 'ip_address' in p:
req_fip_map[p['subnet_id']].add(p['ip_address'])
if external_fixed_ips is not None:
# User passed expected external_fixed_ips configuration.
# Build map of requested ips/subnets.
for p in external_fixed_ips:
if 'ip_address' in p:
req_fip_map[p['subnet_id']].add(p['ip_address'])
# Check if external ip addresses need to be added
for fip in external_fixed_ips:
subnet = fip['subnet_id']
ip = fip.get('ip_address', None)
if subnet in cur_fip_map:
if ip is not None and ip not in cur_fip_map[subnet]:
# mismatching ip for subnet
# Check if external ip addresses need to be added
for fip in external_fixed_ips:
subnet = fip['subnet_id']
ip = fip.get('ip_address', None)
if subnet in cur_fip_map:
if ip is not None and ip not in cur_fip_map[subnet]:
# mismatching ip for subnet
return True
else:
# adding ext ip with subnet 'subnet'
return True
else:
# adding ext ip with subnet 'subnet'
return True
# Check if external ip addresses need to be removed
for fip in cur_ext_fips:
subnet = fip['subnet_id']
ip = fip['ip_address']
if subnet in req_fip_map:
if ip not in req_fip_map[subnet]:
# removing ext ip with subnet (ip clash)
# Check if external ip addresses need to be removed.
for fip in cur_ext_fips:
subnet = fip['subnet_id']
ip = fip['ip_address']
if subnet in req_fip_map:
if ip not in req_fip_map[subnet]:
# removing ext ip with subnet (ip clash)
return True
else:
# removing ext ip with subnet
return True
else:
# removing ext ip with subnet
return True
if not external_fixed_ips and len(cur_ext_fips) > 1:
# No external fixed ips requested but
# router has several external fixed ips
return True
# Check if internal interfaces need update
if to_add or to_remove or missing_port_ids:
@@ -448,7 +446,8 @@ class RouterModule(OpenStackModule):
return kwargs
def _build_router_interface_config(self, filters):
external_fixed_ips = []
# Undefine external_fixed_ips to have possibility to unset them
external_fixed_ips = None
internal_ports_missing = []
internal_ifaces = []
@@ -459,9 +458,11 @@ class RouterModule(OpenStackModule):
.get('external_fixed_ips')
ext_fixed_ips = ext_fixed_ips or self.params['external_fixed_ips']
if ext_fixed_ips:
# User passed external_fixed_ips configuration. Initialize ips list
external_fixed_ips = []
for iface in ext_fixed_ips:
subnet = self.conn.network.find_subnet(
iface['subnet'], ignore_missing=False, **filters)
iface['subnet_id'], ignore_missing=False, **filters)
fip = dict(subnet_id=subnet.id)
if 'ip_address' in iface:
fip['ip_address'] = iface['ip_address']

View File

@@ -34,8 +34,8 @@ options:
security group with a default set of rules.
- Security group rules which are listed in I(security_group_rules)
but not defined in this security group will be created.
- Existing security group rules which are not listed in
I(security_group_rules) will be deleted.
- When I(security_group_rules) is not set, existing security group rules
which are not listed in I(security_group_rules) will be deleted.
- When updating a security group, one has to explicitly list rules from
Neutron's defaults in I(security_group_rules) if those rules should be
kept. Rules which are not listed in I(security_group_rules) will be
@@ -113,6 +113,10 @@ options:
choices: [present, absent]
default: present
type: str
stateful:
description:
- Should the resource be stateful or stateless.
type: bool
extends_documentation_fragment:
- openstack.cloud.openstack
'''
@@ -201,6 +205,14 @@ EXAMPLES = r'''
name: foo
description: security group for foo servers
- name: Create a stateless security group
openstack.cloud.security_group:
cloud: mordred
state: present
stateful: false
name: foo
description: stateless security group for foo servers
- name: Update the existing 'foo' security group description
openstack.cloud.security_group:
cloud: mordred
@@ -260,6 +272,7 @@ class SecurityGroupModule(OpenStackModule):
),
),
state=dict(default='present', choices=['absent', 'present']),
stateful=dict(type="bool"),
)
module_kwargs = dict(
@@ -331,6 +344,12 @@ class SecurityGroupModule(OpenStackModule):
def _build_update_security_group_rules(self, security_group):
if self.params['security_group_rules'] is None:
# Consider a change of security group rules only when option
# 'security_group_rules' was defined explicitly, because undefined
# options in our Ansible modules denote "apply no change"
return {}
def find_security_group_rule_match(prototype, security_group_rules):
matches = [r for r in security_group_rules
if is_security_group_rule_match(prototype, r)]
@@ -399,7 +418,7 @@ class SecurityGroupModule(OpenStackModule):
def _create(self):
kwargs = dict((k, self.params[k])
for k in ['description', 'name']
for k in ['description', 'name', 'stateful']
if self.params[k] is not None)
project_name_or_id = self.params['project']
@@ -439,7 +458,7 @@ class SecurityGroupModule(OpenStackModule):
def _generate_security_group_rule(params):
prototype = dict(
(k, params[k])
for k in ['direction', 'remote_ip_prefix']
for k in ['description', 'direction', 'remote_ip_prefix']
if params[k] is not None)
# When remote_ip_prefix is missing a netmask, then Neutron will add
@@ -521,17 +540,17 @@ class SecurityGroupModule(OpenStackModule):
return security_group
def _update_security_group_rules(self, security_group, update):
create_security_group_rules = update.get('create_security_group_rules')
if create_security_group_rules:
self.conn.network.\
create_security_group_rules(create_security_group_rules)
delete_security_group_rules = update.get('delete_security_group_rules')
if delete_security_group_rules:
for security_group_rule in delete_security_group_rules:
self.conn.network.\
delete_security_group_rule(security_group_rule['id'])
create_security_group_rules = update.get('create_security_group_rules')
if create_security_group_rules:
self.conn.network.\
create_security_group_rules(create_security_group_rules)
if create_security_group_rules or delete_security_group_rules:
# Update security group with created and deleted rules
return self.conn.network.get_security_group(security_group.id)

View File

@@ -308,7 +308,8 @@ class SecurityGroupRuleModule(OpenStackModule):
def _define_prototype(self):
filters = {}
prototype = dict((k, self.params[k])
for k in ['direction', 'remote_ip_prefix']
for k in ['description', 'direction',
'remote_ip_prefix']
if self.params[k] is not None)
# When remote_ip_prefix is missing a netmask, then Neutron will add

View File

@@ -27,7 +27,7 @@ options:
attaching and detaching of floating ip addresses use module
I(openstack.cloud.resource) instead.
type: bool
default: 'yes'
default: 'true'
aliases: ['auto_floating_ip', 'public_ip']
availability_zone:
description:
@@ -40,7 +40,7 @@ options:
the image given. Mutually exclusive with boot_volume.
- This server attribute cannot be updated.
type: bool
default: 'no'
default: 'false'
boot_volume:
description:
- Volume name or id to use as the volume to boot from. Implies
@@ -53,7 +53,7 @@ options:
- Whether to boot the server with config drive enabled.
- This server attribute cannot be updated.
type: bool
default: 'no'
default: 'false'
delete_ips:
description:
- When I(state) is C(absent) and this option is true, any floating IP
@@ -61,7 +61,7 @@ options:
- Floating IP support is unstable in this module, use with caution.
type: bool
aliases: ['delete_fip']
default: 'no'
default: 'false'
description:
description:
- Description of the server.
@@ -183,7 +183,7 @@ options:
- Floating IP support is unstable in this module, use with caution.
- This server attribute cannot be updated.
type: bool
default: 'yes'
default: 'true'
scheduler_hints:
description:
- Arbitrary key/value pairs to the scheduler for custom use.
@@ -207,11 +207,11 @@ options:
type: str
terminate_volume:
description:
- If C(yes), delete volume when deleting the instance and if it has
- If C(true), delete volume when deleting the instance and if it has
been booted from volume(s).
- This server attribute cannot be updated.
type: bool
default: 'no'
default: 'false'
timeout:
description:
- The amount of time the module should wait for the instance to get
@@ -240,7 +240,7 @@ options:
description:
- If the module should wait for the instance to be created.
type: bool
default: 'yes'
default: 'true'
extends_documentation_fragment:
- openstack.cloud.openstack
'''
@@ -288,7 +288,7 @@ EXAMPLES = '''
flavor: 101
security_groups:
- default
auto_ip: yes
auto_ip: true
# Create a new instance in named cloud mordred availability zone az2
# and assigns a pre-known floating IP
@@ -1038,7 +1038,7 @@ class ServerModule(OpenStackModule):
# we wait until the server has been created
# Ref.: https://opendev.org/openstack/openstacksdk/src/commit/3f81d0001dd994cde990d38f6e2671ee0694d7d5/openstack/cloud/_compute.py#L945
self.fail_json(
msg="Option '{0}' requires 'wait: yes'".format(k))
msg="Option '{0}' requires 'wait: true'".format(k))
flavor_name_or_id = self.params['flavor']

View File

@@ -25,7 +25,7 @@ options:
- when true, return additional detail about servers at the expense
of additional API calls.
type: bool
default: 'no'
default: 'false'
filters:
description: |
Used for further filtering of results. Either a string containing a
@@ -37,7 +37,7 @@ options:
- Whether to list servers from all projects or just the current auth
scoped project.
type: bool
default: 'no'
default: 'false'
extends_documentation_fragment:
- openstack.cloud.openstack
'''

View File

@@ -43,7 +43,7 @@ options:
- The gateway IP would not be assigned for this subnet
type: bool
aliases: ['no_gateway_ip']
default: 'no'
default: 'false'
dns_nameservers:
description:
- List of DNS nameservers for this subnet.
@@ -85,7 +85,7 @@ options:
- Whether DHCP should be enabled for this subnet.
type: bool
aliases: ['enable_dhcp']
default: 'yes'
default: 'true'
ipv6_ra_mode:
description:
- IPv6 router advertisement mode

View File

@@ -22,7 +22,7 @@ options:
- Allows or disallows snapshot of a volume to be created,
when the volume is attached to an instance.
type: bool
default: 'no'
default: 'false'
name:
description:
- Name of the snapshot

View File

@@ -0,0 +1,241 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2023 Cleura AB
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = r'''
---
module: volume_type
short_description: Manage OpenStack volume type
author: OpenStack Ansible SIG
description:
- Add, remove or update volume types in OpenStack.
options:
name:
description:
- Volume type name or id.
required: true
type: str
description:
description:
- Description of the volume type.
type: str
extra_specs:
description:
- List of volume type properties
type: dict
is_public:
description:
- Make volume type accessible to the public.
- Can be set only during creation
type: bool
state:
description:
- Indicate desired state of the resource.
- When I(state) is C(present), then I(is_public) is required.
choices: ['present', 'absent']
default: present
type: str
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = r'''
- name: Delete volume type by name
openstack.cloud.volume_type:
name: test_type
state: absent
- name: Delete volume type by id
openstack.cloud.volume_type:
name: fbadfa6b-5f17-4c26-948e-73b94de57b42
state: absent
- name: Create volume type
openstack.cloud.volume_type:
name: unencrypted_volume_type
state: present
extra_specs:
volume_backend_name: LVM_iSCSI
description: Unencrypted volume type
is_public: True
'''
RETURN = '''
volume_type:
description: Dictionary describing volume type
returned: On success when I(state) is 'present'
type: dict
contains:
name:
description: volume type name
returned: success
type: str
sample: test_type
extra_specs:
description: volume type extra parameters
returned: success
type: dict
sample: null
is_public:
description: whether the volume type is public
returned: success
type: bool
sample: True
description:
description: volume type description
returned: success
type: str
sample: Unencrypted volume type
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class VolumeTypeModule(OpenStackModule):
argument_spec = dict(
name=dict(type='str', required=True),
description=dict(type='str', required=False),
extra_specs=dict(type='dict', required=False),
is_public=dict(type='bool'),
state=dict(
type='str', default='present', choices=['absent', 'present']),
)
module_kwargs = dict(
required_if=[('state', 'present', ['is_public'])],
supports_check_mode=True,
)
@staticmethod
def _extract_result(details):
if details is not None:
return details.to_dict(computed=False)
return {}
def run(self):
state = self.params['state']
name_or_id = self.params['name']
volume_type = self.conn.block_storage.find_type(name_or_id)
if self.ansible.check_mode:
self.exit_json(
changed=self._will_change(state, volume_type))
if state == 'present' and not volume_type:
# Create type
create_result = self._create()
volume_type = self._extract_result(create_result)
self.exit_json(changed=True, volume_type=volume_type)
elif state == 'present' and volume_type:
# Update type
update = self._build_update(volume_type)
update_result = self._update(volume_type, update)
volume_type = self._extract_result(update_result)
self.exit_json(changed=bool(update), volume_type=volume_type)
elif state == 'absent' and volume_type:
# Delete type
self._delete(volume_type)
self.exit_json(changed=True)
def _build_update(self, volume_type):
return {
**self._build_update_extra_specs(volume_type),
**self._build_update_volume_type(volume_type)}
def _build_update_extra_specs(self, volume_type):
update = {}
old_extra_specs = volume_type['extra_specs']
new_extra_specs = self.params['extra_specs'] or {}
delete_extra_specs_keys = \
set(old_extra_specs.keys()) - set(new_extra_specs.keys())
if delete_extra_specs_keys:
update['delete_extra_specs_keys'] = delete_extra_specs_keys
stringified = {k: str(v) for k, v in new_extra_specs.items()}
if old_extra_specs != stringified:
update['create_extra_specs'] = new_extra_specs
return update
def _build_update_volume_type(self, volume_type):
update = {}
allowed_attributes = [
'is_public', 'description', 'name']
type_attributes = {
k: self.params[k]
for k in allowed_attributes
if k in self.params and self.params.get(k) is not None
and self.params.get(k) != volume_type.get(k)}
if type_attributes:
update['type_attributes'] = type_attributes
return update
def _create(self):
kwargs = {k: self.params[k]
for k in ['name', 'is_public', 'description', 'extra_specs']
if self.params.get(k) is not None}
volume_type = self.conn.block_storage.create_type(**kwargs)
return volume_type
def _delete(self, volume_type):
self.conn.block_storage.delete_type(volume_type.id)
def _update(self, volume_type, update):
if not update:
return volume_type
volume_type = self._update_volume_type(volume_type, update)
volume_type = self._update_extra_specs(volume_type, update)
return volume_type
def _update_extra_specs(self, volume_type, update):
delete_extra_specs_keys = update.get('delete_extra_specs_keys')
if delete_extra_specs_keys:
self.conn.block_storage.delete_type_extra_specs(
volume_type, delete_extra_specs_keys)
# refresh volume_type information
volume_type = self.conn.block_storage.find_type(volume_type.id)
create_extra_specs = update.get('create_extra_specs')
if create_extra_specs:
self.conn.block_storage.update_type_extra_specs(
volume_type, **create_extra_specs)
# refresh volume_type information
volume_type = self.conn.block_storage.find_type(volume_type.id)
return volume_type
def _update_volume_type(self, volume_type, update):
type_attributes = update.get('type_attributes')
if type_attributes:
updated_type = self.conn.block_storage.update_type(
volume_type, **type_attributes)
return updated_type
return volume_type
def _will_change(self, state, volume_type):
if state == 'present' and not volume_type:
return True
if state == 'present' and volume_type:
return bool(self._build_update(volume_type))
if state == 'absent' and volume_type:
return True
return False
def main():
module = VolumeTypeModule()
module()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,233 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2023 Cleura AB
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = r'''
---
module: volume_type_encryption
short_description: Manage OpenStack volume type encryption
author: OpenStack Ansible SIG
description:
- Add, remove or update volume type encryption in OpenStack.
options:
volume_type:
description:
- Volume type name or id.
required: true
type: str
state:
description:
- Indicate desired state of the resource.
- When I(state) is C(present), then I(encryption options) are required.
choices: ['present', 'absent']
default: present
type: str
encryption_provider:
description:
- class that provides encryption support for the volume type
- admin only
type: str
encryption_cipher:
description:
- encryption algorithm or mode
- admin only
type: str
encryption_control_location:
description:
- Set the notional service where the encryption is performed
- admin only
choices: ['front-end', 'back-end']
type: str
encryption_key_size:
description:
- Set the size of the encryption key of this volume type
- admin only
choices: [128, 256, 512]
type: int
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = r'''
- name: Create volume type encryption
openstack.cloud.volume_type_encryption:
volume_type: test_type
state: present
encryption_provider: nova.volume.encryptors.luks.LuksEncryptor
encryption_cipher: aes-xts-plain64
encryption_control_location: front-end
encryption_key_size: 256
- name: Delete volume type encryption
openstack.cloud.volume_type_encryption:
volume_type: test_type
state: absent
register: the_result
'''
RETURN = '''
encryption:
description: Dictionary describing volume type encryption
returned: On success when I(state) is 'present'
type: dict
contains:
cipher:
description: encryption cipher
returned: success
type: str
sample: aes-xts-plain64
control_location:
description: encryption location
returned: success
type: str
sample: front-end
created_at:
description: Resource creation date and time
returned: success
type: str
sample: "2023-08-04T10:23:03.000000"
deleted:
description: Boolean if the resource was deleted
returned: success
type: str
sample: false,
deleted_at:
description: Resource delete date and time
returned: success
type: str
sample: null,
encryption_id:
description: UUID of the volume type encryption
returned: success
type: str
sample: b75d8c5c-a6d8-4a5d-8c86-ef4f1298525d
id:
description: Alias to encryption_id
returned: success
type: str
sample: b75d8c5c-a6d8-4a5d-8c86-ef4f1298525d
key_size:
description: Size of the key
returned: success
type: str
sample: 256,
provider:
description: Encryption provider
returned: success
type: str
sample: "nova.volume.encryptors.luks.LuksEncryptor"
updated_at:
description: Resource last update date and time
returned: success
type: str
sample: null
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class VolumeTypeModule(OpenStackModule):
argument_spec = dict(
volume_type=dict(type='str', required=True),
state=dict(
type='str', default='present', choices=['absent', 'present']),
encryption_provider=dict(type='str', required=False),
encryption_cipher=dict(type='str', required=False),
encryption_control_location=dict(
type='str', choices=['front-end', 'back-end'], required=False),
encryption_key_size=dict(
type='int', choices=[128, 256, 512], required=False),
)
module_kwargs = dict(
required_if=[('state', 'present', [
'encryption_provider', 'encryption_cipher',
'encryption_control_location', 'encryption_key_size'])],
supports_check_mode=True,
)
@staticmethod
def _extract_result(details):
if details is not None:
return details.to_dict(computed=False)
return {}
def run(self):
state = self.params['state']
name = self.params['volume_type']
volume_type = self.conn.block_storage.find_type(name)
# TODO: Add get type_encryption by id
type_encryption = self.conn.block_storage.get_type_encryption(
volume_type.id)
encryption_id = type_encryption.get('encryption_id')
if self.ansible.check_mode:
self.exit_json(
changed=self._will_change(state, encryption_id))
if state == 'present':
update = self._build_update_type_encryption(type_encryption)
if not bool(update):
# No change is required
self.exit_json(changed=False)
if not encryption_id: # Create new type encryption
result = self.conn.block_storage.create_type_encryption(
volume_type, **update)
else: # Update existing type encryption
result = self.conn.block_storage.update_type_encryption(
encryption=type_encryption, **update)
encryption = self._extract_result(result)
self.exit_json(changed=bool(update), encryption=encryption)
elif encryption_id is not None:
# absent state requires type encryption delete
self.conn.block_storage.delete_type_encryption(type_encryption)
self.exit_json(changed=True)
def _build_update_type_encryption(self, type_encryption):
attributes_map = {
'encryption_provider': 'provider',
'encryption_cipher': 'cipher',
'encryption_key_size': 'key_size',
'encryption_control_location': 'control_location'}
encryption_attributes = {
attributes_map[k]: self.params[k]
for k in self.params
if k in attributes_map.keys() and self.params.get(k) is not None
and self.params.get(k) != type_encryption.get(attributes_map[k])}
if 'encryption_provider' in encryption_attributes.keys():
encryption_attributes['provider'] = \
encryption_attributes['encryption_provider']
return encryption_attributes
def _update_type_encryption(self, type_encryption, update):
if update:
updated_type = self.conn.block_storage.update_type_encryption(
encryption=type_encryption,
**update)
return updated_type
return {}
def _will_change(self, state, type_encryption):
encryption_id = type_encryption.get('encryption_id')
if state == 'present' and not encryption_id:
return True
if state == 'present' and encryption_id is not None:
return bool(self._build_update_type_encryption(type_encryption))
if state == 'absent' and encryption_id is not None:
return True
return False
def main():
module = VolumeTypeModule()
module()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,175 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2023 Cleura AB
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = r'''
---
module: volume_type_info
short_description: Get OpenStack volume type details
author: OpenStack Ansible SIG
description:
- Get volume type details in OpenStack.
- Get volume type encryption details in OpenStack
options:
name:
description:
- Volume type name or id.
required: true
type: str
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = r'''
- name: Get volume type details
openstack.cloud.volume_type_info:
name: test_type
- name: Get volume type details by id
openstack.cloud.volume_type_info:
name: fbadfa6b-5f17-4c26-948e-73b94de57b42
'''
RETURN = '''
access_project_ids:
description:
- List of project IDs allowed to access volume type
- Public volume types returns 'null' value as it is not applicable
returned: On success when I(state) is 'present'
type: list
elements: str
volume_type:
description: Dictionary describing volume type
returned: On success when I(state) is 'present'
type: dict
contains:
id:
description: volume_type uuid
returned: success
type: str
sample: b75d8c5c-a6d8-4a5d-8c86-ef4f1298525d
name:
description: volume type name
returned: success
type: str
sample: test_type
extra_specs:
description: volume type extra parameters
returned: success
type: dict
sample: null
is_public:
description: whether the volume type is public
returned: success
type: bool
sample: True
description:
description: volume type description
returned: success
type: str
sample: Unencrypted volume type
encryption:
description: Dictionary describing volume type encryption
returned: On success when I(state) is 'present'
type: dict
contains:
cipher:
description: encryption cipher
returned: success
type: str
sample: aes-xts-plain64
control_location:
description: encryption location
returned: success
type: str
sample: front-end
created_at:
description: Resource creation date and time
returned: success
type: str
sample: "2023-08-04T10:23:03.000000"
deleted:
description: Boolean if the resource was deleted
returned: success
type: str
sample: false
deleted_at:
description: Resource delete date and time
returned: success
type: str
sample: null
encryption_id:
description: UUID of the volume type encryption
returned: success
type: str
sample: b75d8c5c-a6d8-4a5d-8c86-ef4f1298525d
id:
description: Alias to encryption_id
returned: success
type: str
sample: b75d8c5c-a6d8-4a5d-8c86-ef4f1298525d
key_size:
description: Size of the key
returned: success
type: str
sample: 256
provider:
description: Encryption provider
returned: success
type: str
sample: "nova.volume.encryptors.luks.LuksEncryptor"
updated_at:
description: Resource last update date and time
returned: success
type: str
sample: null
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class VolumeTypeModule(OpenStackModule):
argument_spec = dict(
name=dict(type='str', required=True)
)
module_kwargs = dict(
supports_check_mode=True,
)
@staticmethod
def _extract_result(details):
if details is not None:
return details.to_dict(computed=False)
return {}
def run(self):
name_or_id = self.params['name']
volume_type = self.conn.block_storage.find_type(name_or_id)
type_encryption = self.conn.block_storage.get_type_encryption(
volume_type.id)
if volume_type.is_public:
type_access = None
else:
type_access = [
proj['project_id']
for proj in self.conn.block_storage.get_type_access(
volume_type.id)]
self.exit_json(
changed=False,
volume_type=self._extract_result(volume_type),
encryption=self._extract_result(type_encryption),
access_project_ids=type_access)
def main():
module = VolumeTypeModule()
module()
if __name__ == '__main__':
main()

View File

@@ -28,7 +28,7 @@ class DictDataLoader(DataLoader):
def __init__(self, file_mapping=None):
file_mapping = {} if file_mapping is None else file_mapping
assert type(file_mapping) == dict
assert isinstance(file_mapping, dict)
super(DictDataLoader, self).__init__()