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

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.

Related

Chef update certain user's .bashrc

I'm trying to update certain users .bashrc JAVA_HOME environment variable after installing JDK. I get this strange error that I don't understand. Here's the block of code in question.
node['etc']['passwd'].each do |user, data|
only_if {data['uid'] > 9000}
jdk_dir_array = Dir.entries('/usr/java/').select {|entry| File.directory? File.join('/usr/java/',entry) and !(entry =='.' || entry == '..') }
jdk_dir_name = jdk_dir_array.shift
file = Chef::Util::FileEdit.new("#{data['dir']}/.bashrc")
file.search_file_replace( /JAVA_HOME=.*/,"JAVA_HOME=/usr/java/#{jdk_dir_name}")
file.write_file
end
The error I'm getting is this:
NoMethodError
-------------
No resource or method named `only_if' for `Chef::Recipe "install_jdk"'
I don't understand why it thinks "only_if" is a method of the recipe when I declare it inside of the node.each block.
i should point out that if I put this in a ruby_block and hardcode the path to a single user's home directory the code works as expected. I'm trying to update multiple users and that's where I'm stumped.
only_if is a method you use on a resource, not in either a recipe or inside the block of a ruby_block. What you want is something more like like:
node['etc']['passwd'].each do |user, data|
ruby_block "edit #{user} bashrc" do
only_if { data['uid'] > 9000 }
block do
jdk_dir_array = Dir.entries('/usr/java/').select {|entry| File.directory? File.join('/usr/java/',entry) and !(entry =='.' || entry == '..') }
jdk_dir_name = jdk_dir_array.shift
file = Chef::Util::FileEdit.new("#{data['dir']}/.bashrc")
file.search_file_replace( /JAVA_HOME=.*/,"JAVA_HOME=/usr/java/#{jdk_dir_name}")
file.write_file
end
end
I really recommend not doing this though. Check out the line cookbook for a more refined way to approach this, or consider having Chef manage the whole file via a template resource.

Ruby paths with backslash on Mac

