Iterate though ENV and write the result to a file in Chef - ruby

I am trying to iterate through the EVN Hash and save the result into a file. Below is my attempt:
file "/srv/www/shared/test-create-file.txt" do
owner "root"
group "root"
mode "0755"
tempVar = ""
ENV.each_pair do |k, v|
tempVar = tempVar#{k}#{" = "}#{v}
end
content tempVar
action "create"
end
The file is created but with an empty content. Could I get some hints?

You don't really need to create a variable for that:
file "/srv/www/shared/test-create-file.txt" do
owner "root"
group "root"
mode "0755"
content ENV.map { |k,v| "#{k} = #{v}" }.join("\n")
action "create"
end
In case you just want to fix your iteration, try this instead:
ENV.each_pair do |k, v|
tempVar = "#{tempVar}#{k} = #{v}\n"
end

You don't need to create a temporary variable in this case. You can just pass the method directly into the content block as cassianoleal suggested.
However, I would strongly suggest moving to a template resource. You are performing data manipulation, which should really be left for a template:
template "/srv/www/shared/test-create-file.txt" do
source "my-template.erb"
owner "root"
group "root"
mode "0755"
action "create"
end
And then in the template:
<% ENV.each do |key, value| %>
<%= key %>=<%= value %>
<% end %>
You can read more about the ERB templating language and Chef templates on the Chef Docs.

