Reading a file and creating a user in Chef - ruby

I have a list of IP address along with me. In front of those IP I have a username. What I am trying to do is make Chef read the file having IP and username and once it encounter the IP, it should create a user of that name.
But when I do I get a user but the name of the user comes out to be a number.
Here is my recipe
File.open("/tmp/users.txt", "r") do |file|
file.readlines.each_with_index do |ip,user|
if ip = node[:ipaddress]
user ip[user] do
action :create
supports :manage_home => true
comment 'Test User'
home '/home/ip[user]'
shell '/bin/bash'
password 'password'
end
end
end
my users.txt file
231.27.59.232, test1
272.27.59.15, tes2
985.54.25.22, test3
Now when I run the recipe this is what I get
Recipe: repo_update::users
* cookbook_file[/tmp/users.txt] action create (up to date)
* user[1] action create
- create user 1
* user[7] action create
- create user 7
* user[2] action create
- create user 2
Please tell me what is wrong here.

Lots of problem here... The answer of Tejay is the way to go, I'll just try to explain why your code don't work and how to fix it so it could be of some use later :)
File.open("/tmp/users.txt", "r") do |file|
file.readlines.each_with_index do |ip,user|
puts "values are #{ip} and #{user}"
end
end
Gives:
values are 231.27.59.232, test1
and 0
values are 272.27.59.15, tes2
and 1
values are 985.54.25.22, test3
and 2
each_with_index won't split magically your line into two part, it will just assign the last parameter the actual index in the iteration.
A fixed version of your code would be:
File.open("/tmp/users.txt", "r") do |file|
file.readlines.each do |line| # just iterate and get line
ip,myuser=line.gsub("\n",'').split(',') # set ip and myuser variable with values comma separated, using myuser to avoid conflict with the resource name. Using gsub to remove traling carriage return in user name
if ip == node[:ipaddress] # test equality, a single = will assign ip a value and always be true.
user myuser do # create the user using the variable, no need to interpolate here
action :create
supports :manage_home => true
comment 'Test User'
home "/home/#{myuser}" # use interpolation here inside double quotes (won't work in single quotes)
shell '/bin/bash'
password 'password'
end
end
end
end

The problem is this line:
user ip[user] do
You are calling the [] method on the ip string. Furthermore, you're going to get a name collision between the resource user and the block variable. Finally, you are giving each user the home of '/home/ip[user]'. You need to put the string in "'s and wrap the variable in #{ and } Try this:
File.open("/tmp/users.txt", "r") do |file|
file.readlines.each do |line|
entries = line.split(',')
ip = entries[0].strip
username = entries[1].strip
if ip = node[:ipaddress]
user username do
action :create
supports :manage_home => true
comment 'Test User'
home "/home/#{username}"
shell '/bin/bash'
password 'password'
end
end
end
Also, reading this all from a file is a very non cheffy thing to do. Either use a databag or a hash stored in an environment variable, which also saves you from needing to loop at all:
userhash = node['my_users'][node['ipadddress']]
user userhash['username']
action :create
supports :manage_home => true
comment 'test user'
home userhash['home'] || "/home/#{userhash['username']"
shell userhash['shell'] || '/bin/bash'
password userhash['password'] || 'password'
end

Related

How do you update an environment variable using Ruby and Chef?

