remote_file block copies only one specific file.
Is there any possibility in Chef to copy all files from specific folder on ftp?
my current code is quite weird as for me:
require 'net/ftp'
ftp = Net::FTP::new("server")
ftp.login("user", "password")
ftp.chdir("/folder")
fileList = ftp.nlst('*.jar')
fileList.each do |file|
remote_file "C:\\Temp\\" + file do
source "ftp://user:password#server/folder/" + file
action :create_if_missing
end
end
ftp.close
If your solution works, why not wrap it in an LWRP? They are quite easy to create, and would tuck away the implementation in its own file. This is what I would do.
See: http://docs.opscode.com/chef/lwrps_custom.html
And for a real-life - easy to understand - example, see:
https://github.com/opscode-cookbooks/ssh_known_hosts/blob/master/providers/entry.rb
https://github.com/opscode-cookbooks/ssh_known_hosts/blob/master/resources/entry.rb
Related
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 would like to copy http://seapower/spring.txt and http://seapower/has_sprung.txt and append second one to the first one in a new file named src_filepath.txt:
remote_file 'src_filepath.txt' do
source 'http://seapower/spring.txt', 'http://seapower/has_sprung.txt'
checksum node['nginx']['foo123']['checksum']
owner 'root'
group 'root'
mode '0755'
end
It doesn't work and just copy the first file to src_filepath.txt
Something like this is probably a good place to start and then tweak however you like:
cache1 = "#{Chef::Config[:file_cache_path]}/content1"
cache2 = "#{Chef::Config[:file_cache_path]}/content2"
# this will not redownload if cache1 exists and has not been updated
remote_file cache1 do
source "http://source.url/content1"
end
# this will not redownload if cache1 exists and has not been updated
remote_file cache2 do
source "http://source.url/content2"
end
# this will not update the file if the contents has not changed
file "/my/combined/file" do
content lazy { IO.read(cache1) + IO.read(cache2) }
end
This is not something Chef supports directly. You could use multiple remote_file resources and either a ruby_block or execute plus cat to implement the concat.
remote_file does not support concatenation, so you would not be able to implement this using that resource directly, however you could piece together the desired result using the file resource and Net::HTTP like so:
file_path = '/path/to/your_whole_file'
unless File.exist?(file_path) &&
Digest::SHA256.hexdigest(File.read(file_path)) == 'your_file_checksum'
file file_path do
content(
Net::HTTP.get(URI('http://source.url/content1')) +
Net::HTTP.get(URI('http://source.url/content2'))
)
owner 'root'
group 'root'
mode '0755'
end
end
The reason for the Digest::SHA256 call at the beginning is to prevent Chef from trying to download both files during every Chef run. Note that you may have to require the net/http and digest gems at the top of your recipe for this to work.
Also, because it's against best practices to put Ruby code directly into your recipes, you may want to wrap the above code in a simple custom resource.
My test suite has a cucumber front end with a ruby backend, running the latest version of watir-webdriver and its dependencies atop the latest version of OSX. My cucumber environment is setup to execute in Firefox.
The export feature of our app creates a zip file but to test the import feature, I need the contents of the zip file.
My actual test needs to unpack that zip file and select the individual files in it for use in testing the import feature of our web application.
Can anyone point me to a reference that can help me figure out how to write that?
Based off my experience, you download this file the same way that a normal user might. So first off, you just click the download button or whatever and then can access the file wherever it is and check out its contents.
Assuming the downloads just go to your Downloads folder by default, there is some simple code you can use to select the most recently downloaded item:
fn = Dir.glob("~/Downloads/*.zip").max { |a,b| File.ctime(a) <=> File.ctime(b)}
Then just use the unzip shell command to unzip the file. No reason to add another gem into the mix when you can just use generic shell commands.
`unzip #{fn}`
Then, you'd use Dir.glob again to get the filenames of everything inside the unzipped files folder. Assuming the file was named "thing.zip", you do this:
files = Dir.glob("~/Downloads/thing/*")
If you want to files to be downloaded directly to your project folder, you can try this. This also prevents the popup from asking you if you really want to save the file which is handy. I think this still works but haven't used it in some time. The above stuff works for sure though.
profile = Selenium::WebDriver::Firefox::Profile.new
download_dir = Dir.pwd + "/test_downloads"
profile['browser.download.dir'] = download_dir
profile['browser.helperApps.neverAsk.saveToDisk'] = "application/zip"
b = Watir::Browser.new. :firefox, :profile => profile
I ended up adding the rubyzip gem at https://github.com/rubyzip/rubyzip
the solution is on that link but i modified mine a little bit. I added the following to my common.rb file. see below:
require 'Zip'
def unpack_zip
test_home='/Users/yournamegoeshere/SRC/watir_testing/project files'
sleep(5) #<--manually making time to hit the save download dialog
zip_file_paths = []
Find.find(test_home) do |path|
zip_file_paths << path if path =~ /.*\.zip$/
end
file_name=zip_file_paths[0]
Zip::File.open(file_name) do |zip_file|
# Handle entries one by one
zip_file.each do |entry|
# Extract to file/directory/symlink
puts "Extracting #{entry.name}"
entry.extract(test_home + "/" + entry.name)
# Read into memory
content = entry.get_input_stream.read
end
# Find specific entry
entry = zip_file.glob('*.csv').first
puts entry.get_input_stream.read
end
end
This solution works great!
I experienced some pain when deal with the "path" in developing a gem.
Here is the folder structure
production codes:
lib/gem_name/foo/templates/some_template.erb
lib/gem_name/foo/bar.rb
test codes:
test/gem_name/foo/bar_test.rb
In bar.rb, I read the template by:
File.read("templates/some_template.erb") => Errno::ENOENT: No such file or directory
when I run the unit test in bar_test.rb in RubyMine, it gives me the error:
Errno::ENOENT: No such file or directory - D:/.../test/gem_name/foo/templates/some_template.erb
Obviously the test in the path is wrong.
My question are,
How to deal with this issues?
What is the best practice to handle
such path problem while developing a gem?
Edit:
Since __FILE__ only returns the path of the file it is written, currently I define fname (see #ckruse's answer) like functions in every file I need it. It works but it is not elegant. Perhaps someone will have a better solution than mine on this. If so, please let me know.:)
You can always refer to the directory of the current file by File.dirname(__FILE__) and then use relative pathes, e.g.:
fname = File.dirname(__FILE__) + "/templates/some_template.rb"
File.read(fname)
Edit: To shortcut this just write a method:
def fname(file)
File.dirname(__FILE__) + "/../til/../project/../root/../" + file
end
Edit 3: You also could use caller to always refer to the directory of the calling file:
def fname(file)
path, _ = caller.first.split(':', 2)
File.dirname(path) + "/" + file
end
I'm trying to use remote_file to cache a local copy of a large package on a Windows share. How is this done?
I can't get it to work with a drive-letter-based path, a UNC-based path, or a file: URL.
Don't have windows to test, but something like this should work:
require 'fileutils'
remote_path = '...'
local_path = '...'
ruby "cache-#{remote_path}" do
block { FileUtils.copy_file(remote_path, local_path) }
not_if { File.exists?(local_path) }
end
I worked out a trick that I think is pretty neat. I created the following definition (and put it in definitions/default.rb):
define :file_from_network, :action => :create do
myPath = (params[:path] || params[:name])
mySource = params[:source]
if File.exist?(mySource)
file myPath do
action params[:action]
content File.open(mySource) {|io| io.read}
end
else
Chef::Log.error("File #{mySource} not found!")
end
end
Definitions do not work quite the same way resources do, but this was easy to implement and does what I need it to do. The in-memory read makes it impractical for huge files, of course, but it allows Chef to check that the content is different before triggering an action on the file resource.