File Coverage

lib/Config/HAProxy.pm
Criterion Covered Total %
statement 147 192 76.5
branch 35 74 47.3
condition 3 7 42.8
subroutine 29 32 90.6
pod 10 15 66.6
total 224 320 70.0


line stmt bran cond sub pod time code
1             package Config::HAProxy;
2 3     3   224517 use 5.010;
  3         30  
3 3     3   13 use strict;
  3         5  
  3         55  
4 3     3   12 use warnings;
  3         5  
  3         66  
5 3     3   1582 use Text::Locus;
  3         21096  
  3         154  
6 3     3   1177 use Config::HAProxy::Node::Root;
  3         7  
  3         95  
7 3     3   16 use Config::HAProxy::Node::Section;
  3         5  
  3         58  
8 3     3   1249 use Config::HAProxy::Node::Statement;
  3         7  
  3         90  
9 3     3   1063 use Config::HAProxy::Node::Comment;
  3         7  
  3         85  
10 3     3   1098 use Config::HAProxy::Node::Empty;
  3         7  
  3         84  
11 3     3   1563 use Text::ParseWords;
  3         3969  
  3         187  
12 3     3   23 use File::Basename;
  3         4  
  3         395  
13 3     3   2438 use File::Temp qw(tempfile);
  3         58166  
  3         202  
14 3     3   1531 use File::stat;
  3         20866  
  3         14  
15 3     3   191 use File::Spec;
  3         5  
  3         73  
16 3     3   2208 use IPC::Cmd qw(run);
  3         160598  
  3         198  
17 3     3   29 use Carp;
  3         6  
  3         5827  
