#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright: Nis Wechselberg # MIT License (see https://spdx.org/licenses/MIT.html) # Based upon code by s3lph # MIT License (see https://spdx.org/licenses/MIT.html) from __future__ import (absolute_import, division, print_function) DOCUMENTATION = r''' --- module: nextcloud_app short_description: Enable or disable Nextcloud apps. version_added: "1.0.0" description: Enable or disable Nextcloud apps via C(occ app:) in the running container. options: name: description: Name of the app or apps to install/remove/enable/disable. required: true type: list elements: str state: description: State the app or apps should be in. required: false default: enabled type: str choices: - enabled - disabled - absent force: description: Install and enable apps that are not compatible with the current Nextcloud version. required: false default: false type: bool engine: description: Engine to use for the OCI runtime. required: false default: podman type: str container_user: description: The user to run the occ command as while in the container. required: false default: www-data type: str container_name: description: The name of the container to run the command in. required: false default: nextcloud-app type: str author: - Nis Wechselberg ''' EXAMPLES = r''' - name: Install and enable photos app enbewe.nextcloud.nextcloud_app: name: photos - name: Install and enable PIM apps enbewe.nextcloud.nextcloud_app: name: - contacts - calendar - mail - name: Disable resource hungry dashboard enbewe.nextcloud.nextcloud_app: name: dashboard state: disabled ''' RETURN = r''' ''' import json import subprocess from ansible.module_utils.basic import AnsibleModule def check_nextcloud_status(module, result): ''' Gather nextcloud status through calling `occ status`. ''' sc = subprocess.run([module.params['engine'], 'exec', '--user', module.params['container_user'], module.params['container_name'], 'php', 'occ', 'status', '--output=json'], capture_output=True, encoding='utf-8', check=False) 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 app is not available.', **result) def get_app_status(module, result): ''' Gather state of installed apps in nextcloud. ''' # Gather Nextcloud app list ac = subprocess.run([module.params['engine'], 'exec', '--user', module.params['container_user'], module.params['container_name'], 'php', 'occ', 'app:list', '--output=json'], capture_output=True, encoding='utf-8', check=False) if ac.returncode != 0: result['stdout'] = ac.stdout result['stderr'] = ac.stderr module.fail_json( msg='occ app:list returned non-zero exit code. Run with -vvv to view the output', **result) return json.loads(ac.stdout) def remove_app(module, result, app): ''' Remove the given app from the installation. ''' result['changed'] = True if not module.check_mode: c = subprocess.run([module.params['engine'], 'exec', '--user', module.params['container_user'], module.params['container_name'], 'php', 'occ', 'app:remove', '--keep-data', app], capture_output=True, encoding='utf-8', check=False) if c.returncode != 0: result['stdout'] = c.stdout result['stderr'] = c.stderr module.fail_json( msg='occ app:remove returned non-zero exit code. Run with -vvv to view the output', **result) def install_app(module, result, app): ''' Install the given app to the installation. ''' state = module.params['state'] result['changed'] = True if not module.check_mode: cmdline = [module.params['engine'], 'exec', '--user', module.params['container_user'], module.params['container_name'], 'php', 'occ', 'app:install'] if state == 'disabled': cmdline.append('--keep-disabled') if module.params['force']: cmdline.append('--force') cmdline.append(app) c = subprocess.run(cmdline, capture_output=True, encoding='utf-8', check=False) if c.returncode != 0: result['stdout'] = c.stdout result['stderr'] = c.stderr module.fail_json( msg='occ app:install returned non-zero exit code. Run with -vvv to view the output', **result) def enable_app(module, result, app): ''' Enable the (already installed) app. ''' result['changed'] = True if not module.check_mode: cmdline = [module.params['engine'], 'exec', '--user', module.params['container_user'], module.params['container_name'], 'php', 'occ', 'app:enable'] if module.params['force']: cmdline.append('--force') cmdline.append(app) c = subprocess.run(cmdline, capture_output=True, encoding='utf-8', check=False) if c.returncode != 0: result['stdout'] = c.stdout result['stderr'] = c.stderr module.fail_json( msg='occ app:enable returned non-zero exit code. Run with -vvv to view the output', **result) def disable_app(module, result, app): ''' Disable the given app without removing it. ''' result['changed'] = True if not module.check_mode: c = subprocess.run([module.params['engine'], 'exec', '--user', module.params['container_user'], module.params['container_name'], 'php', 'occ', 'app:disable', app], capture_output=True, encoding='utf-8', check=False) if c.returncode != 0: result['stdout'] = c.stdout result['stderr'] = c.stderr module.fail_json( msg='occ app:disable returned non-zero exit code. Run with -vvv to view the output', **result) def apply_app_status(module, result, app_status): ''' Apply the status from the configuration to the applications in nextcloud. ''' state = module.params['state'] apps = module.params['name'] if isinstance(apps, str): apps = [apps] # Apply app configuration changes for app in apps: if state == 'absent' and (app in app_status['enabled'] or app in app_status['disabled']): remove_app(module, result, app) elif (state in ['enabled', 'disabled'] and app not in app_status['enabled'] and app not in app_status['disabled']): install_app(module, result, app) elif state == 'enabled' and app in app_status['disabled']: enable_app(module, result, app) elif state == 'disabled' and app in app_status['enabled']: disable_app(module, result, app) def run_module(): ''' Run the ansible module code. ''' # define available arguments/parameters a user can pass to the module module_args = { 'name': { 'required': True, 'type': 'list' }, 'state': { 'required': False, 'default': 'enabled', 'type': 'str' }, 'force': { 'required': False, 'default': False, 'type': 'bool' }, 'engine': { 'required': False, 'default': 'podman', 'type': 'str' }, 'container_user': { 'required': False, 'default': 'www-data', 'type': 'str' }, 'container_name': { 'required': False, 'default': 'nextcloud-app', 'type': 'str' }, } # 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 = { 'changed': False } # 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, ) if module.params['state'] not in ['enabled', 'disabled', 'absent']: module.fail_json(msg='state must be one of enabled, disabled or absent', **result) check_nextcloud_status(module, result) app_status = get_app_status(module, result) apply_app_status(module, result, app_status) module.exit_json(**result) def main(): ''' The main method called for the module. ''' run_module() if __name__ == '__main__': main()