Only_if with Loop in Ruby_block to pass Foodcritic FC022 - ruby

I've been trying to figure out the better approach to use condition inside a ruby_block to avoid FC022 rulewhen the code gets evaluated by Foodcritic.
FC022: Resource condition within loop may not behave as expected
The code of mine is as follows
ruby_block 'file configuration' do
block do
files = [
'/etc/file01.conf',
'/etc/file02.conf',
]
files.each do |f|
file = Chef::Util:FileEdit.new(f)
file.insert_line_if_no_match('something', 'something')
file.write_file
only_if { ::File.exist?(f) }
end
end
Removing only_if will pass FC022 rule in Footcritic.

Related

Unable to set node attribute in runtime and reference it in chef template

I am getting template error due to backend.key=<%= node['key']%> used in source key.properties.erb doesn’t have a value while running shellout.
Error : Chef::Mixin::Template::TemplateError - undefined method `[]' for nil:NilClass
I have a ruby block to get the output of the file cat /tmp/key.txt and assigning as a node value.
Ruby block :
ruby_block "Get_key" do
block do
#tricky way to load this Chef::Mixin::ShellOut utilities
Chef::Resource::RubyBlock.send(:include, Chef::Mixin::ShellOut)
command = 'cat /tmp/key.txt'
command_out = shell_out(command)
node.set['key'] = command_out.stdout
end
action :create
end
Erb :
backend.key=<%= node['key']%>
There is no need to shell_out to read the contents of a file. Try this instead:
ruby_block "Get_key" do
only_if { node['key'] == "" }
block do
node.set['key'] = File.read('/tmp/key.txt')
end
end
But I think your actual problem is somewhere else. The error message indicates that node is nil within your template, which is pretty unusual.
So either I am blind and you really have some typo in the posted template line, or you simplified your code example in such a way that it hides your error. I assume your real template looks more like
backend.key=<%= node['foo']['key']%>
and foo not being an array. Check that.
Please don't use this pattern. It's slow and puts extra data in your node object which takes up space and RAM and makes you search index sad. What you want is this:
template "whatever" do
# Other stuff ...
variables my_file: lazy { IO.read('/tmp/key.txt') }
end
That will delay the read until converge time.

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.

Chef Foodcritic rule not catching attribute strings

I have written a foodcritic rule to catch any attempt to write to a blacklist of directories/files under the /etc directory.
When blacklisted paths are passed to resource declarations as strings in a recipe, the rule triggers, however when they are passed as attributes, the rule does not trigger:
#resources = [
'file',
'template',
'remote_file',
'remote_directory',
'directory'
]
#blacklist = [
'/etc/ssh/',
'/etc/init',
...
]
rule 'RULE001', 'do not manipulate /etc other than init/,init.d/ & default/' do
tags %w(security)
recipe do |ast|
violations = []
#resources.each do |resource_type|
violations << find_resources(ast, type: resource_type).select do |resource|
res_str = (resource_attribute(resource, 'path' || resource_name(resource)).to_s
#blacklist.any? { |cmd| res_str.include? cmd }
end
end
violations.flatten
end
end
Testing this using the below, the literal strings are caught, however when passed as attributes they are passed. Can anyone see what I'm missing?
attributes/default.rb:
default['testbook']['etc-test'] = '/etc/ssh/test.conf'
default['testbook']['etc-dir-test'] = 'etc/ssh/somedir/'
recipes/default.rb:
#template '/etc/ssh/test.conf' do <-- caught
template node['testbook']['etc-test'] do #<-- not caught
source 'test.conf'
owner 'nobody'
group 'nobody'
mode '0644'
action :create
end
#directory '/etc/ssh/somedir' do <-- caught
directory node['testbook']['etc-dir-test'] do <-- not caught
action :create
end
Yes, this isn't something you can fully handle via static analysis. Foodcritic and tools like it can only handle things that are static in the code, anything that could vary at runtime won't be known.

Test nested File.open

I need to test a file open operation. I am able to test the first operation but not the second.
File.open("#{TemplateFile.fixture_path}/#{#template_file}") do |input_file|
template = ERB.new(input_file.read)
File.open("#{#project_name}/#{#destination_file}", 'w') do |output_file|
output_file.puts template.result binding
end
end
end
I am using this code:
module Pod
describe TemplateFile do
it "opens the template" do
dict = {"README.md.erb" => "README.md"}
File.expects(:open).with("#{TemplateFile.fixture_path}/README.md.erb")
File.expects(:open).with("Sample/README.md.erb", 'w')
TemplateFile.new(dict, "Sample")
end
end
end
But I am getting an error:
unsatisfied expectations:
- expected exactly once, not yet invoked: File.open('/README.md.erb', 'w')
satisfied expectations:
- expected exactly once, invoked once: File.open('/lib/pod/command/../../../fixtures/README.md.erb')
It seems that Mocha is not geeting the second File.open.
The reason is because expects verifies the call would happen but doesn't actually let it go through. So what's in the block doesn't get run.
However, beyond just telling you why it's not working, I also wanted to point out what you are doing is probably not what you want to do.
What you likely want to do do is:
template = ERB.new(File.read("#{TemplateFile.fixture_path}/#{#template_file}"))
File.open("#{#project_name}/#{#destination_file}", 'w') do |output_file|
output_file.puts template.result binding
end
You don't need that nesting.
Then when testing what you want to do to verify your the correct file is read is:
File.expects(:read).with("#{TemplateFile.fixture_path}/README.md.erb").returns(some_known_fixture)
The returns part says when it does get this read method with the specified argument I want you to return this known thing so that template will have a good value for the rest of the code.

Ruby Load multiple scripts from directory with foreach loop

I'm using a loop to load and execute Ruby scripts in a directory. At the moment the script will load the script, but how do I execute it when the only reference to it is the filename in the form of a string?
Dir.foreach('tests') do |item|
next if item == '.' or item == '..' #removes extra "." or ".."
load dirname + '/' + item #successfully loads the script
if item # the scripts return true/false
numberPassed+=1
else
numberFailed+=1
failed.push(item)
end
numberTested+=1
end
For some reason I'm getting 2 Passed, but it never actually runs the scripts "item" represents.
EDIT: here is an example of a script that would need to be loaded. They all follow this format:
require "watir-webdriver"
class TestScript
puts 'Testing etc etc"...'
browser = Watir::Browser.new :ie
browser.goto "webpage.htm"
browser.text_field(:name => "j_username").set "username"
browser.text_field(:name => "j_password").set "password"
browser.link(:id, "watSubmitLogin").click
browser.wait
browser.link(:id=> 'watCommDir').fire_event("onmouseover")
browser.link(:id=> 'watAddFi').click
browser.wait
...
browser.link(:href, "javascript: submitForm();").click
browser.wait
if browser.text.include?( 'The user already Exists')
puts 'Passed'
browser.close
return true
else
puts 'Failed'
browser.close
return false
end
end
I need to somehow tell the main script whether the sub-scripts pass or fail so I can keep track of how many pass/fail/error/total and create a report of all the tests that failed.
Looks like you are doing acceptance testing with Watir and try to do custom test results reporting.
I would recommend to use existing test runners to run all your tests and build custom output formatter for your needs. Existing test runners already solve a lot of issues you will encounter during creation of your own test runner (like how to run tests from specified folder, how to identify failing/successful test etc).
One of the commmon test runners for acceptance tests in Ruby community is Cucumber. Another good alternative is RSpec. Both these libraries support custom formatters:
In RSpec you would need to subclass RSpec::Core::Formatters::BaseFormatter.
In Cucumber you would need to implement class with methods specified in this documentation.
If you want to stay with the current simple implementation, here is one possible approach that is inpired by ruby Regexps: Inside the test set global variable, e.g. $test_succeeded (like $~, $& etc. global variables generated by ruby regular expressions) and then examine this value in your test runner.
In tests
if browser.text.include?( 'The user already Exists')
puts 'Passed'
browser.close
$test_succeeded = true
# ...
In tests runner
Dir.foreach('tests') do |item|
next if item == '.' or item == '..' #removes extra "." or ".."
load dirname + '/' + item #successfully loads the script
if $test_succeeded
# ...
If you have problems running the script then I can recommend to define special method to run tests (similar to RSpec approach):
def test
test_res = yield # call test
$test_results ||= {}
$test_results << test_res # and store its result in arra of test results
end
Then your tests will look like:
require 'file_with_test_method'
require 'watir-webdriver'
test do
# your test code
browser.text.include?( 'The user already Exists') # last expression in the block will be the test result
end

Resources