Puppet agent considers old version of package installed using exec - puppet

I am trying to install autoconf version 2.69 by building it from source. After autoconf is installed, my intention is to build another package called crmsh from its source. I want to do this using Puppet.
I have written a few classes that enable me to do this using puppet. The class contents are below.
Download autoconf from source
class custom-autoconf {
require custom-packages-1
exec { "download_autoconf" :
command => "wget http://ftp.gnu.org/gnu/autoconf/autoconf-2.69.tar.gz ; \
tar xvfvz autoconf-2.69.tar.gz; ",
path => ["/bin","/usr/bin","/sbin","/usr/sbin"],
cwd => '/root',
unless => "test -e /root/autoconf-2.69.tar.gz",
provider => shell,
}
notify { 'autoconf_download' :
withpath => true,
name => "download_autoconf",
message => "Execution of autoconf download completed. "
}
}
Build autoconf
class custom-autoconf::custom-autoconf-2 {
require custom-autoconf
exec { "install_autoconf" :
command => "sh configure ; \
make && make install ; \
sleep 5 ; \
autoconf --version",
path => ["/bin","/usr/bin","/sbin","/usr/sbin"],
timeout => 1800,
logoutput => true,
cwd => '/root/autoconf-2.69',
onlyif => "test -d /root/autoconf-2.69",
provider => shell,
}
notify { 'autoconf_install' :
withpath => true,
name => "install_autoconf",
message => "Execution of autoconf install completed. Requires custom-autoconf class completion "
}
}
Download crmsh source
class custom-autoconf::custom-crmsh {
require custom-autoconf::custom-autoconf-2
exec { "clone_crmsh" :
command => "git clone https://github.com/crmsh/crmsh.git ; ",
path => ["/bin","/usr/bin","/sbin","/usr/sbin"],
cwd => '/root',
unless => "test -d /root/crmsh",
provider => shell,
}
notify { 'crmsh_clone' :
withpath => true,
name => "clone_crmsh",
message => "Execution of git clone https://github.com/crmsh/crmsh.git completed. Requires custom-autoconf-2 "
}
}
Build crmsh
class custom-autoconf::custom-crmsh-1 {
require custom-autoconf::custom-crmsh
exec {"build_crmsh" :
command => "pwd ; \
autoconf --version ; \
sleep 5 ; \
autoconf --version ; \
sh autogen.sh ; \
sh configure ; \
make && make install ; ",
path => ["/bin","/usr/bin","/sbin","/usr/sbin"],
require => Class['custom-autoconf::custom-crmsh'],
cwd => '/root/crmsh',
onlyif => "test -d /root/crmsh",
provider => shell,
}
notify { 'crmsh_build' :
withpath => true,
name => "build_crmsh",
message => "Execution of crmsh build is complete. Depends on custom-crmsh"
}
}
The problem is that the crmsh build fails saying autoconf version is 2.63. Notice: /Stage[main]/Custom-autoconf::Custom-crmsh-1/Exec[build_crmsh]/returns: configure.ac:11: error: Autoconf version 2.69 or higher is required
When puppet execution completes with this failure, I see that autoconf version is 2.69 (meaning, the initial build of autoconf was successful).
Could someone please tell me why Puppet is considering autoconf version as 2.63 when in the system it is 2.69. Or, am I missing something here?

It was actually my mistake. It turns out that autoconf binary was present in /usr/bin and /usr/local/bin. The custom autoconf build creates the binary in /usr/local/bin and this was not mentioned in the "path =>" section. Since this was missing, puppet was executing autoconf present in /usr/bin. Adding /usr/local/bin in path fixed the issue.
Thanks for the help.

Related

Puppet install DS with 2 different commands and only if

