Chef: how to store only attributes for explicitly included recipes - ruby

I'm fixing a cookbook for redis, with 2 distinct recipes + a default one.
On the default recipe I add the a PPA repository and do the apt-get update stuff.
What I would like to do is be able to persist on the node, only the attributes for the corresponding enabled recipes:
If the node includes "server" recipe, then store the defaults from attributes/server.rb
If the node includes "client" recipe, then store the defaults from attributes/client.rb
The documentation isn't clear enough to whether the above will happen or it will store both, despite the recipes inclusion.
What i've seen people suggesting is to move the node.default[...] definitions that are specific to a recipe only and does not fit on "default" recipe to the recipe itself. I believe that this is non logical.
What is the "best-practice" to achieve this separation?

The key quote from Chef's documentation is: "When a cookbook is run against a node, the attributes contained in all attribute files are evaluated in the context of the node object." (emphasis mine). You can only achieve what you're talking about by moving to using node.(scope) in the recipes.

For thing you need in the client recipe
node[:redis][:client][:foo] = "bar"
For things you need in the server recipe
node[:redis][:server][:foo] = "bar"
Out of curiosity, which redis recipe are you using?

Related

Linking against an external object file (.o) with autoconf

For work purposes I need to link against an object file generated by another program and found in its folder, the case is that I did not find information about this kind of linkage. I think that if I hardcode the paths and put the name-of-obj.o in front of the package_LDADD variable should work, but the case is that I don't want to do it that way.
If the object is not found I want the configure to fail and tell the user that the name-of-obj.o is missing.
I tried by using AC_LIBOBJ([name-of-obj.o]) but this will try to find in the root directory a name-of-obj.c and compile it.
Any tip or solution around this issue?
Thank you!
I need to link against an object file generated by another program and
found in its folder
What you describe is a very unusual requirement, not among those that the Autotools are designed to handle cleanly or easily. In particular, Autoconf has no mechanisms specifically applicable to searching for bare object files, as opposed to libraries, and Automake has no particular automation around including such objects when it links. Nevertheless, these tools do have enough general purpose functionality to do what you want; it just won't be as tidy as you might like.
I think that if I hardcode the paths and put the
name-of-obj.o in front of the package_LDADD variable should work, but
the case is that I don't want to do it that way.
I take it that it is the "hardcode the paths" part that you want to avoid. Adding an item to an appropriate LDADD variable is not negotiable; it is the right way to get your object included in the link.
If the object is not found I want the configure to fail and tell the
user that the name-of-obj.o is missing.
Well, then, the key thing appears to be to get configure to perform a search for your object file. Autoconf does not have a built-in mechanism to perform such a search, but it's just a macro-based shell-script generator, so you can write such a search in shell script + Autoconf, maybe something like this:
AC_MSG_CHECKING([for name-of-obj.o])
OTHER_LOCATION=
for my_dir in
/some/location/other_program/src
/another/location/other_program.12345/src
$srcdir/../relative/location/other_program/src; do
AS_IF([test -r "${my_dir}/name-of-obj.o"], [
# optionally, perform any desired test to check that the object is usable
# ... perhaps one using AC_LINK_IFELSE ...
# if it passes, then
OTHER_LOCATION=${my_dir}
break
])
done
# Check whether the object was in fact discovered, and act appropriately
AS_IF([test "x${OTHER_LOCATION}" = x], [
# Not found
AC_MSG_RESULT([not found])
AC_MSG_ERROR([Cannot configure without name-of-obj.o])
], [
AC_MSG_RESULT([${OTHER_LOCATION}/name-of-obj.o])
AC_SUBST([OTHER_LOCATION])
])
That's functional, but of course you could embellish, such as by providing for the package builder to specify a location to use via a command-line argument (AC_ARG_WITH(...)). And if you want to do this for multiple objects, then you would probably want to wrap up at least some of that into a custom macro.
The Automake side is much less involved. To get the object linked, you just need to add it to the appropriate LDADD variable, using the output variable created by the above, such as:
foo_LDADD = $(OTHER_LOCATION)/name-of-obj.o
Note that if you're building just one program target then you can use the general LDADD instead of foo_LDADD, but note that by default these are alternatives not complements.
With that said, this is a bad idea overall. If you want to link something that is not part of your project, then you should get it from an installed library. That can be a local, custom-built library, of course, so long as it is a library, not a bare object file, and it is installed. It can be a static library if you don't want to rely on or distribute a separate shared library.
On the other hand, if your project is part of a larger build, then the best approach is probably to integrate it into that build, maybe as a subproject. It would still be best to link a library instead of a bare object file, but in a subproject context it might make sense to use a lib that was not installed to the build system. In conjunction with a command-line argument that tells it where to find the wanted lib, this could make the needed Autoconf code much cleaner and clearer.

Cookbook dependencies based on chef version

