Chef Recipe How To Check If File Exists - ruby

I just started using Chef and I'm trying to figure out how to first check if a file exists before doing anything.
I have the file part down for my current use case, where I'm removing a login file for the production server, ex:
file '/var/www/html/login.php' do
action :delete
end
However, I'd like the abilty to first check if the file exists, ex.
if (file_exists === true)
file '/var/www/html/login.php' do
action :delete
end
end

As mentioned in the comments, for a deletion action, the if statement is unnecessary, as mentioned, because if chef doesn't find the file to be deleted, it will assume it was already deleted.
Otherwise, you generally want to use guard properties in the resource (available for all resources), rather than wrapping a resource in an if-then.
file '/var/www/html/login.php' do
only_if { ::File.exist?('/var/www/html/login.php') }
action :touch
end
And you probably also want to familiarize yourself with the Ruby File class methods.

The basic idea of Chef is that you state the desired state of the system, and then Chef compares that to the actual state, and makes any changes needed to bring the system into the desired state. You do not need to have an if statement to check if the file exists before deleting it; Chef itself should check if the file exists if I'm not mistaken.

Related

Send variable from one Chef recipe to another in order to trigger a resource block

In one recipe I have a rubyblock that ultimately obtains the port of a service that I'd like to restart.
Individually the recipes work fine and I am now trying to tie the two together.
I cannot seem to pass the variable to the other recipe having tried to follow a custom resource example.
my default recipe that obtains the port is:
Chef::Log.info("Port: #{port}")
Chef::Resource::Notification.new("stop-solr_#{port}", :run, self)
I'm trying to trigger the resource block of the name 'stop-solr_'portNumber'' using the notification sender.
My other recipe looks like the following and has a start/stop service purpose
solrCore = "solr_#{port}"
#define the service - does nothing
service solrCore do
action :nothing
end
#do something that triggers
execute "start-solr_#{port}" do
Chef::Log.info('triggers start')
action :nothing
notifies :start, run_context.resource_collection.find(:service => "#{solrCore}")
end
execute "stop-solr_#{port}" do
# some stuff
# on success...
Chef::Log.info('triggers restart')
notifies :stop, run_context.resource_collection.find(:service => "#{solrCore}"), :immediately
notifies :run, "execute[start-solr_#{port}]"
end
My main problem (I think) is that the variable solrCore uses 'port' which I cannot seem to obtain.
Is anyone able to help with what I need ot do in order to get this working?
Thanks in advance.
Variables in Ruby are local by default. You would have to use a global variable to share state between files, either a real Ruby global variable ($foo) or using the global node.run_state hash we expose to all recipes.
That said: there is a reason that mutable global variables have been a CS cliché for decades. Code like this is very fragile and difficult to debug. I would consider turning both of those recipes into custom resources and calling them from the same recipe with the same input port.
In the first recipe, you can save information in the node.
Sample:
node.normal['A']['B']['C']="completed"
node.save
and In second Recipe you can retrieve information from the node.
status=node.normal['A']['B']['C']
and based on the retrieved value you can take action

How copy file in Ruby (custom Puppet type)?

I am creating a custom puppet type. I am quiet new to Ruby, so I have a little problem. I have two params, dest and file:
newparam(:dest) do
desc "The destination of the file"
isnamevar
validate do |value|
unless Puppet::Util.absolute_path?(value)
fail Puppet::Error, "File paths must be fully qualified, not '#{value}'"
end
end
end
newparam(:file) do
desc "The file to be copied."
FileUtils.cp(file, dest)
# As source I would like to use the value passed by :file.
# As destination I would like to use the value passed by :dest above
end
As source I would like to use the value passed by :file.
As destination I would like to use the value passed by :dest.
How can I achieve this?
I think you need to take a step back and think about what your type is supposed to model.
Are you trying to recreate the file type's source property? (Do you really need to?)
Your dest should likely be a property, and the copying will happen during its sync action. But this model will be rather clunky abstraction. You should create a new question, describe what your type needs to do and ask for advice on how to approach your greater problem.

How can I call a Chef resource from an HWRP?

