mirror of
https://opendev.org/openstack/ansible-collections-openstack.git
synced 2026-05-06 13:23:06 +00:00
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:
@@ -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
|
||||
required: true
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Name to be give to the object. If omitted, operations will be on
|
||||
the entire container
|
||||
required: false
|
||||
type: str
|
||||
filename:
|
||||
description:
|
||||
- Path to local file to be uploaded.
|
||||
required: false
|
||||
type: str
|
||||
container_access:
|
||||
description:
|
||||
- desired container access level.
|
||||
required: false
|
||||
choices: ['private', 'public']
|
||||
default: private
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Should the resource be present or absent.
|
||||
choices: [present, absent]
|
||||
default: present
|
||||
type: str
|
||||
container:
|
||||
description:
|
||||
- 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
|
||||
data:
|
||||
description:
|
||||
- The content to upload to the object.
|
||||
- Mutually exclusive with I(filename).
|
||||
- This attribute cannot be updated.
|
||||
type: str
|
||||
filename:
|
||||
description:
|
||||
- The path to the local file whose contents will be uploaded.
|
||||
- Mutually exclusive with I(data).
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Name (and ID) of the object.
|
||||
required: true
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Whether the object should be C(present) or C(absent).
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
type: str
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
- "openstacksdk"
|
||||
|
||||
- "python >= 3.6"
|
||||
- "openstacksdk"
|
||||
extends_documentation_fragment:
|
||||
- openstack.cloud.openstack
|
||||
- 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()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user