I know how to set the variables for both user and machine.
The problem arises when I try to add to the PATH. Currently my code will overwrite what is in the PATH.
execute 'set java_home2' do
command "setx -m PATH2 \"D:\\Home"
*only_if {"PATH2" == " "}*
end
This currently ensures that the PATH will only run if there is no PATH. When the only_if is removed the problem of overwriting arises.
EDIT:
I am now able to modify the system variable but cannot work out how to do the same with the user variables
env 'path addition' do
key_name "PATH"
value (ENV["PATH"] + ";D:\\Home\\Apps\\variable")
:modify
end
From the question, it looks like you are trying to add PATH on windows server. In that case you can use windows cookbook resource called windows_path for such operation:
windows_path 'C:\Sysinternals' do
action :add
end
https://github.com/chef-cookbooks/windows
https://supermarket.chef.io/cookbooks/windows
I can't speak for specifics in chef, but in ruby, you can access environment variables with the ENV hash. So for PATH, you could do the following:
ENV["PATH"] = ENV["PATH"].split(":").push("/my/new/path").join(":")
That will update your PATH for the duration of the program's execution. Keep in mind that:
This will only update PATH for your ruby script, and only temporarily. Permanently changing your PATH is more complicated and dependent on OS.
This code assumes you're using linux. In windows, the PATH delimiter is ; instead of :, so you should update the code accordingly.
I found the answer:
#Append notepad to user PATH variable
registry_key "HKEY_CURRENT_USER\\Environment" do
$path_name = ""
subkey_array = registry_get_values("HKEY_CURRENT_USER\\Environment", :x86_64)
subkey_array.each{ |val|
case val[:name].include?("PATH")
when true
$path_name = val[:data]
print "\n The User PATH is: #{$path_name}"
break
when false
print ':'
end
}
values [{
:name => "PATH",
:type => :string,
:data => "#{$path_name};D:\\Home\\Apps\\Notepad++\\Notepad++"
}]
action :create
#add a guard to prevent duplicates
not_if {
$path_name.include?("D:\\Home\\Apps\\Notepad++\\Notepad++")
}
end
This code when ran from the CMD line will print the current User PATH variables, then it will append D:/Home/Apps/Notepad++/Notepad++ IF it is not currently in the PATH. If it already exists then this will be skipped.

