From ruby array to json in bash - ruby

In my chef environment I have a variable like this:
"repl_set_members": ["tmongo01", "tmongo02", "tmongo03"]
I need to create JSON to be sent to a Mongo instance and build the replica set.
I created a bash file from a template with:
template "/opt/create_repl_set.sh" do
source "create_repl_set.sh.erb"
owner 'root'
group 'root'
mode "0755"
variables(
:repl_set_name => node['mongodb']['shardname'],
:members => node['mongodb']['repl_set_members']
)
end
And in the bash template I would have something like this:
config = "{_id: '<%= #repl_set_name %>', members: [
<% #members.each_with_index do |name,number| %>
{_id: <%= "#{number}" %>, host: '<%= "#{name}" %>:27017'},
<% end %>
] }"
mongo localhost:27091 --eval "JSON.stringify(db.adminCommand({'replSetInitiate' : $config}))"
But the resulting JSON includes a last comma which I don't know how to get rid of.
Does anyone have a better idea?

A quick and dirty way to remove your last comma would be to use bash string manipulation and call your script as follow :
mongo localhost:27091 --eval "JSON.stringify(db.adminCommand({'replSetInitiate' : ${config/%,/}))"
Note that this will always delte the last comma in your JSON value, so it will only work if your resulting JSON always has an extra comma (i.e. even when your JSON is empty)

What you probably want is an execute resource an .to_json
mongo_cmd = {
'replSetInitiate' => {
'_id' => node['mongodb']['shardname'],
'members' => node['mongodb']['repl_set_members'].each_with_index.map { |name, number|
{'_id' => number, 'host' => "#{name}:27017"}
},
},
}
execute "mongo localhost:27091 --eval 'JSON.stringify(db.adminCommand(#{mongo_cmd.to_json}))'"

Related

Ruby accessing nested hash and erb template

