Dependency loop during Pupppet provisioning due missing OS package - vagrant

Im trying to provision my development server using Vagrant and Puppet. Below is some of my Puppet Manifest at this point. The issue im having is that im ending up in a dependency loop which is ofcourse correct. The only problem is that i dont see a way to do it without so therefor i need some help.
Im using the latest version of the box provided by Puppetlabs named puppetlabs/ubuntu-14.04-64-puppet. While adding a PPA to the package manager i receive an error that apt-add-repository is not available. Therefor you need to install the software-properties-common package.
The only problem is that before installing this package, you need to run apt-get update. The second problem is that the manifest wont accept it and it will try to add the PPA before so that, ofcourse which is a logic conclusion, it only has to update the package manager once. But by picking this last solution i will end up in a loop which triggers an error:
==> default: Error: Failed to apply catalog: Found 1 dependency cycle:
==> default: (Exec[add-apt-repository-ppa:ondrej/php-7.0] => Class[Apt::Update] => Exec[apt_update] => Class[Apt::Update] =>
Package[git] => Class[Systempackages] => Apt::Ppa[ppa:ondrej/php-7.0]
=> Exec[add-apt-repository-ppa:ondrej/php-7.0])
class systempackages {
package { [ 'git', 'curl', 'acl', 'unattended-upgrades', 'vim', 'software-properties-common']:
ensure => "installed",
require => [
Class['apt::update'],
],
}
}
/*===========================================*/
## System
Exec { path => [ "/bin/", "/sbin/" , "/usr/bin/", "/usr/sbin/" ] }
class{'systempackages':}
# APT
class { 'apt':
update => {
frequency => 'always',
},
}
apt::ppa { 'ppa:ondrej/php-7.0':
before => Package['php7.0-cli'],
require => Class['systempackages'],
}
# PHP
package {'php7.0-cli':
ensure => 'installed',
}

Given that this is on vagrant, I suggest installing package software-properties-common manually as part of your Vagrantfile.
Something like config.vm.provision "shell", inline: "apt-get update && apt-get install software-properties-common should work.

The circular dependency reflects the fact that Puppet is not a provisioning system. It can be used by a provisioning system or in conjunction with one, but it depends on a fairly substantial software stack being available before it can get off the ground. If Package 'software-properties-common' is necessary for full functioning of the Apt subsystem, then your best bet is to rely on your provisioning system to install it, so that it is available before Puppet ever runs, and to avoid declaring any relationship between that package and the classes and resources of the Apt module.
You are also impacted by the puppetlabs-apt module being quite good about declaring the relationships needed to ensure proper order of application. This is a double-edged sword, however: people cause themselves trouble with surprising frequency by declaring their own relationships with classes or defined types from that module that conflict with the ones it declares itself. In particular, it is asking for trouble to have your Apt::ppa resource require a class containing resources that themselves require any class or resource from the Apt module.
In any case, class apt::update is not a public class of the module. The main implication is that code outside the module should not reference it in any way. You should instead rely on the value you provided for class parameter $apt::update to instruct Puppet to perform an apt-get update at a suitable time.

Related

Install an apt package using chef.json on Vagrant solo-provisioner

