{"id":3373,"date":"2016-03-13T15:52:56","date_gmt":"2016-03-13T14:52:56","guid":{"rendered":"https:\/\/www.devco.net\/?p=3373"},"modified":"2016-03-15T13:08:22","modified_gmt":"2016-03-15T12:08:22","slug":"the-puppet-4-lookup-function","status":"publish","type":"post","link":"https:\/\/www.devco.net\/archives\/2016\/03\/13\/the-puppet-4-lookup-function.php","title":{"rendered":"The Puppet 4 Lookup Function"},"content":{"rendered":"
Puppet 4 has a new lookup subsystem exposed to the user in a few places:<\/p>\n
I’ve not been able to figure out everything the docs have been trying to say about this function but it turns out they were copied from the deep_merge gem and it actually has better examples in some cases. So I thought a post exploring it and it’s various forms is in order<\/p>\n
It’s pivotal to the use of data in Puppet so while you probably don’t need to fully grasp all of it’s intricacies as in this post a passing knowledge is valuable as is knowing how to find good help for it. I do think there’s some opportunity for improving the UX of this function though.<\/p>\n
As usual the challenge when faced with all these options isn’t in how to use them all but in which options to use when that won’t result in a giant unmaintainable mess down the line. I think this function is definitely on the wrong side of the line in this regard. It’s massive and unwieldy in that it is exposing internals of Puppet in a 1:1 manner to the user.<\/p>\n
So I would not recommend writing code that calls this function directly unless in extraordinary circumstances. With the Data in Modules and Automatic Parameter Lookup features you can achieve this, see the last section of the post for that. <\/p>\n
First though you need to know the behaviours and terminology of the lookup() function in order to get to a point where you can use the other methods, so lets dive in.<\/p>\n
\r\nlookup(\"some::thing\", String, \"first\", \"default value\")\r\n<\/pre>\nHere we’re looking up the key some::thing<\/em> and it has to be a String<\/em> from the data store. It will do a first<\/em> style lookup which is your basic traditional Hiera first-match-wins and there’s a default. Apparently there is no simple case lookup(“some::thing”, “default”)<\/em> which seems like it would be the most common use. You can come kind of close though with (more on this below):<\/p>\n\r\nlookup({\"name\" => \"some::thing\", \"default_value\" => \"default\"})\r\n<\/pre>\nAnyway, you’re not really going to be using the lookup function directly much so this is probably fine<\/p>\n
The thing to note here are the lookup strategies, there are a few and you will always have to know them:<\/p>\n
\n\nfirst<\/strong><\/td>\nFirst match found is returned, just like in traditional hiera() default behaviour<\/td>\n<\/tr>\n \nunique<\/strong><\/td>\nThis is an array merge like old hiera_array().<\/td>\n<\/tr>\n \nhash<\/strong><\/td>\nThis is hiera_hash() without deep merging enabled.<\/td>\n<\/tr>\n \ndeep<\/strong><\/td>\nThis is hiera_hash() with deep merging enabled. You would not guess this from the description in the docs.<\/td>\n<\/tr>\n<\/table>\nSo this is your basic replacement for the old hiera()<\/em>, hiera_hash()<\/em> and hiera_array()<\/em> and as you can see from the last 2 the merge strategy isn’t set globally like in old Hiera, this is a big improvement.<\/p>\nI will not go into a full exploration of what Tiers mean, the old Hiera docs are pretty good for that. Effectively a merge strategy describe what Hiera does when it finds interesting data in many different levels of data or in different data sources.<\/p>\n
Complex Strategies for Setting Defaults<\/H4>
\nFrom here it gets a bit crazy, but there are some really great things you can do with some of these so lets look at them. <\/p>\n
First I’ll look at the task of setting defaults. Hiera had quite basic features in this space which was enough to get going but lookup has some nice additions.<\/p>\n
First the above lookup can also be written like this:<\/p>\n
\r\nlookup({\"name\" => \"some::thing\", \"value_type\" => String, \"default_value\" => \"default\", \"merge\" => \"first\"})\r\nlookup({\"name\" => \"some::thing\", \"default_value\" => \"default\"}) # though accepts any data type\r\n<\/pre>\nSo this is quite nice because now you can decide the order of arguments and which to include.<\/p>\n
There’s a more powerful way to set defaults though:<\/p>\n
\r\nfunction some_module::params() {\r\n $result = {\r\n \"some_module::thing\" => \"default\",\r\n \"some_module::other_thing\" => false\r\n }\r\n}\r\n\r\nlookup({\"name\" => \"some_module::thing\", \"default_values_hash\" => some_module::params()})\r\n<\/pre>\nWhich at first does not seem a huge improvement, but if you’re thinking about strategies to replace something like params.pp<\/em> you could come up with some interesting patterns using this method. For example you can have a module function like here and an environment one (it supports environment level native functions) and combine them like environment_params() + some_module::params()<\/em> to come up with layered sets of defaults, in effect this would be a micro hiera on it’s own programmed in pure Puppet DSL.<\/p>\nAnd finally you can use a lambda to set the default:<\/p>\n
\r\nlookup(\"some::thing\") |$key| { \"Could not find a value for key '${key}', please configure it in your hiera data\" }\r\n<\/pre>\nHere we return a custom string instead that tells the user what is going on rather than blow up badly and we can of course include any helpful information like fact values and such to help them find the right place in your possibly complex data store.<\/p>\n
Sticking to the Lambda I saw Henrik mention this on IRC yesterday:<\/p>\n
\r\n$result = with(lookup(\"some::thing\")) |$value| { if $value =~ Array { $value } else { [$value] } }\r\n<\/pre>\n