`[]': no implicit conversion of Symbol into Integer (TypeError) for a simple ruby Hash

In my ruby code, I define a new hash:
options = {
host: 'localhost',
user: nil,
file: nil,
}
I then parse the hash using the OptionsParser
OptionParser.new do |opts|
opts.banner = 'Usage: ruby-remote-tailer.rb [options]'
opts.on('-h', '--host', 'The host to run ssh session with') do |host|
options[:host] = "#{host}"
end
opts.on('-h', '--user', 'The user account that will run the session') do |user|
options[:user] = "#{user}"
end
opts.on('-f', '--file', 'The file to run the tail on') do |file|
options[:file] = "#{file}"
end
end
And run:
options = ARGV.parse!
puts options[:host]
The last puts line results in an error no implicit conversion of Symbol into Integer (TypeError). I know that the input I put in is correct as doing p options works. Any ideas on how to fix this?
(Note I would prefer not to use a .each loop as suggested in other answers to get the single values I need).
Thanks.
You are not using OptionParser correctly in several ways.
When defining the options you have to provide a placeholder for the actual value of the option. If you do not, it will be interpreted as a switch, returning either true or false depending on whether the switch was set.
opts.on('-h', '--host HOST', 'The host to run ssh session with') do |host|
options[:host] = "#{host}"
end
Another mistake is that you defined -h for both the host and the user option. You better define a different letter to each of them, you probably intended to have -u for the user anyway.
But the main problem, the one causing the error is that you treat the return value of the #parse! method as if it would return the parsed values. But the return value is actually the remainder of the ARGV array that was not matched by the parser. And because you try to access it like a Hash by asking for an element by Symbol, it complains because array elements are accessed only by Integer values. To fix it, just keep the options reference from before and don't assign the return value to it.
ARGV.parse!
From here on, I will give you some further criticism, but the things I will recommend should not be the reason of any errors:
Additionally you might skip the default nil values you provided in the Hash in the beginning, if you ask a Hash for an undefined key, you will provide you with a nil anyway.
options = {
host: 'localhost'
}
I'd say calling #parse! on the the command line argument array ARGV, while it seems to be an option to do so, is not very obvious. I'd recommend saving a reference to the parser to a variable and call the method on the parser.
parser = OptionParser.new do |opts|
…
end
parser.parse!(ARGV)
If you want you can even call it without an argument, it will use ARGV by default. But this would again make your code harder to understand in my opinion.
And you can also get rid of the string conversion of the values through interpolation. At least when you are actually getting your values from the command line arguments array ARGV you can be quite sure that all elements will be String objects. However if you intend to feed the parser with other arrays which are not entirely built with string elements, you should keep the conversion.
opts.on('-h', '--host HOST', 'The host to run ssh session with') do |host|
options[:host] = host
end
Also please note that there is a very, very widespread convention that you use an of exactly two spaces for each level of indentation in Ruby code. In your examples you use four and eight spaces, which a lot of Rubyists would dislike very much. See the The Ruby styleguide for more information.
Here is a fixed-up version of your code:
#!/usr/bin/env ruby
require 'optionparser'
options = {
host: 'localhost'
}
parser = OptionParser.new do |opts|
opts.banner = 'Usage: ruby-remote-tailer.rb [options]'
opts.on('-h', '--host HOST', 'The host to run ssh session with') do |host|
options[:host] = host
end
opts.on('-u', '--user USER', 'The user account that will run the session') do |user|
options[:user] = user
end
opts.on('-f', '--file FILE', 'The file to run the tail on') do |file|
options[:file] = file
end
end
parser.parse!(ARGV)
puts options[:host]

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

How can I use arrays to loop through a collective set of variables I read from a file?

I'm trying to make a loop so that during each loop it will take the name and password variables from the file and enter where called.
array = []
File.open("file_users.txt") do |login|
login.each do |item|
name, password = item.chomp.split(',')
array << "#{name}" "#{password}"
browser.goto "https://website.com"
browser.text_field(:id => "user_name").set "#{name}"
browser.text_field(:id => "user_password").set "#{password}"
browser.button(:id => "login").click
sleep(5)
browser.close
end
end
I think the main issue is trying to make the loop call the next set of email and password after using the previous ones.
*edited:
The result I'm trying to get is to pull text from a file, then give it a "name" and "password" value, then have it be entered into the text field on the browser when called...
for example, the text file looks like:
jerryname
jerrypassword
careyname
careypassword
britneyname
britneypassword
The result I want is:
#=> loop 1
puts jerrynamme
puts jerrypassword
#=> logs in
#=> waits, then closes browser
#=> loop 2
puts careyname
puts careypassword
#=> logs in
#=> waits, then closes browser... and so on.
The result I get is the browser opening and the name first being entered then the code just stops....the browser doesn't close, it just remains still.
You say:
the text file looks like:
jerryname jerrypassword careyname careypassword britneyname britneypassword
If the file_users.txt file is as you describe, one line with a list of names and password pairs separated by spaces, the line
File.open("file_users.txt") do |login|
will return the entire contents of the file in first login value. The code
name, password = item.chomp.split(',')
will assign the entire contents of the file to name and set password to nil
You need to build a new "file_users.txt" file in the following form (note the commas and line breaks):
jerryname,jerrypassword
careyname,careypassword
britneyname,britneypassword
Then your code will be closer to working.
PS, this line doesn't seem to be used for anything and can be removed.
array << "#{name}" "#{password}"

Ruby script pucking on new line in a file

If I only have one host in the file the scripts does as intended. As soon as I add another address in the file I get this error. I understand that it does not like the new line character at the end of the first host in the file, how is this problem alleviated? I'm basically looking for the script to run down the hostfile, and for every address or host-name in the file, run the session.
'initialize': newline at the end of hostname
File.read('hostfile').each_line do |hostname|
session = Net::SSH.start(hostname, #username, :password => #password, :encryption => 'aes256-cbc', :host_key => 'ssh-rsa')
cmd_session = Net::SSH::Telnet.new('Session' => session)
cmd_session.cmd("en\r#{#enable}")
cmd_session.cmd('terminal pager 0')
cmd_session.cmd('show threat-detection statistics') { |c| print c }
cmd_session.close
end
session = Net::SSH.start(hostname.strip, #username.....
Should get you going. For further reference see the docs

Resources