updates network_common lib (#21306)
* removes connection functions refactored into connection * updates ComplexDict and ComplexList objects to use with AnsibleModule * updates modules to add new argument to ComplexList & ComplexDict
This commit is contained in:
parent
009ac075b7
commit
b0c01bbb82
7 changed files with 111 additions and 103 deletions
|
@ -25,13 +25,8 @@
|
||||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
#
|
#
|
||||||
import socket
|
|
||||||
import struct
|
|
||||||
import signal
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import get_exception
|
|
||||||
from ansible.module_utils._text import to_bytes, to_native
|
|
||||||
from ansible.module_utils.six import iteritems
|
from ansible.module_utils.six import iteritems
|
||||||
|
from ansible.module_utils.basic import AnsibleFallbackNotFound
|
||||||
|
|
||||||
def to_list(val):
|
def to_list(val):
|
||||||
if isinstance(val, (list, tuple, set)):
|
if isinstance(val, (list, tuple, set)):
|
||||||
|
@ -41,103 +36,116 @@ def to_list(val):
|
||||||
else:
|
else:
|
||||||
return list()
|
return list()
|
||||||
|
|
||||||
class ComplexDict:
|
class ComplexDict(object):
|
||||||
|
"""Transforms a dict to with an argument spec
|
||||||
|
|
||||||
def __init__(self, attrs):
|
This class will take a dict and apply an Ansible argument spec to the
|
||||||
|
values. The resulting dict will contain all of the keys in the param
|
||||||
|
with appropriate values set.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
argument_spec = dict(
|
||||||
|
command=dict(key=True),
|
||||||
|
display=dict(default='text', choices=['text', 'json']),
|
||||||
|
validate=dict(type='bool')
|
||||||
|
)
|
||||||
|
transform = ComplexDict(argument_spec, module)
|
||||||
|
value = dict(command='foo')
|
||||||
|
result = transform(value)
|
||||||
|
print result
|
||||||
|
{'command': 'foo', 'display': 'text', 'validate': None}
|
||||||
|
|
||||||
|
Supported argument spec:
|
||||||
|
* key - specifies how to map a single value to a dict
|
||||||
|
* read_from - read and apply the argument_spec from the module
|
||||||
|
* required - a value is required
|
||||||
|
* type - type of value (uses AnsibleModule type checker)
|
||||||
|
* fallback - implements fallback function
|
||||||
|
* choices - set of valid options
|
||||||
|
* default - default value
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, attrs, module):
|
||||||
self._attributes = attrs
|
self._attributes = attrs
|
||||||
|
self._module = module
|
||||||
self.attr_names = frozenset(self._attributes.keys())
|
self.attr_names = frozenset(self._attributes.keys())
|
||||||
|
|
||||||
|
self._has_key = False
|
||||||
|
for name, attr in iteritems(self._attributes):
|
||||||
|
if attr.get('read_from'):
|
||||||
|
spec = self._module.argument_spec.get(attr['read_from'])
|
||||||
|
if not spec:
|
||||||
|
raise ValueError('argument_spec %s does not exist' % attr['read_from'])
|
||||||
|
for key, value in iteritems(spec):
|
||||||
|
if key not in attr:
|
||||||
|
attr[key] = value
|
||||||
|
|
||||||
|
if attr.get('key'):
|
||||||
|
if self._has_key:
|
||||||
|
raise ValueError('only one key value can be specified')
|
||||||
|
self_has_key = True
|
||||||
|
attr['required'] = True
|
||||||
|
|
||||||
|
|
||||||
|
def _dict(self, value):
|
||||||
|
obj = {}
|
||||||
for name, attr in iteritems(self._attributes):
|
for name, attr in iteritems(self._attributes):
|
||||||
if attr.get('key'):
|
if attr.get('key'):
|
||||||
attr['required'] = True
|
obj[name] = value
|
||||||
|
else:
|
||||||
|
obj[name] = attr.get('default')
|
||||||
|
return obj
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
if isinstance(value, dict):
|
if not isinstance(value, dict):
|
||||||
unknown = set(value.keys()).difference(self.attr_names)
|
value = self._dict(value)
|
||||||
if unknown:
|
|
||||||
raise ValueError('invalid keys: %s' % ','.join(unknown))
|
|
||||||
for name, attr in iteritems(self._attributes):
|
|
||||||
if attr.get('required') and name not in value:
|
|
||||||
raise ValueError('missing required attribute %s' % name)
|
|
||||||
if not value.get(name):
|
|
||||||
value[name] = attr.get('default')
|
|
||||||
return value
|
|
||||||
else:
|
|
||||||
obj = {}
|
|
||||||
for name, attr in iteritems(self._attributes):
|
|
||||||
if attr.get('key'):
|
|
||||||
obj[name] = value
|
|
||||||
else:
|
|
||||||
obj[name] = attr.get('default')
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
unknown = set(value).difference(self.attr_names)
|
||||||
|
if unknown:
|
||||||
|
raise ValueError('invalid keys: %s' % ','.join(unknown))
|
||||||
|
|
||||||
class ComplexList:
|
|
||||||
|
|
||||||
def __init__(self, attrs):
|
|
||||||
self._attributes = attrs
|
|
||||||
self.attr_names = frozenset(self._attributes.keys())
|
|
||||||
for name, attr in iteritems(self._attributes):
|
for name, attr in iteritems(self._attributes):
|
||||||
if attr.get('key'):
|
if not value.get(name):
|
||||||
attr['required'] = True
|
value[name] = attr.get('default')
|
||||||
|
|
||||||
|
if attr.get('fallback') and not value.get(name):
|
||||||
|
fallback = attr.get('fallback', (None,))
|
||||||
|
fallback_strategy = fallback[0]
|
||||||
|
fallback_args = []
|
||||||
|
fallback_kwargs = {}
|
||||||
|
if fallback_strategy is not None:
|
||||||
|
for item in fallback[1:]:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
fallback_kwargs = item
|
||||||
|
else:
|
||||||
|
fallback_args = item
|
||||||
|
try:
|
||||||
|
value[name] = fallback_strategy(*fallback_args, **fallback_kwargs)
|
||||||
|
except AnsibleFallbackNotFound:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if attr.get('required') and value.get(name) is None:
|
||||||
|
raise ValueError('missing required attribute %s' % name)
|
||||||
|
|
||||||
|
if 'choices' in attr:
|
||||||
|
if value[name] not in attr['choices']:
|
||||||
|
raise ValueError('%s must be one of %s, got %s' % \
|
||||||
|
(name, ', '.join(attr['choices']), value[name]))
|
||||||
|
|
||||||
|
if value[name] is not None:
|
||||||
|
value_type = attr.get('type', 'str')
|
||||||
|
type_checker = self._module._CHECK_ARGUMENT_TYPES_DISPATCHER[value_type]
|
||||||
|
type_checker(value[name])
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
class ComplexList(ComplexDict):
|
||||||
|
"""Extends ```ComplexDict``` to handle a list of dicts """
|
||||||
|
|
||||||
def __call__(self, values):
|
def __call__(self, values):
|
||||||
objects = list()
|
if not isinstance(values, (list, tuple)):
|
||||||
for value in values:
|
raise TypeError('value must be an ordered iterable')
|
||||||
if isinstance(value, dict):
|
return [(super(ComplexList, self).__call__(v)) for v in values]
|
||||||
for name, attr in iteritems(self._attributes):
|
|
||||||
if attr.get('required') and name not in value:
|
|
||||||
raise ValueError('missing required attr %s' % name)
|
|
||||||
if not value.get(name):
|
|
||||||
value[name] = attr.get('default')
|
|
||||||
objects.append(value)
|
|
||||||
else:
|
|
||||||
obj = {}
|
|
||||||
for name, attr in iteritems(self._attributes):
|
|
||||||
if attr.get('key'):
|
|
||||||
obj[name] = value
|
|
||||||
else:
|
|
||||||
obj[name] = attr.get('default')
|
|
||||||
objects.append(obj)
|
|
||||||
return objects
|
|
||||||
|
|
||||||
def send_data(s, data):
|
|
||||||
packed_len = struct.pack('!Q',len(data))
|
|
||||||
return s.sendall(packed_len + data)
|
|
||||||
|
|
||||||
def recv_data(s):
|
|
||||||
header_len = 8 # size of a packed unsigned long long
|
|
||||||
data = to_bytes("")
|
|
||||||
while len(data) < header_len:
|
|
||||||
d = s.recv(header_len - len(data))
|
|
||||||
if not d:
|
|
||||||
return None
|
|
||||||
data += d
|
|
||||||
data_len = struct.unpack('!Q',data[:header_len])[0]
|
|
||||||
data = data[header_len:]
|
|
||||||
while len(data) < data_len:
|
|
||||||
d = s.recv(data_len - len(data))
|
|
||||||
if not d:
|
|
||||||
return None
|
|
||||||
data += d
|
|
||||||
return data
|
|
||||||
|
|
||||||
def exec_command(module, command):
|
|
||||||
try:
|
|
||||||
sf = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
||||||
sf.connect(module._socket_path)
|
|
||||||
|
|
||||||
data = "EXEC: %s" % command
|
|
||||||
send_data(sf, to_bytes(data.strip()))
|
|
||||||
|
|
||||||
rc = int(recv_data(sf), 10)
|
|
||||||
stdout = recv_data(sf)
|
|
||||||
stderr = recv_data(sf)
|
|
||||||
except socket.error:
|
|
||||||
exc = get_exception()
|
|
||||||
sf.close()
|
|
||||||
module.fail_json(msg='unable to connect to socket', err=str(exc))
|
|
||||||
|
|
||||||
sf.close()
|
|
||||||
|
|
||||||
return (rc, to_native(stdout), to_native(stderr))
|
|
||||||
|
|
|
@ -143,13 +143,14 @@ def to_lines(stdout):
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
def parse_commands(module, warnings):
|
def parse_commands(module, warnings):
|
||||||
transform = ComplexList(dict(
|
spec = dict(
|
||||||
command=dict(key=True),
|
command=dict(key=True),
|
||||||
output=dict(),
|
output=dict(),
|
||||||
prompt=dict(),
|
prompt=dict(),
|
||||||
response=dict()
|
response=dict()
|
||||||
))
|
)
|
||||||
|
|
||||||
|
transform = ComplexList(spec, module)
|
||||||
commands = transform(module.params['commands'])
|
commands = transform(module.params['commands'])
|
||||||
|
|
||||||
for index, item in enumerate(commands):
|
for index, item in enumerate(commands):
|
||||||
|
|
|
@ -272,12 +272,12 @@ def map_params_to_obj(module):
|
||||||
lookup_source = ComplexList(dict(
|
lookup_source = ComplexList(dict(
|
||||||
interface=dict(key=True),
|
interface=dict(key=True),
|
||||||
vrf=dict()
|
vrf=dict()
|
||||||
))
|
), module)
|
||||||
|
|
||||||
name_servers = ComplexList(dict(
|
name_servers = ComplexList(dict(
|
||||||
server=dict(key=True),
|
server=dict(key=True),
|
||||||
vrf=dict(default='default')
|
vrf=dict(default='default')
|
||||||
))
|
), module)
|
||||||
|
|
||||||
for arg, cast in [('lookup_source', lookup_source), ('name_servers', name_servers)]:
|
for arg, cast in [('lookup_source', lookup_source), ('name_servers', name_servers)]:
|
||||||
if module.params[arg] is not None:
|
if module.params[arg] is not None:
|
||||||
|
|
|
@ -149,7 +149,7 @@ def parse_commands(module, warnings):
|
||||||
command=dict(key=True),
|
command=dict(key=True),
|
||||||
prompt=dict(),
|
prompt=dict(),
|
||||||
response=dict()
|
response=dict()
|
||||||
))
|
), module)
|
||||||
commands = command(module.params['commands'])
|
commands = command(module.params['commands'])
|
||||||
for index, item in enumerate(commands):
|
for index, item in enumerate(commands):
|
||||||
if module.check_mode and not item['command'].startswith('show'):
|
if module.check_mode and not item['command'].startswith('show'):
|
||||||
|
|
|
@ -311,17 +311,17 @@ def map_params_to_obj(module):
|
||||||
domain_name = ComplexList(dict(
|
domain_name = ComplexList(dict(
|
||||||
name=dict(key=True),
|
name=dict(key=True),
|
||||||
vrf=dict()
|
vrf=dict()
|
||||||
))
|
), module)
|
||||||
|
|
||||||
domain_search = ComplexList(dict(
|
domain_search = ComplexList(dict(
|
||||||
name=dict(key=True),
|
name=dict(key=True),
|
||||||
vrf=dict()
|
vrf=dict()
|
||||||
))
|
), module)
|
||||||
|
|
||||||
name_servers = ComplexList(dict(
|
name_servers = ComplexList(dict(
|
||||||
server=dict(key=True),
|
server=dict(key=True),
|
||||||
vrf=dict()
|
vrf=dict()
|
||||||
))
|
), module)
|
||||||
|
|
||||||
for arg, cast in [('domain_name', domain_name),
|
for arg, cast in [('domain_name', domain_name),
|
||||||
('domain_search', domain_search),
|
('domain_search', domain_search),
|
||||||
|
|
|
@ -163,7 +163,7 @@ def parse_commands(module, warnings):
|
||||||
command=dict(key=True),
|
command=dict(key=True),
|
||||||
prompt=dict(),
|
prompt=dict(),
|
||||||
response=dict()
|
response=dict()
|
||||||
))
|
), module)
|
||||||
commands = command(module.params['commands'])
|
commands = command(module.params['commands'])
|
||||||
|
|
||||||
for index, item in enumerate(commands):
|
for index, item in enumerate(commands):
|
||||||
|
|
|
@ -152,8 +152,7 @@ def parse_commands(module, warnings):
|
||||||
command=dict(key=True),
|
command=dict(key=True),
|
||||||
prompt=dict(),
|
prompt=dict(),
|
||||||
response=dict(),
|
response=dict(),
|
||||||
))
|
), module)
|
||||||
|
|
||||||
commands = command(module.params['commands'])
|
commands = command(module.params['commands'])
|
||||||
|
|
||||||
for index, cmd in enumerate(commands):
|
for index, cmd in enumerate(commands):
|
||||||
|
|
Loading…
Reference in a new issue