Puppet Class Default Parameter/Variables - puppet

I am using a custom ENC and would like to be able to have classes default parameters to variables higher in scope.
This allows me to set a variable in the top scope, node scope, wrapper class scope, etc and the value will be picked up as a default parameter for a class.
This then allows me to also still set the parameter at the class definition. The one downside to this approach is that a class could potentially pick up an "unsafe" default although I feel that it is an unlikely scenario.
Has anyone else looked at solving a problem such as this and is this overall a good idea or a bad idea?
custom_enc.yaml
classes:
rsyslog::client:
port: 1234
parameters:
server: my-rsyslog-server
manifest/server.pp
class rsyslog::client(
$server => $server, # $server = $server || undef
$port => $port ? { # $port = $port || '514'
'' => '514',
default => $port
}
) {
if !defined($server) { fail "server must be defined" }
notify { "The server is ${server}": }
notify { "The port is ${port}": }
}

Related

Puppet: conditional restart of service

Is it possible to conditionally skip refresh events on a service resource? Or alternatively: Is it possible to prevent a service resource inside a class to be refreshed when the class is notified?
Context: I have a Puppet module containing the following manifest (simplified):
class foo(
Boolean pre_process_service = true,
Boolean auto_restart_service = true
) {
if $pre_process_service {
exec { 'process config':
... # details including a pretty complex command - should be hidden in my module
notify => Service['foo'],
}
}
service { 'foo':
ensure => 'running',
enable => true,
}
}
which might be used like so:
file { 'config':
... # copies config from somewhere
}
class { 'foo':
auto_restart_service => false,
subscribe => File['config'],
}
How can I avoid restarting the service when the user specifies auto_restart_service => false?
Note that the user of the module decides how to provide the configuration (copying files, checking out a Git repository,...) so I can't do that inside my module. Instead the class subscribes to the resource providing the configuration. As long as the user goes with the default of auto_restart_service = true everything works fine and even disabling the preprocessing of the configuration works correctly. However, when the user specifies auto_restart_service = false the service will still restart since the service resource is refreshed when the class is notified. Wrapping the service resource into an if block like I did with the exec resource doesn't work either since the service resource does multiple things:
It starts the service if it isn't running
It enables the service if it isn't enabled
It restarts the service if notified
I only want to conditionally prevent (3) from happening while always doing (1) and (2). Is there a way to do this?
I don't think there's a way to not refresh the service when you notify the class. However, you can try to conditionally override how Puppet should restart the service with the restart attribute of the service resource.
Something like this:
if $auto_restart_service {
# Let the provider handle the restart
$_attr = {}
} else {
# Let Puppet execute `true` instead of actually restarting the service
$_attr = { 'restart' => '/usr/bin/true' }
}
service { 'foo':
ensure => 'running',
enable => true,
* => $_attr,
}
The idea from tectux is really nice. I've enhanced the if conditions. I have decided to use script for custom fact.
modules/autofs/facts.d/autofs.sh
#!/bin/sh
DECISION=`test -f /usr/bin/docker && /usr/bin/docker ps -q |grep -q . && echo no`
if [ "x$DECISION" == "x" ] ; then
DECISION=yes
fi
echo '{'
echo ' "autofs": {'
echo " \"do_automatic_restart\": \"$DECISION\""
echo ' }'
echo '}'
So, the output of the script
# modules/autofs/facts.d/autofs.sh
{
"autofs": {
"do_automatic_restart": "yes"
}
}
Now we can use the custom fact
if $facts['autofs']['do_automatic_restart'] == "no" {
$_attr = { 'restart' => "logger puppet agent: automounter is NOT going to be RESTARTED due to active containers on a host" }
} else {
$_attr = {}
}

Iterate a puppet resource collector

I am trying to develop a puppet class with a defined resource which creates the configuration for a website.
One of the things that the defined resource has to do is assign the IP address of the website to a dummy interface. Due to constraints of the project this is done with NetworkManager.
So I have to generate a file like
[connection]
id=dummydsr
uuid=50819d31-8967-4321-aa34-383f4a658789
type=dummy
interface-name=dummydsr
permissions=
[ipv4]
method=manual
#IP Addresses come here
ipaddress1=1.2.3.4/32
ipaddress2=5.6.7.8/32
ipaddress3=8.7.6.5/32
[ipv6]
method=ignore
There is to be a line ipaddressX=... for every instance of the defined resource.
My problem is how do I track the number of times the defined resource has been instantiated so I can somehow increment a counter and generate the ipaddress lines.
Or for each instantiated defined resource, append the IP address to an array which I can later use to build the file
If I understand you, and I'm not certain that I do, but I think you would want to do something like this:
define mytype(
Integer $count,
...
) {
file { 'some_network_manager_file':
content => template(...)
}
}
And then you would have a loop:
$mystuff.each |$count, $data| {
mytype { ...:
count => $count,
...
}
}
Key insight here may be that the each function has some magic in it that allows you to get the index if you need it, see also this answer.
Now I think that's how it will work, without me spending time researching NetworkManager. If you provide more of your code, I may be able to update this to be more helpful.
This is less than ideal since I would prefer to have it inside the defined resource, but since I instantiate the defined resource with the data from a hash I use said hash to iterate that part.
class xxx_corp_webserver (
Hash $websites ={}
){
create_resources('xxx_corp_webserver::website', $websites)
# This would be nicer inside the defined class, but I did not find any other way
# Build and array with the IP addresses which are for DSR
$ipaddresses = $websites.map | $r | {
if $r[1]['enabledsr'] {
$r[1]['ipaddress']
}
}
# For each DSR address add the line
$ipaddresses.each | Integer $index , String $ipaddress | {
$num = $index+1
file_line{"dummydsr-ipaddress${num}":
ensure => present,
path => '/etc/NetworkManager/system-connections/dummydsr',
line => "address${num} = ${ipaddress}/32",
match => "^address.* = ${ipaddress}/32",
after => '# IP Addresses come here',
notify => Service['NetworkManager'],
require => File['/etc/NetworkManager/system-connections/dummydsr'],
}
}
}

