0

I am creating a recipe that has two resources. The initial VPN IP address is a empty valued string. Upon execution of the first resource, a IP address within the range 10.12.xx.xx is set. Then a second resource is executed with a guard block that checks for the VPN IP.

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

#Getting the IP address using the ruby's Socket class.

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

execute 'manually_start_open_vpn' do
command "sudo openvpn #{node['openvpn-conf-path']}/#{host}.conf &"
action :nothing
only_if {ip_addr.length.eql?(0)}
end

new_ip_list = Socket.ip_address_list
new_vpn_ip_list = new_ip_list.select{ |ip| 
                                   ip.ip_address.match(/^10.12/) }
!new_vpn_ip_list.empty? ? newvpn_ip_addr = 
                new_vpn_ip_list.first.ip_address : newvpn_ip_addr = ""

ruby_block 'chat-bot' do
   block do
        machine_data = {text: "OpenVPN IP not assigned to #{host} 
            software_version: 18.4.4 \n This is a test message please 
              ignore @all"}.to_json 
        header = {'Content-Type': 'text/json'}
        http = Net::HTTP.new(google_chat_uri.host, 
                   google_chat_uri.port)
        http.use_ssl = true
        request = Net::HTTP::Post.new(google_chat_uri.request_uri, header)
        request.body = machine_data
        response = http.request(request)
    end 

   end
only_if {newvpn_ip_addr.length.eql?(0)}
action :nothing
end 

Since there is no vpn ip address of the range 10.12.xx.xx during compile phase, the value of ip_addr.length.eql?(0) is true and the guard block condition in first resource is satisfied and it's executed.

As the vpn ip has already been assigned, the newvpn_ip_addr.length.eql?(0) should be false and the 2nd ruby block should not be executed. But this returns a false value as the code newvpn_ip_addr is executed during compile time itself.

Is there any way to make sure that the value for newvpn_ip_addr in the below code is assigned and checked during the execution phase post the execution of first resource?

    new_ip_list = Socket.ip_address_list
    new_vpn_ip_list = new_ip_list.select{ |ip| 
                                   ip.ip_address.match(/^10.12/) }
    !new_vpn_ip_list.empty? ? newvpn_ip_addr = 
                new_vpn_ip_list.first.ip_address : newvpn_ip_addr = ""

So that the guard block in 2nd resource is not executed if the expected IP address is not assigned. only_if {newvpn_ip_addr.length.eql?(0)}

1
  • I've rolled back you changes, you can see your modifications in the question history (the 'edited X ago' under the question) to use it as a new question. StackExchange sites are not a forum, follow up questions are to be asked separately.
    – Tensibai
    Mar 8, 2019 at 8:25

1 Answer 1

1

You have a problem of compile vs converge times as you guessed.

You code between the execute resource and the ruby_block resource is executed before the execute resource is converged.

The easiest way to circumvent that is to put that code within a ruby_block, and to avoid scoping problem use node.run_state hash to store the value to be checked.

A rewrite of your recipe which should work would be:

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

#Getting the IP address using the ruby's Socket class.

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

execute 'manually_start_open_vpn' do
  command "sudo openvpn #{node['openvpn-conf-path']}/#{host}.conf &"
  action :nothing
  only_if {ip_addr.length.eql?(0)}
end

ruby_block 'check_vpn_ip_list' do
  block do
    new_ip_list = Socket.ip_address_list
    new_vpn_ip_list = new_ip_list.select{ |ip| ip.ip_address.match(/^10.12/) }
    node.run_state['newvpn_ip_addr'] = !new_vpn_ip_list.empty? ?  new_vpn_ip_list.first.ip_address : ""
  end
end

ruby_block 'chat-bot' do
  block do
    machine_data = {text: "OpenVPN IP not assigned to #{host} 
        software_version: 18.4.4 \n This is a test message please 
        ignore @all"}.to_json 
    header = {'Content-Type': 'text/json'}
    http = Net::HTTP.new(google_chat_uri.host,                       google_chat_uri.port)
    http.use_ssl = true
    request = Net::HTTP::Post.new(google_chat_uri.request_uri, header)
    request.body = machine_data
    response = http.request(request)
  end
  only_if {node.run_state['newvpn_ip_addr'].length.eql?(0)}
  action :nothing
end 

There's no real use of response = in the last line as you don't use it later, but I let it as is in case you wish to log in the ruby block the response.

7
  • Thanks for the reply @Tensibai . Been struggling with the assignment of variable value due to the compile vs converge time properties of Chef. I have read that any code within the ruby_block, guard statement and lazy are evaluated only at run time/converge phase. I tried using a ruby block to get the value of variable newvpn_ip_addr but I didn't make use of the node.run_state. I will try the solution and get back with the results.
    – Yuge
    Jan 31, 2019 at 17:37
  • Also I have come up an alternative solution that makes use of the property of guard statements. i.e. Any code with the guard statement will be executed only at the converge phase or run-time. I removed the entire code that checks and assign the value for newvpn_ip_addr variable and instead put the code directly inside the only_if guard block like shown below so that the resources are executed based on the values created after run time. I don't know if this is the correct approach but it works. only_if {Socket.ip_address_list.select{ |ip| ip.ip_address.match(/^10.12/) }.empty?}
    – Yuge
    Jan 31, 2019 at 17:41
  • It works for this case, but look at it, it's hard to grasp for your future self in 9 month :)
    – Tensibai
    Jan 31, 2019 at 21:30
  • 1
    And for the problem you got, a variable within a ruby block is only available within the block, you can workaround with a global variable, but that's even more complex to understand after
    – Tensibai
    Jan 31, 2019 at 21:32
  • Thanks for the node.runstate[:variable] tip. I used that to pass data between two ruby_block in a recipe. But I have another problem, the code inside the ruby_block is not executed at run-time. Please have a look at my code, I have made changes to the question @Tensibai
    – Yuge
    Mar 8, 2019 at 1:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.