#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright: (c) 2018, Rafael Bodill # Copyright: (c) 2020, Borjan Tchakaloff # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = { 'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community' } DOCUMENTATION = ''' --- module: syncthing_folder short_description: Manage Syncthing folders version_added: "2.7" description: - "This is my longer description explaining my sample module" options: id: description: - This is the unique id of this new folder required: true label: description: - The label for this new folder required: false path: description: - This is the path of the folder required: false devices: description: - List of device ids to share folder with. Always share with self. required: false fs_watcher: description: - Whether to activate the file-system watcher. default: true ignore_perms: description: - Whether to ignore permissions when looking for changes. default: false type: description: - The folder type: sending local chances, and/or receiving remote changes. default: sendreceive choices: ['sendreceive', 'sendonly', 'receiveonly'] host: description: - Host to connect to, including port default: http://127.0.0.1:8384 api_key: description: - API key to use for authentication with host. If not provided, will try to auto-configure from filesystem. required: false config_file: description: - Path to the Syncthing configuration file for automatic discovery (`api_key`). Note that the running user needs read access to the file. required: false timeout: description: - The socket level timeout in seconds default: 30 state: description: - Use present/absent to ensure folder is shared, or not. default: present choices: ['absent', 'present', 'paused'] author: - Rafael Bodill (@rafi) ''' EXAMPLES = ''' # Add a folder to synchronize with another device - name: Create folder become: yes become_user: syncthing community.syncthing.folder: host: http://localhost unix_socket: /run/syncthing/syncthing.sock config_file: /var/lib/syncthing/.config/syncthing/config.xml id: 'my-folder-1' label: 'my folder 1' path: '/root/test-folder-1' devices: - '1234-1234-1234' - '5678-5678-5678' register: folder ''' RETURN = ''' response: description: The API response, in-case of an error. type: dict ''' from ansible_collections.community.syncthing.plugins.module_utils.syncthing_api import SyncthingModule # Returns an object of a new folder def create_folder(params, self_id, existing_device_ids): wanted_device_ids = {self_id} for device_id in params['devices']: if device_id in existing_device_ids: wanted_device_ids.add(device_id) # Keep the original ordering if collections are equivalent. # Again, for idempotency reasons. device_ids = sorted(wanted_device_ids) # Sort the device IDs to keep idem-potency devices = [ { 'deviceID': device_id, 'introducedBy': '', } for device_id in device_ids ] return { 'autoNormalize': True, 'copiers': 0, 'devices': devices, 'disableSparseFiles': False, 'disableTempIndexes': False, 'filesystemType': 'basic', 'fsWatcherDelayS': 10, 'fsWatcherEnabled': params['fs_watcher'], 'hashers': 0, 'id': params['id'], 'ignoreDelete': False, 'ignorePerms': params['ignore_perms'], 'label': params['label'] if params['label'] else params['id'], 'markerName': '.stfolder', 'maxConflicts': -1, 'minDiskFree': { 'unit': '%', 'value': 1 }, 'order': 'random', 'path': params['path'], 'paused': True if params['state'] == 'paused' else False, 'pullerMaxPendingKiB': 0, 'pullerPauseS': 0, 'rescanIntervalS': 3600, 'scanProgressIntervalS': 0, 'type': params['type'], 'useLargeBlocks': False, 'versioning': { 'params': {}, 'type': '' }, 'weakHashThresholdPct': 25 } def update_folder(folder, params, self_id, existing_device_ids): wanted_device_ids = {self_id} for device_id in params['devices']: if device_id in existing_device_ids: wanted_device_ids.add(device_id) current_device_ids = {d['deviceID'] for d in folder['devices']} device_ids = ( current_device_ids if current_device_ids == wanted_device_ids else sorted(wanted_device_ids) ) # Sort the device IDs to keep idem-potency devices = [ { 'deviceID': device_id, 'introducedBy': '', } for device_id in device_ids ] folder.update({ 'devices': devices, 'fsWatcherEnabled': params['fs_watcher'], 'ignorePerms': params['ignore_perms'], 'label': params['label'] if params['label'] else params['id'], 'path': params['path'], 'paused': True if params['state'] == 'paused' else False, 'type': params['type'], }) return folder def run_module(): # module arguments module_args = {} module_args.update(dict( id=dict(type='str', required=True), label=dict(type='str', required=False), path=dict(type='path', required=False), devices=dict(type='list', required=False, default=[]), fs_watcher=dict(type='bool', default=True), ignore_perms=dict(type='bool', required=False, default=False), type=dict(type='str', default='sendreceive', choices=['sendreceive', 'sendonly', 'receiveonly']), state=dict(type='str', default='present', choices=['absent', 'present', 'pause']), )) module = SyncthingModule( api_url='/rest/config/folders', argument_spec=module_args ) device_module = SyncthingModule( api_url='/rest/config/devices', argument_spec=module_args ) if module.params['state'] != 'absent' and not module.params['path']: module.fail_json(msg='You must provide a path when creating') folder_exists = False folder = module.get_call(target=module.params['id']) if 'id' in folder and folder['id'] == module.params['id']: folder_exists = True if module.params['state'] == 'absent': if folder_exists: if module.check_mode: module.result['changed'] = True else: module.delete_call(target=folder['id']) else: devices_list = device_module.get_call() existing_device_ids = [device['deviceID'] for device in devices_list] self_id = device_module.result['response']['x-syncthing-id'] if folder_exists: # exists but maybe needs changing if module.check_mode: module.result['changed'] = True else: folder_with_updated_data = update_folder( folder, module.params, self_id, existing_device_ids ) module.patch_call(data=folder_with_updated_data, target=folder['id']) else: # Doesn't exist but needs to be added if module.check_mode: module.result['changed'] = True else: folder_config_wanted = create_folder( module.params, self_id, existing_device_ids ) module.post_call(data=folder_config_wanted) module.exit_json() def main(): run_module() if __name__ == '__main__': main()