File Coverage

lib/Config/HAProxy.pm
Criterion Covered Total %
statement 131 192 68.2
branch 22 74 29.7
condition 1 7 14.2
subroutine 28 32 87.5
pod 10 15 66.6
total 192 320 60.0


line stmt bran cond sub pod time code
1             package Config::HAProxy;
2 2     2   111969 use 5.010;
  2         13  
3 2     2   8 use strict;
  2         2  
  2         30  
4 2     2   7 use warnings;
  2         2  
  2         33  
5 2     2   755 use Text::Locus;
  2         10492  
  2         77  
6 2     2   600 use Config::HAProxy::Node::Root;
  2         4  
  2         47  
7 2     2   9 use Config::HAProxy::Node::Section;
  2         3  
  2         30  
8 2     2   582 use Config::HAProxy::Node::Statement;
  2         4  
  2         47  
9 2     2   579 use Config::HAProxy::Node::Comment;
  2         5  
  2         45  
10 2     2   553 use Config::HAProxy::Node::Empty;
  2         4  
  2         44  
11 2     2   698 use Text::ParseWords;
  2         2179  
  2         99  
12 2     2   12 use File::Basename;
  2         2  
  2         175  
13 2     2   1223 use File::Temp qw(tempfile);
  2         29441  
  2         104  
14 2     2   736 use File::stat;
  2         10894  
  2         6  
15 2     2   96 use File::Spec;
  2         4  
  2         39  
16 2     2   1098 use IPC::Cmd qw(run);
  2         83232  
  2         109  
17 2     2   19 use Carp;
  2         2  
  2         3033  
