Chef:Powerful Infrastructure Automation
上QQ阅读APP看书,第一时间看更新

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.