=pod

=head1 NAME

Net::Appliance::Session::Cookbook::Recipe07 - Changing Device Configurations

=head1 NOTE

This Cookbook was contributed to the L<Net::Appliance::Session> project by
Nigel Bowden. Source code from the Cookbook is shipped in the C<examples>
folder of this module's distribution.

=head1 PROBLEM

You need to be able to access a Cisco device to update its configuration.

=head1 SOLUTION

Here is the solution to apply device configuration updates to a Cisco IOS
device.

Again, for simplicity, it is assumed that after login, the user account being
used is dropped in to privilege level 15 (enable). The login username and
password are also hard-coded in to the script for simplicity of demonstrating
this technique.

Also, it is assumed that this script will be passed the IP address of the
target IOS device via the command line, as a command line parameter.

In addition, the configuration commands that need to be executed are also
passed in via the command line. Note that each IOS command passed to the
script via the command line needs to be wrapped in quotes - this is to prevent
the shell from splitting the commands up and passing them incorrectly to the
script.

A simple example of usage from the command line would be:

 perl Recipe_07.pl 10.250.249.215 "snmp-server location London" "snmp-server contact Network Ops"

This would send two commands to the device at C<10.250.249.215> that would
update the snmp configuration of that device (it just adds a location name and
contact details in this instance).

 use strict;
 use warnings;
 
 use Net::Appliance::Session;
 
 my $device_ip = shift @ARGV; # get the IP address from the command line
 my @cmd       = @ARGV; # get the commands from the command line
 
 # check we have passed at least one command
 unless ($cmd[0]) {
     print qq(Usage : Recipe_07.pl <device_ip> "<ios_conf_command>" );
     print qq([ "<ios_conf_command>" .. "<ios_conf_command>" ]\n);
     exit 1;
 }
 
 # common username and password for all devices
 my $ios_username        = 'cisco';
 my $ios_password        = 'cisco';
 
 my $session_obj = Net::Appliance::Session->new(
     Host      => $device_ip,
     Transport => 'Telnet',
 );
 
 $session_obj->input_log(*STDOUT);
 
 # tell our object we'll be in privileged mode straight after login
 $session_obj->do_privileged_mode(0);    
 
 # start eval block to trap errors in interactive session
 eval {
     
     # try to login to the ios device, ignoring host check
     $session_obj->connect(Name => $ios_username, Password => $ios_password);
     
     # go in to conf mode (i.e. 'conf t')
     $session_obj->begin_configure;
     
     for my $conf_cmd (@cmd) {
         $session_obj->cmd($conf_cmd);
     }
     
     # uncomment these lines to write the changes to the startup config
     #$session_obj->end_configure;
     #$session_obj->cmd("write memory");
     
 };
 
 # close down our session
 $session_obj->close;
 
 # did we get an error ?
 if ($@) {
     print error_report($@, $device_ip);
 }
     
 sub error_report {
     
     # standard subroutine used to extract failure info when
     # interactive session fails
     
     my $err         = shift or croak("No err !");
     my $device_name = shift or croak("No device name !");
     
     my $report; # holder for report message to return to caller
     
     if ( UNIVERSAL::isa($err, 'Net::Appliance::Session::Exception') ) {
             
         # fault description from Net::Appliance::Session
         $report  =  "We had an error during our Telnet/SSH session to device  : $device_name \n"; 
         $report .= $err->message . " \n";
             
         # message from Net::Telnet
         $report .= "Net::Telnet message : " . $err->errmsg . "\n"; 
             
         # last line of output from your appliance  
         $report .=  "Last line of output from device : " . $err->lastline . "\n\n";
 
     }
     elsif (UNIVERSAL::isa($err, 'Net::Appliance::Session::Error') ) {
         
         # fault description from Net::Appliance::Session
         $report  = "We had an issue during program execution to device : $device_name \n";
         $report .=  $err->message . " \n";
 
     }
     else {
         
         # we had some other error that wasn't a deliberately created exception
         $report  = "We had an issue when accessing the device : $device_name \n";
         $report .= "The reported error was : $err \n";
     }
         
     return $report;
 }

=head1 DISCUSSION

If you've read through the the previous recipes in this section of the
cookbook, you shouldn't have too many issues following the flow of this
particular example.

We start by taking some command line arguments to give us the IP address of
the device we want to make changes on, together with the commands that we wish
to process :

 my $device_ip = shift @ARGV; # get the IP address from the command line
 my @cmd       = @ARGV; # get the commands from the command line

The first argument passed in is the IP address of our target device, and any
subsequent arguments are assumed to be IOS commands that will be applied to
the device, once we have logged into it.

As stated previously (but I'll say it again in case you didn't read that
part), each command should be wrapped in quotes, otherwise any space
characters in the IOS commands will assumed to be parameter delimiters and the
script will try to send each indiviudual word as an IOS command (which
obviously won't work).

If we find that we haven't passed any IOS commands to the script, we output a
helpful little message and the script terminates :

 # check we have passed at least one command
 unless ($cmd[0]) {   
     print qq(Usage : Recipe_07.pl <device_ip> "<ios_conf_command>" );
     print qq([ "<ios_conf_command>" .. "<ios_conf_command>" ]\n);
     exit 1;
 }

Once we get into our eval segment, where we execute our interactive commands,
we see a few things which we haven't met before :

 # go in to conf mode (i.e. 'conf t')
 $session_obj->begin_configure;
 
 for my $conf_cmd (@cmd) {
     $session_obj->cmd($conf_cmd);
 }
 
 # uncomment these lines to write the changes to the startup config
 #$session_obj->end_configure;
 #$session_obj->cmd("wr m");


The C<begin_configure> method takes us in to the configuration mode for our
device - its the equivilent of a C<conf t> command (in fact that's what
Net::Session::Appliance does in the background). Note that in this example we
have previously executed C<< $session_obj->do_privileged_mode(0) >> to tell our
session object that we are already in enable mode when we logged in to our
device.

We then proceed to execute the commands that were passed via the command line,
that have been stored in the C<@cmd> array. Don't forget, if any of the commands
fail (e.g. due to a syntax error), an exception will be generated and no
subsequent commands will be executed.

 # uncomment these lines to write the changes to the startup config
 #$session_obj->end_configure;
 #$session_obj->cmd("write memory");

After the commands have been executed, the configuration changes are only
applied to running configuration of the device. If we want to make the changes
permanent we need to write them to the startup configuration of the device. To
do this, we have to first drop out of config mode using the C<end_configure>
method.

Next, we need to execute the C<write memory> command (I know there are quite a
few variations to this command: C<wr m>, C<write mem>, C<copy run start> etc.
- use whichever you feel most comfortable with).

I left these two lines commented out in the script so that if you are
experimenting with it, and you inadvertently send anything which you later
regret, you will be able to go back to the original configuration by rebooting
the device or doing a C<copy startup-config running-config> command on the
device.

Although this script is quite crude, it demonstrates the principles required
to change an IOS device configuration. By using some of techniques
demonstrated in previous recipes, some powerful scripts could be created for
managing configuration changes across IOS devices on your network. 

=head1 SEE ALSO

=over 4

=item L<Net::Appliance::Session>

=back

=head1 AUTHOR

Nigel Bowden, with POD formatting by Oliver Gorwits.

=head1 COPYRIGHT & LICENSE

Copyright (c) Nigel Bowden 2007. All Rights Reserved.

You may distribute and/or modify this documentation under the same terms as
Perl itself.

=cut