Venturing into Ruby lands (learning Ruby). I like it, fun programming language.
Anyhow, I'm trying to build a simple program to delete suffixes from a folder, where user provides the path to the folder in the Mac terminal.
The scenario goes like this:
User runs my program
The program ask user to enter the folder path
User drags and drop the folder into the Mac terminal
Program receives path such as "/Users/zhang/Desktop/test\ folder"
Program goes and renames all files in that folder with suffix such as "image_mdpi.png" to "image.png"
I'm encountering a problem though.
Right now, I'm trying to list the contents of the directory using:
Dir.entries(#directoryPath)
However, it seems Dir.entries doesn't like backslashes '\' in the path. If I use Dir.entries() for a path with backslash, I get an exception saying folder or file doesn't exist.
So my next thought would be to use :
Pathname.new(rawPath)
To let Ruby create a proper path. Unfortunately, even Pathname.new() doesn't like backslash either. My terminal is spitting out
#directoryPath is not dir
This is my source code so far:
# ------------------------------------------------------------------------------
# Renamer.rb
# ------------------------------------------------------------------------------
#
# Program to strip out Android suffixes like _xhdpi, _hpdi, _mdpi and _ldpi
# but only for Mac at the moment.
#
# --------------------------------------------------
# Usage:
# --------------------------------------------------
# 1. User enters a the path to the drawable folder to clean
# 2. program outputs list of files and folder it detects to clean
# 3. program ask user to confirm cleaning
require "Pathname"
#directoryPath = ''
#isCorrectPath = false
# --------------------------------------------------
# Method definitions
# --------------------------------------------------
def ask_for_directory_path
puts "What is the path to the drawable folder you need cleaning?:"
rawPath = gets.chomp.strip
path = Pathname.new("#{rawPath}")
puts "Stored dir path = '#{path}'"
if path.directory?
puts "#directoryPath is dir"
else
puts "#directoryPath is not dir"
end
#directoryPath = path.to_path
end
def confirm_input_correct
print "\n\nIs this correct? [y/N]: "
#isCorrectPath = gets.chomp.strip
end
def reconfirm_input_correct
print "please enter 'y' or 'N': "
#isCorrectPath = gets.strip
end
def output_folder_path
puts "The folder '#{#directoryPath}' contains the following files and folders:"
# Dir.entries doesn't like \
# #directoryPath = #directoryPath.gsub("\\", "")
puts "cleaned path is '#{#directoryPath}'"
begin
puts Dir.entries(#directoryPath)
rescue
puts "\n\nLooks like the path is incorrect:"
puts #directoryPath
end
end
def clean_directory
puts "Cleaning directory now..."
end
puts "Hello, welcome to Renamer commander.\n\n"
ask_for_directory_path
output_folder_path
confirm_input_correct
while #isCorrectPath != 'y' && #isCorrectPath != 'N' do
reconfirm_input_correct
end
if #isCorrectPath == 'y'
clean_directory
else
ask_for_directory_path
end
I went through this learning resource for Ruby two three days ago:
http://rubylearning.com/satishtalim/tutorial.html
I'm also using these resource to figure out what I'm doing wrong:
http://ruby-doc.org/core-2.3.0/Dir.html
https://robm.me.uk/ruby/2014/01/18/pathname.html
Any ideas?
Edit
Well, the current work around(?) is to clean my raw string and delete any backslashes, using new method:
def cleanBackslash(originalString)
return originalString.gsub("\\", "")
end
Then
def ask_for_directory_path
puts "\nWhat is the path to the drawable folder you need cleaning?:"
rawPath = gets.chomp.strip
rawPath = cleanBackslash(rawPath)
...
end
Not the prettiest I guess.
A sample run of the program:
Zhang-computer:$ ruby Renamer.rb
Hello, welcome to Renamer commander.
What is the path to the drawable folder you need cleaning?:
/Users/zhang/Desktop/test\ folder
Stored dir path = '/Users/zhang/Desktop/test folder'
#directoryPath is dir
The folder '/Users/zhang/Desktop/test folder' contains the following files and folders:
cleaned path is '/Users/zhang/Desktop/test folder'
.
..
.DS_Store
file1.txt
file2.txt
file3.txt
Is this correct? [y/N]:
:]
I don't think the problem is with the backslash, but with the whitespace. You don't need to escape it:
Dir.pwd
# => "/home/lbrito/work/snippets/test folder"
Dir.entries(Dir.pwd)
# => ["..", "."]
Try calling Dir.entries without escaping the whitespace.
There is no backslash in the path. The backslash is an escape character displayed by the shell to prevent the space from being interpreted as a separator.
Just like Ruby displays strings containing double quotes by escaping those double quotes.
Okay, first of all, using gets.chomp.strip is probably not a good idea :P
The better and closer solution to what your normally see in a real bash program is to use the Readline library:
i.e.
require "Readline"
...
def ask_for_directory_path
rawPath = String.new
rawPath = Readline.readline("\nWhat is the path to the drawable folder you need cleaning?\n> ", true)
rawPath = rawPath.chomp.strip
rawPath = cleanBackslash(rawPath)
#directoryPath = Pathname.new(rawPath)
end
Using Readline lets you tab complete the folder path. I also needed to clean my backslash from the readline using my own defined:
def cleanBackslash(originalString)
return originalString.gsub("\\", "")
end
After that, the Dir.entries(#directorPath) is able to list all the files and folders in the path, whether the user typed it in manually or drag and drop the folder into the Mac terminal:
Zhang-iMac:Renamer zhang$ ruby Renamer.rb
Hello, welcome to Renamer commander.
What is the path to the drawable folder you need cleaning?
> /Users/zhang/Ruby\ Learning/Renamer/test_folder
The folder '/Users/zhang/Ruby Learning/Renamer/test_folder' contains the following files and folders:
.
..
.DS_Store
drawable
drawable-hdpi
drawable-mdpi
drawable-xhdpi
drawable-xxhdpi
drawable-xxxhdpi
Is this correct? [y/N]: y
Cleaning directory now...
The program is not finish but I think that fixes my problem of the backslash getting in the way.
I don't know how real bash programs are made, but consider this my poor man's bash program lol.
Final program
Check it out:
I feel like a boss now! :D

Reading a file and creating a user in Chef

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

How do I create directory if none exists using File class in Ruby?

I have this statement:
File.open(some_path, 'w+') { |f| f.write(builder.to_html) }
Where
some_path = "somedir/some_subdir/some-file.html"
What I want to happen is, if there is no directory called somedir or some_subdir or both in the path, I want it to automagically create it.
How can I do that?
You can use FileUtils to recursively create parent directories, if they are not already present:
require 'fileutils'
dirname = File.dirname(some_path)
unless File.directory?(dirname)
FileUtils.mkdir_p(dirname)
end
Edit: Here is a solution using the core libraries only (reimplementing the wheel, not recommended)
dirname = File.dirname(some_path)
tokens = dirname.split(/[\/\\]/) # don't forget the backslash for Windows! And to escape both "\" and "/"
1.upto(tokens.size) do |n|
dir = tokens[0...n]
Dir.mkdir(dir) unless Dir.exist?(dir)
end
For those looking for a way to create a directory if it doesn't exist, here's the simple solution:
require 'fileutils'
FileUtils.mkdir_p 'dir_name'
Based on Eureka's comment.
directory_name = "name"
Dir.mkdir(directory_name) unless File.exists?(directory_name)
How about using Pathname?
require 'pathname'
some_path = Pathname("somedir/some_subdir/some-file.html")
some_path.dirname.mkdir_p
some_path.write(builder.to_html)
Based on others answers, nothing happened (didn't work). There was no error, and no directory created.
Here's what I needed to do:
require 'fileutils'
response = FileUtils.mkdir_p('dir_name')
I needed to create a variable to catch the response that FileUtils.mkdir_p('dir_name') sends back... then everything worked like a charm!
Along similar lines (and depending on your structure), this is how we solved where to store screenshots:
In our env setup (env.rb)
screenshotfolder = "./screenshots/#{Time.new.strftime("%Y%m%d%H%M%S")}"
unless File.directory?(screenshotfolder)
FileUtils.mkdir_p(screenshotfolder)
end
Before do
#screenshotfolder = screenshotfolder
...
end
And in our hooks.rb
screenshotName = "#{#screenshotfolder}/failed-#{scenario_object.title.gsub(/\s+/,"_")}-#{Time.new.strftime("%Y%m%d%H%M%S")}_screenshot.png";
#browser.take_screenshot(screenshotName) if scenario.failed?
embed(screenshotName, "image/png", "SCREENSHOT") if scenario.failed?
The top answer's "core library" only solution was incomplete. If you want to only use core libraries, use the following:
target_dir = ""
Dir.glob("/#{File.join("**", "path/to/parent_of_some_dir")}") do |folder|
target_dir = "#{File.expand_path(folder)}/somedir/some_subdir/"
end
# Splits name into pieces
tokens = target_dir.split(/\//)
# Start at '/'
new_dir = '/'
# Iterate over array of directory names
1.upto(tokens.size - 1) do |n|
# Builds directory path one folder at a time from top to bottom
unless n == (tokens.size - 1)
new_dir << "#{tokens[n].to_s}/" # All folders except innermost folder
else
new_dir << "#{tokens[n].to_s}" # Innermost folder
end
# Creates directory as long as it doesn't already exist
Dir.mkdir(new_dir) unless Dir.exist?(new_dir)
end
I needed this solution because FileUtils' dependency gem rmagick prevented my Rails app from deploying on Amazon Web Services since rmagick depends on the package libmagickwand-dev (Ubuntu) / imagemagick (OSX) to work properly.

Ruby unable to use require

This is a newbie question as I am attempting to learn Ruby by myself, so apologies if it sounds like a silly question!
I am reading through the examples of why's (poignant) guide to ruby and am in chapter 4. I typed the code_words Hash into a file called wordlist.rb
I opened another file and typed the first line as require 'wordlist.rb' and the rest of the code as below
#Get evil idea and swap in code
print "Enter your ideas "
idea = gets
code_words.each do |real, code|
idea.gsub!(real, code)
end
#Save the gibberish to a new file
print "File encoded, please enter a name to save the file"
ideas_name = gets.strip
File::open( 'idea-' + ideas_name + '.txt', 'w' ) do |f|
f << idea
end
When I execute this code, it fails with the following error message:
C:/MyCode/MyRubyCode/filecoder.rb:5: undefined local variable or method `code_words' for main:Object (NameError)
I use Windows XP and Ruby version ruby 1.8.6
I know I should be setting something like a ClassPath, but not sure where/how to do so!
Many thanks in advance!
While the top-level of all files are executed in the same context, each file has its own script context for local variables. In other words, each file has its own set of local variables that can be accessed throughout that file, but not in other files.
On the other hand, constants (CodeWords), globals ($code_words) and methods (def code_words) would be accessible across files.
Some solutions:
CodeWords = {:real => "code"}
$code_words = {:real => "code"}
def code_words
{:real => "code"}
end
An OO solution that is definitely too complex for this case:
# first file
class CodeWords
DEFAULT = {:real => "code"}
attr_reader :words
def initialize(words = nil)
#words = words || DEFAULT
end
end
# second file
print "Enter your ideas "
idea = gets
code_words = CodeWords.new
code_words.words.each do |real, code|
idea.gsub!(real, code)
end
#Save the gibberish to a new file
print "File encoded, please enter a name to save the file"
ideas_name = gets.strip
File::open( 'idea-' + ideas_name + '.txt', 'w' ) do |f|
f << idea
end
I think the problem might be that the require executes the code in another context, so the runtime variable is no longer available after the require.
What you could try is making it a constant:
CodeWords = { :real => 'code' }
That will be available everywhere.
Here is some background on variable scopes etc.
I was just looking at the same example and was having the same problem.
What I did was change the variable name in both files from code_words to $code_words .
This would make it a global variable and thus accesible by both files right?
My question is: wouldn't this be a simpler solution than making it a constant and having to write CodeWords = { :real => 'code' } or is there a reason not to do it ?
A simpler way would be to use the Marshal.dump feature to save the code words.
# Save to File
code_words = {
'starmonkeys' => 'Phil and Pete, those prickly chancellors of the New Reich',
'catapult' => 'chucky go-go', 'firebomb' => 'Heat-Assisted Living',
'Nigeria' => "Ny and Jerry's Dry Cleaning (with Donuts)",
'Put the kabosh on' => 'Put the cable box on'
}
# Serialize
f = File.open('codewords','w')
Marshal.dump(code_words, f)
f.close
Now at the beginning of your file you would put this:
# Load the Serialized Data
code_words = Marshal.load(File.open('codewords','r'))
Here's the easy way to make sure you can always include a file that's in the same directory as your app, put this before the require statement
$:.unshift File.dirname(__FILE__)
$: is the global variable representing the "CLASSPATH"

Resources