The hash (#) symbol in ruby is for making comments. Every thing after the hash (#) symbol on line 7 is being considered a comment.
In the each_pair block you are continually reassigning tempVar to tempVar. As tempVar is initially an empty String it is setting tempVar to an emptyString in each iteration.

If you're expecting tempVar to accumulate the return value from the each_pair block, you should consider using inject:
tempVar = {key1: :value1, key2: :value2}.inject('') do |memo, member|
memo += "#{member[0]} = #{member[1]}\n"
end
# tempVar => "key1 = value1\nkey2 = value2\n"

Related

merge two or more hashes from array in one json file

The issue that I'm facing is when I'm trying to automate a terraform script for bootstrap VMs. I keep all the templates in json files and depending on instance it will take one, two or more templates to create the node json file.
My code looks like:
template.each do |t|
node = JSON.parse(File.read("./terraform/node/#{t}.json"))
atr << node.each { |k, v| puts "#{k}: #{v}" }
concatenated = atr.flatten
temp = concatenated
File.open("./terraform/node/#{instance_name}.json","w") do |f|
f.puts JSON.pretty_generate(temp)
end
end
The output file looks like:
[{"haproxy"=>{"app_server_role"=>["s1", "s2"]}}, {"apache"=>{"listen_ports"=>["80", "443"]}}, {"tags"=>[]}]
The issue is that inside the array we have the exact template stored in erb :
{"haproxy"=>{"app_server_role"=>["s1", "s2"]}}
{"tags"=>[]} ...
What I want is a valid json with the content of templates like:
{"haproxy"=>{"app_server_role"=>["s1", "s2"]}, "apache"=>{"listen_ports"=>["80", "443"]}, {"tags"=>[]}
output_file = [{"haproxy"=>{"app_server_role"=>["s1", "s2"]}}, {"apache"=>{"listen_ports"=>["80", "443"]}}, {"tags"=>[]}]
output_file.each_with_object({}) { |h, h2| h2.merge!(h) }
=> {"haproxy"=>{"app_server_role"=>["s1", "s2"]}, "apache"=>{"listen_ports"=>["80", "443"]}, "tags"=>[]}
another great option suggested by mudasobwa:
output_file.reduce(&:merge)

Chef/Ruby: Iterating through a Hash Array

I'm trying to iterate through a hash defined in my Chef attributes file and write it to a config template:
default['disk'] = node['block_device'].select { |i,j| j['state'] == 'running' && i != 'cdrom' }.select { |r| puts "Disk #{r}"}
In my Chef template, I am calling the variable with <%= #disk %>, so all the work is being done in the attributes file variable.
The above attribute will show me the result I want when it is compiling the cookbook, but using the puts method will not write to the config template, and I come up with empty strings written instead (see below).
Compiling Cookbooks...
Disk sda
Disk sdb
Converging 7 resources
....
+ Disk "{}"
If I remove the puts method (should not need it to write to config template), then I get the entire ['block_device'] structure (instead of just the device name) as a Disk value written to config template instead.
I've also tried playing around with using the puts method within the config template as well, but got no where. How can I write a new line in my template per key value in the array during a chef-client run? I would like to get it written to the config template instead of STDOUT during compile??
Chef templates use Erb formatting do you would want to actually use that:
# recipe
template '/asdf' do
# ...
variables disks: node['block_device'].select { |i,j| j['state'] == 'running' && i != 'cdrom' }
end
# template
<%- #disk.each do |i, j| -%>
<%= i %>
<%- end -%>

how I could redirect variable that contain a value and the value contain another value in ruby?

I have a variable varpage this is equal to start and I need to obtain start value as output and save to file
this is my code
varpage="start"
start="1,4,1,0,1,1,1,30,12,;1,4,1,2,1,1,1,30,29,;1,5,1,2,0,1,1,30,29,;1,4,1,2,0,1,1,30,29,;1,4,1,0,1,1,1,30,29,;"
File.open("mmmm3", "w") do |f| f.puts "##{varpage}" end
I expected this output
"1,4,1,0,1,1,1,30,12,;1,4,1,2,1,1,1,30,29,;1,5,1,2,0,1,1,30,29,;1,4,1,2,0,1,1,30,29,;1,4,1,0,1,1,1,30,29,;"
Please help me
If I understand your question correctly, you can use a hash:
varpage = "start"
options = {"start" => "1,4,1,0,1,1,1,30,12,;1,4,1,2,1,1,1,30,29,;1,5,1,2,0,1,1,30,29,;1,4,1,2,0,1,1,30,29,;1,4,1,0,1,1,1,30,29,;"}
File.open("mmmm3", "w" do |f|
f.puts options[varpage]
end

How to print all linux users with Puppet and ruby?

I want to create a facter that returns all users.
Facter.add("sysusers") do
setcode do
File.readlines('/etc/passwd').each do |line|
line.match(/^[^:]+/)[0]
end
end
end
Then in my .pp file I have this:
$users = inline_template("<%= scope.lookupvar('sysusers') %>")
$users.each |String $user| {
notify { "$user":}
}
This should work but the facter returns just one letter at a time.
So notify { "$user":} just prints:
Notify[r]
Notify[o]
And then it craches because the next letter is also "o" (two o`s in "root" and root is the first user stated in /etc/passwd).
So how can I print all the users?
EDIT
With the edit to:
Facter.add("sysusers") do
setcode do
File.readlines('/etc/passwd').each do |line|
line.match(/^[^:]+/).to_s
end
end
end
Then the output is:
root#mymachine]# facter sysusers
[
"root:x:0:0:root:/root:/bin/bash
",
"bin:x:1:1:bin:/bin:/usr/bin/nologin
",
"daemon:x:2:2:daemon:/:/usr/bin/nologin
...
...
So it still does not seem to work as expeced.
This is the match you want.
line.match(/^[^:]+/).to_s
When you add [0], it is taking the first character from the string that is the user name.
EDIT
File.readlines('/etc/passwd').collect do |line|
line.match(/^[^:]+/).to_s
end
That will collect an array which to be returned in your setcode.
Parsing /etc/passwd is a clunky approach to your problem.
It's cleaner to use the Etc module
require 'etc'
result = []
Etc.passwd { |user| result << user.name }
result
Use the following ruby code, which reads and prints the user names from /etc/passwd file.
IO.readlines("/etc/passwd").each do |val|
user = val.split(":").first
puts user
end

Minitest: How to stub/mock the file result of Kernel.open on a URL

I have been trying to use Minitest to test my code (full repo) but am having trouble with one method which downloads a SHA1 hash from a .txt file on a website and returns the value.
Method:
def download_remote_sha1
#log.info('Downloading Elasticsearch SHA1.')
#remote_sha1 = ''
Kernel.open(#verify_url) do |file|
#remote_sha1 = file.read
end
#remote_sha1 = #remote_sha1.split(/\s\s/)[0]
#remote_sha1
end
You can see that I log what is occurring to the command line, create an object to hold my SHA1 value, open the url (e.g. https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.4.2.deb.sha1.txt)
I then split the string so that I only have the SHA1 value.
The problem is that during a test, I want to stub the Kernel.open which uses OpenURI to open the URL. I would like to ensure that I'm not actually reaching out to download any file, but rather I'm just passing the block my own mock IO object testing just that it correctly splits stuff.
I attempted it like the block below but when #remote_sha1 = file.read occurs the file item is nil.
#mock_file = Minitest::Mock.new
#mock_file.expect(:read, 'd377e39343e5cc277104beee349e1578dc50f7f8 elasticsearch-1.4.2.deb')
Kernel.stub :open, #mock_file do
#downloader = ElasticsearchUpdate::Downloader.new(hash, true)
#downloader.download_remote_sha1.must_equal 'd377e39343e5cc277104beee349e1578dc50f7f8'
end
I was working on this question too, but matt figured it out first. To add to what matt posted:
When you write:
Kernel.stub(:open, #mock_file) do
#block code
end
...that means when Kernel.open() is called--in any code, anywhere before the stub() block ends--the return value of Kernel.open() will be #mock_file. However, you never use the return value of Kernel.open() in your code:
Kernel.open(#verify_url) do |f|
#remote_sha1 = f.read
end
If you wanted to use the return value of Kernel.open(), you would have to write:
return_val = Kernel.open(#verify_url) do |f|
#remote_sha1 = f.read
end
#do something with return_val
Therefore, the return value of Kernel.open() is irrelevant in your code--which means the second argument of stub() is irrelevant.
A careful examination of the source code for stub() reveals that stub() takes a third argument--an argument which will be passed to a block specified after the stubbed method call. You, in fact, have specified a block after your stubbed Kernel.open() method call:
stubbed method call -+ +- start of block
| | |
V V V
Kernel.open(#verify_url) do |f|
#remote_sha1 = f.read
end
^
|
end of block
So, in order to pass #mockfile to the block you need to specify it as the third argument to Kernel.stub():
Kernel.stub(:open, 'irrelevant', #mock_file) do
end
Here is a full example for future searchers:
require 'minitest/autorun'
class Dog
def initialize
#verify_url = 'http://www.google.com'
end
def download_remote_sha1
#remote_sha1 = ''
Kernel.open(#verify_url) do |f|
#remote_sha1 = f.read
end
#puts #remote_sha1[0..300]
#remote_sha1 = #remote_sha1.split(" ")[0] #Using a single space for the split() pattern will split on contiguous whitespace.
end
end
#Dog.new.download_remote_sha1
describe 'downloaded file' do
it 'should be an sha1 code' do
#mock_file = Minitest::Mock.new
#mock_file.expect(:read, 'd377e39343e5cc277104beee349e1578dc50f7f8 elasticsearch-1.4.2.deb')
Kernel.stub(:open, 'irrelevant', #mock_file) do
#downloader = Dog.new
#downloader.download_remote_sha1.must_equal 'd377e39343e5cc277104beee349e1578dc50f7f8'
end
end
end
xxx
The second argument to stub is what you want the return value to be for the duration of your test, but the way Kernel.open is used here requires the value it yields to the block to be changed instead.
You can achieve this by providing a third argument. Try changing the call to Kernel.stub to
Kernel.stub :open, true, #mock_file do
#...
Note the extra argument true, so that #mock_file is now the third argument and will be yielded to the block. The actual value of the second argument doesn’t really matter in this case, you might want to use #mock_file there too to more closely correspond to how open behaves.

Resources