Net-CLI-Interact-2.133420000755001750001750 012250734127 14640 5ustar00oliveroliver000000000000README100644001750001750 1604112250734127 15623 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420NAME Net::CLI::Interact - Toolkit for CLI Automation VERSION version 2.133420 PURPOSE This module exists to support developers of applications and libraries which must interact with a command line interface. 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; 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: * SSH, Telnet and Serial-Line connections * Unix and Windows support * Reuseable device command phrasebooks If you're a new user, please read the Tutorial. There's also a Cookbook and a Phrasebook Listing. For a more complete worked example check out the Net::Appliance::Session distribution, for which this module was written. INTERFACE new( \%options ) Prepares a new session for you, but will not connect to any device. On Windows platforms, you must download the "plink.exe" program, and pass its location to the "app" parameter. Other options are: "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 Net::CLI::Interact::Manual::Phrasebook for further details. "transport => $backend" (required) The name of the transport backend used for the session, which may be one of Telnet, SSH, or Serial. "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. "log_at => $log_level" To make using the "logger" somewhat easier, you can pass this argument the name of a log *level* (such as "debug", "info", etc) and all logging in the library will be enabled at that level. Use "debug" to learn about how the library is working internally. See Net::CLI::Interact::Logger for a list of the valid level names. "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. cmd( $command ) Execute a single command statement on the connected device, and consume output until there is a match with the current *prompt*. The statement is executed verbatim on the device, with a newline appended. In scalar context the "last_response" 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. 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 %options hash reference. In scalar context the "last_response" 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. last_response Returns the gathered output after the most recent "cmd" or "macro". 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. transport Returns the Transport backend which was loaded based on the "transport" option to "new". See the Telnet, SSH, or Serial documentation for further details. phrasebook Returns the Phrasebook object which was loaded based on the "personality" option given to "new". See Net::CLI::Interact::Phrasebook for further details. set_phrasebook( \%options ) Allows you to (re-)configure the loaded phrasebook, perhaps changing the personality or library, or other properties. The %options Hash ref should be any parameters from the Phrasebook module, but at a minimum must include a "personality". set_default_contination( $macro_name ) Briefly, a Continuation handles the slurping of paged output from commands. See the Net::CLI::Interact::Phrasebook 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 "clear_default_continuation" method. logger This is the application's Logger object. A powerful logging subsystem is available to your application, built upon the Log::Dispatch distribution. You can enable logging of this module's processes at various levels, or add your own logging statements. set_global_log_at( $level ) To make using the "logger" somewhat easier, you can pass this method the name of a log *level* (such as "debug", "info", etc) and all logging in the library will be enabled at that level. Use "debug" to learn about how the library is working internally. See Net::CLI::Interact::Logger for a list of the valid level names. FUTHER READING 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 Net::CLI::Interact::Phrasebook dictionaries, and methods of the Net::CLI::Interact::Role::Prompt core component. See that module's documentation for further details. Actions and ActionSets All commands and macros are composed from their phrasebook definitions into Actions and ActionSets (iterable sequences of Actions). See those modules' documentation for further details, in case you wish to introspect their structures. COMPOSITION See the following for further interface details: * Net::CLI::Interact::Role::Engine AUTHOR Oliver Gorwits COPYRIGHT AND LICENSE This software is copyright (c) 2013 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. Changes100644001750001750 1341712250734127 16242 0ustar00oliveroliver000000000000Net-CLI-Interact-2.1334202.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. LICENSE100644001750001750 4370612250734127 15760 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420This software is copyright (c) 2013 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. Terms of the Perl programming language system itself a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" --- The GNU General Public License, Version 1, February 1989 --- This software is Copyright (c) 2013 by Oliver Gorwits. This is free software, licensed under: The GNU General Public License, Version 1, February 1989 GNU GENERAL PUBLIC LICENSE Version 1, February 1989 Copyright (C) 1989 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The license agreements of most software companies try to keep users at the mercy of those companies. By contrast, our General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. The General Public License applies to the Free Software Foundation's software and to any other program whose authors commit to using it. You can use it for your programs, too. When we speak of free software, we are referring to freedom, not price. Specifically, the General Public License is designed to make sure that you have the freedom to give away or sell copies of free software, that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of a such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any work containing the Program or a portion of it, either verbatim or with modifications. Each licensee is addressed as "you". 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this General Public License and to the absence of any warranty; and give any other recipients of the Program a copy of this General Public License along with the Program. You may charge a fee for the physical act of transferring a copy. 2. You may modify your copy or copies of the Program or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains the Program or any part thereof, either with or without modifications, to be licensed at no charge to all third parties under the terms of this General Public License (except that you may choose to grant warranty protection to some or all third parties, at your option). c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the simplest and most usual way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this General Public License. d) You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another independent work with the Program (or its derivative) on a volume of a storage or distribution medium does not bring the other work under the scope of these terms. 3. You may copy and distribute the Program (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal charge for the cost of distribution) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) Source code for a work means the preferred form of the work for making modifications to it. For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs, or for standard header files or definitions files that accompany that operating system. 4. You may not copy, modify, sublicense, distribute or transfer the Program except as expressly provided under this General Public License. Any attempt otherwise to copy, modify, sublicense, distribute or transfer the Program is void, and will automatically terminate your rights to use the Program under this License. However, parties who have received copies, or rights to use copies, from you under this General Public License will not have their licenses terminated so long as such parties remain in full compliance. 5. By copying, distributing or modifying the Program (or any work based on the Program) you indicate your acceptance of this license to do so, and all its terms and conditions. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. 7. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of the license which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the license, you may choose any version ever published by the Free Software Foundation. 8. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to humanity, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19xx name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (a program to direct compilers to make passes at assemblers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice That's all there is to it! --- The Artistic License 1.0 --- This software is Copyright (c) 2013 by Oliver Gorwits. This is free software, licensed under: The Artistic License 1.0 The Artistic License Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: - "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. - "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder. - "Copyright Holder" is whoever is named in the copyright or copyrights for the package. - "You" is you, if you're thinking about copying or distributing this Package. - "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) - "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as ftp.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) accompany any non-standard executables with their corresponding Standard Version executables, giving the non-standard executables non-standard names, and clearly documenting the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this Package. 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of this Package. 8. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End META.yml100644001750001750 170512250734127 16175 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420--- abstract: 'Toolkit for CLI Automation' author: - 'Oliver Gorwits ' build_requires: Test::More: 0.88 warnings: 0 configure_requires: ExtUtils::MakeMaker: 6.30 dynamic_config: 0 generated_by: 'Dist::Zilla version 4.300024, CPAN::Meta::Converter version 2.120921' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: Net-CLI-Interact requires: Class::Load: 0 Class::Mix: 0 File::Basename: 0 FileHandle: 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 strict: 0 resources: bugtracker: https://rt.cpan.org/Public/Dist/Display.html?Name=Net-CLI-Interact homepage: https://github.com/ollyg/Net-CLI-Interact/wiki repository: git://github.com/ollyg/Net-CLI-Interact.git version: 2.133420 MANIFEST100644001750001750 377412250734127 16065 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420Changes LICENSE MANIFEST META.json META.yml Makefile.PL README 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/Platform/Unix.pm lib/Net/CLI/Interact/Transport/Platform/Win32.pm lib/Net/CLI/Interact/Transport/Role/ConnectCore.pm lib/Net/CLI/Interact/Transport/SSH.pm lib/Net/CLI/Interact/Transport/Serial.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 lib/Net/CLI/Interact/phrasebook/cisco/catos/pb lib/Net/CLI/Interact/phrasebook/cisco/extremexos/pb lib/Net/CLI/Interact/phrasebook/cisco/foundry/pb lib/Net/CLI/Interact/phrasebook/cisco/hp/pb lib/Net/CLI/Interact/phrasebook/cisco/ios/pb lib/Net/CLI/Interact/phrasebook/cisco/junos/pb lib/Net/CLI/Interact/phrasebook/cisco/nortel/pb lib/Net/CLI/Interact/phrasebook/cisco/pb lib/Net/CLI/Interact/phrasebook/cisco/pixos/fwsm/fwsm3/pb lib/Net/CLI/Interact/phrasebook/cisco/pixos/pb lib/Net/CLI/Interact/phrasebook/cisco/pixos/pixos7/pb lib/Net/CLI/Interact/phrasebook/unix/bash/pb lib/Net/CLI/Interact/phrasebook/unix/csh/pb lib/Net/CLI/Interact/phrasebook/unix/csh/sdf/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 META.json100644001750001750 334712250734127 16351 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420{ "abstract" : "Toolkit for CLI Automation", "author" : [ "Oliver Gorwits " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 4.300024, CPAN::Meta::Converter version 2.120921", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "Net-CLI-Interact", "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "6.30" } }, "runtime" : { "requires" : { "Class::Load" : "0", "Class::Mix" : "0", "File::Basename" : "0", "FileHandle" : "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", "strict" : "0" } }, "test" : { "requires" : { "Test::More" : "0.88", "warnings" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://rt.cpan.org/Public/Dist/Display.html?Name=Net-CLI-Interact" }, "homepage" : "https://github.com/ollyg/Net-CLI-Interact/wiki", "repository" : { "type" : "git", "url" : "git://github.com/ollyg/Net-CLI-Interact.git", "web" : "https://github.com/ollyg/Net-CLI-Interact" } }, "version" : "2.133420" } Makefile.PL100644001750001750 335412250734127 16700 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420use strict; use warnings; use ExtUtils::MakeMaker 6.30; my %WriteMakefileArgs = ( 'ABSTRACT' => 'Toolkit for CLI Automation', 'AUTHOR' => 'Oliver Gorwits ', 'BUILD_REQUIRES' => { 'Test::More' => '0.88', 'warnings' => '0' }, 'CONFIGURE_REQUIRES' => { 'ExtUtils::MakeMaker' => '6.30' }, 'DISTNAME' => 'Net-CLI-Interact', 'LICENSE' => 'perl', 'NAME' => 'Net::CLI::Interact', 'PREREQ_PM' => { 'Class::Load' => '0', 'Class::Mix' => '0', 'File::Basename' => '0', 'FileHandle' => '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', 'strict' => '0' }, 'VERSION' => '2.133420', 'test' => { 'TESTS' => 't/*.t' }, ); unless ( eval { ExtUtils::MakeMaker->VERSION(6.56) } ) { my $br = delete $WriteMakefileArgs{BUILD_REQUIRES}; my $pp = $WriteMakefileArgs{PREREQ_PM}; for my $mod ( keys %$br ) { if ( exists $pp->{$mod} ) { $pp->{$mod} = $br->{$mod} if $br->{$mod} > $pp->{$mod}; } else { $pp->{$mod} = $br->{$mod}; } } } delete $WriteMakefileArgs{CONFIGURE_REQUIRES} unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; if ( $^O =~ m/^(?!Win32)/ ) { $WriteMakefileArgs{CONFIGURE_REQUIRES}{'ExtUtils::CBuilder'} = 0; eval 'require ExtUtils::CBuilder;'; if (not $@) { my $builder = ExtUtils::CBuilder->new( quiet => 1 ); if ($builder->have_compiler) { $WriteMakefileArgs{PREREQ_PM}{'IO::Pty'} = '0'; } } } WriteMakefile(%WriteMakefileArgs); t000755001750001750 012250734127 15024 5ustar00oliveroliver000000000000Net-CLI-Interact-2.13342010_construct.t100644001750001750 42212250734127 17653 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/t#!/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; release-50_cmd.t100644001750001750 261712250734127 20044 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/t#!/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; release-32_action.t100644001750001750 220312250734127 20545 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/t#!/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; release-60_prompt.t100644001750001750 133612250734127 20620 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/t#!/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; release-20_connect.t100644001750001750 56312250734127 20705 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/t#!/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; CLI000755001750001750 012250734127 16464 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/NetInteract.pm100644001750001750 2360412250734127 20760 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLIpackage Net::CLI::Interact; { $Net::CLI::Interact::VERSION = '2.133420'; } 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; # ABSTRACT: Toolkit for CLI Automation __END__ =pod =head1 NAME Net::CLI::Interact - Toolkit for CLI Automation =head1 VERSION version 2.133420 =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) 2013 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 author-20_sdf_shell.t100644001750001750 170212250734127 21115 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/t#!/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; release-40_transport.t100644001750001750 133312250734127 21326 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/t#!/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; release-31_actionset.t100644001750001750 240512250734127 21264 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/t#!/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; release-30_phrasebook.t100644001750001750 313612250734127 21431 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/t#!/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('basic') }, 'prompt exists'); ok(! eval { $pb->prompt('basic_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; author-13_ssh_timeout.t100644001750001750 125212250734127 21517 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/t#!/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; author-12_ssh_no_route.t100644001750001750 141012250734127 21656 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/t#!/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; author-10_route_server.t100644001750001750 145212250734127 21677 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/t#!/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; testing000755001750001750 012250734127 20636 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/t/phrasebookphrases100644001750001750 101112250734127 22357 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/t/phrasebook/testingprompt 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 /^.+$/ Interact000755001750001750 012250734127 20235 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLIAction.pm100644001750001750 1266412250734127 22201 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interactpackage Net::CLI::Interact::Action; { $Net::CLI::Interact::Action::VERSION = '2.133420'; } 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. =head1 AUTHOR Oliver Gorwits =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 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 Logger.pm100644001750001750 1714612250734127 22203 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interactpackage Net::CLI::Interact::Logger; { $Net::CLI::Interact::Logger::VERSION = '2.133420'; } 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; # ABSTRACT: Per-instance multi-target logging, with categories __END__ =pod =head1 NAME Net::CLI::Interact::Logger - Per-instance multi-target logging, with categories =head1 VERSION version 2.133420 =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. =head1 AUTHOR Oliver Gorwits =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 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 author-11_ssh_unknown_host.t100644001750001750 116212250734127 22563 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/t#!/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; ActionSet.pm100644001750001750 2074112250734127 22650 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interactpackage Net::CLI::Interact::ActionSet; { $Net::CLI::Interact::ActionSet::VERSION = '2.133420'; } 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; # ABSTRACT: Conversation of Send and Match Actions __END__ =pod =head1 NAME Net::CLI::Interact::ActionSet - Conversation of Send and Match Actions =head1 VERSION version 2.133420 =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 =head1 AUTHOR Oliver Gorwits =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 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 Phrasebook.pm100644001750001750 4264412250734127 23062 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interactpackage Net::CLI::Interact::Phrasebook; { $Net::CLI::Interact::Phrasebook::VERSION = '2.133420'; } use Moo; use MooX::Types::MooseLike::Base qw(InstanceOf Str Any HashRef); use Path::Class; 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 { use File::Basename; my (undef, $directory, undef) = fileparse( $INC{ 'Net/CLI/Interact.pm' } ); return [ Path::Class::Dir->new($directory) ->subdir("Interact", "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; # ABSTRACT: Load command phrasebooks from a Library __END__ =pod =head1 NAME Net::CLI::Interact::Phrasebook - Load command phrasebooks from a Library =head1 VERSION version 2.133420 =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 issueing 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 =head1 AUTHOR Oliver Gorwits =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 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 Role000755001750001750 012250734127 21136 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/InteractPrompt.pm100644001750001750 2137612250734127 23146 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/Rolepackage Net::CLI::Interact::Role::Prompt; { $Net::CLI::Interact::Role::Prompt::VERSION = '2.133420'; } 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; eval { 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?'); } }; 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; # ABSTRACT: Command-line prompt management __END__ =pod =head1 NAME Net::CLI::Interact::Role::Prompt - Command-line prompt management =head1 VERSION version 2.133420 =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. =head1 AUTHOR Oliver Gorwits =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 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 Engine.pm100644001750001750 2351612250734127 23070 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/Rolepackage Net::CLI::Interact::Role::Engine; { $Net::CLI::Interact::Role::Engine::VERSION = '2.133420'; } { 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')) { 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')) { $self->logger->log('object', 'debug', Data::Printer::p($new)); } } 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; return (wantarray ? (map {$_ .= "\n"} split m/\n/, $resp) : $resp); } 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; # ABSTRACT: Statement execution engine __END__ =pod =head1 NAME Net::CLI::Interact::Role::Engine - Statement execution engine =head1 VERSION version 2.133420 =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 =head1 AUTHOR Oliver Gorwits =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 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 Iterator.pm100644001750001750 1073612250734127 23454 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/Rolepackage Net::CLI::Interact::Role::Iterator; { $Net::CLI::Interact::Role::Iterator::VERSION = '2.133420'; } 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; # ABSTRACT: Array-based Iterator __END__ =pod =head1 NAME Net::CLI::Interact::Role::Iterator - Array-based Iterator =head1 VERSION version 2.133420 =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. =head1 AUTHOR Oliver Gorwits =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 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 Transport000755001750001750 012250734127 22231 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/InteractSSH.pm100644001750001750 1235712250734127 23414 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/Transportpackage Net::CLI::Interact::Transport::SSH; { $Net::CLI::Interact::Transport::SSH::VERSION = '2.133420'; } 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; # ABSTRACT: SSH based CLI connection __END__ =pod =head1 NAME Net::CLI::Interact::Transport::SSH - SSH based CLI connection =head1 VERSION version 2.133420 =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 =head1 AUTHOR Oliver Gorwits =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 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 FindMatch.pm100644001750001750 74512250734127 23457 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/Rolepackage Net::CLI::Interact::Role::FindMatch; { $Net::CLI::Interact::Role::FindMatch::VERSION = '2.133420'; } 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; Base.pm100644001750001750 1412612250734127 23625 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/Transportpackage Net::CLI::Interact::Transport::Base; { $Net::CLI::Interact::Transport::Base::VERSION = '2.133420'; } use Moo; use MooX::Types::MooseLike::Base qw(InstanceOf); 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; # ABSTRACT: Spawns an Interactive CLI Session __END__ =pod =head1 NAME Net::CLI::Interact::Transport::Base - Spawns an Interactive CLI Session =head1 VERSION version 2.133420 =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 reponse 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. =head1 AUTHOR Oliver Gorwits =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 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 blah000755001750001750 012250734127 23562 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/t/phrasebook/cisco/pixos/pixos7pb100644001750001750 5212250734127 24203 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/t/phrasebook/cisco/pixos/pixos7/blahprompt blahblah match /this and that/ Manual000755001750001750 012250734127 21452 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/InteractTutorial.pod100644001750001750 1565112250734127 24151 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/Manual=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 Cookbook.pod100644001750001750 2034012250734127 24103 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/Manual=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 diffrent 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 cisco000755001750001750 012250734127 23472 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebookpb100644001750001750 132412250734127 24156 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/ciscoprompt generic match /[\/a-zA-Z0-9._\[\]-]+ ?(?:\(config[^)]*\))? ?[#>] ?$/ prompt basic match /> ?$/ 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 basic macro begin_configure send configure terminal match configure macro end_configure send exit match privileged macro disconnect send exit match generic # macro completion # send ? # legacy support prompt prompt match /[\/a-zA-Z0-9._\[\]-]+ ?(?:\(config[^)]*\))? ?[#>] ?$/ Telnet.pm100644001750001750 1023112250734127 24177 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/Transportpackage Net::CLI::Interact::Transport::Telnet; { $Net::CLI::Interact::Transport::Telnet::VERSION = '2.133420'; } 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; # ABSTRACT: TELNET based CLI connection __END__ =pod =head1 NAME Net::CLI::Interact::Transport::Telnet - TELNET based CLI connection =head1 VERSION version 2.133420 =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 =head1 AUTHOR Oliver Gorwits =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 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 Serial.pm100644001750001750 1051112250734127 24164 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/Transportpackage Net::CLI::Interact::Transport::Serial; { $Net::CLI::Interact::Transport::Serial::VERSION = '2.133420'; } 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; # ABSTRACT: Serial-line based CLI connection __END__ =pod =head1 NAME Net::CLI::Interact::Transport::Serial - Serial-line based CLI connection =head1 VERSION version 2.133420 =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 =head1 AUTHOR Oliver Gorwits =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 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 Phrasebook.pod100644001750001750 1616012250734127 24437 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/Manual=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. 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. Below is a list of all current bundled CLI dictionaries. Each lists its name, the available Prompts, Macros and Continuations, and from which other CLI dictionaries it inherits. IOS # Cisco IOS CatOS # for older, pre-IOS Cisco devices PIXOS # for PIX OS-based devices PIXOS7 # Slightly different commands from other PIXOS versions FWSM # currently the same as 'PIXOS' FWSM3 # for FWSM Release 3.x devices (slightly different to FWSM 2.x) JunOS # Juniper JUNOS support HP # HP support Nortel # Nortel support ExtremeOS # Extreme Networks support Foundry # Foundry/Brocade device support Bash # GNU Bourne-Again SHell (i.e. most linux systems) =head1 PERSONALITIES =head2 Bash This personality goes by the name of C. Prompts are C, C, C and C. The C prompt works either for an initial SSH connection, or a C request. Macros are C, C, and C. The C macro issues a C request to become the root user. =head2 Cisco This personality goes by the name of C and provides a basis for many other CLI dictionaries. Prompts are C, C, C, C, and C. Macros are C, C, C, C, and C. =head2 CatOS This personality goes by the name of C and inherits from the C dictionary. Additionally it provides the C Prompt. Additionally it also provides the C Macro to set the terminal page size. =head2 ExtremeOS This personality goes by the name of C and inherits from the C dictionary. Additional Prompts are C, C, C, C, and C. Additional Macros are C, C, and C. =head2 Foundry / Brocade This personality goes by the name of C and inherits from the C dictionary. 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"); =head2 IOS This personality goes by the name of C and inherits from the C dictionary. Additionally it provides the C Macro to set the terminal page size. =head2 HP This personality goes by the name of C and inherits from the C dictionary. Additionally it provides the C and C Prompts. Additionally it provides C and C Macros. =head2 JunOS This personality goes by the name of C and inherits from the C dictionary. Additionally it provides the C, C, and C Prompts. Additionally it also provides the C and C Macros. =head2 Nortel This personality goes by the name of C and inherits from the C dictionary. Additionally it provides the C Prompt. =head2 PIXOS This personality goes by the name of C and inherits from the C dictionary. Additionally it provides the C Macro to set the terminal page size. It can be used in its own right for Cisco PIX firewalls, but is also used as a base for other dictionaries. =head2 PIXOS 7 This personality goes by the name of C and inherits from the C dictionary. Additionally it provides the C Macro to set the terminal page size. =head2 FWSM This personality goes by the name of C and inherits from the C dictionary. It provides no further functionality, as Cisco FWSM software version 1 and 2 was the same as the PIX OS. =head2 FWSM 3 This personality goes by the name of C and inherits from the C dictionary. Additionally it provides the C Macro to set the terminal page size. =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 Loopback.pm100644001750001750 443312250734127 24465 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/Transportpackage Net::CLI::Interact::Transport::Loopback; { $Net::CLI::Interact::Transport::Loopback::VERSION = '2.133420'; } 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; # ABSTRACT: Testable CLI connection __END__ =pod =head1 NAME Net::CLI::Interact::Transport::Loopback - Testable CLI connection =head1 VERSION version 2.133420 =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 =head1 AUTHOR Oliver Gorwits =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 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 csh000755001750001750 012250734130 24124 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/unixpb100644001750001750 15112250734130 24565 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/unix/cshprompt password match /password: $/ prompt shell match /> $/ prompt root_shell match /# $/ hp000755001750001750 012250734130 24073 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/ciscopb100644001750001750 47212250734130 24542 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/cisco/hpprompt generic match /[\/a-zA-Z0-9._:-]+ ?(?:\(config[^)]*\))? ?[#>] ?$/ prompt basic match /: ?$/ prompt user match /login as: ?$/ macro enable_paging send page macro disable_paging send no page # legacy support prompt prompt match /[\/a-zA-Z0-9._:-]+ ?(?:\(config[^)]*\))? ?[#>] ?$/ bash000755001750001750 012250734130 24264 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/unixpb100644001750001750 51312250734130 24727 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/unix/bashprompt user match /[Uu]sername: $/ prompt pass match /password(?: for \w+)?: $/ prompt generic match /\w+@.+\$ $/ prompt privileged match /^root@.+# $/ macro begin_privileged send sudo su - match pass or privileged macro end_privileged send exit match generic macro disconnect send logout ios000755001750001750 012250734130 24256 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/ciscopb100644001750001750 22512250734130 24721 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/cisco/ios# prompt err_string # match /% ?(?:Error|Type "[^?]+\?"|(?:Incomplete|Unknown) command|Invalid input)/ macro paging send terminal length %s pixos000755001750001750 012250734130 24626 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/ciscopb100644001750001750 17312250734130 25273 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/cisco/pixosprompt err_string match /(?:Type help|(?:Error|ERROR|Usage|usage):|not allowed)/ macro paging send pager lines %s catos000755001750001750 012250734130 24575 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/ciscopb100644001750001750 37512250734130 25246 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/cisco/catosprompt generic match /[\/a-zA-Z0-9._-]+ ?[#>] ?(?:\(enable[^)]*\))? ?$/ prompt privileged match /> \(enable\) ?$/ macro paging send set length %s # legacy support prompt prompt match /[\/a-zA-Z0-9._-]+ ?[#>] ?(?:\(enable[^)]*\))? ?$/ junos000755001750001750 012250734130 24622 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/ciscopb100644001750001750 62412250734130 25270 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/cisco/junosprompt 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 # legacy support prompt prompt match /[\/a-zA-Z0-9._\@-]+ ?(?:\(config[^)]*\))? ?[#>] ?$/ Wrapper000755001750001750 012250734130 23643 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/TransportBase.pm100644001750001750 721712250734130 25222 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/Transport/Wrapperpackage Net::CLI::Interact::Transport::Wrapper::Base; { $Net::CLI::Interact::Transport::Wrapper::Base::VERSION = '2.133420'; } 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; sdf000755001750001750 012250734130 24700 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/unix/cshpb100644001750001750 15612250734130 25346 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/unix/csh/sdfprompt generic match /\$ $/ prompt shell match /^\$/ macro sdf_login send %s\n match shell nortel000755001750001750 012250734130 24767 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/ciscopb100644001750001750 31612250734130 25433 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/cisco/nortelprompt generic match /[\/a-zA-Z0-9._:-]+ ?(?:\(config[^)]*\))? ?[#>] ?$/ prompt user match /Login: ?$/ # legacy support prompt prompt match /[\/a-zA-Z0-9._:-]+ ?(?:\(config[^)]*\))? ?[#>] ?$/ Platform000755001750001750 012250734130 24007 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/TransportUnix.pm100644001750001750 134612250734130 25434 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/Transport/Platformpackage Net::CLI::Interact::Transport::Platform::Unix; { $Net::CLI::Interact::Transport::Platform::Unix::VERSION = '2.133420'; } 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; foundry000755001750001750 012250734130 25152 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/ciscopb100644001750001750 121512250734130 25635 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/cisco/foundry# 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 basic match /# ?$/ 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 paging send 'skip-page-display' macro disconnect send exit match generic # legacy support prompt prompt match /[\/a-zA-Z0-9._-]+ ?(?:\(config[^)]*\))? ?[#>] ?$/ Win32.pm100644001750001750 56712250734130 25377 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/Transport/Platformpackage Net::CLI::Interact::Transport::Platform::Win32; { $Net::CLI::Interact::Transport::Platform::Win32::VERSION = '2.133420'; } 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; IPC_Run.pm100644001750001750 341512250734130 25603 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/Transport/Wrapperpackage Net::CLI::Interact::Transport::Wrapper::IPC_Run; { $Net::CLI::Interact::Transport::Wrapper::IPC_Run::VERSION = '2.133420'; } 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; Role000755001750001750 012250734130 23124 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/TransportConnectCore.pm100644001750001750 607012250734130 26027 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/Transport/Rolepackage Net::CLI::Interact::Transport::Role::ConnectCore; { $Net::CLI::Interact::Transport::Role::ConnectCore::VERSION = '2.133420'; } 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 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; extremexos000755001750001750 012250734130 25667 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/ciscopb100644001750001750 112512250734130 26352 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/cisco/extremexosprompt generic match /[\/a-zA-Z0-9._-]+ ?(?:\(config[^)]*\))? ?[#>] ?$/ prompt basic match /> ?$/ prompt privileged match /# ?$/ prompt configure match /\(config[^)]*\)# ?$/ prompt user match /(?:[Ll]ogin|[Uu]sername)/ prompt pass match /[Pp]assword: ?$/ macro begin_privileged send enable match user or pass or privileged macro end_privileged send disable match privileged macro disconnect send exit match generic # macro completion # send ? # legacy support prompt prompt match /[\/a-zA-Z0-9._-]+ ?(?:\(config[^)]*\))? ?[#>] ?$/ Net_Telnet.pm100644001750001750 310612250734130 26402 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/Transport/Wrapperpackage Net::CLI::Interact::Transport::Wrapper::Net_Telnet; { $Net::CLI::Interact::Transport::Wrapper::Net_Telnet::VERSION = '2.133420'; } 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; $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; pixos7000755001750001750 012250734130 26057 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/cisco/pixospb100644001750001750 5612250734130 26504 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/cisco/pixos/pixos7macro paging send terminal pager lines %s fwsm3000755001750001750 012250734130 26641 5ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/cisco/pixos/fwsmpb100644001750001750 5512250734130 27265 0ustar00oliveroliver000000000000Net-CLI-Interact-2.133420/lib/Net/CLI/Interact/phrasebook/cisco/pixos/fwsm/fwsm3macro pager send terminal pager lines %s