mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2025-10-26 13:56:09 -07:00 
			
		
		
		
	* Replace ansible.module_utils._text by ansible.module_utils.common.text.converters. * Also adjust tests.
		
			
				
	
	
		
			347 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			347 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/python
 | |
| # -*- coding: utf-8 -*-
 | |
| 
 | |
| # Copyright: (c) 2014, Sebastien Rohaut <sebastien.rohaut@gmail.com>
 | |
| # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
 | |
| 
 | |
| from __future__ import absolute_import, division, print_function
 | |
| __metaclass__ = type
 | |
| 
 | |
| DOCUMENTATION = r'''
 | |
| ---
 | |
| module: pam_limits
 | |
| author:
 | |
| - "Sebastien Rohaut (@usawa)"
 | |
| short_description: Modify Linux PAM limits
 | |
| description:
 | |
|   - The C(pam_limits) module modifies PAM limits.
 | |
|   - The default file is C(/etc/security/limits.conf).
 | |
|   - For the full documentation, see C(man 5 limits.conf).
 | |
| options:
 | |
|   domain:
 | |
|     type: str
 | |
|     description:
 | |
|       - A username, @groupname, wildcard, UID/GID range.
 | |
|     required: true
 | |
|   limit_type:
 | |
|     type: str
 | |
|     description:
 | |
|       - Limit type, see C(man 5 limits.conf) for an explanation.
 | |
|     required: true
 | |
|     choices: [ "hard", "soft", "-" ]
 | |
|   limit_item:
 | |
|     type: str
 | |
|     description:
 | |
|       - The limit to be set.
 | |
|     required: true
 | |
|     choices:
 | |
|         - "core"
 | |
|         - "data"
 | |
|         - "fsize"
 | |
|         - "memlock"
 | |
|         - "nofile"
 | |
|         - "rss"
 | |
|         - "stack"
 | |
|         - "cpu"
 | |
|         - "nproc"
 | |
|         - "as"
 | |
|         - "maxlogins"
 | |
|         - "maxsyslogins"
 | |
|         - "priority"
 | |
|         - "locks"
 | |
|         - "sigpending"
 | |
|         - "msgqueue"
 | |
|         - "nice"
 | |
|         - "rtprio"
 | |
|         - "chroot"
 | |
|   value:
 | |
|     type: str
 | |
|     description:
 | |
|       - The value of the limit.
 | |
|       - Value must either be C(unlimited), C(infinity) or C(-1), all of which indicate no limit, or a limit of 0 or larger.
 | |
|       - Value must be a number in the range -20 to 19 inclusive, if I(limit_item) is set to C(nice) or C(priority).
 | |
|       - Refer to the C(man 5 limits.conf) manual pages for more details.
 | |
|     required: true
 | |
|   backup:
 | |
|     description:
 | |
|       - Create a backup file including the timestamp information so you can get
 | |
|         the original file back if you somehow clobbered it incorrectly.
 | |
|     required: false
 | |
|     type: bool
 | |
|     default: "no"
 | |
|   use_min:
 | |
|     description:
 | |
|       - If set to C(yes), the minimal value will be used or conserved.
 | |
|       - If the specified value is inferior to the value in the file,
 | |
|         file content is replaced with the new value, else content is not modified.
 | |
|     required: false
 | |
|     type: bool
 | |
|     default: "no"
 | |
|   use_max:
 | |
|     description:
 | |
|       - If set to C(yes), the maximal value will be used or conserved.
 | |
|       - If the specified value is superior to the value in the file,
 | |
|         file content is replaced with the new value, else content is not modified.
 | |
|     required: false
 | |
|     type: bool
 | |
|     default: "no"
 | |
|   dest:
 | |
|     type: str
 | |
|     description:
 | |
|       - Modify the limits.conf path.
 | |
|     required: false
 | |
|     default: "/etc/security/limits.conf"
 | |
|   comment:
 | |
|     type: str
 | |
|     description:
 | |
|       - Comment associated with the limit.
 | |
|     required: false
 | |
|     default: ''
 | |
| notes:
 | |
|   - If I(dest) file does not exist, it is created.
 | |
| '''
 | |
| 
 | |
| EXAMPLES = r'''
 | |
| - name: Add or modify nofile soft limit for the user joe
 | |
|   community.general.pam_limits:
 | |
|     domain: joe
 | |
|     limit_type: soft
 | |
|     limit_item: nofile
 | |
|     value: 64000
 | |
| 
 | |
| - name: Add or modify fsize hard limit for the user smith. Keep or set the maximal value
 | |
|   community.general.pam_limits:
 | |
|     domain: smith
 | |
|     limit_type: hard
 | |
|     limit_item: fsize
 | |
|     value: 1000000
 | |
|     use_max: yes
 | |
| 
 | |
| - name: Add or modify memlock, both soft and hard, limit for the user james with a comment
 | |
|   community.general.pam_limits:
 | |
|     domain: james
 | |
|     limit_type: '-'
 | |
|     limit_item: memlock
 | |
|     value: unlimited
 | |
|     comment: unlimited memory lock for james
 | |
| 
 | |
| - name: Add or modify hard nofile limits for wildcard domain
 | |
|   community.general.pam_limits:
 | |
|     domain: '*'
 | |
|     limit_type: hard
 | |
|     limit_item: nofile
 | |
|     value: 39693561
 | |
| '''
 | |
