ansible-collection-nextcloud/plugins/modules/config.py

263 lines
9.9 KiB
Python
Raw Normal View History

2023-02-07 09:04:38 +01:00
#!/usr/bin/python
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: config
short_description: Set Nextcloud configuration options.
# If this is part of a collection, you need to use semantic versioning,
# i.e. the version is of the form "2.5.0" and not "2.4".
version_added: "0.0.1"
description: Set Nextcloud configuration options via occ config.
options:
system:
description: System configuration options to set.
required: false
default: {}
type: dict
apps:
description: App configuration options to set.
required: false
default: {}
type: dict
force:
description: >-
Override change protection of options that should never be changed.
Use with EXTREME CARE, as IRRECOVERABLE DATA LOSS may be the result of changing these options.
Currently the following options are covered by this protection: instanceid, secret, passwordsalt.
required: false
default: false
type: bool
webroot:
description: Path to the Nextcloud webroot.
required: false
default: /var/www/html
type: str
php:
description: Path to the php-cli binary.
required: false
default: /usr/bin/php
type: str
# Specify this value according to your collection
# in format of namespace.collection.doc_fragment_name
#extends_documentation_fragment:
# - s3lph.nextcloud.status
author:
- s3lph
'''
EXAMPLES = r'''
- name: Set up Redis cache config all redis configuration all at once
s3lph.nextcloud.config:
system:
redis:
host: localhost
port: 6379
dbindex: 0
memcache.local: "\OC\Memcache\Redis"
memcache.remote: "\OC\Memcache\Redis"
memcache.locking: "\OC\Memcache\Redis"
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
import json
import math
import subprocess
# Changing these keys may lead to irrecoverable data loss
REQUIRE_FORCE = ['instanceid', 'secret', 'passwordsalt']
2023-02-07 22:52:23 +01:00
def iter_system(module, result, tree=None, value=None):
2023-02-07 09:04:38 +01:00
if tree is None:
tree = []
if value is None:
value = module.params['system']
# Recursively iterate the options tree
for k, v in value.items():
2023-02-07 21:55:36 +01:00
subtree = tree + [k]
2023-02-07 09:04:38 +01:00
if isinstance(v, dict):
iter_system(module, result, subtree, v)
2023-02-07 09:04:38 +01:00
continue
if isinstance(v, list):
v = {str(i): v for i, v in enumerate(v)}
iter_system(module, result, subtree, v)
2023-02-07 09:04:38 +01:00
continue
elif isinstance(v, int):
typ = 'integer'
elif isinstance(v, float):
typ = 'double'
elif isinstance(v, bool):
typ = 'boolean'
else:
typ = 'string'
# Get current value of the system option
rc = subprocess.run([module.params['php'], 'occ', 'config:system:get', '--output=json'] + subtree,
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if rc.returncode not in [0, 1]:
result['stdout'] = rc.stdout
result['stderr'] = rc.stderr
module.fail_json(msg='occ config:system:get returned non-zero exit code. Run with -vvv to view the output', **result)
if rc.returncode == 1:
old_value = None
else:
old_value = json.loads(rc.stdout)
if isinstance(v, float) and isinstance(old_value, float) and math.isclose(v, old_value):
continue
elif v == old_value:
continue
result['changed'] = True
stjoined = ' => '.join(subtree)
if old_value is not None:
result['diff'][0]['before'] += 'system => {} => {}\n'.format(app, stjoined, old_value)
if v is not None:
result['diff'][0]['after'] += 'system => {} => {}\n'.format(app, stjoined, v)
2023-02-07 09:04:38 +01:00
if not module.check_mode:
# Remove key if the new value is none
if v is None:
wc = subprocess.run([module.params['php'], 'occ', 'config:system:delete'] + subtree,
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if wc.returncode != 0:
result['stdout'] = wc.stdout
result['stderr'] = wc.stderr
msg = 'occ config:system:delete returned non-zero exit code. Run with -vvv to view the output'
module.fail_json(msg=msg, **result)
# Set option to new value
else:
cmdline = [module.params['php'], 'occ', 'config:system:set', '--type', typ, '--value', str(v)] + subtree
wc = subprocess.run(cmdline,
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if wc.returncode != 0:
result['stdout'] = wc.stdout
result['stderr'] = wc.stderr
msg = 'occ config:system:set returned non-zero exit code. Run with -vvv to view the output'
module.fail_json(msg=msg, **result)
def run_module():
# define available arguments/parameters a user can pass to the module
module_args = dict(
webroot=dict(required=False, default='/var/www/html', type='str'),
php=dict(required=False, default='/usr/bin/php', type='str'),
2023-02-07 21:40:03 +01:00
system=dict(required=False, default={}, type='dict'),
apps=dict(required=False, default={}, type='dict'),
2023-02-07 09:04:38 +01:00
force=dict(required=False, default=False, type='bool'),
)
# seed the result dict in the object
# we primarily care about changed and state
# changed is if this module effectively modified the target
# state will include any data that you want your module to pass back
# for consumption, for example, in a subsequent task
result = dict(
changed=False,
diff=[dict(before='', after='')],
2023-02-07 09:04:38 +01:00
)
2023-02-07 09:04:38 +01:00
# the AnsibleModule object will be our abstraction working with Ansible
# this includes instantiation, a couple of common attr would be the
# args/params passed to the execution, as well as if the module
# supports check mode
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True,
)
# Gather Nextcloud installation status
sc = subprocess.run([module.params['php'], 'occ', 'status', '--output=json'],
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if sc.returncode != 0:
result['stdout'] = sc.stdout
result['stderr'] = sc.stderr
module.fail_json(msg='occ status returned non-zero exit code. Run with -vvv to view the output', **result)
status = json.loads(sc.stdout)
if not status['installed']:
module.fail_json(msg='Nextcloud installation has not been completed, so occ config is not available.', **result)
# Check for protected options
for k in REQUIRE_FORCE:
if k in module.params['system'] and not module.params['force']:
msg = 'Refusing to change option "' + k + '" as IRRECOVERABLE DATA LOSS may be the result of such a change.'
module.fail_json(msg=msg, **result)
# Apply Nextcloud system configuration recursively
iter_system(module, result)
2023-02-07 09:04:38 +01:00
# Apply Nextcloud app configuration
for app, ac in module.params['apps'].items():
for k, v in ac.items():
# Get current value of the app option
rc = subprocess.run([module.params['php'], 'occ', 'config:app:get', app, k],
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if rc.returncode not in [0, 1]:
result['stdout'] = rc.stdout
result['stderr'] = rc.stderr
module.fail_json(msg='occ config:app:get returned non-zero exit code. Run with -vvv to view the output', **result)
if rc.returncode == 1:
old_value = None
else:
old_value = rc.stdout
if old_value == v:
continue
result['changed'] = True
if old_value is not None:
result['diff'][0]['before'] += 'apps => {} => {} => {}\n'.format(app, k, old_value)
if v is not None:
result['diff'][0]['after'] += 'apps => {} => {} => {}\n'.format(app, k, v)
2023-02-07 09:04:38 +01:00
if not module.check_mode:
# Delete key if value is None
if v is None:
rc = subprocess.run([module.params['php'], 'occ', 'config:app:delete', app, k],
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if rc.returncode != 0:
result['stdout'] = rc.stdout
result['stderr'] = rc.stderr
msg = 'occ config:app:delete returned non-zero exit code. Run with -vvv to view the output'
module.fail_json(msg=msg, **result)
else:
rc = subprocess.run([module.params['php'], 'occ', 'config:app:set', '--value', v, app, k],
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if rc.returncode != 0:
result['stdout'] = rc.stdout
result['stderr'] = rc.stderr
msg = 'occ config:app:set returned non-zero exit code. Run with -vvv to view the output'
module.fail_json(msg=msg, **result)
module.exit_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()