summaryrefslogtreecommitdiffabout
authorSergey Poznyakoff <gray@gnu.org>2019-05-25 06:43:38 (GMT)
committer Sergey Poznyakoff <gray@gnu.org>2019-05-25 16:58:35 (GMT)
commit585a5a925342c357d46fbad273bfafc03bc33dcd (patch) (unidiff)
tree0aa81c8e85543ebaa25ef88292bb318ac4294e04
parentedfeb103b85e10aa9e6825d3b92c5888fb4dea94 (diff)
downloadconfig-haproxy-585a5a925342c357d46fbad273bfafc03bc33dcd.tar.gz
config-haproxy-585a5a925342c357d46fbad273bfafc03bc33dcd.tar.bz2
Check configuration file syntax before saving it
* Makefile.PL: Require IPC::Cmd * lib/Config/HAProxy.pm (lint): New method. (save): Call linter prior to saving. Take optional dry_run keyword as argument. * t/lint.t: New file.
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--MANIFEST.SKIP2
-rw-r--r--Makefile.PL3
-rw-r--r--lib/Config/HAProxy.pm125
-rw-r--r--t/lint.t33
4 files changed, 153 insertions, 10 deletions
diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP
index c1d2412..e7c7b44 100644
--- a/MANIFEST.SKIP
+++ b/MANIFEST.SKIP
@@ -60,4 +60,4 @@
60^\.emacs\.* 60^\.emacs\.*
61\.tar$ 61\.tar$
62\.tar\.gz$ 62\.tar\.gz$
63Config/HAProxy/VirtualHost.pm 63
diff --git a/Makefile.PL b/Makefile.PL
index d9cc6e8..754d3aa 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -16,7 +16,8 @@ WriteMakefile(
16 'Text::Locus' => 1.00, 16 'Text::Locus' => 1.00,
17 'Text::ParseWords' => 0, 17 'Text::ParseWords' => 0,
18 'File::Basename' => 0, 18 'File::Basename' => 0,
19 'File::Temp' => 0 19 'File::Temp' => 0,
20 'IPC::Cmd' => 0
20 }, 21 },
21 META_MERGE => { 22 META_MERGE => {
22 'meta-spec' => { version => 2 }, 23 'meta-spec' => { version => 2 },
diff --git a/lib/Config/HAProxy.pm b/lib/Config/HAProxy.pm
index 9a8d666..ff4fa8a 100644
--- a/lib/Config/HAProxy.pm
+++ b/lib/Config/HAProxy.pm
@@ -12,6 +12,8 @@ use Text::ParseWords;
12use File::Basename; 12use File::Basename;
13use File::Temp qw(tempfile); 13use File::Temp qw(tempfile);
14use File::stat; 14use File::stat;
15use File::Spec;
16use IPC::Cmd;
15use Carp; 17use Carp;
16 18
17our $VERSION = '1.01'; 19our $VERSION = '1.01';
@@ -26,7 +28,8 @@ my %sections = (
26sub new { 28sub new {
27 my $class = shift; 29 my $class = shift;
28 my $filename = shift // '/etc/haproxy/haproxy.cfg'; 30 my $filename = shift // '/etc/haproxy/haproxy.cfg';
29 my $self = bless { _filename => $filename }, $class; 31 my $self = bless { _filename => $filename,
32 _lint => { enable => 1 } }, $class;
30 $self->reset(); 33 $self->reset();
31 return $self; 34 return $self;
32} 35}
@@ -169,15 +172,74 @@ sub write {
169 close $fh unless ref($file) eq 'GLOB'; 172 close $fh unless ref($file) eq 'GLOB';
170} 173}
171 174
172sub save { 175sub lint {
173 my $self = shift; 176 my $self = shift;
174 177
178 if (@_) {
179 if (@_ == 1) {
180 $self->{_lint}{enable} = !!shift;
181 } elsif (@_ % 2 == 0) {
182 local %_ = @_;
183 my $v;
184 if (defined($v = delete $_{enable})) {
185 $self->{_lint}{enable} = $v;
186 }
187 if (defined($v = delete $_{command})) {
188 $self->{_lint}{command} = $v;
189 }
190 if (defined($v = delete $_{path})) {
191 $self->{_lint}{path} = $v;
192 }
193 croak "unrecognized keywords" if keys %_;
194 } else {
195 croak "bad number of arguments";
196 }
197 }
198
199 if ($self->{_lint}{enable}) {
200 $self->{_lint}{command} ||= 'haproxy -c -f';
201 if ($self->{_lint}{path}) {
202 my ($prog, $args) = split /\s+/, $self->{_lint}{command}, 2;
203 if (!File::Spec->file_name_is_absolute($prog)) {
204 foreach my $dir (split /:/, $self->{_lint}{path}) {
205 my $name = File::Spec->catfile($dir, $prog);
206 if (-x $name) {
207 $prog = $name;
208 last;
209 }
210 }
211 if ($args) {
212 $prog .= ' '.$args;
213 }
214 $self->{_lint}{command} = $prog;
215 }
216 }
217 return $self->{_lint}{command};
218 }
219}
220
221sub save {
222 my $self = shift;
223 croak "bad number of arguments" if @_ % 2;
224 local %_ = @_;
225 my $dry_run = delete $_{dry_run};
226 my @wrargs = %_;
227
175 return unless $self->tree;# FIXME 228 return unless $self->tree;# FIXME
176 return unless $self->tree->is_dirty; 229 return unless $self->tree->is_dirty;
177 my ($fh, $tempfile) = tempfile('haproxy.XXXXXX', 230 my ($fh, $tempfile) = tempfile('haproxy.XXXXXX',
178 DIR => dirname($self->filename)); 231 DIR => dirname($self->filename));
179 $self->write($fh, @_); 232 $self->write($fh, @wrargs);
180 close($fh); 233 close($fh);
234
235 if (my $cmd = $self->lint) {
236 my ($ok, $err, undef, undef, $errbuf) =
237 IPC::Cmd->run(command => "$cmd $tempfile");
238 unless ($ok) {
239 croak "Syntax check failed: $errbuf\n";
240 }
241 }
242 return 1 if $dry_run;
181 243
182 my $sb = stat($self->filename); 244 my $sb = stat($self->filename);
183 $self->backup; 245 $self->backup;
@@ -188,7 +250,8 @@ sub save {
188 # This will fail unless we are root, let it be so. 250 # This will fail unless we are root, let it be so.
189 chown $sb->uid, $sb->gid, $self->filename; 251 chown $sb->uid, $sb->gid, $self->filename;
190 252
191 $self->tree->clear_dirty 253 $self->tree->clear_dirty;
254 return 1;
192} 255}
193 256
194sub backup_name { 257sub backup_name {
@@ -233,9 +296,12 @@ Config::HAProxy - Parser for HAProxy configuration file
233 # do something with $node 296 # do something with $node
234 } 297 }
235 298
236 $cfg->save; 299 $cfg->lint(enable => 1, command => 'haproxy -c -f',
300 path => '/sbin:/usr/sbin')
301
302 $cfg->save(%hash);
237 303
238 $cfg->write($file_or_handle); 304 $cfg->write($file_or_handle, %hash);
239 305
240 $cfg->backup; 306 $cfg->backup;
241 $name = $self->backup_name; 307 $name = $self->backup_name;
@@ -361,11 +427,54 @@ Returns the last node in the tree.
361 427
362=head1 SAVING 428=head1 SAVING
363 429
430=head2 lint
431
432 $cfg->lint(%ARGS);
433
434Configures syntax checking program to be run before saving. Takes a
435hash as argument. Allowed keys are:
436
437=over 4
438
439=item B<enable =E<gt> I<BOOL>>
440
441If I<BOOL> is 0, disables syntax check. Default is 1.
442
443=item B<command =E<gt> I<CMD>>
444
445Configures the command to use for syntax check. The command will be run as
446
447 CMD FILE
448
449where I<FILE> is the name of the HAProxy configuration file to check.
450
451Default command is B<haproxy -c -f>.
452
453=item B<path =E<gt> I<PATH>>
454
455Sets the search path for the check program. I<PATH> is a colon-delimited
456list of directories. Unless the first word of B<command> is an absolute
457file name, it will be looked for in these directories. The first match
458will be used. Default is system B<$PATH>.
459
460=back
461
462Returns the command name.
463
364=head2 save 464=head2 save
365 465
366 $cfg->save; 466 $cfg->save(%hash);
467
468Saves the parse tree in the configuration file. Syntax check will be run
469prior to saving (unless previously disabled). If syntax errors are discovered,
470the method will B<croak> with the appropriate diagnostics, beginning with
471words C<Syntax check failed:>.
472
473If I<%hash> contains a non-zero B<dry_run> value, B<save> will only run syntax
474check, without actually saving the file. If B<$cfg-E<gt>lint(enable =E<gt> 0)>
475was called previously, this is a no-op.
367 476
368Saves the parse tree in the configuration file. 477Other keys in I<%hash> are the same as in B<write>, described below.
369 478
370=head2 write 479=head2 write
371 480
diff --git a/t/lint.t b/t/lint.t
new file mode 100644
index 0000000..aa92fd1
--- a/dev/null
+++ b/t/lint.t
@@ -0,0 +1,33 @@
1# -*- perl -*-
2use lib qw(t lib);
3use strict;
4use warnings;
5use Test::More;
6
7BEGIN {
8 plan tests => 7;
9 use_ok('Test::HAProxy');
10}
11
12my $hp = new Test::HAProxy;
13isa_ok($hp,'Test::HAProxy');
14
15ok($hp->lint, 'haproxy -c -f');
16
17$hp->lint(0);
18ok(!$hp->lint);
19
20$hp->lint(1);
21ok($hp->lint, 'haproxy -c -f');
22
23$hp->lint(enable => 0);
24ok(!$hp->lint);
25
26$hp->lint(enable => 1, command => '/usr/local/bin/haproxy -c -f');
27ok($hp->lint, '/usr/local/bin/haproxy -c -f');
28
29__DATA__
30global
31 log /dev/log daemon
32 user haproxy
33 group haproxy

Return to:

Send suggestions and report system problems to the System administrator.