I have the following hash that lists what services exist on what machine type and Chef code block. Some of the services have port numbers associated with them.
services_hash = {
"bugs_services" => ["bugs"],
"max_api_services" => ["max-api"],
"max_api_ports_max-api" => 84,
"max_data_services" => ["max-data-http"],
"max_data_ports_max-data-http" => 85,
"max_logger_services" => ["max-analytics", "max-logger"],
"max_logger_ports_max-analytics" => 83,
"max_logger_ports_max-logger" => 82
}
%w( max_api max_data max_logger ).each do |haproxy_chef_role|
template "/home/ubuntu/ansible/deploy/role_#{haproxy_chef_role}.yml" do
source 'haproxy_services.yml.erb'
owner 'ubuntu'
group 'ubuntu'
action :create
mode 0644
variables({
number_of_services: number_of_services,
services: services_hash["#{haproxy_chef_role}_services"],
ports: ???
haproxy_chef_role: haproxy_chef_role
})
end
end
I also have an erb template that resembles this.
<% #services.each do |service| -%>
<% if service.include?("max-logger") %>
shell: for i in {0..<%= #number_of_services %>}; do echo <%= service %>:<%= ports %>$(printf '%02d' $i); done
<% else %>
shell: echo <%= service %>:<%= ports %>00
<% end %>
<% end %>
How can I nest hashes such that a port number is associated to a given service and I can is callable from within the erb template?
I was thinking something like this where I have an array with strings representing the services, and then a hash mapping service to port. I think this will work, but I don't know how to properly extract out the data to fill in my erb template.
services_hash = {
"max_logger_services" => [
"max-logger",
"max-analytics",
{ "max-analytics" => 83, "max-logger" => 82] }
}
I was trying to do something like this earlier where I interpolate whatever I'm currently processing into another hash query, but it's not working well and seems like the wrong way to go about this.
ports: services_hash["#{haproxy_chef_role}_ports_#{services_hash["#{haproxy_chef_role}_services"]}"],
edit:
I've now got the line that returns the hash per Sebastian's answer below which returns the hash relating services to ports.
ports: services_hash['max_logger_services'].inject{|_k,v| v},
In the erb template now though, I don't quite understand how I am supposed to query the hash keys for values. Particularly that I don't seem to be able to nest variables together in an erb.
<% #services.each do |service| -%>
<% if service.include?("max-logger") %>
shell: for i in {0..<%= #number_of_services %>}; do echo disable server <%= service.gsub('max-', '') %>-backend/{{ ansible_fqdn }}:<%= #ports["<%= service %>"] %>$(printf '%02d' $i) | sudo socat stdio /run/admin.sock; done
✗ <% else %>
shell: echo disable server <%= service.gsub('max-', '') %>-backend/{{ ansible_fqdn }}:<%= #ports['<%= service -%>'] -%>00 | sud o socat stdio /run/admin.sock
<% end %>
<% end %>
This line right here seems to the be the problem. The hash is being passed to the erb but I'm not able to drop in the current service being processed into the hash to spit out a value. This is the same fundamental problem I was having that prompted me to ask this question: I can't seem to tell which service I am currently processing to find further data regarding that particular service.
<%= #ports["<%= service %>"] %>
If services_hash had this format:
services_hash = {
"max_logger_services" => [
"max-logger",
"max-analytics", {
"max-analytics" => 83,
"max-logger" => 82
}
]
}
Then you could access max-analytics and to max-logger just with:
hash = services_hash['max_logger_services'][2]
p hash['max-analytics']
# => 83
p hash['max-logger']
# => 82
If you don't know if the data will be always formatted the same way, at least you can "dig" until the deeper hash values:
hash = services_hash['max_logger_services'].inject{|_k,v| v}
# => {"max-analytics"=>83, "max-logger"=>82}

Chef template loop: can't convert Chef::Node::immutableMash into String

I've got a Vagrant setup in which I'm trying to use Chef-solo to generate an conf file which loops though defined variables to pass to the application. Everything is working except the loop and I'm not familiar enough with Ruby/Chef to spot the error.
I'm going to lay out the whole chain of events in case there is something along the way that is the problem, but the first portions of this process seem to work fine.
A config file is written in yaml and includes env variable definitions to be passed:
...
variables:
- DEBUG: 2
...
The config file is read in by the Vagrantfile into a ruby hash and used to create the Chef json nodes:
...
settings = YAML::load(File.read("config.yaml"))
# Provision The Virtual Machine Using Chef
config.vm.provision "chef_solo" do |chef|
chef.json = {
"mysql" => {"server_root_password" => "secret"},
"postgresql" => {"password" => {"postgres" => "secret"}},
"nginx" => {"pid" => "/run/nginx.pid"},
"php-fpm" => {"pid" => "/run/php5-fpm.pid"},
"databases" => settings["databases"] || [],
"sites" => settings["sites"] || [],
"variables" => settings["variables"] || []
}
...
A bunch of chef cookbooks are run (apt, php, nginx, mysql etc) and finally my custom cookbook which is whats giving me grief. The portion of the cookbook responsible for creating a the conf file is shown here:
# Configure All Of The Server Environment Variables
template "#{node['php-fpm']['pool_conf_dir']}/vars.conf" do
source "vars.erb"
owner "root"
group "root"
mode 0644
variables(
:vars => node['variables']
)
notifies :restart, "service[php-fpm]"
end
And the vars.erb is just a one-liner
<%= #vars.each {|key, value| puts "env[" + key + " = " + value } %>
So, when I run all this chef spits out an error about not being able to convert a hash to a string.
can't convert Chef::Node::immutableMash into String
So for some reason this is coming across as an immutableMash and the value of key ends up being the hash [{"DEBUG"=>2}] and value ends up a nil object, but I'm not sure why or how to correct it.
The hash is ending up as the value of key in your example because the YAML file declares DEBUG: 2 as a list member of variables. This translates to variables being an array with a single hash member.
Try changing the template code to this:
<%= #vars[0].each {|key, value| puts "env[" + key + " = " + value } %>
Or try changing the YAML to this and not changing the template code:
variables:
DEBUG: 2
Either change will get your template loop iterating over the hash that you are expecting.

In Ruby, how to upload multiple files in single request using RESTClient

I have to upload multiple files as form request. I am using the Rest Client to post my request. I am able to upload single file but I am not sure how to add multiple files in a single request.
I searched/googled for such option and I am not finding any solution that solves my problem.
Below is my code.
It has variable argument (*yamlfile) which takes one or more files. I have to upload all the files together.
The issue now is , I am getting syntax error when I add the loop to extract the file within the payload.
my assumption is now to form this outside the payload and include it inside the payload block but I am not sure how to do it.
Can someone help me with that.
( I have tried net/http/post/multipart library too and I don't find much documents around it)
def uploadRest(endpoint,archive_file_path,,yaml_file_path,*yamlfile)
$arg_len=yamlfile.length
request = RestClient::Request.new(
:method => :post,
:url => endpoint,
:payload => {
:multipart => true,
:job_upload_archive => File.new(archive_file_path,'rb'),
:job_upload_path => "/tmp",
# Trying to add multiple file, but I get syntax error
yamlfile.each_with_index { |yaml, index|
:job_upload_yaml_file+index => File.new("#{yaml_file_path}/#{pmml}")
}
})
response=request.execute
puts response.code
end
uploadRest(endpoint,archive_file_path,yaml_file_path,*yamlfile)
#files=Array.new
yamlfile.each{ |yaml_file|
#files.push(File.new("#{yaml_file_path}/#{yaml_file}"))
}
request = RestClient::Request.new(
:method => :post,
:url => endpoint,
:payload => { :multipart => true, :job_upload_archive => File.new(archive_file_path,'rb'),
:job_upload_path => "/tmp", :job_upload_yaml_file => #files })
response=request.execute
end
I had a similar problem and was able to get this to work by passing an array of arrays as a requests.
file1 = File.new("#{yaml_file_path}/#{yaml_file1}", 'rb')
file2 = File.new("#{yaml_file_path}/#{yaml_file}", 'rb')
request_body = [["files", file1], ["files", file2]]
RestClient.post url, request_body, request_headers
There were two issues with your question code:
1) Attempt to add a symbol to an integer
2) Attempt to insert contents of yamlfile direct into the hash (because that is what yamlfile.each_with_index returns, as opposed to how it calls your block. The return value from the block is not used)
Both of these code issues read as if you have gained experience in HAML or another templating language, and are using structures/ideas that would work in that?
There are lots of possble solutions in Ruby, but a simple approach to build up the hash in parts, as opposed to generate it in one go with clever hash-returning routines embedded. Try something like this:
payload_hash = {
:multipart => true,
:job_upload_archive => File.new(archive_file_path,'rb'),
:job_upload_path => "/tmp",
}
# This does not use the return value from each_with_index, instead it relies
# on the block to make changes to the hash by adding new key/value pairs
yamlfile.each_with_index { |yaml, index|
# This concatenates two strings, and then converts the combined
# string into the symbol that you want
file_key = ("job_upload_yaml_file"+index.to_s).to_sym
payload_hash[file_key] = File.new("#{yaml_file_path}/#{yaml}")
}
request = RestClient::Request.new(
:method => :post,
:url => endpoint,
:payload => payload_hash
)
For added code cleanliness, you could make the first two parts a separate method, and call it where it currently has payload_hash.
This should get you over current syntax hurdles. However, I have made no attempt to check whether this will allow you to upload multiple files via RESTClient.
Section1:
#params = {
"FacialImage" => UploadIO.new(File.new('C:\temp\ATDD\Test\test\sachin.jpg'), "image/jpeg"),
"Login" => UploadIO.new(File.new('C:\temp\ATDD\Test\test\login.txt'), "application/json")
}

Extract comments

I have this code in a controller:
# =begin action
# url: /albums
# method: GET
# autentication: true
# return: [json, xml]
# =end
def show
...
end
Is there some gem that reads comments and returns info in json format, or does something like this? I want to get it to manipulate and generate files with this information.
{
"url" => /albums
"method" => GET
"autentication" => true
"return" => [json, xml]
}
I don't think there is a gem doing exactly what you want, but the task seems to be pretty easy to devide:
first, you need to parse the file and pull that comments - it shouldn't be difficult to do it with a simple ruby script.
Then having info in format like:
url: /albums
method: GET
autentication: true
return: [json, xml]
which seems pretty like a YAML, you can do simply
YAML::load(string).to_json

“can't convert Symbol into Integer” weird error

The is the hash I am working on,
a = {
#...
:fares => {
:itinerary_fare => {
:segment_names=>"C",
:free_seats => "6",
:fare_for_one_passenger => {
:free_seats=>"0",
:#currency => "TL",
:#non_refundable => "false",
:#price => "439.0",
:#service_fee => "25.0",
:#tax => "33.0",
:#type => "Y"
},
:#currency => "TL",
:#non_refundable => "false",
:#price => "439.0",
:#service_fee => "25.0",
:#tax => "33.0",
:#type => "C"
},
:#currency => "TL",
:#tax => "33.0"
},
#..
}
also here another example http://pastebin.com/ukTu8GaG.
The code that gives me headhaches,
a[:fares][:itinerary_fare].each do |f|
puts f[:#price]
end
If I write this into console, it gives me "can't convert Symbol into Integer" error. But if I write, a[:fares][:itinerary_fare][:#price] it works pretty fine.
The weirdest part is, if I write the code to a haml file
%tbody
-#flights.each do |a|
%tr.flight
%td
-a[:fares][:itinerary_fare].each do |f|
-puts f[:#price] #Weird stuff happens here
.prices
%input{:type=>"radio",:name=>"selectedfight",:value=>"#{a[:id]}"}
= f[:#price]
%br
It works, it prints the prices to my console, yet it fails at the SAME LINE.
can't convert Symbol into Integer file: flights.haml location: [] line: 18
This is the most disturbing error I have ever seen, thanks for any help.
Most of the time there are more than 1 :itinerary_fare, I have to iterate.
My data can be shown as http://postimage.org/image/6nnbk9l35/
a[:fares][:itinerary_fare] is a Hash. Hash#each yields key-value-pair arrays to the block.
So, f takes e.g. the array [:#price, "439.0"] in the block.
Hence you are using a symbol (:#price) as an array index. An integer is expected.
In a[:fares][:itinerary_fare][:#price] you are giving it as hash key which works of course.
Why did you delete the previous question, that was already correctly answered? You could only just updated it with more information.
As was answered by other user in your previous post you are iterating over the elements in a[:fares][:itinerary_fare]. You can see this with:
a[:fares][:itinerary_fare].each do |f|
puts f
end
And you dont need a loop, you can use:
a[:fares][:itinerary_fare][:#price]
If you have more than one :itinerary_fare it will only consider the last one, since it's a key of the hash :fares. Maybe you need an array like (left to minimal of elements):
a = {:id=>"1",
:fares=>{
:itinerary_fares=>[{:#price=>"439"}, {:#price=>"1000"}]
}
}
and then:
a[:fares][:itinerary_fares].each do |f|
puts f[:#price]
end

Resources