Maybe this is really simple, and I'm just not understanding something. I want to invoke a Chef resource from within an HWRP that I wrote. In my scenario, I'd like to invoke the reboot resource. How should I go about doing so?
I have tried something like the following:
def reboot_system
wu_reboot = Chef::Resource::Reboot.new('wu_reboot', :reboot_now)
wu_reboot.run_action(:reboot_now)
end
A few things. I am not sure if I should be creating an instance of Chef::Resource::Reboot or Chef::Provider::Reboot. I also don't really understand the second argument listed above..this is supposed to be the "run_context", but I don't know what that is. Finally, I do not know how to set attributes or invoke an action.
I tried using this as a format to go by, but I haven't been able to get it to work so far. Any help understanding would be much appreciated.
EDIT:
I looked at the source code and I could just execute this:
node.run_context.request_reboot(
:delay_mins => #new_resource.delay_mins,
:reason => #new_resource.reason,
:timestamp => Time.now,
:requested_by => #new_resource.name
)
However, I don't think this is the best solution. I would like to know how to accomplish invoking the resource instead of bypassing it this way.
You can find an example of using Chef-Resources inside a HWRP in an older revision of the official Jenkins cookbook (was converted to LWRP in the meantime):
https://github.com/opscode-cookbooks/jenkins/blob/v2.0.2/libraries/plugin.rb#L138-L141
Keep in mind, that the Reboot resource is rather new (Chef 12+)
You can do it the same way you would in a recipe. If you need it to run immediately, then you would do:
reboot 'now' do
action :nothing
end.run_action(:reboot_now)
Within Ruby classes, you don't have access to the Chef DSL, so you have to access the underlying implementation of the resource as a class. The name of the class will be the camelcase-conversion of the resource name. You invoke the action with the run_action method.
Your original version actually was pretty close. You only use the resource, not the provider (because the provider may not even always be the same, depending on your platform).
The run_context is an object that chef uses to pass information to the resource - for instance, you can access node attributes through run_context.node['attributename']. It is already a member variable in your provider (and I think also in the resource object); you can simply pass it in to the constructor for your new resource.
You set attributes through member variables by the same name, and you trigger the actual action with the run_action method.
r = Chef::Resource::Reboot.new("wu_reboot", run_context)
r.reason("Because we need a reboot")
r.run_action(:reboot_now)

chef cookbook lwrp, easiest way to use new_resource.updated_by_last_action(true)

I'm writing a LWRP for chef 10.
And when that resource is run in other recipes it should be marked as "updated_by_last_action" if something has changed. But if nothing has changed. updated_by_last_action should be false.
So as example I have chef documentation http://docs.opscode.com/lwrp_custom_provider.html#updated-by-last-action. That example the resource template is wrapped inside an variable to test if it's been changed, and then set the updated_by_last_action status.
So my code should look something like this
f = file new_resource.filename do
xxx
end
new_resource.updated_by_last_action(f.updated_by_last_action?)
t = template new_resource.templatename do
xxx
end
new_resource.updated_by_last_action(t.updated_by_last_action?)
m mount new_resource.mountpoint do
xxx
end
new_resource.updated_by_last_action(m.updated_by_last_action?)
But if a provider gets bigger and uses a lot of resources like template, file, directory, mount, etc..
Should all those resource be wrapped inside variables like the example to find out if a resource have been updated, so to then further send a status that this provider have been updated.
I'm wondering if there is a simpler and cleaner way to run new_resource.updated_by_last_action(true) other then to wrap all resources inside variables. Cause if I just put a new_resource.updated_by_last_action(true) inside action before end the LWRP is marked as being updated every chef run, which is not optimal.
You can add use_inline_resources at the top of your LWRP, which delegates the updated_by_last_action to the inline resources.

Is there any way to delay a resource's attribute resolution until the "execute" phase?

I have two LWRPs. The first deals with creating a disk volume, formatting it, and mounting it on a virtual machine, we'll call this resource cloud_volume. The second resource (not really important what it does) needs a UUID for the newly formatted volume which is a required attribute, we'll call this resource foobar.
The resources cloud_volume and foobar are used in a recipe something like the following.
volumes.each do |mount_point, volume|
cloud_volume "#{mount_point}" do
size volume['size']
label volume['label']
action [:create, :initialize]
end
foobar "#{mount_point}" do
disk_uuid node[:volumes][mount_point][:uuid] # This is set by cloud_volume
action [:do_stuff]
end
end
So, when I do a chef run I get a Required argument disk_identifier is missing! exception.
After doing some digging I discovered that recipes are processed in two phases, a compile phase and an execute phase. It looks like the issue is at compile time as that is the point in time that node[:volumes][mount_point][:uuid] is not set.
Unfortunately I can't use the trick that OpsCode has here as notifications are being used in the cloud_volume LWRP (so it would fall into the anti-pattern shown in the documentation)
So, after all this, my question is, is there any way to get around the requirement that the value of disk_uuid be known at compile time?
A cleaner way would be to use Lazy Attribute Evaluation. This will evaluate node[:volumes][mount_point][:uuid] during execution time instead of compile
foobar "#{mount_point}" do
disk_uuid lazy { node[:volumes][mount_point][:uuid] }
action [:do_stuff]
end
Disclaimer: this is the way to go with older Chef (<11.6.0), before they added lazy attribute evaluation.
Wrap your foobar resource into ruby_block and define foobar dynamically. This way after the compile stage you will have a ruby code in resource collection and it will be evaluated in run stage.
ruby_block "mount #{mount_point} using foobar" do
block do
res = Chef::Resource::Foobar.new( mount_point, run_context )
res.disk_uuid node[:volumes][mount_point][:uuid]
res.run_action :do_stuff
end
end
This way node[:volumes][mount_point][:uuid] will not be known at compile time, but it also will not be accessed at compile time. It will only be accessed in running stage, when it should already be set.

Resources