| 
 | |
| import os
 | |
| import re
 | |
| import tempfile
 | |
| 
 | |
| from ansible.module_utils.basic import AnsibleModule
 | |
| from ansible.module_utils.common.text.converters import to_native
 | |
| 
 | |
| 
 | |
| def _assert_is_valid_value(module, item, value, prefix=''):
 | |
|     if item in ['nice', 'priority']:
 | |
|         try:
 | |
|             valid = -20 <= int(value) <= 19
 | |
|         except ValueError:
 | |
|             valid = False
 | |
|         if not valid:
 | |
|             module.fail_json(msg="%s Value of %r for item %r is invalid. Value must be a number in the range -20 to 19 inclusive. "
 | |
|                                  "Refer to the limits.conf(5) manual pages for more details." % (prefix, value, item))
 | |
|     elif not (value in ['unlimited', 'infinity', '-1'] or value.isdigit()):
 | |
|         module.fail_json(msg="%s Value of %r for item %r is invalid. Value must either be 'unlimited', 'infinity' or -1, all of "
 | |
|                              "which indicate no limit, or a limit of 0 or larger. Refer to the limits.conf(5) manual pages for "
 | |
|                              "more details." % (prefix, value, item))
 | |
| 
 | |
| 
 | |
| def main():
 | |
|     pam_items = ['core', 'data', 'fsize', 'memlock', 'nofile', 'rss', 'stack', 'cpu', 'nproc', 'as', 'maxlogins', 'maxsyslogins', 'priority', 'locks',
 | |
|                  'sigpending', 'msgqueue', 'nice', 'rtprio', 'chroot']
 | |
| 
 | |
|     pam_types = ['soft', 'hard', '-']
 | |
| 
 | |
|     limits_conf = '/etc/security/limits.conf'
 | |
| 
 | |
|     module = AnsibleModule(
 | |
|         # not checking because of daisy chain to file module
 | |
|         argument_spec=dict(
 | |
|             domain=dict(required=True, type='str'),
 | |
|             limit_type=dict(required=True, type='str', choices=pam_types),
 | |
|             limit_item=dict(required=True, type='str', choices=pam_items),
 | |
|             value=dict(required=True, type='str'),
 | |
|             use_max=dict(default=False, type='bool'),
 | |
|             use_min=dict(default=False, type='bool'),
 | |
|             backup=dict(default=False, type='bool'),
 | |
|             dest=dict(default=limits_conf, type='str'),
 | |
|             comment=dict(required=False, default='', type='str')
 | |
|         ),
 | |
|         supports_check_mode=True,
 | |
|     )
 | |
| 
 | |
|     domain = module.params['domain']
 | |
|     limit_type = module.params['limit_type']
 | |
|     limit_item = module.params['limit_item']
 | |
|     value = module.params['value']
 | |
|     use_max = module.params['use_max']
 | |
|     use_min = module.params['use_min']
 | |
|     backup = module.params['backup']
 | |
|     limits_conf = module.params['dest']
 | |
|     new_comment = module.params['comment']
 | |
| 
 | |
|     changed = False
 | |
| 
 | |
|     if os.path.isfile(limits_conf):
 | |
|         if not os.access(limits_conf, os.W_OK):
 | |
|             module.fail_json(msg="%s is not writable. Use sudo" % limits_conf)
 | |
|     else:
 | |
|         limits_conf_dir = os.path.dirname(limits_conf)
 | |
|         if os.path.isdir(limits_conf_dir) and os.access(limits_conf_dir, os.W_OK):
 | |
|             open(limits_conf, 'a').close()
 | |
|             changed = True
 | |
|         else:
 | |
|             module.fail_json(msg="directory %s is not writable (check presence, access rights, use sudo)" % limits_conf_dir)
 | |
| 
 | |
|     if use_max and use_min:
 | |
|         module.fail_json(msg="Cannot use use_min and use_max at the same time.")
 | |
| 
 | |
|     _assert_is_valid_value(module, limit_item, value)
 | |
| 
 | |
|     # Backup
 | |
|     if backup:
 | |
|         backup_file = module.backup_local(limits_conf)
 | |
| 
 | |
|     space_pattern = re.compile(r'\s+')
 | |
| 
 | |
|     message = ''
 | |
|     f = open(limits_conf, 'rb')
 | |
