puppetconf.com #puppetconf
Loops and Unicorns
Future of the Puppet Language
Henrik Lindberg
Consulting Engineer | Puppet Labs
@hel
puppetconf.com #puppetconf
•  New parser
•  New Language Features
•  New approach
– Opt in to New/Experimental Features
– Maintaining Backwards Compatibility
Introduction
New Parser
The future is already here…
puppetconf.com #puppetconf
•  Rewritten from the ground up
•  Removes quirky grammar constraints
•  Improves error messages
•  Enabler for:
– opt-in to new and experimental features
– backwards compatibility
•  Use on command line, or in settings
New Parser
puppet apply –-parser future ...
puppetconf.com #puppetconf
De-Quirk
"This is a random number: ${fqdn_rand()}"
join([1,2,3])
$a = 0x0EH
$b = 0778
Interpolation and functions work:
Literal Array / Hash as argument in calls works:
Numbers must now be valid:
puppetconf.com #puppetconf
Array & Hash
$a = [1,2,3]
$b = [4,5,6]
$c = $a + $b
Concatenate Arrays:
Append to Array:
Merge Hash:
$d = $a << 4
$a = {name => 'mary'}
$b = {has => 'a little lamb'}
$c = $a + $b
puppetconf.com #puppetconf
Misc
unless $something { . . .}
else { . . . }
Unless Else:
Assignment is an expression:
Expression separator ';' :
$a = $b = 10
fqdn_rand($seed = 30)
$a = $x[1][1]
$a = $x[1];[1]
puppetconf.com #puppetconf
Error Reporting
Error: Could not parse for environment production:
Syntax error at 'node' at line 1 on node
kermit.example.com
puppet apply -e '$a = node "a+b" { }'
Then:
Now: Error: Invalid use of expression. A Node Definition
does not produce a value at line 1:6
Error: The hostname 'a+b' contains illegal characters
(only letters, digits, '_', '-', and '.' are allowed)
at line 1:11
Error: Classes, definitions, and nodes may only appear
at toplevel or inside other classes at line 1:6
Error: Could not parse for environment production:
Found 3 errors. Giving up on node kermit.example.com
New Approach
puppetconf.com #puppetconf
•  Forgiving Grammar
•  Validation is separate
– Can validate a particular language version
– Language version != Puppet version
•  Evaluation is separate
– Can evaluate a particular language version way
– Language version != Puppet version
Separation of Language
Concerns
New Language Features
puppetconf.com #puppetconf
puppetconf.com #puppetconf
puppetconf.com #puppetconf
puppetconf.com #puppetconf
puppetconf.com #puppetconf
•  Iteration & Lambdas
•  Puppet Bindings
•  Heredoc support
•  Puppet Templates
New Language Features
Iteration & Lambdas
puppetconf.com #puppetconf
Concepts
each($foo) |$x| { notice $x }
The lambda:
Inline call:
$foo.each |$x| { notice $x }
each($foo) |$x| { notice $x }
puppetconf.com #puppetconf
•  each
•  select – reject
•  collect
•  reduce
•  slice
Custom functions can take a lambda!
Iterating Functions
puppetconf.com #puppetconf
Each - Array
$a = ['a', 'b', 'c']
$a.each |$value| { notice $value }
Iterating over an array:
Produces:
Notice: Scope(Class[main]): a
Notice: Scope(Class[main]): b
Notice: Scope(Class[main]): c
puppetconf.com #puppetconf
Each – Array (with index)
$a = ['a', 'b', 'c']
$a.each |$index, $value| { notice "$index = $value" }
Iterating over an array – with index:
Produces:
Notice: Scope(Class[main]): 0 = a
Notice: Scope(Class[main]): 1 = b
Notice: Scope(Class[main]): 2 = c
puppetconf.com #puppetconf
Each - Hash
$a = {a => 10, b => 20, c => 30}
$a.each |$key, $value| { notice "$key = $value" }
Iterating over a hash – with key and value:
Produces:
Notice: Scope(Class[main]): a = 10
Notice: Scope(Class[main]): b = 20
Notice: Scope(Class[main]): c = 30
puppetconf.com #puppetconf
Each – Hash (elements)
$a = {a => 10, b => 20, c => 30}
$a.each |$elem| { notice "${elem[0]} = ${elem[1]}" }
Iterating over a hash elements:
Produces:
Notice: Scope(Class[main]): a = 10
Notice: Scope(Class[main]): b = 20
Notice: Scope(Class[main]): c = 30
puppetconf.com #puppetconf
Select / Reject
$a = [1, 2, 3]
notice $a.select |$value| { $v == 2 }
notice $a.reject |$value| { $v == 2 }
Select and Reject elements:
Produces:
Notice: Scope(Class[main]): 2
Notice: Scope(Class[main]): 1 3
puppetconf.com #puppetconf
Collect
$a = [1, 2, 3]
notice $a.collect |$value| { $v * 10 }
Transform each element with collect:
Produces:
Notice: Scope(Class[main]): 10 20 30
puppetconf.com #puppetconf
Reduce
$a = [1, 2, 3]
notice $a.reduce |$memo, $value| { $memo + $value }
Reduce all elements into one:
Produces:
Notice: Scope(Class[main]): 6
puppetconf.com #puppetconf
Examples
$usernames.each |$x| {
file { "/home/$x/.somerc":
owner => $x
}
}
Set ownership of some "rc-file" for each user:
Setting 'owner' and 'mode' from a Hash:
$users_with_mode = ['fred' => 0666, 'mary' => 0777 ]
$users_with_mode.each |$user, $mode| {
file {"/home/$user/.somerc":
owner => $user,
mode => $mode
}
}
puppetconf.com #puppetconf
Examples
$a.select |$x| { $x =~ /com$/ }.each |$x| {
file { "/somewhere/$x":
owner => $x
}
}
Filter and create resources:
Include classes based on array of roles:
$roles.each |$x| { include "role_$x" }
puppetconf.com #puppetconf
Custom Function (Ruby)
pp_block = args[-1]
Lambda always last argument:
Was it given?
Call it:
pp_block.is_a? Puppet::Parser::AST::Lambda
# in a custom function, self is scope)
pp_block.call(self, 'hello', 'world')
puppetconf.com #puppetconf
•  http://links.puppetlabs.com/arm2-iteration
•  http://links.puppetlabs.com/arm2-examples
ARM 2 - Iteration
Heredoc
Not yet on master branch
https://github.com/puppetlabs/puppet/pull/1659
puppetconf.com #puppetconf
Heredoc - Syntax
@( ["]<endtag>["] [:<syntax>] [/<escapes>] )
<text>
[|][-] <endtag>
ENDS-­‐HERE	
  