I would like to install several arbitrary APT packages using Vagrants Chef solo provisioner.
chef.json seems to allow you to execute chef commands, but I'm unclear as to how to do this. Something like:
chef.json = {
apt: {
package: {'libssl-dev': {action: 'install'}}
}
?
Chef uses recipes to define resources that are executed on nodes via a chef-client.
A recipe is basically a definition of what to do (a script)
A resource is a particular element you are configuring (a file, a service, or package etc)
A node is the machine running chef-client
The json that you are setting up for chef-solo defines attributes which are like variables that your Chef can use to decide what to do.
So you have a hash of attributes for Chef to use, but you need a recipe that configures resources based on that hash to be executed on your node
In your case you need to configure the package resource
package "name" do
some_attribute "value"
action :action
end
The package resource supports lots of different package back ends, including apt so you don't need to worry about differences (except for package names).
To install the packages from your hash you can create a recipe like:
node[:apt][:package].each do |pkg,pkg_data|
package pkg do
action pkg_data[:action].to_sym
end
end
Individual recipes are then packaged up into cookbooks which is a logical grouping of like recipes. Generally a cookbook would be for a piece of software, say httpd or mysql.
As Tensibia mentions, read through the Vagrant Chef-Solo docco for where to put your recipe/cookbook and run from there.
chef.json does not execute or define commands.
It defines attributes for the node which can be used by recipes.
I would recomand reading THIS
and THIS
Some of the json content is generated by vagrant like defining the runlist attribute with the chef.add_recipe keyword in the vagrantfile.
For your use case you should have a cookbook with a recipe parsing node['apt'] and using deb_package resource.

Puppet - How to only run 'apt-get update' if a package needs to be installed or updated

I can't seem to figure out how to get Puppet to not run 'apt-get update' during every run.
The standard yet inefficient way:
The way I've been doing this is with the main Puppet manifest having:
exec { 'apt-get update':
path => '/usr/bin',
}
Then each subsequent module that needs a package installed has:
package { 'nginx':
ensure => 'present',
require => Exec['apt-get update'],
}
The problem with this is that, every time Puppet runs, Apt gets updated. This puts unnecessary load on our systems and network.
The solution I tried, but fails:
I looked in the Puppet docs and read about subscribe and refreshonly.
Refresh: exec resources can respond to refresh events (via notify, subscribe, or the ~> arrow). The refresh behavior of execs is non-standard, and can be affected by the refresh and refreshonly attributes:
If refreshonly is set to true, the exec will only run when it receives an event. This is the most reliable way to use refresh with execs.
subscribe
One or more resources that this resource depends on, expressed as resource references. Multiple resources can be specified as an array of references. When this attribute is present:
The subscribed resource(s) will be applied before this resource.
so I tried this in the main Puppet manifest:
# Setup this exec type to be used later.
# Only gets run when needed via "subscribe" calls when installing packages.
exec { 'apt-get update':
path => '/usr/bin',
refreshonly => true,
}
Then this in the module manifests:
# Ensure that Nginx is installed.
package { 'nginx':
ensure => 'present',
subscribe => Exec['apt-get update'],
}
But this fails because apt-get update doesn't get run before installing Nginx, so Apt can't find it.
Surely this is something others have encountered? What's the best way to solve this?
Puppet has a hard time coping with this scenario, because all resources are synchronized in a specific order. For each resource Puppet determines whether it needs a sync, and then acts accordingly, all in one step.
What you would need is a way to implement this process:
check if resource A (a package, say) needs a sync action (e.g., needs installing)
if so, trigger an action on resource B first (the exec for apt-get update)
once that is finished, perform the operation on resource A
And while it would be most helpful if there was such a feature, there currently is not.
It is usually the best approach to try and determine the necessity of apt-get update from changes to the configuration (new repositories added, new keys installed etc.). Changes to apt's configuration can then notify the apt-get upate resource. All packages can safely require this resource.
For the regular refreshing of the database, it is easier to rely on a daily cronjob or similar.
I run 'apt-get update' in a cron script on a daily basis, under the assumption that I don't care if it takes up to 24 hours to update OS packages via apt. Thus...
file { "/etc/cron.daily/updates":
source => "puppet:///modules/myprog/updates",
mode => 755
}
Where /etc/cron.daily/updates is, of course:
#!/bin/sh
apt-get -y update
Then for the applications, I just tell puppet something like:
# Ensure that Nginx is installed.
package { 'nginx':
ensure => latest
}
And done, once apt-get update runs, nginx will get updated to the latest version within the next twenty minutes or so (the next time puppet runs its recipe). Note that this requires you to have done 'apt-get update' in the initial image via whatever process you used to install puppet into the image (for example, if this is in CloudFormation, via the UserData section of the LaunchConfiguration). That is a reasonable requirement, IMHO.
If you want to do 'apt-get update' more often, you'll need to put a cron script into /etc/cron.d with the times you want to run it. I plopped it into cron.daily because that was often enough for me.
This is what you need to do - create an apt-get wrapper that would do apt-get update followed by calling a real apt-get (/usr/bin/apt-get) for install. Install the wrapper into a directory that will be in a PATH before apt-get.
Modify /usr/lib/ruby/vendor_ruby/puppet/provider/package/apt.rb and locate the line:
commands :aptget => "/usr/bin/apt-get"
( it will be right below has_features :versionenable, :install_options )
replace that line with:
commands :aptget => "apt-get"
You're done. For some boneheaded reason puppet insists on calling commands with absolute path rather than using a sane PATH variable.

Vagrant Puppet Class Not Found Error

I am getting the message:
Puppet::Parser::AST::Resource failed with error ArgumentError: Could not find declared class git at /tmp/vagrant-puppet-1/manifests/site.pp:15 on node vagrant-ubuntu-precise-64.wp.comcast.net
Probably the best idea is to see this in action. I have created a GitHub repo of the exact manifest I am using. It is here:
https://github.com/jamorat/puppet-example
The manifests and git module are there. If you have Vagrant, this can be vagrant up and you will see the error for yourself. Would be cool to either receive an answer here and/or also as a commit (for which credit would still be given here for answer.)
Thank you so much!
You need to configure vagrant with the puppet module path. On a side note, you would also usually keep the manifest and module folder in the same folder, instead of modules inside manifests.
This:
class{ git:
svn => 'installed',
gui => 'installed',
}
is telling puppet to create a resource based on the class named git that has 2 parameters: svn and gui. Such a class declaration doesn't exist anywhere in what you've posted. If it were, it would look something like:
class git ($svn, $gui) {
package {'svn':
ensure => $svn,
}
# Whatever 'gui' is, making package b/c use of "installed"
package {'gui':
ensure => $gui,
}
}
Alternative is to declare a class and include it using the "include" directive.
Recommend a good reading of Language: Classes

Copy file from Puppet master to agent unless software is installed

execution of exec or package types by puppet master controllable
Installation of package X on linux, unless X has been installed already:
package { "X": }
&&
Installation of executable Y on Windows, unless Y has been installed already:
exec { "packageYInstalled":
command => "packageY /S",
require => "C:\\temp\\packageY",
unless => "packageYinstalled";
}
Execution of file type by puppet master uncontrollable as unless attribute is not allowed in puppet file type
puppet file attributes
file { "packageYCopiedToTempFolder": }
path => "C:\\temp\\packageY",
source => "puppet:///files/packageY";
}
Execute installers from shared (samba) folder instead of copy it first to agent system does not solve the issue
Puppet runs executed on multiple external systems
Executables, zips and or tar.gz packages are copied to the remote systems during every puppet run, while these files where removed after installation and software has been installed already
The way i tackle this, and there might be a better ways to do this :
Create a module for installing the product
In that module, write a custom fact for discovering the version installed
In the installer class, wrap everything in an 'if'
i.e.
class productx::install (
$version,
$installer_path,
) {
# productx_version is a fact
if ! $::productx_version {
do the install
}
}
You can do other neat stuff then, like audit the software in your environment

puppet apt-get update only once before anything else?

I know the basics of ordering in puppet to run apt-get update before a specific package but would like to specify to just run apt-get update only once and then execute the rest of the puppet file. Is that possible?
All of the ways listed Here need to either run apt-get before every package or use arrows or requires to specify each package.
This would be my recommendation from that list:
exec { "apt-update":
command => "/usr/bin/apt-get update"
}
Exec["apt-update"] -> Package <| |>
This will ensure that the exec is run before any package, not that the exec is run before each package. In fact, any resource in puppet will only ever be executed at most once per puppet run.
But if you're wanting the exec to occur before ANY type of resource I guess you could do something like:
exec { "apt-update":
command => "/usr/bin/apt-get update",
before => Stage["main"],
}
The "main" Stage is the default stage for each resource, so this would make the exec occur before anything else.
I hope that this helps.
With puppetlabs-apt module, it should be enough to define dependency on the module for any package that will be installed:
Class['apt::update'] -> Package <| provider == 'apt' |>
This assumes basic configuration of apt, e.g.:
class { 'apt':
update => {
frequency => 'daily',
},
purge => {
'sources.list' => false,
'sources.list.d' => true,
},
}

Resources