Chef: use attributes created dynamically - attributes

I have 2 recipes that belongs to the same cookbook.
The first recipe uncompress the apache-tomcat-xxx.tar.gz file in /opt/tomcat/apache-tomcat-xxx
In that first recipe I do
tomcat_folder = ls /opt/tomcat
node.default['tomcat']['home'] = "/opt/tomcat/#{tomcat_folder}"
so this attribute is created during execution of that recipe.
My second recipe needs to use that attribute value in resource like:
template node.default['tomcat']['home'] ...
directory node.default['tomcat']['home'] ...
and
ruby_block
block do
node.default['tomcat']['home'] ....
But I receive errors due to that attribute doesn't exist when it executes.
In some other resources I could make it work using lazy{…} blocks, but in those resources I don't know how to make them work.
So my question is, how shall I do to set that attribute from recipe 1 so it is able to be used from recipe 2 when they are executed?

Here you also can use lazy, but with some workaround.
template 'tomcat_home' do
path lazy { node['tomcat']['home'] }
end
directory 'tomcat_home' do
path lazy { node['tomcat']['home'] }
end
path for directory and template is a name attribute, which means, if it is not set inside the block it is taken from the name of the resource. This is how you did it before. But if you need lazy evaluation, you can set any string as the name of the resource. Just make sure, you don't create same resources with same names, but different bodies, as they will overwrite each other.
Another thing you shouldn't to read an attribute from particular precedence level (default, normal, override). Just node['tomcat']['home'] is the way to go.

Related

Puppet contain not working to order class inclusion

