File Coverage

blib/lib/Device/Network/ConfigParser/CheckPoint/Gaia.pm
Criterion Covered Total %
statement 44 82 53.6
branch 1 10 10.0
condition n/a
subroutine 11 17 64.7
pod 6 6 100.0
total 62 115 53.9


line stmt bran cond sub pod time code
1             package Device::Network::ConfigParser::CheckPoint::Gaia;
2             # ABSTRACT: Parse CheckPoint Configuration
3              
4 1     1   1676 use 5.006;
  1         5  
5 1     1   7 use strict;
  1         2  
  1         27  
6 1     1   6 use warnings;
  1         2  
  1         29  
7 1     1   351 use Modern::Perl;
  1         8203  
  1         8  
8 1     1   1159 use Parse::RecDescent;
  1         32457  
  1         10  
9 1     1   549 use Data::Dumper;
  1         6325  
  1         82  
10 1     1   487 use JSON;
  1         7243  
  1         8  
11              
12 1     1   160 use Exporter qw{import};
  1         2  
  1         1006  
13              
14             our @EXPORT_OK = qw{get_parser get_output_drivers parse_config post_process};
15              
16              
17             sub get_parser {
18 1     1 1 607 return new Parse::RecDescent(q{
19            
20             startrule: config_line(s) { $item[1] }
21             config_line:
22             aaa { $item[1] } |
23             arp { $item[1] } |
24             bonding { $item[1] } |
25             clienv { $item[1] } |
26             core_dump { $item[1] } |
27             dns { $item[1] } |
28             domainname { $item[1] } |
29             format { $item[1] } |
30             hostname { $item[1] } |
31             inactivity_timeout { $item[1] } |
32             interface { $item[1] } |
33             ipv6 { $item[1] } |
34             ipv6_state { $item[1] } |
35             management { $item[1] } |
36             management { $item[1] } |
37             max_path_splits { $item[1] } |
38             message { $item[1] } |
39             net_access { $item[1] } |
40             ntp { $item[1] } |
41             ospf { $item[1] } |
42             password_controls { $item[1] } |
43             pbr { $item[1] } |
44             rip { $item[1] } |
45             snmp { $item[1] } |
46             static_route { $item[1] } |
47             timezone { $item[1] } |
48             tracefile { $item[1] } |
49             user { $item[1] } |
50             vrrp { $item[1] } |
51             web { $item[1] } |
52             not_config { $item[1] }
53              
54             aaa: 'set aaa' m{\N+} { { type => $item[0], config => "yes" } }
55             arp: 'set arp' m{\N+} { { type => $item[0], config => "yes" } }
56             bonding: 'set bonding' m{\N+} { { type => $item[0], config => "yes" } }
57              
58             clienv: 'set clienv' config_lock { { type => $item[0], config => { @{ $item[2] } } } }
59             config_lock: 'config-lock' m{on|off} { [$item[0], $item[2]] }
60              
61              
62             core_dump: 'set core-dump' m{\N+} { { type => $item[0], config => "yes" } }
63             dns: 'set dns' m{\N+} { { type => $item[0], config => "yes" } }
64             domainname: 'set domainname' m{\N+} { { type => $item[0], config => "yes" } }
65             format: 'set format' m{\N+} { { type => $item[0], config => "yes" } }
66             hostname: 'set hostname' m{\N+} { { type => $item[0], config => "yes" } }
67             inactivity_timeout: 'set inactivity-timeout' m{\N+} { { type => $item[0], config => "yes" } }
68             ipv6: 'set ipv6' m{\N+} { { type => $item[0], config => "yes" } }
69             ipv6_state: 'set ipv6-state' m{\N+} { { type => $item[0], config => "yes" } }
70             management: 'set management' m{\N+} { { type => $item[0], config => "yes" } }
71             max_path_splits: 'set max-path-splits'm{\N+} { { type => $item[0], config => "yes" } }
72             message: 'set message' m{\N+} { { type => $item[0], config => "yes" } }
73             net_access: 'set net-access' m{\N+} { { type => $item[0], config => "yes" } }
74             ntp: 'set ntp' m{\N+} { { type => $item[0], config => "yes" } }
75             ospf: 'set ospf' m{\N+} { { type => $item[0], config => "yes" } }
76             password_controls: 'set password-controls' m{\N+} { { type => $item[0], config => "yes" } }
77             pbr: 'set pbr' m{\N+} { { type => $item[0], config => "yes" } }
78             rip: 'set rip' m{\N+} { { type => $item[0], config => "yes" } }
79             snmp: 'set snmp' m{\N+} { { type => $item[0], config => "yes" } }
80              
81             static_route: 'set static-route' destination (nexthop | comment) { { type => $item[0], config => { @{ $item[2] }, @{ $item[3]->[1] } } } }
82             destination: m{((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{2})|default)} { [$item[0], $item[1]] }
83             nexthop: 'nexthop' (nexthop_blackhole | nexthop_reject | nexthop_address | nexthop_interface) { [@{$item[2]->[1]}] }
84             nexthop_blackhole: 'blackhole' { ['nexthop_type', $item[1]] }
85             nexthop_reject: 'reject' { ['nexthop_type', $item[1]] }
86             nexthop_address: 'gateway address' ipv4 m{on|off} { [nexthop_type => 'address', nexthop => $item[2]->[1], status => $item[3]] }
87             nexthop_interface: 'gateway logical' interface_name m{on|off} { [nexthop_type => 'interface', nexthop => $item[2]->[1], status => $item[3]] }
88             comment: 'comment' m{"[\w\s]+"} { [$item[0], $item[2]] }
89              
90              
91              
92             timezone: 'set timezone' m{\N+} { { type => $item[0], config => "yes" } }
93             tracefile: 'set tracefile' m{\N+} { { type => $item[0], config => "yes" } }
94             user: 'set user' m{\N+} { { type => $item[0], config => "yes" } }
95             vrrp: 'set vrrp' m{\N+} { { type => $item[0], config => "yes" } }
96             web: 'set web' m{\N+} { { type => $item[0], config => "yes" } }
97            
98              
99             interface:
100             'set interface' interface_name (ipv4_address_mask | vlan | state | comment | mtu | auto_negotiation | link_speed)
101             { { type => $item[0], config => { name => $item[2]->[1], %{ $item[3]->[1] } } } }
102              
103             ipv4_address_mask: ipv4_address ipv4_mask { $return = { @{$item[1]}, @{$item[2]}} }
104             ipv4_address: 'ipv4-address' ipv4 { [$item[0], $item[2]->[1]] }
105             ipv4_mask: 'mask-length' m{\d+} { [$item[0], $item[2]] }
106              
107             vlan: 'vlan' m{\d+} { $return = { $item[0], $item[2] } }
108             state: 'state' m{\S+} { $return = { $item[0], $item[2] } }
109             comment: 'comments' m{"[\w\s]+"} { $return = { $item[0], $item[2] } }
110             mtu: 'mtu' m{\d+} { $return = { $item[0], $item[2] } }
111             auto_negotiation: 'auto-negotiation' m{\S+} { $return = { $item[0], $item[2] } }
112             link_speed: 'link-speed' m{\S+} { $return = { $item[0], $item[2] } }
113              
114             # Utility definitions
115             ipv4: m{\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}}
116             interface_name: m{\S+}
117              
118             not_config: m{\N+}
119             { { type => $item[0], config => $item[1] } }
120             });
121             }
122              
123              
124              
125             sub parse_config {
126 13     13 1 387587 my ($parser, $config_contents) = @_;
127              
128 13         140 my $parse_tree = $parser->startrule($config_contents);
129              
130 13         243926 return $parse_tree;
131             }
132              
133              
134              
135             sub get_output_drivers {
136             return {
137 0     0 1 0 csv => \&csv_output_driver,
138             json => \&json_output_driver,
139             };
140             }
141              
142              
143             sub post_process {
144 13     13 1 114 my ($parsed_config) = @_;
145 13         47 my %aggregation = ();
146 13         30 my %post_processed_config;
147              
148             # For each 'type' of config, (e.g. interface config), the aggregator key we're using to aggregate the separate
149             # config lines together into a single hash.
150 13         83 my $aggregator_keys_for = {
151             interface => q{$config_entry->{config}->{name}},
152             static_route => q{$config_entry->{config}->{destination}},
153             };
154              
155             # Go through each config entry (which was originally each line of config. If there's an aggregate key defined,
156             # aggregate on the 'type' and then this 'key'.
157             #
158             # If not, then just push it to the post processed hash.
159 13         33 for my $config_entry (@{ $parsed_config }) {
  13         53  
160 18 50       97 if (exists $aggregator_keys_for->{ $config_entry->{type} }) {
161 18         1849 my $aggregate_key = eval $aggregator_keys_for->{ $config_entry->{type} };
162 18         112 @{ $aggregation{ $config_entry->{type} }{ $aggregate_key } }{ keys %{ $config_entry->{config} } } = values %{ $config_entry->{config} };
  18         141  
  18         60  
  18         83  
163             } else {
164 0         0 push @{ $post_processed_config{ $config_entry->{type} } }, $config_entry->{config};
  0         0  
165             }
166             }
167              
168             # It's of the form $aggregation{type}{key} = { #interface into }; but the key is implicitly part of the hash it points to.
169             # Turn the hash of hash of hashes into a hash of array of hashes ( $aggregation{type} = [ { #interface info } ];
170 13         47 for my $config_type (keys %aggregation) {
171 13         27 $aggregation{ $config_type } = [ values %{ $aggregation{ $config_type } } ];
  13         69  
172             }
173              
174 13         50 @post_processed_config{ keys %aggregation } = values %aggregation;
175              
176 13         66 return \%post_processed_config;
177             }
178              
179              
180              
181             sub csv_output_driver {
182 0     0 1   my ($fh, $filename, $parsed_config) = @_;
183 0           my $csv_type_driver = {
184             interface => \&_csv_interface_driver,
185             static_route => \&_csv_static_route_driver,
186             not_config => \&_csv_not_config_driver,
187             };
188              
189 0           say "=" x 16 . "BEGIN FILE $filename" . "=" x 16;
190              
191             TYPE:
192 0           for my $type (keys %{ $parsed_config }) {
  0            
193 0           say "-" x 8 . "BEGIN TYPE $type" . "-" x 8;
194              
195             defined $csv_type_driver->{$type} ?
196 0 0         $csv_type_driver->{$type}->($fh, $parsed_config->{$type}) :
    0          
197             warn "No CSV output driver for $type\n" and next TYPE;
198              
199 0           say "-" x 8 . "END TYPE $type" . "-" x 8;
200             }
201              
202 0           say "-" x 8 . "END FILE $filename" . "-" x 8;
203             }
204              
205             sub _csv_interface_driver {
206 0     0     my ($fh, $interfaces_ref) = @_;
207              
208             # Print the CSV schema line
209 0           my @interface_properties = qw{name state vlan ipv4_address ipv4_mask auto_negotiation link_speed mtu comment};
210 0           say $fh join(',', @interface_properties);
211              
212             # Interface through the interfaces, extract and print their properties
213 0           for my $interface (@{ $interfaces_ref }) {
  0            
214 0           my @properties = @{ $interface }{ @interface_properties };
  0            
215              
216             # Replace any undef with an empty string
217 0 0         @properties = map { defined $_ ? $_ : '' } @properties;
  0            
218 0           say $fh join(',', @properties);
219             }
220             }
221              
222              
223             sub _csv_static_route_driver {
224 0     0     my ($fh, $static_routes_ref) = @_;
225              
226 0           my @static_route_properties = qw{destination nexthop nexthop_type status};
227 0           say $fh join(',', @static_route_properties);
228              
229 0           for my $route (@{ $static_routes_ref }) {
  0            
230 0           my @properties = @{ $route }{ @static_route_properties };
  0            
231              
232             # Replace any undef with an empty string
233 0 0         @properties = map { defined $_ ? $_ : '' } @properties;
  0            
234 0           say $fh join(',', @properties);
235             }
236             }
237              
238              
239             sub _csv_not_config_driver {
240 0     0     my ($fh, $not_config) = @_;
241              
242 0           for my $config_line (@{ $not_config }) {
  0            
243 0           print $fh "$config_line\n";
244             }
245             }
246              
247              
248              
249              
250              
251              
252             sub json_output_driver {
253 0     0 1   my ($fh, $filename, $parsed_config) = @_;
254              
255 0           print encode_json($parsed_config);
256             }
257              
258              
259             1; # End of Device::CheckPoint::ConfigParse
260              
261             __END__