18              
19             our $VERSION = '1.05';
20              
21             my %sections = (
22             global => 1,
23             defaults => 1,
24             frontend => 1,
25             backend => 1,
26             );
27              
28             sub new {
29 2     2 0 1477 my $class = shift;
30 2   50     9 my $filename = shift // '/etc/haproxy/haproxy.cfg';
31 2         11 my $self = bless { _filename => $filename,
32             _lint => { enable => 1 } }, $class;
33 2         11 $self->reset();
34 2         9 return $self;
35             }
36              
37 32     32 1 132 sub filename { shift->{_filename} }
38              
39             sub parse {
40 2     2 1 3 my $self = shift;
41              
42 2 50       11 open(my $fh, '<', $self->filename)
43             or croak "can't open ".$self->filename.": $!";
44 2         6 my $line = 0;
45 2         11 $self->reset();
46 2         14 $self->push($self->tos);
47 2         37 while (<$fh>) {
48 30         46 my $locus = new Text::Locus($self->filename, ++$line);
49 30         603 chomp;
50 30         33 my $orig = $_;
51 30         84 s/^\s+//;
52 30         65 s/\s+$//;
53              
54 30 100       65 if ($_ eq "") {
55 2         3 $self->tos->append_node(
56             new Config::HAProxy::Node::Empty(locus => $locus));
57 2         13 next;
58             }
59            
60 28 100       44 if (/^#.*/) {
61 1         3 $self->tos->append_node(
62             new Config::HAProxy::Node::Comment(orig => $orig,
63             locus => $locus));
64 1         3 next;
65             }
66            
67 27         48 my @words = parse_line('\s+', 1, $_);
68 27         1507 my $kw = shift @words;
69 27 100       49 if ($sections{$kw}) {
70 9         28 my $section =
71             new Config::HAProxy::Node::Section(kw => $kw,
72             argv => \@words,
73             orig => $orig,
74             locus => $locus);
75 9         23 $self->pop;
76 9         26 $self->tos->append_node($section);
77 9         15 $self->push($section);
78             } else {
79 18         31 $self->tos->append_node(
80             new Config::HAProxy::Node::Statement(kw => $kw,
81             argv => \@words,
82             orig => $orig,
83             locus => $locus));
84             }
85             }
86 2         7 $self->pop;
87 2         14 close $fh;
88 2         18 return $self;
89             }
90              
91             sub reset {
92 4     4 1 5 my $self = shift;
93 4         23 $self->{_stack} = [ new Config::HAProxy::Node::Root() ];
94             }
95              
96             sub push {
97 11     11 1 12 my $self = shift;
98 11         12 push @{$self->{_stack}}, @_;
  11         33  
99             }
100              
101             sub pop {
102 11     11 1 12 my $self = shift;
103 11 50       12 croak "can't pop the root tree" if @{$self->{_stack}} == 1;
  11         45  
104 11         13 pop @{$self->{_stack}};
  11         16  
105             }
106              
107             sub tos {
108 32     32 1 32 my $self = shift;
109 32         111 $self->{_stack}[-1];
110             }
111              
112             sub tree {
113 6     6 1 7 my $self = shift;
114 6         22 $self->{_stack}[0];
115             }
116              
117             sub select {
118 2     2 0 597 my $self = shift;
119 2         7 $self->tree->select(@_);
120             }
121              
122             sub iterator {
123 4     4 0 4 my $self = shift;
124 4         9 $self->tree->iterator(@_);
125             }
126              
127             sub write {
128 4     4 1 2132 my $self = shift;
129 4         5 my $file = shift;
130 4         4 my $fh;
131              
132 4 50       10 if (ref($file) eq 'GLOB') {
133 0         0 $fh = $file;
134             } else {
135 1 50   1   5 open($fh, '>', $file) or croak "can't open $file: $!";
  1         1  
  1         5  
  4         56  
136             }
137              
138 4         616 local %_ = @_;
139 4         12 my $itr = $self->iterator(inorder => 1);
140            
141 4         9 while (defined(my $node = $itr->next)) {
142 52         89 my $s = $node->as_string;
143 52 100       70 if ($_{indent}) {
144 39 100       55 if ($node->is_comment) {
145 3 100       5 if ($_{reindent_comments}) {
146 1         7 my $indent = ' ' x ($_{indent} * $node->depth);
147 1         2 $s =~ s/^\s+//;
148 1         2 $s = $indent . $s;
149             }
150             } else {
151 36         49 my $indent = ' ' x ($_{indent} * $node->depth);
152 36 100       44 if ($_{tabstop}) {
153 12         18 $s = $indent . $node->kw;
154 12         26 for (my $i = 0; my $arg = $node->arg($i); $i++) {
155 13         12 my $off = 1;
156 13 50       11 if ($i < @{$_{tabstop}}) {
  13         20  
157 13 100       18 if (($off = $_{tabstop}[$i] - length($s)) <= 0) {
158 1         2 $off = 1;
159             }
160             }
161 13         27 $s .= (' ' x $off) . $arg;
162             }
163             } else {
164 24         55 $s =~ s/^\s+//;
165 24         34 $s = $indent . $s;
166             }
167             }
168             }
169 52         113 print $fh $s,"\n";
170             }
171              
172 4 50       23 close $fh unless ref($file) eq 'GLOB';
173             }
174              
175             sub lint {
176 0     0 1   my $self = shift;
177              
178 0 0         if (@_) {
179 0 0         if (@_ == 1) {
    0          
180 0           $self->{_lint}{enable} = !!shift;
181             } elsif (@_ % 2 == 0) {
182 0           local %_ = @_;
183 0           my $v;
184 0 0         if (defined($v = delete $_{enable})) {
185 0           $self->{_lint}{enable} = $v;
186             }
187 0 0         if (defined($v = delete $_{command})) {
188 0           $self->{_lint}{command} = $v;
189             }
190 0 0         if (defined($v = delete $_{path})) {
191 0           $self->{_lint}{path} = $v;
192             }
193 0 0         croak "unrecognized keywords" if keys %_;
194             } else {
195 0           croak "bad number of arguments";
196             }
197             }
198              
199 0 0         if ($self->{_lint}{enable}) {
200 0   0       $self->{_lint}{command} ||= 'haproxy -c -f';
201 0 0         if ($self->{_lint}{path}) {
202 0           my ($prog, $args) = split /\s+/, $self->{_lint}{command}, 2;
203 0 0         if (!File::Spec->file_name_is_absolute($prog)) {
204 0           foreach my $dir (split /:/, $self->{_lint}{path}) {
205 0           my $name = File::Spec->catfile($dir, $prog);
206 0 0         if (-x $name) {
207 0           $prog = $name;
208 0           last;
209             }
210             }
211 0 0         if ($args) {
212 0           $prog .= ' '.$args;
213             }
214 0           $self->{_lint}{command} = $prog;
215             }
216             }
217 0           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__