Definitions
Sometimes, you find that you are creating something repeatedly and, similar to a configuration template, you need a template to generate objects of a given type. Some examples of this might be Apache virtual hosts, a specific type of application, or anything else that is repeated a lot. This is where definitions come in, and they are stored in the definitions
directory inside of a cookbook.
Definitions are loaded and available as named resources just as other resources such as packages, files, and so on are; the only difference is that there is no provider. You can think of them as resources and providers all in one. Subsequently, they are much more rigid and limited in scope than a normal resource would be. Here is an example definition to install Python libraries using pip
and a requirements.txt
file:
define :pip_requirements , :action => :run do name = params[:name] requirements_file = params[:requirements_file] pip = params[:pip] user = params[:user] group = params[:group] if params[:action] == :run script "pip_install_#{name}" do interpreter "bash" user "#{user}" group "#{group}" code <<-EOH #{pip} install -r #{requirements_file} EOH only_if { File.exists?("#{requirements_file}") and File.exists?("#{pip}") } end end end
Here, we are declaring a new type of definition, a pip_requirements
object. This looks and behaves similarly to a resource, except that it is much simpler (and less flexible) than a resource. It has some attributes, which are loaded via the special params
argument, and contains a little bit of logic wrapped around a script
resource. Let's take a look at how it would be used and then see how it works:
pip_requirements "my_requirements" do pip "#{virtualenv}/bin/pip" user node[:app][:user] group node[:app][:group] requirements_file "#{node[:app][:src_dir]}/requirements.txt" end
Here you see what looks like a resource, but is in fact a definition. As mentioned earlier, these look very similar because they behave alike. However, you must have likely noticed that the definition of pip_requirements
itself did not have any sort of abstraction; there is no pluggable provider, no validation, it doesn't subclass the Resource class, among other differences. Definitions provide you with a mechanism to declare reusable chunks of code that your recipes would otherwise duplicate so that your recipe can again describe the what, not the how.
The previous example tells us that we have a pip_requirements
object and that we want to pass some parameters to it, namely, the path to pip
, the user and group to run pip
as, and the requirements.txt
file to load. These are brought into the definition through the params
argument and can be accessed as any other variable data. In this case, the definition says to run bash
as the specified user and group and that the script should run the equivalent of the following:
pip install -r /path/to/requirements.txt
This will happen only if pip
and the /path/to/requirements.txt
file exist (as indicated by the only_if
guard). By creating such a definition, it can be reused any time you need to install Python modules from a specific requirements.txt
file on your host.