Puppet roles/profiles, role with multiple instances of profile - how do parameters work?

I am just learning Puppet (we have Puppet Enterprise locally). I am trying to understand the "roles and profiles" pattern. Please pardon any nomenclature slip ups.
How do I create a role with multiple instances of a profile, where the profile instances differ only by parameter? I'm guessing Hiera fits into this somewhere but I'm not exactly sure how.
For example:
Puppetfile:
mod 'puppetlabs-apache', '2.3.0'
apache.pp profile
class profile::apache (
String $port = '80',
) {
class { 'apache':
listen => $port,
}
}
twoapaches.pp role
class role::twoapaches {
include profile::apache
include profile::apache
}
I want an instance of the twoapaches role to have an apache at ports 90 and 100 - how do I do that?
You actually can't use classes like that in Puppet; a class can only be declared once-per-node.
You probably need some of the defined types in the puppetlabs/apache module. Defined types are used when you need to declare a user-defined "resource" more than once on a single node.
E.g. profile might be:
class profile::two_vhosts {
apache::vhost { 'ip1.example.com':
ip => ['127.0.0.1','169.254.1.1'],
port => '80',
docroot => '/var/www/ip',
}
apache::vhost { 'ip2.example.com':
ip => ['127.0.0.1'],
port => '8080',
docroot => '/var/www/ip',
}
}
And the role might be:
class role::two_vhosts {
include profile::two_vhosts
include profile::other_stuff
...
}
If you needed to then pass the ports in, you might have:
class profile::two_vhosts (
String $ip1_port,
String $ip2_port,
) {
apache::vhost { 'ip1.example.com':
ip => ['127.0.0.1','169.254.1.1'],
port => $ip1_port,
docroot => '/var/www/ip',
}
apache::vhost { 'ip2.example.com':
ip => ['127.0.0.1'],
port => $ip2_port,
docroot => '/var/www/ip',
}
}
You could then have your role as:
class role::two_vhosts {
class { 'profile::two_vhosts':
ip1_port => '80',
ip2_port => '8080',
}
include profile::other_stuff
...
}
But in practice people use the Automatic Parameter Lookup feature here in conjunction with Hiera (ref).
I would use Hiera also for the parameters. This way you can easily change ports if needed and you comply with the rule of no putting classes inside the roles:
class role::two_vhosts {
include profile::two_vhosts
include profile::other_stuff
...
}
Hiera configuration when including the role would be something like this:
profile::two_vhosts::ip1_port: '80'
profile::two_vhosts::ip2_port: '8080'

manage hosts file on Windows using Puppet

I'm trying to manage my hosts file on a Windows machine using Puppet and Hiera. My problem is that I have never really used Hiera and I'm struggling with parsing the data content into a proper format.
The relevant section in hieradata/hiera.yaml looks like this:
myhosts : [
'host1 1.2.3.4',
'host2 2.3.4.5',
'host3 3.4.5.6']
I have code that uses a host module, but it also depends on a class that I don't have, so naturally it doesn't work.
class hosts::module (
$myhosts = hiera('myhosts'),
)
{
define update_hosts {
$value = split($name,' ')
host {
"${value[0]}" : ip => "${value[1]}",
}
}
update_hosts { $myhosts :; }
}
I have tried using the file resource instead of the host resource, and also tried doing it without any class, but for some reason I am getting this error
Error: Could not retrieve catalog from remote server: Error 500 on SERVER:
Server Error: Evaluation Error: Error while evaluating a Resource Statement,
Evaluation Error: Error while evaluating a Resource Statement, Duplicate
declaration: File[C:\Temp\tmp.txt] is already declared in file
/etc/puppetlabs/code/environments/production/manifests/site.pp:4; cannot redeclare
at /etc/puppetlabs/code/environments/production/manifests/site.pp:4
at /etc/puppetlabs/code/environments/production/manifests/site.pp:4:1
at /etc/puppetlabs/code/environments/production/manifests/site.pp:10 on node puppet-agent
As you can see, it claims that I have a duplicate declaration, but the weird thing is that it says it has a problem with the same line. It thinks it's declaring the same thing twice for some reason.
This is the code I have now (I know it won't work but the error doesn't really sound related)
define hosts_update($content) {
file { 'C:\Temp\tmp.txt' :
ensure => file,
content => $content,
}
}
hosts_update{ hiera('myhosts'):
content => split($name," "),
}
Any idea how to do this right?
fixed it.
site.pp
include update_hosts
init.pp
class update_hosts::host
(
$hosts = hiera('hosts_list'),
)
{
update_host { $hosts :; }
}
host.pp
define update_host {
​
$value = split($name,' ')
​
host {
"${value[0]}" : ip => "${value[1]}",
target => "C:/Windows/System32/drivers/etc/hosts"
}
}

