Skip to content

Commit

Permalink
Add augprint tool for creating idempotent augtool scripts (#752)
Browse files Browse the repository at this point in the history
* Add augprint tool for creating idempotent augtool scripts
  • Loading branch information
georgehansper authored Dec 3, 2022
1 parent 09b4f83 commit ff7e200
Show file tree
Hide file tree
Showing 16 changed files with 2,573 additions and 11 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ src/lexer.[ch]
src/augmatch
src/augtool
src/augparse
src/augprint
src/callgrind.*
tests/fatest
tests/leak
Expand Down
2 changes: 1 addition & 1 deletion man/Makefile.am
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

EXTRA_DIST=$(wildcard *.pod) $(wildcard *.[0-9])

man1_MANS=augtool.1 augparse.1 augmatch.1
man1_MANS=augtool.1 augparse.1 augmatch.1 augprint.1

%.1: %.pod
pod2man -c "Augeas" -r "Augeas $(VERSION)" $< > $@
4 changes: 4 additions & 0 deletions man/augmatch.pod
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,7 @@ License (LGPL)
=head1 SEE ALSO

B<Augeas> project homepage L<http://www.augeas.net/>

B<Augeas> path expressions L<https://github.com/hercules-team/augeas/wiki/Path-expressions>

L<augprint>
296 changes: 296 additions & 0 deletions man/augprint.pod
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
# vim: expandtab
=head1 NAME

augprint - create an idempotent augtool script for a given file

=head1 SYNOPSIS

augprint [--pretty|-p] [--regexp[=n]|-r[n]] [--noseq|-s] [--verbose|-v] [--lens name|-l name] [--target /target|-t /target] FILE

=head1 DESCRIPTION

B<augprint> creates an augtool script for a given B<FILE>
consisting primarily of C<set> commands.

The resulting augtool script is designed to be idempotent, and
will not result in any changes when applied to the original file.

B<augprint> replaces each numbered location in the tree with
a path-expression that uniquely identifies the position using the values
I<within> that position.

This makes the path-expression independant of the position-number,
and thereby applicable to files which in which the same data may exist at
an alternate position-number

See "Examples" for sample output

=head2 Regexp output

By default B<augprint> produces path-expressions made up of simple equality C<=> comparisions

set /files/etc/hosts/seq::*[ipaddr='127.0.0.1']/ipaddr '127.0.0.1'

The option B<--regexp> changes the output to produce regular expression comparisions

set /files/etc/hosts/seq::*[ipaddr=~regexp('127\\.0\\..*')]/ipaddr '127.0.0.1'

The minimum length I<N> of the regular expression can be specified using C<--regexp=N>

B<augprint> will choose a longer regular expression than I<N> if multiple values
would match using the I<N> character regular expression.

=head2 Limitations

=head3 Append-only

The output is based primarily on set operations.
The set operation can only:

a) change an existing value in-situ

b) append a new value after the last position in the group

This means that when an entry is re-created, it may not be in the same position as originally intended.
ie if the entry for C<192.0.2.3> does not already exist, it will be created as the I<last> entry in F</etc/hosts>

Often, such out-of-sequence entries will not matter to the resulting configuration file.
If it does matter, further manual editing of the C<augtool> script will be required.

=head3 Repeated Values

B<augprint> is not always successful in finding a path-expression which is unique to a position.
In this case B<augprint> appends a position to an expression which is not unique

This occurs in particular if there are repeated values within the file.

For an F</etc/hosts> file of

#------
192.0.2.3 defaultdns
#------

B<augprint> would produce the output

set /files/etc/hosts/#comment[.='--------'][1] '--------'
set /files/etc/hosts/seq::*[ipaddr='192.0.2.3']/ipaddr '192.0.2.3'
set /files/etc/hosts/seq::*[ipaddr='192.0.2.3']/canonical 'defaultdns'
set /files/etc/hosts/#comment[.='--------'][2] '--------'

Notice how C<#comment> paths have C<[1]> and C<[2]> appended respectively to the C<[expr]>

Other paths which do have unique path-expressions are not directly affected


=head1 OPTIONS

=over 4

=item B<-v>, B<--verbose>

Include the original numbered paths as comments in the output

=item B<-p>, B<--pretty>

Create more readable output by adding spaces and empty lines

=item B<-r>, B<-r>I<N>, B<--regexp>, B<--regexp>=I<N>

Generate regular expressions to match values,
using a minumum length of I<N> characters from the value

I<N> can be omitted and defaults to 8

=item B<-l>, B<--lens>=I<LENS>

Use I<LENS> for the given file; without this option, B<augprint> uses the
default lens for the file

=item B<-t> I<targetfile>, B<--target>=I<targetfile>

Generate the script for the I<FILE> specified as if its path was really I<targetfile>

This will apply the lens corresponding to I<targetfile> to I<FILE>
and modifying the resulting path-expressions of I<FILE> to correspond to I<targetfile>

