Add a custom function in puppet - ruby

I'm trying to port a Ruby script in puppet. As far as I know, the only way to achieve that is by creating a custom function in a module. Feel free to tell me if there's another way. I tried my first test as shown on https://docs.puppetlabs.com/guides/custom_functions.html , I declared a new module in:
/etc/puppet/modules/custom_module
and edited a new function file called /etc/puppet/modules/custom_module/lib/puppet/parser/functions/newfunction with this code:
module Puppet::Parser::Functions
newfunction(:write_line_to_file) do |args|
filename = args[0]
str = args[1]
File.open(filename, 'a') {|fd| fd.puts str }
end
end
Then I used in this manifest /etc/puppet/environments/desarrollo/manifests/des.pp with this content:
node "develserver" {
write_line_to_file('/tmp/some_file', "Hello world!")
}
And finally when I run a puppet agent -tod it shows me the following error:
Error: Could not retrieve catalog from remote server: Error 400 on SERVER: Unknown function write_line_to_file at /etc/puppet/environments/desarrollo/manifests/des.pp:5 on node develserver
What am I doing wrong?

The immediate problem is likely the missing .rb extension on your function file name.
Keep in mind that the Ruby code is run on the master during catalog compilation. If you want Puppet to take action on the agent side, you cannot use custom functions. You will have to write a custom Resource Type (and often a Provider) to do that.

Related

Chef - run install block based on variable condition

Background: our systems are setup in a way that I will only be able to see the local chef log and will have no access to the Chef server console or any other sysadmin privileges. Hence I have a need to log locally if I want to see if or why something failed.
I can hear you asking " If you don't trust the pkg or Chef to install it correctly, then..." My answer is that while you are correct, I still want to be covered by the occasional anomaly.
My goal is to install a pkg, check to see that it installed correctly than go on to the next pkg.
On to the question:
I would like to set a variable that checks for the existence of a directory that was created by the first package using the following code:
mycond = ::File.directory?('/opt/MyPkg/conf')
Chef::Log.fatal("MyPkg package not installed ? conf dir is missing") unless mycond
the next stage in the recipee is to run the next install block checking to see if the variable has been set.
yum_package 'OtherPkg' do
action :install
only_if { mycond }
end
My question is since the only_if is failing, I was wondering if there was something wrong with the way I am setting the mycond variable ? perhapes {} braces are needed somewhere in the code ?
Total Chef newbie so please be specific with your answer.
Thanks !
Full code below:
yum_package 'MyPkg' do
flush_cache [ :before ]
action :install
end
mycond = ::File.directory?('/opt/MyPkg/conf')
Chef::Log.fatal("MyPkg package not installed ? conf dir is missing") unless mycond
yum_package 'OtherPkg' do
action :install
only_if { mycond }
end
The problem is Chef's two-pass model. See https://coderanger.net/two-pass/ for the full explanation for for this you just need to move the condition check in to the only_if block itself since that is delayed until converge time: only_if { ::File.directory?('/opt/MyPkg/conf') }.
Using the fatal log level is also probably not a good idea as this isn't actually a fatal error as written.
Chef has an order of precidance that controls the flow of execution.
Code inside resource blocks (e.g. 'yum_package') will execute AFTER any loose code in your recipe.
The following lines are being executed FIRST, before your 'yum_package' blocks:
mycond = ::File.directory?('/opt/MyPkg/conf')
Chef::Log.fatal("MyPkg package not installed ? conf dir is missing") unless mycond
I believe you can nest resource blocks. You cold be able to combind all this code in a 'ruby_block' and it should execute in order as you'd expect.

Cucumber: Unable to see methods of a module included in the World?

