summaryrefslogtreecommitdiffabout
path: root/lib
Side-by-side diff
Diffstat (limited to 'lib') (more/less context) (ignore whitespace changes)
-rw-r--r--lib/Config/HAProxy.pm140
-rw-r--r--lib/Config/HAProxy/Iterator.pm68
-rw-r--r--lib/Config/HAProxy/Node.pm137
-rw-r--r--lib/Config/HAProxy/Node/Comment.pm31
-rw-r--r--lib/Config/HAProxy/Node/Empty.pm29
-rw-r--r--lib/Config/HAProxy/Node/Root.pm52
-rw-r--r--lib/Config/HAProxy/Node/Section.pm153
-rw-r--r--lib/Config/HAProxy/Node/Statement.pm45
8 files changed, 628 insertions, 27 deletions
diff --git a/lib/Config/HAProxy.pm b/lib/Config/HAProxy.pm
index 7938bcb..f99bf33 100644
--- a/lib/Config/HAProxy.pm
+++ b/lib/Config/HAProxy.pm
@@ -179,16 +179,16 @@ sub save {
close($fh);
my $sb = stat($self->filename);
$self->backup;
rename($tempfile, $self->filename)
or croak "can't rename $tempfile to ".$self->tempfile.": $!";
- # This will fail unless we are root, let it be so.
- chown $sb->uid, $sb->gid, $self->filename;
# This will succeed: we've created the file, so we're owning it.
chmod $sb->mode & 0777, $self->filename;
+ # This will fail unless we are root, let it be so.
+ chown $sb->uid, $sb->gid, $self->filename;
$self->tree->clear_dirty
}
sub backup_name {
my $self = shift;
@@ -212,63 +212,122 @@ sub backup {
1;
__END__
=head1 NAME
-Config::HAProxy - HAProxy configuration file
+Config::HAProxy - Parser for HAProxy configuration file
=head1 SYNOPSIS
-use Config::HAProxy;
-$cfg = new Config::HAProxy($filename);
-$cfg->parse;
+ use Config::HAProxy;
+ $cfg = new Config::HAProxy($filename);
+ $cfg->parse;
-$name = $cfg->filename;
+ $name = $cfg->filename;
-@frontends = $cfg->select(name => 'frontend');
+ @frontends = $cfg->select(name => 'frontend');
-$itr = $cfg->iterator(inorder => 1);
-while (defined($node = $itr->next)) {
- # do something with $node
-}
+ $itr = $cfg->iterator(inorder => 1);
+ while (defined($node = $itr->next)) {
+ # do something with $node
+ }
-$cfg->save;
+ $cfg->save;
-$cfg->write($file_or_handle);
+ $cfg->write($file_or_handle);
-$cfg->backup;
-$name = $self->backup_name;
+ $cfg->backup;
+ $name = $self->backup_name;
-$cfg->reset;
-$cfg->push($node);
-$node = $cfg->pop;
-$node = $cfg->tos;
-$node = $cfg->tree;
+ $cfg->reset;
+ $cfg->push($node);
+ $node = $cfg->pop;
+ $node = $cfg->tos;
+ $node = $cfg->tree;
=head1 DESCRIPTION
-FIXME
+The B<Config::HAProxy> class is a parser that converts the B<HAProxy>
+configuration file to a parse tree and provides methods for various
+operations on this tree, such as: searching, modifying and saving it
+to a file.
+
+An object of this class contains a I<parse tree> representing the
+configuration read from the file (or created from scratch). Nodes in the
+tree can be of four distinct classes:
+
+=over 4
+
+=item Empty
+
+Represents an empty line.
+
+=item Comment
+
+Represents a comment line.
+
+=item Statement
+
+Represents a simple statement.
+
+=item Section
+
+A container, representing a C<compound statement>, i.e. a statement that
+contains multiple sub-statements. Compound statements are: B<global>,
+B<defaults>, B<frontend>, and B<backend>.
+
+=back
+
+In addition to these four classes, a special class B<Root> is provided, which
+represents the topmost node in the parse tree (i.e. the parent of other nodes).
+
+A set of attributes is associated with each node. Among these, the B<orig>
+attribute contains the original line from the configuration file that triggered
+creation of this node, and B<locus> contains the location of this line (or
+lines, for sections) in the configuration file (as a B<Text::Locus>) object.
+
+These two attributes are meaningful for all nodes. For statement nodes (simple
+statements and sections) the B<kw> attribute contains the statement I<keyword>,
+and the B<argv> attribute - its arguments. For example, the statement
+
+ server localhost 127.0.0.1:8080
+
+is represented by a node of class B<Config::HAProxy::Node::Statement>, with
+C<server> as B<kw> and list (C<localhost>, C<127.0.0.1:8080>) as B<argv>.
+
+Additionally, section nodes provide methods for accessing their subtrees.
+
+For a detailed description of the node class and its methods, please refer to
+B<Config::HAProxy::Node>.
=head1 CONSTRUCTOR
$cfg = new Config::HAProxy($filename);
Creates and returns a new object for manipulating the HAProxy configuration.
Optional B<$filename> specifies the name of the file to read configuration
from. It defaults to F</etc/haproxy/haproxy.cfg>.
-=head1 THE PARSE TREE
+=head2 filename
+
+ $s = $cfg->filename;
+
+Returns the configuration file name given when creating the object.
+
+=head1 PARSING
=head2 parse
$cfg->parse;
Reads and parses the configuration file. Croaks if the file does not exist.
+Returns B<$cfg>.
-
+=head1 BUILDING THE PARSE TREE
+
=head2 reset
$cfg->reset;
Clears the parse tree.
@@ -302,13 +361,13 @@ Returns the last node in the tree.
=head1 SAVING
=head2 save
$cfg->save;
-Saves the configuration file.
+Saves the parse tree in the configuration file.
=head2 write
$cfg->write($file, %hash);
Writes configuration to the named file or file handle. If B<$file> is the
@@ -324,6 +383,37 @@ Arguments with B<$i> greater than or equal to B<@tabstop> are appended to
the resulting output, preserving their original offsets.
Normally, comments retain their original indentation. However, if the
key B<reintent_comments> is present, and its value is evaluated as true,
then comments are reindented following the rules described above.
+=head1 SEE ALSO
+
+B<Config::HAProxy::Node>,
+B<Config::HAProxy::Node::Section>,
+B<Config::HAProxy::Node::Statement>,
+B<Config::HAProxy::Iterator>.
+
+=head1 AUTHOR
+
+Sergey Poznyakoff, E<lt>gray@gnu.orgE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2018 by Sergey Poznyakoff
+
+This library is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 3 of the License, or (at your
+option) any later version.
+
+It is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this library. If not, see <http://www.gnu.org/licenses/>.
+
+=cut
+
+
diff --git a/lib/Config/HAProxy/Iterator.pm b/lib/Config/HAProxy/Iterator.pm
index 6b0208e..6d80dd9 100644
--- a/lib/Config/HAProxy/Iterator.pm
+++ b/lib/Config/HAProxy/Iterator.pm
@@ -60,10 +60,78 @@ sub next {
}
return $self->{_cur};
}
1;
+__END__
+
+=head1 NAME
+
+Config::HAProxy::Iterator - Iterate over objects in the parse tree
+
+=head1 SYNOPSIS
+
+ $cfg = Config::HAProxy->new->parse;
+ $itr = $cfg->iterator(inorder => 1);
+ while (defined(my $node = $itr->next)) {
+ # Do something with $node
+ }
+
+=head1 DESCRIPTION
+
+The iterator object provides a method for iterating over all nodes in the
+HAProxy parse tree. The object is returned by the B<iterator> method of
+B<Config::HAProxy> and B<Config::HAProxy::Node> objects. The method takes
+as optional argument the keyword specifying the order in which the tree nodes
+should be traversed. This keyword can be one of the following:
+
+=over 4
+
+=item B<recursive =E<gt> 0>
+
+No recursion. The traversal will not descend into section nodes. This is the
+default.
+
+=item B<inorder =E<gt> 1>
+
+The nodes will be traversed in the inorder manner, i.e. the section node
+will be visited first, and all its sub-nodes after it.
+
+=item B<postorder =E<gt> 1>
+
+The nodes will be traversed in the postorder manner, i.e. for each section
+node, its sub-nodes will be visited first, and the node itself afterward.
+
+=back
+
+=head1 CONSTRUCTOR
+
+Note: This section is informative. You never need to create
+B<Config::HAProxy::Iterator> objects explicitly. Please use the B<iterator>
+method of B<Config::HAProxy> or B<Config::HAProxy::Node> class objects.
+
+ $itr = new Config::HAProxy::Iterator($node, %rec);
+
+Returns new iterator object for traversing the tree starting from B<$node>,
+which must be a B<Config::HAProxy::Node> object. Optional B<%rec> is one of
+the keywords discussed above, in section B<DESCRIPTION>.
+
+=head1 METHODS
+
+=head2 next
+
+ $node = $itr->next;
+
+Returns next node in the traversal sequence. If all nodes were visited, returns
+B<undef>.
+
+=head1 SEE ALSO
+
+B<HAProxy::Config>, B<HAProxy::Config::Node>.
+
+=cut
+
diff --git a/lib/Config/HAProxy/Node.pm b/lib/Config/HAProxy/Node.pm
index be09538..8210067 100644
--- a/lib/Config/HAProxy/Node.pm
+++ b/lib/Config/HAProxy/Node.pm
@@ -94,6 +94,143 @@ sub is_root { 0 }
sub is_section { 0 }
sub is_statement { 0 }
sub is_empty { 0 }
sub is_comment { 0 }
1;
+__END__
+
+=head1 NAME
+
+Config::HAProxy::Node - Abstract HAProxy configuration node
+
+=head1 DESCRIPTION
+
+The class B<Config::HAProxy::Node> represents an abstract node in the
+HAProxy configuration parse tree. It serves as a base class for classes
+representing configuration tree, section, simple statement, comment and
+empty line.
+
+=head1 CONSTRUCTOR
+
+ $obj = new Config::HAProxy::Node(%args);
+
+Returns new object. B<%args> can contain the following keys:
+
+=over 4
+
+=item B<kw>
+
+Configuration keyword (string),
+
+=item B<argv>
+
+Reference to the list of arguments.
+
+=item B<orig>
+
+Original text as read from the configuration file.
+
+=item B<locus>
+
+Locus (a B<Text::Locus> object) where this statement occurred.
+
+=item B<parent>
+
+Parent node.
+
+=back
+
+=head1 METHODS
+
+=head2 B<kw>, B<argv>, B<orig>, B<locus>, B<parent>
+
+These methods return the corresponding field of the node. When called
+with an argument, they set the field prior to returning it. The B<argv>
+method returns array of strings and takes as its argument a reference to
+the array of strings:
+
+ @a = $node->argv;
+
+ $node->argv([@a]);
+
+=head2 index
+
+Index (0-based) of this node in the parent node.
+
+=head2 arg
+
+ $a = $node->arg($n)
+
+Returns the B<$n>th argument (0-based) from the argument list.
+
+=head2 drop
+
+ $node->drop;
+
+Removes this node and destroys it.
+
+=head2 iterator
+
+ $itr = $node->iterator(@args);
+
+Returns the iterator for this node. See B<Config::HAProxy::Iterator> for
+a detailed discussion.
+
+=head2 depth
+
+ $n = $node->depth;
+
+Returns the depth of this node in the configuration tree. Depth is the
+number of parent nodes between the root of tree and this node. Top-level
+nodes have depth 0.
+
+=head2 root
+
+ $root_node = $node->root;
+
+Returns the root node of the parse tree this node belongs to.
+
+=head2 as_string
+
+ $s = $node->as_string;
+
+Returns canonical string representation of this node. The canonical
+representation consists of the keyword followed by arguments delimited
+with horizontal space characters.
+
+=head1 ABSTRACT METHODS
+
+Derived classes must overload at least one of the following methods:
+
+=head2 is_root
+
+True if the node is a root node, false otherwise.
+
+=head2 is_section
+
+True if the node represents a section (i.e. contains subnodes).
+
+=head2 is_statement
+
+True if the node is a simple statement.
+
+=head2 is_empty
+
+True if the node represents an empty line.
+
+=head2 is_comment
+
+True if the node represents a comment.
+
+=head1 SEE ALSO
+
+B<Config::HAProxy::Node::Comment>,
+B<Config::HAProxy::Node::Empty>,
+B<Config::HAProxy::Node::Root>,
+B<Config::HAProxy::Node::Section>,
+B<Config::HAProxy::Node::Statement>,
+B<Config::HAProxy::Iterator>,
+B<Config::HAProxy>,
+B<Text::Locus>.
+
+=cut
diff --git a/lib/Config/HAProxy/Node/Comment.pm b/lib/Config/HAProxy/Node/Comment.pm
index 5d7ef88..17a513b 100644
--- a/lib/Config/HAProxy/Node/Comment.pm
+++ b/lib/Config/HAProxy/Node/Comment.pm
@@ -1,6 +1,37 @@
package Config::HAProxy::Node::Comment;
use parent 'Config::HAProxy::Node';
+=head1 NAME
+
+Config::HAProxy::Node::Comment - comment node in HAProxy configuration
+
+=head1 DESCRIPTION
+
+Objects of this class represent comments in HAProxy configuration file.
+
+=head1 METHODS
+
+=head2 is_comment
+
+Returns true.
+
+=head2 orig
+
+Returns original line as it appeared in the configuration file.
+
+=head2 locus
+
+Returns the location of this statement in the configuration file (the
+B<Text::Locus> object).
+
+=head1 SEE ALSO
+
+B<Config::HAProxy::Node>, B<Text::Locus>.
+
+=cut
+
sub is_comment { 1 }
1;
+
+
diff --git a/lib/Config/HAProxy/Node/Empty.pm b/lib/Config/HAProxy/Node/Empty.pm
index bee0f2e..19ce5da 100644
--- a/lib/Config/HAProxy/Node/Empty.pm
+++ b/lib/Config/HAProxy/Node/Empty.pm
@@ -1,6 +1,35 @@
package Config::HAProxy::Node::Empty;
use parent 'Config::HAProxy::Node';
+=head1 NAME
+
+Config::HAProxy::Node::Empty - empty HAProxy configuration node
+
+=head1 DESCRIPTION
+
+Objects of this class represent empty lines in HAProxy configuration file.
+
+=head1 METHODS
+
+=head2 is_empty
+
+Always true.
+
+=head2 orig
+
+Returns original line as it appeared in the configuration file.
+
+=head2 locus
+
+Returns the location of this statement in the configuration file (the
+B<Text::Locus> object).
+
+=head1 SEE ALSO
+
+B<Config::HAProxy::Node>, B<Text::Locus>.
+
+=cut
+
sub is_empty { 1 }
1;
diff --git a/lib/Config/HAProxy/Node/Root.pm b/lib/Config/HAProxy/Node/Root.pm
index 656bb9f..ca6e575 100644
--- a/lib/Config/HAProxy/Node/Root.pm
+++ b/lib/Config/HAProxy/Node/Root.pm
@@ -1,31 +1,81 @@
package Config::HAProxy::Node::Root;
use strict;
use warnings;
use parent 'Config::HAProxy::Node::Section';
use Carp;
+=head1 NAME
+
+Config::HAProxy::Node::Root - root node of HAProxy configuration parse tree
+
+=head1 DESCRIPTION
+
+Objects of this class represent the topmost node in HAProxy configuration tree.
+Each parse tree contains exactly one object of this class. This node can be
+reached from every node in the tree by following its B<parent> attribute
+and is returned by the B<tree> method of B<Config::HAProxy> class.
+
+=cut
+
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
$self->{dirty} = 0;
return $self;
}
+=head1 METHODS
+
+=head2 is_root
+
+Always true.
+
+=head2 is_dirty
+
+ $bool = $node->is_dirty;
+
+Returns true if the tree is C<dirty>, i.e. it was modified since it has been
+created from the disk configuration file.
+
+=cut
+
sub is_dirty {
my $self = shift;
return $self->{dirty}
}
+=head2 mark_dirty
+
+ $node->mark_dirty;
+
+Sets the C<dirty> attribute.
+
+=cut
+
sub mark_dirty {
my $self = shift;
$self->{dirty} = 1;
}
+=head2 clear_dirty
+
+ $node->clear_dirty;
+
+Clears the C<dirty> attribute.
+
+=cut
+
sub clear_dirty {
my $self = shift;
$self->{dirty} = 0;
}
-
+
+=head1 SEE ALSO
+
+B<Config::HAProxy::Node>.
+
+=cut
+
1;
diff --git a/lib/Config/HAProxy/Node/Section.pm b/lib/Config/HAProxy/Node/Section.pm
index c332155..3ef1cf0 100644
--- a/lib/Config/HAProxy/Node/Section.pm
+++ b/lib/Config/HAProxy/Node/Section.pm
@@ -1,41 +1,113 @@
package Config::HAProxy::Node::Section;
use strict;
use warnings;
use parent 'Config::HAProxy::Node';
use Carp;
+=head1 NAME
+
+Config::HAProxy::Node::Section - HAProxy configuration section
+
+=head1 DESCRIPTION
+
+Objects of this class represent a C<section> in the HAProxy configuration file.
+A section is a statement that can contain sub-statements. The following
+statements form sections: B<global>, B<defaults>, B<frontend>, and B<backend>.
+
+=cut
+
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
$self->{_tree} = [];
return $self;
}
+=head1 ATTRIBUTES
+
+=head2 is_section
+
+Always true.
+
+=cut
+
sub is_section { 1 }
+=head1 METHODS
+
+=head2 kw
+
+Returns the configuration keyword.
+
+=head2 argv
+
+Returns the list of arguments to the configuration keyword.
+
+=head2 arg
+
+ $s = $node->arg($n)
+
+Returns the B<$n>th argument.
+
+=head2 orig
+
+Returns original line as it appeared in the configuration file.
+
+=head2 locus
+
+Returns the location of this statement in the configuration file (the
+B<Text::Locus> object).
+
+=head2 append_node
+
+ $section->append_node(@nodes);
+
+Takes a list of objects of B<Config::HAProxy::Node> derived classes as
+arguments. Adds these objects after the last node in the subtree in this
+section.
+
+=cut
+
sub append_node {
my $self = shift;
my $n = @{$self->{_tree}};
push @{$self->{_tree}},
map {
$_->parent($self);
$_->index($n++);
$_
} @_;
}
+=head2 append_node_nonempty
+
+ $section->append_node_nonempty(@nodes);
+
+Same as B<append_node>, but adds new nodes after the last non-empty
+node in the subtree.
+
+=cut
+
sub append_node_nonempty {
my $self = shift;
my $n = $#{$self->{_tree}};
while ($n >= 0 && $self->{_tree}[$n]->is_empty) {
$n--;
}
$self->insert_node($n+1, @_);
}
-
+
+=head2 insert_node
+
+ $section->insert_node($idx, @nodes);
+
+Inserts B<@nodes> after subnode in position B<$idx> (0-based).
+
+=cut
+
sub insert_node {
my $self = shift;
my $n = shift;
my $i = $n;
splice @{$self->{_tree}}, $n, 0,
map {
@@ -45,30 +117,59 @@ sub insert_node {
} @_;
for (; $i < @{$self->{_tree}}; $i++) {
$self->{_tree}[$i]->index($i);
}
}
+=head2 delete_node
+
+ $section->delete_node($i);
+
+Deletes B<$i>th subnode from the B<$section>.
+
+=cut
+
sub delete_node {
my ($self, $n) = @_;
splice @{$self->{_tree}}, $n, 1;
for (; $n < @{$self->{_tree}}; $n++) {
$self->{_tree}[$n]->index($n);
}
$self->root->mark_dirty;
}
+=head2 tree
+
+ @nodes = $section->tree;
+
+Returns subnodes as a list of B<Config::HAProxy::Node> derived objects.
+
+ $node = $section->tree($i);
+
+Returns B<$i>th subnode from the B<$section>.
+
+=cut
+
sub tree {
my ($self, $n) = @_;
if ($n) {
return undef if $n >= @{$self->{_tree}};
return $self->{_tree}[$n];
}
return @{shift->{_tree}}
};
+=head2 ends_in_empty
+
+ $bool = $section->ends_in_empty
+
+Returns true if the last node in the list of sub-nodes in B<$section> is
+an empty node.
+
+=cut
+
sub ends_in_empty {
my $self = shift;
while ($self->is_section) {
$self = $self->tree(-1);
}
return $self->is_empty;
@@ -107,12 +208,56 @@ my %match = (
my $node = shift;
return $node->is_comment;
}
}
);
+=head2 select
+
+ @nodes = $section->select(%cond);
+
+Returns nodes from B<$section> that match conditions in B<%cond>. Valid
+conditions are:
+
+=over 4
+
+=item B<name =E<gt>> I<$s>
+
+Node matches if its keyword (B<kw>) equals I<$s>.
+
+=item B<arg =E<gt>> B<{ n =E<gt>> I<$n>, B<v> =E<gt> I<$s> B<}>
+
+Node mathches if its I<$n>th argument equals I<$s>.
+
+=item B<section =E<gt>> I<$bool>
+
+Node matches if it is (or is not, if I<$bool> is false) a section.
+
+=item B<statement =E<gt>> I<$bool>
+
+Node matches if it is (not) a simple statement.
+
+=item B<comment =E<gt>> I<$bool>
+
+Node matches if it is (not) a comment.
+
+=back
+
+Multiple conditions are checked in the order of their appearance in the
+argument list and are joined by the short-circuit logical C<and>.
+
+For example, to return all B<frontend> statements:
+
+ @fe = $section->select(name => 'frontend');
+
+To return the frontend named C<in>:
+
+ ($fe) = $section->select( name => 'frontend',
+ arg => { n => 0, v => 'in' } );
+
+=cut
sub select {
my $self = shift;
my @prog;
while (my $p = shift) {
my $arg = shift or croak "missing argument";
@@ -135,9 +280,15 @@ sub _test_node {
return 0 unless &{$f}($node);
}
}
return 1;
}
+=head1 SEE ALSO
+
+B<Config::HAProxy::Node>, B<Config::HAProxy>, B<Text::Locus>.
+
+=cut
+
1;
diff --git a/lib/Config/HAProxy/Node/Statement.pm b/lib/Config/HAProxy/Node/Statement.pm
index abf0e50..305c529 100644
--- a/lib/Config/HAProxy/Node/Statement.pm
+++ b/lib/Config/HAProxy/Node/Statement.pm
@@ -1,6 +1,51 @@
package Config::HAProxy::Node::Statement;
use parent 'Config::HAProxy::Node';
+=head1 NAME
+
+Config::HAProxy::Node::Statement - simple statement node in HAProxy tree
+
+=head1 DESCRIPTION
+
+Objects of this class represent simple statements in HAProxy configuration
+file. A C<simple statement> is any statement excepting: B<global>, B<defaults>,
+B<frontend>, and B<backend>.
+
+=head1 METHODS
+
+=head2 is_statement
+
+Returns true.
+
+=head2 kw
+
+Returns the configuration keyword.
+
+=head2 argv
+
+Returns the list of arguments to the configuration keyword.
+
+=head2 arg
+
+ $s = $node->arg($n)
+
+Returns the B<$n>th argument.
+
+=head2 orig
+
+Returns original line as it appeared in the configuration file.
+
+=head2 locus
+
+Returns the location of this statement in the configuration file (the
+B<Text::Locus> object).
+
+=head1 SEE ALSO
+
+B<Config::HAProxy::Node>, B<Config::HAProxy>, B<Text::Locus>.
+
+=cut
+
sub is_statement { 1 }
1;

Return to:

Send suggestions and report system problems to the System administrator.