1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3
4# (c) 2016, Adam Števko <adam.stevko@gmail.com>
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7from __future__ import absolute_import, division, print_function
8__metaclass__ = type
9
10
11DOCUMENTATION = '''
12---
13module: ipadm_addr
14short_description: Manage IP addresses on an interface on Solaris/illumos systems
15description:
16    - Create/delete static/dynamic IP addresses on network interfaces on Solaris/illumos systems.
17    - Up/down static/dynamic IP addresses on network interfaces on Solaris/illumos systems.
18    - Manage IPv6 link-local addresses on network interfaces on Solaris/illumos systems.
19author: Adam Števko (@xen0l)
20options:
21    address:
22        description:
23            - Specifiies an IP address to configure in CIDR notation.
24        required: false
25        aliases: [ "addr" ]
26    addrtype:
27        description:
28            - Specifiies a type of IP address to configure.
29        required: false
30        default: static
31        choices: [ 'static', 'dhcp', 'addrconf' ]
32    addrobj:
33        description:
34            - Specifies an unique IP address on the system.
35        required: true
36    temporary:
37        description:
38            - Specifies that the configured IP address is temporary. Temporary
39              IP addresses do not persist across reboots.
40        required: false
41        default: false
42        type: bool
43    wait:
44        description:
45            - Specifies the time in seconds we wait for obtaining address via DHCP.
46        required: false
47        default: 60
48    state:
49        description:
50            - Create/delete/enable/disable an IP address on the network interface.
51        required: false
52        default: present
53        choices: [ 'absent', 'present', 'up', 'down', 'enabled', 'disabled', 'refreshed' ]
54'''
55
56EXAMPLES = '''
57- name: Configure IP address 10.0.0.1 on e1000g0
58  community.network.ipadm_addr: addr=10.0.0.1/32 addrobj=e1000g0/v4 state=present
59
60- name: Delete addrobj
61  community.network.ipadm_addr: addrobj=e1000g0/v4 state=absent
62
63- name: Configure link-local IPv6 address
64  community.network.ipadm_addr: addtype=addrconf addrobj=vnic0/v6
65
66- name: Configure address via DHCP and wait 180 seconds for address obtaining
67  community.network.ipadm_addr: addrobj=vnic0/dhcp addrtype=dhcp wait=180
68'''
69
70RETURN = '''
71addrobj:
72    description: address object name
73    returned: always
74    type: str
75    sample: bge0/v4
76state:
77    description: state of the target
78    returned: always
79    type: str
80    sample: present
81temporary:
82    description: specifies if operation will persist across reboots
83    returned: always
84    type: bool
85    sample: True
86addrtype:
87    description: address type
88    returned: always
89    type: str
90    sample: static
91address:
92    description: IP address
93    returned: only if addrtype is 'static'
94    type: str
95    sample: 1.3.3.7/32
96wait:
97    description: time we wait for DHCP
98    returned: only if addrtype is 'dhcp'
99    type: str
100    sample: 10
101'''
102
103import socket
104
105from ansible.module_utils.basic import AnsibleModule
106
107
108SUPPORTED_TYPES = ['static', 'addrconf', 'dhcp']
109
110
111class Addr(object):
112
113    def __init__(self, module):
114        self.module = module
115
116        self.address = module.params['address']
117        self.addrtype = module.params['addrtype']
118        self.addrobj = module.params['addrobj']
119        self.temporary = module.params['temporary']
120        self.state = module.params['state']
121        self.wait = module.params['wait']
122
123    def is_cidr_notation(self):
124
125        return self.address.count('/') == 1
126
127    def is_valid_address(self):
128
129        ip_address = self.address.split('/')[0]
130
131        try:
132            if len(ip_address.split('.')) == 4:
133                socket.inet_pton(socket.AF_INET, ip_address)
134            else:
135                socket.inet_pton(socket.AF_INET6, ip_address)
136        except socket.error:
137            return False
138
139        return True
140
141    def is_dhcp(self):
142        cmd = [self.module.get_bin_path('ipadm')]
143
144        cmd.append('show-addr')
145        cmd.append('-p')
146        cmd.append('-o')
147        cmd.append('type')
148        cmd.append(self.addrobj)
149
150        (rc, out, err) = self.module.run_command(cmd)
151
152        if rc == 0:
153            if out.rstrip() != 'dhcp':
154                return False
155
156            return True
157        else:
158            self.module.fail_json(msg='Wrong addrtype %s for addrobj "%s": %s' % (out, self.addrobj, err),
159                                  rc=rc,
160                                  stderr=err)
161
162    def addrobj_exists(self):
163        cmd = [self.module.get_bin_path('ipadm')]
164
165        cmd.append('show-addr')
166        cmd.append(self.addrobj)
167
168        (rc, _, _) = self.module.run_command(cmd)
169
170        if rc == 0:
171            return True
172        else:
173            return False
174
175    def delete_addr(self):
176        cmd = [self.module.get_bin_path('ipadm')]
177
178        cmd.append('delete-addr')
179        cmd.append(self.addrobj)
180
181        return self.module.run_command(cmd)
182
183    def create_addr(self):
184        cmd = [self.module.get_bin_path('ipadm')]
185
186        cmd.append('create-addr')
187        cmd.append('-T')
188        cmd.append(self.addrtype)
189
190        if self.temporary:
191            cmd.append('-t')
192
193        if self.addrtype == 'static':
194            cmd.append('-a')
195            cmd.append(self.address)
196
197        if self.addrtype == 'dhcp' and self.wait:
198            cmd.append('-w')
199            cmd.append(self.wait)
200
201        cmd.append(self.addrobj)
202
203        return self.module.run_command(cmd)
204
205    def up_addr(self):
206        cmd = [self.module.get_bin_path('ipadm')]
207
208        cmd.append('up-addr')
209
210        if self.temporary:
211            cmd.append('-t')
212
213        cmd.append(self.addrobj)
214
215        return self.module.run_command(cmd)
216
217    def down_addr(self):
218        cmd = [self.module.get_bin_path('ipadm')]
219
220        cmd.append('down-addr')
221
222        if self.temporary:
223            cmd.append('-t')
224
225        cmd.append(self.addrobj)
226
227        return self.module.run_command(cmd)
228
229    def enable_addr(self):
230        cmd = [self.module.get_bin_path('ipadm')]
231
232        cmd.append('enable-addr')
233        cmd.append('-t')
234        cmd.append(self.addrobj)
235
236        return self.module.run_command(cmd)
237
238    def disable_addr(self):
239        cmd = [self.module.get_bin_path('ipadm')]
240
241        cmd.append('disable-addr')
242        cmd.append('-t')
243        cmd.append(self.addrobj)
244
245        return self.module.run_command(cmd)
246
247    def refresh_addr(self):
248        cmd = [self.module.get_bin_path('ipadm')]
249
250        cmd.append('refresh-addr')
251        cmd.append(self.addrobj)
252
253        return self.module.run_command(cmd)
254
255
256def main():
257    module = AnsibleModule(
258        argument_spec=dict(
259            address=dict(aliases=['addr']),
260            addrtype=dict(default='static', choices=SUPPORTED_TYPES),
261            addrobj=dict(required=True),
262            temporary=dict(default=False, type='bool'),
263            state=dict(
264                default='present', choices=['absent', 'present', 'up', 'down', 'enabled', 'disabled', 'refreshed']),
265            wait=dict(default=60, type='int'),
266        ),
267        mutually_exclusive=[
268            ('address', 'wait'),
269        ],
270        supports_check_mode=True
271    )
272
273    addr = Addr(module)
274
275    rc = None
276    out = ''
277    err = ''
278    result = {}
279    result['addrobj'] = addr.addrobj
280    result['state'] = addr.state
281    result['temporary'] = addr.temporary
282    result['addrtype'] = addr.addrtype
283
284    if addr.addrtype == 'static' and addr.address:
285        if addr.is_cidr_notation() and addr.is_valid_address():
286            result['address'] = addr.address
287        else:
288            module.fail_json(msg='Invalid IP address: %s' % addr.address)
289
290    if addr.addrtype == 'dhcp' and addr.wait:
291        result['wait'] = addr.wait
292
293    if addr.state == 'absent':
294        if addr.addrobj_exists():
295            if module.check_mode:
296                module.exit_json(changed=True)
297
298            (rc, out, err) = addr.delete_addr()
299            if rc != 0:
300                module.fail_json(msg='Error while deleting addrobj: "%s"' % err,
301                                 addrobj=addr.addrobj,
302                                 stderr=err,
303                                 rc=rc)
304
305    elif addr.state == 'present':
306        if not addr.addrobj_exists():
307            if module.check_mode:
308                module.exit_json(changed=True)
309
310            (rc, out, err) = addr.create_addr()
311            if rc != 0:
312                module.fail_json(msg='Error while configuring IP address: "%s"' % err,
313                                 addrobj=addr.addrobj,
314                                 addr=addr.address,
315                                 stderr=err,
316                                 rc=rc)
317
318    elif addr.state == 'up':
319        if addr.addrobj_exists():
320            if module.check_mode:
321                module.exit_json(changed=True)
322
323            (rc, out, err) = addr.up_addr()
324            if rc != 0:
325                module.fail_json(msg='Error while bringing IP address up: "%s"' % err,
326                                 addrobj=addr.addrobj,
327                                 stderr=err,
328                                 rc=rc)
329
330    elif addr.state == 'down':
331        if addr.addrobj_exists():
332            if module.check_mode:
333                module.exit_json(changed=True)
334
335            (rc, out, err) = addr.down_addr()
336            if rc != 0:
337                module.fail_json(msg='Error while bringing IP address down: "%s"' % err,
338                                 addrobj=addr.addrobj,
339                                 stderr=err,
340                                 rc=rc)
341
342    elif addr.state == 'refreshed':
343        if addr.addrobj_exists():
344            if addr.is_dhcp():
345                if module.check_mode:
346                    module.exit_json(changed=True)
347
348                (rc, out, err) = addr.refresh_addr()
349                if rc != 0:
350                    module.fail_json(msg='Error while refreshing IP address: "%s"' % err,
351                                     addrobj=addr.addrobj,
352                                     stderr=err,
353                                     rc=rc)
354            else:
355                module.fail_json(msg='state "refreshed" cannot be used with "%s" addrtype' % addr.addrtype,
356                                 addrobj=addr.addrobj,
357                                 stderr=err,
358                                 rc=1)
359
360    elif addr.state == 'enabled':
361        if addr.addrobj_exists():
362            if module.check_mode:
363                module.exit_json(changed=True)
364
365            (rc, out, err) = addr.enable_addr()
366            if rc != 0:
367                module.fail_json(msg='Error while enabling IP address: "%s"' % err,
368                                 addrobj=addr.addrobj,
369                                 stderr=err,
370                                 rc=rc)
371
372    elif addr.state == 'disabled':
373        if addr.addrobj_exists():
374            if module.check_mode:
375                module.exit_json(changed=True)
376
377            (rc, out, err) = addr.disable_addr()
378            if rc != 0:
379                module.fail_json(msg='Error while disabling IP address: "%s"' % err,
380                                 addrobj=addr.addrobj,
381                                 stderr=err,
382                                 rc=rc)
383
384    if rc is None:
385        result['changed'] = False
386    else:
387        result['changed'] = True
388
389    if out:
390        result['stdout'] = out
391    if err:
392        result['stderr'] = err
393
394    module.exit_json(**result)
395
396
397if __name__ == '__main__':
398    main()
399