I am not very experienced in Puppet and I really need your help.
I have 2 servers where I need to install DS (ed. Directory Server) . Is running without error but in the servers not run these commands. What I made wrong?
exec { 'Install first DS':
command => "setup --serverId first-ds --deploymentKeyPassword ${prof_ds::constants::deployment_pwd} --deploymentKey ${prof_ds::constants::deployment_key} --rootUserDn ${prof_ds::constants::admin_user} --rootUserPassword ${prof_ds::constants::admin_pwd} --monitorUserPassword ${prof_ds::constants::monitor_pwd} --hostname ${::fqdn} --ldapPort ${prof_ds::constants::ldap_port} --ldapsPort ${prof_ds::constants::ldaps_port} --httpsPort ${prof_ds::constants::https_port} --adminConnectorPort ${prof_ds::constants::admin_port} --replicationPort ${prof_ds::constants::replication_port} --start --acceptLicense",
onlyif => ['test "${::fqdn}" == "${ds_hosts[0]}" && echo 0 || echo 1'],
environment => ["JAVA_HOME=${java_home}"],
path => ['/usr/bin', '/usr/sbin', '/bin', '/opt/opendj','/opt/opendj/bin'],
}
exec { 'Install second DS':
command => "setup --serverId second-ds --deploymentKeyPassword ${prof_ds::constants::deployment_pwd} --deploymentKey ${prof_ds::constants::deployment_key} --rootUserDn ${prof_ds::constants::admin_user} --rootUserPassword ${prof_ds::constants::admin_pwd} --monitorUserPassword ${prof_ds::constants::monitor_pwd} --hostname ${::fqdn} --ldapPort ${prof_ds::constants::ldap_port} --ldapsPort ${prof_ds::constants::ldaps_port} --httpsPort ${prof_ds::constants::https_port} --adminConnectorPort ${prof_ds::constants::admin_port} --replicationPort ${prof_ds::constants::replication_port} --bootstrapReplicationServer ${ds_hosts[0]}:${prof_ds::constants::replication_port} --start --acceptLicense",
onlyif => ['test "${::fqdn}" == "${ds_hosts[1]}" && echo 0 || echo 1'],
environment => ["JAVA_HOME=${java_home}"],
path => ['/usr/bin', '/usr/sbin', '/bin', '/opt/opendj','/opt/opendj/bin'],
}
I presume your commands are correct. So what may need to be fixed is the onlyif command, that means that the exec command is going to be ran onlyif it will return a non-zero output (more). The command will run and will interpolate when using double-quote and will not if used single quote (is a Ruby standard rather than Puppet)
The full code:
if $::hostname == $ds_hosts[0] {
exec { 'setup first-ds':
command => "setup --serverId first-ds --deploymentKeyPassword ${prof_ds::constants::deployment_pwd} --deploymentKey ${prof_ds::constants::deployment_key} --rootUserDn ${prof_ds::constants::admin_user} --rootUserPassword ${prof_ds::constants::admin_pwd} --monitorUserPassword ${prof_ds::constants::monitor_pwd} --hostname ${::fqdn} --ldapPort ${prof_ds::constants::ldap_port} --ldapsPort ${prof_ds::constants::ldaps_port} --httpsPort ${prof_ds::constants::https_port} --adminConnectorPort ${prof_ds::constants::admin_port} --replicationPort ${prof_ds::constants::replication_port} --start --acceptLicense",
onlyif => ["test '${::fqdn}' == '${ds_hosts[0]}' && echo 0 || echo 1"],
environment => ["JAVA_HOME=${java_home}"],
path => ['/usr/bin', '/usr/sbin', '/bin', '/opt/opendj','/opt/opendj/bin'],
}
}
if $::hostname == $ds_hosts[1] {
exec { 'setup second-ds':
command => "setup --serverId second-ds --deploymentKeyPassword ${prof_ds::constants::deployment_pwd} --deploymentKey ${prof_ds::constants::deployment_key} --rootUserDn ${prof_ds::constants::admin_user} --rootUserPassword ${prof_ds::constants::admin_pwd} --monitorUserPassword ${prof_ds::constants::monitor_pwd} --hostname ${::fqdn} --ldapPort ${prof_ds::constants::ldap_port} --ldapsPort ${prof_ds::constants::ldaps_port} --httpsPort ${prof_ds::constants::https_port} --adminConnectorPort ${prof_ds::constants::admin_port} --replicationPort ${prof_ds::constants::replication_port} --bootstrapReplicationServer ${ds_hosts[0]}:${prof_ds::constants::replication_port} --start --acceptLicense",
onlyif => ["test '${::fqdn}' == '${ds_hosts[1]}' && echo 0 || echo 1"],
environment => ["JAVA_HOME=${java_home}"],
path => ['/usr/bin', '/usr/sbin', '/bin', '/opt/opendj','/opt/opendj/bin'],
}
}
UPDATE:
Another optimization I recommend is avoid testing empty strings (depends on the OS/version), by adding another char in front, e.g. x:
test 'x${::fqdn}' == 'x${ds_hosts[0]}' && echo 0 || echo 1
UPDATE:
More on how you can restrict exec on specific nodes (more), in a very simple empirical way (updated the code snippet above, too)
However, the proper way to restrict specific execution on specific nodes, is to either have 2 Nodegroups: Directory Service Primary and Directory Service Secondary, pin each node to its counterpart, and use the resource collector feature to distribute the work on different nodes.
Another note is that you should avoid using exec resource as they are not idempotent. If that is not possible, to define a type that would wrap that resource and manage all of its lifecycle. In your case would be something like:
directory_service { 'first-ds':
deploymentKeyPassword => '?',
deploymentKey => '?',
}
This would probably come later, when you understand how do you want to manage the configurations of your DS service. To get inspired, check apache. Looking on forge for DS, one module raised my attention: markt-de/puppet-ds_389
Your onlyif attributes are nonsense on several levels. Your other answer points out some of these, but for completeness:
An Exec's onlyif attribute should be a single command, as a string. You may be lucking out via stringification of the arrays you are actually presenting, but you really ought to make them plain strings.
By putting the onlyif command in single quotes, you prevent Puppet from interpolating values for the Puppet variable references within.
You appear to have the idea that it is the standard output of the onlyif command that determines whether the main command will be run, but that is incorrect: it is the onlyif's exit status.
Since all the details being tested are known during catalog building, it is pointless to use an onlyif attribute at all. A Puppet conditional statement would be better suited.
But what you should use an unless or onlyif or creates for is to prevent installing the software again if it has already been installed.
Overall, then, you want something more like this:
if $::fqdn == $ds_hosts[0] {
exec { 'Install first DS':
command => "setup --serverId first-ds ...",
environment => ["JAVA_HOME=${java_home}"],
path => ['/usr/bin', '/usr/sbin', '/bin', '/opt/opendj','/opt/opendj/bin'],
unless => "exit_with_status_0_if_DS_is_already_installed",
}
} elsif $::fqdn == $ds_hosts[1] {
exec { 'Install second DS':
command => "setup --serverId second-ds ...",
environment => ["JAVA_HOME=${java_home}"],
path => ['/usr/bin', '/usr/sbin', '/bin', '/opt/opendj','/opt/opendj/bin'],
unless => "exit_with_status_0_if_DS_is_already_installed",
}
}

