Net-CLI-Interact-2.300002000755000766000024 013170404307 14426 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/Build.PL000444000766000024 221113170404307 16053 0ustar00oliverstaff000000000000use strict; use warnings; use Module::Build; Module::Build->new( module_name => 'Net::CLI::Interact', author => 'Oliver Gorwits ', license => 'perl', dynamic_config => 1, configure_requires => { 'Module::Build' => '0.42', }, build_requires => { 'ExtUtils::CBuilder' => '0', }, requires => { 'Class::Load' => '0', 'Class::Mix' => '0', 'File::Basename' => '0', 'File::ShareDir' => '0', 'FileHandle' => '0', 'IO::Pty' => '0', 'IPC::Run' => '0', 'List::Util' => '0', 'Log::Dispatch::Config' => '0', 'Log::Dispatch::Configurator::Any' => '0', 'Config::Any' => '0.27', 'Moo' => '0', 'Moo::Role' => '0', 'MooX::Types::MooseLike::Base' => '0', 'Net::Telnet' => '0', 'POSIX' => '0', 'Path::Class' => '0', 'Sub::Quote' => '0', 'Time::HiRes' => '0', }, test_requires => { 'Test::More' => '0.88', }, share_dir => 'share', meta_merge => { resources => { bugtracker => 'https://github.com/ollyg/Net-CLI-Interact/issues', repository => 'https://github.com/ollyg/Net-CLI-Interact', }, }, )->create_build_script; Net-CLI-Interact-2.300002/Changes000444000766000024 1622113170404307 16100 0ustar00oliverstaff0000000000002.300002 2017-10-14 * #25 cisco/hp/pb login failure on hp procurve (A. Zanger) * #27 find_prompt doesn't bail out on failure to find a prompt (A. Zanger) * new phrasebooks for many platforms (A. Zanger) * new phrasebooks for Cisco WLC and fsck IOS command (Jv Ingen) 2.200009 2017-07-25 * [#22] remove ANSI terminal codes from CLI response 2.200006 2016-09-30 * [#21] session debug is always output (arcanez) 2.200005 2015-02-18 * POD typo fixes from Debian 2.200004 2015-02-14 * Fixes to distribution 2.200003 2015-02-13 * Fix Serial transport constructing params incompatible with cu (S. Kersley) * Fix spelling mistake and POD syntax issues (G. Herrmann) 2.200002 2015-02-22 * Fix MANIFEST (salva) 2.200001 2015-01-06 * Add Net::OpenSSH transport (salva) * Fix using barewords on filehandles (salva) 2.200000 2015-01-06 * Update dist to migrate away from Dist::Zilla 2.143070 2014-11-03 20:43:12 Europe/London * [#14] Allow square brackets in bash prompts (M. Perry) * [#15] Add phrasebook for Oracle VM Manager CLI (M. Perry) 2.142720 2014-09-29 19:41:26 Europe/London * [#13] Fix cmd in scalar context appends a newline 2.142010 2014-07-20 21:16:26 Europe/London * Resepct timeout in find_prompt (Jørgen Elgaard Larsen) 2.141520 2014-06-01 16:33:00 Europe/London * For some reason timeout not being set? Set on Net::Telnet::get. 2.133420 2013-12-08 00:07:45 Europe/London * Make command removal substitute params as well (M. Perry) 2.131260 2013-05-06 20:29:38 Europe/London * Fix for applying default timeout 2.130880 2013-03-29 22:12:14 Europe/London * Fix bug in debug log using native print instead of Logger (D. Thomas) * Fix bug in setting of log_config after session instantiation (D. Thomas) 2.123620 2012-12-27 22:52:55 Europe/London * Change default_log_categories from an array to a class method. 2.123612 2012-12-26 18:38:34 Europe/London * More logging configuration, and some Cookbook details on how to log. 2.123611 2012-12-26 15:23:41 Europe/London * Remove redundant Net::Telnet->open() (closes #10, closes #11) 2.123610 2012-12-26 15:04:44 Europe/London * Time to release pending changes. 2.123370_002 2012-12-02 00:24:38 Europe/London * Change command remove to be less greedy with whitespace (C. Bennett) * Fix loading of Data::Printer for last_actionset debug 2.123300_001 2012-11-25 13:47:15 Europe/London * Change command remove to be less greedy with whitespace (C. Bennett) 2.123270 2012-11-22 20:50:30 Europe/London * Bug fix for wrong type spec on log_config (A. Friedrich) 2.122940 2012-10-20 12:39:46 Europe/London * Bug fix for wrong type spec on log_at (wmdopple, closes #9) 2.122730 2012-09-29 23:49:08 Europe/London * IMPORTANT: the prompts named "prompt" are now called "generic" If you have written macros depending on these, please update your phrasebooks. * Make IO::Pty dependency based on compiler availability * Refactor to provide unix IPC::Run support where there is no IO::Pty * Add opts support to Serial Transport * Fix phrasebook loading to use all library and add_library paths * Support out of order entries in phrasebooks (macro/prompt refs) * New phrasebook for Bash shell * Improve documentation on how to create new phrasebooks * Permit ASCII escape \033 in device output * Added Loopback transport for testing * find_prompt now stores a full ActionSet after success * debug level logging will use Data::Printer on last_actionset if it can 2.122630 2012-09-19 17:21:30 Europe/London * Port from Moose to Moo 1.122530 2012-09-09 15:52:07 Europe/London * Alter log messages so that notice is a more useful default * Delete SIGCHLD after close (rt.cpan#79450) 1.122100 2012-07-29 00:33:51 Europe/London 1.122020_002 2012-07-20 15:56:04 Europe/London * change docs for wake_up to formalise integer * Add macros for paging for HP (C. Tucker) 1.122010 2012-07-19 20:52:28 Europe/London 1.121990_002 2012-07-17 23:25:28 Europe/London * Add Port number and generic Opts support to Telnet transport. 1.121640 2012-06-12 23:22:19 Europe/London * NCI library version number is logged at instatiation. * New ignore_host_checks option for SSH to replace shkc. Note the following: This option defaults to ENABLED meaning that openssh no longer checks host IDs. See documentation in Net::CLI::Interact::Transport::SSH for details. 1.121570 2012-06-05 19:37:11 Europe/London * Fix Cisco prompt detection when containing [] characters (Alexander Hartmaier) * Detect connection failures and die with buffered transport error 1.120670 2012-03-07 20:20:06 Europe/London * Fix IO::Pty dependency only to appear on non-Win32. This has required a local hack to Dist::Zilla so please contact the author if you build from dist.ini. 1.120560 2012-02-25 16:52:07 Europe/London * fix typo (C. Vicente) * fix Win32 path to locate plink.exe 1.120042 2012-01-04 21:08:07 Europe/London * test release process 1.120040 2012-01-04 20:58:57 Europe/London * ors attribute should be writeable (V. Magnin) 1.113610 2011-12-27 00:51:35 Europe/London * New implementation of output parser. Note the following: For the cmd() and macro() methods: In scalar context all data is returned. In list context the gathered response is returned as a list of lines. In both cases your local platform's newline character will end all lines. 1.113600 2011-12-26 16:35:32 Europe/London * Add has_prompt and has_macro methods * Add Foundry phrasebook (Vincent Magnin) 1.112610 2011-09-18 10:41:37 Europe/London * Escape embedded % in cmd() commands (B. Hogden) * Apply POD typo fixes patch (V. Foitzik) 1.112602 2011-09-17 23:45:50 Europe/London * Newline only added if there was a new *line* in output 1.112601 2011-09-17 18:32:55 Europe/London * Fixes to handling of newlines in returned response. Now aggressively remove control characters, and replace all newlines with \n 1.112600 2011-09-17 14:17:44 Europe/London * Set quotemeta when stripping command from returned output (V. Foitzik) 1.112190 2011-08-07 21:33:07 Europe/London * use_net_telnet_connection is checked even on Win32 * make add_library actually DTRT when you want both lib and add_lib 1.111590 2011-06-08 23:26:09 Europe/London * Support more SSH transport options, handle username 1.111530 2011-06-02 23:44:29 Europe/London * Support multi match in macros * Remove is_lazy - always update the prompt * Phrasebook fixes for Net::Appliance::Session 1.111500 2011-05-30 16:00:03 Europe/London * New cross platform backend (IPC::Run or Net::Telnet) 1.111150 2011-04-25 19:55:40 Europe/London * Cross platform support (i.e. Win32) * More documentation * Added a few minor support methods 1.110911 2011-04-01 15:09:58 Europe/London * Minor POD formatting typos fixed 1.110910 2011-04-01 13:04:09 Europe/London * Fix "private" packages scoping so that Dist::Zilla works * Add tutorial and cookbook POD files 1.110900 2011-03-31 14:41:17 Europe/London * More POD 1.110891 2011-03-30 13:16:46 Europe/London * More POD 1.110890 2011-03-30 13:13:48 Europe/London * Initial release on an unsuspecting world. Net-CLI-Interact-2.300002/MANIFEST000444000766000024 432313170404307 15716 0ustar00oliverstaff000000000000Build.PL Changes lib/Net/CLI/Interact.pm lib/Net/CLI/Interact/Action.pm lib/Net/CLI/Interact/ActionSet.pm lib/Net/CLI/Interact/Logger.pm lib/Net/CLI/Interact/Manual/Cookbook.pod lib/Net/CLI/Interact/Manual/Phrasebook.pod lib/Net/CLI/Interact/Manual/Tutorial.pod lib/Net/CLI/Interact/Phrasebook.pm lib/Net/CLI/Interact/Role/Engine.pm lib/Net/CLI/Interact/Role/FindMatch.pm lib/Net/CLI/Interact/Role/Iterator.pm lib/Net/CLI/Interact/Role/Prompt.pm lib/Net/CLI/Interact/Transport/Base.pm lib/Net/CLI/Interact/Transport/Loopback.pm lib/Net/CLI/Interact/Transport/Net_OpenSSH.pm lib/Net/CLI/Interact/Transport/Platform/Unix.pm lib/Net/CLI/Interact/Transport/Platform/Win32.pm lib/Net/CLI/Interact/Transport/Role/ConnectCore.pm lib/Net/CLI/Interact/Transport/Role/StripControlChars.pm lib/Net/CLI/Interact/Transport/Serial.pm lib/Net/CLI/Interact/Transport/SSH.pm lib/Net/CLI/Interact/Transport/Telnet.pm lib/Net/CLI/Interact/Transport/Wrapper/Base.pm lib/Net/CLI/Interact/Transport/Wrapper/IPC_Run.pm lib/Net/CLI/Interact/Transport/Wrapper/Net_Telnet.pm MANIFEST This list of files META.json META.yml README.md share/phrasebook/avaya/pb share/phrasebook/cisco/asa/pb share/phrasebook/cisco/catos/pb share/phrasebook/cisco/foundry/pb share/phrasebook/cisco/hp/pb share/phrasebook/cisco/ios/pb share/phrasebook/cisco/junos/pb share/phrasebook/cisco/nortel/pb share/phrasebook/cisco/pb share/phrasebook/cisco/pixos/fwsm/fwsm3/pb share/phrasebook/cisco/pixos/pb share/phrasebook/cisco/pixos/pixos7/pb share/phrasebook/cisco/wlc/pb share/phrasebook/extremexos/pb share/phrasebook/f5/f5bigip/pb share/phrasebook/fortinet/pb share/phrasebook/mikrotik/pb share/phrasebook/ovmcli/pb share/phrasebook/redback/pb share/phrasebook/screenos/pb share/phrasebook/unix/bash/pb share/phrasebook/unix/csh/pb share/phrasebook/unix/csh/sdf/pb share/phrasebook/unix/qnap/pb share/phrasebook/zyxel/pb t/10_construct.t t/author-10_route_server.t t/author-11_ssh_unknown_host.t t/author-12_ssh_no_route.t t/author-13_ssh_timeout.t t/author-20_sdf_shell.t t/phrasebook/cisco/pixos/pixos7/blah/pb t/phrasebook/testing/phrases t/release-20_connect.t t/release-30_phrasebook.t t/release-31_actionset.t t/release-32_action.t t/release-40_transport.t t/release-50_cmd.t t/release-60_prompt.t Net-CLI-Interact-2.300002/META.json000444000766000024 1213713170404307 16230 0ustar00oliverstaff000000000000{ "abstract" : "Toolkit for CLI Automation", "author" : [ "Oliver Gorwits " ], "dynamic_config" : 1, "generated_by" : "Module::Build version 0.4224", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Net-CLI-Interact", "prereqs" : { "build" : { "requires" : { "ExtUtils::CBuilder" : "0" } }, "configure" : { "requires" : { "Module::Build" : "0.42" } }, "runtime" : { "requires" : { "Class::Load" : "0", "Class::Mix" : "0", "Config::Any" : "0.27", "File::Basename" : "0", "File::ShareDir" : "1.00", "FileHandle" : "0", "IO::Pty" : "0", "IPC::Run" : "0", "List::Util" : "0", "Log::Dispatch::Config" : "0", "Log::Dispatch::Configurator::Any" : "0", "Moo" : "0", "Moo::Role" : "0", "MooX::Types::MooseLike::Base" : "0", "Net::Telnet" : "0", "POSIX" : "0", "Path::Class" : "0", "Sub::Quote" : "0", "Time::HiRes" : "0" } }, "test" : { "requires" : { "Test::More" : "0.88" } } }, "provides" : { "Net::CLI::Interact" : { "file" : "lib/Net/CLI/Interact.pm", "version" : "2.300002" }, "Net::CLI::Interact::Action" : { "file" : "lib/Net/CLI/Interact/Action.pm", "version" : "2.300002" }, "Net::CLI::Interact::ActionSet" : { "file" : "lib/Net/CLI/Interact/ActionSet.pm", "version" : "2.300002" }, "Net::CLI::Interact::Logger" : { "file" : "lib/Net/CLI/Interact/Logger.pm", "version" : "2.300002" }, "Net::CLI::Interact::Phrasebook" : { "file" : "lib/Net/CLI/Interact/Phrasebook.pm", "version" : "2.300002" }, "Net::CLI::Interact::Role::Engine" : { "file" : "lib/Net/CLI/Interact/Role/Engine.pm", "version" : "2.300002" }, "Net::CLI::Interact::Role::FindMatch" : { "file" : "lib/Net/CLI/Interact/Role/FindMatch.pm", "version" : "2.300002" }, "Net::CLI::Interact::Role::Iterator" : { "file" : "lib/Net/CLI/Interact/Role/Iterator.pm", "version" : "2.300002" }, "Net::CLI::Interact::Role::Prompt" : { "file" : "lib/Net/CLI/Interact/Role/Prompt.pm", "version" : "2.300002" }, "Net::CLI::Interact::Transport::Base" : { "file" : "lib/Net/CLI/Interact/Transport/Base.pm", "version" : "2.300002" }, "Net::CLI::Interact::Transport::Loopback" : { "file" : "lib/Net/CLI/Interact/Transport/Loopback.pm", "version" : "2.300002" }, "Net::CLI::Interact::Transport::Net_OpenSSH" : { "file" : "lib/Net/CLI/Interact/Transport/Net_OpenSSH.pm", "version" : "2.300002" }, "Net::CLI::Interact::Transport::Platform::Unix" : { "file" : "lib/Net/CLI/Interact/Transport/Platform/Unix.pm", "version" : "2.300002" }, "Net::CLI::Interact::Transport::Platform::Win32" : { "file" : "lib/Net/CLI/Interact/Transport/Platform/Win32.pm", "version" : "2.300002" }, "Net::CLI::Interact::Transport::Role::ConnectCore" : { "file" : "lib/Net/CLI/Interact/Transport/Role/ConnectCore.pm", "version" : "2.300002" }, "Net::CLI::Interact::Transport::Role::StripControlChars" : { "file" : "lib/Net/CLI/Interact/Transport/Role/StripControlChars.pm", "version" : "2.300002" }, "Net::CLI::Interact::Transport::SSH" : { "file" : "lib/Net/CLI/Interact/Transport/SSH.pm", "version" : "2.300002" }, "Net::CLI::Interact::Transport::Serial" : { "file" : "lib/Net/CLI/Interact/Transport/Serial.pm", "version" : "2.300002" }, "Net::CLI::Interact::Transport::Telnet" : { "file" : "lib/Net/CLI/Interact/Transport/Telnet.pm", "version" : "2.300002" }, "Net::CLI::Interact::Transport::Wrapper::Base" : { "file" : "lib/Net/CLI/Interact/Transport/Wrapper/Base.pm", "version" : "2.300002" }, "Net::CLI::Interact::Transport::Wrapper::IPC_Run" : { "file" : "lib/Net/CLI/Interact/Transport/Wrapper/IPC_Run.pm", "version" : "2.300002" }, "Net::CLI::Interact::Transport::Wrapper::Net_Telnet" : { "file" : "lib/Net/CLI/Interact/Transport/Wrapper/Net_Telnet.pm", "version" : "2.300002" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/ollyg/Net-CLI-Interact/issues" }, "license" : [ "http://dev.perl.org/licenses/" ], "repository" : { "url" : "https://github.com/ollyg/Net-CLI-Interact" } }, "version" : "2.300002", "x_serialization_backend" : "JSON::PP version 2.94" } Net-CLI-Interact-2.300002/META.yml000444000766000024 710613170404307 16040 0ustar00oliverstaff000000000000--- abstract: 'Toolkit for CLI Automation' author: - 'Oliver Gorwits ' build_requires: ExtUtils::CBuilder: '0' Test::More: '0.88' configure_requires: Module::Build: '0.42' dynamic_config: 1 generated_by: 'Module::Build version 0.4224, CPAN::Meta::Converter version 2.150010' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Net-CLI-Interact provides: Net::CLI::Interact: file: lib/Net/CLI/Interact.pm version: '2.300002' Net::CLI::Interact::Action: file: lib/Net/CLI/Interact/Action.pm version: '2.300002' Net::CLI::Interact::ActionSet: file: lib/Net/CLI/Interact/ActionSet.pm version: '2.300002' Net::CLI::Interact::Logger: file: lib/Net/CLI/Interact/Logger.pm version: '2.300002' Net::CLI::Interact::Phrasebook: file: lib/Net/CLI/Interact/Phrasebook.pm version: '2.300002' Net::CLI::Interact::Role::Engine: file: lib/Net/CLI/Interact/Role/Engine.pm version: '2.300002' Net::CLI::Interact::Role::FindMatch: file: lib/Net/CLI/Interact/Role/FindMatch.pm version: '2.300002' Net::CLI::Interact::Role::Iterator: file: lib/Net/CLI/Interact/Role/Iterator.pm version: '2.300002' Net::CLI::Interact::Role::Prompt: file: lib/Net/CLI/Interact/Role/Prompt.pm version: '2.300002' Net::CLI::Interact::Transport::Base: file: lib/Net/CLI/Interact/Transport/Base.pm version: '2.300002' Net::CLI::Interact::Transport::Loopback: file: lib/Net/CLI/Interact/Transport/Loopback.pm version: '2.300002' Net::CLI::Interact::Transport::Net_OpenSSH: file: lib/Net/CLI/Interact/Transport/Net_OpenSSH.pm version: '2.300002' Net::CLI::Interact::Transport::Platform::Unix: file: lib/Net/CLI/Interact/Transport/Platform/Unix.pm version: '2.300002' Net::CLI::Interact::Transport::Platform::Win32: file: lib/Net/CLI/Interact/Transport/Platform/Win32.pm version: '2.300002' Net::CLI::Interact::Transport::Role::ConnectCore: file: lib/Net/CLI/Interact/Transport/Role/ConnectCore.pm version: '2.300002' Net::CLI::Interact::Transport::Role::StripControlChars: file: lib/Net/CLI/Interact/Transport/Role/StripControlChars.pm version: '2.300002' Net::CLI::Interact::Transport::SSH: file: lib/Net/CLI/Interact/Transport/SSH.pm version: '2.300002' Net::CLI::Interact::Transport::Serial: file: lib/Net/CLI/Interact/Transport/Serial.pm version: '2.300002' Net::CLI::Interact::Transport::Telnet: file: lib/Net/CLI/Interact/Transport/Telnet.pm version: '2.300002' Net::CLI::Interact::Transport::Wrapper::Base: file: lib/Net/CLI/Interact/Transport/Wrapper/Base.pm version: '2.300002' Net::CLI::Interact::Transport::Wrapper::IPC_Run: file: lib/Net/CLI/Interact/Transport/Wrapper/IPC_Run.pm version: '2.300002' Net::CLI::Interact::Transport::Wrapper::Net_Telnet: file: lib/Net/CLI/Interact/Transport/Wrapper/Net_Telnet.pm version: '2.300002' requires: Class::Load: '0' Class::Mix: '0' Config::Any: '0.27' File::Basename: '0' File::ShareDir: '1.00' FileHandle: '0' IO::Pty: '0' IPC::Run: '0' List::Util: '0' Log::Dispatch::Config: '0' Log::Dispatch::Configurator::Any: '0' Moo: '0' Moo::Role: '0' MooX::Types::MooseLike::Base: '0' Net::Telnet: '0' POSIX: '0' Path::Class: '0' Sub::Quote: '0' Time::HiRes: '0' resources: bugtracker: https://github.com/ollyg/Net-CLI-Interact/issues license: http://dev.perl.org/licenses/ repository: https://github.com/ollyg/Net-CLI-Interact version: '2.300002' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' Net-CLI-Interact-2.300002/README.md000444000766000024 375513170404307 16054 0ustar00oliverstaff000000000000### Net::CLI::Interact - Toolkit for CLI Automation ### Automating command line interface (CLI) interactions is not a new idea, but can be tricky to implement. This module aims to provide a simple and manageable interface to CLI interactions, supporting: * SSH, Telnet and Serial-Line connections * Unix and Windows support * Reuseable device command phrasebooks If you're a new user, please read the [Tutorial](https://metacpan.org/pod/Net::CLI::Interact::Manual::Tutorial). There's also a [Cookbook](https://metacpan.org/pod/Net::CLI::Interact::Manual::Cookbook) and a [Phrasebook Listing](https://metacpan.org/pod/Net::CLI::Interact::Manual::Phrasebook). #### Installation #### Using cpanm: cpanm Net::CLI::Interact Or manually, from the source: perl Makefile.PL make test && make install #### Example Usage #### ```perl use Net::CLI::Interact; my $s = Net::CLI::Interact->new({ personality => 'cisco', transport => 'Telnet', connect_options => { host => '192.0.2.1' }, }); # respond to a usename/password prompt $s->macro('to_user_exec', { params => ['my_username', 'my_password'], }); my $interfaces = $s->cmd('show ip interfaces brief'); $s->macro('to_priv_exec', { params => ['my_password'], }); # matched prompt is updated automatically # paged output is slurped into one response $s->macro('show_run'); my $config = $s->last_response; ``` For a more complete worked example check out the [Net::Appliance::Session](https://metacpan.org/pod/Net::Appliance::Session) distribution, for which this module was written. For more information on the API, please check out [Net::CLI::Interact's complete documentation](https://metacpan.org/pod/Net::CLI::Interact) on CPAN. #### Copyright and License This software is copyright (c) 2014-2015 by Oliver Gorwits. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Net-CLI-Interact-2.300002/lib000755000766000024 013170404307 15174 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/lib/Net000755000766000024 013170404307 15722 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/lib/Net/CLI000755000766000024 013170404307 16331 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/lib/Net/CLI/Interact.pm000444000766000024 2345613170404307 20627 0ustar00oliverstaff000000000000package Net::CLI::Interact; { $Net::CLI::Interact::VERSION = '2.300002' } use Moo; use Sub::Quote; use Class::Load (); use MooX::Types::MooseLike::Base qw(InstanceOf Maybe Str HashRef); with 'Net::CLI::Interact::Role::Engine'; has 'my_args' => ( is => 'rwp', isa => HashRef, ); # stash all args in my_args sub BUILDARGS { my ($class, @args) = @_; # accept single hash ref or naked hash my $params = (ref {} eq ref $args[0] ? $args[0] : {@args}); return { my_args => $params }; } sub default_log_categories { return (qw/dialogue dump engine object phrasebook prompt transport/); } has 'log_at' => ( is => 'rw', isa => Maybe[Str], default => quote_sub(q[ $ENV{'NCI_LOG_AT'} ]), trigger => \&set_global_log_at, ); sub set_global_log_at { my ($self, $level) = @_; return unless defined $level and length $level; $self->logger->log_flags({ map {$_ => $level} default_log_categories() }); } sub BUILD { my $self = shift; $self->set_global_log_at($self->log_at); $self->logger->log('engine', 'notice', sprintf "NCI loaded, version %s", ($Net::CLI::Interact::VERSION || 'devel')); } has 'logger' => ( is => 'lazy', isa => InstanceOf['Net::CLI::Interact::Logger'], predicate => 1, clearer => 1, ); sub _build_logger { my $self = shift; use Net::CLI::Interact::Logger; return Net::CLI::Interact::Logger->new($self->my_args); } has 'phrasebook' => ( is => 'lazy', isa => InstanceOf['Net::CLI::Interact::Phrasebook'], predicate => 1, clearer => 1, ); sub _build_phrasebook { my $self = shift; use Net::CLI::Interact::Phrasebook; return Net::CLI::Interact::Phrasebook->new({ %{ $self->my_args }, logger => $self->logger, }); } # does not really *change* the phrasebook, just reconfig and nuke sub set_phrasebook { my ($self, $args) = @_; return unless defined $args and ref {} eq ref $args; $self->my_args->{$_} = $args->{$_} for keys %$args; $self->clear_phrasebook; } has 'transport' => ( is => 'lazy', isa => quote_sub(q{ $_[0]->isa('Net::CLI::Interact::Transport') }), predicate => 1, clearer => 1, ); sub _build_transport { my $self = shift; die 'missing transport' unless exists $self->my_args->{transport}; my $tpt = 'Net::CLI::Interact::Transport::'. $self->my_args->{transport}; Class::Load::load_class($tpt); return $tpt->new({ %{ $self->my_args }, logger => $self->logger, }); } 1; =pod =head1 NAME Net::CLI::Interact - Toolkit for CLI Automation =head1 PURPOSE This module exists to support developers of applications and libraries which must interact with a command line interface. =head1 SYNOPSIS use Net::CLI::Interact; my $s = Net::CLI::Interact->new({ personality => 'cisco', transport => 'Telnet', connect_options => { host => '192.0.2.1' }, }); # respond to a usename/password prompt $s->macro('to_user_exec', { params => ['my_username', 'my_password'], }); my $interfaces = $s->cmd('show ip interfaces brief'); $s->macro('to_priv_exec', { params => ['my_password'], }); # matched prompt is updated automatically # paged output is slurped into one response $s->macro('show_run'); my $config = $s->last_response; =head1 DESCRIPTION Automating command line interface (CLI) interactions is not a new idea, but can be tricky to implement. This module aims to provide a simple and manageable interface to CLI interactions, supporting: =over 4 =item * SSH, Telnet and Serial-Line connections =item * Unix and Windows support =item * Reuseable device command phrasebooks =back If you're a new user, please read the L. There's also a L and a L. For a more complete worked example check out the L distribution, for which this module was written. =head1 INTERFACE =head2 new( \%options ) Prepares a new session for you, but will not connect to any device. On Windows platforms, you B download the C program, and pass its location to the C parameter. Other options are: =over 4 =item C<< personality => $name >> (required) The family of device command phrasebooks to load. There is a built-in library within this module, or you can provide a search path to other libraries. See L for further details. =item C<< transport => $backend >> (required) The name of the transport backend used for the session, which may be one of L, L, or L. =item C<< connect_options => \%options >> If the transport backend can take any options (for example the target hostname), then pass those options in this value as a hash ref. See the respective manual pages for each transport backend for further details. =item C<< log_at => $log_level >> To make using the C somewhat easier, you can pass this argument the name of a log I (such as C, C, etc) and all logging in the library will be enabled at that level. Use C to learn about how the library is working internally. See L for a list of the valid level names. =item C<< timeout => $seconds >> Configures a default timeout value, in seconds, for interaction with the remote device. The default is 10 seconds. You can also set timeout on a per-command or per-macro call (see below). Note that this does not (currently) apply to the initial connection. =back =head2 cmd( $command ) Execute a single command statement on the connected device, and consume output until there is a match with the current I. The statement is executed verbatim on the device, with a newline appended. In scalar context the C is returned (see below). In list context the gathered response is returned as a list of lines. In both cases your local platform's newline character will end all lines. =head2 macro( $name, \%options? ) Execute the commands contained within the named Macro, which must be loaded from a Phrasebook. Options to control the output, including variables for substitution into the Macro, are passed in the C<%options> hash reference. In scalar context the C is returned (see below). In list context the gathered response is returned as a list of lines. In both cases your local platform's newline character will end all lines. =head2 last_response Returns the gathered output after the most recent C or C. In scalar context all data is returned. In list context the gathered response is returned as a list of lines. In both cases your local platform's newline character will end all lines. =head2 transport Returns the L backend which was loaded based on the C option to C. See the L, L, or L documentation for further details. =head2 phrasebook Returns the Phrasebook object which was loaded based on the C option given to C. See L for further details. =head2 set_phrasebook( \%options ) Allows you to (re-)configure the loaded phrasebook, perhaps changing the personality or library, or other properties. The C<%options> Hash ref should be any parameters from the L module, but at a minimum must include a C. =head2 set_default_contination( $macro_name ) Briefly, a Continuation handles the slurping of paged output from commands. See the L documentation for further details. Pass in the name of a defined Contination (Macro) to enable paging handling as a default for all sent commands. This is an alternative to describing the Continuation format in each Macro. To unset the default Continuation, call the C method. =head2 logger This is the application's L object. A powerful logging subsystem is available to your application, built upon the L distribution. You can enable logging of this module's processes at various levels, or add your own logging statements. =head2 set_global_log_at( $level ) To make using the C somewhat easier, you can pass this method the name of a log I (such as C, C, etc) and all logging in the library will be enabled at that level. Use C to learn about how the library is working internally. See L for a list of the valid level names. =head1 FUTHER READING =head2 Prompt Matching Whenever a command statement is issued, output is slurped until a matching prompt is seen in that output. Control of the Prompts is shared between the definitions in L dictionaries, and methods of the L core component. See that module's documentation for further details. =head2 Actions and ActionSets All commands and macros are composed from their phrasebook definitions into L and L (iterable sequences of Actions). See those modules' documentation for further details, in case you wish to introspect their structures. =head1 COMPOSITION See the following for further interface details: =over 4 =item * L =back =head1 AUTHOR Oliver Gorwits =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017 by Oliver Gorwits. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut Net-CLI-Interact-2.300002/lib/Net/CLI/Interact000755000766000024 013170404307 20102 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Action.pm000444000766000024 1206513170404307 22036 0ustar00oliverstaff000000000000package Net::CLI::Interact::Action; { $Net::CLI::Interact::Action::VERSION = '2.300002' } use Moo; use Sub::Quote; use MooX::Types::MooseLike::Base qw(Any Bool Str ArrayRef InstanceOf RegexpRef); use Net::CLI::Interact::ActionSet; has 'type' => ( is => 'ro', isa => quote_sub( q{ die "$_[0] not send/match" unless $_[0] =~ m/^(?:send|match)$/ }), required => 1, ); has 'value' => ( is => 'ro', isa => Any, # FIXME 'Str|ArrayRef[RegexpRef]', required => 1, ); has 'no_ors' => ( is => 'ro', isa => Bool, default => quote_sub('0'), ); has 'continuation' => ( is => 'rw', isa => InstanceOf['Net::CLI::Interact::ActionSet'], ); has 'params' => ( is => 'rw', isa => ArrayRef, default => sub { [] }, ); has 'response' => ( is => 'rw', isa => Str, default => quote_sub(q{''}), ); has 'response_stash' => ( is => 'rw', isa => Str, default => quote_sub(q{''}), ); has 'prompt_hit' => ( is => 'rw', isa => RegexpRef, ); sub BUILDARGS { my ($class, @rest) = @_; # accept single hash ref or naked hash my $params = (ref {} eq ref $rest[0] ? $rest[0] : {@rest}); if (exists $params->{continuation} and ref [] eq ref $params->{continuation}) { $params->{continuation} = Net::CLI::Interact::ActionSet->new({ actions => $params->{continuation}, }); } return $params; } # Only a shallow copy so all the reference based slots still # share data with the original Action's slots. # # I asked in #web-simple and was told that if the object is more magical than # a simple hashref, then someone is trying to be far too clever. agreed! sub clone { my $self = shift; bless({ %{$self}, %{(shift) || {}} }, ref $self) } # count the number of sprintf parameters used in the value sub num_params { my $self = shift; return 0 if ref $self->value; # this tricksy little number comes from the Perl FAQ my $count = () = $self->value =~ m/(? and it's unlikely that an end-user will need to make use of Action objects directly. The interface is documented here as a matter of record. An Action object represents I some kind of text or command to send to a connected device, I a regular expression matching the response from a connected device. Such Actions are built up into ActionSets which describe a conversation with the connected device. If the Action is a C type, then after execution it can be cloned and augmented with the response text of the command. If the response is likely to be paged, then the Action may also store instruction in how to trigger and consume the pages. =head1 INTERFACE =head2 type Denotes the kind of Action, which may be C or C. =head2 value In the case of C, a String command to send to the device. In the case of C, a regular expression reference to match response from the device. In special circumstances an array reference of regular expression references is also valid, and each will be checked for a match against the device response. =head2 no_ors Only applies to the C kind. Whether to skip appending the I (newline) to the C command when sent to the connected device. =head2 continuation Only applies to the C kind. When response output is likely to be paged, this stores an L that contains two Actions: one for the C which indicates output has paused at the end of a page, and one for the C command which triggers printing of the next page. =head2 params Only applies to the C kind, and contains a list of parameters which are substituted into the C using Perl's C function. Insufficient parameters causes C to die. =head2 num_params Only applies to the C kind, and returns the number of parameters which are required for the current C. Used for error checking when setting C. =head2 response A stash for the returned prompt which matched and triggered the end of this action. =head2 response_stash A stash for the returned output following a C command, but not including the matched prompt which ended the action. This slot is used by the C action as it slurps output, but the content is then transferred over to the partner C in the ActionSet. =head2 prompt_hit When a command is successfully issued, the response is terminated by a prompt. However that prompt can be one of a list, defined in the Action. This slot records the regular expression from that list which was actually matched. =head2 clone Returns a new Action, which is a shallow clone of the existing one. All the reference based slots will share data, but you can add (for example) a C without affecting the original Action. Used when preparing to execute an Action which has been retrieved from the L. =cut Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/ActionSet.pm000444000766000024 2016013170404307 22505 0ustar00oliverstaff000000000000package Net::CLI::Interact::ActionSet; { $Net::CLI::Interact::ActionSet::VERSION = '2.300002' } use Moo; use Sub::Quote; use MooX::Types::MooseLike::Base qw(InstanceOf ArrayRef CodeRef RegexpRef); use Net::CLI::Interact::Action; with 'Net::CLI::Interact::Role::Iterator'; has default_continuation => ( is => 'rw', isa => InstanceOf['Net::CLI::Interact::ActionSet'], predicate => 1, ); has current_match => ( is => 'rw', isa => ArrayRef[RegexpRef], predicate => 1, coerce => quote_sub(q{ (ref qr// eq ref $_[0]) ? [$_[0]] : $_[0] }), ); sub BUILDARGS { my ($class, @rest) = @_; # accept single hash ref or naked hash my $params = (ref {} eq ref $rest[0] ? $rest[0] : {@rest}); if (exists $params->{actions} and ref $params->{actions} eq ref []) { foreach my $a (@{$params->{actions}}) { if (ref $a eq 'Net::CLI::Interact::ActionSet') { push @{$params->{_sequence}}, @{ $a->_sequence }; next; } if (ref $a eq 'Net::CLI::Interact::Action') { push @{$params->{_sequence}}, $a; next; } if (ref $a eq ref {}) { push @{$params->{_sequence}}, Net::CLI::Interact::Action->new($a); next; } die "don't know what to do with a: '$a'\n"; } delete $params->{actions}; } return $params; } sub clone { my $self = shift; return Net::CLI::Interact::ActionSet->new({ actions => [ map { $_->clone } @{ $self->_sequence } ], ($self->_has_callbacks ? (_callbacks => $self->_callbacks) : ()), ($self->has_default_continuation ? (default_continuation => $self->default_continuation) : ()), ($self->has_current_match ? (current_match => $self->current_match) : ()), }); } # store params to the set, used when send is passed via sprintf sub apply_params { my ($self, @params) = @_; $self->reset; while ($self->has_next) { my $next = $self->next; $next->params([splice @params, 0, $next->num_params]); } } has _callbacks => ( is => 'rw', isa => ArrayRef[CodeRef], default => sub { [] }, predicate => 1, ); sub register_callback { my $self = shift; $self->_callbacks([ @{$self->_callbacks}, shift ]); } sub execute { my $self = shift; $self->_pad_send_with_match; $self->_forward_continuation_to_match; $self->_do_exec; $self->_marshall_responses; } sub _do_exec { my $self = shift; $self->reset; while ($self->has_next) { $_->($self->next) for @{$self->_callbacks}; } } # pad out the Actions with match Actions if needed between send pairs. sub _pad_send_with_match { my $self = shift; my $match = Net::CLI::Interact::Action->new({ type => 'match', value => $self->current_match, }); $self->reset; while ($self->has_next) { my $this = $self->next; my $next = $self->peek or last; # careful... next unless $this->type eq 'send' and $next->type eq 'send'; $self->insert_at($self->idx + 1, $match->clone); } # always finish on a match if ($self->last->type ne 'match') { $self->insert_at($self->count, $match->clone); } } # carry-forward a continuation beacause it's the match which really does the # heavy lifting. sub _forward_continuation_to_match { my $self = shift; $self->reset; while ($self->has_next) { my $this = $self->next; my $next = $self->peek or last; # careful... my $cont = ($this->continuation || $self->default_continuation); next unless $this->type eq 'send' and $next->type eq 'match' and defined $cont; $next->continuation($cont); } } # marshall the responses so as to move data from match to send sub _marshall_responses { my $self = shift; $self->reset; while ($self->has_next) { my $send = $self->next; my $match = $self->peek or last; # careful... next unless $match->type eq 'match'; # remove echoed command from the beginning my $cmd = quotemeta( sprintf $send->value, @{ $send->params } ); (my $output = $match->response_stash) =~ s/^${cmd}[\t ]*(?:\r\n|\r|\n)?//s; $send->response($output); } } 1; =pod =head1 NAME Net::CLI::Interact::ActionSet - Conversation of Send and Match Actions =head1 DESCRIPTION This class is used internally by L and it's unlikely that an end-user will need to make use of ActionSet objects directly. The interface is documented here as a matter of record. An ActionSet comprises a sequence (usefully, two or more) of L which describe a conversation with a connected network device. Actions will alternate between type C and C, perhaps not in their original L definition, but certainly by the time they are used. If the first Action is of type C then the ActionSet is a normal sequence of "send a command" then "match a response", perhaps repeated. If the first Action is of type C then the ActionSet represents a C, which is the method of dealing with paged output. =head1 INTERFACE =head2 default_continuation An ActionSet (C then C) which will be available for use on all commands sent from this ActionSet. An alternative to explicitly describing the Continuation sequence within the Phrasebook. =head2 current_match A stash for the current Prompt (regular expression reference) which L expects to see after each command. This is passed into the constructor and is used when padding Match Actions into the ActionSet (see C, below). =head2 clone Returns a new ActionSet which is a shallow clone of the existing one. All the reference based slots will share data, but you can add (for example) a C without affecting the original ActionSet. Used when preparing to execute an ActionSet which has been retrieved from the L. =head2 apply_params Accepts a list of parameters which will be used when C is called on each Send Action in the set. You must supply sufficient parameters as a list for I Send Actions in the set, and they will be popped off and stashed with the Action(s) according to how many are required. =head2 register_callback Allows the L to be registered such that when the ActionSet is executed, commands are sent to the registered callback subroutine. May be called more than once, and on execution each of the callbacks will be run, in turn and in order. =head2 execute The business end of this class, where the sequence of Actions is prepared for execution and then control passed to the Transport. This process is split into a number of phases: =over 4 =item Pad C with C The Phrasebook allows missing out of the Match statements between Send statements, when they are expected to be the same as the C. This phase inserts Match statements to restore a complete ActionSet definition. =item Forward C to C In the Phrasebook a user defines a Continuation (C, then C) following a Send statement (because it deals with the response to the sent command). However they are actually used by the Match, as it's the Match which captures output. This phase copies Continuation ActionSets from Send statements to following Match statements in the ActionSet. It also performs a similar action using the C if one is set and there's no existing Continuation configured. =item Callback(s) Here the registered callbacks are executed (i.e. data is sent to the Transport). =item Marshall Responses Finally, responses which are stashed in the Match Actions are copied back to the Send actions, as more logically they are responses to commands sent. The ActionSet is now ready for access to retrieve the C from the device. =back =head1 COMPOSITION See the following for further interface details: =over 4 =item * L =back =cut Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Logger.pm000444000766000024 1635113170404307 22042 0ustar00oliverstaff000000000000package Net::CLI::Interact::Logger; { $Net::CLI::Interact::Logger::VERSION = '2.300002' } use Moo; use Sub::Quote; use MooX::Types::MooseLike::Base qw(HashRef Bool ArrayRef Any); use Class::Mix qw(genpkg); use Time::HiRes qw(gettimeofday tv_interval); use Log::Dispatch::Config; # loads Log::Dispatch use Log::Dispatch::Configurator::Any; sub BUILDARGS { my ($class, @args) = @_; # accept single hash ref or naked hash my $params = (ref {} eq ref $args[0] ? $args[0] : {@args}); # back-compat for old attr name $params->{log_stamp} = $params->{log_stamps} if exists $params->{log_stamps}; return $params; } has log_config => ( is => 'rw', isa => HashRef, builder => 1, trigger => quote_sub(q{ $_[0]->_clear_logger }), ); sub _build_log_config { return { dispatchers => ['screen'], screen => { class => 'Log::Dispatch::Screen', min_level => 'debug', }, }; } has _logger => ( is => 'ro', isa => quote_sub(q{ $_[0]->isa('Log::Dispatch::Config') }), builder => 1, lazy => 1, clearer => 1, ); # this allows each instance of this module to have its own # wrapped logger with different configuration. sub _build__logger { my $self = shift; my $anon_logger = genpkg(); { no strict 'refs'; @{"$anon_logger\::ISA"} = 'Log::Dispatch::Config'; } my $config = Log::Dispatch::Configurator::Any->new($self->log_config); $anon_logger->configure($config); return $anon_logger->instance; } has 'log_stamp' => ( is => 'rw', isa => Bool, default => quote_sub('1'), ); has 'log_category' => ( is => 'rw', isa => Bool, default => quote_sub('1'), ); has 'log_start' => ( is => 'ro', isa => ArrayRef, default => sub{ [gettimeofday] }, ); has 'log_flags' => ( is => 'rw', isa => Any, # FIXME 'ArrayRef|HashRef[Str]', default => sub { {} }, ); my %code_for = ( debug => 0, info => 1, notice => 2, warning => 3, error => 4, critical => 5, alert => 6, emergency => 7, ); sub would_log { my ($self, $category, $level) = @_; return 0 if !defined $category or !defined $level; my $flags = (ref $self->log_flags eq ref [] ? { map {$_ => 'error'} @{$self->log_flags} } : $self->log_flags ); return 0 if !exists $code_for{$level}; return 0 if !exists $flags->{$category}; return ($code_for{$level} >= $code_for{ $flags->{$category} }); } sub log { my ($self, $category, $level, @msgs) = @_; return unless $self->would_log($category, $level); @msgs = grep {defined} @msgs; return unless scalar @msgs; my $prefix = ''; $prefix .= sprintf "[%11s] ", sprintf "%.6f", (tv_interval $self->log_start, [gettimeofday]) if $self->log_stamp; $prefix .= (substr $category, 0, 2) if $self->log_category; my $suffix = ''; $suffix = "\n" if $msgs[-1] !~ m/\n$/; $self->_logger->$level($prefix . (' ' x (2 - $code_for{$level})), (join ' ', @msgs) . $suffix); } 1; =pod =head1 NAME Net::CLI::Interact::Logger - Per-instance multi-target logging, with categories =head1 SYNOPSIS $logger->log($category, $level, @message); =head1 DESCRIPTION This module implements a generic logging service, based on L but with additional options and configuration. Log messages coming from your application are categorized, and each category can be enabled/disabled separately and have its own log level (i.e. C .. C). High resolution timestamps can be added to log messages. =head1 DEFAULT CONFIGURATION Being based on L, this logger can have multiple targets, each configured for independent level thresholds. The overall default configuration is to print log messages to the screen (console), with a minimum level of C. Each category (see below) has its own log level as well. Note that categories, as discussed below, are arbitrary so if a category is not explicitly enabled or disabled, it is assumed to be B. If you wish to invent a new category for your application, simply think of the name and begin to use it, with a C<$level> and C<@message> as above in the SYNOPSIS. =head1 INTERFACE =head2 log( $category, $level, @message ) The combination of category and level determine whether the the log messages are emitted to any of the log destinations. Destinations are set using the C method, and categories are configured using the C method. The C<@message> list will be joined by a space character, and a newline appended if the last message doesn't contain one itself. Messages are prepended with the first character of their C<$category>, and then indented proportionally to their C<$level>. =head2 log_config( \%config ) A C configuration (hash ref), meaning multiple log targets may be specified with different minimum level thresholds. There is a default configuration which emits messages to your screen (console) with no minimum threshold: { dispatchers => ['screen'], screen => { class => 'Log::Dispatch::Screen', min_level => 'debug', }, }; =head2 log_flags( \@categories | \%category_level_map ) The user is expected to specify which log categories they are interested in, and at what levels. If a category is used in the application for logging but not specified, then it is deemed B. Hence, even though the default destination log level is C, no messages are emitted until a category is enabled. In the array reference form, the list should contain category names, and they will all be mapped to the C level: $logger->log_flags([qw/ network disk io cpu /]); In the hash reference form, the keys should be category names and the values log levels from the list below (ordered such that each level "includes" the levels I): emergency alert critical error warning notice info debug For example: $logger->log_flags({ network => 'info', disk => 'debug', io => 'critical', cpu => 'debug', }); Messages at or above the specified level will be passed on to the C target, which may then specify an overriding threshold. =head2 C< Net::CLI::Interact->default_log_categories() >> Not a part of this class, but the only way to retrieve a list of the current log categories used in the L distribution source. Does not take into account any log categories added by the user. =head2 log_stamp( $boolean ) Enable (the default) or disable the display of high resolution interval timestamps with each log message. =head2 log_category( $boolean ) Enable (the default) or disable the display of the first letters of the category name with each log message. =head2 log_start( [$seconds, $microseconds] ) Time of the start for generating a time interval when logging stamps. Defaults to the result of C at the point the module is loaded, in list context. =head2 would_log( $category, $level ) Returns True if, according to the current C, the given C<$category> is enabled at or above the threshold of C<$level>, otherwise returns False. Note that the C targets maintain their own thresholds as well. =cut Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Phrasebook.pm000444000766000024 4174413170404307 22724 0ustar00oliverstaff000000000000package Net::CLI::Interact::Phrasebook; { $Net::CLI::Interact::Phrasebook::VERSION = '2.300002' } use Moo; use MooX::Types::MooseLike::Base qw(InstanceOf Str Any HashRef); use Path::Class; use File::ShareDir 'dist_dir'; use Net::CLI::Interact::ActionSet; has 'logger' => ( is => 'ro', isa => InstanceOf['Net::CLI::Interact::Logger'], required => 1, ); has 'personality' => ( is => 'rw', isa => Str, required => 1, ); has 'library' => ( is => 'lazy', isa => Any, # FIXME 'Str|ArrayRef[Str]', ); sub _build_library { return [ Path::Class::Dir->new( dist_dir('Net-CLI-Interact') ) ->subdir('phrasebook')->stringify ]; } has 'add_library' => ( is => 'rw', isa => Any, # FIXME 'Str|ArrayRef[Str]', default => sub { [] }, ); has '_prompt' => ( is => 'ro', isa => HashRef[InstanceOf['Net::CLI::Interact::ActionSet']], default => sub { {} }, ); sub prompt { my ($self, $name) = @_; die "unknown prompt [$name]" unless $self->has_prompt($name); return $self->_prompt->{$name}; } sub prompt_names { return keys %{ (shift)->_prompt } } sub has_prompt { my ($self, $name) = @_; die "missing prompt name!" unless defined $name and length $name; return exists $self->_prompt->{$name}; } has '_macro' => ( is => 'ro', isa => HashRef[InstanceOf['Net::CLI::Interact::ActionSet']], default => sub { {} }, ); sub macro { my ($self, $name) = @_; die "unknown macro [$name]" unless $self->has_macro($name); return $self->_macro->{$name}; } sub macro_names { return keys %{ (shift)->_macro } } sub has_macro { my ($self, $name) = @_; die "missing macro name!" unless defined $name and length $name; return exists $self->_macro->{$name}; } # matches which are prompt names are resolved to RegexpRefs # and regexp provided by the user are inflated into RegexpRefs sub _resolve_matches { my ($self, $actions) = @_; foreach my $a (@$actions) { next unless $a->{type} eq 'match'; next unless ref $a->{value} eq ref []; my @newvals = (); foreach my $v (@{ $a->{value} }) { if ($v =~ m{^/} and $v =~ m{/$}) { $v =~ s{^/}{}; $v =~ s{/$}{}; push @newvals, qr/$v/; } else { push @newvals, @{ $self->prompt($v)->first->value }; } } $a->{value} = \@newvals; } return $actions; } # inflate the hashref into action objects sub _bake { my ($self, $data) = @_; return unless ref $data eq ref {} and keys %$data; $self->logger->log('phrasebook', 'debug', 'storing', $data->{type}, $data->{name}); my $slot = '_'. lc $data->{type}; $self->$slot->{$data->{name}} = Net::CLI::Interact::ActionSet->new({ actions => $self->_resolve_matches($data->{actions}) }); } sub BUILD { my $self = shift; $self->load_phrasebooks; } # parse phrasebook files and load action objects sub load_phrasebooks { my $self = shift; my $data = {}; my $stash = { prompt => [], macro => [] }; foreach my $file ($self->_find_phrasebooks) { $self->logger->log('phrasebook', 'info', 'reading phrasebook', $file); my @lines = $file->slurp; while ($_ = shift @lines) { # Skip comments and empty lines next if m/^(?:#|\s*$)/; if (m{^(prompt|macro)\s+(\w+)\s*$}) { if (scalar keys %$data) { push @{ $stash->{$data->{type}} }, $data; } $data = {type => $1, name => $2}; next; } # skip new sections we don't yet understand elsif (m{^\w}) { $_ = shift @lines until m{^(?:prompt|macro)}; unshift @lines, $_; next; } if (m{^\s+send\s+(.+)$}) { my $value = $1; $value =~ s/^["']//; $value =~ s/["']$//; push @{ $data->{actions} }, { type => 'send', value => $value, }; next; } if (m{^\s+put\s+(.+)$}) { my $value = $1; $value =~ s/^["']//; $value =~ s/["']$//; push @{ $data->{actions} }, { type => 'send', value => $value, no_ors => 1, }; next; } if (m{^\s+match\s+(.+)\s*$}) { my @vals = split m/\s+or\s+/, $1; if (scalar @vals) { push @{ $data->{actions} }, {type => 'match', value => \@vals}; next; } } if (m{^\s+follow\s+/(.+)/\s+with\s+(.+)\s*$}) { my ($match, $send) = ($1, $2); $send =~ s/^["']//; $send =~ s/["']$//; $data->{actions}->[-1]->{continuation} = [ {type => 'match', value => [qr/$match/]}, {type => 'send', value => eval "qq{$send}", no_ors => 1} ]; next; } die "don't know what to do with this phrasebook line:\n", $_; } # last entry in the file needs baking push @{ $stash->{$data->{type}} }, $data; $data = {}; } # bake the prompts before the macros, to allow macros to reference # prompts which appear later in the same file. foreach my $t (qw/prompt macro/) { foreach my $d (@{ $stash->{$t} }) { $self->_bake($d); } } } # finds the path of Phrasebooks within the Library leading to Personality sub _find_phrasebooks { my $self = shift; my @libs = (ref $self->library ? @{$self->library} : ($self->library)); my @alib = (ref $self->add_library ? @{$self->add_library} : ($self->add_library)); # first find the (relative) path for the requested personality # then within each of @libs gather the files along that path my $target = $self->_find_personality_in( @libs, @alib ); my @files = $self->_gather_pb_from( $target, @libs, @alib ); die (sprintf "Personality [%s] contains no phrasebook files!\n", $self->personality) unless scalar @files; return @files; } sub _find_personality_in { my ($self, @libs) = @_; my $target = undef; foreach my $lib (@libs) { Path::Class::Dir->new($lib)->recurse(callback => sub { return unless $_[0]->is_dir; $target = Path::Class::Dir->new($_[0])->relative($lib) if $_[0]->dir_list(-1) eq $self->personality }); last if defined $target; } return $target; } sub _gather_pb_from { my ($self, $target, @libs) = @_; my @files = (); return () unless $target->isa('Path::Class::Dir') and $target->is_relative; foreach my $lib (@libs) { my $root = Path::Class::Dir->new($lib); foreach my $part ($target->dir_list) { $root = $root->subdir($part); # $self->logger->log('phrasebook', 'debug', sprintf 'searching in [%s]', $root); last if not -d $root->stringify; push @files, sort {$a->basename cmp $b->basename} grep { not $_->is_dir } $root->children(no_hidden => 1); } } return @files; } 1; =pod =head1 NAME Net::CLI::Interact::Phrasebook - Load command phrasebooks from a Library =head1 DESCRIPTION A command phrasebook is where you store the repeatable sequences of commands which can be sent to connected network devices. An example would be a command to show the configuration of a device: storing this in a phrasebook (sometimes known as a dictionary) saves time and effort. This module implements the loading and preparing of phrasebooks from an on-disk file-based hierarchical library, and makes them available to the application as smart objects for use in L sessions. Entries in the phrasebook will be one of the following types: =over 4 =item Prompt Named regular expressions that match the content of a single line of text in the output returned from a connected device. They are a demarcation between commands sent and responses returned. =item Macro Alternating sequences of command statements sent to the device, and regular expressions to match the response. There are different kinds of Macro, explained below. =back The named regular expressions used in Prompts and Macros are known as I statements. The command statements in Macros which are sent to the device are known as I statements. That is, Prompts and Macros are built from one or more Match and Send statements. Each Send or Match statement becomes an instance of the L class. These are built up into Prompts and Macros, which become instances of the L class. =head1 USAGE A phrasebook is a plain text file containing named Prompts or Macros. Each file exists in a directory hierarchy, such that files "deeper" in the hierarchy have their entries override the similarly named entries higher up. For example: /dir1/file1 /dir1/file2 /dir1/dir2/file3 Entries in C sharing a name with any entries from C or C will take precedence. Those in C will also override entries in C, because asciibetical sorting places the files in that order, and later definitions with the same name and type override earlier ones. When this module is loaded, a I key is required. This locates a directory on disk, and then the files in that directory and all its ancestors in the hierarchy are loaded. The directories to search are specified by two I options (see below). All phrasebooks matching the given I are loaded, allowing a user to override or augment the default, shipped phrasebooks. =head1 INTERFACE =head2 new( \%options ) This takes the following options, and returns a loaded phrasebook object: =over 4 =item C<< personality => $directory >> (required) The name of a directory component on disk. Any files higher in the libraries hierarchy are also loaded, but entries in files contained within this directory, or "closer" to it, will take precedence. =item C<< library => $directory | \@directories >> First library hierarchy, specified either as a single directory or a list of directories that are searched in order. The idea is that this option be set in your application code, perhaps specifying some directory of phrasebooks shipped with the distribution. =item C<< add_library => $directory | \@directories >> Second library hierarchy, specified either as a single directory or a list of directories that are searched in order. This parameter is for the end-user to provide the location(s) of their own phrasebook(s). Any entries found via this path will override those found via the first C path. =back =head2 prompt( $name ) Returns the Prompt associated to the given C<$name>, or throws an exception if no such prompt can be found. The returned object is an instance of L. =head2 has_prompt( $name ) Returns true if a prompt of the given C<$name> exists in the loaded phrasebooks. =head2 prompt_names Returns a list of the names of the current loaded Prompts. =head2 macro( $name ) Returns the Macro associated to the given C<$name>, or throws an exception if no such macro can be found. The returned object is an instance of L. =head2 has_macro( $name ) Returns true if a macro of the given C<$name> exists in the loaded phrasebooks. =head2 macro_names Returns a list of the names of the current loaded Macros. =head1 PHRASEBOOK FORMAT =head2 Prompt A Prompt is a named regular expression which matches the content of a single line of text. Here is an example: prompt configure match /\(config[^)]*\)# ?$/ On the first line is the keyword C followed by the name of the Prompt, which must be a valid Perl identifier (letters, numbers, underscores only). On the immediately following line is the keyword C followed by a regular expression, enclosed in two forward-slash characters. Currently, no alternate bookend characters are supported, nor are regular expression modifiers (such as C) outside of the match, but you can of course include them within. The Prompt is used to find out when the connected CLI has emitted all of the response to a command. Try to make the Prompt as specific as possible, including line-end anchors. Remember that it will be matched against one line of text, only. =head2 Macro In general, Macros are alternating sequences of commands to send to the connected CLI, and regular expressions to match the end of the returned response. Macros are useful for issuing commands which have intermediate prompts, or confirmation steps. They also support the I of additional output when the connected CLI has split the response into pages. At its simplest a Macro can be just one command: macro show_int_br send show ip int br match /> ?$/ On the first line is the keyword C followed by the name of the Macro, which must be a valid Perl identifier (letters, numbers, underscores only). On the immediately following line is the keyword C followed by a space and then any text up until the end of the line, and if you want to include whitespace at the beginning or end of the command, use quotes. This text is sent to the connected CLI as a single command statement. The next line contains the keyword C followed by the Prompt (regular expression) which will terminate gathering of returned output from the sent command. Macros support the following features: =over 4 =item Automatic Matching Normally, you ought always to specify C statements along with a following C statement so that the module can tell when the output from your command has ended. However you can omit any Match and the module will insert either the current C value if set by the user, or the last Prompt from the last Macro. So the previous example could be re-written as: macro show_int_br send show ip int br You can have as many C statements as you like, and the Match statements will be inserted for you: macro show_int_br_and_timestamp send show ip int br send show clock However it is recommended that this type of sequence be implemented as individual commands (or separate Macros) rather than a single Macro, as it will be easier for you to retrieve the command response(s). Normally the Automatic Matching is used just to allow missing off of the final Match statement when it's the same as the current Prompt. =item Format Interpolation Each C statement is in fact run through Perl's C command, so variables may be interpolated into the statement using standard C<"%"> fields. For example: macro show_int_x send show interface %s The method for passing variables into the module upon execution of this Macro is documented in L. This feature is useful for username/password prompts. =item Named Match References If you're going to use the same Match (regular expression) in a number of Macros, then set it up as a Prompt (see above) and refer to it by name, instead: prompt priv_exec match /# ?$/ macro to_priv_exec send enable match /[Pp]assword: ?$/ send %s match priv_exec As you can see, in the case of the last Match, we have the keyword C followed by the name of a defined Prompt. To match multiple defined Prompts use this syntax (with as many named references as you like): macro to_privileged send enable match username_prompt or priv_exec =item Continuations Sometimes the connected CLI will not know it's talking to a program and so paginate the output (that is, split it into pages). There is usually a keypress required between each page. This is supported via the following syntax: macro show_run send show running-config follow / --More-- / with ' ' On the line following the C statement is the keyword C and a regular expression enclosed in forward-slashes. This is the Match which will, if seen in the command output, trigger the continuation. On the line you then have the keyword C followed by a space and some text, until the end of the line. If you need to enclose whitespace use quotes, as in the example. The module will send the continuation text and gobble the matched prompt from the emitted output so you only have one complete piece of text returned, even if split over many pages. The sent text can contain metacharacters such as C<\n> for a newline. Note that in the above example the C statement should be seen as an extension of the C statement. There is still an implicit Match prompt added at the end of this Macro, as per Automatic Matching, above. =item Line Endings Normally all sent command statements are appended with a newline (or the value of C, if set). To suppress that feature, use the keyword C instead of C. However this does not prevent the Format Interpolation via C as described above (simply use C<"%%"> to get a literal C<"%">). =back =cut Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Manual000755000766000024 013170404307 21317 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Manual/Cookbook.pod000444000766000024 2034113170404307 23746 0ustar00oliverstaff000000000000=head1 NAME Net::CLI::Interact::Manual::Cookbook - Miscellaneous recipes =head1 Windows Support The library works just fine under native windows (i.e use something like Strawberry Perl - no need for cygwin), for Telnet, Serial and SSH connections. However one additional step is required for you to have success: You B download the C application, and pass its filesystem location in the C parameter to C. Do not try to use any other Telnet or SSH programs (for instance the Windows bundled C) - they I. Here's an example, if C is on your Desktop: my $s = Net::CLI::Interact->new( personality => "cisco", transport => "Telnet", (Net::CLI::Interact::Transport::is_win32() ? (app => "$ENV{HOMEPATH}\\Desktop\\plink.exe") : () ), ); =head1 Unix Support The library works fine on most Unix platforms. It will try to use the native C, C (openssh) and C programs for Telnet, SSH and Serial connections, respectively. If you want to use another application, pass it in the C parameter to C. In some Unix environments there can be zombie child processes left around after running your script. If this happens, set the C option, like so: my $s = Net::CLI::Interact->new( personality => "cisco", transport => "Telnet", connect_options => { reap => 1, }, ); =head1 Running Commands =head2 Simple Commands Simply send the command you wish to execute to the session. If not already done, a connection to the device will be established automatically: $s->cmd('show ip int br'); Normally this matches against a default prompt, which has been discovered automatically, or set by you: $s->set_prompt('user_prompt'); It's also possible to pass in a custom prompt for this command only: $s->cmd('show ip int br', { match => qr/special prompt>$/ }); However be aware that a side effect of this is that the custom prompt becomes the new default prompt for subsequent commands or macros. =head2 Macro Commands Call a predefined Macro from the phrasebook using this method: $s->macro('write_mem'); Sometimes the Macro needs parameters: $s->macro('to_priv_exec', { params => ['my_password'] }); You can't really create a Macro on the fly very easily, but with suitable use of C, C, and the C option to C it's possible to achieve some simple flexibility. =head1 Reconfiguring On-the-Fly =head2 Phrasebook It's possible to load a new phrasebook by the following method, which must be passed at least the name of the personality: $s->set_phrasebook({ personality => 'ios' }); You can pass any options which the L module itself would take. =head2 Prompt The current prompt can be changed by passing the name of the new Prompt as it is known by the phrasebook: $s->set_prompt('name'); If you want to test whether the current prompt matches a different named Prompt from the phrasebook, this method can be used: $s->prompt_looks_like('name'); =head1 Logging A generic logging service is available through the C<< $session->logger >> object, which is based on L. You can configure the logger at startup quite easily. See the L manual page for details of the interface (config for any option can simply be passed to C<< Net::CLI::Interact->new() >>). =head2 Destinations The default configuration sends logging messages to standard output. Let's say you also want to append them to a log file: my $s = Net::CLI::Interact->new({ log_config => { dispatchers => ['screen','file'], screen => { class => 'Log::Dispatch::Screen', min_level => 'warning', }, file => { class => 'Log::Dispatch::File', min_level => 'debug', filename => '/var/log/myapp.log', mode => 'append', format => '[%d] %m', }, }, # etc... }); Note that some keys are required, such as the C and C but others depend on the particular class being used. See L for more details. =head2 Log Levels and Categories Each log message has a standard log level (C, C, etc) but also a I which is a concept local to this module. Categories allow more filtering of what is logged. Each time a message is logged through C<< $s->logger->log(...) >> it has a level and category. Messages are only emitted if they pass the specific level set for that category. In this way we can suppress messages about the transport but, for example, show messages about prompt-matching at a debug level. You can very easily set the log level for all categories using either the C option to C, or the C environment variable. To configure these filters, use the C option together with the list of default log categories used by C. For example: my $s = Net::CLI::Interact->new({ log_flags => { (map {$_ => 'notice'} Net::CLI::Interact->default_log_categories()), dialogue => 'info', }, # etc... }); This example would set all categories to C level except for the C category, which is set to C level to get more output (on what is sent and received by each command). =head1 Phrasebook Libraries You can override or add to the device command phrasebooks which ship with this distribution. To start with, check the shipped dictionary for your device's current level of support, at L. If you want to add either some prompts or macros, first read the documentation for these systems at L. All phrasebooks can inherit from others, and this is based on their location in a filesystem tree. See the phrasebooks bundled with the L distribution for an example of this in action. If you wish to override a phrasebook entry, simply set C in your code, and then create a file at the same relative point beneath that library directory as the original version shipped with the C module, for example "C<< /cisco/pixos/pixos7/my_phrases >>". The file itself (C) does not have to be the same name as the original, and you can have more than one file if it helps. Only the directory is matched against your chosen C and then all files in there, and higher in the C tree, and distribution C tree, are loaded. To check what phrasebooks and prompts/macros are loaded, run your script with debug level set to C. The easiest way to do this is by setting the environment variable C. =head1 Phrasebook Entries =head2 Prompts These are nothing more than named regular expressions: prompt configure match /\(config[^)]*\)# ?$/ =head2 Macros This example waits for the device to ask "[startup-config]?" and then responds with the text C. Remember, there is an implicit C statement added at the end, which is the current prompt. macro copy_run_start send copy running-config startup-config match /Destination filename \[startup-config\]\?$/ send startup-config To send instead a "press" of the Return key (I), use: macro write_mem send copy running-config startup-config match /Destination filename \[startup-config\]\?$/ send '' To instead allow the user to pass in the file name, use a C format. macro save_to_file send copy running-config startup-config match /Destination filename \[startup-config\]\?$/ send %s The user I then pass a parameter to the C call, even if it's an empty string: $s->macro('save_to_file', { params => ['file_name'] }); # or $s->macro('save_to_file', { params => [''] }); =head2 Continuations These are Macros which start with a match instead of a send: macro more_pages match / --More-- / send ' ' Note that the parameter of the C is I sent with a Return character (I) appended. When included in a macro, the continuation can be in-line, like this: macro show_ip_route send show ip route follow / --More-- / with ' ' =cut Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Manual/Phrasebook.pod000444000766000024 1021613170404307 24275 0ustar00oliverstaff000000000000=head1 NAME Net::CLI::Interact::Manual::Phrasebook - List of Supported CLIs =head1 INTRODUCTION The bundled phrasebook includes support for a variety of network device CLIs. Many were contributed by users of the module. If you set up a new CLI dictionary, please consider contributing it back! The phrasebook specification is given in L. For each supported CLI, there is a name which must be passed in the C option to L's C method; this is the same as the directory containing the phrasebook file. After that, you can call the included Macros, and the module will use the included Prompt to match the current state of the CLI. More information is available in the L and L. =head1 PERSONALITIES See the files themselves at the following link for full details: L. =over 4 =item * ASA =item * Avaya =item * Bash =item * CatOS =item * Cisco (generic) =item * Csh =item * ExtremeOS =item * F5 =item * Fortinet =item * Foundry / Brocade Before connecting to the device you probably want to set the output separator to be: $nci->transport->ors("\r\n"); For users of L this should be: $session_obj->nci->transport->ors("\r\n"); =item * FWSM =item * FWSM 3 =item * HP =item * IOS =item * JunOS =item * Mikrotik =item * Nortel =item * OVMCLI =item * PIXOS =item * PIXOS 7 =item * Qnap =item * RedBack =item * ScreenOS =item * WLC =item * Zyxel =back =head1 SUPPORTING A NEW DEVICE In order to support a new device, particularly for the L module, there is a basic set of prompts and macros you must create. =head2 Required Prompts With SSH, no C prompt is required, but for other transports you should include a prompt named C which matches the "C" prompt presented by the device. # example only! prompt user match /[Uu]sername/ With all transports you must provide a C prompt which matches the "C" prompt presented by the device. # example only! prompt pass match /[Pp]assword: ?$/ The last essential prompt is of course a simple command line prompt match, and this should be named C. # example only! prompt generic match /> ?$/ =head2 Desirable Prompt and Macros To cleanly disconnect from your device session, you might want to include a macro named C with the relevant command. Note there is no need for a C statement in this macro, as the device should have detached! # example only! macro disconnect send exit For paging support, include either only a C macro, or two macros named C and C, depending on what the device requires. In all cases, there must be one substitution ("C<%s>") which is where the number of page lines will be inserted into the command. # example only! macro paging send terminal length %s For privileged mode (super-user) support, include a prompt named C first, and then include macros named C and C to enter and leave the mode, respectively. Note that both macros will require explicit match statements, because the prompt encountered I issuing the command will be different to that encountered before. # example only! prompt privileged match /# ?$/ macro begin_privileged send enable match user or pass or privileged macro end_privileged send disable match generic Similarly for configure mode, include a prompt named C first, and then include macros named C and C to enter and leave the mode, respectively. Note that both macros will require explicit match statements, because the prompt encountered I issuing the command will be different to that encountered before. # example only! prompt configure match /\(config[^)]*\)# ?$/ macro begin_configure send configure terminal match configure macro end_configure send exit match privileged =cut Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Manual/Tutorial.pod000444000766000024 1565113170404307 24013 0ustar00oliverstaff000000000000=head1 NAME Net::CLI::Interact::Manual::Tutorial - Guide for new users =head1 Introduction Automating command line interface (CLI) interactions is not a new idea, but can be tricky to implement. Net::CLI::Interact aims to provide a simple and manageable interface to CLI interactions, supporting: =over 4 =item * SSH, Telnet and Serial-Line connections =item * Unix and Windows support =item * Reusable device command phrasebooks =back The module exists to support developers of applications and libraries which must interact with a command line interface. The SYNOPSIS section of L has an overview of the commands demonstrated in this document. =head1 Getting Started Like many other Perl modules, you need to load the module and then create a new Net::CLI::Interact instance (which is C<$s> in the example, below): use Net::CLI::Interact; my $s = Net::CLI::Interact->new({ transport => 'Serial', personality => 'cisco', }); Your application can have multiple independent instances (that is, connect to different devices at the same time); simply repeat the above example more times for variables other than C<$s>. Note that at the time you create the instance, as in the example above, the module I connect to the device. That comes later. There were two options provided to the C call, above, both of which are required for all new instances. Let's look at them in turn: =over 4 =item transport How do you want to connect to your CLI? The current choices are L, L and a L line (that is, a console cable). In this option you need to tell the module which underlying transport is to be used. Some of the transports have additional options that are either required, or optional. For example, the Telnet and SSH transports both need to know which post name or IP address should be contacted. You pass this in another option to C, like so: my $s = Net::CLI::Interact->new({ transport => 'Telnet', connect_options => { host => 'my.server.example.com' }, personality => 'cisco', }); See the manual page of the transport module for the option details. =item personality What language does the connected device speak? In this option you need to pass the name of a personality that's used to load a I. For instance one common format is Cisco's IOS, which is widely cloned on other vendor equipment CLIs. A phrasebook is simply a library, or dictionary, of pre-configured phrases you can use on the CLI. This makes life simple, because Net::CLI::Interact then can automate some of the more difficult tasks. For example, if you issue a command and the output is "paged" so you hit Space or Return to see the next page, the phrasebook can tell Net::CLI::Interact how to slurp all these pages into one body of output before returning it to you. This module ships with many phrasebooks which have been contributed by other users over the years. You can also write and use your own phrasebooks, which might replace, or add to, those shipped with the module. See the L user guide for a list of phrasebooks shipped with this distribution, and their corresponding C names. See also the C option to C for specifying a path to your own phrasebooks. =back =head1 Connecting This is done automatically for you the first time you send a command to the device, so skip this step and move on! =head1 Sending Commands =head2 But first, Prompts The idea of sending a command is, usually, to see some output. The most important part of this process is knowing when the output has all been sent, otherwise the module would sit forever, waiting to gather more text! Between each command sent, the connected device prints a CLI I. This prompt is where you type commands, and it's what tells us that all the output has been sent from our last command. Prompts are loaded in the phrasebook, and given friendly names. If your personality's phrasebook is sufficiently mature, then the prompts might be fully automated, and just like the Connecting step above, you can skip doing anything manually. Consult the L user guide for details. However if you need to set it manually, do the following: $s->set_prompt('friendly_name'); Sometimes you might not know what state the CLI is in; typically this applies to Serial lines. In that case you can ask to find the matching prompt: $s->find_prompt($wake_up); This method gets some output from the connected session and then tries to match it against any loaded prompts, returning if successful. If not successful, and the C<$wake_up> value is non-zero, then C will "hit the return key" to try to get some output. This process is retried according to the value of C<$wake_up> (i.e. 1, 2, 3, etc), and of not successful will die. =head2 Literal Commands There's not a lot to it. Remember that with a mature personality loaded, you were probably able to skip the previous prompt step and go straight to: my $output = $s->cmd('show ip interfaces brief'); Here you will get all the output from the command together in one variable, C<$output>. If you prefer an array where each item is one line of output, simply use C<@output> instead in the above example. =head2 Macros Life gets more complicated when your command has things like confirmation steps (e.g. reboot), other prompts (e.g. extended ping), etc. For these situations we have I in the phrasebook. A macro is simply a sequence of commands we could issue using C<< $s->cmd() >>, bundled together and given a friendly name. Macros are also smart enough either to handle simple confirmation steps themselves, or to allow you to pass in parameters. Some examples probably help: # saves config, accepting the default "startup-config" when prompted $s->macro('write_mem'); # logs in, passing a username and password at the prompts $s->macro('to_user_exec', { params => ['my_username', 'my_password'], }); # simply a parameterized command $s->macro('show_interfaces_x', { params => ['GigabitEthernet 3/4'], }); =head1 Slurping Output As mentioned above, output at the CLI is often "paged" with the user hitting Space or Return to show the next page. Most macros can deal with this automatically if well implemented. If the L user guide says your personality has a named default I for handling paged output, then set it like so: $s->set_default_continuation('friendly_name'); =head1 Disconnecting This is nothing more fancy than issuing the appropriate CLI commands to close the network connection. In the case of the Serial line transport you can usually only log out, and not fully disconnect. Simply end your application and the module will tidy things up as best it can. =cut Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Role000755000766000024 013170404307 21003 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Role/Engine.pm000444000766000024 2313213170404307 22724 0ustar00oliverstaff000000000000package Net::CLI::Interact::Role::Engine; { $Net::CLI::Interact::Role::Engine::VERSION = '2.300002' } { package # hide from pause Net::CLI::Interact::Role::Engine::ExecuteOptions; use Moo; use Sub::Quote; use MooX::Types::MooseLike::Base qw(Bool ArrayRef Str Any); has 'no_ors' => ( is => 'ro', isa => Bool, default => quote_sub('0'), ); has 'params' => ( is => 'ro', isa => ArrayRef[Str], predicate => 1, ); has 'timeout' => ( is => 'ro', isa => quote_sub(q{die "$_[0] is not a posint!" unless $_[0] > 0 }), ); has 'match' => ( is => 'rw', isa => ArrayRef, # FIXME ArrayRef[RegexpRef|Str] predicate => 1, coerce => quote_sub(q{ (ref [] ne ref $_[0]) ? [$_[0]] : $_[0] }), ); } # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ use Moo::Role; use MooX::Types::MooseLike::Base qw(InstanceOf); with 'Net::CLI::Interact::Role::Prompt'; use Net::CLI::Interact::Action; use Net::CLI::Interact::ActionSet; use Class::Load (); # try to load Data::Printer for last_actionset debug output if (Class::Load::try_load_class('Data::Printer', {-version => '0.27'})) { Data::Printer->import({class => { expand => 'all' }}); } has 'last_actionset' => ( is => 'rw', isa => InstanceOf['Net::CLI::Interact::ActionSet'], trigger => 1, ); sub _trigger_last_actionset { my ($self, $new) = @_; $self->logger->log('prompt', 'notice', sprintf ('output matching prompt was "%s"', $new->item_at(-1)->response)); if (Class::Load::is_class_loaded('Data::Printer', {-version => '0.27'})) { Data::Printer::p($new, output => \my $debug); $self->logger->log('object', 'debug', $debug); } } sub last_response { my $self = shift; my $irs_re = $self->transport->irs_re; (my $resp = $self->last_actionset->item_at(-2)->response) =~ s/$irs_re/\n/g; $resp =~ s/\n+$//; return (wantarray ? (map {$_ .= "\n"} split m/\n/, $resp) : ($resp ."\n")); } has 'default_continuation' => ( is => 'rw', isa => InstanceOf['Net::CLI::Interact::ActionSet'], writer => '_default_continuation', predicate => 1, clearer => 1, ); sub set_default_continuation { my ($self, $cont) = @_; die "missing continuation" unless $cont; die "unknown continuation [$cont]" unless eval{ $self->phrasebook->macro($cont) }; $self->_default_continuation( $self->phrasebook->macro($cont) ); $self->logger->log('engine', 'info', 'default continuation set to', $cont); } sub cmd { my ($self, $command, $options) = @_; $options = Net::CLI::Interact::Role::Engine::ExecuteOptions->new($options || {}); $self->logger->log('engine', 'notice', 'running command', $command); if ($options->has_match) { # convert prompt name(s) from name into regexpref, or die $options->match([ map { ref $_ eq ref '' ? @{ $self->phrasebook->prompt($_)->first->value } : $_ } @{ $options->match } ]); $self->logger->log('engine', 'info', 'to match', (ref $options->match eq ref [] ? (join '|', @{$options->match}) : $options->match)); } # command will be run through sprintf but without any params # so convert any embedded % to literal % ($command =~ s/%/%%/g) && $self->logger->log('engine', 'debug', 'command expanded to:', $command); return $self->_execute_actions( $options, Net::CLI::Interact::Action->new({ type => 'send', value => $command, no_ors => $options->no_ors, }), ); } sub macro { my ($self, $name, $options) = @_; $options = Net::CLI::Interact::Role::Engine::ExecuteOptions->new($options || {}); $self->logger->log('engine', 'notice', 'running macro', $name); $self->logger->log('engine', 'info', 'macro params are:', join ', ', @{ $options->params }) if $options->has_params; my $set = $self->phrasebook->macro($name)->clone; $set->apply_params(@{ $options->params }) if $options->has_params; return $self->_execute_actions($options, $set); } sub _execute_actions { my ($self, $options, @actions) = @_; $self->logger->log('engine', 'notice', 'executing actions'); # make connection on transport if not yet done $self->transport->init if not $self->transport->connect_ready; # user can install a prompt, call find_prompt, or let us trigger that $self->find_prompt(1) if not ($self->prompt_re || $self->last_actionset); my $set = Net::CLI::Interact::ActionSet->new({ actions => [@actions], current_match => ($options->match || $self->prompt_re || $self->last_prompt_re), ($self->has_default_continuation ? (default_continuation => $self->default_continuation) : ()), }); $set->register_callback(sub { $self->transport->do_action(@_) }); $self->logger->log('engine', 'debug', 'dispatching to execute method'); my $timeout_bak = $self->transport->timeout; $self->transport->timeout($options->timeout || $timeout_bak); $set->execute; $self->transport->timeout($timeout_bak); $self->last_actionset($set); $self->logger->log('prompt', 'debug', sprintf 'setting new prompt to %s', $self->last_actionset->last->prompt_hit || ''); $self->_prompt( $self->last_actionset->last->prompt_hit ); $self->logger->log('dialogue', 'info', "trimmed command response:\n". $self->last_response); return $self->last_response; # context sensitive } 1; =pod =head1 NAME Net::CLI::Interact::Role::Engine - Statement execution engine =head1 DESCRIPTION This module is the core of L, and serves to take entries from your loaded L, issue them to connected devices, and gather the returned output. =head1 INTERFACE =head2 cmd( $command_statement, \%options? ) Execute a single command statement on the connected device, and consume output until there is a match with the current I. The statement is executed verbatim on the device, with a newline appended. The following options are supported: =over 4 =item C<< timeout => $seconds >> (optional) Sets a value of C for the L local to this call of C, that overrides whatever is set in the Transport, or the default of 10 seconds. =item C<< no_ors => 1 >> (optional) When passed a true value, a newline character (in fact the value of C) will not be appended to the statement sent to the device. =item C<< match => $name | $regexpref | \@names_and_regexprefs >> (optional) Allows this command (only) to complete with a custom match, which must be one or more of either the name of a loaded phrasebook Prompt or your own regular expression reference (C<< qr// >>). The module updates the current prompt to be the same value on a successful match. =back In scalar context the C is returned (see below). In list context the gathered response is returned as a list of lines. In both cases your local platform's newline character will end all lines. =head2 macro( $macro_name, \%options? ) Execute the commands contained within the named Macro, which must be loaded from a Phrasebook. Options to control the output, including variables for substitution into the Macro, are passed in the C<%options> hash reference. The following options are supported: =over 4 =item C<< params => \@values >> (optional) If the Macro contains commands using C Format variables then the corresponding parameters must be passed in this value as an array reference. Values are consumed from the provided array reference and passed to the C commands in the Macro in order, as needed. An exception will be thrown if there are insufficient parameters. =item C<< timeout => $seconds >> (optional) Sets a value of C for the L local to this call of C, that overrides whatever is set in the Transport, or the default of 10 seconds. =back An exception will be thrown if the Match statements in the Macro are not successful against the output returned from the device. This is based on the value of C, which controls how long the module waits for matching output. In scalar context the C is returned (see below). In list context the gathered response is returned as a list of lines. In both cases your local platform's newline character will end all lines. =head2 last_response Returns the gathered output after issuing the last recent C command within the most recent C or C. That is, you get the output from the last command sent to the connected device. In scalar context all data is returned. In list context the gathered response is returned as a list of lines. In both cases your local platform's newline character will end all lines. =head2 last_actionset Returns the complete L that was constructed from the most recent C or C execution. This will be a sequence of L that correspond to C and C statements. In the case of a Macro these directly relate to the contents of your Phrasebook, with the possible addition of C statements added automatically. In the case of a C execution, an "anonymous" Macro is constructed which consists of a single C and a single C. =head1 COMPOSITION See the following for further interface details: =over 4 =item * L =back =cut Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Role/FindMatch.pm000444000766000024 74113170404307 23315 0ustar00oliverstaff000000000000package Net::CLI::Interact::Role::FindMatch; { $Net::CLI::Interact::Role::FindMatch::VERSION = '2.300002' } use Moo::Role; # see if any regexp in the arrayref match the response sub find_match { my ($self, $text, $matches) = @_; $matches = ((ref $matches eq ref qr//) ? [$matches] : $matches); return undef unless (scalar grep {ref $_ eq ref qr//} @$matches) == scalar @$matches; use List::Util 'first'; return first { $text =~ $_ } @$matches; } 1; Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Role/Iterator.pm000444000766000024 1017713170404307 23315 0ustar00oliverstaff000000000000package Net::CLI::Interact::Role::Iterator; { $Net::CLI::Interact::Role::Iterator::VERSION = '2.300002' } use Moo::Role; use Sub::Quote; use MooX::Types::MooseLike::Base qw(ArrayRef Any Int); has '_sequence' => ( is => 'rw', isa => ArrayRef[Any], required => 1, ); # fiddly only in case of auto_deref sub count { return scalar @{ (shift)->_sequence } } sub first { return (shift)->_sequence->[0] } sub last { return (shift)->_sequence->[-1] } sub item_at { my ($self, $pos) = @_; die "position is past the end of sequence\n" if $pos >= $self->count; return $self->_sequence->[$pos]; } sub insert_at { my ($self, $pos, @rest) = @_; my @seq = @{ $self->_sequence }; splice @seq, $pos, 0, @rest; $self->_sequence( \@seq ); } sub append { my $self = shift; $self->insert_at( $self->count, @{ (shift)->_sequence } ); } has '_position' => ( is => 'rw', isa => Int, default => quote_sub('-1'), ); sub idx { my $self = shift; my $pos = $self->_position; die "attempt to read iter index before pulling a value\n" if scalar @_ == 0 and $pos == -1; $self->_position(shift) if scalar @_; return $pos; } sub next { my $self = shift; die "er, please check has_next before next\n" if not $self->has_next; my $position = $self->_position; die "fell off end of iterator\n" if ++$position == $self->count; $self->_position($position); return $self->_sequence->[ $position ]; } sub has_next { my $self = shift; return ($self->_position < ($self->count - 1)); } sub peek { my $self = shift; return $self->_sequence->[ $self->_position + 1 ] if $self->has_next; } sub reset { (shift)->_position(-1) } 1; =pod =head1 NAME Net::CLI::Interact::Role::Iterator - Array-based Iterator =head1 SYNOPSIS my $count = $iter->count; $iter->reset; while ( $iter->has_next ) { print $iter->next; } =head1 DESCRIPTION This module implements an array-based iterator which may be mixed-in to add management of a sequence of elements and processing of that sequence. The iterator is inspired by L but limited to arrays and adds many other facilities. The following section describes the methods provided by this class. =head1 USAGE The slot used for storing iterator elements is named C<_sequence> and you should write your consuming class to marshall data into this slot, perhaps via C or C. For example: has '+_sequence' => ( isa => 'ArrayRef[Thingy]', init_arg => 'things', ); =head1 INTERFACE =head2 count The number of elements currently stored in the iterator. Note that this is of course not the same as the index of the last item in the iterator (which is 0-based) =head2 first Returns the first item in the iterator. =head2 last Returns the last item in the iterator. =head2 item_at( $pos ) Returns the item at the given position in the iterator, or throws an exception if C<$pos> is past the end of the iterator. The position is 0-based. =head2 insert_at( $pos, $iter ) Inserts the contents of the passed iterator starting I (not I) the position given. The passed iterator must also be a consumer of this role. The position is 0-based. =head2 append( $iter ) Shorthand for C when you want to add the contents of the passed iterator I the end of the sequence. =head2 idx( $pos? ) Returns the index (0-based) of the current iterator cursor, or sets the cursor if a position (again, 0-based) is passed. An exception is thrown if you attempt to read the cursor position before having read any elements from the iterator, or if the iterator is empty. =head2 next Returns the next item in the iterator sequence, and advances the cursor. Throws an exception if you have already reached the end of the sequence. =head2 has_next Returns true if there are further elements to be read from the iterator. =head2 peek Returns the next item in the sequence without advancing the position of the cursor. It returns C if you are already at the end of the sequence. =head2 reset Resets the cursor so you can iterate through the sequence of elements again. =cut Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Role/Prompt.pm000444000766000024 2124313170404307 23001 0ustar00oliverstaff000000000000package Net::CLI::Interact::Role::Prompt; { $Net::CLI::Interact::Role::Prompt::VERSION = '2.300002' } use Moo::Role; use MooX::Types::MooseLike::Base qw(Str RegexpRef); use Net::CLI::Interact::ActionSet; with 'Net::CLI::Interact::Role::FindMatch'; has 'wake_up_msg' => ( is => 'rw', isa => Str, default => sub { (shift)->transport->ors }, predicate => 1, ); has '_prompt' => ( is => 'rw', isa => RegexpRef, reader => 'prompt_re', predicate => 'has_set_prompt', clearer => 'unset_prompt', trigger => sub { (shift)->logger->log('prompt', 'info', 'prompt has been set to', (shift)); }, ); sub set_prompt { my ($self, $name) = @_; $self->_prompt( $self->phrasebook->prompt($name)->first->value->[0] ); } sub last_prompt { my $self = shift; return $self->last_actionset->item_at(-1)->response; } sub last_prompt_re { my $self = shift; my $prompt = $self->last_prompt; return qr/^\Q$prompt\E$/; } sub prompt_looks_like { my ($self, $name) = @_; return $self->find_match( $self->last_prompt, $self->phrasebook->prompt($name)->first->value ); } # create an ActionSet of one send and one match Action, for the wake_up sub _fabricate_actionset { my $self = shift; my $output = $self->transport->flush; my $irs_re = $self->transport->irs_re; $output =~ s/^(?:$irs_re)+//; my @output_lines = split $irs_re, $output; my $last_output_line = pop @output_lines; my $current_match = [$self->prompt_re]; my $set = Net::CLI::Interact::ActionSet->new({ current_match => $current_match, actions => [ { type => 'send', value => ($self->has_wake_up_msg ? $self->wake_up_msg : ''), response => (join "\n", @output_lines, ''), }, { type => 'match', response => $last_output_line, value => $current_match, prompt_hit => $current_match->[0], }, ], }); return $set; } # pump until any of the prompts matches the output buffer sub find_prompt { my ($self, $wake_up) = @_; $self->logger->log('prompt', 'notice', 'finding prompt'); # make connection on transport if not yet done $self->transport->init if not $self->transport->connect_ready; # forget the previous prompt; will set new one if successful or bail out if not $self->unset_prompt; eval { my $started_pumping = time; PUMPING: while (1) { $self->transport->pump; $self->logger->log('dump', 'debug', "SEEN:\n". $self->transport->buffer); foreach my $prompt ($self->phrasebook->prompt_names) { # prompts consist of only one match action if ($self->find_match( $self->transport->buffer, $self->phrasebook->prompt($prompt)->first->value)) { $self->logger->log('prompt', 'info', "hit, matches prompt $prompt"); $self->set_prompt($prompt); $self->last_actionset( $self->_fabricate_actionset() ); $self->logger->log('dialogue', 'info', "trimmed command response:\n". $self->last_response); last PUMPING; } $self->logger->log('prompt', 'debug', "nope, doesn't (yet) match $prompt"); } $self->logger->log('prompt', 'debug', 'no match so far, more data?'); last if $self->transport->timeout and time > ($started_pumping + $self->transport->timeout); } }; if ($@ and $self->has_wake_up_msg and $wake_up) { $self->logger->log('prompt', 'notice', "failed: [$@], sending WAKE_UP and trying again"); eval { $self->transport->put( $self->wake_up_msg ); $self->find_prompt(--$wake_up); }; if ($@) { # really died, so this time bail out - with possible transport err my $output = $self->transport->flush; $self->transport->disconnect; die $output; } } else { if (not $self->has_set_prompt) { # trouble... we were asked to find a prompt but failed :-( $self->logger->log('prompt', 'critical', 'failed to find prompt! wrong phrasebook?'); # bail out with what we have... my $output = $self->transport->flush; $self->transport->disconnect; die $output; } } } 1; =pod =head1 NAME Net::CLI::Interact::Role::Prompt - Command-line prompt management =head1 DESCRIPTION This is another core component of L, and its role is to keep track of the current prompt on the connected command line interface. The idea is that most CLI have a prompt where you issue commands, and are returned some output which this module gathers. The prompt is a demarcation between each command and its response data. Note that although we "keep track" of the prompt, Net::CLI::Interact is not a state machine, and the choice of command issued to the connected device bears no relation to the current (or last matched) prompt. =head1 INTERFACE =head2 set_prompt( $prompt_name ) This method will be used most commonly by applications to select and set a prompt from the Phrasebook which matches the current context of the connected CLI session. This allows a sequence of commands to be sent which share the same Prompt. The name you pass in is looked up in the loaded Phrasebook and the entry's regular expression stored internally. An exception is thrown if the named Prompt is not known. Typically you would either refer to a Prompt in a Macro, or set the prompt you are expecting once for a sequence of commands in a particular CLI context. When a Macro completes and it has been defined in the Phrasebook with an explicit named Prompt at the end, we can assume the user is indicating some change of context. Therefore the C is I on such occasions to have the regular expression from that named Prompt. =head2 prompt_re Returns the current Prompt in the form of a regular expression reference. The Prompt is used as a default to catch the end of command response output, when a Macro has not been set up with explicit Prompt matching. Typically you would either refer to a Prompt in a Macro, or set the prompt you are expecting once for a sequence of commands in a particular CLI context. =head2 unset_prompt Use this method to empty the current C setting (see above). The effect is that the module will automatically set the Prompt for itself based on the last line of output received from the connected CLI. Do not use this option unless you know what you are doing. =head2 has_set_prompt Returns True if there is currently a Prompt set, otherwise returns False. =head2 prompt_looks_like( $name ) Returns True if the current prompt matches the given named prompt. This is useful when you wish to make a more specific check on the current prompt. =head2 find_prompt( $wake_up? ) A helper method that consumes output from the connected CLI session until a line matches any one of the named Prompts in the loaded Phrasebook, at which point no more output is consumed. As a consequence the C will be set (see above). This might be used when you're connecting to a device which maintains CLI state between session disconnects (for example a serial console), and you need to discover the current state. However, C is executed automatically for you if you call a C or C before any interaction with the CLI. The current device output will be scanned against all known named Prompts. If nothing is found, the default behaviour is to die. Passing a positive number to the method (as C<$wake_up>) will instead send the content of our C slot (see below), typically a carriage return, and try to match again. The idea is that by sending one carriage return, the connected device will print its CLI prompt. This "send and try to match" process will be repeated up to "C<$wake_up>" times. =head2 wake_up_msg Text sent to a device within the C method if no output has so far matched any known named Prompt. Default is the value of the I from the L (one newline). =head2 last_prompt Returns the Prompt which most recently was matched and terminated gathering of output from the connected CLI. This is a simple text string. =head2 last_prompt_re Returns the text which was most recently matched and terminated gathering of output from the connected CLI, as a quote-escaped regular expression with line start and end anchors. =cut Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Transport000755000766000024 013170404307 22076 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Transport/Base.pm000444000766000024 1345213170404307 23470 0ustar00oliverstaff000000000000package Net::CLI::Interact::Transport::Base; { $Net::CLI::Interact::Transport::Base::VERSION = '2.300002' } use Moo; use MooX::Types::MooseLike::Base qw(InstanceOf); with "Net::CLI::Interact::Transport::Role::StripControlChars"; BEGIN { sub is_win32 { return ($^O eq 'MSWin32') } extends (is_win32() ? 'Net::CLI::Interact::Transport::Platform::Win32' : 'Net::CLI::Interact::Transport::Platform::Unix'); } { package # hide from pause Net::CLI::Interact::Transport::Options; use Moo; extends 'Net::CLI::Interact::Transport::Platform::Options'; } has 'logger' => ( is => 'ro', isa => InstanceOf['Net::CLI::Interact::Logger'], required => 1, ); 1; =pod =head1 NAME Net::CLI::Interact::Transport::Base - Spawns an Interactive CLI Session =head1 DESCRIPTION This module provides a generic cross-platform API with the purpose of interacting with a command line interface. On Windows the L module is used and on Unix when L is available (it requires a compiler) L, else C. In all cases, a program such as openssh is started and methods provided to send and receive data from the interactive session. You should not use this class directly, but instead inherit from it in specific Transport that will set the application command line name, and marshall any runtime options. The OS platform is detected automatically. =head1 INTERFACE =head2 init This method I be called before any other, to bootstrap the application wrapper module (IPC::Run or Net::Telnet). However, via L's C, C or C it will be called for you automatically. Two attributes of the specific loaded Transport are used. First the Application set in C is of course required, plus the options in the Transport's C are retrieved, if set, and passed as command line arguments to the Application. =head2 connect_ready Returns True if C has been called successfully, otherwise returns False. =head2 disconnect Undefines the application wrapper flushes any output data buffer such that the next call to C or C will cause a new connection to be made. Useful if you intentionally timeout a command and end up with junk in the output buffer. =head2 do_action When passed a L instance, will execute the contained instruction on the connected CLI. This might be a command to C, or a regular expression to C in the output. Features of the commands and prompts are supported, such as Continuation matching (and slurping), and sending without an I. On failing to succeed with a Match, the module will time-out (see C, below) and raise an exception. Output returned after issuing a command is stored within the Match Action's C and C slots by this method, with the latter then marshalled into the correct C Action by the L. =head2 put( @data ) Items in C<@data> are joined together by an empty string and sent as input to the connected program's interactive session. =head2 pump Attempts to retrieve pending output from the connected program's interactive session. Returns true if there is new data available in the buffer, else will time-out and raise a Perl exception. See C and C. =head2 flush Empties the buffer used for response data returned from the connected CLI, and returns that data as a single text string (possibly with embedded newlines). =head2 timeout( $seconds? ) When C is polling for response data matching a regular expression Action, it will eventually time-out and throw an exception if nothing matches and no more data arrives. The number of seconds to wait is set via this method, which will also return the current value of C. The default value is 10 seconds. =head2 irs_re Returns the Regular Expression reference used to split lines of response from the connected device. In the end, you will only receive data from this module separated by the C value (by default a newline character). The C is used internally by the module and is: qr/(?:\015\012|\015|\012)/ # i.e. CRLF or CR or LF =head2 ors Line separator character(s) appended to a command sent to the connected CLI. This defaults to a newline on the application's platform. =head2 logger Slot for storing a reference to the application's L object. =head2 is_win32 Returns true if the current platform is Windows. Can be called as either a class or instance method. =head2 app Location and name of the program used to establish an interactive CLI session. On Unix platforms this will be C (openssh), C, or C (serial line). On Windows this must be the C program. =head2 connect_options Slot for storing a set of options for the specific loaded Transport, passed by the user of Net::CLI::Interact as a hash ref. Do not access this directly, but instead use C from the specific Transport class. =head2 wrapper Slot for storing the application wrapper instance (IPC::Run or Net::Telnet). Do not mess with this unless you know what you are doing. =head2 buffer After C returns successfully, the output most recently received is stored in this slot. Do not access this directly, but instead use the C method. =head2 stash During long sections of output, this slot allows more efficient detection of matches. Older data is placed here, and only the most recent line of data is stored in the C. That's why C is the only way to ensure you get all the output data in one go. =head1 NOTES B: On Unix, when the Telnet transport is selected but C is unavailable, C can still be used, but currently C is used instead. =cut Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Transport/Loopback.pm000444000766000024 367113170404307 24332 0ustar00oliverstaff000000000000package Net::CLI::Interact::Transport::Loopback; { $Net::CLI::Interact::Transport::Loopback::VERSION = '2.300002' } use Moo; use Sub::Quote; use MooX::Types::MooseLike::Base qw(InstanceOf); extends 'Net::CLI::Interact::Transport::Base'; { package # hide from pause Net::CLI::Interact::Transport::Loopback::Options; use Moo; extends 'Net::CLI::Interact::Transport::Options'; } # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ has 'connect_options' => ( is => 'ro', isa => InstanceOf['Net::CLI::Interact::Transport::Loopback::Options'], default => sub { {} }, coerce => quote_sub( q{ Net::CLI::Interact::Transport::Loopback::Options->new(@_) if ref '' ne ref $_[0] }), required => 1, ); #sub _which_perl { # use Config; # $secure_perl_path = $Config{perlpath}; # if ($^O ne 'VMS') # {$secure_perl_path .= $Config{_exe} # unless $secure_perl_path =~ m/$Config{_exe}$/i;} # return $secure_perl_path; #} sub _build_app { return $^X } sub runtime_options { return ('-ne', 'BEGIN { $| = 1 }; print $_, time, "\nPROMPT> ";'); } 1; =pod =head1 NAME Net::CLI::Interact::Transport::Loopback - Testable CLI connection =head1 DECRIPTION This module provides a wrapped instance of Perl which simply echoes back any input provided. This is used for the L test suite. =head1 INTERFACE =head2 app Defaults to the value of C<$^X> (that is, Perl itself). =head2 runtime_options Returns Perl options which turn it into a CLI emulator: -ne 'BEGIN { $| = 1 }; print $_, time, "\nPROMPT>\n";' For example: some command some command 1301578196 PROMPT> In this case the output command was "some command" which was echoed, followed by the dummy command output (epoch seconds), followed by a "prompt". =head1 COMPOSITION See the following for further interface details: =over 4 =item * L =back =cut Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Transport/Net_OpenSSH.pm000444000766000024 1040713170404307 24700 0ustar00oliverstaff000000000000package Net::CLI::Interact::Transport::Net_OpenSSH; { $Net::CLI::Interact::Transport::Net_OpenSSH::VERSION = '2.300002' } use Moo; use Sub::Quote; use MooX::Types::MooseLike::Base qw(InstanceOf ArrayRef Str); extends 'Net::CLI::Interact::Transport::Base'; { package # hide from pause Net::CLI::Interact::Transport::Net_OpenSSH::Options; use Moo; use Sub::Quote; use MooX::Types::MooseLike::Base qw(InstanceOf Any Str HashRef ArrayRef); extends 'Net::CLI::Interact::Transport::Options'; has 'master' => ( is => 'rw', isa => InstanceOf['Net::OpenSSH'], required => 1, ); has 'opts' => ( is => 'rw', isa => HashRef[Any], default => sub { {} }, ); has 'shell_cmd' => ( is => 'rw', coerce => quote_sub(q{ (ref '' eq ref $_[0]) ? [$_[0]] : $_[0] }), isa => ArrayRef[Str], default => sub { [] }, ); } # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ has 'connect_options' => ( is => 'ro', isa => InstanceOf['Net::CLI::Interact::Transport::Net_OpenSSH::Options'], coerce => quote_sub(q{ (ref '' eq ref $_[0]) ? $_[0] : Net::CLI::Interact::Transport::Net_OpenSSH::Options->new(@_) }), required => 1, ); has app_and_runtime_options => ( is => 'lazy', isa => ArrayRef[Str], ); sub _build_app_and_runtime_options { my $self = shift; my $master = $self->connect_options->master; [ $master->make_remote_command($self->connect_options->opts, @{$self->connect_options->shell_cmd}) ] } sub app { shift->app_and_runtime_options->[0] } sub runtime_options { my @cmd = @{ shift->app_and_runtime_options }; shift @cmd; @cmd; } 1; =pod =encoding UTF-8 =head1 NAME Net::CLI::Interact::Transport::Net_OpenSSH - Net::OpenSSH based CLI connection =head1 DESCRIPTION This module provides a wrapped instance of a L SSH client object for use by L. This allows one to combine the capability of Net::CLI::Interact to talk to remote servers for which Net::OpenSSH one-command-per-session approach is not well suited (i.e. network equipment running custom administration shells) and still use the capability of Net::OpenSSH to run several sessions over one single SSH connection, including accessing SCP and SFTP services. Note that this transport is not supported on Windows as Net::OpenSSH is not supported there either. =head1 INTERFACE =head2 app_and_runtime_options Based on the C hash provided to Net::CLI::Interact on construction, selects and formats the command and arguments required to run the SSH session over the Net::OpenSSH connection. Under the hood, this method just wraps Net::OpenSSH C method. Supported attributes: =over 4 =item master Reference to the Net::OpenSSH object wrapping the SSH master connection. =item opts Optional hash of extra options to be forwarded to Net::OpenSSH C method. =item shell_cmd Remote command to start the shell. Can be a single string or an array reference. The default is to pass nothing which on conforming SSH implementations starts the shell configured for the user. Examples: # interact with default user shell: $s->new({ # ...other parameters to new()... connect_options => { master => $ssh }, }); # interact with csh: $s->new({ # ...other parameters to new()... connect_options => { master => $ssh, shell_cmd => ['csh', '-i'], }, }); =item reap Only used on Unix platforms, this installs a signal handler which attempts to reap the C child process. Pass a true value to enable this feature only if you notice zombie processes are being left behind after use. =back =head1 COMPOSITION See the following for further interface details: =over 4 =item * L =back =head1 AUTHORS Oliver Gorwits Salvador FandiEo =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2014 by Oliver Gorwits. This software is copyright (c) 2014 by Salvador Fandiño. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Transport/SSH.pm000444000766000024 1161413170404307 23251 0ustar00oliverstaff000000000000package Net::CLI::Interact::Transport::SSH; { $Net::CLI::Interact::Transport::SSH::VERSION = '2.300002' } use Moo; use Sub::Quote; use MooX::Types::MooseLike::Base qw(InstanceOf); extends 'Net::CLI::Interact::Transport::Base'; { package # hide from pause Net::CLI::Interact::Transport::SSH::Options; use Moo; use Sub::Quote; use MooX::Types::MooseLike::Base qw(Str Bool ArrayRef Any); extends 'Net::CLI::Interact::Transport::Options'; has 'host' => ( is => 'rw', isa => Str, required => 1, ); has 'username' => ( is => 'rw', isa => Str, predicate => 1, ); has 'shkc' => ( is => 'rw', isa => Bool, default => quote_sub('0'), ); has 'ignore_host_checks' => ( is => 'rw', isa => Bool, default => quote_sub('1'), ); has 'opts' => ( is => 'rw', isa => ArrayRef[Any], default => sub { [] }, ); } # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ has 'connect_options' => ( is => 'ro', isa => InstanceOf['Net::CLI::Interact::Transport::SSH::Options'], coerce => quote_sub(q{ (ref '' eq ref $_[0]) ? $_[0] : Net::CLI::Interact::Transport::SSH::Options->new(@_) }), required => 1, ); sub _build_app { my $self = shift; die "please pass location of plink.exe in 'app' parameter to new()\n" if $self->is_win32; return 'ssh'; } sub runtime_options { my $self = shift; if ($self->is_win32) { return ( '-ssh', @{$self->connect_options->opts}, ($self->connect_options->has_username ? ($self->connect_options->username . '@') : '') . $self->connect_options->host, ); } else { return ( (($self->connect_options->ignore_host_checks and not $self->connect_options->shkc) ? ( '-o', 'StrictHostKeyChecking=no', '-o', 'UserKnownHostsFile=/dev/null', '-o', 'CheckHostIP=no', ) : ()), @{$self->connect_options->opts}, ($self->connect_options->has_username ? ('-l', $self->connect_options->username) : ()), $self->connect_options->host, ); } } 1; =pod =head1 NAME Net::CLI::Interact::Transport::SSH - SSH based CLI connection =head1 DESCRIPTION This module provides a wrapped instance of an SSH client for use by L. =head1 INTERFACE =head2 app On Windows platforms you B download the C program, and pass its location to the library in this parameter. On other platforms, this defaults to C (openssh). =head2 runtime_options Based on the C hash provided to Net::CLI::Interact on construction, selects and formats parameters to provide to C on the command line. Supported attributes: =over 4 =item host (required) Host name or IP address of the host to which the SSH application is to connect. Alternatively you can pass a value of the form C, but it's probably better to use the separate C parameter instead. =item username Optionally pass in the username for the SSH connection, otherwise the SSH client defaults to the current user's username. When using this option, you should obviously I pass the host name to C. =item ignore_host_checks Under normal interactive use C tracks the identity of connected hosts and verifies these identities upon each connection. In automation this behaviour can be irritating because it is interactive. This option, enabled by default, causes C to skip or ignore this host identity verification. This means the default setting is less secure, but also less likely to trip you up. It is equivalent to the following: StrictHostKeyChecking=no UserKnownHostsFile=/dev/null CheckHostIP=no Pass a false value to this option to disable the above and return C to its default configured settings. =item opts If you want to pass any other options to openssh on its command line, then use this option, which should be an array reference. Each item in the list will be passed to C, separated by a single space character. For example: $s->new({ # ...other parameters to new()... connect_options => { opts => [ '-p', '222', # connect to non-standard port on remote host '-o', 'CheckHostIP=no', # don't check host IP in known_hosts file ], }, }); =item reap Only used on Unix platforms, this installs a signal handler which attempts to reap the C child process. Pass a true value to enable this feature only if you notice zombie processes are being left behind after use. =back =head1 COMPOSITION See the following for further interface details: =over 4 =item * L =back =cut Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Transport/Serial.pm000444000766000024 773213170404307 24021 0ustar00oliverstaff000000000000package Net::CLI::Interact::Transport::Serial; { $Net::CLI::Interact::Transport::Serial::VERSION = '2.300002' } use Moo; use Sub::Quote; use MooX::Types::MooseLike::Base qw(InstanceOf); extends 'Net::CLI::Interact::Transport::Base'; { package # hide from pause Net::CLI::Interact::Transport::Serial::Options; use Moo; use Sub::Quote; use MooX::Types::MooseLike::Base qw(Str Bool Int ArrayRef Any); extends 'Net::CLI::Interact::Transport::Options'; has 'device' => ( is => 'rw', isa => Str, required => 1, ); has 'parity' => ( is => 'rw', isa => quote_sub( q{ die "$_[0] not none/even/odd" unless $_[0] =~ m/^(?:none|even|odd)$/ }), default => quote_sub(q{'none'}), ); has 'nostop' => ( is => 'rw', isa => Bool, default => quote_sub('0'), ); has 'speed' => ( is => 'rw', isa => Int, default => quote_sub('9600'), ); has 'opts' => ( is => 'rw', isa => ArrayRef[Any], default => sub { [] }, ); } # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ has 'connect_options' => ( is => 'ro', isa => InstanceOf['Net::CLI::Interact::Transport::Serial::Options'], coerce => quote_sub(q{ (ref '' eq ref $_[0]) ? $_[0] : Net::CLI::Interact::Transport::Serial::Options->new(@_) }), required => 1, ); sub _build_app { my $self = shift; die "please pass location of plink.exe in 'app' parameter to new()\n" if $self->is_win32; return 'cu'; # unix } sub runtime_options { my $self = shift; if ($self->is_win32) { return ( '-serial', @{$self->connect_options->opts}, $self->connect_options->device, ); } else { return ( ('--parity=' . $self->connect_options->parity), ('-l', $self->connect_options->device), ('-s', $self->connect_options->speed), ($self->connect_options->nostop ? '--nostop' : ()), @{$self->connect_options->opts}, ); } } 1; =pod =head1 NAME Net::CLI::Interact::Transport::Serial - Serial-line based CLI connection =head1 DESCRIPTION This module provides a wrapped instance of a Serial-line client for use by L. =head1 INTERFACE =head2 app On Windows platforms you B download the C program, and pass its location to the library in this parameter. On other platforms, this defaults to C, which again you B download and install. =head2 runtime_options Based on the C hash provided to Net::CLI::Interact on construction, selects and formats parameters to provide to C on the command line. Supported attributes: B on Windows platforms, only the device attribute is supported. =over 4 =item device (required) Name of the device providing access to the Serial-line (e.g. C<< /dev/ttyUSB0 >> or C. =item parity You have a choice of C, C or C for the parity used in serial communication. The default is C. =item nostop You can control whether to use C handling for the serial communication. The default is to disable this, so to enable it pass any True value. =item speed You can set the speed (or I) of the serial line by passing a value to this named parameter. The default is C<9600>. =item opts If you want to pass any other options to the application, then use this option, which should be an array reference. Each item in the list will be passed to the application, separated by a single space character. =item reap Only used on Unix platforms, this installs a signal handler which attempts to reap the child process. Pass a true value to enable this feature only if you notice zombie processes are being left behind after use. =back =head1 COMPOSITION See the following for further interface details: =over 4 =item * L =back =cut Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Transport/Telnet.pm000444000766000024 746313170404307 24036 0ustar00oliverstaff000000000000package Net::CLI::Interact::Transport::Telnet; { $Net::CLI::Interact::Transport::Telnet::VERSION = '2.300002' } use Moo; use Sub::Quote; use MooX::Types::MooseLike::Base qw(InstanceOf); extends 'Net::CLI::Interact::Transport::Base'; { package # hide from pause Net::CLI::Interact::Transport::Telnet::Options; use Moo; use Sub::Quote; use MooX::Types::MooseLike::Base qw(Str Int ArrayRef Any); extends 'Net::CLI::Interact::Transport::Options'; has 'host' => ( is => 'rw', isa => Str, required => 1, ); has 'port' => ( is => 'rw', isa => Int, default => quote_sub('23'), ); has 'opts' => ( is => 'rw', isa => ArrayRef[Any], default => sub { [] }, ); } # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # allow native use of Net::Telnet on Unix if (not Net::CLI::Interact::Transport::Base::is_win32()) { has '+use_net_telnet_connection' => ( default => quote_sub('1') ); } has 'connect_options' => ( is => 'ro', isa => InstanceOf['Net::CLI::Interact::Transport::Telnet::Options'], coerce => quote_sub(q{ (ref '' eq ref $_[0]) ? $_[0] : Net::CLI::Interact::Transport::Telnet::Options->new(@_) }), required => 1, ); sub _build_app { my $self = shift; die "please pass location of plink.exe in 'app' parameter to new()\n" if $self->is_win32; return 'telnet'; } sub runtime_options { my $self = shift; if ($self->is_win32) { return ( '-telnet', '-P', $self->connect_options->port, @{$self->connect_options->opts}, $self->connect_options->host, ); } elsif ($self->can_use_pty) { return ( Host => $self->connect_options->host, Port => $self->connect_options->port, @{$self->connect_options->opts}, ); } else { return ( @{$self->connect_options->opts}, $self->connect_options->host, $self->connect_options->port, ); } } 1; =pod =head1 NAME Net::CLI::Interact::Transport::Telnet - TELNET based CLI connection =head1 DESCRIPTION This module provides a wrapped instance of a TELNET client for use by L. =head1 INTERFACE =head2 app On Windows platforms you B download the C program, and pass its location to the library in this parameter. On other platforms, this defaults to C. =head2 runtime_options Based on the C hash provided to Net::CLI::Interact on construction, selects and formats parameters to provide to C on the command line. Supported attributes: =over 4 =item host (required) Host name or IP address of the host to which the TELNET application is to connect. =item port Port number on the host which is listening for the TELNET connection. Defaults to 23. =item opts If you want to pass any other options to the Telnet application, then use this option, which should be an array reference. On Windows platforms, each item on the list will be passed to the C application, separated by a single space character. On Unix platforms, if depends whether you have L installed (which in turn depends on a compiler). Typically, the L library is used for TELNET connections, so the list can be any options taken by its C constructor. Otherwise the local C application is used. =item reap Only used on Unix platforms, this installs a signal handler which attempts to reap the C child process. Pass a true value to enable this feature only if you notice zombie processes are being left behind after use. =back =head1 COMPOSITION See the following for further interface details: =over 4 =item * L =back =cut Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Transport/Platform000755000766000024 013170404307 23662 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Transport/Platform/Unix.pm000444000766000024 134313170404307 25301 0ustar00oliverstaff000000000000package Net::CLI::Interact::Transport::Platform::Unix; { $Net::CLI::Interact::Transport::Platform::Unix::VERSION = '2.300002' } use Moo; use Class::Load qw(try_load_class); BEGIN { sub can_use_pty { return try_load_class('IO::Pty') } extends (can_use_pty() ? 'Net::CLI::Interact::Transport::Wrapper::Net_Telnet' : 'Net::CLI::Interact::Transport::Wrapper::IPC_Run'); } { package # hide from pause Net::CLI::Interact::Transport::Platform::Options; use Moo; use Sub::Quote; use MooX::Types::MooseLike::Base qw(Int); extends 'Net::CLI::Interact::Transport::Wrapper::Options'; has 'reap' => ( is => 'rw', isa => Int, default => quote_sub('0'), ); } 1; Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Transport/Platform/Win32.pm000444000766000024 56413170404307 25244 0ustar00oliverstaff000000000000package Net::CLI::Interact::Transport::Platform::Win32; { $Net::CLI::Interact::Transport::Platform::Win32::VERSION = '2.300002' } use Moo; extends 'Net::CLI::Interact::Transport::Wrapper::IPC_Run'; { package # hide from pause Net::CLI::Interact::Transport::Platform::Options; use Moo; extends 'Net::CLI::Interact::Transport::Wrapper::Options'; } 1; Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Transport/Role000755000766000024 013170404307 22777 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Transport/Role/ConnectCore.pm000444000766000024 613513170404307 25701 0ustar00oliverstaff000000000000package Net::CLI::Interact::Transport::Role::ConnectCore; { $Net::CLI::Interact::Transport::Role::ConnectCore::VERSION = '2.300002' } use Moo::Role; use MooX::Types::MooseLike::Base qw(Int); use Net::Telnet (); sub connect_core { my $self = shift; if ($self->use_net_telnet_connection) { my $app = shift; # unused return $self->_via_native(@_); } else { return $self->_via_spawn(@_); } } sub _via_native { my $self = shift; my $t = Net::Telnet->new(Cmd_remove_mode => 1, @_); return $t; } sub _via_spawn { my $self = shift; my $t = Net::Telnet->new( Binmode => 1, Cmd_remove_mode => 1, Telnetmode => 0, ); $t->fhopen( $self->_spawn_command(@_) ) or die "failed to spawn connection to target device."; return $t; } # this code is based on that in Expect.pm, and found to be the most reliable. # minor alterations to use CORE::close and die, and to reap child. use FileHandle; use IO::Pty; use POSIX qw(WNOHANG); has 'childpid' => ( is => 'rw', isa => Int, ); sub REAPER { # http://www.perlmonks.org/?node_id=10516 my $stiff; 1 while (($stiff = waitpid(-1, &WNOHANG)) > 0); $SIG{CHLD} = \&REAPER; } sub _spawn_command { my $self = shift; my @command = @_; my $pty = IO::Pty->new(); # try to install handler to reap children $SIG{CHLD} = \&REAPER if !defined $SIG{CHLD}; # set up pipe to detect childs exec error my ($stat_rdr, $stat_wtr); pipe($stat_rdr, $stat_wtr) or die "Cannot open pipe: $!"; $stat_wtr->autoflush(1); eval { fcntl($stat_wtr, F_SETFD, FD_CLOEXEC); }; my $pid = fork; if (! defined ($pid)) { die "Cannot fork: $!" if $^W; return undef; } if($pid) { # parent my $errno; CORE::close $stat_wtr; $pty->close_slave(); $pty->set_raw(); # now wait for child exec (eof due to close-on-exit) or exec error my $errstatus = sysread($stat_rdr, $errno, 256); die "Cannot sync with child: $!" if not defined $errstatus; CORE::close $stat_rdr; if ($errstatus) { $! = $errno+0; die "Cannot exec(@command): $!\n" if $^W; return undef; } # store pid for killing if we're in cygwin $self->childpid( $pid ); } else { # child CORE::close $stat_rdr; $pty->make_slave_controlling_terminal(); my $slv = $pty->slave() or die "Cannot get slave: $!"; $slv->set_raw(); CORE::close($pty); CORE::close(STDIN); open(STDIN,"<&". $slv->fileno()) or die "Couldn't reopen STDIN for reading, $!\n"; CORE::close(STDOUT); open(STDOUT,">&". $slv->fileno()) or die "Couldn't reopen STDOUT for writing, $!\n"; CORE::close(STDERR); open(STDERR,">&". $slv->fileno()) or die "Couldn't reopen STDERR for writing, $!\n"; { exec(@command) }; print $stat_wtr $!+0; die "Cannot exec(@command): $!\n"; } return $pty; } 1; Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Transport/Role/StripControlChars.pm000444000766000024 204513170404307 27116 0ustar00oliverstaff000000000000package Net::CLI::Interact::Transport::Role::StripControlChars; { $Net::CLI::Interact::Transport::Role::StripControlChars::VERSION = '2.300002' } use strict; use warnings FATAL => 'all'; use Moo::Role; my %ansi_codes = ( 1 => q/\x1b\[\d+;\d+H/, # code_position_cursor 3 => q/\x1b\[\?25h/, #code_show_cursor 4 => q/\x1b\x45/, #code_next_line 5 => q/\x1b\[2K/, #code_erase_line 6 => q/\x1b\[K/, #code_erase_start_line 7 => q/\x1b\[\d+;\d+r/, #code_enable_scroll 68 => q/\e\[\??\d+(;\d+)*[A-Za-z]/, #VLZ addon from ytti/oxidized ); # https://github.com/ollyg/Net-CLI-Interact/issues/22 around 'buffer' => sub { my $orig = shift; my $buffer = ($orig->(@_) || ''); # remove control characters $buffer =~ s/[\000-\010\013\014\016-\032\034-\037]//g; # strip ANSI terminal codes foreach my $code (sort keys %ansi_codes) { my $to = ''; $to = "\n" if ($code == 4); # CODE_NEXT_LINE must substitute with '\n' $buffer =~ s/$ansi_codes{$code}/$to/g; } return $buffer; }; 1; Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Transport/Wrapper000755000766000024 013170404307 23516 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Transport/Wrapper/Base.pm000444000766000024 721413170404307 25067 0ustar00oliverstaff000000000000package Net::CLI::Interact::Transport::Wrapper::Base; { $Net::CLI::Interact::Transport::Wrapper::Base::VERSION = '2.300002' } use Moo; use Sub::Quote; use MooX::Types::MooseLike::Base qw(Int RegexpRef Str Object); with 'Net::CLI::Interact::Role::FindMatch'; { package # hide from pause Net::CLI::Interact::Transport::Wrapper::Base::Options; use Moo; } has 'use_net_telnet_connection' => ( is => 'rw', isa => Int, default => quote_sub('0'), ); has 'irs_re' => ( is => 'ro', isa => RegexpRef, default => quote_sub(q{ qr/(?:\015\012|\015|\012)/ }), # first wins ); has 'ors' => ( is => 'rw', isa => Str, default => quote_sub(q{"\n"}), ); has 'timeout' => ( is => 'rw', isa => quote_sub(q{ die "$_[0] is not a posint!" unless $_[0] > 0 }), default => quote_sub('10'), ); has 'app' => ( is => 'lazy', isa => Str, predicate => 1, clearer => 1, ); has 'stash' => ( is => 'rw', isa => Str, default => quote_sub(q{''}), ); has 'wrapper' => ( is => 'lazy', isa => Object, predicate => 'connect_ready', clearer => 1, ); sub _build_wrapper { my $self = shift; $self->logger->log('transport', 'notice', 'connecting with: ', $self->app, (join ' ', map {($_ =~ m/\s/) ? ("'". $_ ."'") : $_} $self->runtime_options)); # this better be wrapped otherwise it'll blow up }; sub init { (shift)->wrapper(@_) } sub flush { my $self = shift; my $content = $self->stash . $self->buffer; $self->stash(''); $self->buffer(''); return $content; } sub disconnect { my $self = shift; $self->clear_wrapper; $self->flush; } sub _abc { die "not implemented." } sub put { _abc() } sub pump { _abc() } sub buffer { _abc() } sub DEMOLISH { (shift)->disconnect } sub do_action { my ($self, $action) = @_; $self->logger->log('transport', 'info', 'callback received for', $action->type); if ($action->type eq 'match') { my $irs_re = $self->irs_re; my $cont = $action->continuation; while ($self->pump) { # remove control characters (my $buffer = $self->buffer) =~ s/[\000-\010\013\014\016-\032\034-\037]//g; $self->logger->log('dump', 'debug', "SEEN:\n". $buffer); if ($buffer =~ m/^(.*$irs_re)(.*)/s) { $self->stash($self->stash . $1); $self->buffer($2 || ''); } if ($cont and $self->find_match($self->buffer, $cont->first->value)) { $self->logger->log('transport', 'debug', 'continuation matched'); $self->buffer(''); $self->put($cont->last->value); } elsif (my $hit = $self->find_match($self->buffer, $action->value)) { $self->logger->log('transport', 'info', sprintf 'output matched %s, storing and returning', $hit); $action->prompt_hit($hit); $action->response_stash($self->stash); $action->response($self->buffer); $self->flush; last; } else { $self->logger->log('transport', 'debug', "nope, doesn't (yet) match", (ref $action->value eq ref [] ? (join '|', @{$action->value}) : $action->value)); } } } if ($action->type eq 'send') { my $command = sprintf $action->value, @{ $action->params }; $self->logger->log('dialogue', 'notice', 'queueing data for send: "'. $command .'"'); $self->put( $command, ($action->no_ors ? () : $self->ors) ); } } 1; Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Transport/Wrapper/IPC_Run.pm000444000766000024 341213170404307 25450 0ustar00oliverstaff000000000000package Net::CLI::Interact::Transport::Wrapper::IPC_Run; { $Net::CLI::Interact::Transport::Wrapper::IPC_Run::VERSION = '2.300002' } use Moo; use Sub::Quote; use MooX::Types::MooseLike::Base qw(ScalarRef InstanceOf); extends 'Net::CLI::Interact::Transport::Wrapper::Base'; { package # hide from pause Net::CLI::Interact::Transport::Wrapper::Options; use Moo; extends 'Net::CLI::Interact::Transport::Wrapper::Base::Options'; } use IPC::Run (); has '_in' => ( is => 'rw', isa => ScalarRef, default => sub { \eval "''" }, ); # writer for the _in slot sub put { ${ (shift)->_in } .= join '', @_ } has '_out' => ( is => 'ro', isa => ScalarRef, default => sub { \eval "''" }, ); sub buffer { my $self = shift; return ${ $self->_out } if scalar(@_) == 0; return ${ $self->_out } = shift; } # clearer for the _out slot has '_err' => ( is => 'ro', isa => ScalarRef, default => sub { \eval "''" }, ); has '_timeout_obj' => ( is => 'lazy', isa => InstanceOf['IPC::Run::Timer'], ); sub _build__timeout_obj { return IPC::Run::timeout((shift)->timeout) } has '+timeout' => ( trigger => quote_sub(q{(shift)->_timeout_obj->start(shift) if scalar @_ > 1}), ); has '+wrapper' => ( isa => InstanceOf['IPC::Run'], handles => ['pump'], ); around '_build_wrapper' => sub { my ($orig, $self) = (shift, shift); $self->logger->log('transport', 'notice', 'booting IPC::Run harness for', $self->app); $self->$orig(@_); return IPC::Run::harness( [$self->app, $self->runtime_options], $self->_in, $self->_out, $self->_err, $self->_timeout_obj, ); }; before 'disconnect' => sub { my $self = shift; $self->wrapper->kill_kill(grace => 1); }; 1; Net-CLI-Interact-2.300002/lib/Net/CLI/Interact/Transport/Wrapper/Net_Telnet.pm000444000766000024 313613170404307 26255 0ustar00oliverstaff000000000000package Net::CLI::Interact::Transport::Wrapper::Net_Telnet; { $Net::CLI::Interact::Transport::Wrapper::Net_Telnet::VERSION = '2.300002' } use Moo; use Sub::Quote; use MooX::Types::MooseLike::Base qw(Str InstanceOf); extends 'Net::CLI::Interact::Transport::Wrapper::Base'; { package # hide from pause Net::CLI::Interact::Transport::Wrapper::Options; use Moo; extends 'Net::CLI::Interact::Transport::Wrapper::Base::Options'; } sub put { (shift)->wrapper->put( join '', @_ ) } has '_buffer' => ( is => 'rw', isa => Str, default => quote_sub(q{''}), ); sub buffer { my $self = shift; return $self->_buffer if scalar(@_) == 0; return $self->_buffer(shift); } sub pump { my $self = shift; my $content = $self->wrapper->get(Timeout => $self->timeout); $self->_buffer($self->_buffer . $content) if defined $content; } has '+timeout' => ( trigger => 1, ); sub _trigger_timeout { my $self = shift; if (scalar @_) { my $timeout = shift; if ($self->connect_ready) { $self->wrapper->timeout($timeout); } } } has '+wrapper' => ( isa => InstanceOf['Net::Telnet'], ); around '_build_wrapper' => sub { my ($orig, $self) = (shift, shift); $self->logger->log('transport', 'notice', 'creating Net::Telnet wrapper for', $self->app); $self->$orig(@_); $SIG{CHLD} = 'IGNORE' if not $self->connect_options->reap; with 'Net::CLI::Interact::Transport::Role::ConnectCore'; return $self->connect_core($self->app, $self->runtime_options); }; after 'disconnect' => sub { delete $SIG{CHLD}; }; 1; Net-CLI-Interact-2.300002/share000755000766000024 013170404307 15530 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook000755000766000024 013170404307 17665 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/avaya000755000766000024 013170404307 20766 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/avaya/pb000444000766000024 77213170404307 21435 0ustar00oliverstaff000000000000prompt generic match /[\/a-zA-Z0-9._:-]+ ?(?:\(config[^)]*\))? ?[#>] ?$/ prompt privileged match /[\/a-zA-Z0-9._:-]+ ?(?:\(config[^)]*\))? ?[#>] ?$/ prompt user match /[Ll]ogin: ?$/ prompt pass match /[Pp]assword: ?$/ macro paging send ' ' macro disconnect send exit match /Do you want to end your session \? \(y\/n\) :/ send y match generic # not used macro more_pages match /Press any key to continue \(q : quit\) :/ send ' ' Net-CLI-Interact-2.300002/share/phrasebook/cisco000755000766000024 013170404307 20765 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/cisco/pb000444000766000024 300113170404307 21440 0ustar00oliverstaff000000000000prompt generic match /[\/a-zA-Z0-9._\[\]-]+ ?(?:\(config[^)]*\))? ?[#>] ?$/ prompt privileged match /# ?$/ prompt configure match /\(config[^)]*\)# ?$/ prompt user match /[Uu]sername: ?$/ prompt pass match /[Pp]assword: ?$/ # MACROS macro begin_privileged send enable match user or pass or privileged macro end_privileged send disable match generic macro begin_configure send configure terminal match configure macro end_configure send exit match privileged macro disconnect send exit match generic # macro completion # send ? # for setting up automated reloading, requires argument N (minutes) # note: if done as send-match sequence, then the 'has been modified' part # is mandatory - but not all ios versions have that prompt... # the follow...with construction on the other hand works with or without # that step. # note all lines that are matched against prompt or follow are STRIPPED from the response. macro reload_in send reload in %s # match /System configuration has been modified. Save\? \[yes\/no\]:/ # send no follow /System configuration has been modified. Save\? \[yes\/no\]:/ with no\n match /Proceed with reload\? \[confirm\]/ send '' match privileged # no arguments. last match is pretty much a dud, the session will be gone. macro reload send reload follow /System configuration has been modified. Save\? \[yes\/no\]:/ with no\n match /Proceed with reload\? \[confirm\]/ send '' match privileged Net-CLI-Interact-2.300002/share/phrasebook/cisco/asa000755000766000024 013170404307 21531 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/cisco/asa/pb000444000766000024 3713170404307 22152 0ustar00oliverstaff000000000000macro paging send pager 0 Net-CLI-Interact-2.300002/share/phrasebook/cisco/catos000755000766000024 013170404307 22076 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/cisco/catos/pb000444000766000024 24013170404307 22533 0ustar00oliverstaff000000000000prompt generic match /[\/a-zA-Z0-9._-]+ ?[#>] ?(?:\(enable[^)]*\))? ?$/ prompt privileged match /> \(enable\) ?$/ macro paging send set length %s Net-CLI-Interact-2.300002/share/phrasebook/cisco/foundry000755000766000024 013170404307 22453 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/cisco/foundry/pb000444000766000024 111613170404307 23133 0ustar00oliverstaff000000000000# if using Net::Appliance::Session, call this before connect(): # $session_obj->nci->transport->ors("\r\n"); prompt generic match /[\/a-zA-Z0-9._-]+ ?(?:\(config[^)]*\))? ?[#>] ?$/ prompt privileged match /> ?$/ prompt configure match /\(config[^)]*\)# ?$/ prompt user match /Login Name: ?$/ prompt pass match /Password: ?$/ macro begin_configure send configure terminal match /configure terminal/ macro disable_paging send skip-page-display match generic macro enable_paging send page-display match generic macro disconnect send exit Net-CLI-Interact-2.300002/share/phrasebook/cisco/hp000755000766000024 013170404307 21374 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/cisco/hp/pb000444000766000024 115313170404307 22055 0ustar00oliverstaff000000000000# phrasebook for hp devices with cli, e.g. hp procurve # hp cli: outputs ansi escapes, before and after the prompt...apparently not possible to disable? # privileged and configure prompt cannot be inherited from cisco pb for that reason prompt generic match /[\/a-zA-Z0-9._:-]+ ?(?:\(config[^)]*\))? ?[#>] ?(?:\x1b\[[0-9;\?]*[A-KSTfhilmnrs])*$/ prompt privileged match /# ?(?:\x1b\[[0-9;\?]*[A-KSTfhilmnrs])*$/ prompt configure match /\(config[^)]*\)# ?(?:\x1b\[[0-9;\?]*[A-KSTfhilmnrs])*$/ prompt user match /login as: ?$/ macro enable_paging send page macro disable_paging send no page Net-CLI-Interact-2.300002/share/phrasebook/cisco/ios000755000766000024 013170404307 21557 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/cisco/ios/pb000444000766000024 32213170404307 22215 0ustar00oliverstaff000000000000# prompt err_string # match /% ?(?:Error|Type "[^?]+\?"|(?:Incomplete|Unknown) command|Invalid input)/ macro paging send terminal length %s macro fsck send fsck follow /\[confirm\]/ with "\n" Net-CLI-Interact-2.300002/share/phrasebook/cisco/junos000755000766000024 013170404307 22123 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/cisco/junos/pb000444000766000024 47113170404307 22566 0ustar00oliverstaff000000000000prompt generic match /[\/a-zA-Z0-9._\@-]+ ?(?:\(config[^)]*\))? ?[#>] ?$/ prompt privileged match /> ?$/ prompt configure match /# ?$/ prompt user match /(?:[Ll]ogin|[Uu]sername): ?$/ macro begin_configure send configure match configure macro paging send set cli screen-length %s Net-CLI-Interact-2.300002/share/phrasebook/cisco/nortel000755000766000024 013170404307 22270 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/cisco/nortel/pb000444000766000024 16113170404307 22727 0ustar00oliverstaff000000000000prompt generic match /[\/a-zA-Z0-9._:-]+ ?(?:\(config[^)]*\))? ?[#>] ?$/ prompt user match /Login: ?$/ Net-CLI-Interact-2.300002/share/phrasebook/cisco/pixos000755000766000024 013170404307 22127 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/cisco/pixos/pb000444000766000024 17313170404307 22571 0ustar00oliverstaff000000000000prompt err_string match /(?:Type help|(?:Error|ERROR|Usage|usage):|not allowed)/ macro paging send pager lines %s Net-CLI-Interact-2.300002/share/phrasebook/cisco/pixos/fwsm000755000766000024 013170404307 23103 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/cisco/pixos/fwsm/fwsm3000755000766000024 013170404307 24142 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/cisco/pixos/fwsm/fwsm3/pb000444000766000024 5513170404307 24563 0ustar00oliverstaff000000000000macro pager send terminal pager lines %s Net-CLI-Interact-2.300002/share/phrasebook/cisco/pixos/pixos7000755000766000024 013170404307 23360 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/cisco/pixos/pixos7/pb000444000766000024 5613170404307 24002 0ustar00oliverstaff000000000000macro paging send terminal pager lines %s Net-CLI-Interact-2.300002/share/phrasebook/cisco/wlc000755000766000024 013170404307 21552 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/cisco/wlc/pb000444000766000024 27413170404307 22216 0ustar00oliverstaff000000000000prompt user match /[Uu]ser/ prompt generic match /\(Cisco Controller\) >/ macro disable_paging send config paging disable macro enable_paging send config paging enable Net-CLI-Interact-2.300002/share/phrasebook/extremexos000755000766000024 013170404307 22070 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/extremexos/pb000444000766000024 67413170404307 22540 0ustar00oliverstaff000000000000# prompts for "extreme networks" extremexos devices, # moved to its own category - not inheriting from cisco anymore prompt user match /(?:[Ll]ogin|[Uu]sername): ?$/ prompt pass match /[Pp]assword: ?$/ prompt generic match / # ?$/ macro disconnect send exit # macro completion # send ? macro disconnect send exit macro paging send disable clipaging # not used macro more_pages match / More / send '\t' Net-CLI-Interact-2.300002/share/phrasebook/f5000755000766000024 013170404307 20177 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/f5/f5bigip000755000766000024 013170404307 21524 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/f5/f5bigip/pb000444000766000024 46313170404307 22170 0ustar00oliverstaff000000000000# phrase book for F5 Big-IP systems prompt user match /[Uu]sername: ?$/ prompt pass match /[Pp]assword( for.*)?: ?$/ prompt generic match /\w+@.+(\$|#) $/ prompt privileged match /\[?root@.+# $/ macro disconnect send quit macro paging send modify cli preference pager disabled Net-CLI-Interact-2.300002/share/phrasebook/fortinet000755000766000024 013170404307 21517 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/fortinet/pb000444000766000024 25513170404307 22162 0ustar00oliverstaff000000000000prompt user match /[Uu]sername: $/ prompt pass match /[Pp]assword:/ prompt generic match /\w+ #/ macro paging send config macro disconnect send exit Net-CLI-Interact-2.300002/share/phrasebook/mikrotik000755000766000024 013170404307 21516 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/mikrotik/pb000444000766000024 76613170404307 22170 0ustar00oliverstaff000000000000# a preliminary phrasebook for mikrotik routeros devices # see http://wiki.mikrotik.com/wiki/Manual:Console_login_process for prompts and quirks # version 0 prompt user match /Login: ?$/ prompt pass match /[Pp]assword: ?$/ # note: that only covers prompts in single-line command mode, not safe mode, # not hotlock mode, and not line continuation mode either prompt generic match /\] (\/.+)?>\s+$/ macro paging send / prompt privileged match /\] (\/.+)?>\s+$/ macro disconnect send /quit Net-CLI-Interact-2.300002/share/phrasebook/ovmcli000755000766000024 013170404307 21156 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/ovmcli/pb000444000766000024 21513170404307 21615 0ustar00oliverstaff000000000000# Oracle VM Manager CLI prompt pass match /password(?: for \w+)?: $/ prompt generic match /OVM> $/ macro disconnect send exit Net-CLI-Interact-2.300002/share/phrasebook/redback000755000766000024 013170404307 21260 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/redback/pb000444000766000024 47613170404307 21730 0ustar00oliverstaff000000000000# prompts for "redback networks" (now part of ericsson) prompt user match /(?:[Ll]ogin|[Uu]sername): ?$/ prompt pass match /[Pp]assword: ?$/ prompt generic match /#$/ macro paging send 'terminal length 0' macro disconnect send exit # not used macro more_pages match /--more--/ send ' ' Net-CLI-Interact-2.300002/share/phrasebook/screenos000755000766000024 013170404307 21506 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/screenos/pb000444000766000024 26013170404307 22145 0ustar00oliverstaff000000000000prompt user match /[Uu]sername: ?$/ prompt pass match /[Pp]assword: ?$/ prompt generic match /-> / macro paging send nothing macro disconnect send quit Net-CLI-Interact-2.300002/share/phrasebook/unix000755000766000024 013170404307 20650 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/unix/bash000755000766000024 013170404307 21565 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/unix/bash/pb000444000766000024 137213170404307 22251 0ustar00oliverstaff000000000000prompt user match /[Uu]sername: $/ prompt pass match /[Pp]assword( for.*)?:/ # workaround for non-fix of rt.cpan#92376, which makes the pass 'command' # match against prompt GENERIC even though we're trying to enter PRIVILEGED # mode - then, after generic matches, it continues to check if privileged ALSO matches... # line 108 in /usr/share/perl5/Net/Appliance/Session/Engine.pm prompt generic match /\w+@.+(\$|#) $/ prompt privileged match /\[?root@.+# $/ macro begin_privileged send sudo -Hi bash match pass or privileged macro end_privileged send exit match generic macro disconnect send logout macro enable_paging send export PAGER=less match generic macro disable_paging send unset PAGER match generic Net-CLI-Interact-2.300002/share/phrasebook/unix/csh000755000766000024 013170404307 21425 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/unix/csh/pb000444000766000024 15113170404307 22063 0ustar00oliverstaff000000000000prompt password match /password: $/ prompt shell match /> $/ prompt root_shell match /# $/ Net-CLI-Interact-2.300002/share/phrasebook/unix/csh/sdf000755000766000024 013170404307 22201 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/unix/csh/sdf/pb000444000766000024 15613170404307 22644 0ustar00oliverstaff000000000000prompt generic match /\$ $/ prompt shell match /^\$/ macro sdf_login send %s\n match shell Net-CLI-Interact-2.300002/share/phrasebook/unix/qnap000755000766000024 013170404307 21607 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/unix/qnap/pb000444000766000024 140113170404307 22264 0ustar00oliverstaff000000000000prompt user match /[Uu]sername: $/ prompt pass match /[Pp]assword( for.*)?:/ # workaround for non-fix of rt.cpan#92376, which makes the pass 'command' # match against prompt GENERIC even though we're trying to enter PRIVILEGED # mode - then, after generic matches, it continues to check if privileged ALSO matches... # line 108 in /usr/share/perl5/Net/Appliance/Session/Engine.pm prompt generic match /\[[\w~\/ ]+\] # $/ prompt privileged match /\[[\w~\/ ]+\] # $/ macro begin_privileged send sudo -Hi bash match pass or privileged macro end_privileged send exit match generic macro disconnect send logout macro enable_paging send export PAGER=less match generic macro disable_paging send unset PAGER match generic Net-CLI-Interact-2.300002/share/phrasebook/zyxel000755000766000024 013170404307 21040 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/share/phrasebook/zyxel/pb000444000766000024 52713170404307 21505 0ustar00oliverstaff000000000000prompt generic match /[\/a-zA-Z0-9._\[\]-]+ ?# ?/ prompt privileged match /[\/a-zA-Z0-9._\[\]-]+ ?# ?/ prompt user match /User name: ?$/ prompt pass match /[Pp]assword: ?$/ macro paging send c # not used macro more_pages match /-- more --, next page: Space, continue: c, quit: ESC/ send ' ' Net-CLI-Interact-2.300002/t000755000766000024 013170404307 14671 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/t/10_construct.t000444000766000024 42213170404307 17515 0ustar00oliverstaff000000000000#!/usr/bin/perl use strict; use warnings FATAL => 'all'; use Test::More 0.88; BEGIN { use_ok( 'Net::CLI::Interact'); } new_ok('Net::CLI::Interact' => [ transport => 'Loopback' ]); new_ok('Net::CLI::Interact' => [{ transport => 'Loopback' }]); done_testing; Net-CLI-Interact-2.300002/t/author-10_route_server.t000444000766000024 145213170404307 21541 0ustar00oliverstaff000000000000#!/usr/bin/perl BEGIN { unless ($ENV{AUTHOR_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for testing by the author'); } } use strict; use warnings FATAL => 'all'; use Test::More 0.88; BEGIN { use_ok( 'Net::CLI::Interact') } my $s = Net::CLI::Interact->new( transport => "Telnet", ($^O eq 'MSWin32' ? (app => "$ENV{HOMEPATH}\\Desktop\\plink.exe") : () ), connect_options => { host => "route-server.bb.pipex.net" }, personality => "cisco", timeout => 5, ); ok( $s->cmd('show ip bgp 163.1.0.0/16'), 'ran show ip bgp 163.1.0.0/16' ); like( $s->last_prompt, qr/\w+ ?>$/, 'command ran and prompt looks ok' ); my @out = $s->last_response; cmp_ok( scalar @out, '==', 15, 'sensible number of lines in the command output'); done_testing; Net-CLI-Interact-2.300002/t/author-11_ssh_unknown_host.t000444000766000024 116213170404307 22425 0ustar00oliverstaff000000000000#!/usr/bin/perl BEGIN { unless ($ENV{AUTHOR_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for testing by the author'); } } use strict; use warnings FATAL => 'all'; use Test::More 0.88; BEGIN { use_ok( 'Net::CLI::Interact') } my $s = Net::CLI::Interact->new( transport => "SSH", ($^O eq 'MSWin32' ? (app => "$ENV{HOMEPATH}\\Desktop\\plink.exe") : () ), connect_options => { host => "bogus.example.com" }, personality => "cisco", ); # should fail eval { $s->cmd('show clock') }; like( $@, qr/Could not resolve hostname/, 'Unknown Host' ); done_testing; Net-CLI-Interact-2.300002/t/author-12_ssh_no_route.t000444000766000024 141013170404307 21520 0ustar00oliverstaff000000000000#!/usr/bin/perl BEGIN { unless ($ENV{AUTHOR_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for testing by the author'); } } BEGIN { if ($ENV{AT_HOME}) { require Test::More; Test::More::plan(skip_all => 'these tests do not work at home'); } } use strict; use warnings FATAL => 'all'; use Test::More 0.88; BEGIN { use_ok( 'Net::CLI::Interact') } my $s = Net::CLI::Interact->new( transport => "SSH", ($^O eq 'MSWin32' ? (app => "$ENV{HOMEPATH}\\Desktop\\plink.exe") : () ), connect_options => { host => "81.21.232.221" }, personality => "cisco", ); # should fail eval { $s->cmd('show clock') }; like( $@, qr/No route to host|Connection timed out|read timed-out/i, 'No Route' ); done_testing; Net-CLI-Interact-2.300002/t/author-13_ssh_timeout.t000444000766000024 125213170404307 21361 0ustar00oliverstaff000000000000#!/usr/bin/perl BEGIN { unless ($ENV{AUTHOR_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for testing by the author'); } } use strict; use warnings FATAL => 'all'; use Test::More 0.88; BEGIN { use_ok( 'Net::CLI::Interact') } my $s = Net::CLI::Interact->new( transport => "SSH", ($^O eq 'MSWin32' ? (app => "$ENV{HOMEPATH}\\Desktop\\plink.exe") : () ), connect_options => { host => "route-server.bb.pipex.net", opts => [qw/-o ConnectTimeout=4/], }, personality => "cisco", ); # should fail eval { $s->cmd('show clock') }; like( $@, qr/Connection timed out/, 'Timed Out' ); done_testing; Net-CLI-Interact-2.300002/t/author-20_sdf_shell.t000444000766000024 170313170404307 20760 0ustar00oliverstaff000000000000#!/usr/bin/perl BEGIN { unless ($ENV{AUTHOR_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for testing by the author'); } } use strict; use warnings FATAL => 'all'; use Test::More 0.88; BEGIN { # Test::More::plan(skip_all => 'SDF login not working'); } BEGIN { use_ok( 'Net::CLI::Interact') } my $s = Net::CLI::Interact->new( transport => 'SSH', ($^O eq 'MSWin32' ? (app => "$ENV{HOMEPATH}\\Desktop\\plink.exe") : () ), connect_options => { host => 'sdfeu.org', username => 'ollyg', # shkc => 0, }, personality => 'sdf', ); ok($s->macro('sdf_login', { params => [$ENV{SDF_PASS} || 'letmein'] }), 'logged in using SSH'); ok( $s->cmd('ls -la'), 'ran ls -la' ); like( $s->last_prompt, qr{^\$}, 'command ran and prompt looks ok' ); my @out = $s->last_response; cmp_ok( scalar @out, '==', 7, 'sensible number of lines in the command output'); done_testing; Net-CLI-Interact-2.300002/t/release-20_connect.t000444000766000024 56313170404307 20547 0ustar00oliverstaff000000000000#!/usr/bin/perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use strict; use warnings FATAL => 'all'; use Test::More 0.88; use Net::CLI::Interact; my $s = Net::CLI::Interact->new({ transport => 'Loopback', }); ok($s->transport->init); done_testing; Net-CLI-Interact-2.300002/t/release-30_phrasebook.t000444000766000024 314213170404307 21270 0ustar00oliverstaff000000000000#!/usr/bin/perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use strict; use warnings FATAL => 'all'; use Test::More 0.88; use Net::CLI::Interact; my $s = new_ok('Net::CLI::Interact' => [{ transport => 'Loopback', personality => 'testing', add_library => 't/phrasebook', }]); my $pb = $s->phrasebook; ok(eval { $pb->prompt('TEST_PROMPT_ONE') }, 'prompt exists'); ok(! eval { $pb->prompt('TEST_PROMPT_XXX') }, 'prompt does not exist'); my $p = $pb->prompt('TEST_PROMPT_ONE'); isa_ok($p, 'Net::CLI::Interact::ActionSet'); ok(eval { $pb->macro('TEST_MACRO_ONE') }, 'macro exists'); ok(! eval { $pb->macro('TEST_MACRO_XXX') }, 'macro does not exist'); my $m = $pb->macro('TEST_MACRO_ONE'); isa_ok($m, 'Net::CLI::Interact::ActionSet'); ok($s->set_phrasebook({ personality => 'fwsm3' }), 'new phrasebook loaded'); $pb = $s->phrasebook; ok(eval { $pb->prompt('generic') }, 'prompt exists'); ok(! eval { $pb->prompt('generic_XXX') }, 'prompt does not exist'); my $p2 = $pb->prompt('privileged'); isa_ok($p2, 'Net::CLI::Interact::ActionSet'); ok(eval { $pb->macro('begin_privileged') }, 'macro exists'); ok(! eval { $pb->macro('begin_privileged_XXX') }, 'macro does not exist'); my $m2 = $pb->macro('end_privileged'); isa_ok($m2, 'Net::CLI::Interact::ActionSet'); ok($s->set_phrasebook({ personality => 'blah' }), 'new phrasebook loaded'); $pb = $s->phrasebook; ok(eval { $pb->prompt('blahblah') }, 'local prompt exists'); ok(eval { $pb->prompt('err_string') }, 'remote prompt exists'); done_testing; Net-CLI-Interact-2.300002/t/release-31_actionset.t000444000766000024 240513170404307 21126 0ustar00oliverstaff000000000000#!/usr/bin/perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use strict; use warnings FATAL => 'all'; use Test::More 0.88; use Net::CLI::Interact; my $s = Net::CLI::Interact->new({ transport => 'Loopback', personality => 'testing', add_library => 't/phrasebook', }); my $pb = $s->phrasebook; my $p = $pb->prompt('TEST_PROMPT_ONE'); ok($p->clone, 'clone method'); my $p2 = $p->clone; isa_ok($p2, 'Net::CLI::Interact::ActionSet'); ok($p2->current_match(qr//), 'current_match method'); ok($p2->register_callback(sub{}), 'register_callback method'); ok(eval{ $p2->execute ;1}, 'execute method does not blow up'); ok(eval{ $p2->apply_params ;1}, 'empty apply_params'); my $p3 = $pb->macro('TEST_MACRO_PARAMS'); ok($p3->clone, 'clone method'); my $p4 = $p3->clone; isa_ok($p4, 'Net::CLI::Interact::ActionSet'); ok($p4->current_match(qr//), 'current_match method'); ok($p4->register_callback(sub{}), 'register_callback method'); $p4->apply_params(0, 1); ok(eval{ $p4->execute ;1}, 'execute method does not blow up'); ok(eval{ $p4->apply_params ;1}, 'empty apply_params'); ok(eval{ $p4->apply_params(qr/a b c d/) ;1}, 'method apply_params'); done_testing; Net-CLI-Interact-2.300002/t/release-32_action.t000444000766000024 220313170404307 20407 0ustar00oliverstaff000000000000#!/usr/bin/perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use strict; use warnings FATAL => 'all'; use Test::More 0.88; use Net::CLI::Interact; my $s = Net::CLI::Interact->new({ transport => 'Loopback', personality => 'testing', add_library => 't/phrasebook', }); my $pb = $s->phrasebook; my $m = $pb->macro('TEST_PROMPT_TWO'); ok($m->clone, 'clone method'); my $m2 = $m->clone; isa_ok($m2, 'Net::CLI::Interact::ActionSet'); isa_ok($m2->item_at(-1), 'Net::CLI::Interact::Action'); my $a = $m2->item_at(-1); is($a->type, 'match', 'is a match'); is($a->value->[0] .'', qr/TEST_PROMPT_TWO$/, 'regexp matches'); is($a->num_params, 0, 'no params'); my $m3 = $pb->macro('TEST_MACRO_PARAMS'); my $a2 = $m3->item_at(-1); is($a2->type, 'match', 'is a match'); is($a2->value->[0] .'', qr/^.+$/, 'regexp matches'); is($a2->num_params, 0, 'no params'); my $a3 = $m3->item_at(-2); is($a3->type, 'send', 'is a send'); is($a3->value, 'param %s param %s', 'send value matches'); is($a3->num_params, 2, 'two params'); done_testing; Net-CLI-Interact-2.300002/t/release-40_transport.t000444000766000024 133313170404307 21170 0ustar00oliverstaff000000000000#!/usr/bin/perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use strict; use warnings FATAL => 'all'; use Test::More 0.88; use Net::CLI::Interact; my $s = new_ok('Net::CLI::Interact' => [{ transport => 'Loopback', personality => 'testing', add_library => 't/phrasebook', }]); $s->set_prompt('TEST_PROMPT'); my $out = $s->cmd('TEST COMMAND'); like($out, qr/^\d{10}$/, 'sent data and command response issued'); ok(eval{$s->transport->disconnect;1}, 'transport reinitialized'); my $out2 = $s->cmd('TEST COMMAND'); like($out2, qr/^\d{10}$/, 'more sent data and command response issued'); done_testing; Net-CLI-Interact-2.300002/t/release-50_cmd.t000444000766000024 261713170404307 17706 0ustar00oliverstaff000000000000#!/usr/bin/perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use strict; use warnings FATAL => 'all'; use Test::More 0.88; use Net::CLI::Interact; my $s = new_ok('Net::CLI::Interact' => [{ transport => 'Loopback', personality => 'testing', add_library => 't/phrasebook', }]); $s->set_prompt('TEST_PROMPT_TWO'); # wrong! ok(! eval { $s->cmd('TEST COMMAND', {timeout => 0} ) }, 'timeout of zero not accepted'); ok(! eval { $s->cmd('TEST COMMAND', {timeout => 1} ) }, 'wrong prompt causes timeout'); # need to reinit the connection ok(eval{$s->transport->disconnect;1}, 'transport reinitialized'); my $out = $s->cmd('TEST COMMAND', {match => ['TEST_PROMPT']}); like($out, qr/^\d{10}$/, 'sent data with named custom match'); my $out2 = $s->cmd('TEST COMMAND', {match => [qr/PROMPT>/]}); like($out2, qr/^\d{10}$/, 'sent data with regexp custom match'); my $outa = $s->cmd('TEST COMMAND', {match => 'TEST_PROMPT'}); like($outa, qr/^\d{10}$/, 'sent data with named custom match, coerced'); my $out2a = $s->cmd('TEST COMMAND', {match => qr/PROMPT>/}); like($out2a, qr/^\d{10}$/, 'sent data with regexp custom match, coerced'); my $out3 = $s->cmd('TEST COMMAND', {match => [qr/PROMPT>/, qr/ANOTHER PROMPT>/]}); like($out3, qr/^\d{10}$/, 'sent data with two regexp custom matches'); done_testing; Net-CLI-Interact-2.300002/t/release-60_prompt.t000444000766000024 133613170404307 20462 0ustar00oliverstaff000000000000#!/usr/bin/perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use strict; use warnings FATAL => 'all'; use Test::More 0.88; use Net::CLI::Interact; my $s = new_ok('Net::CLI::Interact' => [{ transport => 'Loopback', personality => 'testing', add_library => 't/phrasebook', }]); ok($s->set_prompt('TEST_PROMPT'), 'set the prompt'); my $out = $s->cmd('TEST COMMAND'); like($out, qr/^\d{10}$/, 'sent data and command response issued'); ok($s->prompt_looks_like('TEST_PROMPT'), 'prompt looks like set prompt'); ok(! $s->prompt_looks_like('TEST_PROMPT_TWO'), 'prompt does not look like other'); done_testing; Net-CLI-Interact-2.300002/t/phrasebook000755000766000024 013170404307 17026 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/t/phrasebook/cisco000755000766000024 013170404307 20126 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/t/phrasebook/cisco/pixos000755000766000024 013170404307 21270 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/t/phrasebook/cisco/pixos/pixos7000755000766000024 013170404307 22521 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/t/phrasebook/cisco/pixos/pixos7/blah000755000766000024 013170404307 23427 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/t/phrasebook/cisco/pixos/pixos7/blah/pb000444000766000024 5213170404307 24045 0ustar00oliverstaff000000000000prompt blahblah match /this and that/ Net-CLI-Interact-2.300002/t/phrasebook/testing000755000766000024 013170404307 20503 5ustar00oliverstaff000000000000Net-CLI-Interact-2.300002/t/phrasebook/testing/phrases000444000766000024 101113170404307 22221 0ustar00oliverstaff000000000000prompt TEST_PROMPT_ONE match /TEST_PROMPT_ONE$/ prompt TEST_PROMPT_TWO match /TEST_PROMPT_TWO$/ prompt TEST_PROMPT match /PROMPT>/ macro TEST_MACRO_ONE send TEST_MACRO_ONE match /TEST_MACRO_ONE/ macro TEST_MACRO_TWO send TEST_MACRO_TWO match /TEST_MACRO_TWO/ macro TEST_PROMPT_ONE send TEST_PROMPT_ONE macro TEST_PROMPT_TWO send TEST_PROMPT_TWO match TEST_PROMPT_TWO macro TEST_MACRO_PARAMS send param %s param %s match MATCH_ANY prompt MATCH_ANY match /^.+$/