@ -26,6 +26,7 @@ description:
notes :
notes :
- This module supports devices with and without the candidate and
- This module supports devices with and without the candidate and
confirmed - commit capabilities . It always use the safer feature .
confirmed - commit capabilities . It always use the safer feature .
- This module supports the use of connection = netconf
version_added : " 2.2 "
version_added : " 2.2 "
options :
options :
host :
host :
@ -101,6 +102,13 @@ requirements:
'''
'''
EXAMPLES = '''
EXAMPLES = '''
- name : use lookup filter to provide xml configuration
netconf_config :
xml : " {{ lookup( ' file ' , ' ./config.xml ' ) }} "
host : 10.0 .0 .1
username : admin
password : admin
- name : set ntp server in the device
- name : set ntp server in the device
netconf_config :
netconf_config :
host : 10.0 .0 .1
host : 10.0 .0 .1
@ -150,6 +158,8 @@ server_capabilities:
import traceback
import traceback
import xml . dom . minidom
import xml . dom . minidom
from xml . etree . ElementTree import fromstring , tostring
try :
try :
import ncclient . manager
import ncclient . manager
HAS_NCCLIENT = True
HAS_NCCLIENT = True
@ -158,23 +168,31 @@ except ImportError:
from ansible . module_utils . basic import AnsibleModule
from ansible . module_utils . basic import AnsibleModule
from ansible . module_utils . _text import to_native
from ansible . module_utils . _text import to_native
from ansible . module_utils . connection import Connection , ConnectionError
def netconf_edit_config ( m , xml , commit , retkwargs , datastore ):
def netconf_edit_config ( m , xml , commit , retkwargs , datastore , capabilities , local_connection ):
m . lock ( target = datastore )
m . lock ( target = datastore )
try :
try :
if datastore == " candidate " :
if datastore == " candidate " :
m . discard_changes ( )
m . discard_changes ( )
config_before = m . get_config ( source = datastore )
config_before = m . get_config ( source = datastore )
m . edit_config ( target = datastore , config = xml )
m . edit_config ( target = datastore , config = xml )
config_after = m . get_config ( source = datastore )
config_after = m . get_config ( source = datastore )
if local_connection :
changed = config_before . data_xml != config_after . data_xml
changed = config_before . data_xml != config_after . data_xml
else :
changed = config_before != config_after
if changed and commit and datastore == " candidate " :
if changed and commit and datastore == " candidate " :
if " :confirmed-commit " in m . server_capabilities :
if " :confirmed-commit " in capabilities:
m . commit ( confirmed = True )
m . commit ( confirmed = True )
m . commit ( )
m . commit ( )
else :
else :
m . commit ( )
m . commit ( )
return changed
return changed
finally :
finally :
m . unlock ( target = datastore )
m . unlock ( target = datastore )
@ -188,22 +206,28 @@ def main():
module = AnsibleModule (
module = AnsibleModule (
argument_spec = dict (
argument_spec = dict (
host = dict ( type = ' str ' , required = True ) ,
xml = dict ( type = ' str ' , required = False ) ,
src = dict ( type = ' path ' , required = False ) ,
datastore = dict ( choices = [ ' auto ' , ' candidate ' , ' running ' ] , default = ' auto ' ) ,
save = dict ( type = ' bool ' , default = False ) ,
# connection arguments
host = dict ( type = ' str ' ) ,
port = dict ( type = ' int ' , default = 830 ) ,
port = dict ( type = ' int ' , default = 830 ) ,
username = dict ( type = ' str ' , no_log = True ) ,
password = dict ( type = ' str ' , no_log = True ) ,
hostkey_verify = dict ( type = ' bool ' , default = True ) ,
hostkey_verify = dict ( type = ' bool ' , default = True ) ,
allow_agent = dict ( type = ' bool ' , default = True ) ,
look_for_keys = dict ( type = ' bool ' , default = True ) ,
look_for_keys = dict ( type = ' bool ' , default = True ) ,
datastore = dict ( choices = [ ' auto ' , ' candidate ' , ' running ' ] , default = ' auto ' ) ,
save = dict ( type = ' bool ' , default = False ) ,
allow_agent = dict ( type = ' bool ' , default = True ) ,
username = dict ( type = ' str ' , required = True , no_log = True ) ,
password = dict ( type = ' str ' , required = True , no_log = True ) ,
xml = dict ( type = ' str ' , required = False ) ,
src = dict ( type = ' path ' , required = False ) ,
) ,
) ,
mutually_exclusive = [ ( ' xml ' , ' src ' ) ]
mutually_exclusive = [ ( ' xml ' , ' src ' ) ]
)
)
if not HAS_NCCLIENT:
if not module. _socket_path and not HAS_NCCLIENT:
module . fail_json ( msg = ' could not import the python library '
module . fail_json ( msg = ' could not import the python library '
' ncclient required by this module ' )
' ncclient required by this module ' )
@ -214,12 +238,14 @@ def main():
else :
else :
module . fail_json ( msg = ' Option src or xml must be provided ' )
module . fail_json ( msg = ' Option src or xml must be provided ' )
try :
local_connection = module . _socket_path is None
xml . dom . minidom . parseString ( config_xml )
except Exception as e :
if not local_connection :
module . fail_json ( msg = ' error parsing XML: %s ' % to_native ( e ) , exception = traceback . format_exc ( ) )
m = Connection ( module . _socket_path )
capabilities = module . from_json ( m . get_capabilities ( ) )
server_capabilities = capabilities . get ( ' server_capabilities ' )
else :
nckwargs = dict (
nckwargs = dict (
host = module . params [ ' host ' ] ,
host = module . params [ ' host ' ] ,
port = module . params [ ' port ' ] ,
port = module . params [ ' port ' ] ,
@ -232,6 +258,7 @@ def main():
try :
try :
m = ncclient . manager . connect ( * * nckwargs )
m = ncclient . manager . connect ( * * nckwargs )
server_capabilities = list ( m . server_capabilities )
except ncclient . transport . errors . AuthenticationError :
except ncclient . transport . errors . AuthenticationError :
module . fail_json (
module . fail_json (
msg = ' authentication failed while connecting to device '
msg = ' authentication failed while connecting to device '
@ -239,43 +266,54 @@ def main():
except Exception as e :
except Exception as e :
module . fail_json ( msg = ' error connecting to the device: %s ' % to_native ( e ) , exception = traceback . format_exc ( ) )
module . fail_json ( msg = ' error connecting to the device: %s ' % to_native ( e ) , exception = traceback . format_exc ( ) )
try :
xml . dom . minidom . parseString ( config_xml )
except Exception as e :
module . fail_json ( msg = ' error parsing XML: %s ' % to_native ( e ) , exception = traceback . format_exc ( ) )
retkwargs = dict ( )
retkwargs = dict ( )
retkwargs [ ' server_capabilities ' ] = list ( m . server_capabilities )
retkwargs [ ' server_capabilities ' ] = server_capabilities
server_capabilities = ' \n ' . join ( server_capabilities )
if module . params [ ' datastore ' ] == ' candidate ' :
if module . params [ ' datastore ' ] == ' candidate ' :
if ' :candidate ' in m . server_capabilities :
if ' :candidate ' in server_capabilities:
datastore = ' candidate '
datastore = ' candidate '
else :
else :
if local_connection :
m . close_session ( )
m . close_session ( )
module . fail_json (
module . fail_json (
msg = ' :candidate is not supported by this netconf server '
msg = ' :candidate is not supported by this netconf server '
)
)
elif module . params [ ' datastore ' ] == ' running ' :
elif module . params [ ' datastore ' ] == ' running ' :
if ' :writable-running ' in m. server_capabilities:
if ' :writable-running ' in server_capabilities:
datastore = ' running '
datastore = ' running '
else :
else :
if local_connection :
m . close_session ( )
m . close_session ( )
module . fail_json (
module . fail_json (
msg = ' :writable-running is not supported by this netconf server '
msg = ' :writable-running is not supported by this netconf server '
)
)
elif module . params [ ' datastore ' ] == ' auto ' :
elif module . params [ ' datastore ' ] == ' auto ' :
if ' :candidate ' in m. server_capabilities:
if ' :candidate ' in server_capabilities:
datastore = ' candidate '
datastore = ' candidate '
elif ' :writable-running ' in m. server_capabilities:
elif ' :writable-running ' in server_capabilities:
datastore = ' running '
datastore = ' running '
else :
else :
if local_connection :
m . close_session ( )
m . close_session ( )
module . fail_json (
module . fail_json (
msg = ' neither :candidate nor :writable-running are supported by this netconf server '
msg = ' neither :candidate nor :writable-running are supported by this netconf server '
)
)
else :
else :
if local_connection :
m . close_session ( )
m . close_session ( )
module . fail_json (
module . fail_json (
msg = module . params [ ' datastore ' ] + ' datastore is not supported by this ansible module '
msg = module . params [ ' datastore ' ] + ' datastore is not supported by this ansible module '
)
)
if module . params [ ' save ' ] :
if module . params [ ' save ' ] :
if ' :startup ' not in m. server_capabilities:
if ' :startup ' not in server_capabilities:
module . fail_json (
module . fail_json (
msg = ' cannot copy <running/> to <startup/>, while :startup is not supported '
msg = ' cannot copy <running/> to <startup/>, while :startup is not supported '
)
)
@ -287,12 +325,15 @@ def main():
commit = True ,
commit = True ,
retkwargs = retkwargs ,
retkwargs = retkwargs ,
datastore = datastore ,
datastore = datastore ,
capabilities = server_capabilities ,
local_connection = local_connection
)
)
if changed and module . params [ ' save ' ] :
if changed and module . params [ ' save ' ] :
m . copy_config ( source = " running " , target = " startup " )
m . copy_config ( source = " running " , target = " startup " )
except Exception as e :
except Exception as e :
module . fail_json ( msg = ' error editing configuration: %s ' % to_native ( e ) , exception = traceback . format_exc ( ) )
module . fail_json ( msg = ' error editing configuration: %s ' % to_native ( e ) , exception = traceback . format_exc ( ) )
finally :
finally :
if local_connection :
m . close_session ( )
m . close_session ( )
module . exit_json ( changed = changed , * * retkwargs )
module . exit_json ( changed = changed , * * retkwargs )