conditions to chain bash scripts with puppet

I am new to puppet and I have two questions. I want to execute 2 successive custom bash scripts:
file{ 'deploy_0':
ensure => 'file',
path => '/home/user_name/scripts/deploy_0.sh',
...
notify => Exec['deploy_core']
}
file{ 'deploy_1':
ensure => 'file',
path => '/home/user_name/scripts/deploy_1.sh',
...
notify => Exec['deploy_core_api']
}
exec { 'deploy_core':
command => '/bin/bash -c "/home/user_name/scripts/deploy_0"',
}
exec { 'deploy_core_api':
command => '/bin/bash -c "/home/user_name/scripts/deploy_1.sh"',
onlyif => 'deploy_core'
}
But this does not work
I know I can put for the onlyif paramter a bash command such as /bin/bash -c "/home/user_name/scripts/deploy_0.sh, but I prefer to declare a file resource.
You used the notify metaparameters correctly and well to specify the scripts needed to be deployed before execution (file before corresponding exec) and should be executed again if the file content changes. You need similar metaparameters for application order on the exec resources if you want similar functionality there. Note that onlyif is an exec attribute that executes a local command on the client and causes the resource to be considered already in sync (not applied due to idempotence) during catalog application if it returns something falsey.
Since you do not need refreshing here from one exec to the other like you did with the file resource, we can use require or before instead.
# before
exec { 'deploy_core':
command => '/bin/bash -c "/home/user_name/scripts/deploy_0"',
before => File['deploy_core_api'],
}
exec { 'deploy_core_api':
command => '/bin/bash -c "/home/user_name/scripts/deploy_1.sh"',
}
# require
exec { 'deploy_core':
command => '/bin/bash -c "/home/user_name/scripts/deploy_0"',
}
exec { 'deploy_core_api':
command => '/bin/bash -c "/home/user_name/scripts/deploy_1.sh"',
require => File['deploy_core'],
}
This will give you the behavior you are looking for.

fail when a file exist in puppet

