How to code elasticsearch status checks in ruby with Chef? - ruby

I want to accomplish two things:
1) clean out any pointless pid files (if elasticsearch is not running) and then start it, and
2) check that ES has started up before proceeding
Now between what Chef offers out-of-box and what Ruby allows, I can only figure out a pseudo-code like syntax for making it happen but its not going to run so I need some help/advice writing the real thing.
Pseudo-Code For (1):
bash "start it up" do
user "root"
only_if { # pretty sure this syntax is all incorrect, any ideas to make it happen?
(sudo service elasticsearch status).match(/^elasticsearch not running/)
}
code <<-EOS
sudo rm -rf /usr/local/var/run/elasticsearch/*.pid
sudo service elasticsearch restart
EOS
end
Pseudo-Code For (2):
bash "wait for it to start up" do
user "root"
only_if { # pretty sure this syntax is all incorrect, any ideas to make it happen?
(sudo service elasticsearch status).match(/^elasticsearch running with PID/)
}
retries 20
retry_delay 5
code <<-EOS
echo "I can now go on with my life..."
EOS
end

If you wish to ensure a certain particular status before continuing, insert this in a recipe (this is an example and not tested):
service "elasticsearch" do
action [ :enable, :start ]
status_command "/usr/sbin/service elasticsearch status | grep 'running with PID'"
end
It's the job of the init script's start command to wait for the service to be actually started.
Chef docs says:
There is no reason to use the execute resource to control a service because the service resource exposes the start_command attribute directly, which gives a recipe full control over the command issued in a much cleaner, more direct manner.

Related

Strange behavior with ruby_block resource in Chef

I have two ruby blocks at the end of a recipe:
ruby_block 'set permissions for app dir' do
block do
require 'fileutils'
FileUtils.chown_R 'user01', 'user01', '/mnt/app/'
end
action :run
end
ruby_block 'configure node app session' do
block do
cmd = "sudo su - user01 -c \"/mnt/app/http-app-/bin/app create /mnt/app/http-app/#{node['hostname']}\" && sudo su -c 'systemctl enable app' && sudo su -c 'systemctl start app'"
exec(cmd)
end
action :run
not_if "stat -c %U /mnt/app/#{node['hostname']} |grep app"
end
A couple strange things are happening. One, I cannot add any code after the last block... it will not run if added. Two, when the cookbook runs the recipe never ends with if the run failed or was successful. Bootstrapping the system a second time will prove to finish successful... but ssh'ing to the box and running chef-client comes back with an empty run list.
Can anyone explain this behavior? How can i fix it?
exec() is not what you think. That's a Ruby core method which calls the actual exec() syscall, which replaces the current process with something new. What you want is our shell_out!() helper which runs a subcommand and returns and object with the results.

Systemd unit, check status with external script

The short version is:
I have a systemd unit that I want to check the return code of a script when I call:
systemctl status service.service
Long version: I had a lsb init script that did exactly that, when status was passed as parameter it called a script that checked the state of several processes and based on the return code the init system returned the state correctly of the software.
Now when adapting the script to systemd I can't find out how to configure this behaviour.
Short answer
This is impossible in systemd. The systemctl status verb always does the same thing, it cannot be overrided per-unit to a custom action.
Long answer
You can write a foo-status.service unit file with Type=oneshot and ExecStart= pointing to your custom status script, and then run systemctl start foo-status. However, this will only provide a zero/nonzero information (any nonzero exit code will be converted to 1).
To get the real exit code of your status script, run systemctl show -pExecMainStatus foo-status, however, if you go this far, then it is simpler to run your script directly.
You can use:
systemctl show -p ExecMainStatus service.service | sed 's/ExecMainStatus=//g'
This will return the exit code of the service.
If you are in control of the code of the service you start / stop that way, then you can easily edit it and save the result in a file.
Otherwise, you can always add a wrapper that does that for you.
#!/bin/sh
/path/to/service and args here
echo $? >/run/service.result
Then your status can be accessed using the contents of that file:
STATUS=`cat /run/service.result`
if test $STATUS = 1
then
echo "An error occurred..."
fi
(Side note: /run/ is only writable by root, use /tmp/ if you are not root.)

Chef run sh script

I have a problem trying to run shell script via Chef (with docker-provisioning).
This is how I try to execute my script:
bash 'shell_try' do
user "root"
run = "#{some_path_to_script}/my_script.sh some_params"
code " #{run} > stdout.txt 2> stderr.txt"
end
(note that this script should run another scripts, processes and write logs)
Here's no errors in the output, but when I log into machine and run ps aux process isn't running.
I guess something wrong with permissions (or env variables), because when I try the same command manually - it works.
A bash resource just runs the provided script text directly, if you wanted to run a long-running process generally you would set up an Upstart or systemd service and use the service resource to start it.
Finally find a solution (thanks to #coderanger) -
Install supervisor:
Download supervisor cookbook
Add:
include_recipe 'supervisor::default'
Add my service to supervisor:
supervisor_service "name" do
action :enable
#action :start
command '/path/script.sh start'
end
Run supervisor service
All done!
Please see the Chef documentation for your resource: https://docs.chef.io/resource_bash.html. The bash resource does not support a run attribute. Text of the code attribute is run as a bash script. The default action is to run the script unless told otherwise by the resource.
bash 'shell_try' do
user "root"
code " #{run} > stdout.txt 2> stderr.txt"
action :run
end
The code attribute is written to a temporary file where it is then run using the attributes specified in the resource.
The line run = "#{some_path_to_script}/my_script.sh some_params" at this point does nothing.

Chef Bash Resource Not Running

I've currently created a custom lwrp that essentially runs a bash script that curls for the localhost after tomcat restarts to make the sure the service is running.
My provider file looks like this:
use_inline_resources
action :run do
bash "checkhealth" do
user "root"
code <<-EOF
echo Started counting
curl http://localhost/version.html
...
EOF
end
end
On one of my nodes, I have the following block:
service "node" do
supports :start => true, :stop => true, :restart => true, :status => true
action :nothing
notifies :run, "healthcheck[check-status]", :delayed
end
And when i run chef-client, I can see the echos from the bash code running.
However, on a different node, I have a block like this:
service "tomcat" do
action :restart
notifies :run, "healthcheck[check-status]", :delayed
end
But I can't see any output from the echo and it doesn't look like the bash code is running. I know the bash resource is being executed because the log output says the bash resource was successfully run. However, there is a very long delay after the log says:
action run[2014-07-23T09:10:23-07:00] INFO: Processing bash[checkhealth] action run
and when it says it was successful, which makes me think something weird is going on with the bash code, but I'm not sure what. This is where I'm stuck and hoping you guys could help me figure out this weird bug :). I'm guessing it may have something to do with the fact that in the first block, the action is :nothing, but the second block has :restart.
Let me know what you guys think.
Thanks!
Why not emulate what the old Jenkins cookbook (v1.2.2) used to do?
See:
Ruby block that checks for running service
Helper functions contained in a chef library

How do you prompt for a sudo password using Ruby?

Often I find myself needing to write scripts that have to execute some portions as a normal user and other portions as a super user. I am aware of one similar question on SO where the answer was to run the same script twice and execute it as sudo, however that is not sufficient for me. Some times I need to revert to being a normal user after a sudo operation.
I have written the following in Ruby to do this
#!/usr/bin/ruby
require 'rubygems'
require 'highline/import'
require 'pty'
require 'expect'
def sudorun(command, password)
`sudo -k`
PTY.spawn("sleep 1; sudo -u root #{command} 2>&1") { | stdin, stdout, pid |
begin
stdin.expect(/password/) {
stdout.write("#{password}\n")
puts stdin.read.lstrip
}
rescue Errno::EIO
end
}
end
Unfortunately, using that code if the user enters the wrong password the script crashes. Ideally it should give the user 3 tries to get the sudo password right. How do I fix this?
I am running this on Linux Ubuntu BTW.
In my opinion, running a script that does stuff internally with sudo is wrong. A better approach is to have the user run the whole script with sudo, and have the script fork lesser-privileged children to do stuff:
# Drops privileges to that of the specified user
def drop_priv user
Process.initgroups(user.username, user.gid)
Process::Sys.setegid(user.gid)
Process::Sys.setgid(user.gid)
Process::Sys.setuid(user.uid)
end
# Execute the provided block in a child process as the specified user
# The parent blocks until the child finishes.
def do_as_user user
unless pid = fork
drop_priv(user)
yield if block_given?
exit! 0 # prevent remainder of script from running in the child process
end
puts "Child running as PID #{pid} with reduced privs"
Process.wait(pid)
end
at_exit { puts 'Script finished.' }
User = Struct.new(:username, :uid, :gid)
user = User.new('nobody', 65534, 65534)
do_as_user(user) do
sleep 1 # do something more useful here
exit! 2 # optionally provide an exit code
end
puts "Child exited with status #{$?.exitstatus}"
puts 'Running stuff as root'
sleep 1
do_as_user(user) do
puts 'Doing stuff as a user'
sleep 1
end
This example script has two helper methods. #drop_priv takes an object with username, uid, and gid defined and properly reduces the permissions of the executing process. The #do_as_user method calls #drop_priv in a child process before yielding to the provided block. Note the use of #exit! to prevent the child from running any part of the script outside of the block while avoiding the at_exit hook.
Often overlooked security concerns to think about:
Inheritance of open file descriptors
Environment variable filtering
Run children in a chroot?
Depending on what the script is doing, any of these may need to be addressed. #drop_priv is an ideal place to handle all of them.
If it is possible, you could move the stuff you want executed as root to a seperate file and use the system() function to run it as sudo, including the sudo prompt etc:
system("sudo ruby stufftorunasroot.rb")
The system() function is blocking, so the flow of your program doesn't need to be changed.
I do not know if this is what you want or need, but have you tried sudo -A (search the web or the man page for SUDO_ASKPASS which might have a value like /usr/lib/openssh/gnome-ssh-askpass or similar)? This is what I use when I need to present a graphical password dialogue to users in GUI environments.
Sorry if this is the wrong answer, maybe you really want to remain on the console.
#!/usr/bin/ruby
# ... blabla, other code
# part which requires sudo:
system "sudo -p 'sudo password: ' #{command}"
# other stuff
# sudo again
system "sudo -p 'sudo password: ' #{command}"
# usually sudo 'remembers' that you just authenticated yourself successfuly and doesn't ask for the PW again...
# some more code...

Resources