define replace_line($file, $pattern, $replacement) {
$pattern_no_slashes = slash_escape($pattern)
$replacement_no_slashes = slash_escape($replacement)
exec { "/usr/bin/perl -pi -e 's/^.*$pattern_no_slashes.*$/
$replacement_no_slashes/' '$file'":
onlyif => "/usr/bin/perl -ne 'BEGIN { \$ret = 1; } \$ret = 0 if /
$pattern_no_slashes/ && ! /$replacement_no_slashes/ ; END { exit \
$ret; }' '$file'",
}
}
Give that a shot.
Adam
--
HJK Solutions - We Launch Startups - http://www.hjksolutions.com
Adam Jacob, Senior Partner
T: (206) 508-4759 E: ad...@hjksolutions.com
> A question, to save time:
> has someone already written a reusable custom type that finds a string
> in a file the replace its entire line with a specified line?
I have written a few extension types to Puppet for doing various
manipulations of lines in text files. I have planned to clean
them up and post them, but haven't had the time to do so yet.
Basically, they are:
- delete_lines, for deleting lines matching a regexp
- ensure_lines, for inserting and replacing a line
- regexp_replace_lines, for replacing (parts of) lines
- replace_sections, for replacing entire sections of lines
I can probably put them up as they are on a website sometime
tomorrow, if you want, but they really need some cleanup, both
in terms of their interface (names, parameters), and internals
(I only know a little Ruby, and too little of the internal
interfaces in Puppet).
/Thomas Bellman
> Thomas,
> any working sample, even if not cleaned up, is welcomed.
> I managed to have a working type using Adam's code: it works with
> plain strings but there are some things to fix with special chars
> like $ and ' ( I must ascii encode them and the command that checks if
> they are already there doesn't work well).
> Since I'm busy with other configurations I've left this part in s not
> fully cleaned state, but I can reconsider any other way to obtain the
> same result.
>
> I think that a well tested, with standard naming conventions, set of
> functions / types for similar activities (string substitution, lines
> editing and so ono) could be very interesting for the community and a
> good brick to build a community common modules infrastructure.
OK, here goes. I am attaching a snapshot of what we have to this
message as a gzip:ed tar archive. There is some documentation of
the types and functions inline in the source code. I have also
written a couple of definitions using these types, that work on a
somewhat higher level, that I'm also enclosing.
The types might seem to be a bit overgeneralized, with their
group and section parameters, but that is because I want to use
them as building blocks in definitions that work on structured
text files.
If you have any questions on how to use them, feel free to ask.
And if you have suggestions for improvement, both of the code and
of the design, I am very interrested.
/Thomas Bellman
> For the moment, starting from zero ruby knowledge, not great affinity
> with regexps and some cuts of puppet code found around, if have done
> something like this:
[snipped for brevity]
Feeding patterns to sed via a shell escape is non-trivial. You
need to quote things *both* for sed and for the shell. However,
your function regexp_escape() quotes things for Ruby regexps, and
a little bit for shell. Since sed and Ruby does not use the same
syntax for regexps, things will break. For instance, Ruby uses
"(" and ")" for grouping, while sed uses "\(" and "\)".
I'm fairly adept at using shell, sed, and regexps in general (in
lots of different programs, all having their own syntaxes and
quirks), and I seriously recommend against trying to do this kind
of thing with shell escapes; it's painful getting it right.
If I understand your examples right, this is how to do them with
my types:
define fixedstring_replace_lines($file, $pattern, $replacement)
{
$pe = regexp_quote($pattern)
$pe2 = ".*${pe}.*"
regexp_replace_lines {
"fixed--$title--$file--$pattern":
file => $file,
pattern => $pe2,
replacement => $replacement;
}
}
fixedstring_replace_lines {
mailwatch_configdb_host:
file => $mailwatchconf,
pattern => "my(\$db_host)",
replacement => "my(\$db_host) = '$mailwatch_mysqlhost'; #Puppet!";
}
This, however, assumes that the Puppet variable $mailwatch_mysqlhost
does not contain any characters that have special meaning within
Perl strings, or within Ruby replacement strings (thus, avoid
apostrophes and backslashes there).
Another way would be:
ensure_line {
mailwatch_configdb_host:
file => $mailwatchconf,
line => "my(\$db_host) = '$mailwatch_mysqlhost'; #Puppet!",
pattern => ".*my\\(\$db_host\\)";
}
This has the feature that if there is no line 'my($db_host)' in
the $mailwatchconf file, one will be added. regexp_replace_lines
won't do that. There are parameters that control where the line
is added.
Also, the line parameter to ensure_line is a fixed string, and
will not be interpreted for Ruby replacement escapes (see
http://www.ruby-doc.org/core/classes/String.html#M000831); you
will of course still need to watch out for Perl metacharacters.
I could have used regexp_quote() here also, and I would consider
making a special define for this:
define mailwatch_setting($value)
{
$l = sprintf("my(\$%s) = '%s'; #Puppet was here!",
$name, $value)
$p = regexp_quote("my(\$$name)")
ensure_line {
"mailwatch--$name":
file => $mailwatchconf,
line => $l,
pattern => ".*$p.*";
}
}
mailwatch_setting {
db_host: value => $mailwatch_mysqlhost;
db_name: value => $mailwatch_mysqldbname;
db_user: value => $mailwatch_mysqluser;
db_pass: value => $mailwatch_mysqlpassword;
}
If I wanted to be extra safe, I would create a custom function
'perl_string_quote()' that performed the proper quoting of
$value in the mailwatch_setting definition.
I hope these examples are of some help. (I haven't tested them,
so I hope I haven't made some foolish mistakes.)
/Thomas Bellman
>> This, however, assumes that the Puppet variable $mailwatch_mysqlhost
>> does not contain any characters that have special meaning within
>> Perl strings, or within Ruby replacement strings (thus, avoid
>> apostrophes and backslashes there).
> Erm sorry for ignorance, apostrophe is a single quote?
Yes: the ' character (ASCII code 0x27).
> If so, I fear I
> have to use them in some cases.
Then you need to quote them properly for Perl. (At least I *assume*
that it is Perl that is going to interpret the MailWatch.pm file;
the syntax looks like Perl, and the .pm suffix is often used by
Perl libraries.) Note that the problem here is that if you get a
line saying
my($foo) = 'foo'bar';
in a Perl script, then Perl will complain when trying to execute
it:
$ cat test.pl
#! /usr/bin/perl
my($foo) = 'foo'bar';
print "$foo\n";
$ perl test.pl
Bad name after bar' at test.pl line 2.
My Puppet types will gladly create such a line:
$ cat test.pp
node default {
# Note that the current implementation of ensure_line implicitly
# anchors the pattern regexp to the start of the line, but *not*
# to the end of the line.
ensure_line {
test1:
file => "/tmp/syntaxerror.pl",
line => "my(\$foo) = 'foo'bar';",
pattern => regexp_quote("my(\$foo)");
test2:
file => "/tmp/syntaxerror.pl",
line => 'my($gazonk) = \'gazonk\'del\';',
pattern => regexp_quote('my($gazonk)');
}
}
$ >/tmp/syntaxerror.pl
$ RUBYLIB=~/puppetlib/ruby puppet --libdir=$HOME/puppetlib test.pp
notice: //Node[default]/Ensure_line[test1]/line: line changed '' to 'my($foo) = 'foo'bar';'
notice: //Node[default]/Ensure_line[test2]/line: line changed '' to 'my($gazonk) = 'gazonk'del';'
$ cat /tmp/syntaxerror.pl
my($foo) = 'foo'bar';
my($gazonk) = 'gazonk'del';
> I wonder, and I ask, if you think it could be a good idea to ascii
> encode all the pattern matching string that should be used, withouth
> being interpretred by the sed/perl substitution command.
> Something like $pe =ascii_encode($pattern)
> I've no idea on how to make this ascii_encode function in ruby, I
> think it should not be difficult and probably there's already an handy
> class.
I think you are confused. In my opinion, it is a really bad idea
to shell out to sed in order to edit files from Puppet. Don't go
that way. The quoting problems are much too painful to make it
worth it; write native Puppet types instead (or use mine :-). My
types do *not* use sed, and not shell either, and thus avoids
much of the quoting pain. Not having to fork and exec a shell
and a sed process gives better performance also.
You may still need to quote the pattern parameter (and other
regexp parameters), depending on how you want them interpreted,
but my regexp_quote() function can do that for you.
If you absolutely insist on doing it the foolish way (calling sed
via the exec type), then I'm not going to help; you will have to
build your own gun to shoot yourself in the foot. :-) (And the
name 'ascii_encode()' is a rather bad name too; most regexps are
already in ASCII. ascii_encode() sounds more like something to
encode things in Base64, uuencode, Quoted unPrintable, or UTF-7.)
> So before starting to use any of the code you provided I prefer to
> have your explicit permission.
Absolutely! Go ahead!
> I think the best solution in the mid term is to have a common public
> repository of shared code (erm, have I've already talked about
> that ? :-)
Even better would be to get resource types like these into core
Puppet. I think editing of text files is important enough to
warrant that. Yes, types managing higher-level resources are
better than these low-level text editing operations, but it is
unrealistic to have Puppet natively support all the thousands of
strange config file formats that already exist, and the hundreds
that will be created just in the near future. Thus I believe it
is important to have these kinds of powerful low-level operations
in Puppet, and have them work reliably (unlike the recipes using
perl, grep and/or sed that people use today).
I would be glad to help in both specifying and implementing them
(but note that I have only rudimentary knowledge about Ruby and
about Puppet internals).
/Thomas Bellman
> Even better would be to get resource types like these into core
> Puppet. I think editing of text files is important enough to
> warrant that.
We have a native FileLine type we've been working on, and as soon
we're happy we've ironed out any bugs, we're planning to release it,
which should be relatively soon.
usage is like:
fileLine { "networkmanager":
path => "/etc/default/NetworkManager",
text => "exit 0",
ensure => present,
}
fileLine { "open_file_limit_hard":
path => "/etc/security/limits.conf",
text => "* hard nofile $open_file_limit",
match => '^\*\s+hard\s+nofile',
ensure => present,
replace => true,
regexp => true,
}
I tend to think of virtual types via definitions as a good way to
scaffold a new type if it can be done with a cluster of execs, but the
longer term goal is to always move to native types in Ruby.
--
Nigel Kersten
Systems Administrator
MacOps
> I tend to think of virtual types via definitions as a good way to
> scaffold a new type if it can be done with a cluster of execs, but the
> longer term goal is to always move to native types in Ruby.
Yeah, it can be good for prototyping new types. With the text
manipulations I wanted to do, however, it quickly became much too
painful getting the quoting right. So I started learning Ruby and
wrote them as native types instead. That way I could also make
the types much more powerful than I would have ever contemplated
doing with exec.
> We have a native FileLine type we've been working on, and as soon
> we're happy we've ironed out any bugs, we're planning to release it,
> which should be relatively soon.
I'm interrested!
> fileLine { "networkmanager":
> path => "/etc/default/NetworkManager",
> text => "exit 0",
> ensure => present,
> }
>
> fileLine { "open_file_limit_hard":
> path => "/etc/security/limits.conf",
> text => "* hard nofile $open_file_limit",
> match => '^\*\s+hard\s+nofile',
> ensure => present,
> replace => true,
> regexp => true,
> }
I assume this means to replace an existing line that matches the
'match' parameter? Does it add the line if nothing matches? What
does the match parameter mean if you say 'replace=>false'?
Do you have ways of telling fileLine *where* to put the line if it
needs to add the line? Or of only affecting matching lines at some
positions? I have found it absolutely essential to do that in some
cases. Think of managing parameters in xinetd config files; those
*must* be within the { and } of the required service. Or managing
parameters in /etc/ssh/ssh_config, which is also structured.
The types I have written always use regexps for patterns, so if you
want a constant string you need to quote it with a regexp_quote()
function (which I have also written). Having a flag parameter that
decides how to interpret another parameter doesn't feel very clean
to me.
/Thomas Bellman
> The types I have written always use regexps for patterns, so if you
> want a constant string you need to quote it with a regexp_quote()
> function (which I have also written). Having a flag parameter that
> decides how to interpret another parameter doesn't feel very clean
> to me.
No, it's rather dirty, but we didn't want to have to specify
everything as a regexp either.
Even more ideal I think is to Do The Right Thing and model specific
configuration file types as resources if you're manipulating them
regularly. We're not there yet though.
> Even more ideal I think is to Do The Right Thing and model specific
> configuration file types as resources if you're manipulating them
> regularly. We're not there yet though.
Absolutely agreed. But there will *always* be Yet Another Config
File Format that someone invents, making these low-level operations
a necessity.
/Thomas Bellman
can tools like treetop be used ?
http://www.rubyinside.com/treetop-powerful-but-easy-ruby-parser-library-701.html
anyone used this ?
--
Cordialement,
Ghislain
Shameless plug for Augeas[1] (which has Ruby bindings) In a nutshell,
Augeas gives you a language to describe config file formats; the
language is targetted at transforming files in that format into a tree,
and transforming the (changed) tree back into a file.
I've been talking about adding a native 'augeas' type for puppet, but
that has obviously not happened yet. Hopefully RSN ;) The other avenue
of integrating Augeas with Puppet is to use the Augeas API inside a
native Puppet type. That should be considerably more straightforward.
David
Is there a repository of augeas config files for various daemon (i found
the one in the augeas tgz) ? Hum perhaps this is not the best place to
ask for this...
--
Cordialement,
Ghislain
I notice that you're using "File.new", that's a bit cheating in the
sense that one cannot use puppet:// protocol. I'm not criticizing
your work at all, but I'm trying to understand what we could do to
improve the Puppet API. I tried hard to extend the file type for
achieving proper file concatenation with support for puppet://
protocol, but failed miserably and reverted to using Git hooks instead
of trying to do it with Puppet.
So your "Puppet Power User" feedback would be greatly appreciated.
--
Jean-Baptiste Quenot
http://jbq.caraldi.com/
(Digging through the headers, this seems to be a reply to my
message where I posted my types. It would be helpful if you had
quoted some of my message to give some context to the readers.)
> Interesting. This raises the question of how easy it is to write
> custom Puppet types? Did you follow the "trial and error" method to
> write those file manipulation types? How did you learn the various
> subtleties of the Puppet internals?
I read the wiki pages about creating custom types, I looked at
the source for the built-in types in Puppet, and I did a bunch of
trial and error.
> Do you find it easy to write Puppet extensions?
Not overly difficult. Much of my time was spent trying to
understand Ruby, since I hadn't used it at all before that. I
do have fairly extensive experience with programming in several
other languages, though, so I am used to looking at the source
for (to me unknown) programs to debug or extend them, which
probably helps. I suspect, though, that my code is not very
"rubyesque".
> I for one would be hoping for a more
> object-oriented API, instead of the numerous Ruby symbols or inline
> function calls, for example.
I'm not quite sure what you mean here. Much of the code in my
types deals with the actual processing and editing of the files.
In those parts, it does not need any support infrastructure from
Puppet, just ordinary Ruby file and text operations.
> I notice that you're using "File.new", that's a bit cheating in the
> sense that one cannot use puppet:// protocol.
Indeed, my types can only edit existing files on the client.
That's their entire purpose. I don't see how puppet:// support
could be used for this, but if you have any suggestions for what
parameters on what types should support puppet://, and what the
meaning should be, please tell. (I doubt that I would implement
it myself, since I don't use puppetd or puppetmasterd, just plain
'puppet', but maybe someone else could.)
> I'm not criticizing your work at all,
Coward! ;-) Please *do* criticize my work! (Just remember that
constructive and specific criticism is better than destructive
and vague criticism.)
> but I'm trying to understand what we could do to
> improve the Puppet API. I tried hard to extend the file type for
> achieving proper file concatenation with support for puppet://
> protocol, but failed miserably and reverted to using Git hooks instead
> of trying to do it with Puppet.
> So your "Puppet Power User" feedback would be greatly appreciated.
The biggest problem with the internal Puppet API, imho, is that
it is poorly documented. For instance, what values should the
sync() method return? Actually, the very existence of the sync()
method is undocumented. I had to look at existing types, and at
the code that calls the types, to find some of the information I
needed to write my types. Or at least the information I *think*
I needed; I'm not sure I did things the "kosher" way. If someone
who knows how things are supposed to be can tell me how I should
have done some things, I can fix my code, and perhaps help in
improving the documentation.
(This is actually a general problem with Puppet: there are many
"howtos" and recipes, but little reference documentation.)
/Thomas Bellman
Regards
James Turnbull
--
James Turnbull (ja...@lovedthanlost.net)
Author of:
* Pulling Strings with Puppet
(http://www.amazon.com/gp/product/1590599780/)
* Pro Nagios 2.0
(http://www.amazon.com/gp/product/1590596099/)
* Hardening Linux
(http://www.amazon.com/gp/product/1590594444/)
OK, I don't feel alone anymore. Indeed the lack of API docs (and IMHO
the lack of a true OO-API) prevents to efficiently extend Puppet. And
the fact that there is no example for a custom type in puppet is a
real sign (I mean
/usr/share/doc/puppet/examples/code/modules/sample-module on my
system). So in many cases, people revert to using the Exec type of
course, with funny Shell, Perl or any other
scripting-language-specific constructs.
Thanks for your feedback,
I agree that the internal API for the RAL is both ugly and
undocumented. I'm already making plans on fixing it, but it probably
won't get much done on it until the REST work is complete.
I think I had docs for this stuff about 2 years ago, but they kind of
died since no one seemed to care then.
I'd be glad to provide specific help on any of the internal APIs,
especially to the extent that whomever I'm helping also documents
them. Just ask for that help on puppet-dev, as I'm more likely to
follow every thread there these days (since there are so few).
--
Never try to tell everything you know. It may take too short a time.
--Norman Ford
---------------------------------------------------------------------
Luke Kanies | http://reductivelabs.com | http://madstop.com
On Tuesday 20 May 2008, Jean-Baptiste Quenot wrote:
> OK, I don't feel alone anymore. Indeed the lack of API docs (and IMHO
> the lack of a true OO-API) prevents to efficiently extend Puppet. And
> the fact that there is no example for a custom type in puppet is a
> real sign (I mean
> /usr/share/doc/puppet/examples/code/modules/sample-module on my
> system).
Hmm, all types and providers are - in a sense - "example" types. Also there is
http://reductivelabs.com/trac/puppet/wiki/CreatingCustomTypes
http://reductivelabs.com/trac/puppet/wiki/PracticalTypes
or the types at http://git.black.co.at/?p=module-mysql
The wrestling I had to do until I had the latter module, forces me to
acknowledge that the documentation is sub-par relatively to the other docs,
but it's not like there is nothing at all.
> So in many cases, people revert to using the Exec type of
> course, with funny Shell, Perl or any other
> scripting-language-specific constructs.
which is fine enough for simple things, prototyping and getting to grips with
the mental models that are needed for doing a ruby type anyways.
Regards, DavidS
- --
The primary freedom of open source is not the freedom from cost, but the free-
dom to shape software to do what you want. This freedom is /never/ exercised
without cost, but is available /at all/ only by accepting the very different
costs associated with open source, costs not in money, but in time and effort.
- -- http://www.schierer.org/~luke/log/20070710-1129/on-forks-and-forking
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)
iD8DBQFIMvMy/Pp1N6Uzh0URAm2ZAJ4op14lWXM66hlE+06srqu7CHgerQCfYOW2
sNm5pOQ2A6vjLBZ4Kig0S80=
=vTG+
-----END PGP SIGNATURE-----
That seems to keep sitting firmly on the TODO list ;)
> Is there a repository of augeas config files for various daemon (i found
> the one in the augeas tgz) ?
They are all in the lenses/ dir in the tarball - besides httpd.conf,
what other daemons do you need ? I'd be happy to help you write them.
> Hum perhaps this is not the best place to ask for this...
Maybe the Augeas list[1] is better for this. (Or #augeas on freenode)
David
[1] https://www.redhat.com/mailman/listinfo/augeas-devel
vsftpd
proftpd
dovecot
spamd
clamav
hosts.allow
comes to mind
but i first get to try augeas, perhaps i will find time this week end :)
next is to link it to a puppet type...that will be hard for me
--
Cordialement,
Ghislain
Evan
> Hi Thomas and all.. I've tried installing the libraries in the .tgz
> file and seem to have an issue to get it working. I assume everything
> is installed correctly. I do not see any puppet or puppetmaster
> errors. In the .tgz file I see and install these files in these
> folders (per a centos 4.6 setup):
> puppet/parser/functions -> /usr/lib/ruby/site_ruby/1.8/puppet/parser/
> functions/
> puppet/type -> /etc/puppet/modules/custom/plugins/puppet/type/ (which
> then gets synced up on each puppetd node to /var/lib/puppet/lib/
> plugins/puppet/type/)
If you say so. I have never used the syncing things in Puppet.
In fact, I have never used puppetd or puppetmasterd at all; I
only use plain 'puppet', and distribute my manifests, functions,
and types using NFS.
> ruby/nsc_utils.rb -> /usr/lib/ruby/site_ruby/1.8/nsc_utils.rb (I
> assume this is the correct location?)
Yes, that ought to work, I think. Myself, I set the environment
variable RUBYLIB to point to the directory where nsc_utils.rb
live. That should work for puppetd and puppetmasterd also.
In my setup, I have a subdirectory named 'Lib' under where my
manifests are, and have the directories from the tarball in it.
I.e, I have the following structure:
.
Lib/
Lib/ruby/
Lib/puppet/
Lib/puppet/parser/
Lib/puppet/parser/functions/
Lib/puppet/type/
site.pp
I then run puppet with:
# RUBYLIB="`pwd`/Lib/ruby" puppet --libdir="`pwd`/Lib" site.pp
> I created a define to configure sshd_config file. I have the notify
> there to see if it's actually running this define.
>
>
> define sshd_config($value, $service="") {
> notify {"hello":
> message => "they there",
> }
> ensure_line {"sshd_config--${title}":
> file => "/etc/ssh/sshd_config",
> line => "${title} ${value}",
> pattern => "^(#)?${title}.*$",
> notify => $service ? { "" => [], default =>
> Service[$service] };
> }
> }
>
> I then call this in a class:
>
> # make sure SSH 2 only
> sshd_config {"Protocol":
> value => "2",
> service => "sshd",
> }
> }
This looks correct.
> What appears in the the log file:
>
> Jun 10 10:24:46 empoweri00 puppetd[6225]: Starting Puppet client
> version 0.24.4
> Jun 10 10:24:55 empoweri00 puppetd[6225]: Starting catalog run
> Jun 10 10:25:09 empoweri00 puppetd[6225]: they there
> Jun 10 10:25:12 empoweri00 puppetd[6225]: Finished catalog run in
> 17.70 seconds
>
> so it's running but not making any changes to the file? Is there
> something I"m missing?
Are you sure the file doesn't already contain the correct line?
Have you tried a smaller example, something like:
node default {
ensure_line {
"null-test":
file => "/dev/null",
line => "Protocol 2",
pattern => "^(#)?Protocol.*$",
}
}
This gives me the message
notice: //Node[default]/Ensure_line[null-test]/line: line changed '' to 'Protocol 2'
when I try it using 'puppet'. Then change /dev/null to some
existing but empty file and try it again.
/Thomas Bellman