
With "extends_documentation_fragment: ['openstack.cloud.openstack']" it is not necessary to list required Python libraries in section 'requirements' of DOCUMENTATION docstring in modules. Ansible will merge requirements from doc fragments and DOCUMENTATION docstring which previously resulted in duplicates such as in server module [0]: * openstacksdk * openstacksdk >= 0.36, < 0.99.0 * python >= 3.6 When removing the 'requirements' section from server module, then Ansible will list openstacksdk once only: * openstacksdk >= 0.36, < 0.99.0 * python >= 3.6 To see what documentation Ansible will produce for server module run: ansible-doc --type module openstack.cloud.server [0] https://docs.ansible.com/ansible/latest/collections/openstack/\ cloud/server_module.html Change-Id: I727ed95ee480bb644b5a533f6a9526973677064c
364 lines
12 KiB
Python
364 lines
12 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# 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 = r'''
|
|
---
|
|
module: object_container
|
|
short_description: Manage a Swift container.
|
|
author: OpenStack Ansible SIG
|
|
description:
|
|
- Create, update and delete a Swift container.
|
|
options:
|
|
delete_with_all_objects:
|
|
description:
|
|
- 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
|
|
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 the object should be C(present) or C(absent).
|
|
default: 'present'
|
|
choices: ['present', 'absent']
|
|
type: str
|
|
extends_documentation_fragment:
|
|
- openstack.cloud.openstack
|
|
'''
|
|
|
|
RETURN = r'''
|
|
container:
|
|
description: Dictionary describing the Swift container.
|
|
returned: On success when I(state) is C(present).
|
|
type: dict
|
|
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 = r'''
|
|
- name: Create empty container with public access
|
|
openstack.cloud.object_container:
|
|
name: "new-container"
|
|
state: present
|
|
read_ACL: ".r:*,.rlistings"
|
|
|
|
- name: Set metadata for container
|
|
openstack.cloud.object_container:
|
|
name: "new-container"
|
|
metadata:
|
|
'Cache-Control': 'no-cache'
|
|
'foo': 'bar'
|
|
|
|
- name: Delete metadata keys of a container
|
|
openstack.cloud.object_container:
|
|
name: "new-container"
|
|
delete_metadata_keys:
|
|
- foo
|
|
|
|
- name: Delete container
|
|
openstack.cloud.object_container:
|
|
name: "new-container"
|
|
state: absent
|
|
|
|
- name: Delete container and all its objects
|
|
openstack.cloud.object_container:
|
|
name: "new-container"
|
|
delete_with_all_objects: true
|
|
state: absent
|
|
'''
|
|
|
|
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
|
|
|
|
|
class ContainerModule(OpenStackModule):
|
|
|
|
argument_spec = dict(
|
|
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'),
|
|
name=dict(required=True, aliases=['container']),
|
|
read_ACL=dict(),
|
|
state=dict(default='present', choices=['present', 'absent']),
|
|
write_ACL=dict(),
|
|
)
|
|
|
|
module_kwargs = dict(
|
|
supports_check_mode=True
|
|
)
|
|
|
|
def run(self):
|
|
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']
|
|
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
|
|
|
|
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:
|
|
container = self.conn.object_store.set_container_metadata(
|
|
container.name, refresh=True, **metadata)
|
|
|
|
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():
|
|
module = ContainerModule()
|
|
module()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|