Skip to content

Commit ce28d6d

Browse files
committed
Add netconf support
This commit adds support for bootstrapping netconf modules. Although originally meant to be used by junos, it should be portable to any another network os supporting netconf protocol. Depends-on: ansible/ansible#58871 Signed-off-by: Daniel Mellado <[email protected]>
1 parent 3df8a34 commit ce28d6d

File tree

2 files changed

+134
-3
lines changed

2 files changed

+134
-3
lines changed

roles/scaffold_rm_facts/templates/module_utils/network_os/config/resource/resource.py.j2

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ created
1313
from ansible.module_utils.network.common.cfg.base import ConfigBase
1414
from ansible.module_utils.network.common.utils import to_list
1515
from {{ import_path }}.{{ network_os }}.facts.facts import Facts
16+
{% if transport.netconf %}
17+
from ansible.module_utils.network.netconf.netconf import locked_config
18+
from ansible.module_utils.network.common.netconf import (build_root_xml_node,
19+
build_child_xml_node)
20+
{% endif %}
1621

1722

1823
class {{ resource|capitalize }}(ConfigBase):
@@ -51,8 +56,29 @@ class {{ resource|capitalize }}(ConfigBase):
5156
:returns: The result from module execution
5257
"""
5358
result = {'changed': False}
54-
commands = list()
59+
{% if transport.netconf %}
60+
existing_{{ resource }}_facts = self.get_{{ resource }}_facts()
61+
config_xmls = self.set_config(existing_{{ resource }}_facts)
62+
63+
with locked_config(self._module):
64+
for config_xml in to_list(config_xmls):
65+
diff = self._module._connectionload_config(self._module, config_xml, [])
66+
67+
commit = not self._module.check_mode
68+
if diff:
69+
if commit:
70+
self._module._connection.commit_configuration(self._module)
71+
else:
72+
self._module._connection.discard_changes(self._module)
73+
result['changed'] = True
74+
75+
if self._module._diff:
76+
result['diff'] = {'prepared': diff}
77+
78+
result['xml'] = config_xmls
79+
{% else %}
5580
warnings = list()
81+
commands = list()
5682

5783
existing_{{ resource }}_facts = self.get_{{ resource }}_facts()
5884
commands.extend(self.set_config(existing_{{ resource }}_facts))
@@ -62,6 +88,7 @@ class {{ resource|capitalize }}(ConfigBase):
6288
result['changed'] = True
6389
result['commands'] = commands
6490

91+
{% endif %}
6592
changed_{{ resource }}_facts = self.get_{{ resource }}_facts()
6693

6794
result['before'] = existing_{{ resource }}_facts
@@ -93,6 +120,23 @@ class {{ resource|capitalize }}(ConfigBase):
93120
:returns: the commands necessary to migrate the current configuration
94121
to the desired configuration
95122
"""
123+
{% if transport.netconf %}
124+
root = build_root_xml_node('{{ resource }}')
125+
state = self._module.params['state']
126+
if state == 'overridden':
127+
config_xmls = self._state_overridden(want, have)
128+
elif state == 'deleted':
129+
config_xmls = self._state_deleted(want, have)
130+
elif state == 'merged':
131+
config_xmls = self._state_merged(want, have)
132+
elif state == 'replaced':
133+
config_xmls = self._state_replaced(want, have)
134+
135+
for xml in config_xmls:
136+
root.append(xml)
137+
138+
return self._module._connection.tostring(root)
139+
{% else %}
96140
state = self._module.params['state']
97141
if state == 'overridden':
98142
kwargs = {}
@@ -107,7 +151,47 @@ class {{ resource|capitalize }}(ConfigBase):
107151
kwargs = {}
108152
commands = self._state_replaced(**kwargs)
109153
return commands
154+
{% endif %}
155+
{% if transport.netconf %}
156+
def _state_replaced(self, want, have):
157+
""" The command generator when state is replaced
110158

159+
:rtype: A list
160+
:returns: the xml necessary to migrate the current configuration
161+
to the desired configuration
162+
"""
163+
intf_xml = []
164+
return intf_xml
165+
166+
def _state_overridden(self, want, have):
167+
""" The command generator when state is overridden
168+
169+
:rtype: A list
170+
:returns: the xml necessary to migrate the current configuration
171+
to the desired configuration
172+
"""
173+
intf_xml = []
174+
return intf_xml
175+
def _state_deleted(self, want, have):
176+
""" The command generator when state is deleted
177+
178+
:rtype: A list
179+
:returns: the xml necessary to migrate the current configuration
180+
to the desired configuration
181+
"""
182+
intf_xml = []
183+
return intf_xml
184+
185+
def _state_merged(self, want, have):
186+
""" The command generator when state is merged
187+
188+
:rtype: A list
189+
:returns: the xml necessary to migrate the current configuration
190+
to the desired configuration
191+
"""
192+
intf_xml = []
193+
return intf_xml
194+
{% else %}
111195
@staticmethod
112196
def _state_replaced(**kwargs):
113197
""" The command generator when state is replaced
@@ -151,3 +235,4 @@ class {{ resource|capitalize }}(ConfigBase):
151235
"""
152236
commands = []
153237
return commands
238+
{% endif %}

