Uncomment a line in Puppet - puppet

I'm getting into Puppet, and loving it, but am hitting my head against a wall with one small thing which I'm sure must be easier than I'm making it.
I wish to uncomment this line in the .bashrc of a user:
#force_color_prompt=yes
I've been using augeas for lots of things, but it seems as though that won't work with this.
I note that there's a file_line resource which I can use to ensure a line is present in a file, but I need the line to remain in the same place.
I don't wish to replace the .bashrc entirely with my own copy, despite seeing that this pattern is popular in Puppet, it just doesn't make sense to me as I don't want to maintain my own version between upgrades.
In sed I use this:
sed -i "s/#force_color_prompt=yes/force_color_prompt=yes/g" ~/.bashrc
Any ideas?

You could just add your sed command to an exec with an unless option using grep. Something like this (untested):
exec { 'force_color_prompt':
command => 'sed -i "s/#force_color_prompt=yes/force_color_prompt=yes/g" .bashrc'
cwd => '/home/user',
shell => true,
unless => 'grep -Fxq "force_color_prompt=yes" .bashrc',
}
Or use the file_line resource you mentioned (part of the Puppet Labs stdlib module I believe) to add the line uncommented and leave the commented line untouched.

In my experience, file_line will replace a line that matches the regex.
So to get force_color_prompt=yes vs. #force_color_prompt=yes you could:
file_line {
'color_prompt':
path => '/home/<youruser>/.bashrc',
match => 'force_color_prompt',
line => "force_color_prompt=yes",
}
Importantly, if the regex matches, the replace happens in place, ie. it will change the line, not the location of the line. If the regex does not match however, the line will be appended to the end of the file.
This experience is with Puppet 3.1.
Reference: https://forge.puppetlabs.com/puppetlabs/stdlib

The shellvar already can uncomment lines
shellvar { '.bashrc':
ensure => present,
target => "/home/${your_user}/.bashrc",
variable => 'force_color_prompt'
uncomment => true,
}
I perceive this much cleaner than using exec and calling sed.

While Augeas alone cannot do it, the shellvar provider from Augeasproviders will be able to do it, whenever this PR is merged.

Related

How to comment all the uncommented lines in a file using puppet module

I have a sshd_config configuration file which contains commented as well as uncommented lines. I want to comment all the uncommented lines in that file using puppet. Is there any optimal/simple way to do this? Or is there a way to run bash command (maybe sed to replace) via puppet? I am not sure that using bash command is a right approach.
It would be really helpful is someone guides me with this. Thanks in advance!
Is there any optimal/simple way to do this?
There is no built-in resource type or well-known module that specifically ensures that non-blank lines of a file start with a # character.
Or is there a way to run bash command (maybe sed to replace) via puppet?
Yes, the Exec resource type. That's your best bet short of writing a custom resource type.
I am not sure that using bash command is a right approach.
In a general sense, it's not. Appropriate, specific resource types are better than Exec. But when you don't have a suitable one and can't be bothered to make one, Exec is available.
It might look like this:
# The file to work with, so that we don't have to repeat ourselves
$target_file = '/etc/ssh/sshd_config'
exec { "Comment uncommented ${target_file} lines":
# Specifying the command in array form avoids complicated quoting or any
# risk of Puppet word-splitting the command incorrectly
command => ['sed', '-i', '-e', '/^[[:space:]]*[^#]/ s/^/# /', $target_file],
# If we didn't specify a search path then we would need to use fully-qualified
# command names in 'command' above and 'onlyif' below
path => ['/bin', '/usr/bin', '/sbin', '/usr/sbin'],
# The file needs to be modified only if it contains any non-blank, uncommented
# lines. Testing that via an 'onlyif' ensures that Puppet will not
# run 'sed' or (more importantly) report the file changed when it does
# not initially contain any lines that need to be commented
onlyif => [['grep', '-q', '^[[:space:]]*[^#]', $target_file]],
# This is the default provider for any target node where the rest of this
# resource would work anyway. Specifying it explicitly will lead to a more
# informative diagnostic if there is an attempt to apply this resource to
# a system to which it is unsuited.
provider => 'posix',
}
That does not rely on bash or any other shell to run the commands, but it does rely on sed and grep being available in one of the specified directories. In fact, it relies specifically on GNU sed or one that supports an -i option with the same semantics. Notably, that does not include BSD-style sed, such as you will find on macOS.