I am trying to write a puppet script which will install a module by un-tar. I want puppet to fail if it is already un tar. I tried to do below code but it always fails even if directory is absent.
I am checking if /opt/sk is present then fail otherwise proceed on installation.
define splunk::fail($target)
{
$no = 'true'
case $no {
default : { notice($no) }#fail('sk is already installed.')}
}
}
define splunk::forwarder( $filename , $target )
{
file{"$target/sk":
ensure => present
}
splunk::fail{"NO":
target => '/opt/',
require => File[$target],
}
file{"$target/A.tgz":
source => $filename ,
replace => false ,
}
exec{"NO1":
command => "tar xzvf A.tgz" ,
cwd => $target ,
require => File["$target/A.tgz"] ,
}
exec{"Clean":
command => "rm -rf A.tgz" ,
cwd => target ,
require => Exec["NO1"],
}
}
splunk::forwarder {"non":
filename => 'puppet:///modules/splunk/files/NO.tgz' ,
target => '/opt/',
}
Thanks
Define custom_fact and use it combined with fail resource.
In your ruby directory e.g /usr/lib/ruby/vendor_ruby/facter define file tmp_exist.rb with content:
# tmp_exist.rb
Facter.add('tmp_exist') do
setcode do
File.exist? '/root/tmp'
end
end
Next use it in puppet manifest. E.g I combined it with str2bool function from stdlib:
class test {
if !str2bool($::tmp_exist) {
fail('TMP NOT EXIST')
}
if !str2bool($::foo_exist) {
fail('FOO NOT EXIST')
}
}
include test
In /root create only tmp file.
In result you will have:
Error: FOO NOT EXIST at /etc/puppet/deploy/tests/test.pp:8 on node dbmaster
UPDATED: I updated my answer. Chris Pitman was right, my previous solution works only on puppet master or with puppet apply.
I have also found an article describing how to define custom function file_exists in puppet. That also might be helpful.
You should use "creates" attribute of exec, for example:
exec { 'install':
command => "tar zxf ${package}",
cwd => $some_location,
path => $path,
creates => "${some_location}/my_package",
}
Puppet will only execute 'install' if "${some_location}/my_package" doesn't exist.

How can i install a local rpm using puppet

