Refactored object{,_container} modules breaking backward compatibility

Previously both modules object and object_container had huge overlaps in
functionality. Both allowed to create and delete containers. One would
not have to pass a object to the object module at all and could use it
to manage containers only. Now the object module has been changed to
manage an object in a container only while the object_container module
is responsible for managing Swift containers only.

With object module it is now also possible to pass data instead of a
filename via module options. The container_access functionality has been
dropped from object module. It has been moved and extended as read_ACL
and write_ACL options in object_container module.

With object_container module it is now also possible to manage the
container access with read_ACL and write_ACL options. Those mirror
earlier container_access option of the object module which has been
removed.

Change-Id: I96fb9b946444866b157655e148250f1eda35e942
This commit is contained in:
Jakob Meng 2022-11-23 13:49:42 +01:00
parent 1ca6f208f7
commit 7c536e69b3
7 changed files with 768 additions and 304 deletions

View File

@ -0,0 +1,32 @@
expected_fields:
- accept_ranges
- access_control_allow_origin
- content_disposition
- content_encoding
- content_length
- content_type
- copy_from
- delete_after
- delete_at
- etag
- expires_at
- id
- if_match
- if_modified_since
- if_none_match
- if_unmodified_since
- is_content_type_detected
- is_newest
- is_static_large_object
- last_modified_at
- manifest
- metadata
- multipart_manifest
- name
- object_manifest
- range
- signature
- symlink_target
- symlink_target_account
- timestamp
- transfer_encoding

View File

@ -1,22 +1,25 @@
---
- name: Create a test object file
shell: mktemp
register: tmp_file
- name: Create container
openstack.cloud.object:
openstack.cloud.object_container:
cloud: "{{ cloud }}"
state: present
container: ansible_container
container_access: private
name: ansible_container
- name: Put object
- name: Create object
openstack.cloud.object:
cloud: "{{ cloud }}"
state: present
name: ansible_object
filename: "{{ tmp_file.stdout }}"
data: "this is a test"
container: ansible_container
register: object
- name: Assert return values of object module
assert:
that:
- object.object.id == "ansible_object"
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(object.object.keys())|length == 0
- name: Delete object
openstack.cloud.object:
@ -26,12 +29,7 @@
container: ansible_container
- name: Delete container
openstack.cloud.object:
openstack.cloud.object_container:
cloud: "{{ cloud }}"
state: absent
container: ansible_container
- name: Delete test object file
file:
name: "{{ tmp_file.stdout }}"
state: absent
name: ansible_container

View File

@ -1 +1,22 @@
container_name: "test-container"
expected_fields:
- bytes
- bytes_used
- content_type
- count
- history_location
- id
- if_none_match
- is_content_type_detected
- is_newest
- meta_temp_url_key
- meta_temp_url_key_2
- metadata
- name
- object_count
- read_ACL
- storage_policy
- sync_key
- sync_to
- timestamp
- versions_location
- write_ACL

View File

