mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-21 12:20:21 -07:00
Unflatmap community.general (#5461)
* Move files. * Update imports and references. * Move wrongly placed files. * Reverse redirects, deprecate long → short name redirects. * Simplify contribution guidelines for new modules. * Rewrite BOTMETA. * Add changelog fragment. * Fix ignore.txt files.
This commit is contained in:
parent
2b0bebc8fc
commit
b531ecdc9b
1033 changed files with 4802 additions and 1989 deletions
478
plugins/modules/xfs_quota.py
Normal file
478
plugins/modules/xfs_quota.py
Normal file
|
@ -0,0 +1,478 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2018, Emmanouil Kampitakis <info@kampitakis.de>
|
||||
# Copyright (c) 2018, William Leemans <willie@elaba.net>
|
||||
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
---
|
||||
module: xfs_quota
|
||||
short_description: Manage quotas on XFS filesystems
|
||||
description:
|
||||
- Configure quotas on XFS filesystems.
|
||||
- Before using this module /etc/projects and /etc/projid need to be configured.
|
||||
author:
|
||||
- William Leemans (@bushvin)
|
||||
options:
|
||||
type:
|
||||
description:
|
||||
- The XFS quota type.
|
||||
type: str
|
||||
required: true
|
||||
choices:
|
||||
- user
|
||||
- group
|
||||
- project
|
||||
name:
|
||||
description:
|
||||
- The name of the user, group or project to apply the quota to, if other than default.
|
||||
type: str
|
||||
mountpoint:
|
||||
description:
|
||||
- The mount point on which to apply the quotas.
|
||||
type: str
|
||||
required: true
|
||||
bhard:
|
||||
description:
|
||||
- Hard blocks quota limit.
|
||||
- This argument supports human readable sizes.
|
||||
type: str
|
||||
bsoft:
|
||||
description:
|
||||
- Soft blocks quota limit.
|
||||
- This argument supports human readable sizes.
|
||||
type: str
|
||||
ihard:
|
||||
description:
|
||||
- Hard inodes quota limit.
|
||||
type: int
|
||||
isoft:
|
||||
description:
|
||||
- Soft inodes quota limit.
|
||||
type: int
|
||||
rtbhard:
|
||||
description:
|
||||
- Hard realtime blocks quota limit.
|
||||
- This argument supports human readable sizes.
|
||||
type: str
|
||||
rtbsoft:
|
||||
description:
|
||||
- Soft realtime blocks quota limit.
|
||||
- This argument supports human readable sizes.
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Whether to apply the limits or remove them.
|
||||
- When removing limit, they are set to 0, and not quite removed.
|
||||
type: str
|
||||
default: present
|
||||
choices:
|
||||
- present
|
||||
- absent
|
||||
|
||||
requirements:
|
||||
- xfsprogs
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
- name: Set default project soft and hard limit on /opt of 1g
|
||||
community.general.xfs_quota:
|
||||
type: project
|
||||
mountpoint: /opt
|
||||
bsoft: 1g
|
||||
bhard: 1g
|
||||
state: present
|
||||
|
||||
- name: Remove the default limits on /opt
|
||||
community.general.xfs_quota:
|
||||
type: project
|
||||
mountpoint: /opt
|
||||
state: absent
|
||||
|
||||
- name: Set default soft user inode limits on /home of 1024 inodes and hard of 2048
|
||||
community.general.xfs_quota:
|
||||
type: user
|
||||
mountpoint: /home
|
||||
isoft: 1024
|
||||
ihard: 2048
|
||||
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
bhard:
|
||||
description: the current bhard setting in bytes
|
||||
returned: always
|
||||
type: int
|
||||
sample: 1024
|
||||
bsoft:
|
||||
description: the current bsoft setting in bytes
|
||||
returned: always
|
||||
type: int
|
||||
sample: 1024
|
||||
ihard:
|
||||
description: the current ihard setting in bytes
|
||||
returned: always
|
||||
type: int
|
||||
sample: 100
|
||||
isoft:
|
||||
description: the current isoft setting in bytes
|
||||
returned: always
|
||||
type: int
|
||||
sample: 100
|
||||
rtbhard:
|
||||
description: the current rtbhard setting in bytes
|
||||
returned: always
|
||||
type: int
|
||||
sample: 1024
|
||||
rtbsoft:
|
||||
description: the current rtbsoft setting in bytes
|
||||
returned: always
|
||||
type: int
|
||||
sample: 1024
|
||||
"""
|
||||
|
||||
import grp
|
||||
import os
|
||||
import pwd
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, human_to_bytes
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
bhard=dict(type="str"),
|
||||
bsoft=dict(type="str"),
|
||||
ihard=dict(type="int"),
|
||||
isoft=dict(type="int"),
|
||||
mountpoint=dict(type="str", required=True),
|
||||
name=dict(type="str"),
|
||||
rtbhard=dict(type="str"),
|
||||
rtbsoft=dict(type="str"),
|
||||
state=dict(type="str", default="present", choices=["absent", "present"]),
|
||||
type=dict(type="str", required=True, choices=["group", "project", "user"]),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
quota_type = module.params["type"]
|
||||
name = module.params["name"]
|
||||
mountpoint = module.params["mountpoint"]
|
||||
bhard = module.params["bhard"]
|
||||
bsoft = module.params["bsoft"]
|
||||
ihard = module.params["ihard"]
|
||||
isoft = module.params["isoft"]
|
||||
rtbhard = module.params["rtbhard"]
|
||||
rtbsoft = module.params["rtbsoft"]
|
||||
state = module.params["state"]
|
||||
|
||||
xfs_quota_bin = module.get_bin_path("xfs_quota", True)
|
||||
|
||||
if bhard is not None:
|
||||
bhard = human_to_bytes(bhard)
|
||||
|
||||
if bsoft is not None:
|
||||
bsoft = human_to_bytes(bsoft)
|
||||
|
||||
if rtbhard is not None:
|
||||
rtbhard = human_to_bytes(rtbhard)
|
||||
|
||||
if rtbsoft is not None:
|
||||
rtbsoft = human_to_bytes(rtbsoft)
|
||||
|
||||
result = dict(
|
||||
changed=False,
|
||||
)
|
||||
|
||||
if not os.path.ismount(mountpoint):
|
||||
module.fail_json(msg="Path '%s' is not a mount point" % mountpoint, **result)
|
||||
|
||||
mp = get_fs_by_mountpoint(mountpoint)
|
||||
if mp is None:
|
||||
module.fail_json(
|
||||
msg="Path '%s' is not a mount point or not located on an xfs file system."
|
||||
% mountpoint,
|
||||
**result
|
||||
)
|
||||
|
||||
if quota_type == "user":
|
||||
type_arg = "-u"
|
||||
quota_default = "root"
|
||||
if name is None:
|
||||
name = quota_default
|
||||
|
||||
if (
|
||||
"uquota" not in mp["mntopts"]
|
||||
and "usrquota" not in mp["mntopts"]
|
||||
and "quota" not in mp["mntopts"]
|
||||
and "uqnoenforce" not in mp["mntopts"]
|
||||
and "qnoenforce" not in mp["mntopts"]
|
||||
):
|
||||
module.fail_json(
|
||||
msg="Path '%s' is not mounted with the uquota/usrquota/quota/uqnoenforce/qnoenforce option."
|
||||
% mountpoint,
|
||||
**result
|
||||
)
|
||||
try:
|
||||
pwd.getpwnam(name)
|
||||
except KeyError as e:
|
||||
module.fail_json(msg="User '%s' does not exist." % name, **result)
|
||||
|
||||
elif quota_type == "group":
|
||||
type_arg = "-g"
|
||||
quota_default = "root"
|
||||
if name is None:
|
||||
name = quota_default
|
||||
|
||||
if (
|
||||
"gquota" not in mp["mntopts"]
|
||||
and "grpquota" not in mp["mntopts"]
|
||||
and "gqnoenforce" not in mp["mntopts"]
|
||||
):
|
||||
module.fail_json(
|
||||
msg="Path '%s' is not mounted with the gquota/grpquota/gqnoenforce option. (current options: %s)"
|
||||
% (mountpoint, mp["mntopts"]),
|
||||
**result
|
||||
)
|
||||
try:
|
||||
grp.getgrnam(name)
|
||||
except KeyError as e:
|
||||
module.fail_json(msg="User '%s' does not exist." % name, **result)
|
||||
|
||||
elif quota_type == "project":
|
||||
type_arg = "-p"
|
||||
quota_default = "#0"
|
||||
if name is None:
|
||||
name = quota_default
|
||||
|
||||
if (
|
||||
"pquota" not in mp["mntopts"]
|
||||
and "prjquota" not in mp["mntopts"]
|
||||
and "pqnoenforce" not in mp["mntopts"]
|
||||
):
|
||||
module.fail_json(
|
||||
msg="Path '%s' is not mounted with the pquota/prjquota/pqnoenforce option."
|
||||
% mountpoint,
|
||||
**result
|
||||
)
|
||||
|
||||
if name != quota_default and not os.path.isfile("/etc/projects"):
|
||||
module.fail_json(msg="Path '/etc/projects' does not exist.", **result)
|
||||
|
||||
if name != quota_default and not os.path.isfile("/etc/projid"):
|
||||
module.fail_json(msg="Path '/etc/projid' does not exist.", **result)
|
||||
|
||||
if name != quota_default and name is not None and get_project_id(name) is None:
|
||||
module.fail_json(
|
||||
msg="Entry '%s' has not been defined in /etc/projid." % name, **result
|
||||
)
|
||||
|
||||
prj_set = True
|
||||
if name != quota_default:
|
||||
cmd = "project %s" % name
|
||||
rc, stdout, stderr = exec_quota(module, xfs_quota_bin, cmd, mountpoint)
|
||||
if rc != 0:
|
||||
result["cmd"] = cmd
|
||||
result["rc"] = rc
|
||||
result["stdout"] = stdout
|
||||
result["stderr"] = stderr
|
||||
module.fail_json(msg="Could not get project state.", **result)
|
||||
else:
|
||||
for line in stdout.split("\n"):
|
||||
if (
|
||||
"Project Id '%s' - is not set." in line
|
||||
or "project identifier is not set" in line
|
||||
):
|
||||
prj_set = False
|
||||
break
|
||||
|
||||
if not prj_set and not module.check_mode:
|
||||
cmd = "project -s"
|
||||
rc, stdout, stderr = exec_quota(module, xfs_quota_bin, cmd, mountpoint)
|
||||
if rc != 0:
|
||||
result["cmd"] = cmd
|
||||
result["rc"] = rc
|
||||
result["stdout"] = stdout
|
||||
result["stderr"] = stderr
|
||||
module.fail_json(
|
||||
msg="Could not get quota realtime block report.", **result
|
||||
)
|
||||
|
||||
result["changed"] = True
|
||||
|
||||
elif not prj_set and module.check_mode:
|
||||
result["changed"] = True
|
||||
|
||||
# Set limits
|
||||
if state == "absent":
|
||||
bhard = 0
|
||||
bsoft = 0
|
||||
ihard = 0
|
||||
isoft = 0
|
||||
rtbhard = 0
|
||||
rtbsoft = 0
|
||||
|
||||
current_bsoft, current_bhard = quota_report(
|
||||
module, xfs_quota_bin, mountpoint, name, quota_type, "b"
|
||||
)
|
||||
current_isoft, current_ihard = quota_report(
|
||||
module, xfs_quota_bin, mountpoint, name, quota_type, "i"
|
||||
)
|
||||
current_rtbsoft, current_rtbhard = quota_report(
|
||||
module, xfs_quota_bin, mountpoint, name, quota_type, "rtb"
|
||||
)
|
||||
|
||||
result["xfs_quota"] = dict(
|
||||
bsoft=current_bsoft,
|
||||
bhard=current_bhard,
|
||||
isoft=current_isoft,
|
||||
ihard=current_ihard,
|
||||
rtbsoft=current_rtbsoft,
|
||||
rtbhard=current_rtbhard,
|
||||
)
|
||||
|
||||
limit = []
|
||||
if bsoft is not None and int(bsoft) != current_bsoft:
|
||||
limit.append("bsoft=%s" % bsoft)
|
||||
result["bsoft"] = int(bsoft)
|
||||
|
||||
if bhard is not None and int(bhard) != current_bhard:
|
||||
limit.append("bhard=%s" % bhard)
|
||||
result["bhard"] = int(bhard)
|
||||
|
||||
if isoft is not None and isoft != current_isoft:
|
||||
limit.append("isoft=%s" % isoft)
|
||||
result["isoft"] = isoft
|
||||
|
||||
if ihard is not None and ihard != current_ihard:
|
||||
limit.append("ihard=%s" % ihard)
|
||||
result["ihard"] = ihard
|
||||
|
||||
if rtbsoft is not None and int(rtbsoft) != current_rtbsoft:
|
||||
limit.append("rtbsoft=%s" % rtbsoft)
|
||||
result["rtbsoft"] = int(rtbsoft)
|
||||
|
||||
if rtbhard is not None and int(rtbhard) != current_rtbhard:
|
||||
limit.append("rtbhard=%s" % rtbhard)
|
||||
result["rtbhard"] = int(rtbhard)
|
||||
|
||||
if len(limit) > 0 and not module.check_mode:
|
||||
if name == quota_default:
|
||||
cmd = "limit %s -d %s" % (type_arg, " ".join(limit))
|
||||
else:
|
||||
cmd = "limit %s %s %s" % (type_arg, " ".join(limit), name)
|
||||
|
||||
rc, stdout, stderr = exec_quota(module, xfs_quota_bin, cmd, mountpoint)
|
||||
if rc != 0:
|
||||
result["cmd"] = cmd
|
||||
result["rc"] = rc
|
||||
result["stdout"] = stdout
|
||||
result["stderr"] = stderr
|
||||
module.fail_json(msg="Could not set limits.", **result)
|
||||
|
||||
result["changed"] = True
|
||||
|
||||
elif len(limit) > 0 and module.check_mode:
|
||||
result["changed"] = True
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
def quota_report(module, xfs_quota_bin, mountpoint, name, quota_type, used_type):
|
||||
soft = None
|
||||
hard = None
|
||||
|
||||
if quota_type == "project":
|
||||
type_arg = "-p"
|
||||
elif quota_type == "user":
|
||||
type_arg = "-u"
|
||||
elif quota_type == "group":
|
||||
type_arg = "-g"
|
||||
|
||||
if used_type == "b":
|
||||
used_arg = "-b"
|
||||
used_name = "blocks"
|
||||
factor = 1024
|
||||
elif used_type == "i":
|
||||
used_arg = "-i"
|
||||
used_name = "inodes"
|
||||
factor = 1
|
||||
elif used_type == "rtb":
|
||||
used_arg = "-r"
|
||||
used_name = "realtime blocks"
|
||||
factor = 1024
|
||||
|
||||
rc, stdout, stderr = exec_quota(
|
||||
module, xfs_quota_bin, "report %s %s" % (type_arg, used_arg), mountpoint
|
||||
)
|
||||
|
||||
if rc != 0:
|
||||
result = dict(
|
||||
changed=False,
|
||||
rc=rc,
|
||||
stdout=stdout,
|
||||
stderr=stderr,
|
||||
)
|
||||
module.fail_json(msg="Could not get quota report for %s." % used_name, **result)
|
||||
|
||||
for line in stdout.split("\n"):
|
||||
line = line.strip().split()
|
||||
if len(line) > 3 and line[0] == name:
|
||||
soft = int(line[2]) * factor
|
||||
hard = int(line[3]) * factor
|
||||
break
|
||||
|
||||
return soft, hard
|
||||
|
||||
|
||||
def exec_quota(module, xfs_quota_bin, cmd, mountpoint):
|
||||
cmd = [xfs_quota_bin, "-x", "-c"] + [cmd, mountpoint]
|
||||
(rc, stdout, stderr) = module.run_command(cmd, use_unsafe_shell=True)
|
||||
if (
|
||||
"XFS_GETQUOTA: Operation not permitted" in stderr.split("\n")
|
||||
or rc == 1
|
||||
and "xfs_quota: cannot set limits: Operation not permitted"
|
||||
in stderr.split("\n")
|
||||
):
|
||||
module.fail_json(
|
||||
msg="You need to be root or have CAP_SYS_ADMIN capability to perform this operation"
|
||||
)
|
||||
|
||||
return rc, stdout, stderr
|
||||
|
||||
|
||||
def get_fs_by_mountpoint(mountpoint):
|
||||
mpr = None
|
||||
with open("/proc/mounts", "r") as s:
|
||||
for line in s.readlines():
|
||||
mp = line.strip().split()
|
||||
if len(mp) == 6 and mp[1] == mountpoint and mp[2] == "xfs":
|
||||
mpr = dict(
|
||||
zip(["spec", "file", "vfstype", "mntopts", "freq", "passno"], mp)
|
||||
)
|
||||
mpr["mntopts"] = mpr["mntopts"].split(",")
|
||||
break
|
||||
return mpr
|
||||
|
||||
|
||||
def get_project_id(name):
|
||||
prjid = None
|
||||
with open("/etc/projid", "r") as s:
|
||||
for line in s.readlines():
|
||||
line = line.strip().partition(":")
|
||||
if line[0] == name:
|
||||
prjid = line[2]
|
||||
break
|
||||
|
||||
return prjid
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Add table
Add a link
Reference in a new issue