I<targetfile> must be the full path name, starting with a '/'

See "Examples" for how B<--target> can be used in practice

=item B<-s>, B<--noseq>

Do not use C<seq::*> in the output, use C<*> instead.
For example

set /files/etc/hosts/*[ipaddr='127.0.0.1']/ipaddr '127.0.0.1'

IMPORTANT: The resulting output will no longer I<create> a new entry
for C<127.0.0.1> if none already exists. The C<--noseq> option exists so
that the resulting paths can be used with augeas versions prior to 1.13.0
(subject to this limitation)

=back

=head1 EXAMPLES

These examples use the following F</etc/hosts> file as the I<FILE>

127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
192.0.2.3 dns-a
192.0.2.4 dns-b

The output from C<augtool 'print /files/etc/hosts'> would be

/files/etc/hosts
/files/etc/hosts/1
/files/etc/hosts/1/ipaddr = "127.0.0.1"
/files/etc/hosts/1/canonical = "localhost"
/files/etc/hosts/1/alias[1] = "localhost.localdomain"
/files/etc/hosts/1/alias[2] = "localhost4"
/files/etc/hosts/1/alias[3] = "localhost4.localdomain4"
/files/etc/hosts/2
/files/etc/hosts/2/ipaddr = "192.0.2.3"
/files/etc/hosts/2/canonical = "dns-a"
/files/etc/hosts/3
/files/etc/hosts/3/ipaddr = "192.0.2.4"
/files/etc/hosts/3/canonical = "dns-b"

=head2 Default output

C<augprint /etc/hosts>

set /files/etc/hosts/seq::*[ipaddr='127.0.0.1']/ipaddr '127.0.0.1'
set /files/etc/hosts/seq::*[ipaddr='127.0.0.1']/canonical 'localhost'
set /files/etc/hosts/seq::*[ipaddr='127.0.0.1']/alias[.='localhost.localdomain'] 'localhost.localdomain'
set /files/etc/hosts/seq::*[ipaddr='127.0.0.1']/alias[.='localhost4'] 'localhost4'
set /files/etc/hosts/seq::*[ipaddr='127.0.0.1']/alias[.='localhost4.localdomain4'] 'localhost4.localdomain4'
set /files/etc/hosts/seq::*[ipaddr='192.0.2.3']/ipaddr '192.0.2.3'
set /files/etc/hosts/seq::*[ipaddr='192.0.2.3']/canonical 'dns-a'
set /files/etc/hosts/seq::*[ipaddr='192.0.2.4']/ipaddr '192.0.2.4'
set /files/etc/hosts/seq::*[ipaddr='192.0.2.4']/canonical 'dns-b'

=head2 Verbose output

C<augprint --verbose /etc/hosts>

# /files/etc/hosts
# /files/etc/hosts/1
# /files/etc/hosts/1/ipaddr '127.0.0.1'
set /files/etc/hosts/seq::*[ipaddr='127.0.0.1']/ipaddr '127.0.0.1'
# /files/etc/hosts/1/canonical 'localhost'
set /files/etc/hosts/seq::*[ipaddr='127.0.0.1']/canonical 'localhost'
# /files/etc/hosts/1/alias[1] 'localhost.localdomain'
set /files/etc/hosts/seq::*[ipaddr='127.0.0.1']/alias[.='localhost.localdomain'] 'localhost.localdomain'
...

=head2 Rexexp output

C<augprint --regexp=4 /etc/hosts>

set /files/etc/hosts/seq::*[ipaddr=~regexp('127\\..*')]/ipaddr '127.0.0.1'
set /files/etc/hosts/seq::*[ipaddr=~regexp('127\\..*')]/canonical 'localhost'
set /files/etc/hosts/seq::*[ipaddr=~regexp('127\\..*')]/alias[.=~regexp('localhost\\..*')] 'localhost.localdomain'
set /files/etc/hosts/seq::*[ipaddr=~regexp('127\\..*')]/alias[.=~regexp('localhost4')] 'localhost4'
set /files/etc/hosts/seq::*[ipaddr=~regexp('127\\..*')]/alias[.=~regexp('localhost4\\..*')] 'localhost4.localdomain4'
set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.3')]/ipaddr '192.0.2.3'
set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.3')]/canonical 'dns-a'
set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.4')]/ipaddr '192.0.2.4'
set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.4')]/canonical 'dns-b'

Note that although a I<minimum> length of 4 has been specified, B<augprint> will choose longer regular expressions
as needed to ensure a unique match.

=head2 Using --lens

If a file is not assocatiated with a lens by default, I<--lens lensname> can be used to specify a lens.

When I<--lens> is specified, the output is prefixed with suitable C<transform> and C<load-file> statements,
as required to complete the augtool script, and a I<setm> statement to exclude other autoloaded lenses.

C<augprint --lens shellvars /etc/skel/.bashrc>

setm /augeas/load/*[incl='/etc/skel/.bashrc' and label() != 'shellvars']/excl '/etc/skel/.bashrc'
transform shellvars incl /etc/skel/.bashrc
load-file /etc/skel/.bashrc
set /files/etc/skel/.bashrc/#comment[.='.bashrc'] '.bashrc'
set /files/etc/skel/.bashrc/#comment[.='Source global definitions'] 'Source global definitions'
set /files/etc/skel/.bashrc/@if[.='[ -f /etc/bashrc ]'] '[ -f /etc/bashrc ]'
set /files/etc/skel/.bashrc/@if[.='[ -f /etc/bashrc ]']/.source '/etc/bashrc'
set /files/etc/skel/.bashrc/#comment[.='User specific environment'] 'User specific environment'
...

The lenses C<simplelines> C<shellvars> are most commonly useful as lenses for files that do not have
a specific lens

=head2 Using --target

In order to prepare an augtool script intended for a given file, it may be desired to
copy the file to another location, rather than editting the original file.

The option I<--target> simplifies this process.

a) copy F</etc/hosts> to a new location

cp /etc/hosts ~

b) edit F<~/hosts> to suit

echo '192.0.2.7 defaultdns' >> ~/hosts

c) Run C<augprint> as follows

augprint --target /etc/hosts ~/hosts

d) Copy the relevant part of the output to an augtool script or other Augeas client

set /files/etc/hosts/seq::*[ipaddr='192.0.2.7']/ipaddr '192.0.2.7'
set /files/etc/hosts/seq::*[ipaddr='192.0.2.7']/canonical 'defaultdns'

Notice that C<augprint> has generated paths corresponding to I<--target> (/etc/hosts) instead of the I<FILE> argument (~/hosts)



=head1 ENVIRONMENT VARIABLES

=over 4

=item B<AUGEAS_ROOT>

The effective file system root, defaults to '/'.

=item B<AUGEAS_LENS_LIB>

Colon separated list of directories with lenses. Directories specified here
are searched before the default directories F</usr/share/augeas/lenses> and
F</usr/share/augeas/lenses/dist>

=back

=head1 EXIT STATUS

The exit status is 0 when the command was successful
and 1 if any error occurred.

=head1 FILES

Lenses and schema definitions in F</usr/share/augeas/lenses> and
F</usr/share/augeas/lenses/dist>

=head1 AUTHOR

George Hansper <[email protected]>

=head1 COPYRIGHT AND LICENSE

Copyright 2022 George Hansper

Augeas (and augprint) are distributed under the GNU Lesser General Public
License (LGPL), version 2.1

=head1 SEE ALSO

augtool(1)

B<Augeas> project homepage L<https://www.augeas.net/>

B<Augeas> path expressions L<https://github.com/hercules-team/augeas/wiki/Path-expressions>
26 changes: 17 additions & 9 deletions man/augtool.1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.\" Automatically generated by Pod::Man 4.09 (Pod::Simple 3.35)
.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.43)
.\"
.\" Standard preamble:
.\" ========================================================================
Expand Down Expand Up @@ -54,16 +54,20 @@
.\" Avoid warning from groff about undefined register 'F'.
.de IX
..
.if !\nF .nr F 0
.if \nF>0 \{\
. de IX
. tm Index:\\$1\t\\n%\t"\\$2"
.nr rF 0
.if \n(.g .if rF .nr rF 1
.if (\n(rF:(\n(.g==0)) \{\
. if \nF \{\
. de IX
. tm Index:\\$1\t\\n%\t"\\$2"
..
. if !\nF==2 \{\
. nr % 0
. nr F 2
. if !\nF==2 \{\
. nr % 0
. nr F 2
. \}
. \}
.\}
.rr rF
.\"
.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
.\" Fear. Run. Save yourself. No user-serviceable parts.
Expand Down Expand Up @@ -129,7 +133,7 @@
.\" ========================================================================
.\"
.IX Title "AUGTOOL 1"
.TH AUGTOOL 1 "2021-05-26" "Augeas 1.13.0" "Augeas"
.TH AUGTOOL 1 "2022-10-24" "Augeas 1.13.0" "Augeas"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
Expand Down Expand Up @@ -441,4 +445,8 @@ License (\s-1LGPL\s0)
.IX Header "SEE ALSO"
\&\fBAugeas\fR project homepage <http://www.augeas.net/>
.PP
\&\fBAugeas\fR path expressions <https://github.com/hercules\-team/augeas/wiki/Path\-expressions>
.PP
augparse
.PP
augprint
4 changes: 4 additions & 0 deletions man/augtool.pod
Original file line number Diff line number Diff line change
Expand Up @@ -390,4 +390,8 @@ License (LGPL)

B<Augeas> project homepage L<http://www.augeas.net/>

B<Augeas> path expressions L<https://github.com/hercules-team/augeas/wiki/Path-expressions>

L<augparse>

L<augprint>
Loading

0 comments on commit ff7e200

Please sign in to comment.