Linux sed replacing word in generic way

I would like to do a sed command in Linux to uncomment the "#auth"
Original file
#%PAM-1.0
auth sufficient pam_rootok.so
# Uncomment the following line to implicitly trust users in the "wheel" group.
#auth sufficient pam_wheel.so trust use_uid
I can write this command to do it:
sed 's/#auth.*sufficient.*pam_wheel.so trust use_uid/auth\t sufficient\t pam_wheel.so trust use_uid/' /etc/pam.d/su
But I think it is too long. Is there any better way to do this (more generic)?
I don't want to specific the line number to replace it, because if someone changed the file, the script will not run normally.
For example:
Search keyword "#auth.*sufficient.*pam_wheel.so trust use_uid", if found, replace this the word "#auth" to "auth", and then append the later wording in the line
With GNU sed, look up the -i option that allows in-place modification and then anchor the regular expression. For instance:
sed -i '/^#auth.*pam_wheel/s/^#//' INPUTFILE
will look for lines beginning with "#auth" that include "pam_wheel" later on the line and replace the "#" at the beginning with nothing.

How to prevent execution of command in ZSH?

I wrote hook for command line:
# Transforms command 'ls?' to 'man ls'
function question_to_man() {
if [[ $2 =~ '^\w+\?$' ]]; then
man ${2[0,-2]}
fi
}
autoload -Uz add-zsh-hook
add-zsh-hook preexec question_to_man
But when I do:
> ls?
After exiting from man I get:
> zsh: no matches found: ls?
How can I get rid of from message about wrong command?
? is special to zsh and is the wildcard for a single character. That means that if you type ls? zsh tries find matching file names in the current directory (any three letter name starting with "ls").
There are two ways to work around that:
You can make "?" "unspecial" by quoting it: ls\?, 'ls?' or "ls?".
You make zsh handle the cases where it does not match better:
The default behaviour if no match can be found is to print an error. This can be changed by disabling the NOMATCH option (also NULL_GLOB must not be set):
setopt NO_NOMATCH
setopt NO_NULL_GLOB
This will leave the word untouched, if there is no matching file.
Caution: In the (maybe unlikely) case that there is a file with a matching name, zsh will try to execute a command with the name of the first matching file. That is if there is a file named "lsx", then ls? will be replaced by lsx and zsh will try to run it. This may or may not fail, but will most likely not be the desired effect.
Both methods have their pro and cons. 1. is probably not exactly what you are looking for and 2. does not work every time as well as changes your shells behaviour.
Also (as #chepner noted in his comment) preexec runs additionally to not instead of a command. That means you may get the help for ls but zsh will still try to run ls? or even lsx (or another matching name).
To avoid that, I would suggest defining a command_not_found_handler function instead of preexec. From the zsh manual:
If no external command is found but a function command_not_found_handler exists the shell executes this function with all command line arguments. The function should return status zero if it successfully handled the command, or non-zero status if it failed. In the latter case the standard handling is applied: ‘command not found’ is printed to standard error and the shell exits with status 127. Note that the handler is executed in a subshell forked to execute an external command, hence changes to directories, shell parameters, etc. have no effect on the main shell.
So this should do the trick:
command_not_found_handler () {
if [[ $1 =~ '\?$' ]]; then
man ${1%\?}
return 0
else
return 1
fi
}
If you have a lot of matching file names but seldomly misstype commands (the usual reason for "Command not found" errors) you might want to consider using this instead:
command_not_found_handler () {
man ${1%?}
}
This does not check for "?" at the end, but just cuts away any last character (note the missing "\" in ${1%?}) and tries to run man on the rest. So even if a file name matches, man will be run unless there is indeed a command with the same name as the matched file.
Note: This will interfere with other tools using command_not_found_handler for example the command-not-found tool from Ubuntu (if enabled for zsh).
That all being said, zsh has a widget called run-help which can be bound to a key (in Emacs mode it is by default bound to Alt+H) and than runs man for the current command.
The main advantages of using run-help over the above are:
You can call it any time while typing a longer command, as long as the command name is complete.
After you leave the manpage, the command is still there unchanged, so you can continue writing on it.
You can even bind it to Alt+? to make it more similar: bindkey '^[?' run-help

Need some help in appending more than one line to a file in puppet

Using this manifest
file_line { 'sudo_rule':
path => '/etc/sudoers',
line => '%sudo ALL=(ALL) ALL',
}
Puppet adds a line, but I want more than one line to append to a file.
File_line only
Ensures that a given line is contained within a file. (according to the doc)
So you may add more lines to your puppet configuration file and it will work out of the box since puppet only ensures that they are there but doesn't add them continuously.
Maybe you tell us what your usecase is so that we can help you better. Another option would also be augeas to load config files into a tree like datastructure and edit the appropriate values in place.
Example usage:
augeas { "sshd_config":
context => "/files/etc/ssh/sshd_config",
changes => [
"set PermitRootLogin no",
],
}

Bash script to copy from one line and replace another line with the copy

I am looking to write a bash script for something slightly more complicated than the usual find/replace via sed. I have a book called bash Cookbook that I have been trying to glean some inspiration from but I am not getting very far.
Basically I am trying to write a script to update the version numbers in a bunch of maven pom.xml files automatically. Here is the general setup I am looking at:
<!-- TEMPLATE:BEGIN
<version>##VERSION##</version>
-->
<version>1.0.0</version>
<!-- TEMPLATE:END -->
After running the script (with the new version number 1.0.1) I'd like the file to read this instead:
<!-- TEMPLATE:BEGIN
<version>##VERSION##</version>
-->
<version>1.0.1</version>
<!-- TEMPLATE:END -->
So this would be in the actual release pom file, with 1.0.0 being the current version (and I am trying to replace it with 1.0.1 or something). Obviously the version number will be changing so there isn't a good way to do a find/replace (since the thing you want to find is variable). I am hoping to be able to write a bash script which can
replace ##VERSION## with the actual version number
delete the current version line
write the updated version line on the line before the TEMPLATE:END (while preserving the ##VERSION## in the file - possibly do this by writing template out to a temp file, doing replacement, then back in?)
I can sort of do some of this (writing out to a new file, doing replacement) using an ant script a la
<replace file="pom.xml">
<replacefilter
token="##VERSION##"
value="${version}"/>
</replace>
But I am not sure what the best ways to a.) delete the line with the old version or b.) tell it to copy the new line in the correct place are. Anyone know how to do this or have any advice?
Assuming the new version number is in a shell variable $VERSION, then you should be able to use:
sed -e '/<!-- TEMPLATE:BEGIN/,/<!-- TEMPLATE:END -->/{
s/<version>[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*<\/version>/<version>'$VERSION'<\/version>/
}'
Note that this ignores the template version line with ##VERSION##, but only matches a three-part version number that appears between the lines containing TEMPLATE:BEGIN and TEMPLATE:END, leaving everything else (including other lines containing a <version>...</version> element) alone.
You can decide how to do file overwriting (maybe your version of sed is from GNU and it does that automatically on request with the -i option), etc. You might also be able to use more powerful regular expression notations that lead to more compact matches. However, that should work on most versions of sed without change.
The steps you outlined (1-3) read as if you do not actually care to perform the replacement in accordance to the templated rules defined within the comments.
As such, here is some code that behaves verbosely as you outlined:
#!/bin/bash
file=$1
newversion=$2
sed -i $file -e "s|<version>\([^#]*\)</version>|<version>$newversion</version>|"
Run it:
chmod +x yourscript.sh
./yourscript.sh filetoupdate.xml 1.0.1
use 5.010;
use strictures;
use Perl::Version qw();
use XML::LibXML qw();
my $dom = XML::LibXML->load_xml(location => 'pox.xml');
for my $node ($dom->findnodes('//version')) {
my $version = Perl::Version->new($node->textContent);
$version->inc_subversion;
$version->stringify;
$node->removeChildNodes;
$node->appendText($version);
};
say $dom->toString;

Resources