{"id":3233,"date":"2015-07-31T19:18:20","date_gmt":"2015-07-31T18:18:20","guid":{"rendered":"https:\/\/www.devco.net\/?p=3233"},"modified":"2016-11-03T05:34:12","modified_gmt":"2016-11-03T04:34:12","slug":"shiny-new-things-in-puppet-4","status":"publish","type":"post","link":"https:\/\/www.devco.net\/archives\/2015\/07\/31\/shiny-new-things-in-puppet-4.php","title":{"rendered":"Shiny new things in Puppet 4"},"content":{"rendered":"
Puppet 4 has been out a while but given the nature of the update – new packaging requiring new modules to manage it etc I’ve been reluctant to upgrade and did not have the time really. Ditto for Centos 7. But Docker will stop supporting Centos 6 Soon Now<\/em> so this meant I had to look into both a bit closer.<\/p>\n Puppet 4 really is a whole new thing, it maintains backward compatibility but really in terms of actually using its features I think you’d be better off just starting fresh. I am moving the bulk of my services out of CM anyway so my code base will be tiny so not a big deal for me to just throw it all out and start fresh.<\/p>\n I came across a few really interesting new things amongst it’s many features and wanted to highlight a few of these. This is by no means an exhaustive list, it’s just a whirlwind tour of a few things I picked up on.<\/p>\n Not really a Puppet 4 thing per se but more a general eco system comment. I have 23 modules in my new freshly minted Puppet repo with 13 of them coming from the forge. To my mind that is a really impressive figure, it makes the new starter experience so much better.<\/p>\n Things I still do on my own: exim, iptables, motd, pki, roles\/profiles of course and users. <\/p>\n In the case of exim I have almost no config, it’s just a package\/config\/service and all it does is setup a local config that talks to my smart relays. It does use my own CA though and that’s why I also have my own PKI module to configure the CA and distribute certs and keys and such. The big one is iptables really and I just haven’t had the time to really consider a replacement – whatever I choose it needs to play well with docker and that’s probably going to be a tall order.<\/p>\n Anyway, big kudos on the forge team and shout outs to forge users: puppetlabs, jfryman, saz and garethr. <\/p>\n Still some things to fix – puppet module tool is pretty grim wrt errors and feedback and I think there’s work left to do on discoverability of good modules and finding ways to promote people investing time in making better ones, but this is a big change from 2 years ago for sure.<\/p>\n Data used to go from hiera to manifests and ending up strings when the data was Boolean now Puppet knows about actual Booleans and does not mess it up – things will become pretty consistant and solid and it will be easy to write well behaved code.<\/p>\n For now though it’s the opposite, there are many more edge cases as a result of it.<\/p>\n Particularly functions that previously took a number and did something with it might have assumed the number was a string with a number in it. Now it’s going to get an actual number and this causes breakage. There are a few of these in stdlib but they are getting fixed – expect this will catch out many templates and functions so there will be a settling in period but it’s well worth the effort.<\/p>\n Here’s an example:<\/p>\n If I passed ensure => bob<\/em> to this I get:<\/p>\n Pretty handy though the errors can improve a lot – something I know is on the road map already.<\/p>\n You can get pretty complex with this like describe the entire contents of a hash and Puppet will just ensure any hash you receive matches this, doing this would have been really hard even with all the stuff in old stdlib:<\/p>\n I suggest you spend a good amount of time with the docs About Values and Data Types<\/a>, Data Types: Data Type Syntax<\/a> and Abstract Data Types<\/a>. There are many interesting types like ones that do Pattern matching etc.<\/p>\n Case statements and Selectors have also become type aware as have normal expressions to test equality etc:<\/p>\n It’s not all wonderful though, I think the syntax choices are pretty poor. I scan parameter lists: a) to discover module features b) to remind myself of the names c) to find things to edit. With the type preceding the variable name every single use case I have for reading a module code has become worse and I fear I’ll have to resort to lots of indention to make the var names stand out from the type definitions. I cannot think of a single case where I will want to know the variable data type before knowing it’s name. So from a readability perspective this is not great at all.<\/p>\n Additionally I cannot see myself using a Struct like above in the argument list – to which Henrik says they are looking to add a typedef thing to the language so you can give complex Struc’s a more convenient name and use that. This will help that a lot. Something like this:<\/p>\n That’ll be handy and Henrik says this is high on the priority list, it’s pretty essential from a usability perspective.<\/p>\n UPDATE:<\/strong> As of 4.4.0 this has been delivered, see Puppet 4 Type Aliases<\/a><\/p>\n You can merge arrays and hashes easily:<\/p>\n And yes you can now use a ;<\/em> instead of awkwardly making new lines all the time for quick one-liner tests like this.<\/p>\n There’s a new way to do resource defaults. I know this is a widely loathed syntax but I quite like it:<\/p>\n The specific mode on \/etc\/ssh_host_dsa_key.pub will override the defaults, pretty handy. And it address a previous issue with old style defaults that they would go all over the scope and make a mess of things. This is confined to just these files.<\/p>\n This is something people often ask for, it’s seems exciting but I don’t think it will be of any practical use because it’s order dependant just like defined()<\/em>.<\/p>\n So this fetches another resource parameter value.<\/p>\n You can also fetch class parameters this way but this seems redundant, there are several ordering caveats so test your code carefully.<\/p>\n This doesn’t really need comment, perhaps only OMFG FINALLY is needed.<\/p>\n More complex things like map and select exist too:<\/p>\n This yields a new hash made up of all the parts:<\/p>\n See Iterating in Puppet<\/a> for more details on this.<\/p>\n If you’re from Ruby this might be a bit more bearable, you can use any function interchangably it seems:<\/p>\n Both result in a,b<\/em><\/p>\n By default it now does manifest ordering. This is a big deal, I’ve had to write no ordering code at all. None. Not a single require or ordering arrows. It’s just does things top down by default but parameters like notifies and specific requires influence it. Such an amazingly massive time saver. Good times when things that were always obviously dumb ideas goes away.<\/p>\n It’s clever enough to also do things in the order they are included. So if you had:<\/p>\n Ordering will magically be right. Containment is still a issue though.<\/p>\n Ever since the first contributor summit I’ve been campaigning for $facts[“foo”]<\/em> and it’s gone all round with people wanting to invent some new hash like construct and worse, but finally we have now a by default enabled facts hash.<\/p>\n Unfortunately we are still stuck with $settings::vardir<\/em> but hopefully some new hash will be created for that.<\/p>\n It’s a reserved word everywhere so you can safely just do $facts[“location”]<\/em> and not even have to worry about $::facts<\/em>, though you might still do that in the interest of consistency.<\/p>\n Facter 3 is really fast:<\/p>\n This makes everything better. It’s also structured data but this is still a bit awkward in Puppet:<\/p>\n There seems to be no elegant way to handle a missing ‘foo’ or ‘bar’ key, things just fail badly in ways you can’t catch or recover from. On the CLI you can do facter foo.bar.baz<\/em> so we’re already careful to not have “.” in a key. We need some function to extract data from hashes like:<\/p>\n It’ll make it a lot easier to deal with.<\/p>\n Hiera 3 is out and at first I thought it didn’t handle hashes well, but it does:<\/p>\n That’s how you’d fetch values out of hashes and it’s pretty great. Notice I didn’t do ::facts<\/em> that’s because facts is reserved so there’ll be no scope layering issues.<\/p>\n You can use functions almost everywhere:<\/p>\n There are an immeasurable amount of small improvements in things the old parser did badly, now it’s really nice to use, things just work the way I expect them to do from other languages.<\/p>\n Even horrible stuff like this works:<\/p>\n Which previously needed an intermediate variable.<\/p>\n A small thing but –test<\/em> in apply now works like in agent – colors, verbose etc, really handy.<\/p>\n I did a PoC to enable Hiera in modules a few years ago and many people loved the idea. This has finally landed in recent Puppet 4 versions and it’s pretty awesome. It lets you have a data<\/em> directory and hiera.yaml<\/em> in your module, this goes some way towards removing what is currently done with params.pp<\/em><\/p>\n I wrote a blog post about it: Native Puppet 4 Data in Modules<\/a>. An additional blog post that covers this is params.pp in Puppet 4<\/a> which shows how it ties together with some other new things.<\/p>\n create_resources is a hack that exists because it was easier to hack up than fix Puppet. Puppet has now been fixed, so this is the new create_resources<\/em>.<\/p>\nThe Forge<\/h2>\n
Puppet 4 Type System<\/H2>
\nPuppet 4 has a data type system, it’s kind of optional which is weird as things go but you can almost think of it like a built in way to do validate_hash<\/em> and friends, almost. The implications of having it though are huge – it means down the line there will be a lot fewer edge cases with things just behaving weirdly. <\/p>\n\r\ndefine users::user(\r\n ...\r\n Enum[\"present\", \"absent\"] $ensure = \"present\",\r\n Optional[String] $ssh_authorized_file = undef,\r\n Optional[String] $email = undef,\r\n Optional[Integer] $uid = undef,\r\n Optional[Integer] $gid = undef,\r\n Variant[Boolean, String] $sudoer = false,\r\n Boolean $setup_shell = false,\r\n Boolean $setup_rbenv = false\r\n) {\r\n...\r\n}\r\n<\/pre>\n
\r\nError: Expected parameter 'ensure' of 'Users::User[rip]' to have type Enum['present', 'absent'], got String\r\n<\/pre>\n
\r\nStruct[{mode => Enum[read, write, update],\r\n path => Optional[String[1]],\r\n NotUndef[owner] => Optional[String[1]]}]\r\n<\/pre>\n
\r\n$enable_real = $enable ? {\r\n Boolean => $enable,\r\n String => str2bool($enable),\r\n Numeric => num2bool($enable),\r\n default => fail('Illegal value for $enable parameter'),\r\n}\r\n\r\nif 5 =~ Integer[1,10] {\r\n notice(\"it's a number between 1 and 10\")\r\n}\r\n<\/pre>\n
\r\ntype MyData = Struct[{ .... }]\r\n\r\ndefine foo(MyData $bar) {\r\n}\r\n<\/pre>\n
Native data merges<\/h2>\n
\r\n$ puppet apply -e '$a={\"a\" => \"b\"}; $b={\"c\" => \"d\"}; notice($a+$b)'\r\nNotice: Scope(Class[main]): {a => b, c => d}\r\n\r\n$ puppet apply -e 'notice([1,2,3] + [4,5,6])'\r\nNotice: Scope(Class[main]): [1, 2, 3, 4, 5, 6]\r\n<\/pre>\n
Resource Defaults<\/h2>\n
\r\nfile {\r\n default:\r\n mode => '0600',\r\n owner => 'root',\r\n group => 'root',\r\n ensure => file,\r\n '\/etc\/ssh_host_key':\r\n ;\r\n '\/etc\/ssh_host_dsa_key.pub':\r\n mode => '0644',\r\n}\r\n<\/pre>\n
Accessing resource parameter values<\/h2>\n
\r\nnotify{\"hello\": message => \"world\"}\r\n\r\n$message = Notify[\"hello\"][\"message\"] # would be 'world'\r\n<\/pre>\n
Loops<\/h2>\n
\r\n[\"puppet\", \"facter\"].each |$file| {\r\n file{\"\/usr\/bin\/${file}\":\r\n ensure => \"link\",\r\n target => \"\/opt\/puppetlabs\/bin\/${file}\"\r\n }\r\n}\r\n<\/pre>\n
\r\n$domains = [\"foo.com\", \"bar.com\"]\r\n$domain_definition = $domains.reduce({}) |$memo, $domain| {\r\n $memo + {$domain => {\"relay\" => \"mx.${domain}\"}}\r\n}\r\n<\/pre>\n
\r\n{\r\n \"foo.com\" => {\"relay\" => \"mx.foo.com\"},\r\n \"bar.com\" => {\"relay\" => \"mx.bar.com\"}\r\n}\r\n<\/pre>\n
. syntax<\/h2>\n
\r\n$x = join([\"a\", \"b\"], \",\")\r\n\r\n$y = [\"a\", \"b\"].join(\",\")\r\n<\/pre>\n
Default Ordering<\/h2>\n
\r\nclass myapp {\r\n include myapp::install\r\n include myapp::config\r\n include myapp::service\r\n}\r\n<\/pre>\n
Facts hash<\/h2>\n
Facter 3<\/h2>\n
\r\n$ time facter\r\nfacter 0.08s user 0.03s system 44% cpu 0.248 total\r\n<\/pre>\n
\r\n$x = $facts[\"foo\"][\"bar\"][\"baz\"]\r\n<\/pre>\n
\r\n$x = $facts.fetch(\"foo.bar.baz\", \"default\")\r\n<\/pre>\n
Hiera 3<\/h3>\n
\r\n:hierarchy:\r\n - \"%{facts.fqdn}\"\r\n - \"location_%{facts.location}\"\r\n - \"country_%{facts.country}\"\r\n - common\r\n<\/pre>\n
Much better parser<\/h2>\n
\r\n$ puppet apply -e 'notify{hiera(\"rsyslog::client::server\"): }'\r\nNotice: loghost.example.net\r\n<\/pre>\n
\r\n$x = hiera_hash(\"something\")[\"foo\"]\r\n<\/pre>\n
puppet apply –test<\/h2>\n
Data in Modules<\/h2>\n
Native create_resources<\/h2>\n
\r\neach($domains) |$name, $domain| { \r\n mail::domain{$name:\r\n * => $domain\r\n }\r\n}\r\n<\/pre>\n