Well, let's start by saying I'm a chef noob and I am trying to hash this code out.
I am in a full mac shop. I am using Chef to automate system wide changes. As I'm new, I'm rolling it out onto our Mac AV systems.
Basically, there is a folder on a file server that has MAC SCREEN SAVERS directory. I copy the server directory locally to the MAC OS X /User/user_name/Pictures directory.
So, this is what I got in chef:
local_folder_modified = File.mtime("~/Pictures/SCREEN SAVER NEW MACS")
server_folder_modified = File.mtime("/Volumes/SERVER/SCREEN\ SAVER\ NEW\ MACS/")
if server_folder_modified != local_folder_modified
# file has changed
then
require 'fileutils'
FileUtils.cd('server_folder_modified') do
FileUtils.rm('local_folder_modified/*')
FileUtils.cp_r './*', 'local_folder_modified'
Else
end
end
Anyways, I can't figure how to set the '~' to be the running user of this recipe. So, if Comp_A has user Jim_Beam and Comp_B has user Jack_Daniels, I don't want to set the code to be:
ENV[HOME] = /user/jimbeam
As it won't work on Jack_Daniels. Right?
I've read that file.expand will work, or ENV, but I am really unsure what will be the best code to say
"hey, I want the current user that will need this screen saver - so set the environment as a variable so it works across different nodes".
Anyways, thanks for your help. I hope I am making sense!
Yes, use File.expand. It will expand the tilde ~ to be the the home directory of the user running this cookbook. Alternatively, you could do:
"#{ENV['HOME']}/Pictures/SCREEN SAVER NEW MACS"
Like the previous comment, this is not chef DSL or ruby code. What is the source of this code or is it just pseudo-code to ask the question?
Also, chef-client is not frequently run as multiple users in a chef server deployment. It's usually run in a sudo context. So maybe you are referring to a --local-mode or chef-zero application?
You may want to use file stat of /dev/console to get the current user. Depending how you are running the chef-client Env[‘Home’] might not give you want you want. Try this:
console_user = Etc.getpwuid(::File.stat("/dev/console").uid).name
home_dir = ::File.join(‘Users’, console_user)
You can see that the chef launchd provider uses this method to determine the console user
Also there is a much simpler way to do what you are trying to accomplish with the remote_file resource. Try this:
console_user = Etc.getpwuid(::File.stat("/dev/console").uid).name
home_dir = ::File.join(‘Users’, console_user)
pics = ::File.join("#{home_dir}/Pictures/")
server_base_url = "https://PLACE_WHERE_STORE/Wallpapers")
[
‘Pic1’,
‘Pic2’,
].each do |pic|
remote_file ::File.join(pics, pic) do
source “#{server_base_url}/#{pic}”
owner console_user
group console_user
mode '0755'
action :create
end
end
For added security you should also include checksum
Related
I have a chef recipe that looks something like this:
package 'build-essential' do
action :install
end
cmd = Mixlib::ShellOut.new("gcc -dumpversion")
cmd.run_command
gcc_version = cmd.stdout.strip()
If I execute the recipe on a system where gcc is installed, the recipe runs fine without errors. However, if I run the recipe on a system which doesn't have gcc install I get the error 'no such file or directory - gcc'.
I came to know about the chef two-phases stuff when trying to find a solution to my problem. I was expecting the package installation to satisfy the gcc requirement. How can I tell chef that this requirement will be satisfied later and not throw an error at compile time?
I tried the following, but the attribute does not get updated.
Chef::Resource::RubyBlock.send(:include, Chef::Mixin::ShellOut)
ruby_block "gcc_version" do
block do
s = shell_out("gcc -dumpversion")
node.default['gcc_version'] = s.stdout.strip()
end
end
echo "echo #{node[:gcc_version]}" do
command "echo #{node[:gcc_version]}"
end
Any help is appreciated. Thanks.
So okay, a few issues here. First, forget that Chef::Resource::whatever.send(:include trick. Never do it, literally never. In this case, the ShellOut mixin is already available in all the places anyway.
Next, and more importantly, you've still got a two-pass confusion issue. See https://coderanger.net/two-pass/ for details but basically the strings in that echo resource (I assume that said execute originally and you messed up the coping?) get interpolated at compile time. You haven't said what you are trying to do, but you probably need to use the lazy{} helper method.
And last, don't store things in node attributes like that, it's super brittle and hard to work with.
The challenge prompt is above, and my latest attempt is below. The directories and files are created as expected, and the read-out after executing chef-apply multipleCopies.rb tells me the files are linked, but when I update any one of the files, the others do not follow suit. Any ideas? Here is my code:
for x in 1..3
directory "multipleCopy#{x}" do
mode '0755'
action :create
end
end
file "multipleCopy1/secret.txt" do
mode '0755'
action :create
end
for x in 2..3
link "multipleCopy#{x}/secret.txt" do
to "multipleCopy1/secret.txt"
link_type :hard
subscribes :reload, "multipleCopy1/secret.txt", :immediately
end
end
Note: For less headache, I am testing the recipe locally before uploading to the ubuntu server referenced in the prompt, which is why my file paths are different and why I have not yet included the ownership properties.
So a file hard link doesn't seem to be what the question is going for (though I would say your solution is maybe better since this is really not what Chef is for, more on that later). Instead they seem to want you to have three actually different files, but sync the contents.
So first the easy parts, creating the directories and the empty initial files. It's rare to see those for loops used in Ruby code, though it is syntactically valid:
3.times do |n|
directory "/var/save/multipleCopy#{n+1}" do
owner "ubuntu"
group "root"
mode "755"
end
file "/var/save/multipleCopy#{n+1}/secret.txt" do
owner "root
group "root"
mode "755"
end
end
But that doesn't implement the hard part of sync'ing the files. For that we need to first analyze the mtimes on the files and use the most recent as the file content to set.
latest_file = 3.times.sort_by { |n| ::File.mtime("/var/save/multipleCopy#{n+1}/secret.txt") rescue 0 }
latest_content = ::File.read("/var/save/multipleCopy#{latest_file+1}/secret.txt") rescue nil
and then in the file resource:
file "/var/save/multipleCopy#{n+1}/secret.txt" do
owner "root
group "root"
mode "755"
content latest_content
end
As for this not being a good use of Chef: Chef is about writing code which asserts the desired state of the machine. In the case of files like this, rather than doing this kind of funky stuff to check if a file has been edited, you would just say that Chef owns the file content for all three and if you want to update it, you do it via your cookbook (and then usually use a template or cookbook_file resource).
I am doing a demo command line project in Ruby. The structure is like this:
/ROOT_DIR
init.rb
/SCRIPT_DIR
(other scripts and files)
I want users to only go into the application using init.rb, but as it stands, anyone can go into the sub-folder and call other ruby scripts directly.
Questions:
What ways can above scenario be prevented?
If I was to use directory permissions, would it get reset when running the code from a Windows machine to on Linux machine?
Is there anything that can be included in Ruby files itself to prevent it from being directly called from OS command line?
You can't do this with file permissions, since the user needs to read the files; removing the read permission means you can't include it either. Removing the execute permission is useful to signal that these file aren't intended to be executed, but won't prevent people from typing ruby incl.rb.
The easiest way is probably to set a global variable in the init.rb script:
#!/usr/bin/env ruby
FROM_INIT = true
require './incl.rb'
puts 'This is init!'
And then check if this variable is defined in the included incl.rb file:
unless defined? FROM_INIT
puts 'Must be called from init.rb'
exit 0
end
puts 'This is incl!'
A second method might be checking the value of $PROGRAM_NAME in incl.rb; this stores the current program name (like argv[0] in many other languages):
unless $PROGRAM_NAME.end_with? 'init.rb'
puts 'Must be called from init.rb'
exit 0
end
I don't recommend this though, as it's not very future-proof; what if you want to rename init.rb or make a second script?
I have a simple chef cookbook and all it does is it sets the MOTD on a CentOS machine. It takes the content of the /tmp/mymotd.txt and turns it into the MOTD.
I also have a simple ruby script (a full-fledged ruby script) that simply reads the text from the web-server and puts in into the /tmp/mymotd.txt.
My questions are:
how do I run this ruby script from within the cookbook?
how do I pass some parameters to the script (e.g. the address of the web-server)
Thanks a lot beforehand.
Ad 1.
You can use libraries directory in scripts to place there your ruby script and declare it in a module. Example:
# includes
module MODULE_NAME
# here some code using your script
# Example function
def example_function (text)
# some code
end
end
You can use then
include MODULE_NAME
in your recipe to import those functions and just use it like
example_function(something)
What's good - you can use there also Chef functions and resources.
IMPORTANT INFO: Just remember that Chef has 2 compilation phases. First will be all of Ruby code, second all of Chef resources. This means, that you have to remember priority of code. I won't write here more info about it, since you haven't asked for this, but if you want, you can find it here.
Ad 2.
You can do this in several ways, but it seems to me, that the best option for you would be to use environments. You can find more info in here. Basically, you can set up environment for script before it will run - this way you can define some variables you would use later.
Hope this helps.
I'd like to have one Vagrantfile for multiple different OS types (e.g. windows and linux). I'm currently attempting to do something like this:
...
if config.vm.guest.to_s == "windows"
# ... do something windows specific
else
# ... do something linux specific
end
However, config.vm.guest returns an Object instance, not a string or a symbol, even though it was set in the base box Vagrant file (i.e. config.vm.guest = :windows).
How can I do this?
I'll bet you're trying to do this in a ~/.vagrant.d/Vagrantfile. The problem is that ~/.vagrant.d/Vagrantfile is run before the Vagrantfile in your current directory.
So it cannot know anything about the configuration before hand.