2017-08-01 18:11:43 +00:00
|
|
|
#!/usr/bin/python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
Introduce new 'required_by' argument_spec option (#28662)
* Introduce new "required_by' argument_spec option
This PR introduces a new **required_by** argument_spec option which allows you to say *"if parameter A is set, parameter B and C are required as well"*.
- The difference with **required_if** is that it can only add dependencies if a parameter is set to a specific value, not when it is just defined.
- The difference with **required_together** is that it has a commutative property, so: *"Parameter A and B are required together, if one of them has been defined"*.
As an example, we need this for the complex options that the xml module provides. One of the issues we often see is that users are not using the correct combination of options, and then are surprised that the module does not perform the requested action(s).
This would be solved by adding the correct dependencies, and mutual exclusives. For us this is important to get this shipped together with the new xml module in Ansible v2.4. (This is related to bugfix https://github.com/ansible/ansible/pull/28657)
```python
module = AnsibleModule(
argument_spec=dict(
path=dict(type='path', aliases=['dest', 'file']),
xmlstring=dict(type='str'),
xpath=dict(type='str'),
namespaces=dict(type='dict', default={}),
state=dict(type='str', default='present', choices=['absent',
'present'], aliases=['ensure']),
value=dict(type='raw'),
attribute=dict(type='raw'),
add_children=dict(type='list'),
set_children=dict(type='list'),
count=dict(type='bool', default=False),
print_match=dict(type='bool', default=False),
pretty_print=dict(type='bool', default=False),
content=dict(type='str', choices=['attribute', 'text']),
input_type=dict(type='str', default='yaml', choices=['xml',
'yaml']),
backup=dict(type='bool', default=False),
),
supports_check_mode=True,
required_by=dict(
add_children=['xpath'],
attribute=['value', 'xpath'],
content=['xpath'],
set_children=['xpath'],
value=['xpath'],
),
required_if=[
['count', True, ['xpath']],
['print_match', True, ['xpath']],
],
required_one_of=[
['path', 'xmlstring'],
['add_children', 'content', 'count', 'pretty_print', 'print_match', 'set_children', 'value'],
],
mutually_exclusive=[
['add_children', 'content', 'count', 'print_match','set_children', 'value'],
['path', 'xmlstring'],
],
)
```
* Rebase and fix conflict
* Add modules that use required_by functionality
* Update required_by schema
* Fix rebase issue
2019-02-15 00:57:45 +00:00
|
|
|
# Copyright: (c) 2016, Roman Belyakovsky <ihryamzik () gmail.com>
|
2017-08-01 21:32:54 +00:00
|
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
2017-08-01 18:11:43 +00:00
|
|
|
|
2017-08-01 19:48:48 +00:00
|
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
__metaclass__ = type
|
|
|
|
|
2017-08-01 21:32:54 +00:00
|
|
|
|
2017-08-16 03:16:38 +00:00
|
|
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
2017-08-01 18:11:43 +00:00
|
|
|
'status': ['stableinterface'],
|
|
|
|
'supported_by': 'community'}
|
|
|
|
|
|
|
|
DOCUMENTATION = '''
|
|
|
|
---
|
|
|
|
module: interfaces_file
|
|
|
|
short_description: Tweak settings in /etc/network/interfaces files
|
|
|
|
extends_documentation_fragment: files
|
|
|
|
description:
|
|
|
|
- Manage (add, remove, change) individual interface options in an interfaces-style file without having
|
|
|
|
to manage the file as a whole with, say, M(template) or M(assemble). Interface has to be presented in a file.
|
|
|
|
- Read information about interfaces from interfaces-styled files
|
|
|
|
version_added: "2.4"
|
|
|
|
options:
|
|
|
|
dest:
|
|
|
|
description:
|
|
|
|
- Path to the interfaces file
|
|
|
|
default: /etc/network/interfaces
|
|
|
|
iface:
|
|
|
|
description:
|
|
|
|
- Name of the interface, required for value changes or option remove
|
2018-08-16 18:27:10 +00:00
|
|
|
address_family:
|
|
|
|
description:
|
|
|
|
- Address family of the interface, useful if same interface name is used for both inet and inet6
|
|
|
|
version_added: "2.8"
|
2017-08-01 18:11:43 +00:00
|
|
|
option:
|
|
|
|
description:
|
|
|
|
- Name of the option, required for value changes or option remove
|
|
|
|
value:
|
|
|
|
description:
|
|
|
|
- If I(option) is not presented for the I(interface) and I(state) is C(present) option will be added.
|
|
|
|
If I(option) already exists and is not C(pre-up), C(up), C(post-up) or C(down), it's value will be updated.
|
|
|
|
C(pre-up), C(up), C(post-up) and C(down) options can't be updated, only adding new options, removing existing
|
|
|
|
ones or cleaning the whole option set are supported
|
|
|
|
backup:
|
|
|
|
description:
|
|
|
|
- Create a backup file including the timestamp information so you can get
|
|
|
|
the original file back if you somehow clobbered it incorrectly.
|
2018-03-15 21:15:24 +00:00
|
|
|
type: bool
|
|
|
|
default: 'no'
|
2017-08-01 18:11:43 +00:00
|
|
|
state:
|
|
|
|
description:
|
|
|
|
- If set to C(absent) the option or section will be removed if present instead of created.
|
|
|
|
default: "present"
|
|
|
|
choices: [ "present", "absent" ]
|
|
|
|
|
|
|
|
notes:
|
|
|
|
- If option is defined multiple times last one will be updated but all will be deleted in case of an absent state
|
|
|
|
requirements: []
|
|
|
|
author: "Roman Belyakovsky (@hryamzik)"
|
|
|
|
'''
|
|
|
|
|
|
|
|
RETURN = '''
|
|
|
|
dest:
|
|
|
|
description: destination file/path
|
|
|
|
returned: success
|
2018-12-18 21:25:30 +00:00
|
|
|
type: str
|
2017-08-01 18:11:43 +00:00
|
|
|
sample: "/etc/network/interfaces"
|
|
|
|
ifaces:
|
|
|
|
description: interfaces dictionary
|
|
|
|
returned: success
|
|
|
|
type: complex
|
|
|
|
contains:
|
|
|
|
ifaces:
|
|
|
|
description: interface dictionary
|
|
|
|
returned: success
|
2018-12-18 21:25:30 +00:00
|
|
|
type: dict
|
2017-08-01 18:11:43 +00:00
|
|
|
contains:
|
|
|
|
eth0:
|
|
|
|
description: Name of the interface
|
|
|
|
returned: success
|
2018-12-18 21:25:30 +00:00
|
|
|
type: dict
|
2017-08-01 18:11:43 +00:00
|
|
|
contains:
|
|
|
|
address_family:
|
|
|
|
description: interface address family
|
|
|
|
returned: success
|
2018-12-18 21:25:30 +00:00
|
|
|
type: str
|
2017-08-01 18:11:43 +00:00
|
|
|
sample: "inet"
|
|
|
|
method:
|
|
|
|
description: interface method
|
|
|
|
returned: success
|
2018-12-18 21:25:30 +00:00
|
|
|
type: str
|
2017-08-01 18:11:43 +00:00
|
|
|
sample: "manual"
|
|
|
|
mtu:
|
|
|
|
description: other options, all values returned as strings
|
|
|
|
returned: success
|
2018-12-18 21:25:30 +00:00
|
|
|
type: str
|
2017-08-01 18:11:43 +00:00
|
|
|
sample: "1500"
|
|
|
|
pre-up:
|
|
|
|
description: list of C(pre-up) scripts
|
|
|
|
returned: success
|
|
|
|
type: list
|
|
|
|
sample:
|
|
|
|
- "route add -net 10.10.10.0/24 gw 10.10.10.1 dev eth1"
|
|
|
|
- "route add -net 10.10.11.0/24 gw 10.10.11.1 dev eth2"
|
|
|
|
up:
|
|
|
|
description: list of C(up) scripts
|
|
|
|
returned: success
|
|
|
|
type: list
|
|
|
|
sample:
|
|
|
|
- "route add -net 10.10.10.0/24 gw 10.10.10.1 dev eth1"
|
|
|
|
- "route add -net 10.10.11.0/24 gw 10.10.11.1 dev eth2"
|
|
|
|
post-up:
|
|
|
|
description: list of C(post-up) scripts
|
|
|
|
returned: success
|
|
|
|
type: list
|
|
|
|
sample:
|
|
|
|
- "route add -net 10.10.10.0/24 gw 10.10.10.1 dev eth1"
|
|
|
|
- "route add -net 10.10.11.0/24 gw 10.10.11.1 dev eth2"
|
|
|
|
down:
|
|
|
|
description: list of C(down) scripts
|
|
|
|
returned: success
|
|
|
|
type: list
|
|
|
|
sample:
|
|
|
|
- "route del -net 10.10.10.0/24 gw 10.10.10.1 dev eth1"
|
|
|
|
- "route del -net 10.10.11.0/24 gw 10.10.11.1 dev eth2"
|
|
|
|
...
|
|
|
|
'''
|
|
|
|
|
|
|
|
EXAMPLES = '''
|
|
|
|
# Set eth1 mtu configuration value to 8000
|
|
|
|
- interfaces_file:
|
|
|
|
dest: /etc/network/interfaces.d/eth1.cfg
|
|
|
|
iface: eth1
|
|
|
|
option: mtu
|
|
|
|
value: 8000
|
|
|
|
backup: yes
|
|
|
|
state: present
|
|
|
|
register: eth1_cfg
|
|
|
|
'''
|
|
|
|
|
|
|
|
import os
|
|
|
|
import re
|
2017-08-01 21:32:54 +00:00
|
|
|
import tempfile
|
|
|
|
|
2017-08-01 18:11:43 +00:00
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
2018-03-20 21:33:14 +00:00
|
|
|
from ansible.module_utils._text import to_bytes
|
2017-08-01 18:11:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
def lineDict(line):
|
|
|
|
return {'line': line, 'line_type': 'unknown'}
|
|
|
|
|
|
|
|
|
2018-08-16 18:27:10 +00:00
|
|
|
def optionDict(line, iface, option, value, address_family):
|
|
|
|
return {'line': line, 'iface': iface, 'option': option, 'value': value, 'line_type': 'option', 'address_family': address_family}
|
2017-08-01 18:11:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
def getValueFromLine(s):
|
2017-11-21 18:24:37 +00:00
|
|
|
spaceRe = re.compile(r'\s+')
|
2017-08-01 18:11:43 +00:00
|
|
|
for m in spaceRe.finditer(s):
|
|
|
|
pass
|
|
|
|
valueEnd = m.start()
|
|
|
|
option = s.split()[0]
|
|
|
|
optionStart = s.find(option)
|
|
|
|
optionLen = len(option)
|
2017-11-21 18:24:37 +00:00
|
|
|
valueStart = re.search(r'\s', s[optionLen + optionStart:]).end() + optionLen + optionStart
|
2017-08-01 18:11:43 +00:00
|
|
|
return s[valueStart:valueEnd]
|
|
|
|
|
|
|
|
|
|
|
|
def read_interfaces_file(module, filename):
|
|
|
|
f = open(filename, 'r')
|
|
|
|
return read_interfaces_lines(module, f)
|
|
|
|
|
|
|
|
|
|
|
|
def read_interfaces_lines(module, line_strings):
|
|
|
|
lines = []
|
|
|
|
ifaces = {}
|
|
|
|
currently_processing = None
|
|
|
|
i = 0
|
|
|
|
for line in line_strings:
|
|
|
|
i += 1
|
|
|
|
words = line.split()
|
|
|
|
if len(words) < 1:
|
|
|
|
lines.append(lineDict(line))
|
|
|
|
continue
|
|
|
|
if words[0][0] == "#":
|
|
|
|
lines.append(lineDict(line))
|
|
|
|
continue
|
|
|
|
if words[0] == "mapping":
|
|
|
|
# currmap = calloc(1, sizeof *currmap);
|
|
|
|
lines.append(lineDict(line))
|
|
|
|
currently_processing = "MAPPING"
|
|
|
|
elif words[0] == "source":
|
|
|
|
lines.append(lineDict(line))
|
|
|
|
currently_processing = "NONE"
|
|
|
|
elif words[0] == "source-dir":
|
|
|
|
lines.append(lineDict(line))
|
|
|
|
currently_processing = "NONE"
|
2018-04-20 11:01:46 +00:00
|
|
|
elif words[0] == "source-directory":
|
|
|
|
lines.append(lineDict(line))
|
|
|
|
currently_processing = "NONE"
|
2017-08-01 18:11:43 +00:00
|
|
|
elif words[0] == "iface":
|
|
|
|
currif = {
|
|
|
|
"pre-up": [],
|
|
|
|
"up": [],
|
|
|
|
"down": [],
|
|
|
|
"post-up": []
|
|
|
|
}
|
2018-02-09 15:09:09 +00:00
|
|
|
iface_name = words[1]
|
|
|
|
try:
|
|
|
|
currif['address_family'] = words[2]
|
|
|
|
except IndexError:
|
|
|
|
currif['address_family'] = None
|
2018-08-16 18:27:10 +00:00
|
|
|
address_family = currif['address_family']
|
2018-02-09 15:09:09 +00:00
|
|
|
try:
|
|
|
|
currif['method'] = words[3]
|
|
|
|
except IndexError:
|
|
|
|
currif['method'] = None
|
2017-08-01 18:11:43 +00:00
|
|
|
|
|
|
|
ifaces[iface_name] = currif
|
2018-08-16 18:27:10 +00:00
|
|
|
lines.append({'line': line, 'iface': iface_name, 'line_type': 'iface', 'params': currif, 'address_family': address_family})
|
2017-08-01 18:11:43 +00:00
|
|
|
currently_processing = "IFACE"
|
|
|
|
elif words[0] == "auto":
|
|
|
|
lines.append(lineDict(line))
|
|
|
|
currently_processing = "NONE"
|
2018-05-11 12:47:06 +00:00
|
|
|
elif words[0].startswith("allow-"):
|
2017-08-01 18:11:43 +00:00
|
|
|
lines.append(lineDict(line))
|
|
|
|
currently_processing = "NONE"
|
|
|
|
elif words[0] == "no-auto-down":
|
|
|
|
lines.append(lineDict(line))
|
|
|
|
currently_processing = "NONE"
|
|
|
|
elif words[0] == "no-scripts":
|
|
|
|
lines.append(lineDict(line))
|
|
|
|
currently_processing = "NONE"
|
|
|
|
else:
|
|
|
|
if currently_processing == "IFACE":
|
|
|
|
option_name = words[0]
|
|
|
|
# TODO: if option_name in currif.options
|
|
|
|
value = getValueFromLine(line)
|
2018-08-16 18:27:10 +00:00
|
|
|
lines.append(optionDict(line, iface_name, option_name, value, address_family))
|
2017-08-01 18:11:43 +00:00
|
|
|
if option_name in ["pre-up", "up", "down", "post-up"]:
|
|
|
|
currif[option_name].append(value)
|
|
|
|
else:
|
|
|
|
currif[option_name] = value
|
|
|
|
elif currently_processing == "MAPPING":
|
|
|
|
lines.append(lineDict(line))
|
|
|
|
elif currently_processing == "NONE":
|
|
|
|
lines.append(lineDict(line))
|
|
|
|
else:
|
|
|
|
module.fail_json(msg="misplaced option %s in line %d" % (line, i))
|
|
|
|
return None, None
|
|
|
|
return lines, ifaces
|
|
|
|
|
|
|
|
|
2018-08-16 18:27:10 +00:00
|
|
|
def setInterfaceOption(module, lines, iface, option, raw_value, state, address_family=None):
|
2017-08-01 18:11:43 +00:00
|
|
|
value = str(raw_value)
|
|
|
|
changed = False
|
|
|
|
|
|
|
|
iface_lines = [item for item in lines if "iface" in item and item["iface"] == iface]
|
2018-08-16 18:27:10 +00:00
|
|
|
if address_family is not None:
|
|
|
|
iface_lines = [item for item in iface_lines
|
|
|
|
if "address_family" in item and item["address_family"] == address_family]
|
2017-08-01 18:11:43 +00:00
|
|
|
|
|
|
|
if len(iface_lines) < 1:
|
|
|
|
# interface not found
|
|
|
|
module.fail_json(msg="Error: interface %s not found" % iface)
|
|
|
|
return changed
|
|
|
|
|
|
|
|
iface_options = list(filter(lambda i: i['line_type'] == 'option', iface_lines))
|
|
|
|
target_options = list(filter(lambda i: i['option'] == option, iface_options))
|
|
|
|
|
|
|
|
if state == "present":
|
|
|
|
if len(target_options) < 1:
|
|
|
|
changed = True
|
|
|
|
# add new option
|
|
|
|
last_line_dict = iface_lines[-1]
|
2018-08-16 18:27:10 +00:00
|
|
|
lines = addOptionAfterLine(option, value, iface, lines, last_line_dict, iface_options, address_family)
|
2017-08-01 18:11:43 +00:00
|
|
|
else:
|
|
|
|
if option in ["pre-up", "up", "down", "post-up"]:
|
|
|
|
if len(list(filter(lambda i: i['value'] == value, target_options))) < 1:
|
|
|
|
changed = True
|
2018-08-16 18:27:10 +00:00
|
|
|
lines = addOptionAfterLine(option, value, iface, lines, target_options[-1], iface_options, address_family)
|
2017-08-01 18:11:43 +00:00
|
|
|
else:
|
|
|
|
# if more than one option found edit the last one
|
|
|
|
if target_options[-1]['value'] != value:
|
|
|
|
changed = True
|
|
|
|
target_option = target_options[-1]
|
|
|
|
old_line = target_option['line']
|
|
|
|
old_value = target_option['value']
|
2018-08-16 18:27:10 +00:00
|
|
|
address_family = target_option['address_family']
|
2017-08-01 18:11:43 +00:00
|
|
|
prefix_start = old_line.find(option)
|
|
|
|
optionLen = len(option)
|
2017-11-21 18:24:37 +00:00
|
|
|
old_value_position = re.search(r"\s+".join(old_value.split()), old_line[prefix_start + optionLen:])
|
2017-08-01 18:11:43 +00:00
|
|
|
start = old_value_position.start() + prefix_start + optionLen
|
|
|
|
end = old_value_position.end() + prefix_start + optionLen
|
|
|
|
line = old_line[:start] + value + old_line[end:]
|
|
|
|
index = len(lines) - lines[::-1].index(target_option) - 1
|
2018-08-16 18:27:10 +00:00
|
|
|
lines[index] = optionDict(line, iface, option, value, address_family)
|
2017-08-01 18:11:43 +00:00
|
|
|
elif state == "absent":
|
|
|
|
if len(target_options) >= 1:
|
|
|
|
if option in ["pre-up", "up", "down", "post-up"] and value is not None and value != "None":
|
|
|
|
for target_option in filter(lambda i: i['value'] == value, target_options):
|
|
|
|
changed = True
|
2018-03-28 09:21:43 +00:00
|
|
|
lines = list(filter(lambda ln: ln != target_option, lines))
|
2017-08-01 18:11:43 +00:00
|
|
|
else:
|
|
|
|
changed = True
|
|
|
|
for target_option in target_options:
|
2018-03-28 09:21:43 +00:00
|
|
|
lines = list(filter(lambda ln: ln != target_option, lines))
|
2017-08-01 18:11:43 +00:00
|
|
|
else:
|
|
|
|
module.fail_json(msg="Error: unsupported state %s, has to be either present or absent" % state)
|
|
|
|
|
|
|
|
return changed, lines
|
|
|
|
|
|
|
|
|
2018-08-16 18:27:10 +00:00
|
|
|
def addOptionAfterLine(option, value, iface, lines, last_line_dict, iface_options, address_family):
|
2018-04-19 09:05:39 +00:00
|
|
|
# Changing method of interface is not an addition
|
|
|
|
if option == 'method':
|
|
|
|
for ln in lines:
|
2018-04-27 09:18:19 +00:00
|
|
|
if ln.get('line_type', '') == 'iface' and ln.get('iface', '') == iface:
|
2018-04-19 09:05:39 +00:00
|
|
|
ln['line'] = re.sub(ln.get('params', {}).get('method', '') + '$', value, ln.get('line'))
|
|
|
|
ln['params']['method'] = value
|
|
|
|
return lines
|
|
|
|
|
2017-08-01 18:11:43 +00:00
|
|
|
last_line = last_line_dict['line']
|
|
|
|
prefix_start = last_line.find(last_line.split()[0])
|
|
|
|
suffix_start = last_line.rfind(last_line.split()[-1]) + len(last_line.split()[-1])
|
|
|
|
prefix = last_line[:prefix_start]
|
|
|
|
|
|
|
|
if len(iface_options) < 1:
|
|
|
|
# interface has no options, ident
|
|
|
|
prefix += " "
|
|
|
|
|
|
|
|
line = prefix + "%s %s" % (option, value) + last_line[suffix_start:]
|
2018-08-16 18:27:10 +00:00
|
|
|
option_dict = optionDict(line, iface, option, value, address_family)
|
2017-08-01 18:11:43 +00:00
|
|
|
index = len(lines) - lines[::-1].index(last_line_dict)
|
|
|
|
lines.insert(index, option_dict)
|
|
|
|
return lines
|
|
|
|
|
|
|
|
|
|
|
|
def write_changes(module, lines, dest):
|
|
|
|
|
|
|
|
tmpfd, tmpfile = tempfile.mkstemp()
|
|
|
|
f = os.fdopen(tmpfd, 'wb')
|
2018-03-28 09:21:43 +00:00
|
|
|
f.write(to_bytes(''.join(lines), errors='surrogate_or_strict'))
|
2017-08-01 18:11:43 +00:00
|
|
|
f.close()
|
|
|
|
module.atomic_move(tmpfile, os.path.realpath(dest))
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
module = AnsibleModule(
|
|
|
|
argument_spec=dict(
|
Introduce new 'required_by' argument_spec option (#28662)
* Introduce new "required_by' argument_spec option
This PR introduces a new **required_by** argument_spec option which allows you to say *"if parameter A is set, parameter B and C are required as well"*.
- The difference with **required_if** is that it can only add dependencies if a parameter is set to a specific value, not when it is just defined.
- The difference with **required_together** is that it has a commutative property, so: *"Parameter A and B are required together, if one of them has been defined"*.
As an example, we need this for the complex options that the xml module provides. One of the issues we often see is that users are not using the correct combination of options, and then are surprised that the module does not perform the requested action(s).
This would be solved by adding the correct dependencies, and mutual exclusives. For us this is important to get this shipped together with the new xml module in Ansible v2.4. (This is related to bugfix https://github.com/ansible/ansible/pull/28657)
```python
module = AnsibleModule(
argument_spec=dict(
path=dict(type='path', aliases=['dest', 'file']),
xmlstring=dict(type='str'),
xpath=dict(type='str'),
namespaces=dict(type='dict', default={}),
state=dict(type='str', default='present', choices=['absent',
'present'], aliases=['ensure']),
value=dict(type='raw'),
attribute=dict(type='raw'),
add_children=dict(type='list'),
set_children=dict(type='list'),
count=dict(type='bool', default=False),
print_match=dict(type='bool', default=False),
pretty_print=dict(type='bool', default=False),
content=dict(type='str', choices=['attribute', 'text']),
input_type=dict(type='str', default='yaml', choices=['xml',
'yaml']),
backup=dict(type='bool', default=False),
),
supports_check_mode=True,
required_by=dict(
add_children=['xpath'],
attribute=['value', 'xpath'],
content=['xpath'],
set_children=['xpath'],
value=['xpath'],
),
required_if=[
['count', True, ['xpath']],
['print_match', True, ['xpath']],
],
required_one_of=[
['path', 'xmlstring'],
['add_children', 'content', 'count', 'pretty_print', 'print_match', 'set_children', 'value'],
],
mutually_exclusive=[
['add_children', 'content', 'count', 'print_match','set_children', 'value'],
['path', 'xmlstring'],
],
)
```
* Rebase and fix conflict
* Add modules that use required_by functionality
* Update required_by schema
* Fix rebase issue
2019-02-15 00:57:45 +00:00
|
|
|
dest=dict(type='path', default='/etc/network/interfaces'),
|
|
|
|
iface=dict(type='str'),
|
|
|
|
address_family=dict(type='str'),
|
|
|
|
option=dict(type='str'),
|
|
|
|
value=dict(type='str'),
|
|
|
|
backup=dict(type='bool', default=False),
|
|
|
|
state=dict(type='str', default='present', choices=['absent', 'present']),
|
2017-08-01 18:11:43 +00:00
|
|
|
),
|
|
|
|
add_file_common_args=True,
|
Introduce new 'required_by' argument_spec option (#28662)
* Introduce new "required_by' argument_spec option
This PR introduces a new **required_by** argument_spec option which allows you to say *"if parameter A is set, parameter B and C are required as well"*.
- The difference with **required_if** is that it can only add dependencies if a parameter is set to a specific value, not when it is just defined.
- The difference with **required_together** is that it has a commutative property, so: *"Parameter A and B are required together, if one of them has been defined"*.
As an example, we need this for the complex options that the xml module provides. One of the issues we often see is that users are not using the correct combination of options, and then are surprised that the module does not perform the requested action(s).
This would be solved by adding the correct dependencies, and mutual exclusives. For us this is important to get this shipped together with the new xml module in Ansible v2.4. (This is related to bugfix https://github.com/ansible/ansible/pull/28657)
```python
module = AnsibleModule(
argument_spec=dict(
path=dict(type='path', aliases=['dest', 'file']),
xmlstring=dict(type='str'),
xpath=dict(type='str'),
namespaces=dict(type='dict', default={}),
state=dict(type='str', default='present', choices=['absent',
'present'], aliases=['ensure']),
value=dict(type='raw'),
attribute=dict(type='raw'),
add_children=dict(type='list'),
set_children=dict(type='list'),
count=dict(type='bool', default=False),
print_match=dict(type='bool', default=False),
pretty_print=dict(type='bool', default=False),
content=dict(type='str', choices=['attribute', 'text']),
input_type=dict(type='str', default='yaml', choices=['xml',
'yaml']),
backup=dict(type='bool', default=False),
),
supports_check_mode=True,
required_by=dict(
add_children=['xpath'],
attribute=['value', 'xpath'],
content=['xpath'],
set_children=['xpath'],
value=['xpath'],
),
required_if=[
['count', True, ['xpath']],
['print_match', True, ['xpath']],
],
required_one_of=[
['path', 'xmlstring'],
['add_children', 'content', 'count', 'pretty_print', 'print_match', 'set_children', 'value'],
],
mutually_exclusive=[
['add_children', 'content', 'count', 'print_match','set_children', 'value'],
['path', 'xmlstring'],
],
)
```
* Rebase and fix conflict
* Add modules that use required_by functionality
* Update required_by schema
* Fix rebase issue
2019-02-15 00:57:45 +00:00
|
|
|
supports_check_mode=True,
|
|
|
|
required_by=dict(
|
2019-02-19 19:29:51 +00:00
|
|
|
option=('iface',),
|
Introduce new 'required_by' argument_spec option (#28662)
* Introduce new "required_by' argument_spec option
This PR introduces a new **required_by** argument_spec option which allows you to say *"if parameter A is set, parameter B and C are required as well"*.
- The difference with **required_if** is that it can only add dependencies if a parameter is set to a specific value, not when it is just defined.
- The difference with **required_together** is that it has a commutative property, so: *"Parameter A and B are required together, if one of them has been defined"*.
As an example, we need this for the complex options that the xml module provides. One of the issues we often see is that users are not using the correct combination of options, and then are surprised that the module does not perform the requested action(s).
This would be solved by adding the correct dependencies, and mutual exclusives. For us this is important to get this shipped together with the new xml module in Ansible v2.4. (This is related to bugfix https://github.com/ansible/ansible/pull/28657)
```python
module = AnsibleModule(
argument_spec=dict(
path=dict(type='path', aliases=['dest', 'file']),
xmlstring=dict(type='str'),
xpath=dict(type='str'),
namespaces=dict(type='dict', default={}),
state=dict(type='str', default='present', choices=['absent',
'present'], aliases=['ensure']),
value=dict(type='raw'),
attribute=dict(type='raw'),
add_children=dict(type='list'),
set_children=dict(type='list'),
count=dict(type='bool', default=False),
print_match=dict(type='bool', default=False),
pretty_print=dict(type='bool', default=False),
content=dict(type='str', choices=['attribute', 'text']),
input_type=dict(type='str', default='yaml', choices=['xml',
'yaml']),
backup=dict(type='bool', default=False),
),
supports_check_mode=True,
required_by=dict(
add_children=['xpath'],
attribute=['value', 'xpath'],
content=['xpath'],
set_children=['xpath'],
value=['xpath'],
),
required_if=[
['count', True, ['xpath']],
['print_match', True, ['xpath']],
],
required_one_of=[
['path', 'xmlstring'],
['add_children', 'content', 'count', 'pretty_print', 'print_match', 'set_children', 'value'],
],
mutually_exclusive=[
['add_children', 'content', 'count', 'print_match','set_children', 'value'],
['path', 'xmlstring'],
],
)
```
* Rebase and fix conflict
* Add modules that use required_by functionality
* Update required_by schema
* Fix rebase issue
2019-02-15 00:57:45 +00:00
|
|
|
),
|
2017-08-01 18:11:43 +00:00
|
|
|
)
|
|
|
|
|
2017-10-07 21:57:13 +00:00
|
|
|
dest = module.params['dest']
|
2017-08-01 18:11:43 +00:00
|
|
|
iface = module.params['iface']
|
2018-08-16 18:27:10 +00:00
|
|
|
address_family = module.params['address_family']
|
2017-08-01 18:11:43 +00:00
|
|
|
option = module.params['option']
|
|
|
|
value = module.params['value']
|
|
|
|
backup = module.params['backup']
|
|
|
|
state = module.params['state']
|
|
|
|
|
|
|
|
if option is not None and state == "present" and value is None:
|
|
|
|
module.fail_json(msg="Value must be set if option is defined and state is 'present'")
|
|
|
|
|
|
|
|
lines, ifaces = read_interfaces_file(module, dest)
|
|
|
|
|
|
|
|
changed = False
|
|
|
|
|
|
|
|
if option is not None:
|
2018-08-16 18:27:10 +00:00
|
|
|
changed, lines = setInterfaceOption(module, lines, iface, option, value, state, address_family)
|
2017-08-01 18:11:43 +00:00
|
|
|
|
|
|
|
if changed:
|
|
|
|
_, ifaces = read_interfaces_lines(module, [d['line'] for d in lines if 'line' in d])
|
|
|
|
|
|
|
|
if changed and not module.check_mode:
|
|
|
|
if backup:
|
|
|
|
module.backup_local(dest)
|
|
|
|
write_changes(module, [d['line'] for d in lines if 'line' in d], dest)
|
|
|
|
|
|
|
|
module.exit_json(dest=dest, changed=changed, ifaces=ifaces)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|