├── tests ├── library ├── dladm_vlan.yml ├── dladm_iptun.yml ├── ipadm_ifprop.yml ├── ipadm_addrprop.yml └── dladm_linkprop.yml ├── ansible.cfg ├── .gitignore ├── Vagrantfile ├── LICENSE ├── library ├── smartos_image_facts ├── dladm_etherstub ├── smartos_image_source ├── zpool_facts.py ├── dladm_vlan.py ├── ipadm_if ├── zfs_facts.py ├── dladm_vnic ├── ipadm_prop ├── ipadm_addrprop.py ├── dladm_iptun.py ├── dladm_linkprop.py ├── ipadm_ifprop.py ├── solaris_project ├── beadm.py ├── ipadm_addr.py └── flowadm.py ├── vagrant.py └── README.md /tests/library: -------------------------------------------------------------------------------- 1 | ../library -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | hostfile = ./vagrant.py 3 | module_name = shell 4 | nocows=0 5 | host_key_checking = False 6 | retry_files_enabled = False 7 | 8 | [ssh_connection] 9 | ssh_args = -o ControlMaster=auto -o ControlPersist=60s 10 | pipelining = true 11 | -------------------------------------------------------------------------------- /tests/dladm_vlan.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test dladm_vlan module 3 | hosts: default 4 | gather_facts: false 5 | become: True 6 | become_method: sudo 7 | tasks: 8 | - dladm_vlan: name=vlan42 vlan_id=42 link=e1000g0 state=present 9 | - dladm_vlan: name=vlan42 vlan_id=42 link=e1000g0 state=present 10 | register: state 11 | failed_when: state|changed 12 | 13 | - dladm_vlan: name=vlan42 vlan_id=42 14 | ignore_errors: yes 15 | - dladm_vlan: name=vlan42 16 | ignore_errors: yes 17 | 18 | 19 | - dladm_vlan: name=vlan4200 vlan_id=4200 link=e1000g0 state=present 20 | ignore_errors: yes 21 | 22 | - dladm_vlan: name=vlan42 state=absent 23 | - dladm_vlan: name=vlan42 state=absent 24 | -------------------------------------------------------------------------------- /tests/dladm_iptun.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test dladm_iptun module 3 | hosts: default 4 | gather_facts: false 5 | become: True 6 | become_method: sudo 7 | tasks: 8 | - ipadm_if: name=e1000g0 state=present 9 | - ipadm_addr: addrobj=e1000g0/test address=192.168.1.1/32 state=present 10 | 11 | - dladm_iptun: name=iptun0 local_address=192.168.1.1 remote_address=2.2.2.2 type=ipv4 state=present 12 | - dladm_iptun: name=iptun0 local_address=192.168.1.1 remote_address=2.2.2.2 type=ipv4 state=present 13 | register: state 14 | failed_when: state|changed 15 | 16 | - dladm_iptun: name=iptun0 17 | ignore_errors: yes 18 | 19 | - dladm_iptun: name=iptun0 type=ipv4 local_address=192.168.1.1 20 | ignore_errors: yes 21 | 22 | - dladm_iptun: name=iptun0 state=absent 23 | - dladm_iptun: name=iptun0 state=absent 24 | register: state 25 | failed_when: state|changed 26 | -------------------------------------------------------------------------------- /tests/ipadm_ifprop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test ipadm_ifprop module 3 | hosts: default 4 | gather_facts: false 5 | become: True 6 | become_method: sudo 7 | tasks: 8 | - ipadm_ifprop: protocol=ipv4 name=mtu interface=e1000g0 value=1400 9 | - ipadm_ifprop: protocol=ipv4 name=mtu interface=e1000g0 value=1400 10 | - ipadm_ifprop: protocol=ipv4 name=mtu interface=e1000g0 state=reset 11 | - ipadm_ifprop: protocol=ipv4 name=mtu interface=e1000g0 state=absent 12 | 13 | - ipadm_ifprop: protocol=ipv6 name=arp interface=e1000g0 state=absent 14 | ignore_errors: yes 15 | 16 | - ipadm_ifprop: protocol=ipv6 name=mtu interface=e1000g0 value=1480 17 | - ipadm_ifprop: protocol=ipv6 name=mtu interface=e1000g0 value=1400 18 | - ipadm_ifprop: protocol=ipv6 name=mtu interface=e1000g0 value=1400 19 | - ipadm_ifprop: protocol=ipv6 name=mtu interface=e1000g0 state=reset 20 | - ipadm_ifprop: protocol=ipv6 name=mtu interface=e1000g0 state=absent 21 | -------------------------------------------------------------------------------- /tests/ipadm_addrprop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test ipadm_addrprop module 3 | hosts: default 4 | gather_facts: false 5 | become: True 6 | become_method: sudo 7 | tasks: 8 | - ipadm_addr: addr=10.0.0.1/32 addrobj=e1000g0/test4 state=present 9 | - shell: ipadm show-addrprop -c -o current e1000g0/test4 10 | register: prefixlen 11 | - assert: 12 | that: 13 | - "'32' in prefixlen.stdout" 14 | msg: "Prefixlen is not /32" 15 | - ipadm_addrprop: name=prefixlen addrobj=e1000g0/test4 value=24 state=present 16 | 17 | - shell: ipadm show-addrprop -c -o current e1000g0/test4 18 | register: prefixlen 19 | - assert: 20 | that: 21 | - "'24' in prefixlen.stdout" 22 | msg: "Prefixlen is not /24" 23 | 24 | - ipadm_addrprop: name=prefixlen addrobj=e1000g0/test4 state=reset 25 | - ipadm_addrprop: name=prefixlen addrobj=e1000g0/test4 state=reset 26 | 27 | - ipadm_addr: addrobj=e1000g0/test4 state=absent 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | #Ipython Notebook 62 | .ipynb_checkpoints 63 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # All Vagrant configuration is done below. The "2" in Vagrant.configure 5 | # configures the configuration version (we support older styles for 6 | # backwards compatibility). Please don't change it unless you know what 7 | # you're doing. 8 | Vagrant.configure("2") do |config| 9 | # The most common configuration options are documented and commented below. 10 | # For a complete reference, please see the online documentation at 11 | # https://docs.vagrantup.com. 12 | 13 | # Every Vagrant development environment requires a box. You can search for 14 | # boxes at https://atlas.hashicorp.com/search. 15 | config.vm.box = "openindiana/hipster" 16 | 17 | # Disable automatic box update checking. If you disable this, then 18 | # boxes will only be checked for updates when the user runs 19 | # `vagrant box outdated`. This is not recommended. 20 | config.vm.box_check_update = false 21 | 22 | # Create a private network, which allows host-only access to the machine 23 | # using a specific IP. 24 | config.vm.network "private_network", ip: "192.168.33.10" 25 | 26 | config.vm.provider "virtualbox" do |vb| 27 | vb.memory = "2048" 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /tests/dladm_linkprop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test dladm_linkprop module 3 | hosts: default 4 | gather_facts: false 5 | become: True 6 | become_method: sudo 7 | tasks: 8 | - ipadm_if: name=e1000g1 state=absent 9 | 10 | - dladm_linkprop: link=e1000g1 property=mtu state=present 11 | ignore_errors: true 12 | register: state 13 | 14 | - assert: 15 | that: 16 | - state.failed == True 17 | msg: "Argument 'value' validation failed!" 18 | 19 | - dladm_linkprop: link=e1000g1 property=mtu value=9000 state=present 20 | - dladm_linkprop: link=e1000g1 property=mtu value=9000 state=present 21 | register: state 22 | failed_when: state|changed 23 | 24 | - dladm_linkprop: link=e1000g1 property=mtu value=1560 state=present 25 | - dladm_linkprop: link=e1000g1 property=mtu value=1560 state=present 26 | register: state 27 | failed_when: state|changed 28 | 29 | - dladm_linkprop: link=e1000g1 property=mtu state=reset 30 | - dladm_linkprop: link=e1000g1 property=mtu state=reset 31 | register: state 32 | failed_when: state|changed 33 | 34 | - dladm_linkprop: link=e1000g1 property=state value=down state=present 35 | ignore_errors: true 36 | register: state 37 | 38 | - assert: 39 | that: 40 | - state.failed == True 41 | msg: "Modification of read-only properties succeded!" 42 | 43 | 44 | - ipadm_if: name=e1000g1 state=present 45 | - ipadm_addr: addrobj=e1000g1/v4 address=192.168.33.10/24 state=present 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Adam Števko 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of ansible-illumos nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /library/smartos_image_facts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2015, Adam Števko 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | # 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: smartos_image_facts 25 | short_description: Get SmartOS image details. 26 | description: 27 | - Retrieve facts about all installed images on SmartOS. Facts will be 28 | inserted to the ansible_facts key. 29 | version_added: "2.0" 30 | author: Adam Števko (@xen0l) 31 | options: 32 | filters: 33 | description: 34 | - Criteria for selecting image. Can be any value from image 35 | manifest and 'published_date', 'published', 'source', 'clones', 36 | and 'size'. More informaton can be found at U(https://smartos.org/man/1m/imgadm) 37 | under 'imgadm list'. 38 | required: false 39 | default: None 40 | ''' 41 | 42 | EXAMPLES = ''' 43 | # Return facts about all installed images. 44 | smartos_image_facts: 45 | 46 | # Return all private active Linux images. 47 | smartos_image_facts: filters="os=linux state=active public=false" 48 | 49 | # Show, how many clones does every image have. 50 | smartos_image_facts: 51 | 52 | debug: msg="{{ smartos_images[item]['name'] }}-{{smartos_images[item]['version'] }} 53 | has {{ smartos_images[item]['clones'] }} VM(s)" 54 | with_items: smartos_images.keys() 55 | ''' 56 | 57 | 58 | try: 59 | import json 60 | except ImportError: 61 | import simplejson as json 62 | 63 | 64 | class ImageFacts(object): 65 | 66 | def __init__(self, module): 67 | self.module = module 68 | 69 | self.filters = module.params['filters'] 70 | 71 | def return_all_installed_images(self): 72 | cmd = [self.module.get_bin_path('imgadm')] 73 | 74 | cmd.append('list') 75 | cmd.append('-j') 76 | 77 | if self.filters: 78 | cmd.append(self.filters) 79 | 80 | (rc, out, err) = self.module.run_command(cmd) 81 | 82 | if rc != 0: 83 | self.module.exit_json( 84 | msg='Failed to get all installed images', stderr=err) 85 | 86 | images = json.loads(out) 87 | 88 | result = {} 89 | for image in images: 90 | result[image['manifest']['uuid']] = image['manifest'] 91 | # Merge additional attributes with the image manifest. 92 | for attrib in ['clones', 'source', 'zpool']: 93 | result[image['manifest']['uuid']][attrib] = image[attrib] 94 | 95 | return result 96 | 97 | 98 | def main(): 99 | module = AnsibleModule( 100 | argument_spec=dict( 101 | filters=dict(default=None), 102 | ), 103 | supports_check_mode=False, 104 | ) 105 | 106 | image_facts = ImageFacts(module) 107 | 108 | data = {} 109 | data['smartos_images'] = image_facts.return_all_installed_images() 110 | 111 | module.exit_json(ansible_facts=data) 112 | 113 | from ansible.module_utils.basic import * 114 | main() 115 | -------------------------------------------------------------------------------- /vagrant.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Vagrant external inventory script. Automatically finds the IP of the booted vagrant vm(s), and 4 | returns it under the host group 'vagrant' 5 | 6 | Example Vagrant configuration using this script: 7 | 8 | config.vm.provision :ansible do |ansible| 9 | ansible.playbook = "./provision/your_playbook.yml" 10 | ansible.inventory_file = "./provision/inventory/vagrant.py" 11 | ansible.verbose = true 12 | end 13 | """ 14 | 15 | # Copyright (C) 2013 Mark Mandel 16 | # 2015 Igor Khomyakov 17 | # 18 | # This program is free software: you can redistribute it and/or modify 19 | # it under the terms of the GNU General Public License as published by 20 | # the Free Software Foundation, either version 3 of the License, or 21 | # (at your option) any later version. 22 | # 23 | # This program is distributed in the hope that it will be useful, 24 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 25 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 26 | # GNU General Public License for more details. 27 | # 28 | # You should have received a copy of the GNU General Public License 29 | # along with this program. If not, see . 30 | 31 | # 32 | # Thanks to the spacewalk.py inventory script for giving me the basic structure 33 | # of this. 34 | # 35 | 36 | import sys 37 | import os.path 38 | import subprocess 39 | import re 40 | from paramiko import SSHConfig 41 | from cStringIO import StringIO 42 | from optparse import OptionParser 43 | from collections import defaultdict 44 | try: 45 | import json 46 | except: 47 | import simplejson as json 48 | 49 | _group = 'vagrant' # a default group 50 | _ssh_to_ansible = [('user', 'ansible_ssh_user'), 51 | ('hostname', 'ansible_ssh_host'), 52 | ('identityfile', 'ansible_ssh_private_key_file'), 53 | ('port', 'ansible_ssh_port')] 54 | 55 | # Options 56 | # ------------------------------ 57 | 58 | parser = OptionParser(usage="%prog [options] --list | --host ") 59 | parser.add_option('--list', default=False, dest="list", action="store_true", 60 | help="Produce a JSON consumable grouping of Vagrant servers for Ansible") 61 | parser.add_option('--host', default=None, dest="host", 62 | help="Generate additional host specific details for given host for Ansible") 63 | (options, args) = parser.parse_args() 64 | 65 | # 66 | # helper functions 67 | # 68 | 69 | 70 | # get all the ssh configs for all boxes in an array of dictionaries. 71 | def get_ssh_config(): 72 | return dict((k, get_a_ssh_config(k)) for k in list_running_boxes()) 73 | 74 | 75 | # list all the running boxes 76 | def list_running_boxes(): 77 | output = subprocess.check_output(["vagrant", "status"]).split('\n') 78 | 79 | boxes = [] 80 | 81 | for line in output: 82 | matcher = re.search("([^\s]+)[\s]+running \(.+", line) 83 | if matcher: 84 | boxes.append(matcher.group(1)) 85 | 86 | return boxes 87 | 88 | 89 | # get the ssh config for a single box 90 | def get_a_ssh_config(box_name): 91 | """Gives back a map of all the machine's ssh configurations""" 92 | 93 | output = subprocess.check_output(["vagrant", "ssh-config", box_name]) 94 | config = SSHConfig() 95 | config.parse(StringIO(output)) 96 | host_config = config.lookup(box_name) 97 | 98 | # man 5 ssh_config: 99 | # > It is possible to have multiple identity files ... 100 | # > all these identities will be tried in sequence. 101 | for id in host_config['identityfile']: 102 | if os.path.isfile(id): 103 | host_config['identityfile'] = id 104 | 105 | return dict((v, host_config[k]) for k, v in _ssh_to_ansible) 106 | 107 | # List out servers that vagrant has running 108 | # ------------------------------ 109 | if options.list: 110 | ssh_config = get_ssh_config() 111 | meta = defaultdict(dict) 112 | 113 | for host in ssh_config: 114 | meta['hostvars'][host] = ssh_config[host] 115 | 116 | print(json.dumps({_group: list(ssh_config.keys()), '_meta': meta})) 117 | sys.exit(0) 118 | 119 | # Get out the host details 120 | # ------------------------------ 121 | elif options.host: 122 | print(json.dumps(get_a_ssh_config(options.host))) 123 | sys.exit(0) 124 | 125 | # Print out help 126 | # ------------------------------ 127 | else: 128 | parser.print_help() 129 | sys.exit(0) 130 | -------------------------------------------------------------------------------- /library/dladm_etherstub: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2015, Adam Števko 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | # 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: dladm_etherstub 25 | short_description: Manage etherstubs on Solaris/illumos systems. 26 | description: 27 | - Create or delete etherstubs on Solaris/illumos systems. 28 | version_added: "2.1" 29 | author: Adam Števko (@xen0l) 30 | options: 31 | name: 32 | description: 33 | - Etherstub name. 34 | required: true 35 | temporary: 36 | description: 37 | - Specifies that the etherstub is temporary. Temporary etherstubs 38 | do not persist across reboots. 39 | required: false 40 | default: false 41 | state: 42 | description: 43 | - Create or delete Solaris/illumos etherstub. 44 | required: false 45 | default: "present" 46 | choices: [ "present", "absent" ] 47 | ''' 48 | 49 | EXAMPLES = ''' 50 | # Create 'stub0' etherstub 51 | dladm_etherstub: name=stub0 state=present 52 | 53 | # Remove 'stub0 etherstub 54 | dladm_etherstub: name=stub0 state=absent 55 | ''' 56 | 57 | 58 | class Etherstub(object): 59 | 60 | def __init__(self, module): 61 | self.module = module 62 | 63 | self.name = module.params['name'] 64 | self.temporary = module.params['temporary'] 65 | self.state = module.params['state'] 66 | 67 | def etherstub_exists(self): 68 | cmd = [self.module.get_bin_path('dladm', True)] 69 | 70 | cmd.append('show-etherstub') 71 | cmd.append(self.name) 72 | 73 | (rc, _, _) = self.module.run_command(cmd) 74 | 75 | if rc == 0: 76 | return True 77 | else: 78 | return False 79 | 80 | def create_etherstub(self): 81 | cmd = [self.module.get_bin_path('dladm', True)] 82 | 83 | cmd.append('create-etherstub') 84 | 85 | if self.temporary: 86 | cmd.append('-t') 87 | cmd.append(self.name) 88 | 89 | return self.module.run_command(cmd) 90 | 91 | def delete_etherstub(self): 92 | cmd = [self.module.get_bin_path('dladm', True)] 93 | 94 | cmd.append('delete-etherstub') 95 | 96 | if self.temporary: 97 | cmd.append('-t') 98 | cmd.append(self.name) 99 | 100 | return self.module.run_command(cmd) 101 | 102 | 103 | def main(): 104 | module = AnsibleModule( 105 | argument_spec=dict( 106 | name=dict(required=True), 107 | temporary=dict(default=False, type='bool'), 108 | state=dict(default='present', choices=['absent', 'present']), 109 | ), 110 | supports_check_mode=True 111 | ) 112 | 113 | etherstub = Etherstub(module) 114 | 115 | rc = None 116 | out = '' 117 | err = '' 118 | result = {} 119 | result['name'] = etherstub.name 120 | result['state'] = etherstub.state 121 | result['temporary'] = etherstub.temporary 122 | 123 | if etherstub.state == 'absent': 124 | if etherstub.etherstub_exists(): 125 | if module.check_mode: 126 | module.exit_json(changed=True) 127 | (rc, out, err) = etherstub.delete_etherstub() 128 | if rc != 0: 129 | module.fail_json(name=etherstub.name, msg=err, rc=rc) 130 | elif etherstub.state == 'present': 131 | if not etherstub.etherstub_exists(): 132 | if module.check_mode: 133 | module.exit_json(changed=True) 134 | (rc, out, err) = etherstub.create_etherstub() 135 | 136 | if rc is not None and rc != 0: 137 | module.fail_json(name=etherstub.name, msg=err, rc=rc) 138 | 139 | if rc is None: 140 | result['changed'] = False 141 | else: 142 | result['changed'] = True 143 | 144 | if out: 145 | result['stdout'] = out 146 | if err: 147 | result['stderr'] = err 148 | 149 | module.exit_json(**result) 150 | 151 | from ansible.module_utils.basic import * 152 | main() 153 | -------------------------------------------------------------------------------- /library/smartos_image_source: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2015, Adam Števko 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | # 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: smartos_image_source 25 | short_description: Manage image sources on SmartOS compute nodes. 26 | description: 27 | - Add/delete image source on SmartOS compute nodes. 28 | version_added: "2.1" 29 | author: Adam Števko (@xen0l) 30 | options: 31 | url: 32 | description: 33 | - URL of the image server. 34 | required: true 35 | type: 36 | description: 37 | - Specifies the type of the image server. 38 | required: false 39 | default: imgapi 40 | choices: [ "imgapi", "docker", "dsapi" ] 41 | state: 42 | description: 43 | - Set or reset the property value. 44 | required: true 45 | default: present 46 | choices: [ "present", "absent" ] 47 | ''' 48 | 49 | EXAMPLES = ''' 50 | # Add datasets.at image source 51 | smartos_image_source: url=http://datasets.at state=present 52 | 53 | # Remove official image source 54 | smartos_image_source: url=https://images.joyent.com state=absent 55 | 56 | # Add docker registry as image source 57 | smartos_image_source: url=https://index.docker.io type=docker 58 | ''' 59 | 60 | SUPPORTED_IMAGE_SOURCE_TYPES = ['imgapi', 'docker', 'dsapi'] 61 | 62 | 63 | class ImageSource(object): 64 | 65 | def __init__(self, module): 66 | self.module = module 67 | 68 | self.url = module.params['url'] 69 | self.type = module.params['type'] 70 | self.state = module.params['state'] 71 | 72 | def image_source_exists(self): 73 | cmd = [self.module.get_bin_path('imgadm')] 74 | 75 | cmd.append('sources') 76 | 77 | if self.module.check_mode: 78 | return True 79 | 80 | (rc, out, _) = self.module.run_command(cmd) 81 | 82 | if rc == 0 and self.url in out: 83 | return True 84 | else: 85 | return False 86 | 87 | def add_image_source(self): 88 | cmd = [self.module.get_bin_path('imgadm')] 89 | 90 | cmd.append('sources') 91 | cmd.append('-a') 92 | cmd.append(self.url) 93 | cmd.append('-t') 94 | cmd.append(self.type) 95 | 96 | return self.module.run_command(cmd) 97 | 98 | def delete_image_source(self): 99 | cmd = [self.module.get_bin_path('imgadm')] 100 | 101 | cmd.append('sources') 102 | cmd.append('-d') 103 | cmd.append(self.url) 104 | 105 | return self.module.run_command(cmd) 106 | 107 | 108 | def main(): 109 | module = AnsibleModule( 110 | argument_spec=dict( 111 | url=dict(required=True), 112 | type=dict(required=False, default='imgapi', 113 | choices=SUPPORTED_IMAGE_SOURCE_TYPES), 114 | state=dict( 115 | default='present', choices=['absent', 'present', 'reset']), 116 | ), 117 | supports_check_mode=True 118 | ) 119 | 120 | image_source = ImageSource(module) 121 | 122 | rc = None 123 | out = '' 124 | err = '' 125 | result = {} 126 | result['state'] = image_source.state 127 | result['type'] = image_source.type 128 | result['url'] = image_source.url 129 | 130 | if image_source.state == 'absent': 131 | if image_source.image_source_exists(): 132 | if module.check_mode: 133 | module.exit_json(changed=True) 134 | 135 | (rc, out, err) = image_source.delete_image_source() 136 | if rc != 0: 137 | module.fail_json(msg='Failed to delete %s' % image_source.url) 138 | 139 | elif image_source.state == 'present': 140 | if not image_source.image_source_exists(): 141 | if module.check_mode: 142 | module.exit_json(changed=True) 143 | 144 | (rc, out, err) = image_source.add_image_source() 145 | if rc != 0: 146 | module.fail_json(msg='Failed to add %s' % image_source.url) 147 | 148 | if rc is None: 149 | result['changed'] = False 150 | else: 151 | result['changed'] = True 152 | 153 | if out: 154 | result['stdout'] = out 155 | if err: 156 | result['stderr'] = err 157 | 158 | module.exit_json(**result) 159 | 160 | 161 | from ansible.module_utils.basic import * 162 | main() 163 | -------------------------------------------------------------------------------- /library/zpool_facts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2016, Adam Števko 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | # 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: zpool_facts 25 | short_description: Gather facts about ZFS pools. 26 | version_added: "2.2" 27 | author: Adam Števko (@xen0l) 28 | options: 29 | name: 30 | description: 31 | - ZFS pool name. 32 | alias: [ "pool", "zpool" ] 33 | type: str 34 | required: false 35 | parsable: 36 | description: 37 | - Specifies if property values should be displayed in machine 38 | friendly format. 39 | type: bool 40 | default: False 41 | required: false 42 | properties: 43 | description: 44 | - Specifies which dataset properties should be queried in comma-separated format. 45 | For more information about dataset properties, check zpool(1M) man page. 46 | alias: [ "props" ] 47 | type: str 48 | default: all 49 | required: false 50 | ''' 51 | 52 | EXAMPLES = ''' 53 | # Gather facts about ZFS pool rpool 54 | zpool_facts: pool=rpool 55 | 56 | # Gather space usage about all imported ZFS pools 57 | zpool_facts: properties='free,size' 58 | debug: msg='ZFS pool {{ item.name }} has {{ item.free }} free space out of {{ item.size }}.' 59 | with_items: '{{ ansible_zfs_pools }}' 60 | ''' 61 | 62 | RETURN = ''' 63 | ''' 64 | 65 | import os 66 | from collections import defaultdict 67 | 68 | 69 | class ZPoolFacts(object): 70 | def __init__(self, module): 71 | 72 | self.module = module 73 | 74 | self.name = module.params['name'] 75 | self.parsable = module.params['parsable'] 76 | self.properties = module.params['properties'] 77 | 78 | self._pools = defaultdict(dict) 79 | self.facts = [] 80 | 81 | def pool_exists(self): 82 | cmd = [self.module.get_bin_path('zpool')] 83 | 84 | cmd.append('list') 85 | cmd.append(self.name) 86 | 87 | (rc, out, err) = self.module.run_command(cmd) 88 | 89 | if rc == 0: 90 | return True 91 | else: 92 | return False 93 | 94 | def get_facts(self): 95 | cmd = [self.module.get_bin_path('zpool')] 96 | 97 | cmd.append('get') 98 | cmd.append('-H') 99 | if self.parsable: 100 | cmd.append('-p') 101 | cmd.append('-o') 102 | cmd.append('name,property,value') 103 | cmd.append(self.properties) 104 | if self.name: 105 | cmd.append(self.name) 106 | 107 | (rc, out, err) = self.module.run_command(cmd) 108 | 109 | if rc == 0: 110 | for line in out.splitlines(): 111 | pool, property, value = line.split('\t') 112 | 113 | self._pools[pool].update({property: value}) 114 | 115 | for k, v in self._pools.iteritems(): 116 | v.update({'name': k}) 117 | self.facts.append(v) 118 | 119 | return {'ansible_zfs_pools': self.facts} 120 | else: 121 | self.module.fail_json(msg='Error while trying to get facts about ZFS pool: %s' % self.name, 122 | stderr=err, 123 | rc=rc) 124 | 125 | 126 | def main(): 127 | module = AnsibleModule( 128 | argument_spec=dict( 129 | name=dict(required=False, aliases=['pool', 'zpool'], type='str'), 130 | parsable=dict(required=False, default=False, type='bool'), 131 | properties=dict(required=False, default='all', type='str'), 132 | ), 133 | supports_check_mode=True 134 | ) 135 | 136 | zpool_facts = ZPoolFacts(module) 137 | 138 | result = {} 139 | result['changed'] = False 140 | result['name'] = zpool_facts.name 141 | 142 | if zpool_facts.parsable: 143 | result['parsable'] = zpool_facts.parsable 144 | 145 | if zpool_facts.name is not None: 146 | if zpool_facts.pool_exists(): 147 | result['ansible_facts'] = zpool_facts.get_facts() 148 | else: 149 | module.fail_json(msg='ZFS pool %s does not exist!' % zpool_facts.name) 150 | else: 151 | result['ansible_facts'] = zpool_facts.get_facts() 152 | 153 | module.exit_json(**result) 154 | 155 | 156 | from ansible.module_utils.basic import * 157 | if __name__ == '__main__': 158 | main() 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansible modules for managing Solarish systems 2 | 3 | This repository contains modules for managing certain aspects of Solarish systems. There are plans to bring these modules into [ansible-modules-extra](https://github.com/ansible/ansible-modules-extras) repository in the future. These modules are developed under [OpenIndiana](http://www.openindiana.org) or [SmartOS](http://www.smartos.org). Modules are also known to work on [Oracle Solaris](https://www.oracle.com/solaris/solaris11/index.html). 4 | 5 | So far, following modules have been developed: 6 | 7 | ## Solaris/illumos networking 8 | - ~~[dladm_etherstub](https://github.com/xen0l/ansible-illumos/blob/master/library/dladm_etherstub) - Manage etherstubs on Solaris/illumos systems.~~ (already upstreamed) 9 | - ~~[dladm_vnic](https://github.com/xen0l/ansible-illumos/blob/master/library/dladm_vnic) - Manage VNICs on Solaris/illumos systems.~~ (already upstreamed) 10 | - ~~[dladm_iptun](https://github.com/xen0l/ansible-illumos-modules/blob/master/library/dladm_iptun.py) - manage IP tunnel interfaces on Solaris/illumos systems.~~ (already upstreamed) 11 | - ~~[dladm_vlan](https://github.com/xen0l/ansible-illumos-modules/blob/master/library/dladm_vlan.py) - manage VLAN links on Solaris/illumos systems~~ (already upstreamed) 12 | - ~~[dladm_linkprop](https://github.com/xen0l/ansible-illumos-modules/blob/master/library/dladm_linkprop.py) - manage link properties on Solaris/illumos systems.~~ (already upstreamed) 13 | - ~~[ipadm_addr](https://github.com/xen0l/ansible-illumos-modules/blob/master/library/ipadm_addr.py) - manage IP addresses on Solaris/illumos systems.~~ (already upstreamed) 14 | - ~~[ipadm_addrprop](https://github.com/xen0l/ansible-illumos-modules/blob/master/library/ipadm_addrprop.py) - manage IP address properties on Solaris/illumos systems.~~ (already upstreamed) 15 | - ~~[ipadm_if](https://github.com/xen0l/ansible-illumos/blob/master/library/ipadm_if) - Manage IP interfaces on Solaris/illumos systems.~~ (already upstreamed) 16 | - ~~[ipadm_ifprop](https://github.com/xen0l/ansible-illumos-modules/blob/master/library/ipadm_ifprop.py) - manage interface properties on Solaris/illumos systems.~~ (already upstreamed) 17 | - ~~[ipadm_prop](https://github.com/xen0l/ansible-illumos/blob/master/library/ipadm_prop) - Manage protocol properties on Solaris/illumos systems.~~ (already upstreamed) 18 | - ~~[flowadm](https://github.com/xen0l/ansible-illumos-modules/blob/master/library/flowadm.py) - Manage bandwidth resource control and priority for protocols, services and zones.~~ (already upstreamed) 19 | 20 | ## Solaris/illumos processes 21 | - [solaris_project](https://github.com/xen0l/ansible-illumos/blob/master/library/solaris_project) - Manage Solaris/illumos projects 22 | 23 | ## ZFS datasets and pools 24 | - ~~[zfs_facts](https://github.com/xen0l/ansible-illumos/blob/master/library/zfs_facts.py) - Gather facts about ZFS datasets.~~ (already upstreamed) 25 | - ~~[zpool_facts](https://github.com/xen0l/ansible-illumos/blob/master/library/zpool_facts.py) - Gather facts about ZFS pools.~~ (already upstreamed) 26 | 27 | ## Boot Environments 28 | - ~~[beadm](https://github.com/xen0l/ansible-illumos-modules/blob/master/library/beadm.py) - manage ZFS boot environments on Solaris/illumos/FreeBSD systems.~~ (already upstreamed) 29 | 30 | ## SmartOS 31 | - ~~[smartos_image_facts](https://github.com/ansible/ansible-modules-extras/blob/devel/cloud/smartos/smartos_image_facts.py) - Get SmartOS image details.~~ (already upstreamed) 32 | - [smartos_image_source](https://github.com/xen0l/ansible-illumos/blob/master/library/smartos_image_source) - Manage image sources on SmartOS compute nodes. 33 | 34 | # How to install 35 | Download desired modules and place it under library/ directory in your ansible repository. 36 | 37 | # Future development and desired modules 38 | I plan to develop more modules. The list of following modules, which I will be working on in the near future: 39 | 40 | ## Solaris/illumos 41 | ### Networking 42 | - dladm_aggr - manage link aggregations on Solaris/illumos systems 43 | - dladm_bridge - manage bridges on Solaris/illumos systems 44 | - routeadm - manage IP forwarding and routing configuration on Solaris/illumos systems 45 | 46 | ### System 47 | - coreadm - core file administration 48 | - dumpadm - configure operating system crash dump 49 | - ldom_facts - gather facts about LDOMs 50 | - kstat_facts - query kstats for facts 51 | 52 | ### Storage 53 | - zpool - manage (Open)ZFS pools on Linux/FreeBSD/Solaris/illumos systems 54 | 55 | ### Logging 56 | - logadm - manage log rotations on Solaris/illumos systems 57 | 58 | ### SMF 59 | - smf_property - manage SMF properties on Solaris/illumos systems 60 | - smf_manifest - manage SMF manifests on Solaris/illumos systems 61 | - smf_instance - manage SMF instances on Solaris/illumos systems 62 | 63 | ### Boot Environments 64 | - beadm_facts - get facts about boot environments on Solaris/illumos/FreeBSD systems 65 | 66 | ### IPS 67 | - pkg5_mediator - manage IPS mediators on Solaris/illumos systems 68 | 69 | ### Other ideas 70 | I am also open to new ideas for modules and collaboration. 71 | 72 | ## SmartOS 73 | In the future, I might also develop modules for [SmartOS](http://www.smartos.org). However, I am not using it on a daily basis nowadays, so development might lag. 74 | 75 | ### Networking 76 | - nictagadm - manage nic tags on SmartOS systems 77 | 78 | -------------------------------------------------------------------------------- /library/dladm_vlan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2015, Adam Števko 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | # 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: dladm_vlan 25 | short_description: Manage VLAN interfaces on Solaris/illumos systems. 26 | description: 27 | - Create or delete VLAN interfaces on Solaris/illumos systems. 28 | version_added: "2.3" 29 | author: Adam Števko (@xen0l) 30 | options: 31 | name: 32 | description: 33 | - VLAN interface name. 34 | required: true 35 | link: 36 | description: 37 | - VLAN underlying link name. 38 | required: true 39 | temporary: 40 | description: 41 | - Specifies that the VLAN interface is temporary. Temporary VLANs 42 | do not persist across reboots. 43 | required: false 44 | default: false 45 | vlan_id: 46 | description: 47 | - VLAN ID value for VLAN interface. 48 | required: false 49 | default: false 50 | aliases: [ "vid" ] 51 | state: 52 | description: 53 | - Create or delete Solaris/illumos VNIC. 54 | required: false 55 | default: "present" 56 | choices: [ "present", "absent" ] 57 | ''' 58 | 59 | EXAMPLES = ''' 60 | # Create 'vlan42' VLAN over 'bnx0' link 61 | dladm_vlan: name=vlan42 link=bnx0 vlan_id=42 state=present 62 | 63 | # Remove 'vlan1337' VLAN interface 64 | dladm_vlan: name=vlan1337 state=absent 65 | ''' 66 | 67 | 68 | class VLAN(object): 69 | 70 | def __init__(self, module): 71 | self.module = module 72 | 73 | self.name = module.params['name'] 74 | self.link = module.params['link'] 75 | self.vlan_id = module.params['vlan_id'] 76 | self.temporary = module.params['temporary'] 77 | self.state = module.params['state'] 78 | 79 | def vlan_exists(self): 80 | cmd = [self.module.get_bin_path('dladm', True)] 81 | 82 | cmd.append('show-vlan') 83 | cmd.append(self.name) 84 | 85 | (rc, _, _) = self.module.run_command(cmd) 86 | 87 | if rc == 0: 88 | return True 89 | else: 90 | return False 91 | 92 | def create_vlan(self): 93 | cmd = [self.module.get_bin_path('dladm', True)] 94 | 95 | cmd.append('create-vlan') 96 | 97 | if self.temporary: 98 | cmd.append('-t') 99 | 100 | cmd.append('-l') 101 | cmd.append(self.link) 102 | cmd.append('-v') 103 | cmd.append(self.vlan_id) 104 | cmd.append(self.name) 105 | 106 | return self.module.run_command(cmd) 107 | 108 | def delete_vlan(self): 109 | cmd = [self.module.get_bin_path('dladm', True)] 110 | 111 | cmd.append('delete-vlan') 112 | 113 | if self.temporary: 114 | cmd.append('-t') 115 | cmd.append(self.name) 116 | 117 | return self.module.run_command(cmd) 118 | 119 | def is_valid_vlan_id(self): 120 | 121 | return 0 <= int(self.vlan_id) <= 4095 122 | 123 | 124 | def main(): 125 | module = AnsibleModule( 126 | argument_spec=dict( 127 | name=dict(required=True, type='str'), 128 | link=dict(default=None, type='str'), 129 | vlan_id=dict(default=0, aliases=['vid']), 130 | temporary=dict(default=False, type='bool'), 131 | state=dict(default='present', choices=['absent', 'present']), 132 | ), 133 | required_if=[ 134 | ['state', 'present', ['vlan_id', 'link', 'name']], 135 | ], 136 | supports_check_mode=True 137 | ) 138 | 139 | vlan = VLAN(module) 140 | 141 | rc = None 142 | out = '' 143 | err = '' 144 | result = {} 145 | result['name'] = vlan.name 146 | result['link'] = vlan.link 147 | result['state'] = vlan.state 148 | result['temporary'] = vlan.temporary 149 | 150 | if int(vlan.vlan_id) != 0: 151 | if not vlan.is_valid_vlan_id(): 152 | module.fail_json(msg='Invalid VLAN id value', 153 | name=vlan.name, 154 | state=vlan.state, 155 | link=vlan.link, 156 | vlan_id=vlan.vlan_id) 157 | result['vlan_id'] = vlan.vlan_id 158 | 159 | if vlan.state == 'absent': 160 | if vlan.vlan_exists(): 161 | if module.check_mode: 162 | module.exit_json(changed=True) 163 | (rc, out, err) = vlan.delete_vlan() 164 | if rc != 0: 165 | module.fail_json(name=vlan.name, msg=err, rc=rc) 166 | elif vlan.state == 'present': 167 | if not vlan.vlan_exists(): 168 | if module.check_mode: 169 | module.exit_json(changed=True) 170 | (rc, out, err) = vlan.create_vlan() 171 | 172 | if rc is not None and rc != 0: 173 | module.fail_json(name=vlan.name, msg=err, rc=rc) 174 | 175 | if rc is None: 176 | result['changed'] = False 177 | else: 178 | result['changed'] = True 179 | 180 | if out: 181 | result['stdout'] = out 182 | if err: 183 | result['stderr'] = err 184 | 185 | module.exit_json(**result) 186 | 187 | from ansible.module_utils.basic import * 188 | main() 189 | -------------------------------------------------------------------------------- /library/ipadm_if: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2015, Adam Števko 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | # 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: ipadm_if 25 | short_description: Manage IP interfaces on Solaris/illumos systems. 26 | description: 27 | - Create, delete, enable or disable IP interfaces on Solaris/illumos 28 | systems. 29 | version_added: "2.1" 30 | author: Adam Števko (@xen0l) 31 | options: 32 | name: 33 | description: 34 | - IP interface name. 35 | required: true 36 | temporary: 37 | description: 38 | - Specifies that the IP interface is temporary. Temporary IP 39 | interfaces do not persist across reboots. 40 | required: false 41 | default: false 42 | state: 43 | description: 44 | - Create or delete Solaris/illumos IP interfaces. 45 | required: false 46 | default: "present" 47 | choices: [ "present", "absent", "enabled", "disabled" ] 48 | ''' 49 | 50 | EXAMPLES = ''' 51 | # Create vnic0 interface 52 | ipadm_if: name=vnic0 state=enabled 53 | 54 | # Disable vnic0 interface 55 | ipadm_if: name=vnic0 state=disabled 56 | ''' 57 | 58 | 59 | class IPInterface(object): 60 | 61 | def __init__(self, module): 62 | self.module = module 63 | 64 | self.name = module.params['name'] 65 | self.temporary = module.params['temporary'] 66 | self.state = module.params['state'] 67 | 68 | def interface_exists(self): 69 | cmd = [self.module.get_bin_path('ipadm', True)] 70 | 71 | cmd.append('show-if') 72 | cmd.append(self.name) 73 | 74 | (rc, _, _) = self.module.run_command(cmd) 75 | if rc == 0: 76 | return True 77 | else: 78 | return False 79 | 80 | def interface_is_disabled(self): 81 | cmd = [self.module.get_bin_path('ipadm', True)] 82 | 83 | cmd.append('show-if') 84 | cmd.append('-o') 85 | cmd.append('state') 86 | cmd.append(self.name) 87 | 88 | (rc, out, err) = self.module.run_command(cmd) 89 | if rc != 0: 90 | self.module.fail_json(name=self.name, rc=rc, msg=err) 91 | 92 | return 'disabled' in out 93 | 94 | def create_interface(self): 95 | cmd = [self.module.get_bin_path('ipadm', True)] 96 | 97 | cmd.append('create-if') 98 | 99 | if self.temporary: 100 | cmd.append('-t') 101 | 102 | cmd.append(self.name) 103 | 104 | return self.module.run_command(cmd) 105 | 106 | def delete_interface(self): 107 | cmd = [self.module.get_bin_path('ipadm', True)] 108 | 109 | cmd.append('delete-if') 110 | 111 | if self.temporary: 112 | cmd.append('-t') 113 | 114 | cmd.append(self.name) 115 | 116 | return self.module.run_command(cmd) 117 | 118 | def enable_interface(self): 119 | cmd = [self.module.get_bin_path('ipadm', True)] 120 | 121 | cmd.append('enable-if') 122 | cmd.append('-t') 123 | cmd.append(self.name) 124 | 125 | return self.module.run_command(cmd) 126 | 127 | def disable_interface(self): 128 | cmd = [self.module.get_bin_path('ipadm', True)] 129 | 130 | cmd.append('disable-if') 131 | cmd.append('-t') 132 | cmd.append(self.name) 133 | 134 | return self.module.run_command(cmd) 135 | 136 | 137 | def main(): 138 | module = AnsibleModule( 139 | argument_spec=dict( 140 | name=dict(required=True), 141 | temporary=dict(default=False, type='bool'), 142 | state=dict(default='present', choices=['absent', 143 | 'present', 144 | 'enabled', 145 | 'disabled']), 146 | ), 147 | supports_check_mode=True 148 | ) 149 | 150 | interface = IPInterface(module) 151 | 152 | rc = None 153 | out = '' 154 | err = '' 155 | result = {} 156 | result['name'] = interface.name 157 | result['state'] = interface.state 158 | result['temporary'] = interface.temporary 159 | 160 | if interface.state == 'absent': 161 | if interface.interface_exists(): 162 | if module.check_mode: 163 | module.exit_json(changed=True) 164 | (rc, out, err) = interface.delete_interface() 165 | if rc != 0: 166 | module.fail_json(name=interface.name, msg=err, rc=rc) 167 | elif interface.state == 'present': 168 | if not interface.interface_exists(): 169 | if module.check_mode: 170 | module.exit_json(changed=True) 171 | (rc, out, err) = interface.create_interface() 172 | 173 | if rc is not None and rc != 0: 174 | module.fail_json(name=interface.name, msg=err, rc=rc) 175 | 176 | elif interface.state == 'enabled': 177 | if interface.interface_is_disabled(): 178 | (rc, out, err) = interface.enable_interface() 179 | 180 | if rc is not None and rc != 0: 181 | module.fail_json(name=interface.name, msg=err, rc=rc) 182 | 183 | elif interface.state == 'disabled': 184 | if not interface.interface_is_disabled(): 185 | (rc, out, err) = interface.disable_interface() 186 | 187 | if rc is not None and rc != 0: 188 | module.fail_json(name=interface.name, msg=err, rc=rc) 189 | 190 | if rc is None: 191 | result['changed'] = False 192 | else: 193 | result['changed'] = True 194 | 195 | if out: 196 | result['stdout'] = out 197 | if err: 198 | result['stderr'] = err 199 | 200 | module.exit_json(**result) 201 | 202 | from ansible.module_utils.basic import * 203 | main() 204 | -------------------------------------------------------------------------------- /library/zfs_facts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2016, Adam Števko 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | # 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: zfs_facts 25 | short_description: Gather facts about ZFS datasets. 26 | version_added: "2.2" 27 | author: Adam Števko (@xen0l) 28 | options: 29 | name: 30 | description: 31 | - ZFS dataset name. 32 | alias: [ "ds", "dataset" ] 33 | type: str 34 | required: yes 35 | recurse: 36 | description: 37 | - Specifies if properties for any children should be recursively 38 | display. 39 | type: bool 40 | default: False 41 | required: false 42 | parsable: 43 | description: 44 | - Specifies if property values should be displayed in machine 45 | friendly format. 46 | type: bool 47 | default: False 48 | required: false 49 | properties: 50 | description: 51 | - Specifies which dataset properties should be queried in comma-separated format. 52 | For more information about dataset properties, check zfs(1M) man page. 53 | alias: [ "props" ] 54 | type: str 55 | default: all 56 | required: false 57 | type: 58 | description: 59 | - Specifies which datasets types to display. Multiple values have to be 60 | provided in comma-separated form. 61 | alias: [ "props" ] 62 | type: str 63 | default: all 64 | choices: [ 'all', 'filesystem', 'volume', 'snapshot', 'bookmark' ] 65 | required: false 66 | depth: 67 | description: 68 | - Specifiies recurion depth. 69 | type: int 70 | default: None 71 | required: false 72 | ''' 73 | 74 | EXAMPLES = ''' 75 | # Gather facts about ZFS dataset rpool/export/home 76 | zfs_facts: dataset=rpool/export/home 77 | 78 | # Report space usage on ZFS filesystems under data/home 79 | zfs_facts: name=data/home recurse=yes type=filesystem 80 | debug: msg='ZFS dataset {{ item.name }} consumes {{ item.used }} of disk space.' 81 | with_items: '{{ ansible_zfs_datasets }} 82 | ''' 83 | 84 | RETURN = ''' 85 | ''' 86 | 87 | import os 88 | from collections import defaultdict 89 | 90 | 91 | SUPPORTED_TYPES = ['all', 'filesystem', 'volume', 'snapshot', 'bookmark'] 92 | 93 | 94 | class ZFSFacts(object): 95 | def __init__(self, module): 96 | 97 | self.module = module 98 | 99 | self.name = module.params['name'] 100 | self.recurse = module.params['recurse'] 101 | self.parsable = module.params['parsable'] 102 | self.properties = module.params['properties'] 103 | self.type = module.params['type'] 104 | self.depth = module.params['depth'] 105 | 106 | self._datasets = defaultdict(dict) 107 | self.facts = [] 108 | 109 | def dataset_exists(self): 110 | cmd = [self.module.get_bin_path('zfs')] 111 | 112 | cmd.append('list') 113 | cmd.append(self.name) 114 | 115 | (rc, out, err) = self.module.run_command(cmd) 116 | 117 | if rc == 0: 118 | return True 119 | else: 120 | return False 121 | 122 | def get_facts(self): 123 | cmd = [self.module.get_bin_path('zfs')] 124 | 125 | cmd.append('get') 126 | cmd.append('-H') 127 | if self.parsable: 128 | cmd.append('-p') 129 | if self.recurse: 130 | cmd.append('-r') 131 | if int(self.depth) != 0: 132 | cmd.append('-d') 133 | cmd.append('%s' % self.depth) 134 | if self.type: 135 | cmd.append('-t') 136 | cmd.append(self.type) 137 | cmd.append('-o') 138 | cmd.append('name,property,value') 139 | cmd.append(self.properties) 140 | cmd.append(self.name) 141 | 142 | (rc, out, err) = self.module.run_command(cmd) 143 | 144 | if rc == 0: 145 | for line in out.splitlines(): 146 | dataset, property, value = line.split('\t') 147 | 148 | self._datasets[dataset].update({property: value}) 149 | 150 | for k, v in self._datasets.iteritems(): 151 | v.update({'name': k}) 152 | self.facts.append(v) 153 | 154 | return {'ansible_zfs_datasets': self.facts} 155 | else: 156 | self.module.fail_json(msg='Error while trying to get facts about ZFS dataset: %s' % self.name, 157 | stderr=err, 158 | rc=rc) 159 | 160 | 161 | def main(): 162 | module = AnsibleModule( 163 | argument_spec=dict( 164 | name=dict(required=True, aliases=['ds', 'dataset'], type='str'), 165 | recurse=dict(required=False, default=False, type='bool'), 166 | parsable=dict(required=False, default=False, type='bool'), 167 | properties=dict(required=False, default='all', type='str'), 168 | type=dict(required=False, default='all', type='str', choices=SUPPORTED_TYPES), 169 | depth=dict(required=False, default=0, type='int') 170 | ), 171 | supports_check_mode=True 172 | ) 173 | 174 | zfs_facts = ZFSFacts(module) 175 | 176 | result = {} 177 | result['changed'] = False 178 | result['name'] = zfs_facts.name 179 | 180 | if zfs_facts.parsable: 181 | result['parsable'] = zfs_facts.parsable 182 | 183 | if zfs_facts.recurse: 184 | result['recurse'] = zfs_facts.recurse 185 | 186 | if zfs_facts.dataset_exists(): 187 | result['ansible_facts'] = zfs_facts.get_facts() 188 | else: 189 | module.fail_json(msg='ZFS dataset %s does not exist!' % zfs_facts.name) 190 | 191 | module.exit_json(**result) 192 | 193 | 194 | from ansible.module_utils.basic import * 195 | if __name__ == '__main__': 196 | main() 197 | -------------------------------------------------------------------------------- /library/dladm_vnic: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2015, Adam Števko 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | # 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: dladm_vnic 25 | short_description: Manage VNICs on Solaris/illumos systems. 26 | description: 27 | - Create or delete VNICs on Solaris/illumos systems. 28 | version_added: "2.1" 29 | author: Adam Števko (@xen0l) 30 | options: 31 | name: 32 | description: 33 | - VNIC name. 34 | required: true 35 | link: 36 | description: 37 | - VNIC underlying link name. 38 | required: true 39 | temporary: 40 | - Specifies that the VNIC is temporary. Temporary VNICs 41 | do not persist across reboots. 42 | required: false 43 | default: false 44 | mac: 45 | description: 46 | - Sets the VNIC's MAC address. Must be valid unicast MAC address. 47 | required: false 48 | default: false 49 | aliases: [ "macaddr" ] 50 | vlan: 51 | description: 52 | - Enable VLAN tagging for this VNIC. The VLAN tag will have id 53 | I(vlan). 54 | required: false 55 | default: false 56 | aliases: [ "vlan_id" ] 57 | state: 58 | description: 59 | - Create or delete Solaris/illumos VNIC. 60 | required: false 61 | default: "present" 62 | choices: [ "present", "absent" ] 63 | ''' 64 | 65 | EXAMPLES = ''' 66 | # Create 'vnic0' VNIC over 'bnx0' link 67 | dladm_vnic: name=vnic0 link=bnx0 state=present 68 | 69 | # Create VNIC with specified MAC and VLAN tag over 'aggr0' 70 | dladm_vnic: name=vnic1 link=aggr0 mac=2:33:af:12:ab:cd vlan=4 71 | 72 | # Remove 'vnic0' VNIC 73 | dladm_vnic: name=vnic0 link=bnx0 state=absent 74 | ''' 75 | 76 | import re 77 | 78 | 79 | class VNIC(object): 80 | 81 | UNICAST_MAC_REGEX = r'^[a-f0-9][2-9a-f0]:([a-f0-9]{2}:){4}[a-f0-9]{2}$' 82 | 83 | def __init__(self, module): 84 | self.module = module 85 | 86 | self.name = module.params['name'] 87 | self.link = module.params['link'] 88 | self.mac = module.params['mac'] 89 | self.vlan = module.params['vlan'] 90 | self.temporary = module.params['temporary'] 91 | self.state = module.params['state'] 92 | 93 | def vnic_exists(self): 94 | cmd = [self.module.get_bin_path('dladm', True)] 95 | 96 | cmd.append('show-vnic') 97 | cmd.append(self.name) 98 | 99 | (rc, _, _) = self.module.run_command(cmd) 100 | 101 | if rc == 0: 102 | return True 103 | else: 104 | return False 105 | 106 | def create_vnic(self): 107 | cmd = [self.module.get_bin_path('dladm', True)] 108 | 109 | cmd.append('create-vnic') 110 | 111 | if self.temporary: 112 | cmd.append('-t') 113 | 114 | if self.mac: 115 | cmd.append('-m') 116 | cmd.append(self.mac) 117 | 118 | if self.vlan: 119 | cmd.append('-v') 120 | cmd.append(self.vlan) 121 | 122 | cmd.append('-l') 123 | cmd.append(self.link) 124 | cmd.append(self.name) 125 | 126 | return self.module.run_command(cmd) 127 | 128 | def delete_vnic(self): 129 | cmd = [self.module.get_bin_path('dladm', True)] 130 | 131 | cmd.append('delete-vnic') 132 | 133 | if self.temporary: 134 | cmd.append('-t') 135 | cmd.append(self.name) 136 | 137 | return self.module.run_command(cmd) 138 | 139 | def is_valid_unicast_mac(self): 140 | 141 | mac_re = re.match(self.UNICAST_MAC_REGEX, self.mac) 142 | 143 | return mac_re is None 144 | 145 | def is_valid_vlan_id(self): 146 | 147 | return 0 <= self.vlan <= 4095 148 | 149 | 150 | def main(): 151 | module = AnsibleModule( 152 | argument_spec=dict( 153 | name=dict(required=True), 154 | link=dict(required=True), 155 | mac=dict(default=None, aliases=['macaddr']), 156 | vlan=dict(default=None, aliases=['vlan_id']), 157 | temporary=dict(default=False, type='bool'), 158 | state=dict(default='present', choices=['absent', 'present']), 159 | ), 160 | supports_check_mode=True 161 | ) 162 | 163 | vnic = VNIC(module) 164 | 165 | rc = None 166 | out = '' 167 | err = '' 168 | result = {} 169 | result['name'] = vnic.name 170 | result['link'] = vnic.link 171 | result['state'] = vnic.state 172 | result['temporary'] = vnic.temporary 173 | 174 | if vnic.mac is not None: 175 | if vnic.is_valid_unicast_mac(): 176 | module.fail_json(msg='Invalid unicast MAC address', 177 | mac=vnic.mac, 178 | name=vnic.name, 179 | state=vnic.state, 180 | link=vnic.link, 181 | vlan=vnic.vlan) 182 | result['mac'] = vnic.mac 183 | 184 | if vnic.vlan is not None: 185 | if vnic.is_valid_vlan_id(): 186 | module.fail_json(msg='Invalid VLAN tag', 187 | mac=vnic.mac, 188 | name=vnic.name, 189 | state=vnic.state, 190 | link=vnic.link, 191 | vlan=vnic.vlan) 192 | result['vlan'] = vnic.vlan 193 | 194 | if vnic.state == 'absent': 195 | if vnic.vnic_exists(): 196 | if module.check_mode: 197 | module.exit_json(changed=True) 198 | (rc, out, err) = vnic.delete_vnic() 199 | if rc != 0: 200 | module.fail_json(name=vnic.name, msg=err, rc=rc) 201 | elif vnic.state == 'present': 202 | if not vnic.vnic_exists(): 203 | if module.check_mode: 204 | module.exit_json(changed=True) 205 | (rc, out, err) = vnic.create_vnic() 206 | 207 | if rc is not None and rc != 0: 208 | module.fail_json(name=vnic.name, msg=err, rc=rc) 209 | 210 | if rc is None: 211 | result['changed'] = False 212 | else: 213 | result['changed'] = True 214 | 215 | if out: 216 | result['stdout'] = out 217 | if err: 218 | result['stderr'] = err 219 | 220 | module.exit_json(**result) 221 | 222 | from ansible.module_utils.basic import * 223 | main() 224 | -------------------------------------------------------------------------------- /library/ipadm_prop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2015, Adam Števko 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | # 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: ipadm_prop 25 | short_description: Manage protocol properties on Solaris/illumos systems. 26 | description: 27 | - Modify protocol properties on Solaris/illumos systems. 28 | version_added: "2.1" 29 | author: Adam Števko (@xen0l) 30 | options: 31 | protocol: 32 | description: 33 | - Specifies the procotol for which we want to mange properties. 34 | required: true 35 | property: 36 | description: 37 | - Specifies the name of property we want to manage. 38 | required: true 39 | value: 40 | description: 41 | - Specifies the value we want to set for the property. 42 | required: false 43 | temporary: 44 | description: 45 | - Specifies that the property value is temporary. Temporary 46 | property values do not persist across reboots. 47 | required: false 48 | default: false 49 | state: 50 | description: 51 | - Set or reset the property value. 52 | required: false 53 | default: present 54 | choices: [ "present", "absent", "reset" ] 55 | ''' 56 | 57 | EXAMPLES = ''' 58 | # Set TCP receive buffer size 59 | ipadm_prop: protocol=tcp property=recv_buf value=65536 60 | 61 | # Reset UDP send buffer size to the default value 62 | ipadm_prop: protocol=udp property=send_buf state=reset 63 | ''' 64 | 65 | SUPPORTED_PROTOCOLS = ['ipv4', 'ipv6', 'icmp', 'tcp', 'udp', 'sctp'] 66 | 67 | 68 | class Prop(object): 69 | 70 | def __init__(self, module): 71 | self.module = module 72 | 73 | self.protocol = module.params['protocol'] 74 | self.property = module.params['property'] 75 | self.value = module.params['value'] 76 | self.temporary = module.params['temporary'] 77 | self.state = module.params['state'] 78 | 79 | def property_exists(self): 80 | cmd = [self.module.get_bin_path('ipadm')] 81 | 82 | cmd.append('show-prop') 83 | cmd.append('-p') 84 | cmd.append(self.property) 85 | cmd.append(self.protocol) 86 | 87 | (rc, _, _) = self.module.run_command(cmd) 88 | 89 | if rc == 0: 90 | return True 91 | else: 92 | self.module.fail_json(msg='Unknown property "%s" for protocol %s' % 93 | (self.property, self.protocol), 94 | protocol=self.protocol, 95 | property=self.property) 96 | 97 | def property_is_modified(self): 98 | cmd = [self.module.get_bin_path('ipadm')] 99 | 100 | cmd.append('show-prop') 101 | cmd.append('-c') 102 | cmd.append('-o') 103 | cmd.append('current,default') 104 | cmd.append('-p') 105 | cmd.append(self.property) 106 | cmd.append(self.protocol) 107 | 108 | (rc, out, _) = self.module.run_command(cmd) 109 | 110 | out = out.rstrip() 111 | (value, default) = out.split(':') 112 | 113 | if rc == 0 and value == default: 114 | return True 115 | else: 116 | return False 117 | 118 | def property_is_set(self): 119 | cmd = [self.module.get_bin_path('ipadm')] 120 | 121 | cmd.append('show-prop') 122 | cmd.append('-c') 123 | cmd.append('-o') 124 | cmd.append('current') 125 | cmd.append('-p') 126 | cmd.append(self.property) 127 | cmd.append(self.protocol) 128 | 129 | (rc, out, _) = self.module.run_command(cmd) 130 | 131 | out = out.rstrip() 132 | 133 | if rc == 0 and self.value == out: 134 | return True 135 | else: 136 | return False 137 | 138 | def set_property(self): 139 | cmd = [self.module.get_bin_path('ipadm')] 140 | 141 | cmd.append('set-prop') 142 | 143 | if self.temporary: 144 | cmd.append('-t') 145 | 146 | cmd.append('-p') 147 | cmd.append(self.property + "=" + self.value) 148 | cmd.append(self.protocol) 149 | 150 | return self.module.run_command(cmd) 151 | 152 | def reset_property(self): 153 | cmd = [self.module.get_bin_path('ipadm')] 154 | 155 | cmd.append('reset-prop') 156 | 157 | if self.temporary: 158 | cmd.append('-t') 159 | 160 | cmd.append('-p') 161 | cmd.append(self.property) 162 | cmd.append(self.protocol) 163 | 164 | return self.module.run_command(cmd) 165 | 166 | 167 | def main(): 168 | module = AnsibleModule( 169 | argument_spec=dict( 170 | protocol=dict(required=True, choices=SUPPORTED_PROTOCOLS), 171 | property=dict(required=True), 172 | value=dict(required=False), 173 | temporary=dict(default=False, type='bool'), 174 | state=dict( 175 | default='present', choices=['absent', 'present', 'reset']), 176 | ), 177 | supports_check_mode=True 178 | ) 179 | 180 | prop = Prop(module) 181 | 182 | rc = None 183 | out = '' 184 | err = '' 185 | result = {} 186 | result['protocol'] = prop.protocol 187 | result['property'] = prop.property 188 | result['state'] = prop.state 189 | if prop.value: 190 | result['value'] = prop.value 191 | 192 | if prop.state == 'absent' or prop.state == 'reset': 193 | if prop.property_exists(): 194 | if not prop.property_is_modified(): 195 | if module.check_mode: 196 | module.exit_json(changed=True) 197 | (rc, out, err) = prop.reset_property() 198 | if rc != 0: 199 | module.fail_json(protocol=prop.protocol, 200 | property=prop.property, 201 | msg=err, 202 | rc=rc) 203 | 204 | elif prop.state == 'present': 205 | if prop.value is None: 206 | module.fail_json(msg='Value is mandatory with state "present"') 207 | 208 | if prop.property_exists(): 209 | if not prop.property_is_set(): 210 | if module.check_mode: 211 | module.exit_json(changed=True) 212 | 213 | (rc, out, err) = prop.set_property() 214 | if rc != 0: 215 | module.fail_json(protocol=prop.protocol, 216 | property=prop.property, 217 | msg=err, 218 | rc=rc) 219 | 220 | if rc is None: 221 | result['changed'] = False 222 | else: 223 | result['changed'] = True 224 | 225 | if out: 226 | result['stdout'] = out 227 | if err: 228 | result['stderr'] = err 229 | 230 | module.exit_json(**result) 231 | 232 | 233 | from ansible.module_utils.basic import * 234 | main() 235 | -------------------------------------------------------------------------------- /library/ipadm_addrprop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2015, Adam Števko 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | # 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: ipadm_addrprop 25 | short_description: Manage IP address properties on Solaris/illumos systems. 26 | description: 27 | - Modify IP address properties on Solaris/illumos systems. 28 | version_added: "2.3" 29 | author: Adam Števko (@xen0l) 30 | options: 31 | addrobj: 32 | description: 33 | - Specifies the address object we want to manage. 34 | required: true 35 | aliases: [nic, interface] 36 | property: 37 | description: 38 | - Specifies the name of the address property we want to manage. 39 | required: true 40 | aliases: [name] 41 | value: 42 | description: 43 | - Specifies the value we want to set for the address property. 44 | required: false 45 | temporary: 46 | description: 47 | - Specifies that the address property value is temporary. 48 | Temporary values do not persist across reboots. 49 | required: false 50 | default: false 51 | state: 52 | description: 53 | - Set or reset the property value. 54 | required: false 55 | default: present 56 | choices: [ "present", "absent", "reset" ] 57 | ''' 58 | 59 | EXAMPLES = ''' 60 | # Mark address on addrobj as deprecated 61 | ipadm_addrprop: property=deprecated value=on addrobj=e1000g0/v6 62 | 63 | # Set network prefix length for addrobj 64 | ipadm_addrprop: addrobj=bge0/v4 name=prefixlen value=26 65 | ''' 66 | 67 | RETURN = ''' 68 | ''' 69 | 70 | 71 | class AddrProp(object): 72 | 73 | def __init__(self, module): 74 | self.module = module 75 | 76 | self.addrobj = module.params['addrobj'] 77 | self.property = module.params['property'] 78 | self.value = module.params['value'] 79 | self.temporary = module.params['temporary'] 80 | self.state = module.params['state'] 81 | 82 | def property_exists(self): 83 | cmd = [self.module.get_bin_path('ipadm')] 84 | 85 | cmd.append('show-addrprop') 86 | cmd.append('-p') 87 | cmd.append(self.property) 88 | cmd.append(self.addrobj) 89 | 90 | (rc, _, _) = self.module.run_command(cmd) 91 | 92 | if rc == 0: 93 | return True 94 | else: 95 | self.module.fail_json(msg='Unknown property "%s" on addrobj %s' % 96 | (self.property, self.addrobj), 97 | property=self.property, 98 | addrobj=self.addrobj) 99 | 100 | def property_is_modified(self): 101 | cmd = [self.module.get_bin_path('ipadm')] 102 | 103 | cmd.append('show-addrprop') 104 | cmd.append('-c') 105 | cmd.append('-o') 106 | cmd.append('current,default') 107 | cmd.append('-p') 108 | cmd.append(self.property) 109 | cmd.append(self.addrobj) 110 | 111 | (rc, out, _) = self.module.run_command(cmd) 112 | 113 | out = out.rstrip() 114 | (value, default) = out.split(':') 115 | 116 | if rc == 0 and value == default: 117 | return True 118 | else: 119 | return False 120 | 121 | def property_is_set(self): 122 | cmd = [self.module.get_bin_path('ipadm')] 123 | 124 | cmd.append('show-addrprop') 125 | cmd.append('-c') 126 | cmd.append('-o') 127 | cmd.append('current') 128 | cmd.append('-p') 129 | cmd.append(self.property) 130 | cmd.append(self.addrobj) 131 | 132 | (rc, out, _) = self.module.run_command(cmd) 133 | 134 | out = out.rstrip() 135 | 136 | if rc == 0 and self.value == out: 137 | return True 138 | else: 139 | return False 140 | 141 | def set_property(self): 142 | cmd = [self.module.get_bin_path('ipadm')] 143 | 144 | cmd.append('set-addrprop') 145 | 146 | if self.temporary: 147 | cmd.append('-t') 148 | 149 | cmd.append('-p') 150 | cmd.append(self.property + '=' + self.value) 151 | cmd.append(self.addrobj) 152 | 153 | return self.module.run_command(cmd) 154 | 155 | def reset_property(self): 156 | cmd = [self.module.get_bin_path('ipadm')] 157 | 158 | cmd.append('reset-addrprop') 159 | 160 | if self.temporary: 161 | cmd.append('-t') 162 | 163 | cmd.append('-p') 164 | cmd.append(self.property) 165 | cmd.append(self.addrobj) 166 | 167 | return self.module.run_command(cmd) 168 | 169 | 170 | def main(): 171 | module = AnsibleModule( 172 | argument_spec=dict( 173 | addrobj=dict(required=True, default=None, aliases=['nic, interface']), 174 | property=dict(required=True, aliases=['name']), 175 | value=dict(required=False), 176 | temporary=dict(default=False, type='bool'), 177 | state=dict( 178 | default='present', choices=['absent', 'present', 'reset']), 179 | ), 180 | supports_check_mode=True 181 | ) 182 | 183 | addrprop = AddrProp(module) 184 | 185 | rc = None 186 | out = '' 187 | err = '' 188 | result = {} 189 | result['property'] = addrprop.property 190 | result['addrobj'] = addrprop.addrobj 191 | result['state'] = addrprop.state 192 | if addrprop.value: 193 | result['value'] = addrprop.value 194 | 195 | if addrprop.state == 'absent' or addrprop.state == 'reset': 196 | if addrprop.property_exists(): 197 | if not addrprop.property_is_modified(): 198 | if module.check_mode: 199 | module.exit_json(changed=True) 200 | (rc, out, err) = addrprop.reset_property() 201 | if rc != 0: 202 | module.fail_json(property=addrprop.property, 203 | addrobj=addrprop.addrobj, 204 | msg=err, 205 | rc=rc) 206 | 207 | elif addrprop.state == 'present': 208 | if addrprop.value is None: 209 | module.fail_json(msg='Value is mandatory with state "present"') 210 | 211 | if addrprop.property_exists(): 212 | if not addrprop.property_is_set(): 213 | if module.check_mode: 214 | module.exit_json(changed=True) 215 | 216 | (rc, out, err) = addrprop.set_property() 217 | if rc != 0: 218 | module.fail_json(property=addrprop.property, 219 | addrobj=addrprop.addrobj, 220 | msg=err, 221 | rc=rc) 222 | 223 | if rc is None: 224 | result['changed'] = False 225 | else: 226 | result['changed'] = True 227 | 228 | if out: 229 | result['stdout'] = out 230 | if err: 231 | result['stderr'] = err 232 | 233 | module.exit_json(**result) 234 | 235 | 236 | from ansible.module_utils.basic import * 237 | main() 238 | -------------------------------------------------------------------------------- /library/dladm_iptun.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2016, Adam Števko 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | # 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: dladm_iptun 25 | short_description: Manage IP tunnel interfaces on Solaris/illumos systems. 26 | description: 27 | - Manage IP tunnel interfaces on Solaris/illumos systems. 28 | version_added: "2.3" 29 | author: Adam Števko (@xen0l) 30 | options: 31 | name: 32 | description: 33 | - IP tunnel interface name. 34 | required: true 35 | aliases: [ "tunnel", "link" ] 36 | temporary: 37 | description: 38 | - Specifies that the IP tunnel interface is temporary. Temporary IP tunnel 39 | interfaces do not persist across reboots. 40 | required: false 41 | default: false 42 | type: 43 | description: 44 | - Specifies the type of tunnel to be created. 45 | required: false 46 | default: "ipv4" 47 | choices: [ "ipv4", "ipv6", "6to4" ] 48 | local_address: 49 | description: 50 | - Literat IP address or hostname corresponding to the tunnel source. 51 | required: false 52 | aliases: [ "local" ] 53 | remote_address: 54 | description: 55 | - Literal IP address or hostname corresponding to the tunnel destination. 56 | required: false 57 | aliases: [ "remote" ] 58 | state: 59 | description: 60 | - Create or delete Solaris/illumos VNIC. 61 | required: false 62 | default: "present" 63 | choices: [ "present", "absent" ] 64 | ''' 65 | 66 | EXAMPLES = ''' 67 | # Create IPv4 tunnel interface 'iptun0' 68 | dladm_iptun: name=iptun0 local_address=192.0.2.23 remote_address=203.0.113.10 state=present 69 | 70 | # Change IPv4 tunnel remote address 71 | dladm_iptun: name=iptun0 type=ipv4 local_address=192.0.2.23 remote_address=203.0.113.11 72 | 73 | # Create IPv6 tunnel interface 'tun0' 74 | dladm_iptun: name=tun0 type=ipv6 local_address=192.0.2.23 remote_address=203.0.113.42 75 | 76 | # Remove 'iptun0' tunnel interface 77 | dladm_iptun: name=iptun0 state=absent 78 | ''' 79 | 80 | SUPPORTED_TYPES = ['ipv4', 'ipv6', '6to4'] 81 | 82 | 83 | class IPTun(object): 84 | 85 | def __init__(self, module): 86 | self.module = module 87 | 88 | self.name = module.params['name'] 89 | self.type = module.params['type'] 90 | self.local_address = module.params['local_address'] 91 | self.remote_address = module.params['remote_address'] 92 | self.temporary = module.params['temporary'] 93 | self.state = module.params['state'] 94 | 95 | self.dladm_bin = self.module.get_bin_path('dladm', True) 96 | 97 | def iptun_exists(self): 98 | cmd = [self.dladm_bin] 99 | 100 | cmd.append('show-iptun') 101 | cmd.append(self.name) 102 | 103 | (rc, _, _) = self.module.run_command(cmd) 104 | 105 | if rc == 0: 106 | return True 107 | else: 108 | return False 109 | 110 | def create_iptun(self): 111 | cmd = [self.dladm_bin] 112 | 113 | cmd.append('create-iptun') 114 | 115 | if self.temporary: 116 | cmd.append('-t') 117 | 118 | cmd.append('-T') 119 | cmd.append(self.type) 120 | cmd.append('-a') 121 | cmd.append('local=' + self.local_address + ',remote=' + self.remote_address) 122 | cmd.append(self.name) 123 | 124 | return self.module.run_command(cmd) 125 | 126 | def delete_iptun(self): 127 | cmd = [self.dladm_bin] 128 | 129 | cmd.append('delete-iptun') 130 | 131 | if self.temporary: 132 | cmd.append('-t') 133 | cmd.append(self.name) 134 | 135 | return self.module.run_command(cmd) 136 | 137 | def update_iptun(self): 138 | cmd = [self.dladm_bin] 139 | 140 | cmd.append('modify-iptun') 141 | 142 | if self.temporary: 143 | cmd.append('-t') 144 | cmd.append('-a') 145 | cmd.append('local=' + self.local_address + ',remote=' + self.remote_address) 146 | cmd.append(self.name) 147 | 148 | return self.module.run_command(cmd) 149 | 150 | def _query_iptun_props(self): 151 | cmd = [self.dladm_bin] 152 | 153 | cmd.append('show-iptun') 154 | cmd.append('-p') 155 | cmd.append('-c') 156 | cmd.append('link,type,flags,local,remote') 157 | cmd.append(self.name) 158 | 159 | return self.module.run_command(cmd) 160 | 161 | def iptun_needs_updating(self): 162 | (rc, out, err) = self._query_iptun_props() 163 | 164 | NEEDS_UPDATING = False 165 | 166 | if rc == 0: 167 | configured_local, configured_remote = out.split(':')[3:] 168 | 169 | if self.local_address != configured_local or self.remote_address != configured_remote: 170 | NEEDS_UPDATING = True 171 | 172 | return NEEDS_UPDATING 173 | else: 174 | self.module.fail_json(msg='Failed to query tunnel interface %s properties' % self.name, 175 | err=err, 176 | rc=rc) 177 | 178 | 179 | 180 | def main(): 181 | module = AnsibleModule( 182 | argument_spec=dict( 183 | name=dict(required=True, type='str'), 184 | type=dict(default='ipv4', type='str', aliases=['tunnel_type'], 185 | choices=SUPPORTED_TYPES), 186 | local_address=dict(type='str', aliases=['local']), 187 | remote_address=dict(type='str', aliases=['remote']), 188 | temporary=dict(default=False, type='bool'), 189 | state=dict(default='present', choices=['absent', 'present']), 190 | ), 191 | required_if=[ 192 | ['state', 'present', ['local_address', 'remote_address']], 193 | ], 194 | supports_check_mode=True 195 | ) 196 | 197 | iptun = IPTun(module) 198 | 199 | rc = None 200 | out = '' 201 | err = '' 202 | result = {} 203 | result['name'] = iptun.name 204 | result['type'] = iptun.type 205 | result['local_address'] = iptun.local_address 206 | result['remote_address'] = iptun.remote_address 207 | result['state'] = iptun.state 208 | result['temporary'] = iptun.temporary 209 | 210 | if iptun.state == 'absent': 211 | if iptun.iptun_exists(): 212 | if module.check_mode: 213 | module.exit_json(changed=True) 214 | (rc, out, err) = iptun.delete_iptun() 215 | if rc != 0: 216 | module.fail_json(name=iptun.name, msg=err, rc=rc) 217 | elif iptun.state == 'present': 218 | if not iptun.iptun_exists(): 219 | if module.check_mode: 220 | module.exit_json(changed=True) 221 | (rc, out, err) = iptun.create_iptun() 222 | 223 | if rc is not None and rc != 0: 224 | module.fail_json(name=iptun.name, msg=err, rc=rc) 225 | else: 226 | if iptun.iptun_needs_updating(): 227 | (rc, out, err) = ipyim.update_iptun() 228 | if rc != 0: 229 | module.fail_json(msg='Error while updating tunnel interface: "%s"' % err, 230 | name=iptun.name, 231 | stderr=err, 232 | rc=rc) 233 | 234 | if rc is None: 235 | result['changed'] = False 236 | else: 237 | result['changed'] = True 238 | 239 | if out: 240 | result['stdout'] = out 241 | if err: 242 | result['stderr'] = err 243 | 244 | module.exit_json(**result) 245 | 246 | from ansible.module_utils.basic import * 247 | main() 248 | -------------------------------------------------------------------------------- /library/dladm_linkprop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2016, Adam Števko 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | # 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: dladm_linkprop 25 | short_description: Manage link properties on Solaris/illumos systems. 26 | description: 27 | - Set / reset link properties on Solaris/illumos systems. 28 | version_added: "2.3" 29 | author: Adam Števko (@xen0l) 30 | options: 31 | link: 32 | description: 33 | - Link interface name. 34 | type: str 35 | required: true 36 | aliases: [ "nic", "interface" ] 37 | property: 38 | description: 39 | - Specifies the name of the property we want to manage. 40 | type: str 41 | required: true 42 | aliases: [ "name" ] 43 | value: 44 | description: 45 | - Specifies the value we want to set for the link property. 46 | type: str 47 | required: false 48 | temporary: 49 | description: 50 | - Specifies that lin property configuration is temporary. Temporary 51 | link property configuration does not persist across reboots. 52 | required: false 53 | type: boolean 54 | default: false 55 | state: 56 | description: 57 | - Set or reset the property value. 58 | required: false 59 | type: str 60 | default: "present" 61 | choices: [ "present", "absent", "reset" ] 62 | ''' 63 | 64 | EXAMPLES = ''' 65 | # Set 'maxbw' to 100M on e1000g1 66 | dladm_linkprop: name=e1000g1 property=maxbw value=100M state=present 67 | 68 | # Set 'mtu' to 9000 on e1000g1 69 | dladm_linkprop: name=e1000g1 property=mtu value=9000 70 | 71 | # Reset 'mtu' property on e1000g1 72 | dladm_linkprop: name=e1000g1 property=mtu state=reset 73 | ''' 74 | 75 | RETURN = ''' 76 | ''' 77 | 78 | class LinkProp(object): 79 | 80 | def __init__(self, module): 81 | self.module = module 82 | 83 | self.link = module.params['link'] 84 | self.property = module.params['property'] 85 | self.value = module.params['value'] 86 | self.temporary = module.params['temporary'] 87 | self.state = module.params['state'] 88 | 89 | self.dladm_bin = self.module.get_bin_path('dladm', True) 90 | 91 | def property_exists(self): 92 | cmd = [self.dladm_bin] 93 | 94 | cmd.append('show-linkprop') 95 | cmd.append('-p') 96 | cmd.append(self.property) 97 | cmd.append(self.link) 98 | 99 | (rc, _, _) = self.module.run_command(cmd) 100 | 101 | if rc == 0: 102 | return True 103 | else: 104 | self.module.fail_json(msg='Unknown property "%s" on link %s' % 105 | (self.property, self.link), 106 | property=self.property, 107 | link=self.link) 108 | 109 | def property_is_modified(self): 110 | cmd = [self.dladm_bin] 111 | 112 | cmd.append('show-linkprop') 113 | cmd.append('-c') 114 | cmd.append('-o') 115 | cmd.append('value,default') 116 | cmd.append('-p') 117 | cmd.append(self.property) 118 | cmd.append(self.link) 119 | 120 | (rc, out, _) = self.module.run_command(cmd) 121 | 122 | out = out.rstrip() 123 | (value, default) = out.split(':') 124 | 125 | if rc == 0 and value == default: 126 | return True 127 | else: 128 | return False 129 | 130 | def property_is_readonly(self): 131 | cmd = [self.dladm_bin] 132 | 133 | cmd.append('show-linkprop') 134 | cmd.append('-c') 135 | cmd.append('-o') 136 | cmd.append('perm') 137 | cmd.append('-p') 138 | cmd.append(self.property) 139 | cmd.append(self.link) 140 | 141 | (rc, out, _) = self.module.run_command(cmd) 142 | 143 | out = out.rstrip() 144 | 145 | if rc == 0 and out == 'r-': 146 | return True 147 | else: 148 | return False 149 | 150 | def property_is_set(self): 151 | cmd = [self.dladm_bin] 152 | 153 | cmd.append('show-linkprop') 154 | cmd.append('-c') 155 | cmd.append('-o') 156 | cmd.append('value') 157 | cmd.append('-p') 158 | cmd.append(self.property) 159 | cmd.append(self.link) 160 | 161 | (rc, out, _) = self.module.run_command(cmd) 162 | 163 | out = out.rstrip() 164 | 165 | if rc == 0 and self.value == out: 166 | return True 167 | else: 168 | return False 169 | 170 | def set_property(self): 171 | cmd = [self.dladm_bin] 172 | 173 | cmd.append('set-linkprop') 174 | 175 | if self.temporary: 176 | cmd.append('-t') 177 | 178 | cmd.append('-p') 179 | cmd.append(self.property + '=' + self.value) 180 | cmd.append(self.link) 181 | 182 | return self.module.run_command(cmd) 183 | 184 | def reset_property(self): 185 | cmd = [self.dladm_bin] 186 | 187 | cmd.append('reset-linkprop') 188 | 189 | if self.temporary: 190 | cmd.append('-t') 191 | 192 | cmd.append('-p') 193 | cmd.append(self.property) 194 | cmd.append(self.link) 195 | 196 | return self.module.run_command(cmd) 197 | 198 | 199 | def main(): 200 | module = AnsibleModule( 201 | argument_spec=dict( 202 | link=dict(required=True, default=None, type='str', aliases=['nic', 'interface']), 203 | property=dict(required=True, type='str', aliases=['name']), 204 | value=dict(required=False, type='str'), 205 | temporary=dict(default=False, type='bool', choices=BOOLEANS), 206 | state=dict( 207 | default='present', choices=['absent', 'present', 'reset']), 208 | ), 209 | required_if=[ 210 | ['state', 'present', ['value']], 211 | ], 212 | 213 | supports_check_mode=True 214 | ) 215 | 216 | 217 | linkprop = LinkProp(module) 218 | 219 | rc = None 220 | out = '' 221 | err = '' 222 | result = {} 223 | result['property'] = linkprop.property 224 | result['link'] = linkprop.link 225 | result['state'] = linkprop.state 226 | if linkprop.value: 227 | result['value'] = linkprop.value 228 | 229 | if linkprop.state == 'absent' or linkprop.state == 'reset': 230 | if linkprop.property_exists(): 231 | if not linkprop.property_is_modified(): 232 | if module.check_mode: 233 | module.exit_json(changed=True) 234 | (rc, out, err) = linkprop.reset_property() 235 | if rc != 0: 236 | module.fail_json(property=linkprop.property, 237 | link=linkprop.link, 238 | msg=err, 239 | rc=rc) 240 | 241 | elif linkprop.state == 'present': 242 | if linkprop.property_exists(): 243 | if not linkprop.property_is_readonly(): 244 | if not linkprop.property_is_set(): 245 | if module.check_mode: 246 | module.exit_json(changed=True) 247 | 248 | (rc, out, err) = linkprop.set_property() 249 | if rc != 0: 250 | module.fail_json(property=linkprop.property, 251 | link=linkprop.link, 252 | msg=err, 253 | rc=rc) 254 | else: 255 | module.fail_json(msg='Property "%s" is read-only!' % (linkprop.property), 256 | property=linkprop.property, 257 | link=linkprop.link) 258 | 259 | if rc is None: 260 | result['changed'] = False 261 | else: 262 | result['changed'] = True 263 | 264 | if out: 265 | result['stdout'] = out 266 | if err: 267 | result['stderr'] = err 268 | 269 | module.exit_json(**result) 270 | 271 | from ansible.module_utils.basic import * 272 | main() 273 | -------------------------------------------------------------------------------- /library/ipadm_ifprop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2015, Adam Števko 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | # 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: ipadm_ifprop 25 | short_description: Manage IP interface properties on Solaris/illumos systems. 26 | description: 27 | - Modify IP interface properties on Solaris/illumos systems. 28 | version_added: "2.3" 29 | author: Adam Števko (@xen0l) 30 | options: 31 | interface: 32 | description: 33 | - Specifies the IP interface we want to manage. 34 | required: true 35 | aliases: [nic] 36 | protocol: 37 | description: 38 | - Specifies the procotol for which we want to manage properties. 39 | required: true 40 | property: 41 | description: 42 | - Specifies the name of the property we want to manage. 43 | required: true 44 | aliases: [name] 45 | value: 46 | description: 47 | - Specifies the value we want to set for the property. 48 | required: false 49 | temporary: 50 | description: 51 | - Specifies that the property value is temporary. Temporary 52 | property values do not persist across reboots. 53 | required: false 54 | default: false 55 | state: 56 | description: 57 | - Set or reset the property value. 58 | required: false 59 | default: present 60 | choices: [ "present", "absent", "reset" ] 61 | ''' 62 | 63 | EXAMPLES = ''' 64 | # Allow forwarding of IPv4 packets on network interface e1000g0 65 | ipadm_ifprop: protocol=ipv4 property=forwarding value=on interface=e1000g0 66 | 67 | # Temporarily reset IPv4 forwarding property on network interface e1000g0 68 | ipadm_ifprop: protocol=ipv4 interface=e1000g0 temporary=true property=forwarding state=reset 69 | 70 | # Configure IPv6 metric on network interface e1000g0 71 | ipadm_ifprop: protocol=ipv6 nic=e1000g0 name=metric value=100 72 | 73 | # Set IPv6 MTU on network interface bge0 74 | ipadm_ifprop: interface=bge0 name=mtu value=1280 protocol=ipv6 75 | ''' 76 | 77 | RETURN = ''' 78 | ''' 79 | 80 | SUPPORTED_PROTOCOLS = ['ipv4', 'ipv6'] 81 | 82 | 83 | class IfProp(object): 84 | 85 | def __init__(self, module): 86 | self.module = module 87 | 88 | self.interface = module.params['interface'] 89 | self.protocol = module.params['protocol'] 90 | self.property = module.params['property'] 91 | self.value = module.params['value'] 92 | self.temporary = module.params['temporary'] 93 | self.state = module.params['state'] 94 | 95 | def property_exists(self): 96 | cmd = [self.module.get_bin_path('ipadm')] 97 | 98 | cmd.append('show-ifprop') 99 | cmd.append('-p') 100 | cmd.append(self.property) 101 | cmd.append('-m') 102 | cmd.append(self.protocol) 103 | cmd.append(self.interface) 104 | 105 | (rc, _, _) = self.module.run_command(cmd) 106 | 107 | if rc == 0: 108 | return True 109 | else: 110 | self.module.fail_json(msg='Unknown %s property "%s" on IP interface %s' % 111 | (self.protocol, self.property, self.interface), 112 | protocol=self.protocol, 113 | property=self.property, 114 | interface=self.interface) 115 | 116 | def property_is_modified(self): 117 | cmd = [self.module.get_bin_path('ipadm')] 118 | 119 | cmd.append('show-ifprop') 120 | cmd.append('-c') 121 | cmd.append('-o') 122 | cmd.append('current,default') 123 | cmd.append('-p') 124 | cmd.append(self.property) 125 | cmd.append('-m') 126 | cmd.append(self.protocol) 127 | cmd.append(self.interface) 128 | 129 | (rc, out, _) = self.module.run_command(cmd) 130 | 131 | out = out.rstrip() 132 | (value, default) = out.split(':') 133 | 134 | if rc == 0 and value == default: 135 | return True 136 | else: 137 | return False 138 | 139 | def property_is_set(self): 140 | cmd = [self.module.get_bin_path('ipadm')] 141 | 142 | cmd.append('show-ifprop') 143 | cmd.append('-c') 144 | cmd.append('-o') 145 | cmd.append('current') 146 | cmd.append('-p') 147 | cmd.append(self.property) 148 | cmd.append('-m') 149 | cmd.append(self.protocol) 150 | cmd.append(self.interface) 151 | 152 | (rc, out, _) = self.module.run_command(cmd) 153 | 154 | out = out.rstrip() 155 | 156 | if rc == 0 and self.value == out: 157 | return True 158 | else: 159 | return False 160 | 161 | def set_property(self): 162 | cmd = [self.module.get_bin_path('ipadm')] 163 | 164 | cmd.append('set-ifprop') 165 | 166 | if self.temporary: 167 | cmd.append('-t') 168 | 169 | cmd.append('-p') 170 | cmd.append(self.property + "=" + self.value) 171 | cmd.append('-m') 172 | cmd.append(self.protocol) 173 | cmd.append(self.interface) 174 | 175 | return self.module.run_command(cmd) 176 | 177 | def reset_property(self): 178 | cmd = [self.module.get_bin_path('ipadm')] 179 | 180 | cmd.append('reset-ifprop') 181 | 182 | if self.temporary: 183 | cmd.append('-t') 184 | 185 | cmd.append('-p') 186 | cmd.append(self.property) 187 | cmd.append('-m') 188 | cmd.append(self.protocol) 189 | cmd.append(self.interface) 190 | 191 | return self.module.run_command(cmd) 192 | 193 | 194 | def main(): 195 | module = AnsibleModule( 196 | argument_spec=dict( 197 | protocol=dict(required=True, choices=SUPPORTED_PROTOCOLS), 198 | property=dict(required=True, aliases=['name']), 199 | value=dict(required=False), 200 | temporary=dict(default=False, type='bool'), 201 | interface=dict(required=True, default=None, aliases=['nic']), 202 | state=dict( 203 | default='present', choices=['absent', 'present', 'reset']), 204 | ), 205 | supports_check_mode=True 206 | ) 207 | 208 | ifprop = IfProp(module) 209 | 210 | rc = None 211 | out = '' 212 | err = '' 213 | result = {} 214 | result['protocol'] = ifprop.protocol 215 | result['property'] = ifprop.property 216 | result['interface'] = ifprop.interface 217 | result['state'] = ifprop.state 218 | if ifprop.value: 219 | result['value'] = ifprop.value 220 | 221 | if ifprop.state == 'absent' or ifprop.state == 'reset': 222 | if ifprop.property_exists(): 223 | if not ifprop.property_is_modified(): 224 | if module.check_mode: 225 | module.exit_json(changed=True) 226 | (rc, out, err) = ifprop.reset_property() 227 | if rc != 0: 228 | module.fail_json(protocol=ifprop.protocol, 229 | property=ifprop.property, 230 | interface=ifprop.interface, 231 | msg=err, 232 | rc=rc) 233 | 234 | elif ifprop.state == 'present': 235 | if ifprop.value is None: 236 | module.fail_json(msg='Value is mandatory with state "present"') 237 | 238 | if ifprop.property_exists(): 239 | if not ifprop.property_is_set(): 240 | if module.check_mode: 241 | module.exit_json(changed=True) 242 | 243 | (rc, out, err) = ifprop.set_property() 244 | if rc != 0: 245 | module.fail_json(protocol=ifprop.protocol, 246 | property=ifprop.property, 247 | interface=ifprop.interface, 248 | msg=err, 249 | rc=rc) 250 | 251 | if rc is None: 252 | result['changed'] = False 253 | else: 254 | result['changed'] = True 255 | 256 | if out: 257 | result['stdout'] = out 258 | if err: 259 | result['stderr'] = err 260 | 261 | module.exit_json(**result) 262 | 263 | 264 | from ansible.module_utils.basic import * 265 | main() 266 | -------------------------------------------------------------------------------- /library/solaris_project: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2015, Adam Števko 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | # 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: solaris_project 25 | short_description: Manage Solaris/illumos projects 26 | description: 27 | - Create, delete or update Solaris/illumos projects 28 | version_added: "2.1" 29 | author: Adam Števko (@xen0l) 30 | options: 31 | comment: 32 | description: 33 | - Project comment. 34 | required: false 35 | default: null 36 | project_file: 37 | description: 38 | - Project file location. 39 | required: false 40 | default: /etc/project 41 | project_id: 42 | description: 43 | - Set the project ID of the new project. 44 | required: false 45 | default: null 46 | name: 47 | description: 48 | - Project name. 49 | required: true 50 | users: 51 | description: 52 | - User list for the project. 53 | required: false 54 | default: null 55 | aliases: ['user'] 56 | groups: 57 | description: 58 | - Group list for the project. 59 | required: false 60 | default: null 61 | aliases: ['group'] 62 | attributes: 63 | description: 64 | - Attribute list for the project. 65 | required: false 66 | default: null 67 | append: 68 | description: 69 | - When modifying Solaris/illumos projects, users, group 70 | and attributes should be added to the existing list. 71 | required: false 72 | default: false 73 | remove: 74 | description: 75 | - When modifying Solaris/illumos projects, users, group 76 | and attributes should be remove from the existing list. 77 | required: false 78 | default: false 79 | state: 80 | description: 81 | - Create or delete Solaris/illumos project. 82 | required: false 83 | default: "present" 84 | choices: [ "present", "absent" ] 85 | ''' 86 | 87 | EXAMPLES = ''' 88 | # Create new Solaris project 'accounting' with users 'alice' and 'bob' 89 | - solaris_project: name=accounting users='alice,bob' state=present 90 | 91 | # Add user 'eve' to existing project 92 | - solaris_project: name=accounting user=eve append=true 93 | 94 | # Remove user 'alice' from existing project 95 | - solaris_project: name=accounting user=alice remove=true 96 | 97 | # Remove 'accounting' project 98 | - solaris_project: name=accounting state=absent 99 | ''' 100 | 101 | PROJECTFILE = '/etc/project' 102 | 103 | 104 | class Project(object): 105 | 106 | def __init__(self, module): 107 | self.module = module 108 | 109 | self.attributes = module.params['attributes'] 110 | self.append = module.params['append'] 111 | self.remove = module.params['remove'] 112 | self.groups = module.params['groups'] 113 | self.users = module.params['users'] 114 | self.comment = module.params['comment'] 115 | self.project_file = module.params['project_file'] 116 | self.project_id = module.params['project_id'] 117 | self.name = module.params['name'] 118 | self.state = module.params['state'] 119 | 120 | def project_exists(self): 121 | with open(self.project_file, 'r') as f: 122 | for line in f.readlines(): 123 | if self.name == line.split(':')[0]: 124 | return True 125 | 126 | return False 127 | 128 | def create_project(self): 129 | cmd = [self.module.get_bin_path('projadd', True)] 130 | 131 | if self.project_file != PROJECTFILE: 132 | cmd.append('-f') 133 | cmd.append(self.project_file) 134 | 135 | if self.project_id: 136 | cmd.append('-p') 137 | cmd.append(self.project_id) 138 | 139 | if self.comment: 140 | cmd.append('-c') 141 | cmd.append(self.comment) 142 | 143 | if self.users: 144 | cmd.append('-U') 145 | cmd.append(self.users) 146 | 147 | if self.groups: 148 | cmd.append('-G') 149 | cmd.append(self.groups) 150 | 151 | if self.attributes: 152 | for attribute in self.attributes.split(';'): 153 | cmd.append('-K') 154 | cmd.append(attribute) 155 | 156 | cmd.append(self.name) 157 | return self.module.run_command(cmd) 158 | 159 | def delete_project(self): 160 | cmd = [self.module.get_bin_path('projdel', True)] 161 | 162 | if self.project_file != PROJECTFILE: 163 | cmd.append('-f') 164 | cmd.append(self.project_file) 165 | 166 | cmd.append(self.name) 167 | 168 | return self.module.run_command(cmd) 169 | 170 | def modify_project(self): 171 | 172 | # projname:projid:comment:user-list:group-list:attributes 173 | 174 | _project_entry = self._get_entry_from_project_file() 175 | 176 | cmd = [self.module.get_bin_path('projmod', True)] 177 | 178 | if self.project_file != PROJECTFILE: 179 | cmd.append('-f') 180 | cmd.append(self.project_file) 181 | 182 | if self.project_id is not None and self.project_id != _project_entry[1]: 183 | cmd.append('-p') 184 | cmd.append(self.project_id) 185 | 186 | if self.comment is not None and self.comment != _project_entry[2]: 187 | cmd.append('-c') 188 | cmd.append(self.comment) 189 | 190 | if self.append: 191 | cmd.append('-a') 192 | 193 | if self.remove: 194 | cmd.append('-r') 195 | 196 | if self.users is not None: 197 | cmd.append('-U') 198 | cmd.append(self.users) 199 | 200 | if self.groups is not None: 201 | cmd.append('-G') 202 | cmd.append(self.groups) 203 | 204 | if self.attributes is not None: 205 | for attribute in self.attributes.split(';'): 206 | cmd.append('-K') 207 | cmd.append(attribute) 208 | 209 | # skip if no changes to be made 210 | if len(cmd) == 1: 211 | return (None, '', '') 212 | elif self.module.check_mode: 213 | return (0, '', '') 214 | 215 | cmd.append(self.name) 216 | 217 | return self.module.run_command(cmd) 218 | 219 | def _get_entry_from_project_file(self): 220 | 221 | with open(self.project_file, "r") as f: 222 | for line in f.readlines(): 223 | if self.name == line.split(':')[0]: 224 | return line.split(':') 225 | 226 | 227 | def main(): 228 | module = AnsibleModule( 229 | argument_spec=dict( 230 | name=dict(required=True, aliases=['projname']), 231 | project_file=dict(default=PROJECTFILE), 232 | project_id=dict(default=None, aliases=['projid']), 233 | comment=dict(default=None), 234 | attributes=dict(default=None), 235 | groups=dict(default=None, aliases=['group']), 236 | users=dict(default=None, aliases=['user']), 237 | state=dict(default='present', choices=['absent', 'present']), 238 | # following options are specific to projmod 239 | append=dict(default=False, type='bool'), 240 | remove=dict(default=False, type='bool'), 241 | ), 242 | mutually_exclusive=[ 243 | ['append', 'remove'] 244 | ], 245 | supports_check_mode=True 246 | ) 247 | 248 | project = Project(module) 249 | 250 | rc = None 251 | out = '' 252 | err = '' 253 | result = {} 254 | result['name'] = project.name 255 | result['state'] = project.state 256 | result['projid'] = project.project_id 257 | result['users'] = project.users 258 | result['groups'] = project.groups 259 | result['attributes'] = project.attributes 260 | 261 | if project.state == 'absent': 262 | if project.project_exists(): 263 | if module.check_mode: 264 | module.exit_json(changed=True) 265 | (rc, out, err) = project.delete_project() 266 | if rc != 0: 267 | module.fail_json(name=project.name, msg=err, rc=rc) 268 | elif project.state == 'present': 269 | if not project.project_exists(): 270 | if module.check_mode: 271 | module.exit_json(changed=True) 272 | (rc, out, err) = project.create_project() 273 | else: 274 | (rc, out, err) = project.modify_project() 275 | if rc is not None and rc != 0: 276 | module.fail_json(name=project.name, msg=err, rc=rc) 277 | 278 | if rc is None: 279 | result['changed'] = False 280 | else: 281 | result['changed'] = True 282 | 283 | if out: 284 | result['stdout'] = out 285 | if err: 286 | result['stderr'] = err 287 | 288 | module.exit_json(**result) 289 | 290 | from ansible.module_utils.basic import * 291 | main() 292 | -------------------------------------------------------------------------------- /library/beadm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2016, Adam Števko 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | # 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: beadm 25 | short_description: Manage ZFS boot environments on FreeBSD/Solaris/illumos systems. 26 | description: 27 | - Create, delete or activate ZFS boot environments. 28 | - Mount and unmount ZFS boot environments. 29 | version_added: "2.2" 30 | author: Adam Števko (@xen0l) 31 | options: 32 | name: 33 | description: 34 | - ZFS boot environment name. 35 | alias: [ "be" ] 36 | required: True 37 | snapshot: 38 | description: 39 | - If specified, the new boot environment will be cloned from the given 40 | snapshot or inactive boot environment. 41 | required: false 42 | default: false 43 | description: 44 | description: 45 | - Associate a description with a new boot environment. This option is 46 | available only on Solarish platforms. 47 | required: false 48 | default: false 49 | options: 50 | description: 51 | - Create the datasets for new BE with specific ZFS properties. Multiple 52 | options can be specified. This option is available only on 53 | Solarish platforms. 54 | required: false 55 | default: false 56 | mountpoint: 57 | description: 58 | - Path where to mount the ZFS boot environment 59 | required: false 60 | default: false 61 | state: 62 | description: 63 | - Create or delete ZFS boot environment. 64 | required: false 65 | default: "present" 66 | choices: [ "present", "absent", "activated", "mounted", "unmounted" ] 67 | force: 68 | description: 69 | - Specifies if the unmount should be forced. 70 | required: false 71 | default: false 72 | choices: [ "true", "false" ] 73 | ''' 74 | 75 | EXAMPLES = ''' 76 | # Create ZFS boot environment 77 | beadm: name=upgrade-be state=present 78 | 79 | # Create ZFS boot environment from existing inactive boot environment 80 | beadm: name=upgrade-be snapshot=be@old state=present 81 | 82 | # Create ZFS boot environment with compression enabled and description "upgrade" 83 | beadm: name=upgrade-be options="compression=on" description="upgrade" 84 | state=present 85 | 86 | # Delete ZFS boot environment 87 | beadm: name=old-be state=absent 88 | 89 | # Mount ZFS boot environment on /tmp/be 90 | beadm: name=BE mountpoint=/tmp/be state=mounted 91 | 92 | # Unmount ZFS boot environment 93 | beadm: name=BE state=unmounted 94 | 95 | # Activate ZFS boot environment 96 | beadm: name=upgrade-be state=activated 97 | ''' 98 | 99 | RETURN = ''' 100 | ''' 101 | 102 | import os 103 | 104 | 105 | class BE(object): 106 | def __init__(self, module): 107 | self.module = module 108 | 109 | self.name = module.params['name'] 110 | self.snapshot = module.params['snapshot'] 111 | self.description = module.params['description'] 112 | self.options = module.params['options'] 113 | self.mountpoint = module.params['mountpoint'] 114 | self.state = module.params['state'] 115 | self.force = module.params['force'] 116 | 117 | self.is_freebsd = os.uname()[0] == 'FreeBSD' 118 | 119 | def _beadm_list(self): 120 | cmd = [self.module.get_bin_path('beadm')] 121 | cmd.append('list') 122 | cmd.append('-H') 123 | 124 | if not self.is_freebsd: 125 | cmd.append(self.name) 126 | 127 | return self.module.run_command(cmd) 128 | 129 | def _find_be_by_name(self, out): 130 | for line in out.splitlines(): 131 | if line.split('\t')[0] == self.name: 132 | return line 133 | 134 | return None 135 | 136 | def exists(self): 137 | (rc, out, _) = self._beadm_list() 138 | 139 | if rc == 0: 140 | if self.is_freebsd: 141 | if self._find_be_by_name(out): 142 | return True 143 | else: 144 | return True 145 | else: 146 | return False 147 | 148 | def is_activated(self): 149 | (rc, out, _) = self._beadm_list() 150 | 151 | if rc == 0: 152 | if self.is_freebsd: 153 | line = self._find_be_by_name(out) 154 | if line is not None and 'R' in line.split('\t')[1]: 155 | return True 156 | else: 157 | if 'R' in out.split(';')[2]: 158 | return True 159 | 160 | return False 161 | 162 | def activate_be(self): 163 | cmd = [self.module.get_bin_path('beadm')] 164 | 165 | cmd.append('activate') 166 | cmd.append(self.name) 167 | 168 | return self.module.run_command(cmd) 169 | 170 | def create_be(self): 171 | cmd = [self.module.get_bin_path('beadm')] 172 | 173 | cmd.append('create') 174 | 175 | if self.snapshot: 176 | cmd.append('-e') 177 | cmd.append(self.snapshot) 178 | 179 | if not self.is_freebsd: 180 | if self.description: 181 | cmd.append('-d') 182 | cmd.append(self.description) 183 | 184 | if self.options: 185 | cmd.append('-o') 186 | cmd.append(self.options) 187 | 188 | cmd.append(self.name) 189 | 190 | return self.module.run_command(cmd) 191 | 192 | def destroy_be(self): 193 | cmd = [self.module.get_bin_path('beadm')] 194 | 195 | cmd.append('destroy') 196 | cmd.append('-F') 197 | cmd.append(self.name) 198 | 199 | return self.module.run_command(cmd) 200 | 201 | def is_mounted(self): 202 | (rc, out, _) = self._beadm_list() 203 | 204 | if rc == 0: 205 | if self.is_freebsd: 206 | line = self._find_be_by_name(out) 207 | # On FreeBSD, we exclude currently mounted BE on /, as it is 208 | # special and can be activated even if it is mounted. That is not 209 | # possible with non-root BEs. 210 | if line.split('\t')[2] is not '-' and \ 211 | line.split('\t')[2] is not '/': 212 | return True 213 | else: 214 | if out.split(';')[3]: 215 | return True 216 | 217 | return False 218 | 219 | def mount_be(self): 220 | cmd = [self.module.get_bin_path('beadm')] 221 | 222 | cmd.append('mount') 223 | cmd.append(self.name) 224 | 225 | if self.mountpoint: 226 | cmd.append(self.mountpoint) 227 | 228 | return self.module.run_command(cmd) 229 | 230 | def unmount_be(self): 231 | cmd = [self.module.get_bin_path('beadm')] 232 | 233 | cmd.append('unmount') 234 | if self.force: 235 | cmd.append('-f') 236 | cmd.append(self.name) 237 | 238 | return self.module.run_command(cmd) 239 | 240 | 241 | def main(): 242 | module = AnsibleModule( 243 | argument_spec=dict( 244 | name=dict(required=True, aliases=['be'], type='str'), 245 | snapshot=dict(type='str'), 246 | description=dict(type='str'), 247 | options=dict(type='str'), 248 | mountpoint=dict(default=False, type='path'), 249 | state=dict( 250 | default='present', 251 | choices=['present', 'absent', 'activated', 252 | 'mounted', 'unmounted']), 253 | force=dict(default=False, type='bool'), 254 | ), 255 | supports_check_mode=True 256 | ) 257 | 258 | be = BE(module) 259 | 260 | rc = None 261 | out = '' 262 | err = '' 263 | result = {} 264 | result['name'] = be.name 265 | result['state'] = be.state 266 | 267 | if be.snapshot: 268 | result['snapshot'] = be.snapshot 269 | 270 | if be.description: 271 | result['description'] = be.description 272 | 273 | if be.options: 274 | result['options'] = be.options 275 | 276 | if be.mountpoint: 277 | result['mountpoint'] = be.mountpoint 278 | 279 | if be.state == 'absent': 280 | # beadm on FreeBSD and Solarish systems differs in delete behaviour in 281 | # that we are not allowed to delete activated BE on FreeBSD while on 282 | # Solarish systems we cannot delete BE if it is mounted. We add mount 283 | # check for both platforms as BE should be explicitly unmounted before 284 | # being deleted. On FreeBSD, we also check if the BE is activated. 285 | if be.exists(): 286 | if not be.is_mounted(): 287 | if module.check_mode: 288 | module.exit_json(changed=True) 289 | 290 | if be.is_freebsd: 291 | if be.is_activated(): 292 | module.fail_json(msg='Unable to remove active BE!') 293 | 294 | (rc, out, err) = be.destroy_be() 295 | 296 | if rc != 0: 297 | module.fail_json(msg='Error while destroying BE: "%s"' % err, 298 | name=be.name, 299 | stderr=err, 300 | rc=rc) 301 | else: 302 | module.fail_json(msg='Unable to remove BE as it is mounted!') 303 | 304 | elif be.state == 'present': 305 | if not be.exists(): 306 | if module.check_mode: 307 | module.exit_json(changed=True) 308 | 309 | (rc, out, err) = be.create_be() 310 | 311 | if rc != 0: 312 | module.fail_json(msg='Error while creating BE: "%s"' % err, 313 | name=be.name, 314 | stderr=err, 315 | rc=rc) 316 | 317 | elif be.state == 'activated': 318 | if not be.is_activated(): 319 | if module.check_mode: 320 | module.exit_json(changed=True) 321 | 322 | # On FreeBSD, beadm is unable to activate mounted BEs, so we add 323 | # an explicit check for that case. 324 | if be.is_freebsd: 325 | if be.is_mounted(): 326 | module.fail_json(msg='Unable to activate mounted BE!') 327 | 328 | (rc, out, err) = be.activate_be() 329 | 330 | if rc != 0: 331 | module.fail_json(msg='Error while activating BE: "%s"' % err, 332 | name=be.name, 333 | stderr=err, 334 | rc=rc) 335 | elif be.state == 'mounted': 336 | if not be.is_mounted(): 337 | if module.check_mode: 338 | module.exit_json(changed=True) 339 | 340 | (rc, out, err) = be.mount_be() 341 | 342 | if rc != 0: 343 | module.fail_json(msg='Error while mounting BE: "%s"' % err, 344 | name=be.name, 345 | stderr=err, 346 | rc=rc) 347 | 348 | elif be.state == 'unmounted': 349 | if be.is_mounted(): 350 | if module.check_mode: 351 | module.exit_json(changed=True) 352 | 353 | (rc, out, err) = be.unmount_be() 354 | 355 | if rc != 0: 356 | module.fail_json(msg='Error while unmounting BE: "%s"' % err, 357 | name=be.name, 358 | stderr=err, 359 | rc=rc) 360 | 361 | if rc is None: 362 | result['changed'] = False 363 | else: 364 | result['changed'] = True 365 | 366 | if out: 367 | result['stdout'] = out 368 | if err: 369 | result['stderr'] = err 370 | 371 | module.exit_json(**result) 372 | 373 | 374 | from ansible.module_utils.basic import * 375 | if __name__ == '__main__': 376 | main() 377 | -------------------------------------------------------------------------------- /library/ipadm_addr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2015, Adam Števko 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | # 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: ipadm_addr 25 | short_description: Manage IP addresses on an interface. 26 | description: 27 | - Create/delete static/dynamic IP addresses on network interfaces on Solaris/illumos systems. 28 | - Up/down static/dynamic IP addresses on network interfaces on Solaris/illumos systems. 29 | - Manage IPv6 link-local addresses on network interfaces on Solaris/illumos systems. 30 | version_added: "2.1" 31 | author: Adam Števko (@xen0l) 32 | options: 33 | address: 34 | description: 35 | - Specifiies an IP address to configure in CIDR notation. 36 | required: false 37 | aliases: [ 'addr' ] 38 | addrtype: 39 | description: 40 | - Specifiies a type of IP address to configure. 41 | required: false 42 | default: static 43 | choices: [ 'static', 'dhcp', 'addrconf' ] 44 | addrobj: 45 | description: 46 | - Specifies an unique IP address on the system. 47 | required: true 48 | temporary: 49 | description: 50 | - Specifies that the configured IP address is temporary. Temporary 51 | IP addresses do not persist across reboots. 52 | required: false 53 | default: false 54 | wait: 55 | description: 56 | - Specifies the time in seconds we wait for obtaining address via DHCP. 57 | required: false 58 | default: 60 59 | state: 60 | description: 61 | - Create/delete/enable/disable an IP address on the network interface. 62 | required: false 63 | default: present 64 | choices: [ 'absent', 'present', 'up', 'down', 'enabled', 'disabled', 'refreshed' ] 65 | ''' 66 | 67 | EXAMPLES = ''' 68 | # Configure IP address 10.0.0.1 on e1000g0 69 | ipadm_addr: addr=10.0.0.1/32 addrobj=e1000g0/v4 state=present 70 | 71 | # Delete addrobj 72 | ipadm_addr: addrobj=e1000g0/v4 state=absent 73 | 74 | # Configure link-local IPv6 address 75 | ipadm_addr: addtype=addrconf addrobj=vnic0/v6 76 | 77 | # Configure address via DHCP and wait 180 seconds for address obtaining 78 | ipadm_addr: addrobj=vnic0/dhcp addrtype=dhcp wait=180 79 | ''' 80 | 81 | import socket 82 | 83 | SUPPORTED_TYPES = ['static', 'addrconf', 'dhcp'] 84 | 85 | 86 | class Addr(object): 87 | 88 | def __init__(self, module): 89 | self.module = module 90 | 91 | self.address = module.params['address'] 92 | self.addrtype = module.params['addrtype'] 93 | self.addrobj = module.params['addrobj'] 94 | self.temporary = module.params['temporary'] 95 | self.state = module.params['state'] 96 | self.wait = module.params['wait'] 97 | 98 | def is_cidr_notation(self): 99 | 100 | return self.address.count('/') == 1 101 | 102 | def is_valid_address(self): 103 | 104 | ip_address = self.address.split('/')[0] 105 | 106 | try: 107 | if len(ip_address.split('.')) == 4: 108 | socket.inet_pton(socket.AF_INET, ip_address) 109 | else: 110 | socket.inet_pton(socket.AF_INET6, ip_address) 111 | except socket.error: 112 | return False 113 | 114 | return True 115 | 116 | def is_dhcp(self): 117 | cmd = [self.module.get_bin_path('ipadm')] 118 | 119 | cmd.append('show-addr') 120 | cmd.append('-p') 121 | cmd.append('-o') 122 | cmd.append('type') 123 | cmd.append(self.addrobj) 124 | 125 | (rc, out, err) = self.module.run_command(cmd) 126 | 127 | if rc == 0: 128 | if out.rstrip() != 'dhcp': 129 | return False 130 | 131 | return True 132 | else: 133 | self.module.fail_json(msg='Wrong addrtype %s for addrobj "%s": %s' % (out, self.addrobj, err), 134 | rc=rc, 135 | stderr=err) 136 | 137 | def addrobj_exists(self): 138 | cmd = [self.module.get_bin_path('ipadm')] 139 | 140 | cmd.append('show-addr') 141 | cmd.append(self.addrobj) 142 | 143 | (rc, _, _) = self.module.run_command(cmd) 144 | 145 | if rc == 0: 146 | return True 147 | else: 148 | return False 149 | 150 | def delete_addr(self): 151 | cmd = [self.module.get_bin_path('ipadm')] 152 | 153 | cmd.append('delete-addr') 154 | cmd.append(self.addrobj) 155 | 156 | return self.module.run_command(cmd) 157 | 158 | def create_addr(self): 159 | cmd = [self.module.get_bin_path('ipadm')] 160 | 161 | cmd.append('create-addr') 162 | cmd.append('-T') 163 | cmd.append(self.addrtype) 164 | 165 | if self.temporary: 166 | cmd.append('-t') 167 | 168 | if self.addrtype == 'static': 169 | cmd.append('-a') 170 | cmd.append(self.address) 171 | 172 | if self.addrtype == 'dhcp' and self.wait: 173 | cmd.append('-w') 174 | cmd.append(self.wait) 175 | 176 | cmd.append(self.addrobj) 177 | 178 | return self.module.run_command(cmd) 179 | 180 | def up_addr(self): 181 | cmd = [self.module.get_bin_path('ipadm')] 182 | 183 | cmd.append('up-addr') 184 | 185 | if self.temporary: 186 | cmd.append('-t') 187 | 188 | cmd.append(self.addrobj) 189 | 190 | return self.module.run_command(cmd) 191 | 192 | def down_addr(self): 193 | cmd = [self.module.get_bin_path('ipadm')] 194 | 195 | cmd.append('down-addr') 196 | 197 | if self.temporary: 198 | cmd.append('-t') 199 | 200 | cmd.append(self.addrobj) 201 | 202 | return self.module.run_command(cmd) 203 | 204 | def enable_addr(self): 205 | cmd = [self.module.get_bin_path('ipadm')] 206 | 207 | cmd.append('enable-addr') 208 | cmd.append('-t') 209 | cmd.append(self.addrobj) 210 | 211 | return self.module.run_command(cmd) 212 | 213 | def disable_addr(self): 214 | cmd = [self.module.get_bin_path('ipadm')] 215 | 216 | cmd.append('disable-addr') 217 | cmd.append('-t') 218 | cmd.append(self.addrobj) 219 | 220 | return self.module.run_command(cmd) 221 | 222 | def refresh_addr(self): 223 | cmd = [self.module.get_bin_path('ipadm')] 224 | 225 | cmd.append('refresh-addr') 226 | cmd.append(self.addrobj) 227 | 228 | return self.module.run_command(cmd) 229 | 230 | 231 | def main(): 232 | module = AnsibleModule( 233 | argument_spec=dict( 234 | address=dict(aliases=['addr']), 235 | addrtype=dict(default='static', choices=SUPPORTED_TYPES), 236 | addrobj=dict(required=True), 237 | temporary=dict(default=False, type='bool'), 238 | state=dict( 239 | default='present', choices=['absent', 'present', 'up', 'down', 'enabled', 'disabled', 'refreshed']), 240 | wait=dict(default=60), 241 | ), 242 | mutually_exclusive=[ 243 | ('address', 'wait'), 244 | ], 245 | supports_check_mode=True 246 | ) 247 | 248 | addr = Addr(module) 249 | 250 | rc = None 251 | out = '' 252 | err = '' 253 | result = {} 254 | result['addrobj'] = addr.addrobj 255 | result['state'] = addr.state 256 | result['temporary'] = addr.temporary 257 | result['addrtype'] = addr.addrtype 258 | 259 | if addr.addrtype == 'static' and addr.address: 260 | if addr.is_cidr_notation() and addr.is_valid_address(): 261 | result['address'] = addr.address 262 | else: 263 | module.fail_json(msg='Invalid IP address: %s' % addr.address) 264 | 265 | if addr.addrtype == 'dhcp' and addr.wait: 266 | result['wait'] = addr.wait 267 | 268 | if addr.state == 'absent': 269 | if addr.addrobj_exists(): 270 | if module.check_mode: 271 | module.exit_json(changed=True) 272 | 273 | (rc, out, err) = addr.delete_addr() 274 | if rc != 0: 275 | module.fail_json(msg='Error while deleting addrobj: "%s"' % err, 276 | addrobj=addr.addrobj, 277 | stderr=err, 278 | rc=rc) 279 | 280 | elif addr.state == 'present': 281 | if not addr.addrobj_exists(): 282 | if module.check_mode: 283 | module.exit_json(changed=True) 284 | 285 | (rc, out, err) = addr.create_addr() 286 | if rc != 0: 287 | module.fail_json(msg='Error while configuring IP address: "%s"' % err, 288 | addrobj=addr.addrobj, 289 | addr=addr.address, 290 | stderr=err, 291 | rc=rc) 292 | 293 | elif addr.state == 'up': 294 | if addr.addrobj_exists(): 295 | if module.check_mode: 296 | module.exit_json(changed=True) 297 | 298 | (rc, out, err) = addr.up_addr() 299 | if rc != 0: 300 | module.fail_json(msg='Error while bringing IP address up: "%s"' % err, 301 | addrobj=addr.addrobj, 302 | stderr=err, 303 | rc=rc) 304 | 305 | elif addr.state == 'down': 306 | if addr.addrobj_exists(): 307 | if module.check_mode: 308 | module.exit_json(changed=True) 309 | 310 | (rc, out, err) = addr.down_addr() 311 | if rc != 0: 312 | module.fail_json(msg='Error while bringing IP address down: "%s"' % err, 313 | addrobj=addr.addrobj, 314 | stderr=err, 315 | rc=rc) 316 | 317 | elif addr.state == 'refreshed': 318 | if addr.addrobj_exists(): 319 | if addr.is_dhcp(): 320 | if module.check_mode: 321 | module.exit_json(changed=True) 322 | 323 | (rc, out, err) = addr.refresh_addr() 324 | if rc != 0: 325 | module.fail_json(msg='Error while refreshing IP address: "%s"' % err, 326 | addrobj=addr.addrobj, 327 | stderr=err, 328 | rc=rc) 329 | else: 330 | module.fail_json(msg='state "refreshed" cannot be used with "%s" addrtype' % addr.addrtype, 331 | addrobj=addr.addrobj, 332 | stderr=err, 333 | rc=1) 334 | 335 | elif addr.state == 'enabled': 336 | if addr.addrobj_exists(): 337 | if module.check_mode: 338 | module.exit_json(changed=True) 339 | 340 | (rc, out, err) = addr.enable_addr() 341 | if rc != 0: 342 | module.fail_json(msg='Error while enabling IP address: "%s"' % err, 343 | addrobj=addr.addrobj, 344 | stderr=err, 345 | rc=rc) 346 | 347 | elif addr.state == 'disabled': 348 | if addr.addrobj_exists(): 349 | if module.check_mode: 350 | module.exit_json(changed=True) 351 | 352 | (rc, out, err) = addr.disable_addr() 353 | if rc != 0: 354 | module.fail_json(msg='Error while disabling IP address: "%s"' % err, 355 | addrobj=addr.addrobj, 356 | stderr=err, 357 | rc=rc) 358 | 359 | if rc is None: 360 | result['changed'] = False 361 | else: 362 | result['changed'] = True 363 | 364 | if out: 365 | result['stdout'] = out 366 | if err: 367 | result['stderr'] = err 368 | 369 | module.exit_json(**result) 370 | 371 | 372 | from ansible.module_utils.basic import * 373 | main() 374 | -------------------------------------------------------------------------------- /library/flowadm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2016, Adam Števko 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | # 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: flowadm 25 | short_description: Manage bandwidth resource control and priority for protocols, services and zones. 26 | description: 27 | - Create/modify/remove networking bandwidth and associated resources for a type of traffic on a particular link. 28 | version_added: "2.1" 29 | author: Adam Števko (@xen0l) 30 | options: 31 | name: 32 | description: 33 | - A flow is defined as a set of attributes based on Layer 3 and Layer 4 34 | headers, which can be used to identify a protocol, service, or a 35 | zone. 36 | required: true 37 | aliases: [ 'flow' ] 38 | link: 39 | description: 40 | - Specifiies a link to configure flow on. 41 | required: false 42 | local_ip: 43 | description: 44 | - Identifies a network flow by the local IP address. 45 | required: false 46 | remove_ip: 47 | description: 48 | - Identifies a network flow by the remote IP address. 49 | required: false 50 | transport: 51 | description: 52 | - Specifies a Layer 4 protocol to be used. It is typically used in combination with I(local_port) to 53 | identify the service that needs special attention. 54 | required: false 55 | local_port: 56 | description: 57 | - Identifies a service specified by the local port. 58 | required: false 59 | dsfield: 60 | description: 61 | - Identifies the 8-bit differentiated services field (as defined in 62 | RFC 2474). The optional dsfield_mask is used to state the bits of interest in 63 | the differentiated services field when comparing with the dsfield 64 | value. Both values must be in hexadecimal. 65 | required: false 66 | maxbw: 67 | description: 68 | - Sets the full duplex bandwidth for the flow. The bandwidth is 69 | specified as an integer with one of the scale suffixes(K, M, or G 70 | for Kbps, Mbps, and Gbps). If no units are specified, the input 71 | value will be read as Mbps. 72 | required: false 73 | priority: 74 | description: 75 | - Sets the relative priority for the flow. 76 | required: false 77 | default: 'medium' 78 | choices: [ 'low', 'medium', 'high' ] 79 | temporary: 80 | description: 81 | - Specifies that the configured flow is temporary. Temporary 82 | flows do not persist across reboots. 83 | required: false 84 | default: false 85 | state: 86 | description: 87 | - Create/delete/enable/disable an IP address on the network interface. 88 | required: false 89 | default: present 90 | choices: [ 'absent', 'present', 'resetted' ] 91 | ''' 92 | 93 | EXAMPLES = ''' 94 | # Limit SSH traffic to 100M via vnic0 interface 95 | flowadm: link=vnic0 flow=ssh_out transport=tcp local_port=22 maxbw=100M state=present 96 | 97 | # Reset flow properties 98 | flowadm: name=dns state=resetted 99 | 100 | # Configure policy for EF PHB (DSCP value of 101110 from RFC 2598) with a bandwidth of 500 Mbps and a high priority. 101 | flowadm: link=bge0 dsfield=0x2e:0xfc maxbw=500M priority=high flow=efphb-flow state=present 102 | ''' 103 | 104 | # local_ip[/prefixlen]=address 105 | # remote_ip[/prefixlen]=address 106 | # transport={tcp|udp|sctp|icmp|icmpv6} 107 | # transport={tcp|udp|sctp},local_port=port 108 | # dsfield=val[:dsfield_mask] 109 | 110 | import socket 111 | 112 | SUPPORTED_TRANSPORTS = ['tcp', 'udp', 'sctp', 'icmp', 'icmpv6'] 113 | SUPPORTED_PRIORITIES = ['low', 'medium', 'high'] 114 | # SUPPORTED_PORTRANGE = range(1, 65535) 115 | 116 | SUPPORTED_ATTRIBUTES = ['local_ip', 'remote_ip', 'transport', 'local_port', 'dsfield'] 117 | SUPPORTPED_PROPERTIES = ['maxbw', 'priority'] 118 | 119 | 120 | class Flow(object): 121 | 122 | def __init__(self, module): 123 | self.module = module 124 | 125 | self.name = module.params['name'] 126 | self.link = module.params['link'] 127 | self.local_ip = module.params['local_ip'] 128 | self.remote_ip = module.params['remote_ip'] 129 | self.transport = module.params['transport'] 130 | self.local_port = module.params['local_port'] 131 | self.dsfield = module.params['dsfield'] 132 | self.maxbw = module.params['maxbw'] 133 | self.priority = module.params['priority'] 134 | self.temporary = module.params['temporary'] 135 | self.state = module.params['state'] 136 | 137 | self._needs_updating = { 138 | 'maxbw': False, 139 | 'priority': False, 140 | } 141 | 142 | @classmethod 143 | def is_valid_port(cls, port): 144 | return 1 <= int(port) <= 65535 145 | 146 | @classmethod 147 | def is_valid_address(cls, ip): 148 | 149 | if ip.count('/') == 1: 150 | ip_address, netmask = ip.split('/') 151 | else: 152 | ip_address = ip 153 | 154 | if len(ip_address.split('.')) == 4: 155 | try: 156 | socket.inet_pton(socket.AF_INET, ip_address) 157 | except socket.error: 158 | return False 159 | 160 | if not 0 <= netmask <= 32: 161 | return False 162 | else: 163 | try: 164 | socket.inet_pton(socket.AF_INET6, ip_address) 165 | except socket.error: 166 | return False 167 | 168 | if not 0 <= netmask <= 128: 169 | return False 170 | 171 | return True 172 | 173 | @classmethod 174 | def is_hex(cls, number): 175 | try: 176 | int(number, 16) 177 | except ValueError: 178 | return False 179 | 180 | return True 181 | 182 | @classmethod 183 | def is_valid_dsfield(cls, dsfield): 184 | 185 | dsmask = None 186 | 187 | if dsfield.count(':') == 1: 188 | dsval = dsfield.split(':')[0] 189 | else: 190 | dsval, dsmask = dsfield.split(':') 191 | 192 | if dsmask and not 0x01 <= int(dsmask, 16) <= 0xff and not 0x01 <= int(dsval, 16) <= 0xff: 193 | return False 194 | elif not 0x01 <= int(dsval, 16) <= 0xff: 195 | return False 196 | 197 | return True 198 | 199 | def flow_exists(self): 200 | cmd = [self.module.get_bin_path('flowadm')] 201 | 202 | cmd.append('show-flow') 203 | cmd.append(self.name) 204 | 205 | (rc, _, _) = self.module.run_command(cmd) 206 | 207 | if rc == 0: 208 | return True 209 | else: 210 | return False 211 | 212 | def delete_flow(self): 213 | cmd = [self.module.get_bin_path('flowadm')] 214 | 215 | cmd.append('remove-flow') 216 | if self.temporary: 217 | cmd.append('-t') 218 | cmd.append(self.name) 219 | 220 | return self.module.run_command(cmd) 221 | 222 | def create_flow(self): 223 | cmd = [self.module.get_bin_path('flowadm')] 224 | 225 | cmd.append('add-flow') 226 | cmd.append('-l') 227 | cmd.append(self.link) 228 | 229 | if self.local_ip: 230 | cmd.append('-a') 231 | cmd.append('local_ip=' + self.local_ip) 232 | 233 | if self.remote_ip: 234 | cmd.append('-a') 235 | cmd.append('remote_ip=' + self.remote_ip) 236 | 237 | if self.transport: 238 | cmd.append('-a') 239 | cmd.append('transport=' + self.transport) 240 | 241 | if self.local_port: 242 | cmd.append('-a') 243 | cmd.append('local_port=' + self.local_port) 244 | 245 | if self.dsfield: 246 | cmd.append('-a') 247 | cmd.append('dsfield=' + self.dsfield) 248 | 249 | if self.maxbw: 250 | cmd.append('-p') 251 | cmd.append('maxbw=' + self.maxbw) 252 | 253 | if self.priority: 254 | cmd.append('-p') 255 | cmd.append('priority=' + self.priority) 256 | 257 | if self.temporary: 258 | cmd.append('-t') 259 | cmd.append(self.name) 260 | 261 | return self.module.run_command(cmd) 262 | 263 | def _query_flow_props(self): 264 | cmd = [self.module.get_bin_path('flowadm')] 265 | 266 | cmd.append('show-flowprop') 267 | cmd.append('-c') 268 | cmd.append('-o') 269 | cmd.append('property,possible') 270 | cmd.append(self.name) 271 | 272 | return self.module.run_command(cmd) 273 | 274 | def flow_needs_udpating(self): 275 | (rc, out, err) = self._query_flow_props() 276 | 277 | NEEDS_UPDATING = False 278 | 279 | if rc == 0: 280 | properties = (line.split(':') for line in out.rstrip().split('\n')) 281 | for prop, value in properties: 282 | if prop == 'maxbw' and self.maxbw != value: 283 | self._needs_updating.update({prop: True}) 284 | NEEDS_UPDATING = True 285 | 286 | elif prop == 'priority' and self.priority != value: 287 | self._needs_updating.update({prop: True}) 288 | NEEDS_UPDATING = True 289 | 290 | return NEEDS_UPDATING 291 | else: 292 | self.module.fail_json(msg='Error while checking flow properties: %s' % err, 293 | stderr=err, 294 | rc=rc) 295 | 296 | def update_flow(self): 297 | cmd = [self.module.get_bin_path('flowadm')] 298 | 299 | cmd.append('set-flowprop') 300 | 301 | if self.maxbw and self._needs_updating['maxbw']: 302 | cmd.append('-p') 303 | cmd.append('maxbw=' + self.maxbw) 304 | 305 | if self.priority and self._needs_updating['priority']: 306 | cmd.append('-p') 307 | cmd.append('priority=' + self.priority) 308 | 309 | if self.temporary: 310 | cmd.append('-t') 311 | cmd.append(self.name) 312 | 313 | return self.module.run_command(cmd) 314 | 315 | 316 | def main(): 317 | module = AnsibleModule( 318 | argument_spec=dict( 319 | name=dict(required=True, aliases=['flow']), 320 | link=dict(required=False), 321 | local_ip=dict(required=False), 322 | remote_ip=dict(required=False), 323 | transport=dict(required=False, choices=SUPPORTED_TRANSPORTS), 324 | local_port=dict(required=False), 325 | dsfield=dict(required=False), 326 | maxbw=dict(required=False), 327 | priority=dict(required=False, 328 | default='medium', 329 | choices=SUPPORTED_PRIORITIES), 330 | temporary=dict(default=False, type='bool'), 331 | state=dict(required=False, 332 | default='present', 333 | choices=['absent', 'present', 'resetted']), 334 | ), 335 | mutually_exclusive=[ 336 | ('local_ip', 'remote_ip'), 337 | ('local_ip', 'transport'), 338 | ('local_ip', 'local_port'), 339 | ('local_ip', 'dsfield'), 340 | ('remote_ip', 'transport'), 341 | ('remote_ip', 'local_port'), 342 | ('remote_ip', 'dsfield'), 343 | ('transport', 'dsfield'), 344 | ('local_port', 'dsfield'), 345 | ], 346 | supports_check_mode=True 347 | ) 348 | 349 | flow = Flow(module) 350 | 351 | rc = None 352 | out = '' 353 | err = '' 354 | result = {} 355 | result['name'] = flow.name 356 | result['state'] = flow.state 357 | result['temporary'] = flow.temporary 358 | 359 | if flow.link: 360 | result['link'] = flow.link 361 | 362 | if flow.maxbw: 363 | result['maxbw'] = flow.maxbw 364 | 365 | if flow.priority: 366 | result['priority'] = flow.priority 367 | 368 | if flow.local_ip: 369 | if flow.is_valid_address(flow.local_ip): 370 | result['local_ip'] = flow.local_ip 371 | 372 | if flow.remote_ip: 373 | if flow.is_valid_address(flow.remote_ip): 374 | result['remote_ip'] = flow.remote_ip 375 | 376 | if flow.transport: 377 | result['transport'] = flow.transport 378 | 379 | if flow.local_port: 380 | if flow.is_valid_port(flow.local_port): 381 | result['local_port'] = flow.local_port 382 | else: 383 | module.fail_json(msg='Invalid port: %s' % flow.local_port, 384 | rc=1) 385 | 386 | if flow.dsfield: 387 | if flow.is_valid_dsfield(flow.dsfield): 388 | result['dsfield'] = flow.dsfield 389 | else: 390 | module.fail_json(msg='Invalid dsfield: %s' % flow.dsfield, 391 | rc=1) 392 | 393 | if flow.state == 'absent': 394 | if flow.flow_exists(): 395 | if module.check_mode: 396 | module.exit_json(changed=True) 397 | 398 | (rc, out, err) = flow.delete_flow() 399 | if rc != 0: 400 | module.fail_json(msg='Error while deleting flow: "%s"' % err, 401 | name=flow.name, 402 | stderr=err, 403 | rc=rc) 404 | 405 | elif flow.state == 'present': 406 | if not flow.flow_exists(): 407 | if module.check_mode: 408 | module.exit_json(changed=True) 409 | 410 | (rc, out, err) = flow.create_flow() 411 | if rc != 0: 412 | module.fail_json(msg='Error while creating flow: "%s"' % err, 413 | name=flow.name, 414 | stderr=err, 415 | rc=rc) 416 | else: 417 | if flow.flow_needs_udpating(): 418 | (rc, out, err) = flow.update_flow() 419 | if rc != 0: 420 | module.fail_json(msg='Error while updating flow: "%s"' % err, 421 | name=flow.name, 422 | stderr=err, 423 | rc=rc) 424 | 425 | elif flow.state == 'resetted': 426 | if flow.flow_exists(): 427 | if module.check_mode: 428 | module.exit_json(changed=True) 429 | 430 | (rc, out, err) = flow.reset_flow() 431 | if rc != 0: 432 | module.fail_json(msg='Error while resetting flow: "%s"' % err, 433 | name=flow.name, 434 | stderr=err, 435 | rc=rc) 436 | 437 | if rc is None: 438 | result['changed'] = False 439 | else: 440 | result['changed'] = True 441 | 442 | if out: 443 | result['stdout'] = out 444 | if err: 445 | result['stderr'] = err 446 | 447 | module.exit_json(**result) 448 | 449 | 450 | from ansible.module_utils.basic import * 451 | main() 452 | --------------------------------------------------------------------------------