@ -19,6 +19,7 @@
from __future__ import ( absolute_import , division , print_function )
from __future__ import ( absolute_import , division , print_function )
__metaclass__ = type
__metaclass__ = type
import collections
import json
import json
import re
import re
@ -27,30 +28,30 @@ from itertools import chain
from ansible . errors import AnsibleConnectionFailure
from ansible . errors import AnsibleConnectionFailure
from ansible . module_utils . _text import to_bytes , to_text
from ansible . module_utils . _text import to_bytes , to_text
from ansible . module_utils . connection import ConnectionError
from ansible . module_utils . connection import ConnectionError
from ansible . module_utils . network . common . config import NetworkConfig , dumps
from ansible . module_utils . network . common . utils import to_list
from ansible . module_utils . network . common . utils import to_list
from ansible . plugins . cliconf import CliconfBase
from ansible . plugins . cliconf import CliconfBase , enable_mode
from ansible . plugins . connection . network_cli import Connection as NetworkCli
from ansible . plugins . connection . network_cli import Connection as NetworkCli
from ansible . plugins . connection . httpapi import Connection as HttpApi
class Cliconf ( CliconfBase ) :
class Cliconf ( CliconfBase ) :
def send_command ( self , command , prompt = None , answer = None , sendonly = False , newline = True , prompt_retry_check = False ) :
def __init__ ( self , * args , * * kwargs ) :
super ( Cliconf , self ) . __init__ ( * args , * * kwargs )
def send_command ( self , command , * * kwargs ) :
""" Executes a cli command and returns the results
""" Executes a cli command and returns the results
This method will execute the CLI command on the connection and return
This method will execute the CLI command on the connection and return
the results to the caller . The command output will be returned as a
the results to the caller . The command output will be returned as a
string
string
"""
"""
kwargs = { ' command ' : to_bytes ( command ) , ' sendonly ' : sendonly ,
' newline ' : newline , ' prompt_retry_check ' : prompt_retry_check }
if prompt is not None :
kwargs [ ' prompt ' ] = to_bytes ( prompt )
if answer is not None :
kwargs [ ' answer ' ] = to_bytes ( answer )
if isinstance ( self . _connection , NetworkCli ) :
if isinstance ( self . _connection , NetworkCli ) :
resp = self . _connection . send ( * * kwargs )
resp = super ( Cliconf , self ) . send_command ( command , * * kwargs )
el se:
elif isinstance ( self . _connection , HttpApi ) :
resp = self . _connection . send_request ( command , * * kwargs )
resp = self . _connection . send_request ( command , * * kwargs )
else :
raise ValueError ( " Invalid connection type " )
return resp
return resp
def get_device_info ( self ) :
def get_device_info ( self ) :
@ -101,57 +102,110 @@ class Cliconf(CliconfBase):
return device_info
return device_info
def get_config ( self , source = ' running ' , format = ' text ' , flags = None ) :
def get_diff ( self , candidate = None , running = None , diff_match = ' line ' , diff_ignore_lines = None , path = None , diff_replace = ' line ' ) :
diff = { }
device_operations = self . get_device_operations ( )
option_values = self . get_option_values ( )
if candidate is None and device_operations [ ' supports_generate_diff ' ] :
raise ValueError ( " candidate configuration is required to generate diff " )
if diff_match not in option_values [ ' diff_match ' ] :
raise ValueError ( " ' match ' value %s in invalid, valid values are %s " % ( diff_match , ' , ' . join ( option_values [ ' diff_match ' ] ) ) )
if diff_replace not in option_values [ ' diff_replace ' ] :
raise ValueError ( " ' replace ' value %s in invalid, valid values are %s " % ( diff_replace , ' , ' . join ( option_values [ ' diff_replace ' ] ) ) )
# prepare candidate configuration
candidate_obj = NetworkConfig ( indent = 2 )
candidate_obj . load ( candidate )
if running and diff_match != ' none ' and diff_replace != ' config ' :
# running configuration
running_obj = NetworkConfig ( indent = 2 , contents = running , ignore_lines = diff_ignore_lines )
configdiffobjs = candidate_obj . difference ( running_obj , path = path , match = diff_match , replace = diff_replace )
else :
configdiffobjs = candidate_obj . items
diff [ ' config_diff ' ] = dumps ( configdiffobjs , ' commands ' ) if configdiffobjs else ' '
return diff
def get_config ( self , source = ' running ' , format = ' text ' , filter = None ) :
options_values = self . get_option_values ( )
if format not in options_values [ ' format ' ] :
raise ValueError ( " ' format ' value %s is invalid. Valid values are %s " % ( format , ' , ' . join ( options_values [ ' format ' ] ) ) )
lookup = { ' running ' : ' running-config ' , ' startup ' : ' startup-config ' }
lookup = { ' running ' : ' running-config ' , ' startup ' : ' startup-config ' }
if source not in lookup :
return self . invalid_params ( " fetching configuration from %s is not supported " % source )
cmd = ' show {0} ' . format ( lookup [ source ] )
cmd = ' show {0} ' . format ( lookup [ source ] )
if flags :
if format and format is not ' text ' :
cmd + = ' ' . join ( flags )
cmd + = ' | %s ' % format
if filter :
cmd + = ' ' . join ( to_list ( filter ) )
cmd = cmd . strip ( )
cmd = cmd . strip ( )
return self . send_command ( cmd )
return self . send_command ( cmd )
def edit_config ( self , command ) :
def edit_config ( self , c andidate= None , commit = True , replace = None , comment = None ) :
responses = [ ]
resp = { }
for cmd in chain ( [ ' configure ' ] , to_list ( command ) , [ ' end ' ] ) :
operations = self . get_device_operations ( )
responses . append ( self . send_command ( cmd ) )
self . check_edit_config_capabiltiy ( operations , candidate , commit , replace , comment )
resp = responses [ 1 : - 1 ]
res ults = [ ]
return json . dumps ( resp )
requests = [ ]
def get ( self , command , prompt = None , answer = None , sendonly = False ) :
if replace :
return self . send_command ( command , prompt = prompt , answer = answer , sendonly = sendonly )
candidate = ' config replace {0} ' . format ( replace )
def get_capabilities ( self ) :
if commit :
result = { }
self . send_command ( ' configure terminal ' )
result [ ' rpc ' ] = self . get_base_rpc ( )
result [ ' device_info ' ] = self . get_device_info ( )
for line in to_list ( candidate ) :
if isinstance ( self . _connection , NetworkCli ) :
if not isinstance ( line , collections . Mapping ) :
result [ ' network_api ' ] = ' cliconf '
line = { ' command ' : line }
cmd = line [ ' command ' ]
if cmd != ' end ' :
results . append ( self . send_command ( * * line ) )
requests . append ( cmd )
self . send_command ( ' end ' )
else :
else :
result [ ' network_api ' ] = ' nxapi '
raise ValueError ( ' check mode is not supported ' )
return json . dumps ( result )
resp [ ' request ' ] = requests
resp [ ' response ' ] = results
return resp
def get ( self , command , prompt = None , answer = None , sendonly = False , output = None ) :
if output :
command = self . _get_command_with_output ( command , output )
return self . send_command ( command , prompt = prompt , answer = answer , sendonly = sendonly )
def run_commands ( self , commands = None , check_rc = True ) :
if commands is None :
raise ValueError ( " ' commands ' value is required " )
# Migrated from module_utils
def run_commands ( self , commands , check_rc = True ) :
""" Run list of commands on remote device and return results
"""
responses = list ( )
responses = list ( )
for cmd in to_list ( commands ) :
if not isinstance ( cmd , collections . Mapping ) :
cmd = { ' command ' : cmd }
for item in to_list ( commands ) :
output = cmd . pop ( ' output ' , None )
if item [ ' output ' ] == ' json ' and not item [ ' command ' ] . endswith ( ' | json ' ) :
if output :
cmd = ' %s | json ' % item [ ' command ' ]
cmd [ ' command ' ] = self . _get_command_with_output ( cmd [ ' command ' ] , output )
elif item [ ' output ' ] == ' text ' and item [ ' command ' ] . endswith ( ' | json ' ) :
cmd = item [ ' command ' ] . rsplit ( ' | ' , 1 ) [ 0 ]
else :
cmd = item [ ' command ' ]
try :
try :
out = self . get( cmd )
out = self . send_command ( * * cmd )
except AnsibleConnectionFailure as e :
except AnsibleConnectionFailure as e :
if check_rc :
if check_rc :
raise
raise
out = getattr ( e , ' err ' , e )
out = getattr ( e , ' err ' , e )
if out is not None :
try :
try :
out = to_text ( out , errors = ' surrogate_or_strict ' ) . strip ( )
out = to_text ( out , errors = ' surrogate_or_strict ' ) . strip ( )
except UnicodeError :
except UnicodeError :
@ -160,7 +214,57 @@ class Cliconf(CliconfBase):
try :
try :
out = json . loads ( out )
out = json . loads ( out )
except ValueError :
except ValueError :
pass
out = to_text ( out , errors = ' surrogate_or_strict ' ) . strip ( )
responses . append ( out )
responses . append ( out )
return responses
return responses
def get_device_operations ( self ) :
return {
' supports_diff_replace ' : True ,
' supports_commit ' : False ,
' supports_rollback ' : False ,
' supports_defaults ' : True ,
' supports_onbox_diff ' : False ,
' supports_commit_comment ' : False ,
' supports_multiline_delimiter ' : False ,
' supports_diff_match ' : True ,
' supports_diff_ignore_lines ' : True ,
' supports_generate_diff ' : True ,
' supports_replace ' : True
}
def get_option_values ( self ) :
return {
' format ' : [ ' text ' , ' json ' ] ,
' diff_match ' : [ ' line ' , ' strict ' , ' exact ' , ' none ' ] ,
' diff_replace ' : [ ' line ' , ' block ' , ' config ' ] ,
' output ' : [ ' text ' , ' json ' ]
}
def get_capabilities ( self ) :
result = { }
result [ ' rpc ' ] = self . get_base_rpc ( )
result [ ' device_info ' ] = self . get_device_info ( )
result . update ( self . get_option_values ( ) )
if isinstance ( self . _connection , NetworkCli ) :
result [ ' network_api ' ] = ' cliconf '
elif isinstance ( self . _connection , HttpApi ) :
result [ ' network_api ' ] = ' nxapi '
else :
raise ValueError ( " Invalid connection type " )
return json . dumps ( result )
def _get_command_with_output ( self , command , output ) :
options_values = self . get_option_values ( )
if output not in options_values [ ' output ' ] :
raise ValueError ( " ' output ' value %s is invalid. Valid values are %s " % ( output , ' , ' . join ( options_values [ ' output ' ] ) ) )
if output == ' json ' and not command . endswith ( ' | json ' ) :
cmd = ' %s | json ' % command
elif output == ' text ' and command . endswith ( ' | json ' ) :
cmd = command . rsplit ( ' | ' , 1 ) [ 0 ]
else :
cmd = command
return cmd