@ -1,63 +1,93 @@
---
- module_defaults:
group/openstack.cloud.openstack:
cloud: "{{ cloud }}"
# Backward compatibility with Ansible 2.9
- name: Create an empty container with public access
openstack.cloud.object_container:
cloud: "{{ cloud }}"
block:
- name: Create an empty container
openstack.cloud.object_container:
container: "{{ container_name }}"
name: ansible_container
read_ACL: ".r:*,.rlistings"
register: container
- name: Verify container was created
- name: Assert return values of container module
assert:
that:
- container is success
- container is changed
- container.container.name == container_name
- container.container.name == "ansible_container"
- container.container.read_ACL == ".r:*,.rlistings"
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(container.container.keys())|length == 0
- name: Set metadata for a container
- name: Set container metadata aka container properties
openstack.cloud.object_container:
container: "{{ container_name }}"
metadata: "Cache-Control='no-cache'"
register: set_meta
cloud: "{{ cloud }}"
name: ansible_container
metadata:
'Cache-Control': 'no-cache'
'foo': 'bar'
register: container
- name: Verify container metadata was set
assert:
that:
- set_meta is success
- set_meta is changed
- container is changed
- ('cache-control' in container.container.metadata.keys()|map('lower'))
- container.container.metadata['foo'] == 'bar'
- name: Delete some keys from container metadata
- name: Update a container
openstack.cloud.object_container:
container: "{{ container_name }}"
keys:
- Cache-Control
register: delete_meta
cloud: "{{ cloud }}"
name: ansible_container
delete_metadata_keys:
- 'Cache-Control'
read_ACL: ""
register: container
- name: Verify some keys from container metadata was deleted
- name: Verify updated container
assert:
that:
- delete_meta is success
- delete_meta is changed
- container is changed
- ('cache-control' not in container.container.metadata.keys()|map('lower'))
- "container.container.metadata == {'foo': 'bar'}"
- container.container.read_ACL is none or container.container.read_ACL == ""
- name: Delete container
openstack.cloud.object_container:
container: "{{ container_name }}"
cloud: "{{ cloud }}"
name: ansible_container
state: absent
register: deleted
register: container
- name: Verify container was deleted
assert:
that:
- deleted is success
- deleted is changed
- container is changed
always:
- name: Delete container
- name: Delete container again
openstack.cloud.object_container:
container: "{{ container_name }}"
cloud: "{{ cloud }}"
name: ansible_container
state: absent
ignore_errors: yes
register: container
- name: Verify container was not deleted again
assert:
that:
- container is not changed
- name: Create another container for recursive deletion
openstack.cloud.object_container:
cloud: "{{ cloud }}"
name: ansible_container2
- name: Load an object into container
openstack.cloud.object:
cloud: "{{ cloud }}"
state: present
name: ansible_object
data: "this is another test"
container: ansible_container2
- name: Delete container recursively
openstack.cloud.object_container:
cloud: "{{ cloud }}"
state: absent
name: ansible_container2
delete_with_all_objects: yes

View File

@ -11,9 +11,6 @@
- { role: compute_flavor_access, tags: compute_flavor_access }
- { role: config, tags: config }
- { role: dns_zone_info, tags: dns_zone_info }
- role: object_container
tags: object_container
when: sdk_version is version(0.18, '>=')
- role: dns
tags: dns
when: sdk_version is version(0.28, '>=')
@ -45,6 +42,7 @@
tags: nova_services
when: sdk_version is version(0.44, '>=')
- { role: object, tags: object }
- { role: object_container, tags: object_container }
- { role: port, tags: port }
- { role: project, tags: project }
- { role: project_info, tags: project_info }

View File