Spark/Gradle -- Getting IP Address in build.gradle to use for starting master and workers

I understand at a basic level the various moving parts of build.gradle build scripts but am having trouble tying it all together.
In Apache Spark standalone mode, just trying to start a master and worker on the same box from build.gradle. (Later will extend with call with $SPARK_HOME/sbin/start-slaves with the proper argument for masterIP.)
Question: How can I assign my IP address to a variable in Groovy/build.gradle so I can pass it to a command in an Exec task? We want this to run on a couple different development machines.
We have a (I think fairly standard) /etc/hosts config with the FQDN and hostname assigned to 127.0.1.1. The driver gets around this OK but starting master and slaves with hostnames is not an option, I need the ip address.
I am trying:
task getMasterIP (type: Exec){
// declare script scope variable using no def or
executable "hostname"
args += "-I"
// need results of hostname call assigned to script scope variable
sparkMasterIP = <resultsOfHostnameCall>
}
// added this because startSlave stops if Master is already running
task startSlaveOnly(dependsOn:'getMasterIP', type: Exec){
executable "/usr/local/spark/sbin/start-slave.sh"
args += "spark://$sparkMasterIP:7077"
doLast {
println "enslaved"
}
}
// now make startSlave call startSlaveOnly after the initial startMaster
task startSlave(dependsOn:'startMaster', type: Exec) {
finalizedBy 'startSlaveOnly'
}
When I try something like suggested in the docs for Exec for Groovy calls:
task getMasterIP (type: Exec){
// declare script scope variable using no def or
sparkMasterIP = executable "hostname"
args += "-I"
}
I get a warning that executable is not recognized.
The " for a little more background on what I am thinking" section, not the main question.
Googling "build.gradle script scope variables" and looking at the first two results, in the basic docs I only see one type of variable and ext properties to be used.
16.4. Declaring variables -- There are two kinds of variables that can be declared in a build script: local variables and extra properties.
But in this other Gradle doc Appendix B. Potential Traps I am seeing two kinds of variables scopes aside from the ext properties:
For Gradle users it is important to understand how Groovy deals with
script variables. Groovy has two types of script variables. One with a
local scope and one with a script-wide scope.
With this example usage:
String localScope1 = 'localScope1'
def localScope2 = 'localScope2'
scriptScope = 'scriptScope'
I am assuming I should be using script-scope variables with no "def" or type declaration.
To fetch local IPs:
// Return all IPv4 addresses
def getLocalIPv4() {
def ip4s = []
NetworkInterface.getNetworkInterfaces()
.findAll { it.isUp() && !it.isLoopback() && !it.isVirtual() }
.each {
it.getInetAddresses()
.findAll { !it.isLoopbackAddress() && it instanceof Inet4Address }
.each { ip4s << it.getHostAddress() }
}
return ip4s
}
// Optionally, return all IPv6 addresses
def getLocalIPv6() {
def ip6s = []
NetworkInterface.getNetworkInterfaces()
.findAll { it.isUp() && !it.isLoopback() && !it.isVirtual() }
.each {
it.getInetAddresses()
.findAll { !it.isLoopbackAddress() && it instanceof Inet6Address }
.each { ip6s << it.getHostAddress() }
}
return ip6s
}
task printIP() doLast {
println getLocalIPv4()
println getLocalIPv6()
}
The two functions above return a list of IPv4 or IPv6 addresses respectively. You might notice that I'm skipping all localhosts, interfaces that are not up, all loopbacks and virtual interfaces. If you want to use the first IPv4 address, you can use it elsewhere as:
getLocalIPv4()[0]
or in your case:
args += "spark://"+ getLocalIPv4()[0] + ":7077"
I found this post that appears to be a more straightforward way of doing this but it limited to Linux platforms, hostname -I doesn't work in Windows and maybe not all Linux distros?
getting hostname
assigning it to variable
using in a build.gradle
task
Here's the task I built as a result, the accepted answer is much better and more universal, this is just for another way of looking at it
task getMasterIP{
doLast {
new ByteArrayOutputStream().withStream { os ->
def result = exec {
executable = 'hostname'
args += '-I'
}
ext.ipAddress = os.toString()
}
}
}
RaGe's answer does a better job of looking at all interfaces on all platforms

Resources