How does Chef include files generated on runtime as a template source - ruby

Using Chef recipe, I am first generating a .erb file dynamically based on inputs from a CSV file and then I want to use that .erb file as a template source. But unfortunately the changes made (in .erb file) are not considered while the recipe is converging the resources. I also tried to use lazy evaluation but not able to figure out how to use it for the template source.

Quoting the template documentation:
source Ruby Types: String, Array
The location of a template file. By default, the chef-client looks for
a template file in the /templates directory of a cookbook. When the
local property is set to true, use to specify the path to a template
on the local node. This property may also be used to distribute
specific files to specific platforms. See “File Specificity” below for
more information. Default value: the name of the resource block. See
“Syntax” section above for more information.)
And
local
Ruby Types: TrueClass, FalseClass
Load a template from a local path. By default, the chef-client loads
templates from a cookbook’s /templates directory. When this property
is set to true, use the source property to specify the path to a
template on the local node. Default value: false.
so what you can do is:
# generate the local .erb file let's say source.erb
template "/path/to/file" do
source "/path/to/source.erb"
local true
end
Your question sounds like and XY problem, reading a csv file to make a template sounds counter-productive and could probably be done with attributes and taking advantage of the variable attribute of template resource.

Assuming you know how to capture the values from the CSV file as a local variable in the recipe.
Examples:
csv_hostname
csv_fqdn
Here is what you do to create a template with lazy loading attributes. The following example creates a config file.
example.erb file
# Dynamically generated by awesome Chef so don't alter by hand.
HOSTNAME=<% #host_name %>
FQDN=<% #fqdn %>
recipe.rb file
template 'path\to\example.config' do
source 'example.erb'
variables(
lazy {
:host_name => csv_hostname,
:fqdn => csv_fqdn
})
end
If you need it to run at compile time, add the action to the block.
template 'xxx' do
# blah blah
end.run_action(:create)

Related

How to set dynamic variable once it is identified to all recipes in a cookbook

I need a help to set a global variable in chef recipes.
I have below series of recipes:
Discovers the tomcat from path variable/attibutes/default.rb:
default['tomcat_cookbook']['tomcathome']="['/home/tomcat','/home/ApacheTomcat']"
This recipe will identify the tomcat installation as either one of the directory will be available on server out of this two directories.
Lets say, if it sets the tomcathome to directory "/home/tomcat", I have some more subsequent recipes like start/stop/restart tomcat.
Currently for every recipe I am running discovery logic inside stop/start recipes while knowing that on a particular server, tomcathome is set to "/home/tomcat" .
Is there any way I can remove duplicate code for tomcat home discovery and make use of the identified tomcathome variable for remaining recipes.
Please suggest.
I think this would be a good use of libraries. I'll assume the cookbook name is tomcat_cookbook. In the libraries folder in a cookbook, create a file called path.rb.
Add the following code into the path.rb file. I prefer to namespace my libraries to organize my methods using CookbookName::ModuleName format.
libraries/path.rb:
module TomcatCookbook
module Path
def install_path
node['tomcat_cookbook']['tomcathome'].each do |path|
return path if ::Dir.exist?(path)
end
end
end
end
Within any recipe, you can include this module and use the methods in it:
# Use this include for use in the recipe
Chef::Recipe.send(:include, TomcatCookbook::Path)
# Use this include for using methods in the directory resource itself
Chef::Resource::Directory.send(:include, TomcatCookbook::Path)
Chef::Log.info("Install Path: #{install_path}")
directory "tomcat_install_path" do
path install_path
action :create
end
In certain situations, I have needed to create a common cookbook which includes only libraries which I can use across multiple cookbooks.

Reuse template across different recipes

