File Coverage

lib/Config/HAProxy.pm
Criterion Covered Total %
statement 147 196 75.0
branch 35 74 47.3
condition 3 7 42.8
subroutine 29 34 85.2
pod 10 17 58.8
total 224 328 68.2


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