Puppet contain not working to order class inclusion - puppet

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.

Related

Puppet read file content after a class

I am trying to read a file content after executing a class GetContentsAPI, basically this class GetContentsAPI will write into the file /etc/api/token.
class Main{
require GetContentsAPI
file("/etc/api/token")
}
When I did the above steps, its says Evaluation Error: Error while evaluating a Function Call, Could not find any files from /etc/api. Not sure how to make sure the file is already created before trying to read.
Thanks James
The file() function reads the contents of a file during catalog building. You don't present any details of class GetContentsApi, but all of the standard puppet facilities that write to files (especially, but not limited to, File resources) write during catalog application. Unless you've cooked up something highly customized, the file() function will always read before GetContentsApi writes.
Moreover, in a master / agent setup (which is the only kind supported in current Puppet), catalog building happens on the master, whereas catalog application happens on the target node, which is usually a different machine, so you're unlikely even to be able to read what was written during a previous catalog-building run.
Also, file() just returns the file contents as a string, so it's not very useful to call it without using the return value somehow.
It's not at all clear what you're trying to achieve, but from what I can see, you are not going in a fruitful direction. Perhaps you should take a step back and ask a different question about that.

Puppet iteration from external file

I'm new to configuration management, just FYI.
I'm trying to puppetize elasticsearch, and want to have a master list of elasticsearch nodes in a file (which can be used for multiple things, not just this purpose).
I would like to add elasticsearch.yml via an ERB template and expand the list of FDQN's into the discovery.zen.ping.unicast.hosts: [] param.
For example I have an external file called es_hosts in module/files that contains:
host1.domain.com
host2.domain.com
host3.domain.com
host4.domain.com
Then when puppet builds the ERB template have this in the param:
discovery.zen.ping.unicast.hosts: ["host1.domain.com", "host2.domain.com", "host3.domain.com", "host4.domain.com"]
I've tried a few things, but I can't get my head wrapped around it.
I would be using this list for other things like building firewall rules, etc, so I'd like to have one master list for reference that can be updated by my team.
Thanks for any help!
Rather than have a list in a file, it would be better to have it in Hiera, since defining lists and other external data is specifically what Hiera is for.
(If you have not used Hiera yet, you definitely should read up on it.)
So in Hiera you would have:
---
es_hosts:
- host1.domain.com
- host2.domain.com
- host3.domain.com
- host4.domain.com
In your manifest, you would read that in from Hiera using the hiera function:
$es_hosts = hiera('es_hosts')
(Note that instead of the hiera function, we often use Puppet's Automatic Parameter Lookup feature instead to read data into our manifests from Hiera, but your requirement here - a list of ES hosts to be used in multiple contexts - suggests you will want this list not to be bound to a specific class input. If this does not make sense to you right now, you will need to learn about Parameterised Classes and Automatic Parameter Lookup, but it's otherwise not relevant to this answer.)
Finally, in your ERB template you would have:
discovery.zen.ping.unicast.hosts: ["<%= #es_hosts.join('", "') %>"]
Pay attention to the fact that the $es_hosts variable from your manifest is accessed via a Ruby instance variable #es_hosts in your ERB template.
Finally, note that there is an Elasticsearch Puppet module available on the Puppet Forget here. You may find that using that module is better than writing your own.

Puppet : How to load file from agent - Part 3

Using the functions in my earlier queries (see reference below), I am able to pull the file from the agent and perform the necessary tasks. However, this is affecting all the users on the system as it throws an exception stating that the file is not found. Is there anyway I can add some logic like unless file_exists .... to this ruby function ?
My hierarchy is shown below. I am not following why it affects other users who are not even in "mymodules".
Root
modules
mymodules
lib
facter
ruby_function1.rb
ruby_function2.rb
modules_by_userxx1
modules_by_userxx2
modules_by_userxx3
....
Reference :
Puppet : How to load file from agent
Puppet : How to load file from agent - Part 2
As requested by Dominic, adding reference code :
# module_name/lib/facter/master_hash.rb
require 'json'
Facter.add(:master_hash) do
setcode do
# return content of foo as a string
f = File.read('/path/to/some_file.json')
master_hash = JSON.parse(f)
master_hash
end
end
I'll assume you're talking about the custom fact from the previous answers rather than a Ruby function, in which case, add a File.exist? conditional:
# module_name/lib/facter/master_hash.rb
require 'json'
Facter.add(:master_hash) do
setcode do
if File.exist?('/path/to/some_file.json')
# return content of foo as a string
f = File.read('/path/to/some_file.json')
master_hash = JSON.parse(f)
master_hash
end
end
end
Please include the full error message (with the filename/line number it provides) and the source code when asking questions.
Custom facts are shipped within modules, but are all synced to agents as they're used to discover data.
Facts are synced and run on agents before the node is classified because they can be used to make classification and catalog decisions, e.g. based on hostname or OS. Because facts run before classification, it isn't yet possible to know which classes (and perhaps modules) are going to be applied, so should be safe to run on every node in the environment.

Puppet : How to load file from agent

I have written a manifest that performs a number of tasks. The very first task is that it loads the contents of a file into a variable. The file will exist on the target node ( or managed node or the one running the Puppet agent).
However, when I triggered the manifest via a puppet run, I realized that it was expecting to find the file on the master, not the agent!
$some_var = file("path_to_file")
How do I fix this so that it loads the file from the agent?
Indeed functions execute only on the master. Therefore, you need either an external or custom fact for this to execute on the node. Here is a custom fact ready to go for this purpose of returning the contents of a file foo:
# module_name/lib/facter/foo_content.rb
Facter.add(:foo_content) do
setcode do
# return content of foo as a string
File.read('/path/to/foo')
end
end
You can then use this thusly:
# facter 3
$some_var = $facts['foo_content']
# facter 2
$some_var = $::foo_content
Note this solution assumes foo is not some extremely enormous file.
https://docs.puppet.com/facter/3.6/custom_facts.html

Chef: use attributes created dynamically

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.

Resources