I have a Chef cookbook with many recipes that have the same code, beside other particular things.
template 'stack_file' do
local true
source File.join(base_dir, 'stack_templates/admin.yml.erb')
path File.join(base_dir, 'stacks/admin.yml')
variables(context)
end
template 'settings_file' do
sensitive true
local true
source File.join(base_dir, 'config_templates/settings_admin.yml.erb')
path File.join(base_dir, 'configs/settings_admin.yml')
variables(context)
end
Is it possible to somehow put this code in a method that I would call with my source_file, destination_file and variables?
I guess you can write a module and include it in your recipes as you do in plain Ruby.
module StackFile
...the code you want to share...
end
Then you can use:
inlclude StackFile
or
Chef::Recipe.send(:include, StackFile)
or when using *_if conditions
Chef::Resource.send(:include, StackFile)
Do this:
create new cookbook.
don't create recipes in it, but instead create a resource
you can define any parameters (inputs like source file, dest file etc like you mentioned)
add your templates to the new cookbook.
in your other cookbooks, create dependency to the previously created cookbook. This will enable you to call the resource you created there (remember that when you call resource from another cookbook and it is creating templates, it will try to take the template file from the current cookbook and not the one where the resource is defined. That is why you need to specify cookbook name when creating template (in the shared cookbook's resource) - see cookbook attribute of https://docs.chef.io/resource_template.html)
Repeat for any number of cookbooks.

Check if a file exists with puppet template

I try to check if a file exists on client who run puppet agent.
On my puppet master, I have a template.erb like this :
<% if File.exists?('/usr/bin/lwp-request') %>SCRIPTWHITELIST="/usr/bin/lwp-request"<% end %>
This little code in my template is needed to my rkhunter module.
The result is always false, however the file exists.
If I add the file on the puppet master, the result is true. So the ruby code seems to be executed on the master.
How can I check on my template if a file exists on client ?
Tested on puppet 2.7.5 and 2.8.1.
Thanks
The only information you have about the node when compiling manifests and templates are Facts that are sent by the node when requesting a catalog.
If you need additional information from the node, then you need to add a Custom Fact that retrieves the information you need (like whether or not a file exists). You can then use the custom fact inside of templates.
Within a Puppet module create a custom fact lib/facter/lwp.rb:
Facter.add(:lwp_request_exists) do
setcode do
File.exists?('/usr/bin/lwp-request')
end
end
then within erb template use something like:
<% if $::lwp_request_exists -%>
some code...
<% end -%>

Chef - Read a file from git repo at runtime and use parse value in recipe

I would like to read a file from a checkout git repository to parse a config file and use this data to perform few resources commands.
git "/var/repository" do
action :sync
end
config = JSON.parse(File.read("/var/repository/config.json" ))
config.each do |job, flags|
#do some resources stuff here
end
This will not work because the file doesn't exist at compile time:
================================================================================
Recipe Compile Error in /var/chef/cache/cookbooks/...
================================================================================
Errno::ENOENT
No such file or directory - /var/repository/config.json
I where trying to load the file in ruby_block and perform the Chef resource actions there, but this didn't worked. Also setting the parsed config to a variable and use it outside of the ruby_block didn't work.
ruby_block "load config" do
block do
config = JSON.parse(File.read("/var/repository/config.json"))
#node["config"] = config doesn't work - node["config"] will not be set
config.each do |job, flags|
#do some stuff - will not work because Chef context is missing
end
end
end
Any idea how I could read the file at runtime and used the parsed values in my recipe?
You may also find it helpful to use lazy evaluation in scenarios like this.
In some cases, the value for an attribute cannot be known until the execution phase of a chef-client run. In this situation, using lazy evaluation of attribute values can be helpful. Instead of an attribute being assigned a value, it may instead be assigned a code block.

Why would I get "Errno::ENOENT: No such file or directory" when viewing a Sinatra form?

I am trying to follow this tutorial:
http://net.tutsplus.com/tutorials/ruby/singing-with-sinatra/
Got stuck in "We’ll also make use of a “view file”, which allows us to split the markup for a view into a separate file. "
I have my basics.rb file running fine.
And My files are stored as follows:
Desktop/RubyForm/basics.rb
Desktop/RubyForm/view/form.erb
However, now when i go to http://localhost:9393/form , I am greeted with:
Errno::EIO at /form
Input/output error - <STDERR> file: lint.rb location: write line: 398
sinatra.error
Errno::ENOENT: No such file or directory -
/Users/HelenasMac/Desktop/views/form.erb
UPDATE! : Got the form to work right after running ruby basics.rb and going to http://localhost:4567/form .
However, after I run "shotgun basics.rb" , I have to go to
http://localhost:9393/form, and that's when the form doesn't show up.
What am I doing wrong? Disclaimer: mega beginner to ruby and using the terminal.
Thanks in advance!
If you cannot get shotgun to work then the new recommended way to reload Sinatra seems to be rerun.
To use it:
> gem install rerun
> cd /Users/HelenasMac/Desktop/RubyForm
> rerun ruby basics.rb
Explicity Set a Views Directory
Unless you're using inline template for your views with enable :inline_templates, you may need to explicitly define a template directory if the default values aren't working for you. The docs describe how to set your views directory as follows:
:views - view template directory
A string specifying the directory where view templates are located. By default, this is assumed to be a directory named “views” within the application’s root directory (see the :root setting). The best way to specify an alternative directory name within the root of the application is to use a deferred value that references the :root setting:
set :views, Proc.new { File.join(root, "templates") }
You may also need to explicitly set :root, and make sure that both :root and :views make sense from your current working directory.

Resources