@ -5,115 +5,341 @@
# Copyright (c) 2013, Benno Joy <benno@ansible.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
DOCUMENTATION = r'''
---
module: object
short_description: Create or Delete objects and containers from OpenStack
short_description: Create or delete Swift objects in OpenStack clouds
author: OpenStack Ansible SIG
description:
- Create or Delete objects and containers from OpenStack
- Create or delete Swift objects in OpenStack clouds
options:
container:
description:
- The name of the container in which to create the object
- The name (and ID) of the container in which to create the object in.
- This container will not be created if it does not exist already.
required: true
type: str
name:
data:
description:
- Name to be give to the object. If omitted, operations will be on
the entire container
required: false
- The content to upload to the object.
- Mutually exclusive with I(filename).
- This attribute cannot be updated.
type: str
filename:
description:
- Path to local file to be uploaded.
required: false
- The path to the local file whose contents will be uploaded.
- Mutually exclusive with I(data).
type: str
container_access:
name:
description:
- desired container access level.
required: false
choices: ['private', 'public']
default: private
- Name (and ID) of the object.
required: true
type: str
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
- Whether the object should be C(present) or C(absent).
choices: ['present', 'absent']
default: present
type: str
requirements:
- "python >= 3.6"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
- name: "Create a object named 'fstab' in the 'config' container"
RETURN = r'''
object:
description: Dictionary describing the object.
returned: On success when I(state) is C(present).
type: dict
contains:
accept_ranges:
description: The type of ranges that the object accepts.
type: str
access_control_allow_origin:
description: CORS for RAX (deviating from standard)
type: str
content_disposition:
description: If set, specifies the override behavior for the browser.
For example, this header might specify that the browser use
a download program to save this file rather than show the
file, which is the default. If not set, this header is not
returned by this operation.
type: str
content_encoding:
description: If set, the value of the Content-Encoding metadata.
If not set, this header is not returned by this operation.
type: str
content_length:
description: HEAD operations do not return content. However, in this
operation the value in the Content-Length header is not the
size of the response body. Instead it contains the size of
the object, in bytes.
type: str
content_type:
description: The MIME type of the object.
type: int
copy_from:
description: If set, this is the name of an object used to create the new
object by copying the X-Copy-From object. The value is in
form {container}/{object}. You must UTF-8-encode and then
URL-encode the names of the container and object before you
include them in the header. Using PUT with X-Copy-From has
the same effect as using the COPY operation to copy an
object.
type: str
delete_after:
description: Specifies the number of seconds after which the object is
removed. Internally, the Object Storage system stores this
value in the X-Delete-At metadata item.
type: int
delete_at:
description: If set, the time when the object will be deleted by the
system in the format of a UNIX Epoch timestamp. If not set,
this header is not returned by this operation.
type: str
etag:
description: For objects smaller than 5 GB, this value is the MD5
checksum of the object content. The value is not quoted.
For manifest objects, this value is the MD5 checksum of the
concatenated string of MD5 checksums and ETags for each of
the segments in the manifest, and not the MD5 checksum of
the content that was downloaded. Also the value is enclosed
in double-quote characters.
You are strongly recommended to compute the MD5 checksum of
the response body as it is received and compare this value
with the one in the ETag header. If they differ, the content
was corrupted, so retry the operation.
type: str
expires_at:
description: Used with temporary URLs to specify the expiry time of the
signature. For more information about temporary URLs, see
OpenStack Object Storage API v1 Reference.
type: str
id:
description: ID of the object. Equal to C(name).
type: str
if_match:
description: See U(http://www.ietf.org/rfc/rfc2616.txt).
type: list
if_modified_since:
description: See U(http://www.ietf.org/rfc/rfc2616.txt).
type: str
if_none_match:
description: "In combination with C(Expect: 100-Continue), specify an
C(If-None-Match: *) header to query whether the server
already has a copy of the object before any data is sent."
type: list
if_unmodified_since:
description: See U(http://www.ietf.org/rfc/rfc2616.txt).
type: str
is_content_type_detected:
description: If set to true, Object Storage guesses the content type
based on the file extension and ignores the value sent in
the Content-Type header, if present.
type: bool
is_newest:
description: If set to True, Object Storage queries all replicas to
return the most recent one. If you omit this header, Object
Storage responds faster after it finds one valid replica.
Because setting this header to True is more expensive for
the back end, use it only when it is absolutely needed.
type: bool
is_static_large_object:
description: Set to True if this object is a static large object manifest
object.
type: bool
last_modified_at:
description: The date and time that the object was created or the last
time that the metadata was changed.
type: str
manifest:
description: If present, this is a dynamic large object manifest object.
The value is the container and object name prefix of the
segment objects in the form container/prefix.
type: str
multipart_manifest:
description: If you include the multipart-manifest=get query parameter
and the object is a large object, the object contents are
not returned. Instead, the manifest is returned in the
X-Object-Manifest response header for dynamic large objects
or in the response body for static large objects.
type: str
name:
description: Name of the object.
returned: success
type: str
object_manifest:
description: If set, to this is a dynamic large object manifest object.
The value is the container and object name prefix of the
segment objects in the form container/prefix.
type: str
range:
description: TODO.
type: dict
signature:
description: Used with temporary URLs to sign the request. For more
information about temporary URLs, see OpenStack Object
Storage API v1 Reference.
type: str
symlink_target:
description: If present, this is a symlink object. The value is the
relative path of the target object in the format
<container>/<object>.
type: str
symlink_target_account:
description: If present, and X-Symlink-Target is present, then this is a
cross-account symlink to an object in the account specified
in the value.
type: str
timestamp:
description: The timestamp of the transaction.
type: str
transfer_encoding:
description: Set to chunked to enable chunked transfer encoding. If used,
do not set the Content-Length header to a non-zero value.
type: str
'''
EXAMPLES = r'''
- name: Create a object named 'fstab' in the 'config' container
openstack.cloud.object:
cloud: mordred
state: present
name: fstab
container: config
filename: /etc/fstab
name: fstab
state: present
- name: Delete a container called config and all of its contents
openstack.cloud.object:
cloud: rax-iad
state: absent
container: config
state: absent
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class SwiftObjectModule(OpenStackModule):
class ObjectModule(OpenStackModule):
argument_spec = dict(
name=dict(),
container=dict(required=True),
data=dict(),
filename=dict(),
container_access=dict(default='private', choices=['private', 'public']),
name=dict(required=True),
state=dict(default='present', choices=['absent', 'present']),
)
module_kwargs = dict()
def process_object(
self, container, name, filename, container_access, **kwargs
):
changed = False
container_obj = self.conn.get_container(container)
if kwargs['state'] == 'present':
if not container_obj:
container_obj = self.conn.create_container(container)
changed = True
if self.conn.get_container_access(container) != container_access:
self.conn.set_container_access(container, container_access)
changed = True
if name:
if self.conn.is_object_stale(container, name, filename):
self.conn.create_object(container, name, filename)
changed = True
else:
if container_obj:
if name:
if self.conn.get_object_metadata(container, name):
self.conn.delete_object(container, name)
changed = True
else:
self.conn.delete_container(container)
changed = True
return changed
module_kwargs = dict(
mutually_exclusive=[
('data', 'filename'),
],
required_if=[
('state', 'present', ('data', 'filename'), True),
],
supports_check_mode=True
)
def run(self):
changed = self.process_object(**self.params)
state = self.params['state']
object = self._find()
self.exit_json(changed=changed)
if self.ansible.check_mode:
self.exit_json(changed=self._will_change(state, object))
if state == 'present' and not object:
# Create object
object = self._create()
self.exit_json(changed=True,
# metadata is not returned by
# to_dict(computed=False) so return it explicitly
object=dict(metadata=object.metadata,
**object.to_dict(computed=False)))
elif state == 'present' and object:
# Update object
update = self._build_update(object)
if update:
object = self._update(object, update)
self.exit_json(changed=bool(update),
# metadata is not returned by
# to_dict(computed=False) so return it explicitly
object=dict(metadata=object.metadata,
**object.to_dict(computed=False)))
elif state == 'absent' and object:
# Delete object
self._delete(object)
self.exit_json(changed=True)
elif state == 'absent' and not object:
# Do nothing
self.exit_json(changed=False)
def _build_update(self, object):
update = {}
container_name = self.params['container']
filename = self.params['filename']
if filename is not None:
if self.conn.object_store.is_object_stale(container_name,
object.id, filename):
update['filename'] = filename
return update
def _create(self):
name = self.params['name']
container_name = self.params['container']
kwargs = dict((k, self.params[k])
for k in ['data', 'filename']
if self.params[k] is not None)
return self.conn.object_store.create_object(container_name, name,
**kwargs)
def _delete(self, object):
container_name = self.params['container']
self.conn.object_store.delete_object(object.id,
container=container_name)
def _find(self):
name_or_id = self.params['name']
container_name = self.params['container']
# openstacksdk has no object_store.find_object() function
try:
return self.conn.object_store.get_object(name_or_id,
container=container_name)
except self.sdk.exceptions.ResourceNotFound:
return None
def _update(self, object, update):
filename = update.get('filename')
if filename:
container_name = self.params['container']
object = self.conn.object_store.create_object(container_name,
object.id,
filename=filename)
return object
def _will_change(self, state, object):
if state == 'present' and not object:
return True
elif state == 'present' and object:
return bool(self._build_update(object))
elif state == 'absent' and object:
return True
else:
# state == 'absent' and not object:
return False
def main():
module = SwiftObjectModule()
module = ObjectModule()
module()

View File

@ -4,106 +4,205 @@
# Copyright (c) 2021 by Open Telekom Cloud, operated by T-Systems International GmbH
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
DOCUMENTATION = r'''
---
module: object_container
short_description: Manage Swift container.
short_description: Manage a Swift container.
author: OpenStack Ansible SIG
description:
- Manage Swift container.
- Create, update and delete a Swift container.
options:
container:
description: Name of a container in Swift.
type: str
required: true
metadata:
description:
- Key/value pairs to be set as metadata on the container.
- If a container doesn't exist, it will be created.
- Both custom and system metadata can be set.
- Custom metadata are keys and values defined by the user.
- The system metadata keys are content_type, content_encoding, content_disposition, delete_after,\
delete_at, is_content_type_detected
type: dict
required: false
keys:
description: Keys from 'metadata' to be deleted.
type: list
elements: str
required: false
delete_with_all_objects:
description:
- Whether the container should be deleted with all objects or not.
- Without this parameter set to "true", an attempt to delete a container that contains objects will fail.
- Whether the container should be deleted recursively,
i.e. including all of its objects.
- If I(delete_with_all_objects) is set to C(false), an attempt to
delete a container which contains objects will fail.
type: bool
default: False
required: false
delete_metadata_keys:
description:
- Keys from I(metadata) to be deleted.
- "I(metadata) has precedence over I(delete_metadata_keys): If any
key is present in both options, then it will be created or updated,
not deleted."
- Metadata keys are case-insensitive.
type: list
elements: str
aliases: ['keys']
metadata:
description:
- Key value pairs to be set as metadata on the container.
- Both custom and system metadata can be set.
- Custom metadata are keys and values defined by the user.
- I(metadata) is the same as setting properties in openstackclient with
C(openstack container set --property ...).
- Metadata keys are case-insensitive.
type: dict
name:
description:
- Name (and ID) of a Swift container.
type: str
required: true
aliases: ['container']
read_ACL:
description:
- The ACL that grants read access.
- For example, use C(.r:*,.rlistings) for public access
and C('') for private access.
type: str
write_ACL:
description:
- The ACL that grants write access.
type: str
state:
description: Whether resource should be present or absent.
description:
- Whether the object should be C(present) or C(absent).
default: 'present'
choices: ['present', 'absent']
type: str
requirements:
- "python >= 3.6"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
RETURN = '''
RETURN = r'''
container:
description: Specifies the container.
returned: On success when C(state=present)
description: Dictionary describing the Swift container.
returned: On success when I(state) is C(present).
type: dict
sample:
{
"bytes": 5449,
"bytes_used": 5449,
"content_type": null,
"count": 1,
"id": "otc",
"if_none_match": null,
"is_content_type_detected": null,
"is_newest": null,
"meta_temp_url_key": null,
"meta_temp_url_key_2": null,
"name": "otc",
"object_count": 1,
"read_ACL": null,
"sync_key": null,
"sync_to": null,
"timestamp": null,
"versions_location": null,
"write_ACL": null
}
contains:
bytes:
description: The total number of bytes that are stored in Object Storage
for the container.
type: int
sample: 5449
bytes_used:
description: The count of bytes used in total.
type: int
sample: 5449
content_type:
description: The MIME type of the list of names.
type: str
sample: null
count:
description: The number of objects in the container.
type: int
sample: 1
history_location:
description: Enables versioning on the container.
type: str
sample: null
id:
description: The ID of the container. Equals I(name).
type: str
sample: "otc"
if_none_match:
description: "In combination with C(Expect: 100-Continue), specify an
C(If-None-Match: *) header to query whether the server
already has a copy of the object before any data is sent."
type: str
sample: null
is_content_type_detected:
description: If set to C(true), Object Storage guesses the content type
based on the file extension and ignores the value sent in
the Content-Type header, if present.
type: bool
sample: null
is_newest:
description: If set to True, Object Storage queries all replicas to
return the most recent one. If you omit this header, Object
Storage responds faster after it finds one valid replica.
Because setting this header to True is more expensive for
the back end, use it only when it is absolutely needed.
type: bool
sample: null
meta_temp_url_key:
description: The secret key value for temporary URLs. If not set,
this header is not returned by this operation.
type: str
sample: null
meta_temp_url_key_2:
description: A second secret key value for temporary URLs. If not set,
this header is not returned by this operation.
type: str
sample: null
name:
description: The name of the container.
type: str
sample: "otc"
object_count:
description: The number of objects.
type: int
sample: 1
read_ACL:
description: The ACL that grants read access. If not set, this header is
not returned by this operation.
type: str
sample: null
storage_policy:
description: Storage policy used by the container. It is not possible to
change policy of an existing container.
type: str
sample: null
sync_key:
description: The secret key for container synchronization. If not set,
this header is not returned by this operation.
type: str
sample: null
sync_to:
description: The destination for container synchronization. If not set,
this header is not returned by this operation.
type: str
sample: null
timestamp:
description: The timestamp of the transaction.
type: str
sample: null
versions_location:
description: Enables versioning on this container. The value is the name
of another container. You must UTF-8-encode and then
URL-encode the name before you include it in the header. To
disable versioning, set the header to an empty string.
type: str
sample: null
write_ACL:
description: The ACL that grants write access. If not set, this header is
not returned by this operation.
type: str
sample: null
'''
EXAMPLES = '''
# Create empty container
- openstack.cloud.object_container:
container: "new-container"
EXAMPLES = r'''
- name: Create empty container with public access
openstack.cloud.object_container:
name: "new-container"
state: present
read_ACL: ".r:*,.rlistings"
# Set metadata for container
- openstack.cloud.object_container:
container: "new-container"
metadata: "Cache-Control='no-cache'"
- name: Set metadata for container
openstack.cloud.object_container:
name: "new-container"
metadata:
'Cache-Control': 'no-cache'
'foo': 'bar'
# Delete some keys from metadata of a container
- openstack.cloud.object_container:
container: "new-container"
keys:
- content_type
- name: Delete metadata keys of a container
openstack.cloud.object_container:
name: "new-container"
delete_metadata_keys:
- foo
# Delete container
- openstack.cloud.object_container:
container: "new-container"
- name: Delete container
openstack.cloud.object_container:
name: "new-container"
state: absent
# Delete container and its objects
- openstack.cloud.object_container:
container: "new-container"
- name: Delete container and all its objects
openstack.cloud.object_container:
name: "new-container"
delete_with_all_objects: true
state: absent
'''
@ -114,88 +213,148 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O
class ContainerModule(OpenStackModule):
argument_spec = dict(
container=dict(required=True),
delete_metadata_keys=dict(type='list', elements='str',
no_log=False, # := noqa no-log-needed
aliases=['keys']),
delete_with_all_objects=dict(type='bool', default=False),
metadata=dict(type='dict'),
keys=dict(type='list', elements='str', no_log=False),
name=dict(required=True, aliases=['container']),
read_ACL=dict(),
state=dict(default='present', choices=['present', 'absent']),
delete_with_all_objects=dict(type='bool', default=False)
write_ACL=dict(),
)
def create(self, container):
data = {}
if self._container_exist(container):
self.exit_json(changed=False)
container_data = self.conn.object_store.create_container(name=container).to_dict()
container_data.pop('location')
data['container'] = container_data
self.exit_json(changed=True, **data)
def delete(self, container):
delete_with_all_objects = self.params['delete_with_all_objects']
changed = False
if self._container_exist(container):
objects = []
for raw in self.conn.object_store.objects(container):
dt = raw.to_dict()
dt.pop('location')
objects.append(dt)
if len(objects) > 0:
if delete_with_all_objects:
for obj in objects:
self.conn.object_store.delete_object(container=container, obj=obj['id'])
else:
self.fail_json(msg="Container has objects")
self.conn.object_store.delete_container(container=container)
changed = True
self.exit(changed=changed)
def set_metadata(self, container, metadata):
data = {}
if not self._container_exist(container):
new_container = self.conn.object_store.create_container(name=container).to_dict()
new_container = self.conn.object_store.set_container_metadata(container, **metadata).to_dict()
new_container.pop('location')
data['container'] = new_container
self.exit(changed=True, **data)
def delete_metadata(self, container, keys):
if not self._container_exist(container):
self.fail_json(msg="Container doesn't exist")
self.conn.object_store.delete_container_metadata(container=container, keys=keys)
self.exit(changed=True)
def _container_exist(self, container):
try:
self.conn.object_store.get_container_metadata(container)
return True
except self.sdk.exceptions.ResourceNotFound:
return False
module_kwargs = dict(
supports_check_mode=True
)
def run(self):
container = self.params['container']
state = self.params['state']
container = self._find()
if self.ansible.check_mode:
self.exit_json(changed=self._will_change(state, container))
if state == 'present' and not container:
# Create container
container = self._create()
self.exit_json(changed=True,
# metadata is not returned by
# to_dict(computed=False) so return it explicitly
container=dict(metadata=container.metadata,
**container.to_dict(computed=False)))
elif state == 'present' and container:
# Update container
update = self._build_update(container)
if update:
container = self._update(container, update)
self.exit_json(changed=bool(update),
# metadata is not returned by
# to_dict(computed=False) so return it explicitly
container=dict(metadata=container.metadata,
**container.to_dict(computed=False)))
elif state == 'absent' and container:
# Delete container
self._delete(container)
self.exit_json(changed=True)
elif state == 'absent' and not container:
# Do nothing
self.exit_json(changed=False)
def _build_update(self, container):
update = {}
metadata = self.params['metadata']
keys = self.params['keys']
if metadata is not None:
# Swift metadata keys must be treated as case-insensitive
old_metadata = dict((k.lower(), v)
for k, v in (container.metadata or {}))
new_metadata = dict((k, v) for k, v in metadata.items()
if k.lower() not in old_metadata
or v != old_metadata[k.lower()])
if new_metadata:
update['metadata'] = new_metadata
if state == 'absent':
self.delete(container)
delete_metadata_keys = self.params['delete_metadata_keys']
if delete_metadata_keys is not None:
for key in delete_metadata_keys:
if (container.metadata is not None
and key.lower() in [k.lower()
for k in container.metadata.keys()]):
update['delete_metadata_keys'] = delete_metadata_keys
break
attributes = dict((k, self.params[k])
for k in ['read_ACL', 'write_ACL']
if self.params[k] is not None
and self.params[k] != container[k])
if attributes:
update['attributes'] = attributes
return update
def _create(self):
kwargs = dict((k, self.params[k])
for k in ['metadata', 'name', 'read_ACL', 'write_ACL']
if self.params[k] is not None)
return self.conn.object_store.create_container(**kwargs)
def _delete(self, container):
if self.params['delete_with_all_objects']:
for object in self.conn.object_store.objects(container.name):
self.conn.object_store.delete_object(obj=object.name,
container=container.name)
self.conn.object_store.delete_container(container=container.name)
def _find(self):
name_or_id = self.params['name']
# openstacksdk has no container_store.find_container() function
try:
return self.conn.object_store.get_container_metadata(name_or_id)
except self.sdk.exceptions.ResourceNotFound:
return None
def _update(self, container, update):
delete_metadata_keys = update.get('delete_metadata_keys')
if delete_metadata_keys:
self.conn.object_store.delete_container_metadata(
container=container.name, keys=delete_metadata_keys)
# object_store.delete_container_metadata() does not delete keys
# from metadata dictionary so reload container
container = \
self.conn.object_store.get_container_metadata(container.name)
# metadata has higher precedence than delete_metadata_keys
# and thus is updated after later
metadata = update.get('metadata')
if metadata:
self.set_metadata(container, metadata)
if keys:
self.delete_metadata(container, keys)
container = self.conn.object_store.set_container_metadata(
container.name, refresh=True, **metadata)
self.create(container)
attributes = update.get('attributes')
if attributes:
container = self.conn.object_store.set_container_metadata(
container.name, refresh=True, **attributes)
return container
def _will_change(self, state, container):
if state == 'present' and not container:
return True
elif state == 'present' and container:
return bool(self._build_update(container))
elif state == 'absent' and container:
return True
else:
# state == 'absent' and not container:
return False
def main():