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