Checking if a directory exists before copying in Puppet - puppet

I am trying to get a backup of a subdirectory before deleting the parent directory by copying the subdirectory into a different location.
This is how I have done this:
exec { "install_path_exists":
command => "/bin/true",
onlyif => "/usr/bin/test -d ${install_path}",
path => ['/usr/bin','/usr/sbin','/bin','/sbin'],
}
file { "server_backup_dir" :
ensure => 'directory',
path => "${distribution_path}/backup/server",
recurse => true,
source => "file:///${install_path}/repository/deployment/server",
require => Exec["install_path_exists"],
}
Exec checks if the directory exists, and returns true if so. The "server_backup_dir" file resource requires the "install_path_exists" exec to return true if the directory exists.
When the directory does not exist, and "install_path_exists" returns false, "server_backup_dir" executes anyway, and produces the following error.
Error: /Stage[main]/Is/File[server_backup_dir]: Could not evaluate:
Could not retrieve information from environment production source(s)
file:////usr/local/{project_location}/repository/deployment/server
What is wrong with my approach, and how can I fix this? Thanks in advance.

I'll break this up into two parts, what is wrong, and how to fix it.
What is wrong with my approach ...
You are misunderstanding the 'require' line and the nature of relationships in Puppet, and also how Puppet uses the return code of the command executed in an Exec.
When you use any of the four so-called metaparameters for relationships in Puppet - those being: require, before, subscribe & notify - you tell Puppet that you want the application of one resource to be ordered in time in relation to another. (Additionally, the 'subscribe' and 'notify' respond to refresh events but that's not relevant here.)
So, when Puppet applies a catalog built from your code, it will firstly apply the Exec resource, i.e. execute the /bin/true command, if and only if the install path exists; and then it will secondly manage the server_backup_dir File resource. Note also that it will apply the File resource irrespective of whether the Exec command actually was executed; the only guarantee being that /bin/true will never be run after the File resource.
Furthermore, the return code of the command in the Exec functions differently to what you're expecting. An exit status of 0 as the /bin/true command returns only tells Puppet to allow configuration to continue; compare that to an Exec command returning a non-zero exit status, which would cause Puppet to halt execution with an error.
Here's a simple demonstration of that:
▶ puppet apply -e "exec { '/usr/bin/false': }"
Notice: Compiled catalog for alexs-macbook-pro.local in environment production in 0.08 seconds
Error: '/usr/bin/false' returned 1 instead of one of [0]
Error: /Stage[main]/Main/Exec[/usr/bin/false]/returns: change from 'notrun' to ['0'] failed: '/usr/bin/false' returned 1 instead of one of [0]
Notice: Applied catalog in 0.02 seconds
For more info, read the page I linked above carefully. It generally takes a bit of time to get your head around relationships and ordering in Puppet.
how can I fix this?
You would normally use a custom fact like this:
# install_path.rb
Facter.add('install_path') do
setcode do
Facter::Core::Execution.execute('/usr/bin/test -d /my/install/path')
end
end
And then in your manifests:
if $facts['install_path'] {
file { "server_backup_dir" :
ensure => 'directory',
path => "${distribution_path}/backup/server",
recurse => true,
source => "file:///my/install/path/repository/deployment/server",
}
}
Consults docs for more info on writing and including custom facts in your code base.
Note:
I notice at the end that you reuse $install_path in the source parameter. If your requirement is to have a map of install paths to distribution paths, you can also build a structured fact. Without knowing exactly what you're trying to do, however, I can't be sure how you would write that piece.

Related

Puppet noop When Executable does not exist yet

The following is a simplified manifest I am running:
package {'ruby2.4':
ensure => installed
}
exec { "gem2.4_install_bundler":
command => "/usr/bin/gem2.4 install bundler",
require => Package['ruby2.4']
}
Puppet apply runs this manifest correctly i.e
installs ruby2.4 package (which includes gem2.4)
Installs bundler using gem2.4
However, puppet apply --noop FAILS because puppet cannot find the executable '/usr/bin/gem2.4' because ruby2.4 is not installed with --noop.
My question is if there is a standard way to test a scenario like this with puppet apply --noop? To validate that my puppet manifest is executing correctly?
It occurs to me that I may have to parse the output and validate the order of the executions. If this is the case, is there a standard way/tool for this?
A last resort is a very basic check that the puppet at least runs, which can be determined with the --detailed-exitcodes option. (a code different to 1).
Thank you in advance
rspec-puppet is the standard tool for that level of verification. It can build a catalog from the manifest (e.g. for a class, defined type, or host) and then you can write tests to verify the contents.
In your case you could verify that the package resource exists, that the exec resource exists, and verify the ordering between them. This would be just as effective as running the agent with --noop mode and parsing the output - but easier and cheaper to run.
rspec-puppet works best with modules, so assuming you follow the setup for your module from the website (adding rspec-puppet to your Gemfile, running rspec-puppet-init), and let's say this is in a class called ruby24, a simple spec in spec/classes/ruby24_spec.rb would be:
require 'spec_helper'
describe 'ruby24' do
it { is_expected.to compile.with_all_deps }
it { is_expected.to contain_package('ruby2.4').with_ensure('installed') }
it { is_expected.to contain_exec('gem2.4_install_bundler').with_command('/usr/bin/gem2.4 install bundler') }
it { is_expected.to contain_exec('gem2.4_install_bundler').that_requires('Package[ruby2.4]') }
end

Squelch puppet state chown

I'm hoping to use puppet to manage my rc files (i.e. sharing configuration files between work and home). I keep my rc files in a subversion respository. Some machines, I have sudo privileges on, some I don't. And none of the machines are on the same network.
I have a simple puppet file:
class bashResources ( $home, $svn ) {
file { "$home/.bash" :
ensure => 'directory',
}
file { "$home/.bash/bashrc.d" :
ensure => 'directory',
}
file { "$home/.bash/bashrc.d/bashrc" :
ensure => present,
target => "$home/$svn/rc/bashrc",
}
}
node 'ubuntuwgu290' {
class { 'bashResources':
home => '/home/dshaw',
svn => 'mysvn',
}
}
I have a simple config file that I'm using to squelch some errors:
[main]
report=false
When I run puppet, I get an annoying error about not being able to execute chown:
dshaw#ubuntuwgu290:~/mysvn/rc$ puppet apply rc.pp --config ./puppet.conf
Notice: Compiled catalog for ubuntuwgu290.maplesoft.com in environment production in 0.12 seconds
Error: Failed to apply catalog: Operation not permitted # rb_file_chown - /home/dshaw/.puppet/var/state/state.yaml20170316-894-rzkggd
Error: Could not save last run local report: Operation not permitted # rb_file_chown - /home/dshaw/.puppet/var/state/last_run_summary.yaml20170316-894-l9embs
I have attempted to squelch the error by adding reports=none to my config file, but it has not been effective.
How can I squelch these errors? Alternatively, is there a more lightwieght tool for managing rc files?
Thanks,
Derek
The error is related to Puppet trying to manage its own metadata in /home/dshaw/.puppet, not any of the files enrolled in Puppet's catalog for management. This is not normally a problem, even when you run Puppet as an ordinary user. In fact, supporting this sort of thing is one of the reasons why per-user Puppet metadata exists.
The files that Puppet is trying to chown do not already belong to you (else Puppet would not be trying to chown them), but they should belong to you, where "you" means the puppet process's (e)UID and (e)GID. You might be able to solve the problem by just removing Puppet's state directory, and letting it rebuild it on the next run. Alternatively, you might be able to perform or arrange for a manual chown such as Puppet is trying to perform.
On the other hand, it's unclear how this situation arose in the first place, and some of the mechanisms I can imagine would render those suggestions ineffective.

puppet - How to debug and test to see if your module is working properly

I wrote a simple module to install a package (BioPerl) on a Ubuntu VM. The whole init.pp file is here:
https://gist.github.com/anonymous/17b4c31bf7309aff14dfdcd378e44f40
The problem is it doesn't work, and it gives me no feedback to let me know why it doesn't work. There are 3 simple steps in the module. I checked and it didn't do any of them. Heres the first 2:
Step 1: Download an archive and save it to /usr/local/lib
exec { 'bioperl-download':
command => "sudo /usr/bin/wget --no-check-certificate -O ${archive_path} ${package_uri}",
require => Package['wget']
}
Step 2: Extract the archive
exec { 'bioperl-extract':
command => "sudo /usr/bin/tar zxvf ${archive_path} --directory ${install_path}; sudo rm ${archive_path}",
require => Exec['bioperl-download']
}
pretty simple. But I have no idea where the problem is because I can't see what its doing. The provisioner is set to verbose mode, and here are the output lines for my module:
==> default: Notice: /Stage[main]/Bioperl/Exec[bioperl-download]/returns: executed successfully
==> default: Notice: /Stage[main]/Bioperl/Exec[bioperl-extract]/returns: executed successfully
==> default: Notice: /Stage[main]/Bioperl/Exec[bioperl-path]/returns: executed successfully
So all I know is it executed these three steps successfully. It doesn't tell me anything about whether the steps did their job properly or not. I know that it didn't download the archive to /usr/local/lib that directory, and that it didn't add an environment variable file to /usr/profile.d. Maybe the issue is the variables containing the directories are wrong. Maybe the variable containing the archives download URI is wrong. How can I find these things out?
UPDATE:
It turns out the module does work. But to improve the module (since I want to upload it to forge.puppetlabs.com, I tried implementing the changes suggested by Matt. Heres the new code:
file { 'bioperl-download':
path => "${archive_path}",
source => "http://cpan.metacpan.org/authors/id/C/CJ/CJFIELDS/${archive_name}",
ensure => "present"
}
exec { 'bioperl-extract':
command => "sudo /bin/tar zxvf ${archive_name}",
cwd => "${bioperl_target_dir}",
require => File['bioperl-download']
}
A problem: It gives me an error telling me that the source cannot be http://. I see in the docs that they do indeed allow http:// files as the source for the file resource. Maybe I'm using an older version of puppet?
I want to try out the puppet-archive module, but I'm not sure how I can set it as a required dependency. By that, I mean how I can make sure its installed first. Do I need to get my module to download the module from github and save it to the modules directory? Or is there a way to let puppet install it automatically? I added it as a dependency to the metadata.json file, but that doesn't install it. I know I can just get my module to download the package, but I was wondering what best practice for this is.
The initial problem you describe is acceptance testing. Verifying that the Puppet resources and code you wrote actually resulted in the desired end state you wanted is normally accomplished with Serverspec: http://serverspec.org/. For example, you can write a Puppet module to deploy an application, but you only know that Puppet did what you told it to, and not that the application actually successfully deployed. Note Serverspec is also what people generally use to solve this problem for Ansible and Chef also.
You can write a Serverspec test similar to the following to help test your module's end state:
describe file('/usr/local/lib/bioperl.tar.gz') do
it { expect(subject).to be_file }
end
describe file('/usr/profile.d/env_file') do
it { expect_subject).to be_file }
its(:content) { is_expected.to match(/env stuff/) }
end
However, your problem also seems to deal with debugging why your acceptance tests failed. For that, you need unit testing. This is normally solved with RSpec-Puppet: http://rspec-puppet.com/. I would show you how to write some tests for your situation, but I don't think you should be writing your Puppet module the way that you did, so it would render the unit tests irrelevant.
Instead, consider using a file resource with the source attribute and a HTTP URI to grab the tarball instead of an exec with wget: https://docs.puppet.com/puppet/latest/type.html#file-attribute-source. Also, you might want to consider using the Puppet archive module to assist you: https://forge.puppet.com/puppet/archive.
If you have questions on how to use these tools to provide unit and acceptance testing, or have questions on how to refactor your module, then don't hesitate to write followup questions on StackOverflow and we can help you.

puppet: Could not back up <file>: Got passed new contents for sum

I had a question I was hoping someone might have an answer to. Essentially what I'm doing is try to ensure I'm always using a fixed, slightly older version of phpunit, which I've placed in my module's file resources.
The manifest:
file
{
"/usr/bin/phpunit":
ensure => file,
owner => 'root',
group => 'root',
mode => 0755,
source => "puppet:///modules/php/phpunit"
}
Preparation: I download the current ('wrong') version of phpunit and place it in /usr/bin.
So the first run puppet succeeds:
Notice: Compiled catalog for <hostname> in environment production in 3.06 seconds
Notice: /Stage[main]/Php/File[/usr/bin/phpunit]/content: content changed '{md5}9f61f732829f4f9e3d31e56613f1a93a' to '{md}38789acbf53196e20e9b89e065cbed94'
Notice: /Stage[main]/Httpd/Service[httpd]: Triggered 'refresh' from 1 events
Notice: Finished catalog run in 15.86 seconds
Then I download the current (still 'wrong') version of phpunit and place it in /usr/bin again.
This time the puppet run fails.
Notice: Compiled catalog for <hostname> in environment production in 2.96 seconds
Error: Could not back up /usr/bin/phpunit: Got passed new contents for sum {md5}9f61f732829f4f9e3d31e56613f1a93a
Error: Could not back up /usr/bin/phpunit: Got passed new contents for sum {md5}9f61f732829f4f9e3d31e56613f1a93a
Error: /Stage[main]/Php/File[/usr/bin/phpunit]/content: change from {md5}9f61f732829f4f9e3d31e56613f1a93a to {md5}38789acbf53196e20e9b89e065cbed94 failed: Could not back up /usr/bin/phpunit: Got passed new contents for sum {md5}9f61f732829f4f9e3d31e56613f1a93a
What gives? If I delete the file ( /var/lib/puppet/clientbucket/9/f/6/1/f/7/3/2/9f61f732829f4f9e3d31e56613f1a93a/ ) from my filebucket it will work again... for the next run, but not the one after that.
What am I doing wrong?
I'd appreciate any input and thanks in advance.
Been having this error as well. I solved it with a combination of two previous answers.
Firstly I had to delete /var/lib/puppet/clientbucket on the client node by running:
sudo rm -r /var/lib/puppet/clientbucket
Just doing this will only let it run once more.
Then I had to mark the backup => false to stop it recreating the file, missing out either step failed to solve it for me. The accepted answer is incorrect by saying there is
"no solution other than upgrading".
I was able to fix the same problem by removing /var/lib/puppet/clientbucket on the client node.
This node has been running out of disk space, so puppet has probably incorrectly stored empty files there.
As a workaround, you can set backup => false in the file resource. This is a little unsafe, of course.
This has no solution other than to upgrade since there's a bug in certain versions of puppet where files containing both UTF8 and binary characters are treated wrongly, and it results in an error message.
https://tickets.puppetlabs.com/browse/PUP-1038
The ridiculously overcomplicated solution I used as a workaround is to have a .tar file in the file resource which notifies an exec which untars and places the actual executable in the correct directory, making sure the timestamp for the latter is newer than the former.
It's far from ideal but it works in cases like mine where upgrading puppet to the most current version isn't an attractive option.

Boxen project manifest isn't resolving $repo_dir

I'm trying to use Boxen to setup our dev environment. We have a number of repos that we want to pull down and run a script to get started. We landed on a convention: repos have a scripts/ directory with a bootstrap script that needs to be run.
It looks like this would be possible with the exec command. But in order to tell it what to run, I have to access the repo's directory. Other scripts use $repo_dir or ${boxen::config:srcdir}/${name}. I've tried each of these, and a number of different styles of exec, to no avail.
The Manifest
class projects::hero {
include ruby
boxen::project { 'hero':
ruby => '2.0.0',
source => 'myorg/hero'
}
->
Exec {
command => '$repo_dir/scripts/echo'
}
->
notify {'hero is running at $srcdir':}
}
This is simpler than the stated goal. The scripts need to be run within the directory they reside. So my first (and hopefully eventual) manifest would have something like this for the exec step:
->
exec { 'running bootstrap on hero':
command => '$repo_dir/scripts/bootstrap',
cwd => '$repo_dir/scripts'
}
The script
For right now, scripts/echo is super simple:
#!/bin/bash
echo "Echo File!"
touch `date`
Since the output isn't really going to be seen, we're making a file with the date so we can observe this side effect and know that the script actually ran.
Calling boxen
I just call this project directly from the manifests directory:
Chris:manifests chris$ boxen hero
The output
Warning: Scope(Class[Boxen::Environment]): Setting up 'hero'. This can be made permanent by having 'include projects::hero' in your personal manifest.
Error: Could not find resource 'command => $repo_dir/scripts/echo' for relationship from 'Boxen::Project[hero]' on node chris.local
Error: Could not find resource 'command => $repo_dir/scripts/echo' for relationship from 'Boxen::Project[hero]' on node chris.local
This is also true if I try ${boxen::config::srcdir} instead. Looking at other examples, these variables are used and seem to work. Am I calling it wrong? Is there a different variable I should be using?
I've noticed two mistakes in your manifest here:
->
Exec {
command => '$repo_dir/scripts/echo'
}
->
The first is that you've capitalized the first letter of exec. In puppet language this means you are specifying a default for all subsequent exec resource definitions (docs). This is not a resource definition itself, therefore resource ordering can not be applied, hence the error.
Another mistake is the use of single quotes in combination with variables. Single quoted strings are interpreted as literals. In other words, '$repo_dir' is interpreted literally as $repo_dir while "$repo_dir" is interpreted as the contents of the varialbe $repo_dir (docs).
Hope this helps,
Good luck

Resources