18              
19             our $VERSION = '1.06';
20              
21             my %sections = (
22             global => 1,
23             defaults => 1,
24             frontend => 1,
25             backend => 1,
26             );
27              
28             sub new {
29 3     3 0 2871 my $class = shift;
30 3   50     15 my $filename = shift // '/etc/haproxy/haproxy.cfg';
31 3         20 my $self = bless { _filename => $filename,
32             _lint => { enable => 1 } }, $class;
33 3         20 $self->reset();
34 3         19 return $self;
35             }
36              
37 37     37 1 227 sub filename { shift->{_filename} }
38              
39             sub parse {
40 3     3 1 6 my $self = shift;
41              
42 3 50       22 open(my $fh, '<', $self->filename)
43             or croak "can't open ".$self->filename.": $!";
44 3         13 my $line = 0;
45 3         17 $self->reset();
46 3         26 $self->push($self->tos);
47 3         79 while (<$fh>) {
48 34         70 my $locus = new Text::Locus($self->filename, ++$line);
49 34         887 chomp;
50 34         47 my $orig = $_;
51 34         122 s/^\s+//;
52 34         94 s/\s+$//;
53              
54 34 100       131 if ($_ eq "") {
55 2         3 $self->tos->append_node(
56             new Config::HAProxy::Node::Empty(locus => $locus));
57 2         17 next;
58             }
59            
60 32 100       64 if (/^#.*/) {
61 1         4 $self->tos->append_node(
62             new Config::HAProxy::Node::Comment(orig => $orig,
63             locus => $locus));
64 1         4 next;
65             }
66            
67 31         73 my @words = parse_line('\s+', 1, $_);
68 31         2287 my $kw = shift @words;
69 31 100       85 if ($sections{$kw}) {
70 10         36 my $section =
71             new Config::HAProxy::Node::Section(kw => $kw,
72             argv => \@words,
73             orig => $orig,
74             locus => $locus);
75 10         35 $self->pop;
76 10         37 $self->tos->append_node($section);
77 10         21 $self->push($section);
78             } else {
79 21         44 $self->tos->append_node(
80             new Config::HAProxy::Node::Statement(kw => $kw,
81             argv => \@words,
82             orig => $orig,
83             locus => $locus));
84             }
85             }
86 3         18 $self->pop;
87 3         31 close $fh;
88 3         31 return $self;
89             }
90              
91             sub reset {
92 6     6 1 12 my $self = shift;
93 6         46 $self->{_stack} = [ new Config::HAProxy::Node::Root() ];
94             }
95              
96             sub push {
97 13     13 1 19 my $self = shift;
98 13         16 push @{$self->{_stack}}, @_;
  13         49  
99             }
100              
101             sub pop {
102 13     13 1 19 my $self = shift;
103 13 50       13 croak "can't pop the root tree" if @{$self->{_stack}} == 1;
  13         64  
104 13         20 pop @{$self->{_stack}};
  13         22  
105             }
106              
107             sub tos {
108 37     37 1 47 my $self = shift;
109 37         166 $self->{_stack}[-1];
110             }
111              
112             sub tree {
113 6     6 1 7 my $self = shift;
114 6         31 $self->{_stack}[0];
115             }
116              
117             sub select {
118 2     2 0 792 my $self = shift;
119 2         10 $self->tree->select(@_);
120             }
121              
122             sub iterator {
123 4     4 0 6 my $self = shift;
124 4         12 $self->tree->iterator(@_);
125             }
126              
127             sub write {
128 4     4 1 2828 my $self = shift;
129 4         6 my $file = shift;
130 4         5 my $fh;
131              
132 4 50       12 if (ref($file) eq 'GLOB') {
133 0         0 $fh = $file;
134             } else {
135 1 50   1   7 open($fh, '>', $file) or croak "can't open $file: $!";
  1         2  
  1         6  
  4         77  
136             }
137              
138 4         938 local %_ = @_;
139 4         17 my $itr = $self->iterator(inorder => 1);
140            
141 4         10 while (defined(my $node = $itr->next)) {
142 52         100 my $s = $node->as_string;
143 52 100       85 if ($_{indent}) {
144 39 100       65 if ($node->is_comment) {
145 3 100       8 if ($_{reindent_comments}) {
146 1         7 my $indent = ' ' x ($_{indent} * $node->depth);
147 1         3 $s =~ s/^\s+//;
148 1         2 $s = $indent . $s;
149             }
150             } else {
151 36         60 my $indent = ' ' x ($_{indent} * $node->depth);
152 36 100       52 if ($_{tabstop}) {
153 12         27 $s = $indent . $node->kw;
154 12         36 for (my $i = 0; my $arg = $node->arg($i); $i++) {
155 13         14 my $off = 1;
156 13 50       13 if ($i < @{$_{tabstop}}) {
  13         25  
157 13 100       21 if (($off = $_{tabstop}[$i] - length($s)) <= 0) {
158 1         2 $off = 1;
159             }
160             }
161 13         32 $s .= (' ' x $off) . $arg;
162             }
163             } else {
164 24         62 $s =~ s/^\s+//;
165 24         44 $s = $indent . $s;
166             }
167             }
168             }
169 52         131 print $fh $s,"\n";
170             }
171              
172 4 50       29 close $fh unless ref($file) eq 'GLOB';
173             }
174              
175             sub lint {
176 9     9 1 764 my $self = shift;
177              
178 9 100       21 if (@_) {
179 4 100       14 if (@_ == 1) {
    50          
180 2         7 $self->{_lint}{enable} = !!shift;
181             } elsif (@_ % 2 == 0) {
182 2         7 local %_ = @_;
183 2         4 my $v;
184 2 50       6 if (defined($v = delete $_{enable})) {
185 2         3 $self->{_lint}{enable} = $v;
186             }
187 2 100       7 if (defined($v = delete $_{command})) {
188 1         2 $self->{_lint}{command} = $v;
189             }
190 2 50       5 if (defined($v = delete $_{path})) {
191 0         0 $self->{_lint}{path} = $v;
192             }
193 2 50       8 croak "unrecognized keywords" if keys %_;
194             } else {
195 0         0 croak "bad number of arguments";
196             }
197             }
198              
199 9 100       68 if ($self->{_lint}{enable}) {
200 5   100     14 $self->{_lint}{command} ||= 'haproxy -c -f';
201 5 50       9 if ($self->{_lint}{path}) {
202 0         0 my ($prog, $args) = split /\s+/, $self->{_lint}{command}, 2;
203 0 0       0 if (!File::Spec->file_name_is_absolute($prog)) {
204 0         0 foreach my $dir (split /:/, $self->{_lint}{path}) {
205 0         0 my $name = File::Spec->catfile($dir, $prog);
206 0 0       0 if (-x $name) {
207 0         0 $prog = $name;
208 0         0 last;
209             }
210             }
211 0 0       0 if ($args) {
212 0         0 $prog .= ' '.$args;
213             }
214 0         0 $self->{_lint}{command} = $prog;
215             }
216             }
217 5         14 return $self->{_lint}{command};
218             }
219             }
220              
221             sub save {
222 0     0 1   my $self = shift;
223 0 0         croak "bad number of arguments" if @_ % 2;
224 0           local %_ = @_;
225 0           my $dry_run = delete $_{dry_run};
226 0           my @wrargs = %_;
227            
228 0 0         return unless $self->tree;# FIXME
229 0 0         return unless $self->tree->is_dirty;
230 0           my ($fh, $tempfile) = tempfile('haproxy.XXXXXX',
231             DIR => dirname($self->filename));
232 0           $self->write($fh, @wrargs);
233 0           close($fh);
234 0 0         if (my $cmd = $self->lint) {
235 0           my ($ok, $err, $full, $outbuf, $errbuf) =
236             run(command => "$cmd $tempfile");
237 0 0         unless ($ok) {
238 0           unlink $tempfile;
239 0 0 0       if ($errbuf && @$errbuf) {
240 0           croak "Syntax check failed: ".join("\n", @$errbuf)."\n";
241             }
242 0           croak $err;
243             }
244             }
245 0 0         return 1 if $dry_run;
246            
247 0           my $sb = stat($self->filename);
248 0           $self->backup;
249 0 0         rename($tempfile, $self->filename)
250             or croak "can't rename $tempfile to ".$self->tempfile.": $!";
251             # This will succeed: we've created the file, so we're owning it.
252 0           chmod $sb->mode & 0777, $self->filename;
253             # This will fail unless we are root, let it be so.
254 0           chown $sb->uid, $sb->gid, $self->filename;
255            
256 0           $self->tree->clear_dirty;
257 0           return 1;
258             }
259              
260             sub backup_name {
261 0     0 0   my $self = shift;
262 0           $self->filename . '~'
263             }
264              
265             sub backup {
266 0     0 0   my $self = shift;
267 0           my $backup = $self->backup_name;
268 0 0         if (-f $backup) {
269 0 0         unlink $backup
270             or croak "can't unlink $backup: $!"
271             }
272 0 0         rename $self->filename, $self->backup_name
273             or croak "can't rename :"
274             . $self->filename
275             . " to "
276             . $self->backup_name
277             . ": $!";
278             }
279              
280             1;
281             __END__