I am trying to install a particular rpm using puppet, my init.pp is:
class nmap {
package {'nmap':
provider => 'rpm',
source => "<Local PATH to the RPM>",
}
}
and the rpm is in ...modules/nmap/files
If i move the rpm to manifests, and provide the rpm name in source => ''
class nmap {
package {'nmap':
provider => 'rpm',
source => "rpm-name.rpm",
}
}
it works, but how can i specify source path with ../files/ and do puppet apply successfully
When i use :
source => 'puppet:///files/nmap-6.45-1.x86_64.rpm',
i get an error:
Debug: Executing '/bin/rpm -i puppet:///files/nmap-6.45-1.x86_64.rpm'
Error: Execution of '/bin/rpm -i puppet:///files/nmap-6.45-1.x86_64.rpm' returned 1: error: open of puppet:///files/nmap-6.45-1.x86_64.rpm failed: No such file or directory
Error: /Stage[main]/Nmap/Package[nmap]/ensure: change from absent to present failed: Execution of '/bin/rpm -i puppet:///files/nmap-6.45-1.x86_64.rpm' returned 1: error: open of puppet:///files/nmap-6.45-1.x86_64.rpm failed: No such file or directory
`
when running the command:
sudo puppet apply --modulepath=/home/user1/qa/puppet_qa/modules/ -e "include nmap" --debug
Unlike the file resource type, the package type has no support for Puppet fileserver URLs. You will need to use a file resource to download the rpm prior to installing it. If this is a recurring problem for you, make a defined type that does those in one go (think macros), e.g.
define fileserver_package($source, $ensure='installed') {
file { "/my/tmp/dir/$name.rpm": source => $source }
package { $name:
ensure => $ensure,
provider => 'rpm',
source => "/my/tmp/dir/$name.rpm",
require => File["/my/tmp/dir/$name.rpm"],
}
}
Edit: it is generally advisable to use a local yum repo instead, see also the first comment by #rojs below.
The RPM package can be installed this way:
package { 'epel-release-6':
provider => 'rpm',
ensure => 'present',
source => '/usr/local/rpms/epel-release-latest-6.noarch.rpm',
}
It seems the module name you are using is nmap. You can use the same source parameter like this,
source => 'puppet:///modules/nmap/nmap-6.45-1.x86_64.rpm',
The syntax to access a file under a module goes like this,
puppet:///modules/<modulename>/<file you want to access>
See this link here, http://docs.puppetlabs.com/puppet/latest/reference/modules_fundamentals.html#files
Lets start from start :
on server:
$rpm -ivh https://yum.puppetlabs.com/puppetlabs-release-pc1-el-7.noarch.rpm
$yum -y install puppetserver
$vi /etc/sysconfig/puppetserver #change JAVA args
$systemctl start puppetserver
$systemctl enable puppetserver
$vi /etc/puppetlabs/puppet/puppet.conf #Add “dns_alt_names” in [master]
On Agent:
$rpm -ivh https://yum.puppetlabs.com/puppetlabs-release-pc1-el-7.noarch.rpm
$yum -y install puppet-agent
$systemctl start puppet
$systemctl enable puppet
$vi /etc/puppetlabs/puppet/puppet.conf # Add “server = pupmaster” in [main]
puppet cert list
puppet cert sign
/etc/puppetlabs/code/environments/production/manifests/site.pp:
node webserver {
class { 'apache': }
}
node dbserver {
class { ‘mysql’: }
}
mkdir –p /etc/puppetlabs/code/environments/production/modules/apache/{manifests, files}
apacheinstall.pp:
class apache::apacheinstall {
if $osfamily == 'redhat' {
package { 'httpd':
ensure => 'latest'
}
service {'httpd':
ensure => 'running',
require => Package["httpd"],
}
file { '/var/www/html/ndex.html':
mode => "0644",
owner => 'root',
group => 'root',
source => 'puppet:///modules/apache/index.html',
}
}
elsif $osfamily == 'debian' {
package { 'apache2':
ensure => 'latest'
}
service {'httpd':
ensure => 'running',
require => Package["httpd"],
}
}
}
INIT.pp
class apache {
notify { 'Installing and Configuring Webserver for $osfamily': }
include apache::mysqlinstall
}
Mysqlinstall.pp:
class apache::mysqlinstall {
exec { 'wget':
path => [ "/bin/", "/sbin/", "/usr/bin/", "/usr/sbin/" ],
command => "/usr/bin/wget https://dev.mysql.com/get/mysql57-community-release-el7-9.noarch.rpm && rpm -ivh /tmp/mysql57-community-release-el7-9.noarch.rpm",
cwd => '/tmp/',
creates => '/etc/firstruns/p1.done',
}
}

Sequence of Execs in Puppet

I have a sequence of exec in my Puppet manifest:
The first one downloads ZIP file with binary (unless the binary has already been installed) and saves it to /tmp.
The second one unzips it.
When I apply the manifest for the first time, it works correctly. However, when I clean my /tmp and apply the manifest again, it fails because the first exec doesn't executed (that is correct), but the second still tries to execute and fails because ZIP file is not present.
How do I modify the manifest to skip the second exec if the first one doesn't download file?
exec { 'ngrok-download':
command => 'wget https://dl.ngrok.com/linux_386/ngrok.zip -O /tmp/ngrok.zip',
unless => 'which ngrok',
path => ['/bin', '/usr/bin'],
}
exec { 'ngrok-unzip':
command => 'unzip ngrok.zip',
cwd => '/tmp',
path => ['/usr/bin'],
require => Exec['ngrok-download'],
}
Try this:
exec { 'ngrok-download':
command => 'wget https://dl.ngrok.com/linux_386/ngrok.zip -O /tmp/ngrok.zip',
unless => 'which ngrok',
path => ['/bin', '/usr/bin'],
notify => Exec['ngrok-unzip'],
}
exec { 'ngrok-unzip':
command => 'unzip ngrok.zip',
cwd => '/tmp',
path => ['/usr/bin'],
refreshonly => true,
require => Exec['ngrok-download'],
}
This will result in the unzip exec only running when the wget exec actually does something -- which it won't if ngrok is found.
Normally I would wget it to a more permanent location and leave it there. Then instead of the unless => 'which ngrok' check, replace with creates => '/path/to/zip.file'. The result being as long as the file is still there, none of the execs fire.
Comes in handy when you version the zip files and want to change versions.
You could also try easier approach:
exec { 'ngrok-download':
command => 'wget https://dl.ngrok.com/linux_386/ngrok.zip -O /tmp/ngrok.zip',
unless => 'which ngrok',
path => ['/bin', '/usr/bin'],
} ~>
exec { 'ngrok-unzip':
command => 'unzip ngrok.zip',
cwd => '/tmp',
path => ['/usr/bin'],
refreshonly => true,
}
Where Exec['ngrok-download'] notifies Exec['ngrok-unzip'] if applied and Exec['ngrok-unzip'] refresh its state only if needed
Same thing can be achieved by doing following:
exec { 'ngrok-download':
command => 'wget https://dl.ngrok.com/linux_386/ngrok.zip -O /tmp/ngrok.zip',
unless => 'which ngrok',
path => ['/bin', '/usr/bin'],
}
exec { 'ngrok-unzip':
command => 'unzip ngrok.zip',
cwd => '/tmp',
path => ['/usr/bin'],
refreshonly => true,
}
Exec['ngrok-download'] ~> Exec['ngrok-unzip']
Hope this helps.

Resources