Without getting to far into the weeds, Nginx is forcing my hand in order to accomplish some magic with vhosts and the map
directive.
Is there an elegant (relative) solution to sharing a variable across multiple define calls, which allows each define call to append it's data to the global variable? In software this would be known as a singleton.
-- The weeds --
Nginx has a map
directive which dictates which upstream server pool the request should be passed to, like so:
map $http_host $upstream_pool {
hostnames;
blog.example.com blog_example_com;
repo.example.com repo_example_com;
default example_com;
}
As you can see, any requests for blog.example.com will be passed to the blog_example_com upstream server pool (by way of proxy_pass
).
The problem is the map
directive syntax is that it can only be included within the main http
block (nginx.conf), whereas vhost specific directives such as upstream
and location
can be included in the server
block of a vhost config.
My nodes.pp manifest looks something like this:
service-a-1.example.com inherits project_dev {
nginx::vhost { 'mothership': }
nginx::vhost { 'mothership_blog': }
nginx::vhost { 'repo': }
}
As you can see, after a successful puppet run, I should end up with 3 distinct vhost config files in /etc/nginx/vhost.d/
dir.
The problem I am having is that in order for the map
directive to work, I need to know which vhosts were loaded, so I can add their respective upstream ids to the map
directive, which I have defined in the primary config: /etc/nginx/nginx.conf
within the http
block (of which there can only be one).
-- What I have tried --
I have a global.pp file which does some "bootstrapping" and in this file I added a $singleton = ''
syntax, then in the nginx::vhost define, I added this syntax:
$tpl_upstream_pool_labels = inline_template("<% tpl_upstream_pools.keys.sort.each do |role| %><%= role %>_<%= tpl_domain_primary %>_<%= tpl_domain_top_level %>|<% end %>")
$singleton = "${singleton}${tpl_upstream_pool_labels}"
notify { "\n--------------------- Nginx::Conf::Vhost::Touch | Timestamp: ${timestamp} | Pool labels: ${singleton} -------------------------\n": }
Which should result in a pipe delimited list of upstream ids. As mentioned earlier, in the nodes.pp manifest, I make three calls to nginx::vhost, and would expect the $singleton
global variable to be appended for every call, however it is not, it only contains the last call's data.
I also tried to hack my way around this by writing a temp file like so:
$temp_file_upstream_pool_labels_uri = "/tmp/puppet_known_upstreams_${timestamp}.txt"
exec { "event_record_known_upstream_${name}" :
command => "touch ${temp_file_upstream_pool_labels_uri} && echo ${tpl_upstream_pool_labels} >> ${temp_file_upstream_pool_labels_uri}",
provider => 'shell'
}
Then in the nginx::conf::touch define, where the primary config nginx.conf is to be written by puppet, I tried this:
$temp_file_upstream_pool_labels_uri = "/tmp/puppet_known_upstreams_${timestamp}.txt"
$contents = file($temp_file_upstream_pool_labels_uri)
Which should, in theory, load the contents of the file into the $contents variable. But when I run puppet using this approach I get an error that the file does not exist. I ensured that the nginx::conf::touch call is not made until after all the vhosts were considered, but still to no avail.
The problem with global variables in Puppet is that you cannot actually append to them. The
+=
syntax is allowed, but it creates a local copy of the global, with the right hand side value appended.I have implemented a pattern that does what you want, but I'm not proud of it and cannot recommend using it. Still, since you're asking, here goes:
The variable from the class can be appended, at least in
2.7.x
.The magic happens in the mysterious final line, which instantiates yet another
define
.The
content
property fornginx.conf
is overridden with the result of another evaluation of the template, with the new variable value.Full disclosure, I'm not sure why this works. It probably shouldn't, and may rely on an obscure bug. It may stop working in future versions.
Note that I called it
$global
instead of$singleton
, because that's what it is. Singletons can be used to implement the semantics of globals, but they are not the same thing.Finally, even though I can feel your pain facing the Puppet 3 update, you should really take the time and get it underway. We all will likely not be able to afford running
2.x
much longer.Adding another answer to keep the issues separate.
The approach that combines
exec
andfile()
is flawed because those work on very different levels.The
exec
resource is added to the catalog and sent to the agent, where it is evaluated after compiling has finished. Since you are trying to gather information during compile time (before you collect it using thefile()
function), you cannot rely on any resources.It should be possible to build what you have in mind by relying on the generate function instead of
exec
resources.