I am trying to force a Puppet class that creates a file to be processed before another class that needs this file to exist to run properly. Following the Puppet article Language: Containment of resources I am using contain.
My code does not work and I do not understand why. It gives this error:
Error: Evaluation Error: Error while evaluating a Function Call, Failed to parse template testing/def.erb:
Filepath: /root/local/testing/templates/def.erb
Line: 1
Detail: No such file or directory # rb_sysopen - /tmp/abc
at /root/local/test2.pp:16:16 on node example.com
Here is the code (stripped down):
### test2.pp
class klass1 {
file { '/tmp/abc':
content => 'xxx',
}
}
# Stage 0 creates the file /tmp/abc.
class stage0 {
contain klass1
}
# Stage 1 uses the contents of /tmp/abc to create the
# file /tmp/def.
class stage1 {
file { '/tmp/def':
content => template('testing/def.erb'),
}
}
# Try to force stage0 to be loaded before stage1.
include stage0
class { 'stage1':
require => Class['stage0']
}
### testing/templates/def.erb
Contents: <%= File.read("/tmp/abc") %>
I am using Puppet 5.3.3.
The issue here does not relate to containment, but to the dependency in your template at compile time on the call to File.read("/tmp/abc").
Ordinarily, compilation occurs on the Puppet Master a.k.a. Puppet Server, and the template function also runs at this time. Thus, your template def.erb attempts to read from a nonexistent file at compile time on the Puppet Master.
A better solution is likely to be define the content of file /tmp/abc in Puppet itself as data or a variable and then pass that variable to the template function, and so remove the dependency on reading from the file on disk altogether.
Without fully understanding why you were trying to separate this file content into multiple classes in the first place, I can't really comment any further.
Puppet is a declarative language that is typically used to ensure certain state of resources. But if you really need make a decision based on local code evaluation, you're basically left with 2 options:
Firstly, use facter, in some module create lib/facter/my_fact.rb:
#!/usr/bin/env ruby
require 'facter'
Facter.add(:load1) do
confine kernel: 'Linux'
setcode do
Facter::Util::Resolution.exec("cat /proc/loadavg | awk '{print $1}'")
end
end
Then your "function" result will be available via facter load1 (from shell) or $facts['load1'] from Puppet code. Such code is always evaluated before applying Puppet's catalog. If your function doesn't take arguments, this might be a good option. Note this is a silly example, the load is already available via facter load_averages.1m (though the usefulness of such fact is questionable). Using too many facts isn't good idea, it would prolong time required for applying a Puppet catalog. There's a soft limit for number of facts on puppetserver.
Second option would be using Deferred function. Evaluation of such function is delayed into later phase of catalog application (won't be evaluated on compile server).
Puppet code looks like this:
$value = Deferred("mymodule::load", ["1m"])
and the actual implementation should be in a Puppet module in a Ruby function, e.g. lib/puppet/functions/load.rb:
Puppet::Functions.create_function(:'mymodule::load') do
dispatch :load do
param 'String', :load_type
return_type 'String'
end
def load(load_type)
case load_type
when '1m'
avgs[0]
when '5m'
avgs[1]
else
raise "#{load_type} not supported"
end
end
end
The advantage is the latter approach is that you can pass multiple arguments. Although returning more complex types than String or Numeric doesn't seem to currently supported.

Chef 12 Opsworks attributes get overriden by last recipe in run list

I am running a Chef 12 Opsworks stack and I created a custom cookbook to take some backups of a few folders on my webserver.
Maybe my approach to this is wrong, but I basically have two (or more) recipes for each website I want backed up and then I'm updating some attributes (site name, backup folder, etc) in each recipe.
So I start with the following in my default.rb file in the attributes folder:
default['backup']['site'] = "SITE1"
default['backup']['root'] = "/var/www/SITE1"
Then in each backup recipe I have the following at the top of the recipe followed by my back up code:
site1.rb
node.override['backup']['site'] = "SITE1"
node.override['backup']['root'] = "/var/www/SITE1"
site2.rb
node.override['backup']['site'] = "SITE2"
node.override['backup']['root'] = "/var/www/SITE2"
Now in my Setup step on the Opsworks Layer I add all the backup recipes, but the problem arises when I start an instance (or run the Setup step from the Deployments) because the attributes seem to be set to whatever the last recipe sets them in alphabetical order.
So SITE1 backup script for example will end up being built with the /var/www/SITE2 root folder in its config and thus not backing up the right site.
Is there are way to prevent this from happening? From what I gather (from my example and reading Chef docs) the attributes are all compiled together at the beginning and then the recipes are being run - which is why the last set of attributes gets set as the final version and then all recipes using those attributes will get those values.
The only way I can deploy them at the moment is by running each recipe independently thus using the correct attribute values, but the moment the instance gets rebooted or the Setup step is ran manually all backup scripts will go back to backing up just one site.
Is my approach to this wrong? Should I be creating separate named attributes for each recipe?
I ended up fixing this by slightly modifying my approach to using attributes upon reading more about the 'chef run' process.
I am still using attributes for the actual 'common' attributes between all recipes, but then in each recipe instead of overriding the recipe specific attributes I replaced them with local ruby variables which then get passed to my 'template' via the 'variables' property.
So my site1.rb recipe for example looks the following way:
site = "SITE1"
root = "/var/www/SITE1"
...
config = {
:site => site,
:root => root,
...
}
...
template "/path/to/config" do
source "config.erb"
variables(
:config => config
)
...
end
This way I get to keep the same variable names inside my config template yet have each backup recipe use its own custom variables without interfering with other recipes.

Is there a way to access a root level variable in a module?

I have a root level variable, whose value is set at run time via a tfvar file. The tfvar file used and the var value can vary. Now I want to use this variable inside a module, the terraform way to do this is to set it up as a module variable and pass the root var when creating the module.
Except, I have this module used in our infrastructure several hundred times. Is repeating this variable 100s of times the only way to do this? Can the module access root namespace to grab the variable value?
I'm half tempted to use an external data script in the module to fetch the value instead, except, I wont know which tfvars file will be in effect at runtime..
Unfortunately, the only Terraform supported way is to pass the value into the module as a variable. Terraform has made me a copy/paste expert.
The only other approach that comes to mind that Terraform supports is making creative use of the External Data Source.

Chef wrapper cookbooks only apply internal cookbook once

I have a cookbook "blah-deploy-nodejs-from-git" cookbook that installs a nodejs codebase from GIT and calls NPM install on the directory. It has the following attributes
git_repo
branch
destination
I have then written cookbooks that wrap that cookook for inidividual sites, that need to get installed. In this particar case "blah-pricing" and "blah-notifications" which have different overriding attributes:
me#me cat cookbooks/blah-svc-pricing/attributes/default.rb
node.override[:blah_deploy_nodejs_from_git][:destination] = "/var/blah/pricing"
node.override[:blah_deploy_nodejs_from_git][:branch] = "master"
node.override[:blah_deploy_nodejs_from_git][:git_repo] = "https://hqdevgit01.blah.lan/micro-services/blah-pricing.git"
me#me:~/chef-repo$ cat cookbooks/blah-svc-notifications/attributes/default.rb
node.override[:blah_deploy_nodejs_from_git][:destination] = "/var/blah/notifications"
node.override[:blah_deploy_nodejs_from_git][:branch] = "master"
node.override[:blah_deploy_nodejs_from_git][:git_repo] = "https://hqdevgit01.blah.lan/micro-services/blah-notifications.git"
And then the recipe is the same in both cases:
include_recipe 'blah-deploy-nodejs-from-git'
Unfortunately it is applying the inner recipe only once even though my node has both cookbooks applied to it. My understanding was that wrapper cookbooks are used to customize a cookbook and make it unique.
Can encapsulate the inner cookbook to two different cookbooks, with different attributes, and have the wrapper cookbooks both apply that inner recipe? OR Am I going to have to completely replicate the code that is in the inner cookbook?
This is due to a basic misunderstanding of how chef works. Recipes are not meant to be a procedure for how to do something, they are meant to be a declaration of what that something should look like. As such, you need to think of them as describing the end state, not the process for getting there.
Thus, chef will never run a recipe twice. And attributes really should not be changed mid run (unless they are updated to indicate something that happens mid run. Luckily, there are other chef capabilities that can solve your problem. You need either a definition or an LWRP (light weight resource provider)
Definitions are just groups of resources that are often repeated. So you can create a definition and then later call it multiple times in the same recipe with different attributes. Much like what you currently are doing with your recipe.
While definitions are sometimes appropriate, LWRPs are generally more powerful, and have become the prefered approach for most repetitive (library like) task in Chef. With LWRPs you will define a new chef primitive (much like file, service, etc), and then write the code for accomplishing the goal of that primitive. You can then use these resources anywhere in your recipes. In your case, you'd have an npm_deployer resource that took attributes for repo, branch, and destination. It would then do the work that is currently in your deployer recipe. Your "wrapper" recipes would then stop calling include_recipe and instead just declare a npm_deploy resource and pass in the attributes needed.

Puppet: manage the recursiveness of owner/group/mode attributes separately

I need to write a Puppet script to manage the directory /foo/bar such that:
the file mode on /foo/bar is 777, but the permissions of everything within the directory are not managed by Puppet.
the owner/group on /foo/bar and everything within it is baz.
That is, the first requirement is non-recursive, but the second attribute is recursive.
Puppet provides a single recursive attribute, which affects the behavior of owner, group, and mode simultaneously. This means that I cannot specify the desired behavior using a single resource declaration.
I tried using two resource declarations, but then I get the error
Error: Duplicate declaration: File[/foo/bar] is already declared in file /my/puppet/file.pp at line XX; cannot redeclare
Yes, this will not work. Mind that Puppet is not a scripting engine, but a tool to model your desired state.
You will therefor have to decide how you want to manage your directory: As a single file system entry (recurse => false) or a whole tree (recurse => true). In the latter case, Puppet will always manage all properties for which you are passing values.
In your situation, you will likely have to fall back to the workaround of managing the permissions of the directory itself through a different resource, likely an exec resource that calls chmod, independently of the file resource. The latter must not pass a value for mode in this constellation, otherwise the two resources will always work against one another.
It's no ideal, but Puppet is not well equipped to deal with your specific requirements.

Resources