roles/scaffold_rm_facts/templates/module_utils/network_os/facts/resource/resource.py.j2

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,24 @@ It is in this file the configuration is collected from the device
99
for a given resource, parsed, and the facts tree is populated
1010
based on the configuration.
1111
"""
12+
{% if not transport.netconf %}
1213
import re
14+
{% endif %}
1315
from copy import deepcopy
1416

17+
{% if transport.netconf %}
18+
from ansible.module_utils._text import to_bytes
19+
{% endif %}
1520
from ansible.module_utils.network.common import utils
1621
from {{ import_path }}.{{ network_os }}.argspec.{{ resource }}.{{ resource }} import {{ resource|capitalize }}Args
22+
{% if transport.netconf %}
23+
from ansible.module.utils.six import string_types
24+
try:
25+
from lxml import etree
26+
HAS_LXML = True
27+
except ImportError:
28+
HAS_LXML = False
29+
{% endif %}
1730

1831

1932
class {{ resource|capitalize }}Facts(object):
@@ -42,6 +55,25 @@ class {{ resource|capitalize }}Facts(object):
4255
:rtype: dictionary
4356
:returns: facts
4457
"""
58+
{% if transport.netconf %}
59+
if not HAS_LXML:
60+
self._module.fail_json(msg='lxml is not installed.')
61+
62+
if not data:
63+
config_filter = """
64+
<configuration>
65+
<resource>
66+
</resource>
67+
</configuration>
68+
"""
69+
data = connection.get_configuration(filter=config_filter)
70+
71+
if isinstance(data, string_types):
72+
data = etree.fromstring(to_bytes(data,
73+
errors='surrogate_then_replace'))
74+
75+
resources = data.xpath('configuration/resources/resource')
76+
{% else %}
4577
if connection: # just for linting purposes, remove
4678
pass
4779

@@ -64,6 +96,7 @@ class {{ resource|capitalize }}Facts(object):
6496
resources = [p.strip() for p in re.findall(find_pattern,
6597
data,
6698
re.DOTALL)]
99+
{% endif %}
67100

68101
objs = []
69102
for resource in resources:
@@ -72,11 +105,21 @@ class {{ resource|capitalize }}Facts(object):
72105
if obj:
73106
objs.append(obj)
74107

108+
{% if transport.netconf %}
109+
facts = {}
110+
if objs:
111+
facts['resource'] = []
112+
params = utils.validate_config(self.argument_spec,
113+
{'config': objs})
114+
for cfg in params['config']:
115+
facts['resource'].append(utils.remove_empties(cfg))
116+
{% else %}
75117
ansible_facts['ansible_network_resources'].pop('{{ resource }}', None)
76118
facts = {}
77119
if objs:
78120
params = utils.validate_config(self.argument_spec, {'config': objs})
79121
facts['{{ resource }}'] = params['config']
122+
{% endif %}
80123

81124
ansible_facts['ansible_network_resources'].update(facts)
82125
return ansible_facts
@@ -92,7 +135,10 @@ class {{ resource|capitalize }}Facts(object):
92135
:returns: The generated config
93136
"""
94137
config = deepcopy(spec)
95-
138+
{% if transport.netconf %}
139+
config['name'] = utils.get_xml_conf_arg(conf, 'name')
140+
config['some_value'] = utils.get_xml_conf_arg(conf, 'some_value')
141+
{% else %}
96142
config['name'] = utils.parse_conf_arg(conf, 'resource')
97143
config['some_string'] = utils.parse_conf_arg(conf, 'a_string')
98144

@@ -113,5 +159,5 @@ class {{ resource|capitalize }}Facts(object):
113159
config['some_int'] = int(utils.parse_conf_arg(conf, 'an_int'))
114160
except TypeError:
115161
config['some_int'] = None
116-
162+
{% endif %}
117163
return utils.remove_empties(config)

0 commit comments

Comments
 (0)