mirror of https://github.com/ceph/ceph-ansible.git
library: Add radosgw_caps to manage capabilities
This commit add `radosgw_caps` module to be able to manage RadosGW users capabilities. Usage from module's documentation: ```YAML - name: add users read write and all buckets capabilities radosgw_caps: name: foo state: present caps: - users=read,write - buckets=* - name: remove usage write capabilities radosgw_caps: name: foo state: absent caps: - usage=write ``` This module support check mode by simulating the original `radosgw-admin` behavior when adding capabilities. Signed-off-by: Mathias Chapelain <mathias.chapelain@proton.ch>pull/7052/head
parent
055326be7c
commit
65003f4ff9
|
@ -0,0 +1,378 @@
|
||||||
|
# Copyright 2022, Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ansible.module_utils.ca_common import (
|
||||||
|
exit_module,
|
||||||
|
exec_command,
|
||||||
|
is_containerized,
|
||||||
|
container_exec,
|
||||||
|
)
|
||||||
|
except ImportError:
|
||||||
|
from module_utils.ca_common import (
|
||||||
|
exit_module,
|
||||||
|
exec_command,
|
||||||
|
is_containerized,
|
||||||
|
container_exec,
|
||||||
|
)
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from enum import IntFlag
|
||||||
|
|
||||||
|
|
||||||
|
ANSIBLE_METADATA = {
|
||||||
|
"metadata_version": "1.1",
|
||||||
|
"status": ["preview"],
|
||||||
|
"supported_by": "community",
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
---
|
||||||
|
module: radosgw_caps
|
||||||
|
|
||||||
|
short_description: Manage RADOS Gateway Admin capabilities
|
||||||
|
|
||||||
|
version_added: "2.10"
|
||||||
|
|
||||||
|
description:
|
||||||
|
- Manage RADOS Gateway capabilities addition and deletion.
|
||||||
|
options:
|
||||||
|
cluster:
|
||||||
|
description:
|
||||||
|
- The ceph cluster name.
|
||||||
|
required: false
|
||||||
|
default: ceph
|
||||||
|
type: str
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- name of the RADOS Gateway user (uid).
|
||||||
|
required: true
|
||||||
|
type: str
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
If 'present' is used, the module will assign capabilities
|
||||||
|
defined in `caps`.
|
||||||
|
If 'absent' is used, the module will remove the capabilities.
|
||||||
|
required: false
|
||||||
|
choices: ['present', 'absent']
|
||||||
|
default: present
|
||||||
|
type: str
|
||||||
|
caps:
|
||||||
|
description:
|
||||||
|
- The set of capabilities to assign or remove.
|
||||||
|
required: true
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
|
||||||
|
author:
|
||||||
|
- Mathias Chapelain <mathias.chapelain@proton.ch>
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = """
|
||||||
|
- name: add users read capabilties to a user
|
||||||
|
radosgw_caps:
|
||||||
|
name: foo
|
||||||
|
state: present
|
||||||
|
caps:
|
||||||
|
- users=read
|
||||||
|
|
||||||
|
- name: add users read write and all buckets capabilities
|
||||||
|
radosgw_caps:
|
||||||
|
name: foo
|
||||||
|
state: present
|
||||||
|
caps:
|
||||||
|
- users=read,write
|
||||||
|
- buckets=*
|
||||||
|
|
||||||
|
- name: remove usage write capabilities
|
||||||
|
radosgw_caps:
|
||||||
|
name: foo
|
||||||
|
state: absent
|
||||||
|
caps:
|
||||||
|
- usage=write
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = """
|
||||||
|
---
|
||||||
|
cmd:
|
||||||
|
description: The radosgw-admin command being run by the module to apply caps settings.
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
start:
|
||||||
|
description: Timestamp of module execution start.
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
end:
|
||||||
|
description: Timestamp of module execution end.
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
delta:
|
||||||
|
description: Time of module execution between start and end.
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
diff:
|
||||||
|
description: Dict containing the user capabilities before and after modifications.
|
||||||
|
returned: always
|
||||||
|
type: dict
|
||||||
|
contains:
|
||||||
|
before:
|
||||||
|
description: Contains user capabilities, json-formatted, as returned by `radosgw-admin user info`.
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
after:
|
||||||
|
description: Contains user capabilities, json-formatted, as returned by `radosgw-admin caps add/rm`.
|
||||||
|
returned: success
|
||||||
|
type: str
|
||||||
|
rc:
|
||||||
|
description: Return code of the module command executed, see `cmd` return value.
|
||||||
|
returned: always
|
||||||
|
type: int
|
||||||
|
stdout:
|
||||||
|
description: Output of the executed command.
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
stderr:
|
||||||
|
description: Error output of the executed command.
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
changed:
|
||||||
|
description: Specify if user capabilities has been changed during module execution.
|
||||||
|
returned: always
|
||||||
|
type: bool
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def pre_generate_radosgw_cmd(container_image=None):
|
||||||
|
"""
|
||||||
|
Generate radosgw-admin prefix comaand
|
||||||
|
"""
|
||||||
|
if container_image:
|
||||||
|
cmd = container_exec("radosgw-admin", container_image)
|
||||||
|
else:
|
||||||
|
cmd = ["radosgw-admin"]
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
|
def generate_radosgw_cmd(cluster, args, container_image=None):
|
||||||
|
"""
|
||||||
|
Generate 'radosgw' command line to execute
|
||||||
|
"""
|
||||||
|
|
||||||
|
cmd = pre_generate_radosgw_cmd(container_image=container_image)
|
||||||
|
|
||||||
|
base_cmd = ["--cluster", cluster, "caps"]
|
||||||
|
|
||||||
|
cmd.extend(base_cmd + args)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
|
def add_caps(module, container_image=None):
|
||||||
|
"""
|
||||||
|
Add capabilities
|
||||||
|
"""
|
||||||
|
|
||||||
|
cluster = module.params.get("cluster")
|
||||||
|
name = module.params.get("name")
|
||||||
|
caps = module.params.get("caps")
|
||||||
|
|
||||||
|
args = ["add", "--uid=" + name, "--caps=" + ";".join(caps)]
|
||||||
|
|
||||||
|
cmd = generate_radosgw_cmd(
|
||||||
|
cluster=cluster, args=args, container_image=container_image
|
||||||
|
)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
|
def remove_caps(module, container_image=None):
|
||||||
|
"""
|
||||||
|
Remove capabilities
|
||||||
|
"""
|
||||||
|
|
||||||
|
cluster = module.params.get("cluster")
|
||||||
|
name = module.params.get("name")
|
||||||
|
caps = module.params.get("caps")
|
||||||
|
|
||||||
|
args = ["rm", "--uid=" + name, "--caps=" + ";".join(caps)]
|
||||||
|
|
||||||
|
cmd = generate_radosgw_cmd(
|
||||||
|
cluster=cluster, args=args, container_image=container_image
|
||||||
|
)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
|
def get_user(module, container_image=None):
|
||||||
|
"""
|
||||||
|
Get existing user
|
||||||
|
"""
|
||||||
|
|
||||||
|
cluster = module.params.get("cluster")
|
||||||
|
name = module.params.get("name")
|
||||||
|
|
||||||
|
args = ["info", "--uid=" + name, "--format=json"]
|
||||||
|
|
||||||
|
cmd = pre_generate_radosgw_cmd(container_image=container_image)
|
||||||
|
|
||||||
|
base_cmd = ["--cluster", cluster, "user"]
|
||||||
|
|
||||||
|
cmd.extend(base_cmd + args)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
|
class RGWUserCaps(IntFlag):
|
||||||
|
INVALID = 0x0
|
||||||
|
READ = 0x1
|
||||||
|
WRITE = 0x2
|
||||||
|
ALL = READ | WRITE
|
||||||
|
|
||||||
|
|
||||||
|
def perm_string_to_flag(perm):
|
||||||
|
splitted = re.split(",|=| |\t", perm)
|
||||||
|
if ("read" in splitted and "write" in splitted) or "*" in splitted:
|
||||||
|
return RGWUserCaps.ALL
|
||||||
|
elif "read" in splitted:
|
||||||
|
return RGWUserCaps.READ
|
||||||
|
elif "write" in splitted:
|
||||||
|
return RGWUserCaps.WRITE
|
||||||
|
return RGWUserCaps.INVALID
|
||||||
|
|
||||||
|
|
||||||
|
def perm_flag_to_string(perm):
|
||||||
|
if perm == RGWUserCaps.ALL:
|
||||||
|
return "*"
|
||||||
|
elif perm == RGWUserCaps.READ:
|
||||||
|
return "read"
|
||||||
|
elif perm == RGWUserCaps.WRITE:
|
||||||
|
return "write"
|
||||||
|
else:
|
||||||
|
return "invalid"
|
||||||
|
|
||||||
|
|
||||||
|
def params_to_caps_output(current_caps, params, deletion=False):
|
||||||
|
out_caps = current_caps
|
||||||
|
for param in params:
|
||||||
|
splitted = param.split("=", maxsplit=1)
|
||||||
|
cap = splitted[0]
|
||||||
|
|
||||||
|
new_perm = perm_string_to_flag(splitted[1])
|
||||||
|
current = next((item for item in out_caps if item["type"] == cap), None)
|
||||||
|
|
||||||
|
if not current:
|
||||||
|
if not deletion:
|
||||||
|
out_caps.append(dict(type=cap, perm=perm_flag_to_string(new_perm)))
|
||||||
|
continue
|
||||||
|
|
||||||
|
current_perm = perm_string_to_flag(current["perm"])
|
||||||
|
|
||||||
|
new_perm = current_perm & ~new_perm if deletion else new_perm | current_perm
|
||||||
|
|
||||||
|
if new_perm == 0x0:
|
||||||
|
out_caps.remove(current)
|
||||||
|
|
||||||
|
current["perm"] = perm_flag_to_string(new_perm)
|
||||||
|
|
||||||
|
return out_caps
|
||||||
|
|
||||||
|
|
||||||
|
def run_module():
|
||||||
|
module_args = dict(
|
||||||
|
cluster=dict(type="str", required=False, default="ceph"),
|
||||||
|
name=dict(type="str", required=True),
|
||||||
|
state=dict(
|
||||||
|
type="str", required=False, choices=["present", "absent"], default="present"
|
||||||
|
),
|
||||||
|
caps=dict(type="list", required=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=module_args,
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Gather module parameters in variables
|
||||||
|
name = module.params.get("name")
|
||||||
|
state = module.params.get("state")
|
||||||
|
caps = module.params.get("caps")
|
||||||
|
|
||||||
|
startd = datetime.datetime.now()
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
# will return either the image name or None
|
||||||
|
container_image = is_containerized()
|
||||||
|
|
||||||
|
diff = dict(before="", after="")
|
||||||
|
|
||||||
|
# get user infos for diff
|
||||||
|
rc, cmd, out, err = exec_command(
|
||||||
|
module, get_user(module, container_image=container_image)
|
||||||
|
)
|
||||||
|
|
||||||
|
if rc == 0:
|
||||||
|
before_user = json.loads(out)
|
||||||
|
before_caps = sorted(before_user["caps"], key=lambda d: d["type"])
|
||||||
|
diff["before"] = json.dumps(before_caps, indent=4)
|
||||||
|
|
||||||
|
out = ""
|
||||||
|
err = ""
|
||||||
|
|
||||||
|
if state == "present":
|
||||||
|
cmd = add_caps(module, container_image=container_image)
|
||||||
|
elif state == "absent":
|
||||||
|
cmd = remove_caps(module, container_image=container_image)
|
||||||
|
|
||||||
|
if not module.check_mode:
|
||||||
|
rc, cmd, out, err = exec_command(module, cmd)
|
||||||
|
else:
|
||||||
|
out_caps = params_to_caps_output(
|
||||||
|
before_user["caps"], caps, deletion=(state == "absent")
|
||||||
|
)
|
||||||
|
out = json.dumps(dict(caps=out_caps))
|
||||||
|
|
||||||
|
if rc == 0:
|
||||||
|
after_user = json.loads(out)["caps"]
|
||||||
|
after_user = sorted(after_user, key=lambda d: d["type"])
|
||||||
|
diff["after"] = json.dumps(after_user, indent=4)
|
||||||
|
changed = diff["before"] != diff["after"]
|
||||||
|
else:
|
||||||
|
out = "User {} doesn't exist".format(name)
|
||||||
|
|
||||||
|
exit_module(
|
||||||
|
module=module,
|
||||||
|
out=out,
|
||||||
|
rc=rc,
|
||||||
|
cmd=cmd,
|
||||||
|
err=err,
|
||||||
|
startd=startd,
|
||||||
|
changed=changed,
|
||||||
|
diff=diff,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
run_module()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
Loading…
Reference in New Issue