anything	
  not	
  in	
  <text>	
  
	
  
"ENDS-­‐HERE"	
  
with	
  interpola2on	
  
:json	
  
syntax	
  check	
  result	
  
/tsrn$L	
   	
  turns	
  on	
  escape	
  
/	
   	
   	
  turns	
  on	
  all	
  
|	
  
set	
  le7	
  margin	
  
-­‐	
  
trim	
  trailing	
  
t 	
  tab	
  
s 	
  space	
  
r 	
  return	
  
n 	
  new-­‐line	
  
$ 	
  $	
  
L 	
  <end	
  of	
  line>	
  
puppetconf.com #puppetconf
Heredoc – example
Example:
#.........1.........2.........3.........4.........5....
$a = @(END)
This is indented 2 spaces in the source, but produces
a result flush left with the initial 'T'
This line is thus indented 2 spaces.
| END
puppetconf.com #puppetconf
Heredoc – example
multiple on same line:
#.........1.........2.........3.........4.........5....
foo(@(FIRST), @(SECOND))
This is the text for the first heredoc
FIRST
This is the text for the second
SECOND
puppetconf.com #puppetconf
For more examples and details
•  http://links.puppetlabs.com/arm4-heredoc
ARM-4 Heredoc
Puppet Templates
Not yet on master branch
https://github.com/puppetlabs/puppet/pull/1660
puppetconf.com #puppetconf
•  Embedded Puppet (EPP) - like ERB
•  Same tags
§  <%, <%=, <%-, <%%, <%#
§  %>,-%>
•  Expressions are Puppet DSL
•  Parameterized
•  .epp file extension (by convention)
Puppet Templates
puppetconf.com #puppetconf
Use by calling
epptemplate(<name> [,<params_hash>])
inline_epptemplate(<text>[,<params_hash>])
Puppet Templates
$x = 'human'
inline_epptemplate('This is not the <%= $x %> you are
looking for.', { 'x' => 'droid'})
# => 'This is not the droid you are looking for.'
puppetconf.com #puppetconf
•  Parameterized
– declare parameters
– set default values
– parameter without value and no default = error
Puppet Templates
<%- ($x = 'human') -%>
This is not the <%= $x %> you are looking for.
puppetconf.com #puppetconf
For more examples and details
•  http://links.puppetlabs.com/arm3-
puppet_templates
ARM-3 Puppet Templates
Puppet Bindings / Data in Modules
puppetconf.com #puppetconf
•  More powerful data bindings
•  For both Data, and Puppet Extensions
•  Composes Hiera2 data in modules and
environment + Hiera1
•  Bindings in Ruby
•  Opt in on command line or in settings
Puppet Binder
puppet apply --binder
puppet apply –-parser future ...
puppetconf.com #puppetconf
•  Default:
– All hiera-2 data (in default location) and all
(default) ruby bindings from all modules on
module path composed
– Site level hiera-2 data and ruby bindings
override contributions from modules.
•  Customize
– include alternatives, exclude bindings
– add / reorganize overriding "layers"
Configuration
puppetconf.com #puppetconf
•  Composable
•  Interpolation using Puppet DSL expressions
•  Changed hiera.yaml syntax (for version 2)
Hiera 2
puppetconf.com #puppetconf
Minimal opt-in (in a module)
---
version: 2
hiera.yaml:
data/common.yaml:
data/${osfamily}.yaml:
---
myclass::myparam: '1.2.3'
---
myclass::myparam: '2.4.6'
puppetconf.com #puppetconf
For more examples and details
•  http://links.puppetlabs.com/arm8-
puppet_bindings
•  http://links.puppetlabs.com/arm9-
data_in_modules
ARM-8 & 9
Thank You
Henrik Lindberg
Consulting Engineer | Puppet Labs
@hel
Collaborate. Automate. Ship.
Follow us on Twitter @puppetlabs
youtube.com/puppetlabsinc
slideshare.net/puppetlabs
Collaborate. Automate. Ship.

Loops and Unicorns - The Future of the Puppet Language - PuppetConf 2013

  • 1.
  • 2.
    Loops and Unicorns Futureof the Puppet Language Henrik Lindberg Consulting Engineer | Puppet Labs @hel
  • 3.
    puppetconf.com #puppetconf •  Newparser •  New Language Features •  New approach – Opt in to New/Experimental Features – Maintaining Backwards Compatibility Introduction
  • 4.
    New Parser The futureis already here…
  • 5.
    puppetconf.com #puppetconf •  Rewrittenfrom the ground up •  Removes quirky grammar constraints •  Improves error messages •  Enabler for: – opt-in to new and experimental features – backwards compatibility •  Use on command line, or in settings New Parser puppet apply –-parser future ...
  • 6.
    puppetconf.com #puppetconf De-Quirk "This isa random number: ${fqdn_rand()}" join([1,2,3]) $a = 0x0EH $b = 0778 Interpolation and functions work: Literal Array / Hash as argument in calls works: Numbers must now be valid:
  • 7.
    puppetconf.com #puppetconf Array &Hash $a = [1,2,3] $b = [4,5,6] $c = $a + $b Concatenate Arrays: Append to Array: Merge Hash: $d = $a << 4 $a = {name => 'mary'} $b = {has => 'a little lamb'} $c = $a + $b
  • 8.
    puppetconf.com #puppetconf Misc unless $something{ . . .} else { . . . } Unless Else: Assignment is an expression: Expression separator ';' : $a = $b = 10 fqdn_rand($seed = 30) $a = $x[1][1] $a = $x[1];[1]
  • 9.
    puppetconf.com #puppetconf Error Reporting Error:Could not parse for environment production: Syntax error at 'node' at line 1 on node kermit.example.com puppet apply -e '$a = node "a+b" { }' Then: Now: Error: Invalid use of expression. A Node Definition does not produce a value at line 1:6 Error: The hostname 'a+b' contains illegal characters (only letters, digits, '_', '-', and '.' are allowed) at line 1:11 Error: Classes, definitions, and nodes may only appear at toplevel or inside other classes at line 1:6 Error: Could not parse for environment production: Found 3 errors. Giving up on node kermit.example.com
  • 10.
  • 11.
    puppetconf.com #puppetconf •  ForgivingGrammar •  Validation is separate – Can validate a particular language version – Language version != Puppet version •  Evaluation is separate – Can evaluate a particular language version way – Language version != Puppet version Separation of Language Concerns
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
    puppetconf.com #puppetconf •  Iteration& Lambdas •  Puppet Bindings •  Heredoc support •  Puppet Templates New Language Features
  • 18.
  • 19.
    puppetconf.com #puppetconf Concepts each($foo) |$x|{ notice $x } The lambda: Inline call: $foo.each |$x| { notice $x } each($foo) |$x| { notice $x }
  • 20.
    puppetconf.com #puppetconf •  each • select – reject •  collect •  reduce •  slice Custom functions can take a lambda! Iterating Functions
  • 21.
    puppetconf.com #puppetconf Each -Array $a = ['a', 'b', 'c'] $a.each |$value| { notice $value } Iterating over an array: Produces: Notice: Scope(Class[main]): a Notice: Scope(Class[main]): b Notice: Scope(Class[main]): c
  • 22.
    puppetconf.com #puppetconf Each –Array (with index) $a = ['a', 'b', 'c'] $a.each |$index, $value| { notice "$index = $value" } Iterating over an array – with index: Produces: Notice: Scope(Class[main]): 0 = a Notice: Scope(Class[main]): 1 = b Notice: Scope(Class[main]): 2 = c
  • 23.
    puppetconf.com #puppetconf Each -Hash $a = {a => 10, b => 20, c => 30} $a.each |$key, $value| { notice "$key = $value" } Iterating over a hash – with key and value: Produces: Notice: Scope(Class[main]): a = 10 Notice: Scope(Class[main]): b = 20 Notice: Scope(Class[main]): c = 30
  • 24.
    puppetconf.com #puppetconf Each –Hash (elements) $a = {a => 10, b => 20, c => 30} $a.each |$elem| { notice "${elem[0]} = ${elem[1]}" } Iterating over a hash elements: Produces: Notice: Scope(Class[main]): a = 10 Notice: Scope(Class[main]): b = 20 Notice: Scope(Class[main]): c = 30
  • 25.
    puppetconf.com #puppetconf Select /Reject $a = [1, 2, 3] notice $a.select |$value| { $v == 2 } notice $a.reject |$value| { $v == 2 } Select and Reject elements: Produces: Notice: Scope(Class[main]): 2 Notice: Scope(Class[main]): 1 3
  • 26.
    puppetconf.com #puppetconf Collect $a =[1, 2, 3] notice $a.collect |$value| { $v * 10 } Transform each element with collect: Produces: Notice: Scope(Class[main]): 10 20 30
  • 27.
    puppetconf.com #puppetconf Reduce $a =[1, 2, 3] notice $a.reduce |$memo, $value| { $memo + $value } Reduce all elements into one: Produces: Notice: Scope(Class[main]): 6
  • 28.
    puppetconf.com #puppetconf Examples $usernames.each |$x|{ file { "/home/$x/.somerc": owner => $x } } Set ownership of some "rc-file" for each user: Setting 'owner' and 'mode' from a Hash: $users_with_mode = ['fred' => 0666, 'mary' => 0777 ] $users_with_mode.each |$user, $mode| { file {"/home/$user/.somerc": owner => $user, mode => $mode } }
  • 29.
    puppetconf.com #puppetconf Examples $a.select |$x|{ $x =~ /com$/ }.each |$x| { file { "/somewhere/$x": owner => $x } } Filter and create resources: Include classes based on array of roles: $roles.each |$x| { include "role_$x" }
  • 30.
    puppetconf.com #puppetconf Custom Function(Ruby) pp_block = args[-1] Lambda always last argument: Was it given? Call it: pp_block.is_a? Puppet::Parser::AST::Lambda # in a custom function, self is scope) pp_block.call(self, 'hello', 'world')
  • 31.
    puppetconf.com #puppetconf •  http://links.puppetlabs.com/arm2-iteration • http://links.puppetlabs.com/arm2-examples ARM 2 - Iteration
  • 32.
    Heredoc Not yet onmaster branch https://github.com/puppetlabs/puppet/pull/1659
  • 33.
    puppetconf.com #puppetconf Heredoc -Syntax @( ["]<endtag>["] [:<syntax>] [/<escapes>] ) <text> [|][-] <endtag> ENDS-­‐HERE   anything  not  in  <text>     "ENDS-­‐HERE"   with  interpola2on   :json   syntax  check  result   /tsrn$L    turns  on  escape   /      turns  on  all   |   set  le7  margin   -­‐   trim  trailing   t  tab   s  space   r  return   n  new-­‐line   $  $   L  <end  of  line>  
  • 34.
    puppetconf.com #puppetconf Heredoc –example Example: #.........1.........2.........3.........4.........5.... $a = @(END) This is indented 2 spaces in the source, but produces a result flush left with the initial 'T' This line is thus indented 2 spaces. | END
  • 35.
    puppetconf.com #puppetconf Heredoc –example multiple on same line: #.........1.........2.........3.........4.........5.... foo(@(FIRST), @(SECOND)) This is the text for the first heredoc FIRST This is the text for the second SECOND
  • 36.
    puppetconf.com #puppetconf For moreexamples and details •  http://links.puppetlabs.com/arm4-heredoc ARM-4 Heredoc
  • 37.
    Puppet Templates Not yeton master branch https://github.com/puppetlabs/puppet/pull/1660
  • 38.
    puppetconf.com #puppetconf •  EmbeddedPuppet (EPP) - like ERB •  Same tags §  <%, <%=, <%-, <%%, <%# §  %>,-%> •  Expressions are Puppet DSL •  Parameterized •  .epp file extension (by convention) Puppet Templates
  • 39.
    puppetconf.com #puppetconf Use bycalling epptemplate(<name> [,<params_hash>]) inline_epptemplate(<text>[,<params_hash>]) Puppet Templates $x = 'human' inline_epptemplate('This is not the <%= $x %> you are looking for.', { 'x' => 'droid'}) # => 'This is not the droid you are looking for.'
  • 40.
    puppetconf.com #puppetconf •  Parameterized – declareparameters – set default values – parameter without value and no default = error Puppet Templates <%- ($x = 'human') -%> This is not the <%= $x %> you are looking for.
  • 41.
    puppetconf.com #puppetconf For moreexamples and details •  http://links.puppetlabs.com/arm3- puppet_templates ARM-3 Puppet Templates
  • 42.
    Puppet Bindings /Data in Modules
  • 43.
    puppetconf.com #puppetconf •  Morepowerful data bindings •  For both Data, and Puppet Extensions •  Composes Hiera2 data in modules and environment + Hiera1 •  Bindings in Ruby •  Opt in on command line or in settings Puppet Binder puppet apply --binder puppet apply –-parser future ...
  • 44.
    puppetconf.com #puppetconf •  Default: – Allhiera-2 data (in default location) and all (default) ruby bindings from all modules on module path composed – Site level hiera-2 data and ruby bindings override contributions from modules. •  Customize – include alternatives, exclude bindings – add / reorganize overriding "layers" Configuration
  • 45.
    puppetconf.com #puppetconf •  Composable • Interpolation using Puppet DSL expressions •  Changed hiera.yaml syntax (for version 2) Hiera 2
  • 46.
    puppetconf.com #puppetconf Minimal opt-in(in a module) --- version: 2 hiera.yaml: data/common.yaml: data/${osfamily}.yaml: --- myclass::myparam: '1.2.3' --- myclass::myparam: '2.4.6'
  • 47.
    puppetconf.com #puppetconf For moreexamples and details •  http://links.puppetlabs.com/arm8- puppet_bindings •  http://links.puppetlabs.com/arm9- data_in_modules ARM-8 & 9
  • 49.
    Thank You Henrik Lindberg ConsultingEngineer | Puppet Labs @hel Collaborate. Automate. Ship.
  • 50.
    Follow us onTwitter @puppetlabs youtube.com/puppetlabsinc slideshare.net/puppetlabs Collaborate. Automate. Ship.