File names in Chef InSpec - ruby

In Ruby, * is used to represent the name of a file.
For example, /home/user/*.rb will return all files ending with .rb. I want to do something similar in Chef InSpec.
For example:
describe file ('home/user/*') do
it {should_not exist }
end
It should give me all the files inside /home/user directory and check that no file exists inside this directory. In other words, I want to check if this directory is empty or not in Chef.
How can I do that?

* for globs is mostly a shell feature, and as you might expect the file resource doesn't support them. Use a command resource instead:
describe command('ls /home/user') do
its(:stdout) { is_expected.to eq "\n" }
end

Here's an alternate approach that tests for the existence of the directory, and if it exists it uses Ruby to test for files within it. It also uses the expect syntax, which allows for a custom error message.
control 'Test for an empty dir' do
describe directory('/hey') do
it 'This directory should exist and be a directory.' do
expect(subject).to(exist)
expect(subject).to(be_directory)
end
end
if (File.exist?('/hey'))
Array(Dir["/hey/*"]).each do |bad_file|
describe bad_file do
it 'This file should not be here.' do
expect(bad_file).to(be_nil)
end
end
end
end
end
If there are files present, the resulting error message is informative:
× Test for an empty dir: Directory /hey (2 failed)
✔ Directory /hey This directory should exist and be a directory.
× /hey/test2.png This file should not be here.
expected: nil
got: "/hey/test2.png"
× /hey/test.png This file should not be here.
expected: nil
got: "/hey/test.png"

Related

Keeping files updated with a Chef recipe

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).

Trouble Creating Directories with mkdir

New to Ruby, probably something silly
Trying to make a directory in order to store files in it. Here's my code to do so
def generateParsedEmailFile
apath = File.expand_path($textFile)
filepath = Pathname.new(apath + '/' + #subject + ' ' + #date)
if filepath.exist?
filepath = Pathname.new(filepath+ '.1')
end
directory = Dir.mkdir (filepath)
Dir.chdir directory
emailText = File.new("emailtext.txt", "w+")
emailText.write(self.generateText)
emailText.close
for attachment in #attachments
self.generateAttachment(attachment,directory)
end
end
Here's the error that I get
My-Name-MacBook-2:emails myname$ ruby etext.rb email4.txt
etext.rb:196:in `mkdir': Not a directory - /Users/anthonydreessen/Developer/Ruby/emails/email4.txt/Re: Make it Brief Report Wed 8 May 2013 (Errno::ENOTDIR)
from etext.rb:196:in `generateParsedEmailFile'
from etext.rb:235:in `<main>'
I was able to recreate the error - it looks like email4.txt is a regular file, not a directory, so you can't use it as part of your directory path.
If you switched to mkdir_p and get the same error, perhaps one of the parents named in '/Users/anthonydreessen/Developer/Ruby/emails/email4.txt/Re: Make it Brief Report Wed 8 May 2013' already exists as a regular file and can't be treated like a directory. Probably that last one named email.txt
You've got the right idea, but should be more specific about the files you're opening. Changing the current working directory is really messy as it changes it across the entire process and could screw up other parts of your application.
require 'fileutils'
def generate_parsed_email_file(text_file)
path = File.expand_path("#{#subject} #{date}", text_file)
while (File.exist?(path))
path.sub!(/(\.\d+)?$/) do |m|
".#{m[1].to_i + 1}"
end
end
directory = File.dirname(path)
unless (File.exist?(directory))
FileUtils.mkdir_p(directory)
end
File.open(path, "w+") do |email|
emailText.write(self.generateText)
end
#attachments.each do |attachment|
self.generateAttachment(attachment, directory)
end
end
I've taken the liberty of making this example significantly more Ruby-like:
Using mixed-case names in methods is highly irregular, and global variables are frowned on.
It's extremely rare to see for used, each is much more flexible.
The File.open method yields to a block if the file could be opened, and closes automatically when the block is done.
The ".1" part has been extended to keep looping until it finds an un-used name.
FileUtils is employed to makes sure the complete path is created.
The global variable has been converted to an argument.

Referencing file location in RSpec Rake task vs. rspec runner

I have this directory structure:
project_dir
spec
person
person_invalid_address_examples.yaml
person_spec.rb
rakefile.rb
The person_spec.rb has this piece of code in it:
describe "Create person tests"
...
context "Person with invalid address" do
invalid_address_examples = []
File.open("person_invalid_address_examples.yaml", "r") do |file|
invalid_address_examples = YAML::load(file)
end
invalid_address_examples.each do |example|
it "Does not allow to create person with #{example[:description]}" do
person.address = example[:value]
result = person.create
result.should_not be_success
end
end
end
...
end
Now when I run from the person directory rspec person_spec.rb everything works as expected. But if I run RSpec rake task from the rakefile I get No such file or directory error... The problem is obviously present also the other way round - if I configure filename with path relative to the rakefile location then RSpec rake task works fine but I get No such file or directory error from the rspec runner.. Is there a way to configure filename with path so that it is working for the RSpec rake task and Rspec runner at the same time?
Whether your File.open works depends on the load path -- ruby looks up that relative path in the dirs in the current load path. You can look at the load path in the special $: variable.
Try looking at the value of this variable compared between both methods of executing the spec, and see how/if it differs.
It may be that the current working directory (basically, what directory you executed the command from, shows up in a list of paths as .) is on the load path, and the current working directory ends up different in your two different methods of running the spec.
Where is your yaml file located? Is your YAML file used only for testing, can you put it wherever you want?
You have various options, but they all depend on supplying either an absolute path, or a relative path that will always be on the load path.
Move the yml file to somewhere that is always on the load path. Your spec dir is probably already on the load path. You can put your yml in ./spec/example.yml. Or put your yml in a subdir, but reference that subdir in the open too -- spec/support/data/examples.yml, and then open "data/examples.yml" (starting from a dir on the load path, data/examples.yml will resolve).
Or, ignoring the load path, you could use the special __FILE__ variable to construct the complete path to your yml file, from it's relative location to the current file. __FILE__ is the file path of the source file where the code referencing it is.
Or, probably better than 2, you could add a directory of example data to the load path in your spec_helper.rb, by constructing a path with __FILE__, and then adding it to the $: variable. For instance, a example_data directory.
Probably #1 is sufficient for your needs though. Put the yml inside your spec directory -- or put it in a subdir of your spec directory, but include that subdir in the open argument.
It's because of
File.open("person_invalid_address_examples.yaml", "r")
It opens the file where the rspec is running.
In your case you should define file more apparently something like this:
file_path = File.expand_path("person_invalid_address_examples.yaml", File.dirname(__FILE__))
File.open(file_path, "r")

Using yaml files within gems

I'm just working on my first gem (pretty new to ruby as well), entire code so far is here;
https://github.com/mikeyhogarth/tablecloth
One thing I've tried to do is to create a yaml file which the gem can access as a lookup (under lib/tablecloth/yaml/qty.yaml). This all works great and the unit tests all pass, hwoever when I build and install the gem and try to run under irb (from my home folder) I am getting;
Errno::ENOENT: No such file or directory - lib/tablecloth/yaml/qty.yaml
The code is now looking for the file in ~/lib/tablecloth... rather than in the directory the gem is installed to. So my questions are;
1) How should i change line 27 of recipe.rb such that it is looking in the folder that the gem is installed to?
2) Am I in fact approaching this whole thing incorrectly (is it even appropriate to use static yaml files within gems in this way)?
Well first of all you should refer to the File in the following way:
file_path = File.join(File.dirname(__FILE__),"yaml/qty.yaml")
units_hash = YAML.load_file(filepath)
File.dirname(__FILE__) gives you the directory in which the current file (recipe.rb) lies.
File.join connects filepaths in the right way. So you should use this to reference the yaml-file relative to the recipe.rb folder.
If using a YAML-file in this case is a good idea, is something which is widely discussed. I, myself think, this is an adequate way, especially in the beginning of developing with ruby.
A valid alternative to yaml-files would be a rb-File (Ruby Code), in which you declare constants which contain your data. Later on you can use them directly. This way only the ruby-interpreter has to work and you save computing time for other things. (no parser needed)
However in the normal scenario you should also take care that reading in a YAML file might fail. So you should be able to handle that:
file_path = File.join(File.dirname(__FILE__),"yaml/qty.yaml")
begin
units_hash = YAML.load_file(filepath)
rescue Psych::SyntaxError
$stderr.puts "Invalid yaml-file found, at #{file_path}"
exit 1
rescue Errno::EACCES
$stderr.puts "Couldn't access file due to permissions at #{file_path}"
exit 1
rescue Errno::ENOENT
$stderr.puts "Couldn't access non-existent file #{file_path}"
exit 1
end
Or if you don't care about the details:
file_path = File.join(File.dirname(__FILE__),"yaml/qty.yaml")
units_hash =
begin
YAML.load_file(filepath)
rescue Psych::SyntaxError, Errno::EACCES, Errno::ENOENT
{}
end

list files based on a pattern in a specific directory - one command only

I can make it work this way
Dir.chdir(basedir)
puts Dir.glob("#{filename}*").inspect
Is there any way to do so using only one command? I want to list
all files that stat with filename
the the directory basedir
Update 1
puts "#{csv_dir_name}\\#{testsuite}*"
puts Dir["#{csv_dir_name}\\#{testsuite}*"].inspect
retuns
C:\Program Files\TestPro\TestPro Automation Framework\Output Files\builds\basics\logs\basics\2011\07\07114100\login*
[]
on the other hand, this code works fine
Dir.chdir(csv_dir_name)
csv_file_name = Dir.glob("#{testsuite}*")
I think this should do what you want:
puts Dir["#{basedir}/#{filename}*"]
Or alternatively:
puts Dir["#{File.join(basedir, filename)}*"]
previous working directory is restored when the command executed:
Dir.chdir(basedir) { puts Dir.glob("#{filename}*").inspect }

Resources