File Coverage

blib/lib/Device/Network/ConfigParser.pm
Criterion Covered Total %
statement 35 72 48.6
branch 0 16 0.0
condition 0 27 0.0
subroutine 12 15 80.0
pod 1 1 100.0
total 48 131 36.6


line stmt bran cond sub pod time code
1             package Device::Network::ConfigParser;
2             # ABSTRACT: A harness for parsing network device configuration.
3             our $VERSION = '0.006'; # VERSION
4              
5              
6 1     1   45471 use 5.006;
  1         4  
7 1     1   5 use strict;
  1         1  
  1         24  
8 1     1   4 use warnings;
  1         2  
  1         24  
9 1     1   261 use Modern::Perl;
  1         6625  
  1         5  
10 1     1   569 use Getopt::Long;
  1         9291  
  1         4  
11 1     1   490 use Pod::Usage;
  1         34690  
  1         114  
12 1     1   349 use Perl6::Slurp;
  1         1098  
  1         6  
13 1     1   286 use Module::Load;
  1         802  
  1         5  
14 1     1   383 use Data::Dumper;
  1         4352  
  1         50  
15 1     1   399 use JSON;
  1         6939  
  1         4  
16 1     1   108 use Scalar::Util qw{reftype};
  1         2  
  1         41  
17              
18              
19 1     1   5 use Exporter qw{import};
  1         2  
  1         462  