|     # Tempfile
 | |
|     nf = tempfile.NamedTemporaryFile(mode='w+')
 | |
| 
 | |
|     found = False
 | |
|     new_value = value
 | |
| 
 | |
|     for line in f:
 | |
|         line = to_native(line, errors='surrogate_or_strict')
 | |
|         if line.startswith('#'):
 | |
|             nf.write(line)
 | |
|             continue
 | |
| 
 | |
|         newline = re.sub(space_pattern, ' ', line).strip()
 | |
|         if not newline:
 | |
|             nf.write(line)
 | |
|             continue
 | |
| 
 | |
|         # Remove comment in line
 | |
|         newline = newline.split('#', 1)[0]
 | |
|         try:
 | |
|             old_comment = line.split('#', 1)[1]
 | |
|         except Exception:
 | |
|             old_comment = ''
 | |
| 
 | |
|         newline = newline.rstrip()
 | |
| 
 | |
|         if not new_comment:
 | |
|             new_comment = old_comment
 | |
| 
 | |
|         line_fields = newline.split(' ')
 | |
| 
 | |
|         if len(line_fields) != 4:
 | |
|             nf.write(line)
 | |
|             continue
 | |
| 
 | |
|         line_domain = line_fields[0]
 | |
|         line_type = line_fields[1]
 | |
|         line_item = line_fields[2]
 | |
|         actual_value = line_fields[3]
 | |
| 
 | |
|         _assert_is_valid_value(module, line_item, actual_value,
 | |
|                                prefix="Invalid configuration found in '%s'." % limits_conf)
 | |
| 
 | |
|         # Found the line
 | |
|         if line_domain == domain and line_type == limit_type and line_item == limit_item:
 | |
|             found = True
 | |
|             if value == actual_value:
 | |
|                 message = line
 | |
|                 nf.write(line)
 | |
|                 continue
 | |
| 
 | |
|             if line_type not in ['nice', 'priority']:
 | |
|                 actual_value_unlimited = actual_value in ['unlimited', 'infinity', '-1']
 | |
|                 value_unlimited = value in ['unlimited', 'infinity', '-1']
 | |
|             else:
 | |
|                 actual_value_unlimited = value_unlimited = False
 | |
| 
 | |
|             if use_max:
 | |
|                 if actual_value_unlimited:
 | |
|                     new_value = actual_value
 | |
|                 elif value_unlimited:
 | |
|                     new_value = value
 | |
|                 else:
 | |
|                     new_value = str(max(int(value), int(actual_value)))
 | |
| 
 | |
|             if use_min:
 | |
|                 if actual_value_unlimited and value_unlimited:
 | |
|                     new_value = actual_value
 | |
|                 elif actual_value_unlimited:
 | |
|                     new_value = value
 | |
|                 elif value_unlimited:
 | |
|                     new_value = actual_value
 | |
|                 else:
 | |
|                     new_value = str(min(int(value), int(actual_value)))
 | |
| 
 | |
|             # Change line only if value has changed
 | |
|             if new_value != actual_value:
 | |
|                 changed = True
 | |
|                 if new_comment:
 | |
|                     new_comment = "\t#" + new_comment
 | |
|                 new_limit = domain + "\t" + limit_type + "\t" + limit_item + "\t" + new_value + new_comment + "\n"
 | |
|                 message = new_limit
 | |
|                 nf.write(new_limit)
 | |
|             else:
 | |
|                 message = line
 | |
|                 nf.write(line)
 | |
|         else:
 | |
|             nf.write(line)
 | |
| 
 | |
|     if not found:
 | |
|         changed = True
 | |
|         if new_comment:
 | |
|             new_comment = "\t#" + new_comment
 | |
|         new_limit = domain + "\t" + limit_type + "\t" + limit_item + "\t" + new_value + new_comment + "\n"
 | |
|         message = new_limit
 | |
|         nf.write(new_limit)
 | |
| 
 | |
|     f.close()
 | |
|     nf.flush()
 | |
| 
 | |
|     with open(limits_conf, 'r') as content:
 | |
|         content_current = content.read()
 | |
| 
 | |
|     with open(nf.name, 'r') as content:
 | |
|         content_new = content.read()
 | |
| 
 | |
|     if not module.check_mode:
 | |
|         # Copy tempfile to newfile
 | |
|         module.atomic_move(nf.name, limits_conf)
 | |
| 
 | |
|     try:
 | |
|         nf.close()
 | |
|     except Exception:
 | |
|         pass
 | |
| 
 | |
|     res_args = dict(
 | |
|         changed=changed,
 | |
|         msg=message,
 | |
|         diff=dict(before=content_current, after=content_new),
 | |
|     )
 | |
| 
 | |
|     if backup:
 | |
|         res_args['backup_file'] = backup_file
 | |
| 
 | |
|     module.exit_json(**res_args)
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main()
 |