├── tests ├── roles │ └── ansible-freenas ├── inventory └── test.yml ├── handlers └── main.yml ├── vars └── main.yml ├── meta └── main.yml ├── tasks ├── network.yml ├── main.yml ├── services.yml ├── system.yml ├── groups.yml ├── users.yml ├── sharing.yml ├── interfaces.yml └── storage.yml ├── README.md ├── templates ├── users_config.yaml.j2 ├── groups_config.yaml.j2 ├── sharing_config.yaml.j2 ├── interfaces_config.yaml.j2 └── storage_config.yaml.j2 └── defaults └── main.yml /tests/roles/ansible-freenas: -------------------------------------------------------------------------------- 1 | ../../ -------------------------------------------------------------------------------- /tests/inventory: -------------------------------------------------------------------------------- 1 | freenas ansible_host=127.0.0.1 2 | 3 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for ansible-freenas 3 | -------------------------------------------------------------------------------- /vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for ansible-freenas 3 | freenas_existing_users: [] 4 | -------------------------------------------------------------------------------- /tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | gather_facts: false 4 | roles: 5 | - ansible-freenas 6 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: Larry Smith Jr. 4 | description: An Ansible role to manage FreeNAS via API 5 | 6 | license: MIT 7 | 8 | min_ansible_version: 2.4 9 | 10 | galaxy_tags: [] 11 | 12 | dependencies: [] 13 | -------------------------------------------------------------------------------- /tasks/network.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: network | Updating Network Global Configuration 3 | uri: 4 | body: "{{ freenas_global_config }}" 5 | body_format: json 6 | force_basic_auth: true 7 | method: PUT 8 | password: "{{ freenas_password }}" 9 | return_content: true 10 | status_code: 200 11 | url: "{{ freenas_api_url + '/network/globalconfiguration/' }}" 12 | user: "{{ freenas_user }}" 13 | delegate_to: localhost 14 | tags: 15 | - freenas_network 16 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for ansible-freenas 3 | - include_tasks: system.yml 4 | tags: 5 | - freenas_system 6 | 7 | - include_tasks: network.yml 8 | tags: 9 | - freenas_network 10 | 11 | - include_tasks: interfaces.yml 12 | tags: 13 | - freenas_interfaces 14 | - freenas_network 15 | 16 | - include_tasks: storage.yml 17 | tags: 18 | - freenas_storage 19 | 20 | - include_tasks: groups.yml 21 | tags: 22 | - freenas_groups 23 | 24 | - include_tasks: users.yml 25 | tags: 26 | - freenas_users 27 | 28 | - include_tasks: services.yml 29 | tags: 30 | - freenas_services 31 | 32 | - include_tasks: sharing.yml 33 | tags: 34 | - freenas_sharing 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ansible-freenas 2 | 3 | An Ansible role to manage FreeNAS via API 4 | 5 | ## Testing 6 | 7 | I highly recommend testing prior to running against an existing environment. I 8 | am not responsible for any loss data! This role is still under extensive testing. 9 | To test a setup, I highly recommend using my [FreeNAS Vagrant box](https://github.com/mrlesmithjr/vagrant-box-templates) which is what is being used in developing this role. 10 | 11 | ## Requirements 12 | 13 | ## Role Variables 14 | 15 | [defaults/main.yml](defaults/main.yml) 16 | 17 | ## Dependencies 18 | 19 | ## Example Playbook 20 | 21 | [tests/test.yml](tests/test.yml) 22 | 23 | ## License 24 | 25 | MIT 26 | 27 | ## Author Information 28 | 29 | Larry Smith Jr. 30 | 31 | - [EverythingShouldBeVirtual](http://everythingshouldbevirtual.com) 32 | - [@mrlesmithjr](https://www.twitter.com/mrlesmithjr) 33 | - 34 | -------------------------------------------------------------------------------- /tasks/services.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: services | Capturing Services 3 | uri: 4 | force_basic_auth: true 5 | method: GET 6 | password: "{{ freenas_password }}" 7 | return_content: true 8 | status_code: 200 9 | url: "{{ freenas_api_url + '/services/services/' }}" 10 | user: "{{ freenas_user }}" 11 | register: freenas_services_discovered 12 | delegate_to: localhost 13 | tags: 14 | - freenas_services 15 | 16 | - name: services | Displaying Discovered Services 17 | debug: 18 | var: freenas_services_discovered['json'] 19 | tags: 20 | - freenas_services 21 | 22 | - name: services | Updating Existing Services 23 | uri: 24 | body: "{{ item['config'] }}" 25 | body_format: json 26 | force_basic_auth: true 27 | method: PUT 28 | password: "{{ freenas_password }}" 29 | return_content: true 30 | status_code: 200 31 | url: "{{ freenas_api_url + '/services/services/' + item['name'] + '/' }}" 32 | user: "{{ freenas_user }}" 33 | delegate_to: localhost 34 | with_items: "{{ freenas_services }}" 35 | tags: 36 | - freenas_services 37 | -------------------------------------------------------------------------------- /templates/users_config.yaml.j2: -------------------------------------------------------------------------------- 1 | {% set freenas_users_config = dict() %} 2 | {% set create_users = [] %} 3 | {% set update_users = [] %} 4 | {% set delete_users = [] %} 5 | {% set existing_users = dict() %} 6 | {% for user in freenas_users_found['json'] %} 7 | {% set _ = existing_users.update({user['bsdusr_username']: {'id': user['id']}}) %} 8 | {% endfor %} 9 | {% for user in freenas_users %} 10 | {% if user['name'] not in existing_users and user['state'] == 'present' %} 11 | {% set _ = create_users.append(user['config']) %} 12 | {% elif user['name'] in existing_users and user['state'] == 'present' %} 13 | {% set user_config = { 14 | 'id': existing_users[user['name']]['id'], 15 | 'config': user['config'] 16 | } 17 | %} 18 | {% set _ = update_users.append(user_config) %} 19 | {% elif user['name'] in existing_users and user['state'] == 'absent' %} 20 | {% set _ = delete_users.append(existing_users[user['name']]['id']) %} 21 | {% endif %} 22 | {% endfor %} 23 | {% set _ = freenas_users_config.update( 24 | { 25 | 'create_users': create_users, 26 | 'update_users': update_users, 27 | 'delete_users': delete_users 28 | }) 29 | %} 30 | {{ freenas_users_config }} -------------------------------------------------------------------------------- /templates/groups_config.yaml.j2: -------------------------------------------------------------------------------- 1 | {% set freenas_groups_config = dict() %} 2 | {% set existing_groups = dict() %} 3 | {% set create_groups = [] %} 4 | {% set update_groups = [] %} 5 | {% set delete_groups = [] %} 6 | {% for group in freenas_groups_found['json'] %} 7 | {% set _ = existing_groups.update({group['bsdgrp_group']: {'id': group['id'], 'bsdgrp_gid': group['bsdgrp_gid']}}) %} 8 | {% endfor %} 9 | {% for group in freenas_groups %} 10 | {% if group['name'] not in existing_groups and group['state'] == 'present' %} 11 | {% set _ = create_groups.append(group['config']) %} 12 | {% elif group['name'] in existing_groups and group['state'] == 'present' %} 13 | {% set group_config = { 14 | 'id': existing_groups[group['name']]['id'], 15 | 'config': group['config'] 16 | } 17 | %} 18 | {% set _ = update_groups.append(group_config) %} 19 | {% elif group['name'] in existing_groups and group['state'] == 'absent' %} 20 | {% set _ = delete_groups.append(existing_groups[group['name']]['id']) %} 21 | {% endif %} 22 | {% endfor %} 23 | {% set _ = freenas_groups_config.update( 24 | { 25 | 'create_groups': create_groups, 26 | 'update_groups': update_groups, 27 | 'delete_groups': delete_groups 28 | }) 29 | %} 30 | {{ freenas_groups_config }} -------------------------------------------------------------------------------- /tasks/system.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: system | Updating System Settings 3 | uri: 4 | body: "{{ item['config'] }}" 5 | body_format: json 6 | force_basic_auth: true 7 | method: PUT 8 | password: "{{ freenas_password }}" 9 | return_content: true 10 | status_code: 200 11 | url: "{{ freenas_api_url + '/system/' + item['name'] + '/' }}" 12 | user: "{{ freenas_user }}" 13 | delegate_to: localhost 14 | with_items: "{{ freenas_system }}" 15 | tags: 16 | - freenas_system 17 | 18 | - name: system | Checking For Pending Updates 19 | uri: 20 | force_basic_auth: true 21 | method: GET 22 | password: "{{ freenas_password }}" 23 | return_content: true 24 | status_code: 200 25 | url: "{{ freenas_api_url + '/system/update/check/' }}" 26 | user: "{{ freenas_user }}" 27 | register: freenas_pending_updates 28 | delegate_to: localhost 29 | tags: 30 | - freenas_system 31 | 32 | - name: system | Displaying Pending Updates 33 | debug: 34 | var: freenas_pending_updates['json'] 35 | tags: 36 | - freenas_system 37 | 38 | - name: system | Applying Pending Updates 39 | uri: 40 | force_basic_auth: true 41 | method: POST 42 | password: "{{ freenas_password }}" 43 | return_content: true 44 | status_code: 200 45 | url: "{{ freenas_api_url + '/system/update/check/' }}" 46 | user: "{{ freenas_user }}" 47 | delegate_to: localhost 48 | when: > 49 | freenas_apply_updates and 50 | freenas_pending_updates['json'] != [] 51 | tags: 52 | - freenas_system 53 | -------------------------------------------------------------------------------- /tasks/groups.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: groups | Capturing Existing Groups 3 | uri: 4 | force_basic_auth: true 5 | method: GET 6 | password: "{{ freenas_password }}" 7 | return_content: true 8 | url: "{{ freenas_api_url + '/account/groups/' }}" 9 | user: "{{ freenas_user }}" 10 | register: freenas_groups_found 11 | delegate_to: localhost 12 | tags: 13 | - freenas_groups 14 | 15 | - name: groups | Setting groups Config Fact 16 | set_fact: 17 | freenas_groups_config: "{{ lookup('template', 'groups_config.yaml.j2') }}" 18 | tags: 19 | - freenas_groups 20 | 21 | - name: groups | Displaying groups Config Fact 22 | debug: 23 | var: freenas_groups_config 24 | tags: 25 | - freenas_groups 26 | 27 | - name: groups | Creating Groups 28 | uri: 29 | body: "{{ item }}" 30 | body_format: json 31 | force_basic_auth: true 32 | method: POST 33 | password: "{{ freenas_password }}" 34 | return_content: true 35 | status_code: 201 36 | url: "{{ freenas_api_url + '/account/groups/' }}" 37 | user: "{{ freenas_user }}" 38 | delegate_to: localhost 39 | with_items: "{{ freenas_groups_config['create_groups'] }}" 40 | tags: 41 | - freenas_groups 42 | 43 | - name: groups | Updating Groups 44 | uri: 45 | body: "{{ item['config'] }}" 46 | body_format: json 47 | force_basic_auth: true 48 | method: PUT 49 | password: "{{ freenas_password }}" 50 | return_content: true 51 | status_code: 200 52 | url: "{{ freenas_api_url + '/account/groups/' + item['id']|string + '/' }}" 53 | user: "{{ freenas_user }}" 54 | delegate_to: localhost 55 | with_items: "{{ freenas_groups_config['update_groups'] }}" 56 | tags: 57 | - freenas_groups 58 | 59 | - name: groups | Deleting Groups 60 | uri: 61 | force_basic_auth: true 62 | method: DELETE 63 | password: "{{ freenas_password }}" 64 | return_content: true 65 | status_code: 204 66 | url: "{{ freenas_api_url + '/account/groups/' + item|string + '/' }}" 67 | user: "{{ freenas_user }}" 68 | delegate_to: localhost 69 | with_items: "{{ freenas_groups_config['delete_groups'] }}" 70 | tags: 71 | - freenas_groups 72 | -------------------------------------------------------------------------------- /tasks/users.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: users | Capturing Existing Users 3 | uri: 4 | force_basic_auth: true 5 | method: GET 6 | password: "{{ freenas_password }}" 7 | return_content: true 8 | url: "{{ freenas_api_url + '/account/users/' }}" 9 | user: "{{ freenas_user }}" 10 | register: freenas_users_found 11 | delegate_to: localhost 12 | tags: 13 | - freenas_users 14 | 15 | - name: users | Setting Users Config Fact 16 | set_fact: 17 | freenas_users_config: "{{ lookup('template', 'users_config.yaml.j2') }}" 18 | tags: 19 | - freenas_users 20 | 21 | - name: users | Creating Users 22 | uri: 23 | body: "{{ item }}" 24 | body_format: json 25 | force_basic_auth: true 26 | method: POST 27 | password: "{{ freenas_password }}" 28 | return_content: true 29 | status_code: 201 30 | url: "{{ freenas_api_url + '/account/users/' }}" 31 | user: "{{ freenas_user }}" 32 | delegate_to: localhost 33 | no_log: true 34 | with_items: "{{ freenas_users_config['create_users'] }}" 35 | tags: 36 | - freenas_users 37 | 38 | - name: users | Updating Users 39 | uri: 40 | body: "{{ item['config'] }}" 41 | body_format: json 42 | force_basic_auth: true 43 | method: PUT 44 | password: "{{ freenas_password }}" 45 | return_content: true 46 | status_code: 200 47 | url: "{{ freenas_api_url + '/account/users/' + item['id']|string + '/' }}" 48 | user: "{{ freenas_user }}" 49 | delegate_to: localhost 50 | no_log: true 51 | with_items: "{{ freenas_users_config['update_users'] }}" 52 | tags: 53 | - freenas_users 54 | 55 | - name: users | Updating Users Passwords 56 | uri: 57 | body: 58 | bsdusr_password: "{{ item['config']['bsdusr_password'] }}" 59 | body_format: json 60 | force_basic_auth: true 61 | method: POST 62 | password: "{{ freenas_password }}" 63 | return_content: true 64 | status_code: 200 65 | url: "{{ freenas_api_url + '/account/users/' + item['id']|string + '/password/' }}" 66 | user: "{{ freenas_user }}" 67 | delegate_to: localhost 68 | no_log: true 69 | with_items: "{{ freenas_users_config['update_users'] }}" 70 | tags: 71 | - freenas_users 72 | 73 | - name: users | Deleting Users 74 | uri: 75 | force_basic_auth: true 76 | method: DELETE 77 | password: "{{ freenas_password }}" 78 | return_content: true 79 | status_code: 204 80 | url: "{{ freenas_api_url + '/account/users/' + item|string + '/' }}" 81 | user: "{{ freenas_user }}" 82 | delegate_to: localhost 83 | with_items: "{{ freenas_users_config['delete_users'] }}" 84 | tags: 85 | - freenas_users 86 | -------------------------------------------------------------------------------- /templates/sharing_config.yaml.j2: -------------------------------------------------------------------------------- 1 | {% set freenas_sharing_config = dict() %} 2 | {% set _ = freenas_sharing_config.update({'cifs': dict()}) %} 3 | {% set _ = freenas_sharing_config.update({'nfs': dict()}) %} 4 | {% for share in freenas_cifs['json'] %} 5 | {% set _ = freenas_sharing_config['cifs'].update( 6 | { 7 | share['cifs_name']: { 8 | 'cifs_path': share['cifs_path'], 9 | 'id': share['id'] 10 | } 11 | }) 12 | %} 13 | {% endfor %} 14 | {% set create_nfs_shares = [] %} 15 | {% set update_nfs_shares = [] %} 16 | {% set delete_nfs_shares = [] %} 17 | {% set existing_nfs_paths = [] %} 18 | {% for nfs_share in freenas_nfs['json'] %} 19 | {% for path in nfs_share['nfs_paths'] %} 20 | {% if path not in existing_nfs_paths %} 21 | {% set _ = existing_nfs_paths.append(path) %} 22 | {% endif %} 23 | {% endfor %} 24 | {% endfor %} 25 | {% for share in freenas_sharing %} 26 | {% if share['type']|lower == 'nfs' and share['state'] == 'present' %} 27 | {% set create_nfs_paths = [] %} 28 | {% set update_nfs_paths = [] %} 29 | {% for path in share['config']['nfs_paths'] %} 30 | {% if path not in existing_nfs_paths %} 31 | {% set _ = create_nfs_paths.append(path) %} 32 | {% else %} 33 | {% set _ = update_nfs_paths.append(path) %} 34 | {% endif %} 35 | {% endfor %} 36 | {% if create_nfs_paths != [] %} 37 | {% set nfs_share_config = share['config'] %} 38 | {% set _ = create_nfs_shares.append(nfs_share_config) %} 39 | {% endif %} 40 | {% if update_nfs_paths != [] %} 41 | {% set nfs_share_config = {'config': share['config']} %} 42 | {% for nfs_share in freenas_nfs['json'] %} 43 | {% for path in nfs_share['nfs_paths'] %} 44 | {% if path in update_nfs_paths %} 45 | {% set _ = nfs_share_config.update({'id': nfs_share['id']}) %} 46 | {% endif %} 47 | {% endfor %} 48 | {% endfor %} 49 | {% set _ = update_nfs_shares.append(nfs_share_config) %} 50 | {% endif %} 51 | {% elif share['type']|lower == 'nfs' and share['state'] == 'absent' %} 52 | {% for path in share['config']['nfs_paths'] %} 53 | {% if path in existing_nfs_paths %} 54 | {% for nfs_share in freenas_nfs['json'] %} 55 | {% for delete_path in nfs_share['nfs_paths'] %} 56 | {% if delete_path == path %} 57 | {% if nfs_share['id'] not in delete_nfs_shares %} 58 | {% set _ = delete_nfs_shares.append(nfs_share['id']) %} 59 | {% endif %} 60 | {% endif %} 61 | {% endfor %} 62 | {% endfor %} 63 | {% endif %} 64 | {% endfor %} 65 | {% endif %} 66 | {% endfor %} 67 | {% set _ = freenas_sharing_config['nfs'].update( 68 | { 69 | 'create_nfs_shares': create_nfs_shares, 70 | 'update_nfs_shares': update_nfs_shares, 71 | 'delete_nfs_shares': delete_nfs_shares 72 | }) 73 | %} 74 | {{ freenas_sharing_config }} -------------------------------------------------------------------------------- /templates/interfaces_config.yaml.j2: -------------------------------------------------------------------------------- 1 | {% set freenas_interfaces_config = dict() %} 2 | {% set create_interfaces = [] %} 3 | {% set update_interfaces = [] %} 4 | {% set delete_interfaces = [] %} 5 | {% set create_vlan_interfaces = [] %} 6 | {% set update_vlan_interfaces = [] %} 7 | {% set delete_vlan_interfaces = [] %} 8 | {% set create_lagg_interfaces = [] %} 9 | {% set update_lagg_interfaces = [] %} 10 | {% set delete_lagg_interfaces = [] %} 11 | {% set existing_interfaces = dict() %} 12 | {% set existing_vlan_interfaces = dict() %} 13 | {% set existing_lagg_interfaces = dict() %} 14 | {% for int in captured_network_interfaces['json'] %} 15 | {% set _ = existing_interfaces.update({int['int_interface']: {'id': int['id']}}) %} 16 | {% endfor %} 17 | {% for int in captured_vlan_interfaces['json'] %} 18 | {% set _ = existing_vlan_interfaces.update({int['vlan_vint']: {'id': int['id']}}) %} 19 | {% endfor %} 20 | {% for int in captured_lagg_interfaces['json'] %} 21 | {% set _ = existing_lagg_interfaces.update({int['lagg_interface']: {'id': int['id']}}) %} 22 | {% endfor %} 23 | {% for int in freenas_interfaces %} 24 | {% if int['int'] in existing_interfaces %} 25 | {% if int['state'] == 'present' %} 26 | {% set int_config = {'id': existing_interfaces[int['int']]['id'], 'config': int['config']} %} 27 | {% set _ = update_interfaces.append(int_config) %} 28 | {% else %} 29 | {% set _ = delete_interfaces.append(existing_interfaces[int['int']]['id']) %} 30 | {% endif %} 31 | {% elif int['int'] not in existing_interfaces and int['state'] == 'present' %} 32 | {% if int['config'] != {} %} 33 | {% set int_config = {'config': int['config']} %} 34 | {% else %} 35 | {% set int_config = {'config': {'int_interface': int['int'], 'int_name': int['int']}} %} 36 | {% endif %} 37 | {% for vlan in freenas_vlan_interfaces %} 38 | {% if vlan['config']['vlan_vint'] == int['int'] %} 39 | {% set _ = int_config.update({'vlan_interface': true}) %} 40 | {% endif %} 41 | {% endfor %} 42 | {% if int_config['vlan_interface'] is not defined %} 43 | {% set _ = int_config.update({'vlan_interface': false}) %} 44 | {% endif %} 45 | {% for lagg in freenas_lagg_interfaces %} 46 | {% if lagg['config']['lagg_interface'] == int['int'] or int['int'] in lagg['config']['lagg_interfaces'] %} 47 | {% set _ = int_config.update({'lagg_interface': true}) %} 48 | {% endif %} 49 | {% endfor %} 50 | {% if int_config['lagg_interface'] is not defined %} 51 | {% set _ = int_config.update({'lagg_interface': false}) %} 52 | {% endif %} 53 | {% set _ = create_interfaces.append(int_config) %} 54 | {% endif %} 55 | {% endfor %} 56 | {% for vlan in freenas_vlan_interfaces %} 57 | {% if vlan['name'] in existing_vlan_interfaces %} 58 | {% if vlan['state'] == 'present' %} 59 | {% set vlan_int_config = {'id': existing_vlan_interfaces[vlan['name']]['id'], 'config': vlan['config']} %} 60 | {% set _ = update_vlan_interfaces.append(vlan_int_config) %} 61 | {% else %} 62 | {% set _ = delete_vlan_interfaces.append(existing_vlan_interfaces[vlan['name']]['id']) %} 63 | {% endif %} 64 | {% elif vlan['name'] not in existing_vlan_interfaces and vlan['state'] == 'present' %} 65 | {% set _ = create_vlan_interfaces.append(vlan['config']) %} 66 | {% endif %} 67 | {% endfor %} 68 | {% for lagg in freenas_lagg_interfaces %} 69 | {% if lagg['name'] in existing_lagg_interfaces %} 70 | {% if lagg['state'] == 'present' %} 71 | {% set lagg_int_config = {'id': existing_lagg_interfaces[lagg['name']]['id'], 'config': lagg['config']} %} 72 | {% set _ = update_lagg_interfaces.append(lagg_int_config) %} 73 | {% else %} 74 | {% set _ = delete_lagg_interfaces.append(existing_lagg_interfaces[lagg['name']]['id']) %} 75 | {% endif %} 76 | {% elif lagg['name'] not in existing_lagg_interfaces and lagg['state'] == 'present' %} 77 | {% set _ = create_lagg_interfaces.append(lagg['config']) %} 78 | {% endif %} 79 | {% endfor %} 80 | {% set _ = freenas_interfaces_config.update( 81 | { 82 | 'create_interfaces': create_interfaces, 83 | 'update_interfaces': update_interfaces, 84 | 'delete_interfaces': delete_interfaces, 85 | 'create_vlan_interfaces': create_vlan_interfaces, 86 | 'update_vlan_interfaces': update_vlan_interfaces, 87 | 'delete_vlan_interfaces': delete_vlan_interfaces, 88 | 'create_lagg_interfaces': create_lagg_interfaces, 89 | 'update_lagg_interfaces': update_lagg_interfaces, 90 | 'delete_lagg_interfaces': delete_lagg_interfaces 91 | }) 92 | %} 93 | {{ freenas_interfaces_config }} -------------------------------------------------------------------------------- /tasks/sharing.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: sharing | Capturing CIFS Sharing 3 | uri: 4 | force_basic_auth: true 5 | method: GET 6 | password: "{{ freenas_password }}" 7 | return_content: true 8 | url: "{{ freenas_api_url + '/sharing/cifs/' }}" 9 | user: "{{ freenas_user }}" 10 | register: freenas_cifs 11 | delegate_to: localhost 12 | tags: 13 | - freenas_sharing 14 | 15 | - name: sharing | Capturing NFS Sharing 16 | uri: 17 | force_basic_auth: true 18 | method: GET 19 | password: "{{ freenas_password }}" 20 | return_content: true 21 | url: "{{ freenas_api_url + '/sharing/nfs/' }}" 22 | user: "{{ freenas_user }}" 23 | register: freenas_nfs 24 | delegate_to: localhost 25 | tags: 26 | - freenas_sharing 27 | 28 | # Currently does not capture anything 29 | - name: sharing | Capturing WebDAV Sharing 30 | uri: 31 | force_basic_auth: true 32 | method: GET 33 | password: "{{ freenas_password }}" 34 | return_content: true 35 | url: "{{ freenas_api_url + '/sharing/webdav/' }}" 36 | user: "{{ freenas_user }}" 37 | register: freenas_webdav 38 | delegate_to: localhost 39 | tags: 40 | - freenas_sharing 41 | 42 | - name: sharing | Displaying WebDAV Sharing 43 | debug: 44 | var: freenas_webdav['json'] 45 | tags: 46 | - freenas_sharing 47 | 48 | - name: sharing | Setting Fact For Sharing Config 49 | set_fact: 50 | freenas_sharing_config: "{{ lookup('template', 'sharing_config.yaml.j2') }}" 51 | tags: 52 | - freenas_sharing 53 | 54 | - name: sharing | Displaying Sharing Config Fact 55 | debug: 56 | var: freenas_sharing_config 57 | 58 | - name: Sharing | Creating Shares (Non-NFS) 59 | uri: 60 | body: "{{ item['config'] }}" 61 | body_format: json 62 | force_basic_auth: true 63 | method: POST 64 | password: "{{ freenas_password }}" 65 | return_content: true 66 | status_code: 201 67 | url: "{{ freenas_api_url + '/sharing/' + item['type'] + '/' }}" 68 | user: "{{ freenas_user }}" 69 | delegate_to: localhost 70 | with_items: "{{ freenas_sharing }}" 71 | when: > 72 | item['name'] is defined and 73 | item['type'] != "nfs" and 74 | item['state'] == "present" and 75 | freenas_sharing_config[item['type']][item['name']] is not defined 76 | tags: 77 | - freenas_sharing 78 | 79 | - name: Sharing | Updating Shares (Non-NFS) 80 | uri: 81 | body: "{{ item['config'] }}" 82 | body_format: json 83 | force_basic_auth: true 84 | method: PUT 85 | password: "{{ freenas_password }}" 86 | return_content: true 87 | status_code: 200 88 | url: "{{ freenas_api_url + '/sharing/' + item['type'] + '/' + freenas_sharing_config[item['type']][item['name']]['id']|string + '/' }}" 89 | user: "{{ freenas_user }}" 90 | delegate_to: localhost 91 | with_items: "{{ freenas_sharing }}" 92 | when: > 93 | item['name'] is defined and 94 | item['type'] != "nfs" and 95 | item['state'] == "present" and 96 | freenas_sharing_config[item['type']][item['name']] is defined 97 | tags: 98 | - freenas_sharing 99 | 100 | - name: Sharing | Deleting Shares (Non-NFS) 101 | uri: 102 | force_basic_auth: true 103 | method: DELETE 104 | password: "{{ freenas_password }}" 105 | return_content: true 106 | status_code: 204 107 | url: "{{ freenas_api_url + '/sharing/' + item['type'] + '/' + freenas_sharing_config[item['type']][item['name']]['id']|string + '/' }}" 108 | user: "{{ freenas_user }}" 109 | delegate_to: localhost 110 | with_items: "{{ freenas_sharing }}" 111 | when: > 112 | item['name'] is defined and 113 | item['type'] != "nfs" and 114 | item['state'] == "absent" and 115 | freenas_sharing_config[item['type']][item['name']] is defined 116 | tags: 117 | - freenas_sharing 118 | 119 | - name: Sharing | Creating Shares (NFS) 120 | uri: 121 | body: "{{ item }}" 122 | body_format: json 123 | force_basic_auth: true 124 | method: POST 125 | password: "{{ freenas_password }}" 126 | return_content: true 127 | status_code: 201 128 | url: "{{ freenas_api_url + '/sharing/nfs/' }}" 129 | user: "{{ freenas_user }}" 130 | delegate_to: localhost 131 | with_items: "{{ freenas_sharing_config['nfs']['create_nfs_shares'] }}" 132 | tags: 133 | - freenas_sharing 134 | 135 | - name: Sharing | Updating Shares (NFS) 136 | uri: 137 | body: "{{ item['config'] }}" 138 | body_format: json 139 | force_basic_auth: true 140 | method: PUT 141 | password: "{{ freenas_password }}" 142 | return_content: true 143 | status_code: 200 144 | url: "{{ freenas_api_url + '/sharing/nfs/' + item['id']|string + '/' }}" 145 | user: "{{ freenas_user }}" 146 | delegate_to: localhost 147 | with_items: "{{ freenas_sharing_config['nfs']['update_nfs_shares'] }}" 148 | tags: 149 | - freenas_sharing 150 | 151 | - name: Sharing | Deleting Shares (NFS) 152 | uri: 153 | force_basic_auth: true 154 | method: DELETE 155 | password: "{{ freenas_password }}" 156 | return_content: true 157 | status_code: 204 158 | url: "{{ freenas_api_url + '/sharing/nfs/' + item|string + '/' }}" 159 | user: "{{ freenas_user }}" 160 | delegate_to: localhost 161 | with_items: "{{ freenas_sharing_config['nfs']['delete_nfs_shares'] }}" 162 | tags: 163 | - freenas_sharing 164 | -------------------------------------------------------------------------------- /templates/storage_config.yaml.j2: -------------------------------------------------------------------------------- 1 | {% set freenas_storage_config = dict() %} 2 | {% set disks_found = dict() %} 3 | {% set update_disks = [] %} 4 | {% set existing_volumes = dict() %} 5 | {% set existing_datasets = {} %} 6 | {% set existing_zvols = [] %} 7 | {% set create_volumes = [] %} 8 | {% set delete_volumes = [] %} 9 | {% set create_datasets = [] %} 10 | {% set update_datasets = [] %} 11 | {% set delete_datasets = [] %} 12 | {% set create_zvols = [] %} 13 | {% set update_zvols = [] %} 14 | {% set delete_zvols = [] %} 15 | {% set permissions = [] %} 16 | {% for disk in freenas_disks_found['json'] %} 17 | {% set _ = disks_found.update({disk['disk_name']: disk}) %} 18 | {% endfor %} 19 | {% for disk in freenas_disks %} 20 | {% if disk['name'] in disks_found %} 21 | {% set disk_config = { 22 | 'id': disks_found[disk['name']]['disk_identifier'], 23 | 'config': disk['config'] 24 | } 25 | %} 26 | {% set _ = update_disks.append(disk_config) %} 27 | {% endif %} 28 | {% endfor %} 29 | {% for volume in freenas_volumes['json'] %} 30 | {% set _ = existing_volumes.update({volume['name']: {'id': volume['id']}}) %} 31 | {% endfor %} 32 | {% for dataset in freenas_datasets['json'] %} 33 | {% set _ = existing_datasets.update({dataset['name']: {'info': dataset}}) %} 34 | {% endfor %} 35 | {% for result in freenas_zvols['results'] %} 36 | {% for zvol in result['json'] %} 37 | {% set _ = existing_zvols.append(result['item']['name'] + '/' + zvol['name']) %} 38 | {% endfor %} 39 | {% endfor %} 40 | {% for storage in freenas_storage %} 41 | {% if storage['name'] not in existing_volumes and storage['state'] == 'present' %} 42 | {% set _ = create_volumes.append(storage['volume_config']) %} 43 | {% elif storage['name'] in existing_volumes and storage['state'] == 'absent' %} 44 | {% set volume_info = { 45 | 'id': existing_volumes[storage['name']]['id'], 46 | 'config': { 47 | 'destroy': true, 48 | 'cascade': true 49 | } 50 | } 51 | %} 52 | {% set _ = delete_volumes.append(volume_info) %} 53 | {% endif %} 54 | {% if storage['state'] == 'present' %} 55 | {% if storage['datasets'] is defined %} 56 | {% for dataset in storage['datasets'] %} 57 | {% set pool = storage['name'] %} 58 | {% set actual_dataset_split = dataset['name'].split('/') %} 59 | {% if actual_dataset_split|length > 1 %} 60 | {% set actual_dataset_path = pool + '/' + actual_dataset_split[:-1]|join('/') %} 61 | {% set actual_dataset = dataset['name'].split('/')[-1] %} 62 | {% else %} 63 | {% set actual_dataset_path = pool %} 64 | {% set actual_dataset = dataset['name'] %} 65 | {% endif %} 66 | {% if (actual_dataset_path + '/' + actual_dataset) not in existing_datasets and dataset['state'] == 'present' %} 67 | {% set dataset_config = {'path': actual_dataset_path, 'dataset_name': actual_dataset, 'config': dataset['dataset_config']} %} 68 | {% if dataset['dataset_config']['name'] != actual_dataset %} 69 | {% set _ = dataset['dataset_config'].update({'name': actual_dataset}) %} 70 | {% endif %} 71 | {% set _ = create_datasets.append(dataset_config) %} 72 | {% elif (actual_dataset_path + '/' + actual_dataset) in existing_datasets and dataset['state'] == 'present' %} 73 | {% set dataset_config = {'path': actual_dataset_path, 'dataset_name': actual_dataset, 'config': dataset['dataset_config']} %} 74 | {% if dataset['dataset_config']['name'] != actual_dataset %} 75 | {% set _ = dataset['dataset_config'].update({'name': actual_dataset}) %} 76 | {% endif %} 77 | {% set _ = update_datasets.append(dataset_config) %} 78 | {% elif (actual_dataset_path + '/' + actual_dataset) in existing_datasets and dataset['state'] == 'absent' %} 79 | {% set dataset_config = {'path': actual_dataset_path, 'dataset_name': actual_dataset} %} 80 | {% if dataset['dataset_config']['name'] != actual_dataset %} 81 | {% set _ = dataset['dataset_config'].update({'name': actual_dataset}) %} 82 | {% endif %} 83 | {% set _ = delete_datasets.append(dataset_config) %} 84 | {% endif %} 85 | {% endfor %} 86 | {% endif %} 87 | {% if storage['zvols'] is defined %} 88 | {% for zvol in storage['zvols'] %} 89 | {% if (storage['name'] + '/' + zvol['name']) not in existing_zvols and zvol['state'] == 'present' %} 90 | {% set zvol_config = {'pool': storage['name'], 'config': zvol['zvol_config']} %} 91 | {% set _ = create_zvols.append(zvol_config) %} 92 | {% elif (storage['name'] + '/' + zvol['name']) in existing_zvols and zvol['state'] == 'present' %} 93 | {% set zvol_config = {'pool': storage['name'], 'name': zvol['name'], 'config': zvol['zvol_config']} %} 94 | {% set _ = update_zvols.append(zvol_config) %} 95 | {% elif (storage['name'] + '/' + zvol['name']) in existing_zvols and zvol['state'] == 'absent' %} 96 | {% set zvol_config = {'pool': storage['name'], 'name': zvol['name']} %} 97 | {% set _ = delete_zvols.append(zvol_config) %} 98 | {% endif %} 99 | {% endfor %} 100 | {% endif %} 101 | {% if storage['permissions'] is defined %} 102 | {% for permission in storage['permissions'] %} 103 | {% set _ = permissions.append(permission) %} 104 | {% endfor %} 105 | {% endif %} 106 | {% endif %} 107 | {% endfor %} 108 | {% set _ = freenas_storage_config.update( 109 | { 110 | 'existing_zvols': existing_zvols, 111 | 'create_volumes': create_volumes, 112 | 'delete_volumes': delete_volumes, 113 | 'create_datasets': create_datasets, 114 | 'update_datasets': update_datasets, 115 | 'delete_datasets': delete_datasets, 116 | 'create_zvols': create_zvols, 117 | 'update_zvols': update_zvols, 118 | 'delete_zvols': delete_zvols, 119 | 'permissions': permissions, 120 | 'update_disks': update_disks 121 | }) 122 | %} 123 | {{ freenas_storage_config }} 124 | -------------------------------------------------------------------------------- /tasks/interfaces.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: interfaces | Capturing Network Interfaces 3 | uri: 4 | force_basic_auth: true 5 | method: GET 6 | password: "{{ freenas_password }}" 7 | return_content: true 8 | status_code: 200 9 | url: "{{ freenas_api_url + '/network/interface/' }}" 10 | user: "{{ freenas_user }}" 11 | register: captured_network_interfaces 12 | delegate_to: localhost 13 | tags: 14 | - freenas_interfaces 15 | - freenas_network 16 | 17 | - name: interfaces | Capturing VLANS 18 | uri: 19 | force_basic_auth: true 20 | method: GET 21 | password: "{{ freenas_password }}" 22 | return_content: true 23 | status_code: 200 24 | url: "{{ freenas_api_url + '/network/vlan/' }}" 25 | user: "{{ freenas_user }}" 26 | register: captured_vlan_interfaces 27 | delegate_to: localhost 28 | tags: 29 | - freenas_interfaces 30 | - freenas_network 31 | 32 | - name: interfaces | Capturing LAGGs 33 | uri: 34 | force_basic_auth: true 35 | method: GET 36 | password: "{{ freenas_password }}" 37 | return_content: true 38 | status_code: 200 39 | url: "{{ freenas_api_url + '/network/lagg/' }}" 40 | user: "{{ freenas_user }}" 41 | register: captured_lagg_interfaces 42 | delegate_to: localhost 43 | tags: 44 | - freenas_interfaces 45 | - freenas_network 46 | 47 | - name: interfaces | Setting Interfaces Config Fact 48 | set_fact: 49 | freenas_interfaces_config: "{{ lookup('template', 'interfaces_config.yaml.j2') }}" 50 | tags: 51 | - freenas_interfaces 52 | - freenas_network 53 | 54 | - name: interfaces | Displaying Interfaces Config Fact 55 | debug: 56 | var: freenas_interfaces_config 57 | tags: 58 | - freenas_interfaces 59 | - freenas_network 60 | 61 | - name: interfaces | Creating Interfaces (Non-VLAN, Non-LAGG) 62 | uri: 63 | body: "{{ item['config'] }}" 64 | body_format: json 65 | force_basic_auth: true 66 | method: POST 67 | password: "{{ freenas_password }}" 68 | return_content: true 69 | status_code: 201 70 | url: "{{ freenas_api_url + '/network/interface/' }}" 71 | user: "{{ freenas_user }}" 72 | delegate_to: localhost 73 | with_items: "{{ freenas_interfaces_config['create_interfaces'] }}" 74 | when: > 75 | item['config'] is defined and 76 | item['config'] != {} and 77 | not item['vlan_interface'] and 78 | not item['lagg_interface'] 79 | tags: 80 | - freenas_interfaces 81 | - freenas_network 82 | 83 | - name: interfaces | Creating VLAN Interfaces 84 | uri: 85 | body: "{{ item }}" 86 | body_format: json 87 | force_basic_auth: true 88 | method: POST 89 | password: "{{ freenas_password }}" 90 | return_content: true 91 | status_code: 201 92 | url: "{{ freenas_api_url + '/network/vlan/' }}" 93 | user: "{{ freenas_user }}" 94 | delegate_to: localhost 95 | with_items: "{{ freenas_interfaces_config['create_vlan_interfaces'] }}" 96 | tags: 97 | - freenas_interfaces 98 | - freenas_network 99 | 100 | - name: interfaces | Creating LAGG Interfaces 101 | uri: 102 | body: "{{ item }}" 103 | body_format: json 104 | force_basic_auth: true 105 | method: POST 106 | password: "{{ freenas_password }}" 107 | return_content: true 108 | status_code: 201 109 | url: "{{ freenas_api_url + '/network/lagg/' }}" 110 | user: "{{ freenas_user }}" 111 | delegate_to: localhost 112 | with_items: "{{ freenas_interfaces_config['create_lagg_interfaces'] }}" 113 | tags: 114 | - freenas_interfaces 115 | - freenas_network 116 | 117 | # We need to catpure the network interfaces again after creating new interfaces 118 | # and vlan interfaces 119 | - name: interfaces | Capturing Network Interfaces 120 | uri: 121 | force_basic_auth: true 122 | method: GET 123 | password: "{{ freenas_password }}" 124 | return_content: true 125 | status_code: 200 126 | url: "{{ freenas_api_url + '/network/interface/' }}" 127 | user: "{{ freenas_user }}" 128 | register: captured_network_interfaces 129 | delegate_to: localhost 130 | tags: 131 | - freenas_interfaces 132 | - freenas_network 133 | 134 | - name: interfaces | Capturing LAGGs 135 | uri: 136 | force_basic_auth: true 137 | method: GET 138 | password: "{{ freenas_password }}" 139 | return_content: true 140 | status_code: 200 141 | url: "{{ freenas_api_url + '/network/lagg/' }}" 142 | user: "{{ freenas_user }}" 143 | register: captured_lagg_interfaces 144 | delegate_to: localhost 145 | tags: 146 | - freenas_interfaces 147 | - freenas_network 148 | 149 | # Update config after re-capturing the network interfaces 150 | - name: interfaces | Setting Interfaces Config Fact 151 | set_fact: 152 | freenas_interfaces_config: "{{ lookup('template', 'interfaces_config.yaml.j2') }}" 153 | tags: 154 | - freenas_interfaces 155 | - freenas_network 156 | 157 | - name: interfaces | Updating Interfaces 158 | uri: 159 | body: "{{ item['config'] }}" 160 | body_format: json 161 | force_basic_auth: true 162 | method: PUT 163 | password: "{{ freenas_password }}" 164 | return_content: true 165 | status_code: 200 166 | url: "{{ freenas_api_url + '/network/interface/' + item['id']|string + '/' }}" 167 | user: "{{ freenas_user }}" 168 | delegate_to: localhost 169 | with_items: "{{ freenas_interfaces_config['update_interfaces'] }}" 170 | when: > 171 | item['config'] is defined and 172 | item['config'] != {} 173 | tags: 174 | - freenas_interfaces 175 | - freenas_network 176 | 177 | - name: interfaces | Updating VLAN Interfaces 178 | uri: 179 | body: "{{ item['config'] }}" 180 | body_format: json 181 | force_basic_auth: true 182 | method: PUT 183 | password: "{{ freenas_password }}" 184 | return_content: true 185 | status_code: 200 186 | url: "{{ freenas_api_url + '/network/vlan/' + item['id']|string + '/' }}" 187 | user: "{{ freenas_user }}" 188 | delegate_to: localhost 189 | with_items: "{{ freenas_interfaces_config['update_vlan_interfaces'] }}" 190 | tags: 191 | - freenas_interfaces 192 | - freenas_network 193 | 194 | - name: interfaces | Deleting VLAN Interfaces 195 | uri: 196 | force_basic_auth: true 197 | method: DELETE 198 | password: "{{ freenas_password }}" 199 | return_content: true 200 | status_code: 204 201 | url: "{{ freenas_api_url + '/network/vlan/' + item|string + '/' }}" 202 | user: "{{ freenas_user }}" 203 | delegate_to: localhost 204 | with_items: "{{ freenas_interfaces_config['delete_vlan_interfaces'] }}" 205 | tags: 206 | - freenas_interfaces 207 | - freenas_network 208 | 209 | - name: interfaces | Deleting Interfaces 210 | uri: 211 | force_basic_auth: true 212 | method: DELETE 213 | password: "{{ freenas_password }}" 214 | return_content: true 215 | status_code: 204 216 | url: "{{ freenas_api_url + '/network/interface/' + item|string + '/' }}" 217 | user: "{{ freenas_user }}" 218 | delegate_to: localhost 219 | with_items: "{{ freenas_interfaces_config['delete_interfaces'] }}" 220 | tags: 221 | - freenas_interfaces 222 | - freenas_network 223 | 224 | - name: interfaces | Deleting LAGG Interfaces 225 | uri: 226 | force_basic_auth: true 227 | method: DELETE 228 | password: "{{ freenas_password }}" 229 | return_content: true 230 | status_code: 204 231 | url: "{{ freenas_api_url + '/network/lagg/' + item|string + '/' }}" 232 | user: "{{ freenas_user }}" 233 | delegate_to: localhost 234 | with_items: "{{ freenas_interfaces_config['delete_lagg_interfaces'] }}" 235 | tags: 236 | - freenas_interfaces 237 | - freenas_network 238 | -------------------------------------------------------------------------------- /tasks/storage.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: storage | Capturing Disks 3 | uri: 4 | force_basic_auth: true 5 | method: GET 6 | password: "{{ freenas_password }}" 7 | return_content: true 8 | status_code: 200 9 | url: "{{ freenas_api_url + '/storage/disk/' }}" 10 | user: "{{ freenas_user }}" 11 | register: freenas_disks_found 12 | delegate_to: localhost 13 | tags: 14 | - freenas_storage 15 | 16 | # We run first time to collect current state 17 | - name: storage | Capturing Initial Volumes 18 | uri: 19 | force_basic_auth: true 20 | method: GET 21 | password: "{{ freenas_password }}" 22 | return_content: true 23 | status_code: 200 24 | url: "{{ freenas_api_url + '/storage/volume/' }}" 25 | user: "{{ freenas_user }}" 26 | register: freenas_volumes 27 | delegate_to: localhost 28 | tags: 29 | - freenas_storage 30 | 31 | # We run first time to collect current state 32 | - name: storage | Capturing Initial Datasets 33 | uri: 34 | force_basic_auth: true 35 | method: GET 36 | password: "{{ freenas_password }}" 37 | return_content: true 38 | status_code: 200 39 | url: "{{ freenas_api_url + '/storage/dataset/' }}" 40 | user: "{{ freenas_user }}" 41 | register: freenas_datasets 42 | delegate_to: localhost 43 | tags: 44 | - freenas_storage 45 | 46 | - name: storage | Capturing Initial ZVOLs 47 | uri: 48 | force_basic_auth: true 49 | method: GET 50 | password: "{{ freenas_password }}" 51 | return_content: true 52 | status_code: 200 53 | url: "{{ freenas_api_url + '/storage/volume/' + item['id']|string + '/zvols/' }}" 54 | user: "{{ freenas_user }}" 55 | delegate_to: localhost 56 | register: freenas_zvols 57 | with_items: "{{ freenas_volumes['json'] }}" 58 | tags: 59 | - freenas_storage 60 | 61 | # We run first time to collect current state 62 | - name: storage | Setting Initial Fact For Storage Config 63 | set_fact: 64 | freenas_storage_config: "{{ lookup('template', 'storage_config.yaml.j2') }}" 65 | tags: 66 | - freenas_storage 67 | 68 | - name: storage | Displaying Storage Config 69 | debug: 70 | var: freenas_storage_config 71 | tags: 72 | - freenas_storage 73 | 74 | - name: storage | Updating Disk Configurations 75 | uri: 76 | body: "{{ item['config'] }}" 77 | body_format: json 78 | force_basic_auth: true 79 | method: PUT 80 | password: "{{ freenas_password }}" 81 | return_content: true 82 | status_code: 200 83 | url: "{{ freenas_api_url + '/storage/disk/' + item['id']|string + '/' }}" 84 | user: "{{ freenas_user }}" 85 | delegate_to: localhost 86 | with_items: "{{ freenas_storage_config['update_disks'] }}" 87 | tags: 88 | - freenas_storage 89 | 90 | - name: storage | Creating Volumes 91 | uri: 92 | body: "{{ item }}" 93 | body_format: json 94 | force_basic_auth: true 95 | method: POST 96 | password: "{{ freenas_password }}" 97 | return_content: true 98 | status_code: 201 99 | url: "{{ freenas_api_url + '/storage/volume/' }}" 100 | user: "{{ freenas_user }}" 101 | delegate_to: localhost 102 | with_items: "{{ freenas_storage_config['create_volumes'] }}" 103 | tags: 104 | - freenas_storage 105 | 106 | - name: storage | Deleting Volumes 107 | uri: 108 | body: "{{ item['config'] }}" 109 | body_format: json 110 | force_basic_auth: true 111 | method: DELETE 112 | password: "{{ freenas_password }}" 113 | return_content: true 114 | status_code: 204 115 | url: "{{ freenas_api_url + '/storage/volume/' + item['id']|string + '/' }}" 116 | user: "{{ freenas_user }}" 117 | delegate_to: localhost 118 | with_items: "{{ freenas_storage_config['delete_volumes'] }}" 119 | tags: 120 | - freenas_storage 121 | 122 | # We run second time to collect current state 123 | - name: storage | Capturing Volumes (After volume creations) 124 | uri: 125 | force_basic_auth: true 126 | method: GET 127 | password: "{{ freenas_password }}" 128 | return_content: true 129 | status_code: 200 130 | url: "{{ freenas_api_url + '/storage/volume/' }}" 131 | user: "{{ freenas_user }}" 132 | register: freenas_volumes 133 | delegate_to: localhost 134 | tags: 135 | - freenas_storage 136 | 137 | # We run second time to collect current state 138 | - name: storage | Updating Fact For Storage Config (After volume creations) 139 | set_fact: 140 | freenas_storage_config: "{{ lookup('template', 'storage_config.yaml.j2') }}" 141 | tags: 142 | - freenas_storage 143 | 144 | - name: storage | Displaying Storage Config 145 | debug: 146 | var: freenas_storage_config 147 | tags: 148 | - freenas_storage 149 | 150 | - name: storage | Creating Datasets 151 | uri: 152 | body: "{{ item['config'] }}" 153 | body_format: json 154 | force_basic_auth: true 155 | method: POST 156 | password: "{{ freenas_password }}" 157 | return_content: true 158 | status_code: 201 159 | url: "{{ freenas_api_url + '/storage/dataset/' + item['path'] + '/' }}" 160 | user: "{{ freenas_user }}" 161 | delegate_to: localhost 162 | with_items: "{{ freenas_storage_config['create_datasets'] }}" 163 | tags: 164 | - freenas_storage 165 | 166 | # We run second time to collect current state 167 | - name: storage | Capturing Datasets (After dataset creations) 168 | uri: 169 | force_basic_auth: true 170 | method: GET 171 | password: "{{ freenas_password }}" 172 | return_content: true 173 | status_code: 200 174 | url: "{{ freenas_api_url + '/storage/dataset/' }}" 175 | user: "{{ freenas_user }}" 176 | register: freenas_datasets 177 | delegate_to: localhost 178 | tags: 179 | - freenas_storage 180 | 181 | # We run third time to collect current state 182 | - name: storage | Updating Fact For Storage Config (After dataset creations) 183 | set_fact: 184 | freenas_storage_config: "{{ lookup('template', 'storage_config.yaml.j2') }}" 185 | tags: 186 | - freenas_storage 187 | 188 | - name: storage | Displaying Storage Config 189 | debug: 190 | var: freenas_storage_config 191 | tags: 192 | - freenas_storage 193 | 194 | - name: storage | Creating ZVOLs 195 | uri: 196 | body: "{{ item['config'] }}" 197 | body_format: json 198 | force_basic_auth: true 199 | method: POST 200 | password: "{{ freenas_password }}" 201 | return_content: true 202 | status_code: 202 203 | url: "{{ freenas_api_url + '/storage/volume/' + item['pool'] + '/zvols/' }}" 204 | user: "{{ freenas_user }}" 205 | delegate_to: localhost 206 | with_items: "{{ freenas_storage_config['create_zvols'] }}" 207 | tags: 208 | - freenas_storage 209 | 210 | # We run third time to collect current state 211 | - name: storage | Capturing Datasets (After ZVOL creations) 212 | uri: 213 | force_basic_auth: true 214 | method: GET 215 | password: "{{ freenas_password }}" 216 | return_content: true 217 | status_code: 200 218 | url: "{{ freenas_api_url + '/storage/dataset/' }}" 219 | user: "{{ freenas_user }}" 220 | register: freenas_datasets 221 | delegate_to: localhost 222 | tags: 223 | - freenas_storage 224 | 225 | - name: storage | Capturing ZVOLs (After ZVOL creations) 226 | uri: 227 | force_basic_auth: true 228 | method: GET 229 | password: "{{ freenas_password }}" 230 | return_content: true 231 | status_code: 200 232 | url: "{{ freenas_api_url + '/storage/volume/' + item['id']|string + '/zvols/' }}" 233 | user: "{{ freenas_user }}" 234 | delegate_to: localhost 235 | register: freenas_zvols 236 | with_items: "{{ freenas_volumes['json'] }}" 237 | tags: 238 | - freenas_storage 239 | 240 | # We run fourth time to collect current state 241 | - name: storage | Updating Fact For Storage Config (After dataset creations) 242 | set_fact: 243 | freenas_storage_config: "{{ lookup('template', 'storage_config.yaml.j2') }}" 244 | tags: 245 | - freenas_storage 246 | 247 | - name: storage | Displaying Storage Config 248 | debug: 249 | var: freenas_storage_config 250 | tags: 251 | - freenas_storage 252 | 253 | - name: storage | Updating Datasets 254 | uri: 255 | body: "{{ item['config'] }}" 256 | body_format: json 257 | force_basic_auth: true 258 | method: PUT 259 | password: "{{ freenas_password }}" 260 | return_content: true 261 | status_code: 200 262 | url: "{{ freenas_api_url + '/storage/dataset/' + item['path'] + '/' + item['dataset_name'] + '/' }}" 263 | user: "{{ freenas_user }}" 264 | delegate_to: localhost 265 | with_items: "{{ freenas_storage_config['update_datasets'] }}" 266 | tags: 267 | - freenas_storage 268 | 269 | - name: storage | Updating ZVOLs 270 | uri: 271 | body: "{{ item['config'] }}" 272 | body_format: json 273 | force_basic_auth: true 274 | method: PUT 275 | password: "{{ freenas_password }}" 276 | return_content: true 277 | status_code: 201 278 | url: "{{ freenas_api_url + '/storage/volume/' + item['pool'] + '/zvols/' + item['name'] + '/' }}" 279 | user: "{{ freenas_user }}" 280 | delegate_to: localhost 281 | with_items: "{{ freenas_storage_config['update_zvols'] }}" 282 | tags: 283 | - freenas_storage 284 | 285 | - name: storage | Deleting Datasets 286 | uri: 287 | force_basic_auth: true 288 | method: DELETE 289 | password: "{{ freenas_password }}" 290 | return_content: true 291 | status_code: 204 292 | url: "{{ freenas_api_url + '/storage/dataset/' + item['path'] + '/' + item['dataset_name'] + '/' }}" 293 | user: "{{ freenas_user }}" 294 | delegate_to: localhost 295 | with_items: "{{ freenas_storage_config['delete_datasets'] }}" 296 | tags: 297 | - freenas_storage 298 | 299 | - name: storage | Deleting ZVOLs 300 | uri: 301 | force_basic_auth: true 302 | method: DELETE 303 | password: "{{ freenas_password }}" 304 | return_content: true 305 | status_code: 204 306 | url: "{{ freenas_api_url + '/storage/volume/' + item['pool'] + '/zvols/' + item['name'] + '/' }}" 307 | user: "{{ freenas_user }}" 308 | delegate_to: localhost 309 | with_items: "{{ freenas_storage_config['delete_zvols'] }}" 310 | tags: 311 | - freenas_storage 312 | 313 | - name: storage | Setting Mountpoint Permissions 314 | uri: 315 | body: "{{ item }}" 316 | body_format: json 317 | force_basic_auth: true 318 | method: PUT 319 | password: "{{ freenas_password }}" 320 | return_content: true 321 | status_code: 201 322 | url: "{{ freenas_api_url + '/storage/permission/' }}" 323 | user: "{{ freenas_user }}" 324 | delegate_to: localhost 325 | with_items: "{{ freenas_storage_config['permissions'] }}" 326 | tags: 327 | - freenas_storage 328 | # TODO: Create logic to determing if snapshot task already exists 329 | # - name: storage | Creating Snapshot Tasks 330 | # uri: 331 | # body: "{{ item[1]['config'] }}" 332 | # body_format: json 333 | # force_basic_auth: true 334 | # method: POST 335 | # password: "{{ freenas_password }}" 336 | # return_content: true 337 | # status_code: 201 338 | # url: "{{ freenas_api_url + '/storage/task/' }}" 339 | # user: "{{ freenas_user }}" 340 | # delegate_to: localhost 341 | # register: freenas_datasets_created 342 | # with_subelements: 343 | # - "{{ freenas_storage }}" 344 | # - snapshot_tasks 345 | # when: > 346 | # item[1] is defined and 347 | # item[1]['state'] == "present" 348 | # tags: 349 | # - freenas_storage 350 | 351 | # - name: storage | Capturing Snapshot Tasks 352 | # uri: 353 | # force_basic_auth: true 354 | # method: GET 355 | # password: "{{ freenas_password }}" 356 | # return_content: true 357 | # status_code: 200 358 | # url: "{{ freenas_api_url + '/storage/task/' }}" 359 | # user: "{{ freenas_user }}" 360 | # delegate_to: localhost 361 | # register: freenas_snapshot_tasks 362 | # tags: 363 | # - freenas_storage 364 | 365 | # - name: storage | Displaying Captured Snapshot Tasks 366 | # debug: 367 | # var: freenas_snapshot_tasks['json'] 368 | # tags: 369 | # - freenas_storage 370 | 371 | - name: storage | Capturing Snapshots 372 | uri: 373 | force_basic_auth: true 374 | method: GET 375 | password: "{{ freenas_password }}" 376 | return_content: true 377 | status_code: 200 378 | url: "{{ freenas_api_url + '/storage/snapshot/' }}" 379 | user: "{{ freenas_user }}" 380 | delegate_to: localhost 381 | register: freenas_snapshots 382 | tags: 383 | - freenas_storage 384 | 385 | - name: storage | Displaying Captured Snapshots 386 | debug: 387 | var: freenas_snapshots['json'] 388 | tags: 389 | - freenas_storage 390 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for ansible-freenas 3 | # Define API url to connect to 4 | freenas_api_url: "{{ freenas_url + '/api/v1.0' }}" 5 | 6 | # Defines whether or not to apply pending updates if any are found 7 | freenas_apply_updates: false 8 | 9 | # Define individual disk configurations to apply 10 | freenas_disks: 11 | [] 12 | # - name: da0 13 | # config: 14 | # disk_acousticlevel: Disabled 15 | # disk_advpowermgmt: Disabled 16 | # disk_description: 17 | # disk_hddstandby: Always On 18 | # disk_togglesmart: true 19 | # - name: da1 20 | # config: 21 | # disk_acousticlevel: Disabled 22 | # disk_advpowermgmt: Disabled 23 | # disk_description: 24 | # disk_hddstandby: Always On 25 | # disk_togglesmart: true 26 | # - name: da2 27 | # config: 28 | # disk_acousticlevel: Disabled 29 | # disk_advpowermgmt: Disabled 30 | # disk_description: 31 | # disk_hddstandby: Always On 32 | # disk_togglesmart: true 33 | # - name: da3 34 | # config: 35 | # disk_acousticlevel: Disabled 36 | # disk_advpowermgmt: Disabled 37 | # disk_description: 38 | # disk_hddstandby: Always On 39 | # disk_togglesmart: true 40 | # - name: da4 41 | # config: 42 | # disk_acousticlevel: Disabled 43 | # disk_advpowermgmt: Disabled 44 | # disk_description: 45 | # disk_hddstandby: Always On 46 | # disk_togglesmart: true 47 | # - name: da5 48 | # config: 49 | # disk_acousticlevel: Disabled 50 | # disk_advpowermgmt: Disabled 51 | # disk_description: 52 | # disk_hddstandby: Always On 53 | # disk_togglesmart: true 54 | 55 | # Define global network configuration 56 | freenas_global_config: 57 | {} 58 | # gc_domain: "{{ freenas_pri_domain }}" 59 | # gc_hostname: "{{ inventory_hostname_short }}" 60 | # gc_ipv4gateway: 61 | # gc_ipv6gateway: 62 | # gc_nameserver1: 63 | # gc_nameserver2: 64 | # gc_nameserver3: 65 | # gc_netwait_enabled: false 66 | 67 | freenas_groups: 68 | [] 69 | # - name: vagrant 70 | # config: 71 | # bsdgrp_gid: 1000 72 | # bsdgrp_group: vagrant 73 | # bsdgrp_sudo: false 74 | # state: present 75 | # - name: testgroup 76 | # config: 77 | # bsdgrp_gid: 1001 78 | # bsdgrp_group: testgroup 79 | # bsdgrp_sudo: false 80 | # state: present 81 | 82 | # Define host 83 | freenas_host: "{{ ansible_host }}" 84 | 85 | # Define network interfaces including vlan|lagg interfaces to configure IP settings 86 | freenas_interfaces: 87 | [] 88 | # - int: em0 89 | # config: 90 | # int_dhcp: true 91 | # int_interface: em0 92 | # int_ipv4address: 93 | # int_name: public 94 | # int_v4netmaskbit: 95 | # state: present 96 | # - int: em1 97 | # config: 98 | # {} 99 | # # int_dhcp: false 100 | # # int_interface: em1 101 | # # int_ipv4address: 192.168.250.10 102 | # # int_name: private 103 | # # int_v4netmaskbit: 24 104 | # state: present 105 | # - int: em2 106 | # config: 107 | # {} 108 | # # int_dhcp: false 109 | # # int_interface: em1 110 | # # int_ipv4address: 192.168.250.20 111 | # # int_name: private 112 | # # int_v4netmaskbit: 24 113 | # state: present 114 | # - int: vlan10 115 | # config: 116 | # int_dhcp: false 117 | # int_interface: vlan10 118 | # int_ipv4address: 192.168.250.10 119 | # int_name: private 120 | # int_v4netmaskbit: 24 121 | # state: present 122 | # - int: lagg0 123 | # config: 124 | # int_dhcp: false 125 | # int_interface: lagg0 126 | # int_ipv4address: 192.168.250.10 127 | # int_name: private 128 | # int_v4netmaskbit: 24 129 | # state: present 130 | 131 | # Define network lagg interfaces to configure for network interfaces 132 | # To configure and IP address on a lagg interface, add the lagg to 133 | # freenas_interfaces 134 | freenas_lagg_interfaces: 135 | [] 136 | # - name: lagg0 137 | # config: 138 | # lagg_interface: lagg0 139 | # lagg_interfaces: 140 | # - em1 141 | # - em2 142 | # lagg_protocol: roundrobin 143 | # state: present 144 | 145 | # Define network vlan interfaces to configure for network interfaces 146 | # To configure an IP address on a vlan interface, add the vlan to 147 | # freenas_interfaces 148 | freenas_vlan_interfaces: 149 | [] 150 | # - name: vlan10 151 | # config: 152 | # vlan_description: VLAN 10 153 | # vlan_pint: em1 154 | # vlan_tag: 10 155 | # vlan_vint: vlan10 156 | # state: present 157 | 158 | # Define root password 159 | freenas_password: freenas 160 | 161 | # Define web port 162 | freenas_port: 8080 163 | 164 | # Define DNS suffix 165 | freenas_pri_domain: vagrant.local 166 | 167 | # Define url 168 | freenas_url: "{{ freenas_web_protocol + '://' + freenas_host + ':' + freenas_port|string }}" 169 | 170 | # Define services 171 | freenas_services: 172 | [] 173 | # - name: afp 174 | # config: 175 | # afp_srv_connections_limit: 50 176 | # afp_srv_guest_user: nobody 177 | # afp_srv_guest: false 178 | # srv_enable: false 179 | # - name: cifs 180 | # config: 181 | # cifs_srv_allow_execute_always: true 182 | # cifs_srv_description: FreeNAS Server 183 | # cifs_srv_domain_logons: false 184 | # cifs_srv_guest: nobody 185 | # cifs_srv_hostlookup: true 186 | # cifs_srv_localmaster: true 187 | # cifs_srv_netbiosname: "{{ inventory_hostname_short }}" 188 | # cifs_srv_nullpw: false 189 | # cifs_srv_obey_pam_restrictions: true 190 | # cifs_srv_timeserver: true 191 | # cifs_srv_unixext: true 192 | # cifs_srv_workgroup: WORKGROUP 193 | # cifs_srv_zeroconf: true 194 | # srv_enable: true 195 | # - name: domaincontroller 196 | # config: 197 | # srv_enable: false 198 | # - name: dynamicdns 199 | # config: 200 | # srv_enable: false 201 | # - name: ftp 202 | # config: 203 | # srv_enable: false 204 | # - name: lldp 205 | # config: 206 | # srv_enable: false 207 | # - name: netdata 208 | # config: 209 | # srv_enable: false 210 | # - name: nfs 211 | # config: 212 | # nfs_srv_16: false 213 | # nfs_srv_allow_nonroot: false 214 | # nfs_srv_servers: 4 215 | # nfs_srv_udp: false 216 | # nfs_srv_v4_krb: false 217 | # nfs_srv_v4: false 218 | # srv_enable: true 219 | # - name: rsync 220 | # config: 221 | # srv_enable: false 222 | # - name: smartd 223 | # config: 224 | # smart_critical: 0 225 | # smart_difference: 0 226 | # smart_email: 227 | # smart_informational: 0 228 | # smart_interval: 30 229 | # smart_powermode: never 230 | # srv_enable: true 231 | # - name: ssh 232 | # config: 233 | # srv_enable: true 234 | # ssh_compression: false 235 | # ssh_passwordauth: true 236 | # ssh_rootlogin: true 237 | # ssh_tcpfwd: false 238 | # ssh_tcpport: 22 239 | # - name: tftp 240 | # config: 241 | # srv_enable: false 242 | # - name: webdav 243 | # config: 244 | # srv_enable: false 245 | 246 | # Define sharing 247 | freenas_sharing: 248 | [] 249 | # - type: cifs 250 | # name: testds 251 | # config: 252 | # cifs_comment: My Test Share 253 | # cifs_default_permissions: true 254 | # cifs_guestok: false 255 | # cifs_guestonly: true 256 | # cifs_name: testds 257 | # cifs_path: /mnt/test1/testds 258 | # cifs_recyclebin: false 259 | # cifs_ro: false 260 | # cifs_vfsobjects: 261 | # - zfs_space 262 | # - zfsacl 263 | # - streams_xattr 264 | # state: present 265 | # - type: nfs 266 | # config: 267 | # nfs_alldirs: false 268 | # nfs_comment: My Test Share 269 | # nfs_mapall_group: nogroup 270 | # nfs_mapall_user: nobody 271 | # nfs_maproot_group: 272 | # nfs_maproot_user: 273 | # nfs_paths: 274 | # - /mnt/test1/testds 275 | # nfs_ro: false 276 | # nfs_security: sys 277 | # state: present 278 | 279 | freenas_storage: 280 | [] 281 | # - name: tank 282 | # datasets: 283 | # - name: vagrant 284 | # dataset_config: 285 | # atime: inherit 286 | # comment: Vagrant Home 287 | # dedup: "off" 288 | # name: vagrant 289 | # quota: 0 290 | # reservation: 0 291 | # state: present 292 | # # These do not work when task_byweekday is more than one day 293 | # snapshot_tasks: [] 294 | # # - name: Daily 295 | # # config: 296 | # # task_begin: "00:00:00" 297 | # # task_byweekday: "1,2" 298 | # # task_enabled: true 299 | # # task_end: "23:45:00" 300 | # # task_filesystem: tank 301 | # # task_interval: 1440 302 | # # task_recursive: true 303 | # # task_repeat_unit: weekly 304 | # # task_ret_count: 1 305 | # # task_ret_unit: week 306 | # # state: present 307 | # permissions: 308 | # [] 309 | # # - mp_path: /mnt/tank 310 | # # mp_acl: unix 311 | # # mp_mode: 755 312 | # # mp_user: root 313 | # # mp_group: wheel 314 | # # - mp_path: /mnt/tank/vagrant 315 | # # mp_acl: unix 316 | # # mp_mode: 755 317 | # # mp_user: vagrant 318 | # # mp_group: vagrant 319 | # state: present 320 | # volume_config: 321 | # mountpoint: /mnt/tank 322 | # volume_name: tank 323 | # layout: 324 | # - vdevtype: stripe 325 | # disks: 326 | # - da1 327 | # - name: test1 328 | # datasets: 329 | # - name: testds 330 | # dataset_config: 331 | # atime: inherit 332 | # comment: Test 333 | # dedup: "off" 334 | # name: testds 335 | # quota: 0 336 | # reservation: 0 337 | # state: present 338 | # permissions: 339 | # [] 340 | # # - mp_path: /mnt/test1 341 | # # mp_acl: unix 342 | # # mp_mode: 755 343 | # # mp_user: root 344 | # # mp_group: wheel 345 | # # - mp_path: /mnt/test1/testds 346 | # # mp_acl: unix 347 | # # mp_mode: 777 348 | # # mp_user: nobody 349 | # # mp_group: nogroup 350 | # # These do not work when task_byweekday is more than one day 351 | # snapshot_tasks: 352 | # [] 353 | # # - name: Daily 354 | # # config: 355 | # # task_begin: "00:00:00" 356 | # # task_byweekday: "1,2" 357 | # # task_enabled: true 358 | # # task_end: "23:45:00" 359 | # # task_filesystem: test1 360 | # # task_interval: 1440 361 | # # task_recursive: true 362 | # # task_repeat_unit: weekly 363 | # # task_ret_count: 1 364 | # # task_ret_unit: week 365 | # # state: present 366 | # state: present 367 | # volume_config: 368 | # mountpoint: /mnt/test1 369 | # volume_name: test1 370 | # layout: 371 | # - vdevtype: mirror 372 | # disks: 373 | # - da2 374 | # - da3 375 | # - vdevtype: mirror 376 | # disks: 377 | # - da4 378 | # - da5 379 | # zvols: 380 | # - name: testzvol 381 | # state: present 382 | # zvol_config: 383 | # blocksize: 4K 384 | # comments: Test ZVOL 385 | # name: testzvol 386 | # sparse: true 387 | # volsize: 2G 388 | 389 | # Define system settings 390 | freenas_system: 391 | [] 392 | # - name: advanced 393 | # config: 394 | # adv_advancedmode: false 395 | # adv_anonstats: true 396 | # adv_autotune: false 397 | # adv_consolemenu: true 398 | # adv_consolemsg: false 399 | # adv_debugkernel: false 400 | # adv_motd: Welcome to FreeNAS 401 | # adv_serialconsole: false 402 | # adv_swapondrive: 2 403 | # adv_traceback: true 404 | # adv_uploadcrash: true 405 | # - name: email 406 | # config: 407 | # em_fromemail: "{{ 'root@' + inventory_hostname_short + '.' + freenas_pri_domain }}" 408 | # em_outgoingserver: 409 | # em_pass: 410 | # em_port: 25 411 | # em_security: plain 412 | # em_smtp: false 413 | # em_user: 414 | # - name: settings 415 | # config: 416 | # stg_directoryservice: 417 | # stg_guiaddress: 0.0.0.0 418 | # stg_guihttpsport: 443 419 | # stg_guihttpsredirect: true 420 | # stg_guiport: 80 421 | # stg_guiprotocol: http 422 | # stg_guiv6address: "::" 423 | # stg_language: en 424 | # stg_syslogserver: 425 | # stg_timezone: UTC 426 | 427 | # Define user 428 | freenas_user: root 429 | 430 | # Define users to manage 431 | freenas_users: 432 | [] 433 | # - name: vagrant 434 | # config: 435 | # bsdusr_username: vagrant 436 | # bsdusr_creategroup: true 437 | # bsdusr_full_name: Vagrant User 438 | # bsdusr_home: /mnt/tank/vagrant 439 | # bsdusr_password: vagrant 440 | # bsdusr_shell: /bin/csh 441 | # bsdusr_sshpubkey: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key" 442 | # bsdusr_sudo: true 443 | # bsdusr_uid: 1001 444 | # state: present 445 | # - name: test1 446 | # config: 447 | # bsdusr_username: test1 448 | # bsdusr_creategroup: true 449 | # bsdusr_full_name: Test User 450 | # bsdusr_home: /nonexistent 451 | # bsdusr_password: test1 452 | # bsdusr_shell: /bin/csh 453 | # bsdusr_sshpubkey: 454 | # bsdusr_sudo: false 455 | # bsdusr_uid: 1002 456 | # state: present 457 | 458 | # Define web protocol http(s) 459 | freenas_web_protocol: http 460 | --------------------------------------------------------------------------------