Ruby code inside guard block not executed at run time

According to Chef's two pass model any code inside ruby-block, guard blocks and lazy are executed only during run time.

I have a recipe with three resources.

Variables whose values are assigned during compile phase.

#
# Cookbook:: test_cookbook
# Recipe:: check-vpn-ip
#
# Copyright:: 2019, The Authors, All Rights Reserved.

#Getting the IP address using the ruby's Socket class.
require 'socket'
require 'down'
require 'net/http'

conf = `hostname`.chomp
vpn_ip_list = Socket.ip_address_list.select{ |ip| ip.ip_address.match(/^10.12/) }
!vpn_ip_list.empty? ? ip_addr = vpn_ip_list.first.ip_address : ip_addr = ""

1st resource - Checks if desired VPN IP address is assigned using code in guard block and if not assigned downloads the conf file and notifies 2nd service resource to restart openvpn. The values for variables inside guard block are obtained during compiled phase.

ruby_block 'download_openvpn_conf' do
block do 
    attempt = 2
    begin
        retries ||= 0
        tempfile = Down.download("http://some-url1/#{conf}",max_redirects: 0)
        FileUtils.mv tempfile.path, "#{node['openvpn-conf-path']}/#{tempfile.original_filename}"
        FileUtils.chmod 0644, "#{node['openvpn-conf-path']}/#{tempfile.original_filename}"
    rescue Down::Error => e
        node.run_state['error'] = e.to_s
        puts e
        Chef::Log.warn ("\n \t ***********#{e}***********")
        retry if (retries += 1) < 1
    end 
end
only_if {vpn_ip_list.size.eql?(0) || vpn_ip_list.size >= 2}
action :run
notifies :restart, 'service[openvpn]', :immediately
notifies :delete, "file[#{node['openvpn-conf-path']}/#{conf}]", :before
end

2nd resource - Restarts the openvpn service. By this time the VPN IP is assigned to system.

service 'openvpn' do
supports :status => true, :restart => true, :start => true, :stop => true
action :nothing
Chef::Log.info ("\n \t *********Restarting OPEN-VPN*********")
end

Since the above service is executed during the converge phase, the VPN ip address should have been assigned based on the downloaded configuration file.

3rd resource - If the VPN IP is not assigned by the time of execution of 2nd resource, this places a request for new vpn conf file.

ruby_block 'manual_vpn' do
block do
    if node.run_state['error'] == "file not found"
        Chef::Log.info ("\n \t ******** VPN IP has not been assigned. File may be corrupted & initiating re-run********")
        uri = URI.parse("http://some-url2=#{host}&action=create")
        Chef::Log.info (uri)
        http = Net::HTTP.new(uri.host,uri.port)
        request = Net::HTTP::Get.new(uri.request_uri)
        response = http.request(request)
        case response.body
        when "error"
            Chef::Log.info ("\n \t Website reported an Error. Config for the given serial number could have already been generated")
        when "Request for vpn successfully added."  
            Chef::Log.warn ("\n \t **** Inside response processing => Request Accepted **")
            Chef::Log.warn ("\n \t *** New vpn config request has been accepted. Waiting for 3 minutes ***")
            sleep 180
        else
            Chef::Log.info ("\n \t Nothing to do - Extra option for future")    
        end 
    else 
        puts "Config file exists and hence not downloading"
    end     
end 
notifies :run, 'ruby_block[re-download-vpn-conf]', :delayed
only_if { Socket.ip_address_list.select{ |ip| ip.ip_address.match(/^10.12/) }.size.eql?(0) }
not_if {node.run_state['error'].eql?("too many redirects")}
end 

The VPN IP assigned is checked by the code in guard block only_if { Socket.ip_address_list.select{ |ip| ip.ip_address.match(/^10.12/) }.size.eql?(0) } and is supposed to be executed only at run time. By the end of 2nd resource execution the VPN IP is assigned for sure but code inside the above guard could not detect it.

I have used Pry debugger at the end of recipe within a test ruby block to verify that IP is assigned post the execution of 2nd service restart resource. Wondering why code inside the guard block of Chef is not able to detect the VPN assigned by the previous resource execution.

Possibly a race condition. The service 'openvpn' resource could return and the guard on the ruby_block 'manual_vpn' resource executed before OpenVPN actually has the interfaces up. The logic you have put in the guard can succeed when run in a debugger because waiting for a human to paste a line of code into a console prompt allows for enough time for OpenVPN to finish setting up interfaces.

1 Like

Thanks for the reply. Let me try adding ruby's sleep 10 command inside the 3rd resource and check if it avoids a race condition.

Man your are just awesome! Race ahead condition was exactly the issue that I was facing. I added a sleep 60 line in a ruby block after the 2nd service block that restarts the openvpn. The code inside the guard block only_if { Socket.ip_address_list.select{ |ip| ip.ip_address.match(/^10.12/) }.size.eql?(0) } was able to detect the vpn ip assigned post service restart, Voila!