I have a wrapper cookbook that uses resources from other cookbooks, but they have different constraints. I tried defining my metadata.rb as follow :
name 'yp_linko'
maintainer 'The Authors'
maintainer_email 'devops#yp.ca'
license 'all_rights'
description 'Installs/Configures yp_linko'
long_description 'Installs/Configures yp_linko'
version '1.3.4'
depends 'apt'
if chef_version '<= 12' then
depends 'ypg_tomcat'
else
depends 'yp_tomcat'
end
This didn't work as chef grabbed both cookbooks during convergence. I tried a couple different syntax (only_if, unless, etc...) and none worked. Anyone has ideas to work around this issue?
TL;DR
You're using a constraint from the Chef DSL in a way that it's not meant to be used. You need to branch based on a Boolean expression, not a metadata constraint. I provide a couple of ways to do that using String#to_f as examples (not recommended if you care about patch levels in semantic versioning), as well as the more accurate but often-overlooked Gem::Version.
Don't Use a Constraint for Branching
if chef_version '<= 12'
You're trying to use a constraint from the DSL. This constraint has a specific purpose: to declare the chef-client versions supported by the cookbook, not to provide logical branching. Without looking at the underlying Chef code for the DSL, I'd say it's unlikely that the expression is feeding your if-then expression the way you expect. Setting aside for the moment whether this is the right pattern to follow at all, you can try grabbing the current version of your Chef tools in a couple of different ways:
Branching on the chef-client version, likely cast as a Float (it's normally a String). For instance:
if Chef::VERSION.to_f <= 12
Get the node value from ohai with something like:
if node['chef_packages']['chef']['version'].to_f <= 12
Parse the value directly from the client, e.g.:
depends %x(chef-client --version).split[1].to_f <= 12 ? 'ypg_tomcat' : 'yp_tomcat'
However, in all cases, you're going to have to deal with the fact that you're being passed a String containing semantic versioning, not a Float or Integer. So, you'll have to figure out how you really want to parse the information, which is potentially error-prone (see below for a trick using Gem::Version, though). In any case, once you've parsed it the way you want, you can match it using a comparison operator to get the branching behavior you want.
Better Options
Rather than trying to make the metadata constraint hold business logic, you should probably move the data out to an attribute. Consider an attribute such as node['yp_linko']['tomcat_cookbook'], which you can set based on some other detectable node value other than semantic versioning.
Another approach would be to declare both cookbooks a dependency, and then include the one you want inside a recipe in your yp_linko cookbook. For example, assuming you haven't declared incompatible chef-client versions in the Tomcat cookbooks:
# metadata.rb
depends 'yp_tomcat'
depends 'ypg_tomcat'
# default.rb
if Chef::VERSION <= Gem::Version.new(12)
include ypg_tomcat::default
else
include yp_tomcat::default
end
And finally, you should consider whether it really makes sense to be running different versions of your Chef clients within the infrastructure in the first place. There may be a business need to do that, but it's the real problem you're actually trying to solve for. Branching in your cookbooks is an X/Y solution to an infrastructure issue. It seems likely that you'd have other cookbooks with similar issues, so it's at least worth considering whether it makes more sense to get all your clients on the same version rather than solving the problem at the cookbook level.

Make Dynamic Variable Available to Multiple Chef Recipes

We dynamically compute the name of a directory at run-time from some attributes:
var1 = (node['foo']['bar']).to_s
var2 = (node['foo']['baz']).to_s
app_dir = "/var/#{var1}/#{var2}
Copying this code block to all of the recipes that needs it works. When we have tried to clean this up, it bombs with "No resource, or local variable named 'app_dir'.
We have tried the following:
1) Move the block of code into attributes/default.rb
2) Move the block of code into recipes/default.rb
3) Same as 2 above, but adding require_relative 'default' in the recipes that require the variable
If you're requiring from a separate file (e.g. require_relative) you need to make the variable something that gets exported. You can try making it a constant instead e.g. AppDir =, or a method. Local variables are not loaded with require.
There isn't really a good way to do this in Chef. Recipe and attributes files run in very different contexts so your best bet is either copy-pasta the boiler plate (if it really is as small as your example, do that) or otherwise maybe a global helper method but those are hard to write safely. Ping me on Slack and we can talk about the right kind of helper method for your exact situation (covering all of them isn't really practical for an SO answer).
If you need to share some code between a bunch of different recipes, you can try using a library loaded from a common cookbook dependency.

GNU make empty recipe vs. no recipe

Looking at the GNU make manual, what exactly is the difference between an empty recipe and no recipe (for example see one instance in Rules without Recipes or Prerequisites)? More importantly, when should one use/avoid each of these two recipes?
In my current understanding, one should always use
target: ;
because sometimes an implicit rule could defeat one's purpose of no rule.
A given target can have only one recipe. If you declare an empty recipe then you've now given that target that recipe and it won't be searched for via implicit rules. Also if you try to give that same target another recipe, then make will complain.
"No recipe" just means you're adding more prerequisites to an existing target or, if you don't list prerequisites, you're just informing make that this is a target you're interested in. But you're not overriding any recipe lookup that make will do elsewhere.
It's definitely not true that one should always use one or the other: the one you use depends on what you're trying to achieve.
I don't know what you mean when you say defeat one's purpose of no rule so I can't respond to that... what are you trying to achieve when you say "no rule"?

Multiple instances of the same target for make

I am working on a Makefile written by someone else in my lab and I see the following:
include /path/to/Makefile.inc
TARGET_A: pre_req_1 pre_req_2
cmd_1;
cmd_2;
...
When I look at /path/to/Makefile.inc I see that it also includes a target TARGET_A
with other pre-requisites and recipes.
Is this a normal practice? (and would it work?) Would make treat the two rules separately? Can we safely make any assumptions about which one is considered first?
It is a dangerous practice since it is confusing to know which one is applied.
The "include" will act has if the included file content was in the Makefile, and the targets will be overrided while reading the Makefile. So, the last target will be honored, and the first one (in the included file) will be ignored.

Resources