I'm working with cucumber/ruby and I wanted to create a new module with some methods to use them in my step definitions.
I was reading how to do this here, https://github.com/cucumber/cucumber/wiki/A-Whole-New-World. But when I've tried the following I get an error:
create the new module under /root_location/lib/new_module.rb
create the module as:
.
module Newmodule
def here
puts "here"
end
end
World(Newmodule)
However, when I then try to use the 'here' method from my steps definition, I just get:
undefined local variable or method `here' for # (NameError)
Any idea what I am doing wrong?
The module needs to be located in features, otherwise it won't be added into World. Cucumber does not look outside of features for anything unless you specifically tell it to.
Put this code either in features/support or features/step_definitions

Custom package provider to work around namespace conflicts

I have a set of RHEL / CentOS systems where I am using the NetBSD pkgsrc system
to install various packages into a certain tree (e.g. /opt/local). I want to
manage this with Puppet. I started out simply using the pkgin provider, but
I quickly ran into namespace conflicts. For example, both the yum and pkgin
providers were adding Package[openssl], and they conflicted.
To fix this, I have been trying to use a static prefix on my pkgsrc packages,
then strip it off when it is actually passed to the pkgin command for handling.
First, I wrote a define to add the prefix and set the provider.
define mypkgin::package($ensure='latest') {
package { "mypkgin_${title}":
ensure => $ensure,
provider => 'mypkgin',
}
}
This will show up in the namespace as Mypkgin::Package[openssl], which does
not itself conflict with anything. And its goal is to add a resource named
Package[mypkgin_openssl], to not conflict with Package[openssl] from yum.
Then, I copied the pkgin provider code and modified it to provide a thing
named mypkgin. I have a small method in here to strip the prefix from the
name, and that is used in a few places so that the pkgin command receives
the pkgsrc package name instead of the prefixed version.
require "puppet/provider/package"
Puppet::Type.type(:package).provide :mypkgin, :parent => Puppet::Provider::Package do
desc "Package management using pkgin/pkgsrc, my local edition."
# Specify full path since it's not in Puppet's exec path
commands :pkgin => "/opt/local/bin/pkgin"
has_feature :installable, :uninstallable, :upgradeable, :versionable
# Strip prefix off of package name
def my_pkgname(package)
package.gsub(/^mypkgin_/, '')
end
def self.parse_pkgin_line(package)
# e.g.
# vim-7.2.446 = Vim editor (vi clone) without GUI
match, name, version, status = *package.match(/(\S+)-(\S+)(?: (=|>|<))?\s+.+$/)
if match
{
:name => name,
:status => status,
:ensure => version
}
end
end
def self.prefetch(packages)
super
# Without -f, no fresh pkg_summary files are downloaded
pkgin("-yf", :update)
end
def self.instances
pkgin(:list).split("\n").map do |package|
new(parse_pkgin_line(package))
end
end
def query
packages = parse_pkgsearch_line
if packages.empty?
if #resource[:ensure] == :absent
notice "declared as absent but unavailable #{#resource.file}:#{resource.line}"
return false
else
#resource.fail "No candidate to be installed"
end
end
packages.first.update( :ensure => :absent )
end
def parse_pkgsearch_line
packages = pkgin(:search, my_pkgname(resource[:name])).split("\n")
return [] if packages.length == 1
packages.slice!(-4, 4)
pkglist = packages.map{ |line| self.class.parse_pkgin_line(line) }
pkglist.select{ |package| my_pkgname(resource[:name]) == package[:name] }
end
def install
if String === #resource[:ensure]
pkgin("-y", :install, "#{my_pkgname(resource[:name])}-#{resource[:ensure]}")
else
pkgin("-y", :install, my_pkgname(resource[:name]))
end
end
def uninstall
pkgin("-y", :remove, my_pkgname(resource[:name]))
end
def latest
package = parse_pkgsearch_line.detect{ |package| package[:status] == '<' }
return properties[:ensure] if not package
return package[:ensure]
end
def update
pkgin("-y", :install, my_pkgname(resource[:name]))
end
end
Every time I run puppet agent --test, it tells me that it has added all of these
packages. So clearly I have some namespace bit wrong. It thinks on each run that
the package either needs to be installed, or upgraded, and it tries to do that.
I assume that in one or more places, I should add or remove calls to
my_pkgname(), but I can't seem to figure out where I've gone wrong.
Your provider determines which packages are already available on the system by means of prefetching. It looks like you have not replaced or overridden the bits that will result in that process ultimately relying on the instances() method.
Looking at that method, it appears that it will generate instances exactly as the pkgin provider would do, right down to the generated package names. That's a problem for you, because your scheme involves translating package names. It's not enough just to translate the names Puppet uses to the native ones -- you must also translate the native ones to the ones you want Puppet to see when you create instances on the provider side.
It looks like self.parse_pkgin_line() could be modified pretty easily to prepend the needed prefix to your package names. That seems like it would be the right thing for it to do, but I'm uncertain whether it would have any undesirable side effects. It is possible that additional changes will be required as well, such as in parse_pkgsearch_line(), but that's not immediately clear to me.

Using File::read in a provider's default.rb in Chef

I am trying to create an LWRP that will call the resource that is defined within itself. My cookbook's structure is as follows:
In the machine cookbook's provider, I have a code snippet as follows:
require 'chef/provisioning' # driver for creating machines
require '::File'
def get_environment_json
##environment_template = JSON.parse(File::read(new_resource.template_path + "environment.json"))
return ##environment_template
end
The code is only trying to read a json file and I am using File::read for it.
I keep getting an error as follows:
LoadError
cannot load such file -- ::File
Does anyone know how I can use File::read inside my LWRP's provider?
OK, so the prior two answers are both half right. You have two problems.
First, you can't require ::File as it's already part of Ruby. This is the cause of your error.
Second, if you call File.read you will grab Chef's File not ruby's. You need to do a ::File.read to use Ruby's File class.
require '::File'
Is incorrect and is causing the LoadError. Delete this line. You don't need it. File is part of the Ruby core and doesn't need to be required.
To further explain, the string argument to require represents the file name of the library you want to load. So, it should look like require "file", or require "rack/utils".
It happens becuase Chef already has a file resource. We have to use the Ruby File class in a recipe.We use ::File to use the Ruby File class to fix this issue. For example:
execute 'apt-get-update' do
command 'apt-get update'
ignore_failure true
only_if { apt_installed? }
not_if { ::File.exist?('/var/lib/apt/periodic/update-success-stamp') }
end
Source: https://docs.chef.io/ruby.html#ruby-class

How do I change the aws-ruby log location?

I've found the method set_log in the documentation, I just can't figure out the syntax to call it. Here's what I tried:
require 'ruby-aws'
Amazon::Util::Logging.set_log('my.log')
NoMethodError: undefined method `set_log' for Amazon::Util::Logging:Module
You can see that Amazon::Util::Logging is a module and set_log is a 'Public Instance method'. So you need
class NewClass
include Amazon::Util::Logging
def foo
set_log('file.txt')
log 'debug_message'
end
end
I ran into this problem when trying to deploy a Ruby-on-Rails site that uses 'aws-ruby' to heroku (I got the "Permission denied - ruby-aws.log" error).
To change the log file location from 'ruby-aws.log' to 'log/ruby-aws.log', I added the following to an initializer. Make sure this is called before you use any of the aws-ruby library. Notice the change on the "set_log..." line.
module Amazon
module Util
module Logging
def log( str )
set_log 'log/ruby-aws.log' if ##AmazonLogger.nil?
##AmazonLogger.debug str
end
end
end
end
A simpler way would be to add this line:
set_log("/dev/null")

Resources