20              
21             our @EXPORT_OK = qw{app};
22              
23             =head1 NAME
24              
25             Device::Network::ConfigParser - harness for parsing network configurations.
26              
27             =head1 VERSION
28              
29             version 0.006
30              
31             =head1 SYNOPSIS
32              
33             Device::Network::ConfigParser is a harness for parsing network device configuration. It exports a single subroutine - C - which takes command line arguments and runs the harness. This module is used by the C command line utility. For information on how to use the command line utility, refer to the L, or following installation type C at the command line.
34              
35             The harness supports specific parsing modules by:
36              
37             =over 4
38              
39             =item * Dynamically loading a specific parsing module based on command line arguments.
40              
41             =item * Slurping in the device configuration from STDIN or from a number of files.
42              
43             =item * Opening the required output filehandles.
44              
45             =back
46              
47             =head1 CURRENT PARSER MODULES
48              
49             =over 4
50              
51             =item L
52              
53             =item L
54              
55             =item L
56              
57             =item L
58              
59             =back
60              
61             =head1 DEVELOPING MODULES
62              
63             Parsing modules exist within the C namespace. For a I and I of device, the module is defined as C.
64              
65             The harness takes care of parsing the command line arguments, opening files (or STDIN/STDOUT) and slurping in their contents. It calls specified subroutines exported by the specified parsing module. All modules must export the following subroutines:
66              
67             =head2 get_parser
68              
69             my $module_parser = get_parser();
70              
71             This sub receives no arguments, and must return a reference to an object or subroutine that parses the configuration. This is most likely going to be a Parse::RecDescent object, but you're not limited to this.
72              
73             =head2 parse_config
74              
75             my $parsed_config = parse_config($module_parser, $device_config);
76              
77             This sub receives the reference returned by the C sub, and the full contents of a file specified on the command line. It should return a reference a data structure that represents the parsed configuration.
78              
79             =head2 post_process
80              
81             my $processed_config = post_process($parsed_config);
82              
83             This sub receives the reference to the data structure returned by C. It allows for some post-processing of the data structure. If no processing is required, it can be defined as C.
84              
85             =head2 get_output_drivers
86              
87             open($fh, ">>:encoding(UTF-8)", $output_filename);
88             my $output_drivers = get_output_drivers();
89              
90             $output_drivers->{csv}->($fh, $output_filename, $processed_config);
91              
92             This sub takes no arguments, and must return a HASHREF of subroutines used to output the parsed configurationm keyed on the command line argument. For example the sub may return:
93              
94             {
95             csv => \&csv_output_driver,
96             }
97              
98             The drivers themselves take a filehandle to write the output to (this may be STDOUT), the output filename, and the post-processed configuration.
99              
100             The driver called is based on the C<--output csv> as a command line argument
101              
102             There are two default drivers:
103              
104             =over 4
105              
106             =item * the 'raw' driver, which uses Data::Dumper to serialise the structure, and
107              
108             =item * the 'json' driver, which encodes the data structure as JSON.
109              
110             =back
111              
112             A module may return their own 'raw' or 'json' drivers which override these defaults.
113              
114             =head1 SUBROUTINES
115              
116             =head2 app
117              
118             The C subroutine in general takes C<@ARGV> (although it could be any list) and runs the harness.
119              
120             =cut
121              
122             sub app {
123 0     0 1   local @ARGV = @_;
124 0           my %args;
125              
126             GetOptions(
127             "vendor=s" => \$args{vendor},
128             "type=s" => \$args{type},
129             "format=s" => \$args{format},
130             "output=s" => \$args{output}
131 0 0         ) or pod2usage(2);
132              
133             # Set the defaults
134 0   0       $args{vendor} //= 'Cisco';
135 0   0       $args{type} //= 'ASA';
136 0   0       $args{format} //= 'raw';
137 0   0       $args{output} //= '-'; # STDOUT
138              
139              
140             # Load the module specified by the command line parameters
141 0           my $parser_module_name = "Device::Network::ConfigParser::$args{vendor}::$args{type}";
142 0           load $parser_module_name, qw{get_parser get_output_drivers parse_config post_process};
143              
144             # Check the exports
145 0 0 0       if (!defined &get_parser ||
      0        
      0        
146             !defined &get_output_drivers ||
147             !defined &parse_config ||
148             !defined &post_process) {
149 0           die "$parser_module_name does not export all required subroutines\n";
150             }
151              
152             # Retrieve the parser and the output drivers from the module. If 'raw' doesn't exist,
153             # it's populated with the default sub from this package.
154             # The active driver is then selected for use.
155 0           my $parser = get_parser();
156 0           my $output_drivers = get_output_drivers();
157 0   0       $output_drivers->{raw} //= \&_default_raw_output_driver;
158 0   0       $output_drivers->{json} //= \&_default_json_output_driver;
159 0           my $active_output_driver = $output_drivers->{ $args{format} };
160              
161 0 0 0       if (!defined $active_output_driver || reftype $active_output_driver ne 'CODE' ) {
162 0           die "'$args{format}' is not a valid output driver for $parser_module_name\n";
163             }
164              
165             # If there are no files specified, we slurp from STDIN
166 0 0         push @ARGV, \*STDIN if !@ARGV;
167              
168 0           for my $config_filename (@ARGV) {
169             # Read in the configuration
170 0           my $raw_config = slurp $config_filename;
171              
172             # Change the name to something sensible so it isn't stringified to something like 'GLOB(0x17c7350)'
173 0 0 0       $config_filename = 'STDIN' if reftype($config_filename) && reftype($config_filename) eq 'GLOB';
174              
175             # Call the parser imported from the module.
176 0           my $parsed_config = parse_config($parser, $raw_config);
177              
178             # Perform any post-processing.
179 0           my $post_processed_config = post_process($parsed_config);
180              
181             # Open the file, which could be STDOUT
182 0           my $fh;
183 0 0         if ($args{output} eq '-') {
184 0           local *STDOUT;
185 0           $fh = \*STDOUT;
186             } else {
187             # Replace the placeholder with the filename
188 0           my ($outfile) = $args{output} =~ s{%in_file%}{$config_filename}rxms;
189 0 0         open($fh, ">>:encoding(UTF-8)", $outfile) or die "Unable to open output file '$outfile': $!\n";
190             }
191              
192             # Call the output driver.
193 0           $active_output_driver->($fh, $config_filename, $post_processed_config);
194              
195 0 0         close($fh) unless $args{output} eq '-';
196             }
197              
198 0           return 0;
199             }
200              
201              
202             sub _default_raw_output_driver {
203 0     0     my ($fh, $filename, $parsed_config) = @_;
204 0           print $fh Dumper($parsed_config);
205             }
206              
207             sub _default_json_output_driver {
208 0     0     my ($fh, $filename, $parsed_config) = @_;
209              
210 0           print encode_json($parsed_config);
211             }
212              
213             =head1 AUTHOR
214              
215             Greg Foletta, C<< >>
216              
217             =head1 BUGS
218              
219             Please report any bugs or feature requests to C, or through
220             the web interface at L. I will be notified, and then you'll
221             automatically be notified of progress on your bug as I make changes.
222              
223              
224              
225              
226             =head1 SUPPORT
227              
228             You can find documentation for this module with the perldoc command.
229              
230             perldoc Device::Network::ConfigParser
231              
232              
233             You can also look for information at:
234              
235             =over 4
236              
237             =item * RT: CPAN's request tracker (report bugs here)
238              
239             L
240              
241             =item * AnnoCPAN: Annotated CPAN documentation
242              
243             L
244              
245             =item * CPAN Ratings
246              
247             L
248              
249             =item * Search CPAN
250              
251             L
252              
253             =back
254              
255              
256             =head1 ACKNOWLEDGEMENTS
257              
258              
259             =head1 LICENSE AND COPYRIGHT
260              
261             Copyright 2017 Greg Foletta.
262              
263             This program is free software; you can redistribute it and/or modify it
264             under the terms of either: the GNU General Public License as published
265             by the Free Software Foundation; or the Artistic License.
266              
267             See L for more information.
268              
269              
270             =cut
271              
272             1; # End of Device::Network::ConfigParser