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:
parent
1ca6f208f7
commit
7c536e69b3
32
ci/roles/object/defaults/main.yml
Normal file
32
ci/roles/object/defaults/main.yml
Normal 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
|
@ -1,37 +1,35 @@
|
|||||||
---
|
---
|
||||||
- name: Create a test object file
|
|
||||||
shell: mktemp
|
|
||||||
register: tmp_file
|
|
||||||
|
|
||||||
- name: Create container
|
- name: Create container
|
||||||
openstack.cloud.object:
|
openstack.cloud.object_container:
|
||||||
cloud: "{{ cloud }}"
|
cloud: "{{ cloud }}"
|
||||||
state: present
|
state: present
|
||||||
container: ansible_container
|
name: ansible_container
|
||||||
container_access: private
|
|
||||||
|
|
||||||
- name: Put object
|
- name: Create object
|
||||||
openstack.cloud.object:
|
openstack.cloud.object:
|
||||||
cloud: "{{ cloud }}"
|
cloud: "{{ cloud }}"
|
||||||
state: present
|
state: present
|
||||||
name: ansible_object
|
name: ansible_object
|
||||||
filename: "{{ tmp_file.stdout }}"
|
data: "this is a test"
|
||||||
container: ansible_container
|
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
|
- name: Delete object
|
||||||
openstack.cloud.object:
|
openstack.cloud.object:
|
||||||
cloud: "{{ cloud }}"
|
cloud: "{{ cloud }}"
|
||||||
state: absent
|
state: absent
|
||||||
name: ansible_object
|
name: ansible_object
|
||||||
container: ansible_container
|
container: ansible_container
|
||||||
|
|
||||||
- name: Delete container
|
- name: Delete container
|
||||||
openstack.cloud.object:
|
openstack.cloud.object_container:
|
||||||
cloud: "{{ cloud }}"
|
cloud: "{{ cloud }}"
|
||||||
state: absent
|
state: absent
|
||||||
container: ansible_container
|
name: ansible_container
|
||||||
|
|
||||||
- name: Delete test object file
|
|
||||||
file:
|
|
||||||
name: "{{ tmp_file.stdout }}"
|
|
||||||
state: absent
|
|
||||||
|
@ -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
|
||||||
|
@ -1,63 +1,93 @@
|
|||||||
---
|
---
|
||||||
- module_defaults:
|
- name: Create an empty container with public access
|
||||||
group/openstack.cloud.openstack:
|
openstack.cloud.object_container:
|
||||||
cloud: "{{ cloud }}"
|
cloud: "{{ cloud }}"
|
||||||
# Backward compatibility with Ansible 2.9
|
name: ansible_container
|
||||||
openstack.cloud.object_container:
|
read_ACL: ".r:*,.rlistings"
|
||||||
cloud: "{{ cloud }}"
|
register: container
|
||||||
block:
|
|
||||||
- name: Create an empty container
|
|
||||||
openstack.cloud.object_container:
|
|
||||||
container: "{{ container_name }}"
|
|
||||||
register: container
|
|
||||||
|
|
||||||
- name: Verify container was created
|
- name: Assert return values of container module
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- container is success
|
- container is changed
|
||||||
- container is changed
|
- container.container.name == "ansible_container"
|
||||||
- container.container.name == container_name
|
- 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:
|
openstack.cloud.object_container:
|
||||||
container: "{{ container_name }}"
|
cloud: "{{ cloud }}"
|
||||||
metadata: "Cache-Control='no-cache'"
|
name: ansible_container
|
||||||
register: set_meta
|
metadata:
|
||||||
|
'Cache-Control': 'no-cache'
|
||||||
|
'foo': 'bar'
|
||||||
|
register: container
|
||||||
|
|
||||||
- name: Verify container metadata was set
|
- name: Verify container metadata was set
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- set_meta is success
|
- container is changed
|
||||||
- set_meta 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:
|
openstack.cloud.object_container:
|
||||||
container: "{{ container_name }}"
|
cloud: "{{ cloud }}"
|
||||||
keys:
|
name: ansible_container
|
||||||
- Cache-Control
|
delete_metadata_keys:
|
||||||
register: delete_meta
|
- 'Cache-Control'
|
||||||
|
read_ACL: ""
|
||||||
|
register: container
|
||||||
|
|
||||||
- name: Verify some keys from container metadata was deleted
|
- name: Verify updated container
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- delete_meta is success
|
- container is changed
|
||||||
- delete_meta 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
|
- name: Delete container
|
||||||
openstack.cloud.object_container:
|
openstack.cloud.object_container:
|
||||||
container: "{{ container_name }}"
|
cloud: "{{ cloud }}"
|
||||||
state: absent
|
name: ansible_container
|
||||||
register: deleted
|
state: absent
|
||||||
|
register: container
|
||||||
|
|
||||||
- name: Verify container was deleted
|
- name: Verify container was deleted
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- deleted is success
|
- container is changed
|
||||||
- deleted is changed
|
|
||||||
|
|
||||||
always:
|
- name: Delete container again
|
||||||
- name: Delete container
|
openstack.cloud.object_container:
|
||||||
openstack.cloud.object_container:
|
cloud: "{{ cloud }}"
|
||||||
container: "{{ container_name }}"
|
name: ansible_container
|
||||||
state: absent
|
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
|
||||||
|
@ -11,9 +11,6 @@
|
|||||||
- { role: compute_flavor_access, tags: compute_flavor_access }
|
- { role: compute_flavor_access, tags: compute_flavor_access }
|
||||||
- { role: config, tags: config }
|
- { role: config, tags: config }
|
||||||
- { role: dns_zone_info, tags: dns_zone_info }
|
- { role: dns_zone_info, tags: dns_zone_info }
|
||||||
- role: object_container
|
|
||||||
tags: object_container
|
|
||||||
when: sdk_version is version(0.18, '>=')
|
|
||||||
- role: dns
|
- role: dns
|
||||||
tags: dns
|
tags: dns
|
||||||
when: sdk_version is version(0.28, '>=')
|
when: sdk_version is version(0.28, '>=')
|
||||||
@ -45,6 +42,7 @@
|
|||||||
tags: nova_services
|
tags: nova_services
|
||||||
when: sdk_version is version(0.44, '>=')
|
when: sdk_version is version(0.44, '>=')
|
||||||
- { role: object, tags: object }
|
- { role: object, tags: object }
|
||||||
|
- { role: object_container, tags: object_container }
|
||||||
- { role: port, tags: port }
|
- { role: port, tags: port }
|
||||||
- { role: project, tags: project }
|
- { role: project, tags: project }
|
||||||
- { role: project_info, tags: project_info }
|
- { role: project_info, tags: project_info }
|
||||||
|
@ -5,115 +5,341 @@
|
|||||||
# Copyright (c) 2013, Benno Joy <benno@ansible.com>
|
# 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)
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
DOCUMENTATION = r'''
|
||||||
---
|
---
|
||||||
module: object
|
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
|
author: OpenStack Ansible SIG
|
||||||
description:
|
description:
|
||||||
- Create or Delete objects and containers from OpenStack
|
- Create or delete Swift objects in OpenStack clouds
|
||||||
options:
|
options:
|
||||||
container:
|
container:
|
||||||
description:
|
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.
|
||||||
required: true
|
- This container will not be created if it does not exist already.
|
||||||
type: str
|
required: true
|
||||||
name:
|
type: str
|
||||||
description:
|
data:
|
||||||
- Name to be give to the object. If omitted, operations will be on
|
description:
|
||||||
the entire container
|
- The content to upload to the object.
|
||||||
required: false
|
- Mutually exclusive with I(filename).
|
||||||
type: str
|
- This attribute cannot be updated.
|
||||||
filename:
|
type: str
|
||||||
description:
|
filename:
|
||||||
- Path to local file to be uploaded.
|
description:
|
||||||
required: false
|
- The path to the local file whose contents will be uploaded.
|
||||||
type: str
|
- Mutually exclusive with I(data).
|
||||||
container_access:
|
type: str
|
||||||
description:
|
name:
|
||||||
- desired container access level.
|
description:
|
||||||
required: false
|
- Name (and ID) of the object.
|
||||||
choices: ['private', 'public']
|
required: true
|
||||||
default: private
|
type: str
|
||||||
type: str
|
state:
|
||||||
state:
|
description:
|
||||||
description:
|
- Whether the object should be C(present) or C(absent).
|
||||||
- Should the resource be present or absent.
|
choices: ['present', 'absent']
|
||||||
choices: [present, absent]
|
default: present
|
||||||
default: present
|
type: str
|
||||||
type: str
|
|
||||||
requirements:
|
requirements:
|
||||||
- "python >= 3.6"
|
- "python >= 3.6"
|
||||||
- "openstacksdk"
|
- "openstacksdk"
|
||||||
|
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- openstack.cloud.openstack
|
- openstack.cloud.openstack
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES = '''
|
RETURN = r'''
|
||||||
- name: "Create a object named 'fstab' in the 'config' container"
|
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:
|
openstack.cloud.object:
|
||||||
cloud: mordred
|
cloud: mordred
|
||||||
state: present
|
|
||||||
name: fstab
|
|
||||||
container: config
|
container: config
|
||||||
filename: /etc/fstab
|
filename: /etc/fstab
|
||||||
|
name: fstab
|
||||||
|
state: present
|
||||||
|
|
||||||
- name: Delete a container called config and all of its contents
|
- name: Delete a container called config and all of its contents
|
||||||
openstack.cloud.object:
|
openstack.cloud.object:
|
||||||
cloud: rax-iad
|
cloud: rax-iad
|
||||||
state: absent
|
|
||||||
container: config
|
container: config
|
||||||
|
state: absent
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
||||||
|
|
||||||
|
|
||||||
class SwiftObjectModule(OpenStackModule):
|
class ObjectModule(OpenStackModule):
|
||||||
argument_spec = dict(
|
argument_spec = dict(
|
||||||
name=dict(),
|
|
||||||
container=dict(required=True),
|
container=dict(required=True),
|
||||||
|
data=dict(),
|
||||||
filename=dict(),
|
filename=dict(),
|
||||||
container_access=dict(default='private', choices=['private', 'public']),
|
name=dict(required=True),
|
||||||
state=dict(default='present', choices=['absent', 'present']),
|
state=dict(default='present', choices=['absent', 'present']),
|
||||||
)
|
)
|
||||||
module_kwargs = dict()
|
|
||||||
|
|
||||||
def process_object(
|
module_kwargs = dict(
|
||||||
self, container, name, filename, container_access, **kwargs
|
mutually_exclusive=[
|
||||||
):
|
('data', 'filename'),
|
||||||
changed = False
|
],
|
||||||
container_obj = self.conn.get_container(container)
|
required_if=[
|
||||||
if kwargs['state'] == 'present':
|
('state', 'present', ('data', 'filename'), True),
|
||||||
if not container_obj:
|
],
|
||||||
container_obj = self.conn.create_container(container)
|
supports_check_mode=True
|
||||||
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
|
|
||||||
|
|
||||||
def run(self):
|
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():
|
def main():
|
||||||
module = SwiftObjectModule()
|
module = ObjectModule()
|
||||||
module()
|
module()
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,106 +4,205 @@
|
|||||||
# Copyright (c) 2021 by Open Telekom Cloud, operated by T-Systems International GmbH
|
# 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)
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
DOCUMENTATION = r'''
|
||||||
---
|
---
|
||||||
module: object_container
|
module: object_container
|
||||||
short_description: Manage Swift container.
|
short_description: Manage a Swift container.
|
||||||
author: OpenStack Ansible SIG
|
author: OpenStack Ansible SIG
|
||||||
description:
|
description:
|
||||||
- Manage Swift container.
|
- Create, update and delete a Swift container.
|
||||||
options:
|
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:
|
delete_with_all_objects:
|
||||||
description:
|
description:
|
||||||
- Whether the container should be deleted with all objects or not.
|
- Whether the container should be deleted recursively,
|
||||||
- Without this parameter set to "true", an attempt to delete a container that contains objects will fail.
|
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
|
type: bool
|
||||||
default: False
|
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:
|
state:
|
||||||
description: Whether resource should be present or absent.
|
description:
|
||||||
|
- Whether the object should be C(present) or C(absent).
|
||||||
default: 'present'
|
default: 'present'
|
||||||
choices: ['present', 'absent']
|
choices: ['present', 'absent']
|
||||||
type: str
|
type: str
|
||||||
requirements:
|
requirements:
|
||||||
- "python >= 3.6"
|
- "python >= 3.6"
|
||||||
- "openstacksdk"
|
- "openstacksdk"
|
||||||
|
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- openstack.cloud.openstack
|
- openstack.cloud.openstack
|
||||||
'''
|
'''
|
||||||
|
|
||||||
RETURN = '''
|
RETURN = r'''
|
||||||
container:
|
container:
|
||||||
description: Specifies the container.
|
description: Dictionary describing the Swift container.
|
||||||
returned: On success when C(state=present)
|
returned: On success when I(state) is C(present).
|
||||||
type: dict
|
type: dict
|
||||||
sample:
|
contains:
|
||||||
{
|
bytes:
|
||||||
"bytes": 5449,
|
description: The total number of bytes that are stored in Object Storage
|
||||||
"bytes_used": 5449,
|
for the container.
|
||||||
"content_type": null,
|
type: int
|
||||||
"count": 1,
|
sample: 5449
|
||||||
"id": "otc",
|
bytes_used:
|
||||||
"if_none_match": null,
|
description: The count of bytes used in total.
|
||||||
"is_content_type_detected": null,
|
type: int
|
||||||
"is_newest": null,
|
sample: 5449
|
||||||
"meta_temp_url_key": null,
|
content_type:
|
||||||
"meta_temp_url_key_2": null,
|
description: The MIME type of the list of names.
|
||||||
"name": "otc",
|
type: str
|
||||||
"object_count": 1,
|
sample: null
|
||||||
"read_ACL": null,
|
count:
|
||||||
"sync_key": null,
|
description: The number of objects in the container.
|
||||||
"sync_to": null,
|
type: int
|
||||||
"timestamp": null,
|
sample: 1
|
||||||
"versions_location": null,
|
history_location:
|
||||||
"write_ACL": null
|
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 = '''
|
EXAMPLES = r'''
|
||||||
# Create empty container
|
- name: Create empty container with public access
|
||||||
- openstack.cloud.object_container:
|
openstack.cloud.object_container:
|
||||||
container: "new-container"
|
name: "new-container"
|
||||||
state: present
|
state: present
|
||||||
|
read_ACL: ".r:*,.rlistings"
|
||||||
|
|
||||||
# Set metadata for container
|
- name: Set metadata for container
|
||||||
- openstack.cloud.object_container:
|
openstack.cloud.object_container:
|
||||||
container: "new-container"
|
name: "new-container"
|
||||||
metadata: "Cache-Control='no-cache'"
|
metadata:
|
||||||
|
'Cache-Control': 'no-cache'
|
||||||
|
'foo': 'bar'
|
||||||
|
|
||||||
# Delete some keys from metadata of a container
|
- name: Delete metadata keys of a container
|
||||||
- openstack.cloud.object_container:
|
openstack.cloud.object_container:
|
||||||
container: "new-container"
|
name: "new-container"
|
||||||
keys:
|
delete_metadata_keys:
|
||||||
- content_type
|
- foo
|
||||||
|
|
||||||
# Delete container
|
- name: Delete container
|
||||||
- openstack.cloud.object_container:
|
openstack.cloud.object_container:
|
||||||
container: "new-container"
|
name: "new-container"
|
||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
# Delete container and its objects
|
- name: Delete container and all its objects
|
||||||
- openstack.cloud.object_container:
|
openstack.cloud.object_container:
|
||||||
container: "new-container"
|
name: "new-container"
|
||||||
delete_with_all_objects: true
|
delete_with_all_objects: true
|
||||||
state: absent
|
state: absent
|
||||||
'''
|
'''
|
||||||
@ -114,88 +213,148 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O
|
|||||||
class ContainerModule(OpenStackModule):
|
class ContainerModule(OpenStackModule):
|
||||||
|
|
||||||
argument_spec = dict(
|
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'),
|
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']),
|
state=dict(default='present', choices=['present', 'absent']),
|
||||||
delete_with_all_objects=dict(type='bool', default=False)
|
write_ACL=dict(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def create(self, container):
|
module_kwargs = dict(
|
||||||
|
supports_check_mode=True
|
||||||
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
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
container = self.params['container']
|
|
||||||
state = self.params['state']
|
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']
|
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':
|
delete_metadata_keys = self.params['delete_metadata_keys']
|
||||||
self.delete(container)
|
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:
|
if metadata:
|
||||||
self.set_metadata(container, metadata)
|
container = self.conn.object_store.set_container_metadata(
|
||||||
if keys:
|
container.name, refresh=True, **metadata)
|
||||||
self.delete_metadata(container, keys)
|
|
||||||
|
|
||||||
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():
|
def main():
|
||||||
|
Loading…
Reference in New Issue
Block a user