I've got a defined resource type to append a line to a file if it doesn't exist, as per below:

define appendLineToFile($file, $line, $user) {
    exec { "echo \"\\n$line\" >> \"$file\"":
        path => '/bin',
        unless => "grep -Fx \"$line\" \"$file\"",
        user => $user,

It works great most of the time, but not in situations like below, where you have double quotes that have to be escaped twice (which is terribly confusing to read):

appendLineToFile { 'Add http_proxy & https_proxy to sudoers':
    file => '/etc/sudoers',
    line => "Defaults env_keep += \\\"http_proxy https_proxy\\\"",
    user => root,

Is there a way to change the defined resource type that it doesn't require escaping as such? e.g.

    line => "Defaults env_keep += \"http_proxy https_proxy\"",

Two things:


Puppet integrates well with a tool called Augeas:

Augeas is a lovely tool that treats config files (well, anything really, but it’s mostly about config files) as trees of values. You then modify the tree as you like, and write the file back.

For files which Augeas understands, it's very easy to make programmatic changes without having to worry about comments, whitespace, parsing, or any other sticky details. Also, Augeas suppors the sudoers file format.

Sudoers includes

As of sudo version 1.7.2, it supports an includedir directive which allows you to specify a directory to extend sudoers. Files in that directory must contain snippets of valid sudoers code, and sudo will read and use them exactly as though they'd been included in the original file. I make use of this in Puppet with a custom resource:

define sudo-include( content ) {
    file { "/etc/sudoers.d/$name":
        content => $content,
        mode => 0440,
        user => root,
        group => root,


sudo-include { "moreusers":
    content => "\
joe   ALL=(ALL)  ALL

When you use the sudo-include resource, it automatically creates the file in the right place with the right permissions, and all you need to do is give it a name and content.


Handyman5 suggested using Augeas. Here is an example of how this could be done:

If you want to do have both entries on one line using +=, you can use Augeas this way:

augeas { "Add http_proxy & https_proxy to sudoers":
   context => "/files/etc/sudoers",
   # Only if no node exists for http_proxy
   onlyif  => "match Defaults/env_keep/var[. = 'http_proxy'] size==0",
   changes => [
     # Create a new Defaults line for the two variables
     "ins Defaults after Defaults[last()]",
     # Make this Defaults line a += type
     "clear Defaults[last()]/env_keep/append",
     # assign values to the two variables
     "set Defaults[last()]/env_keep/var[1] http_proxy",
     "set Defaults[last()]/env_keep/var[2] https_proxy",

However, you might want to avoid correlating the two variables, so that it will still work if one variable is already set. In that case, I would split the problem in two, creating a new Defaults node only for http_proxy, and reusing it (with a dependency) for https_proxy.


Unless you are using Debian Squeeze, which uses dash instead of bash as default shell, you can use single quotes on the parameter to echo, and use -e, which enables limited escape characters.

See also the Common modules for at least two implementations of what you are doing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.