Puppet 4 migration and Invenio modules
What?
- How to Puppet3 -> Puppet4
- New features
- Testing puppet code
- Automating the tests (CI)
- Future of the Invenio modules
How to Puppet3 -> Puppet4
First of all
Run the tests!!
Wait for the next section for the how to, but that will pop up MOST issues
Hiera lookups on templates
# Instead of
some data <%= scope.hiera_lookup('::myclass::myvalue') %>
# Use
some data <%= lookup('::myclass::myvalue') %>
# Or better
some data <%= @myvalue %>
# And then on the .pp file that uses this template:
#
# $myvar = hiera('::myclass::myvalue')
# $content = template('mymodule/mytemplate.erb')
#
Variable namespace on templates
# Given this class:
----- mymodule/myclass.pp
class mymodule::myclass {
$anothervar = 'something'
notify { template('anothermodule/anothertemplate.erb')
}
-----
---- anothermodule/templates/anothertemplate.erb
This is the template from anothermodule, <%= @anothervar %>
----
# with puppet3
----
This is the template from anothermodule, something
----
# BUT!!! in puppet4
----
This is the template from anothermodule,
----
No dynamic namespaces anymore!
Variable namespace on templates
CERN ONLY: Careful with teigi secrets, they might end up being empty
# Instead of passing the template name
teigi::secret::sub_file{"/file_with_secrets":
template => 'mymod/template.erb',
teigi_keys => $tbags,
owner => $user,
group => $group,
mode => '0640',
}
# use the new 'content' parameter
teigi::secret::sub_file{"/file_with_secrets":
content => template('mymod/template.erb'),
teigi_keys => $tbags,
owner => $user,
group => $group,
mode => '0640',
}
Empty string == True!!
# This same snippet
$port = ''
if $port {
notify {"Got port ${port}": }
} else {
notify {'No port passed!': }
}
# On puppet 3
[test@0fc2514c4e5d code]$ bundle exec puppet --version
3.8.7
[test@0fc2514c4e5d code]$ bundle exec puppet apply test.pp
Notice: Compiled catalog for 0fc2514c4e5d.home in environment production in 0.01 seconds
Notice: No port passed!
Notice: /Stage[main]/Main/Notify[No port passed!]/message: defined 'message' as 'No port passed!'
Notice: Finished catalog run in 0.01 seconds
# But on puppet 4
[test@0815d666e8ff code]$ bundle exec puppet --version
4.8.2
[test@0815d666e8ff code]$ bundle exec puppet apply test.pp
Notice: Compiled catalog for 0815d666e8ff.home in environment production in 0.10 seconds
Notice: Got port
Notice: /Stage[main]/Main/Notify[Got port ]/message: defined 'message' as 'Got port '
Notice: Applied catalog in 0.02 seconds
New featues!
Data types
Why?
Data types
Basic (core) data types:
-
String
-
Integer
-
Float
-
Numeric
-
Boolean
-
Array
-
Hash
-
Regexp
-
Undef
-
Default
# Use them everywhere!
class dummy(
String name,
Integer age,
Array[Integer] grades,
Hash extra_params,
) {
...
}
# A combo with Default and bool-string handling:
$use_feature = $feature ? {
Boolean => $feature,
String => str2bool($feature),
Default => true,
}
Data types
Flexible data types:
-
Variant
-
Pattern
-
Optional
-
NotUndef
-
Enum
-
Tuple
-
Struct
class myclass(
# Might be string or integer
Variant[String, Integer] port = 12,
# Match any sting starting with lowercase letter
Pattern[/\A[a-z].*/] lowercased = 'aOEU',
# Might be undef or string
Optional[String] might_be_undef = undef,
# Might be anything but undef
NotUndef not_undef = 'something',
# Matches either string 'one' or 'two'
Enum['one', 'two'] how_many = 'one',
# Specify one by one the types of each element
Tuple[String, String, Integer] triplet = ['one', 'two', 3],
# Definition of a hash structure
Struct[{
name => String,
home => String,
uid => Integer,
}] user,
)
Data types
Parent data types:
-
Scalar
-
Collection
-
Data
-
Catalogentry
-
Type
-
Any
Unusual data types:
- Callable
Data types: restrictions
# Integer ranges
Integer[1,1024] $listen_port = 22
# Enums
Enum['*','::1','127.0.0.1'] $listen_ip = '*',
# Patterns
Pattern[/^\/.*/] $home
# Complex hashmaps
Hash[String, Struct[{ uid => Integer, home => Pattern[/^\/.*/]}]] $hash
# That matches for example:
# {
# uid => 0,
# home => '/root',
# }
#
# but not
# {
# uid => 'root',
# home => 'my/relative/path',
# }
EPP templates
EPP == Embedded puppet templates
Some diffs with erb:
- Puppet style vars
- Typed parameters
- Can use any func
------ example.epp file --------
<% | String $text, $Array $array, Boolean $bool |>
<% if $string -%>
Text from variable: <%= $string %>
<% end -%>
<% if $array -%>
<% $array.each |$element| { -%>
Array item: <%= $element %>
<% } -%>
<% end -%>
<% if $bool -%>
Bool value is true
<% end -%>
---------------------------------
------- .pp code using it -------
file { '/dummy':
ensure => file,
content => epp(
'test/example.epp',
{
text => 'foo',
array => ['one', 'two'],
bool => false
}),
}
---------------------------------
Heredocs
Nicer strings!
class my_example {
$content = @("EOF")
This is a very long string
with some variables on it, for example:
-> here is the fqdn ${::fqdn)
You will also notice that the indentation
in front of the string will be removed, that
can be avoided (if wanted), by not using the
pipe right before the end-of-file tag string.
| EOF
file { '/example':
ensure => file,
content => $content,
}
}
Lambdas and new functions
Finally loops!! (kinda)
- each
- map
- filter
- reduce
- slice
$data = ["routers", "servers", "workstations"]
$data.each |$item| {
notify { $item:
message => $item
}
}
# For the hash $data, return a hash containing all
# values of keys that end with "berry"
$data = {
"orange" => 0,
"blueberry" => 1,
"raspberry" => 2
}
$filtered_data = $data.filter |$items| {
$items[0] =~ /berry$/
}
# $filtered_data = {blueberry => 1, raspberry => 2}
$data = [1,2,3]
$transformed_data = $data.map |$items| { $items * 10 }
$sum = $data.reduce |$memo, $value| { $memo + $value }
slice([1,2,3,4,5], 2) # produces [[1,2], [3,4], [5]]
Testing puppet code
Puppetlint
Checking that the code looks nice
Puppet parse
Checking that puppet understands the code
Rspec
Unit test the s#!$ out of it
Acceptance
Acceptance - kind of*
*CERN specific
- Create new environment
- Add file to it-puppet-environments
- Duplicate any hiera data on your repo for the new env
- Override module and hostgroup branches on the environment file
- Move one node to the new environment (foreman)
- Run puppet on it
- Rinse and repeat
Run them all locally
With docker
Dockerfile:
# Puppet 3 build:
# docker build --build-arg puppet_version=3.0 Dockerfile
#
# Puppet 4 build:
# docker build --build-arg puppet_version=4.8.0 Dockerfile
#
# Latest Puppet 4 build:
# docker build --build-arg puppet_version=4.0 Dockerfile
#
FROM gitlab-registry.cern.ch/ai/it-puppet-module-ci_images:cc7-puppet-ci-master
ARG puppet_version=3.0
WORKDIR /code/code
ENV BUNDLE_GEMFILE=../ci/Gemfile
ENV PUPPET_VERSION="~> ${puppet_version}"
ENV LANG=en_US.UTF-8
# Run tests
RUN useradd test
USER test
ENTRYPOINT rm -f ../ci/Gemfile.lock \
&& bundle install --local --without system_tests development \
&& bundle exec rake --rakefile ../ci/Rakefile test
Run them all locally
With docker
docker-compose.yml:
version: '2'
# To run the tests run the service for the puppet version you want:
# docker-compose run --rm puppet3
#
# If not already built, it will build the images for you.
services:
puppet3:
build:
context: .
dockerfile: Dockerfile
args:
puppet_version: 3.0
container_name: puppet3_ci
volumes:
- "$PWD:/code"
Run them all locally
With docker
docker-compose.yml (continued):
puppet4:
extends: puppet3
build:
args:
puppet_version: 4.8.0
container_name: puppet4_ci
puppet4_latest:
extends: puppet3
build:
args:
puppet_version: 4.0
container_name: puppet4_latest_ci
Run them all locally
With docker
$ docker-compose run puppet3
$ docker-compose run puppet4
$ docker-compose run puppet4_latest
Automating the tests (CI) *
* CERN specific
The modulesync branch
Maintained by CERN config management team
Automatic updates (as merge requests)
The modulesync branch
The modulesync branch
Future of the Invenio modules
Puppet 4 and Invenio
By David Caro
Puppet 4 and Invenio
- 563