How can I conditionally run a block of resources or a recipe in chef? - ruby

I've the following recipe used to create some users, add them to a group and set the password to expire at the first login.
search(:users, '*:*').each do |user|
userPassword = "$1$scmdevop$ZDTyqia9RXSrpHGK75FjN/"
user user['id'] do
comment user['comment']
home user['home']
shell user['shell']
manage_home true
password "#{userPassword}"
end
if user['sudo'] then
group "#{node.default["sudogroup"]}" do
action :modify
members user['id']
append true
end
end
if (user['resetPassword'] == nil) || (user['resetPassword']) then
bash 'setExporation' do
code 'chage -d 0 ' + user['id']
user 'root'
end
end
end
The problem is that in this way it will continue to reset the password and set the espiration at every run so I was trying to find how to make it conditionally. I would like to use the following command to check if the user exist
grep -qs #{user["id"]} /etc/passwd
The problem is that I can use the not_if clause only in the first resource because after that the user has been clearly created. Is there a way to get the entire block of three resources being conditional to a shell exit code?
Thanks,
Michele.

What you probably want is a notification from the user resource, but this might be a little hard because that would trigger on any change, not just creation. The underlying problem here is that the desired behavior you stated is expressed in procedural terms, not in terms of convergent state. Best approach is probably to build a custom resource to hide some of this logic, but at heart what you want is an if statement like you already have.

Related

Send variable from one Chef recipe to another in order to trigger a resource block

In one recipe I have a rubyblock that ultimately obtains the port of a service that I'd like to restart.
Individually the recipes work fine and I am now trying to tie the two together.
I cannot seem to pass the variable to the other recipe having tried to follow a custom resource example.
my default recipe that obtains the port is:
Chef::Log.info("Port: #{port}")
Chef::Resource::Notification.new("stop-solr_#{port}", :run, self)
I'm trying to trigger the resource block of the name 'stop-solr_'portNumber'' using the notification sender.
My other recipe looks like the following and has a start/stop service purpose
solrCore = "solr_#{port}"
#define the service - does nothing
service solrCore do
action :nothing
end
#do something that triggers
execute "start-solr_#{port}" do
Chef::Log.info('triggers start')
action :nothing
notifies :start, run_context.resource_collection.find(:service => "#{solrCore}")
end
execute "stop-solr_#{port}" do
# some stuff
# on success...
Chef::Log.info('triggers restart')
notifies :stop, run_context.resource_collection.find(:service => "#{solrCore}"), :immediately
notifies :run, "execute[start-solr_#{port}]"
end
My main problem (I think) is that the variable solrCore uses 'port' which I cannot seem to obtain.
Is anyone able to help with what I need ot do in order to get this working?
Thanks in advance.
Variables in Ruby are local by default. You would have to use a global variable to share state between files, either a real Ruby global variable ($foo) or using the global node.run_state hash we expose to all recipes.
That said: there is a reason that mutable global variables have been a CS cliché for decades. Code like this is very fragile and difficult to debug. I would consider turning both of those recipes into custom resources and calling them from the same recipe with the same input port.
In the first recipe, you can save information in the node.
Sample:
node.normal['A']['B']['C']="completed"
node.save
and In second Recipe you can retrieve information from the node.
status=node.normal['A']['B']['C']
and based on the retrieved value you can take action

Chef Recipe How To Check If File Exists

I just started using Chef and I'm trying to figure out how to first check if a file exists before doing anything.
I have the file part down for my current use case, where I'm removing a login file for the production server, ex:
file '/var/www/html/login.php' do
action :delete
end
However, I'd like the abilty to first check if the file exists, ex.
if (file_exists === true)
file '/var/www/html/login.php' do
action :delete
end
end
As mentioned in the comments, for a deletion action, the if statement is unnecessary, as mentioned, because if chef doesn't find the file to be deleted, it will assume it was already deleted.
Otherwise, you generally want to use guard properties in the resource (available for all resources), rather than wrapping a resource in an if-then.
file '/var/www/html/login.php' do
only_if { ::File.exist?('/var/www/html/login.php') }
action :touch
end
And you probably also want to familiarize yourself with the Ruby File class methods.
The basic idea of Chef is that you state the desired state of the system, and then Chef compares that to the actual state, and makes any changes needed to bring the system into the desired state. You do not need to have an if statement to check if the file exists before deleting it; Chef itself should check if the file exists if I'm not mistaken.

Passing parameter in "Examples:" table of "Scenario Outline:" in a feature file

Here as you can see I am trying fetch a value from .yml file located in config/environments in Examples: table.
But instead of fetching it is sending the value as it is?
Is it possible to pass parameter like this? If Yes, how?
If not, which Ruby or Cucumber feature/concept refrains user to do so and why?
Feature: Verify login of all test users
I want to verify all test users can login.
Scenario Outline: Login as different users on the website
Given I am on login page
When I enter "<username>" and password
Then I click Login button
And I see "<user>" successfully logged in
Examples:
|user|username|
|testuser1|#{FigNewton.test1_email}|
|testuser2|FigNewton.test2_email|
First of all this is a pretty poor feature, better would be
Scenario: Test Users can login
Given there are some test users
When the test users login
Then all test users should be logged in
or something like that. Features are for stating what you want to do and why, not how you do things.
IF you do the above then all the programming will be done in the step definitions. This will allow you do do whatever you want.
You can implement this quite easily e.g
Given 'there are some test users' do
#test_users = create_test_users
end
When 'the test users login' do
#login_results = login_each(#test_users)
end
Then 'all test users should be logged in' do
expect(check_for_errors(#login_results).count).to eql 0
end
then implement the methods you need in a step helper e.g
module TestUsersLoginStepHelper
def create_test_users
...
def login_each(users)
users.each do
...
...
end
World TestUsersLoginStepHelper
By putting all the work in the step definitions, you make your live much easier, as you can use the full power of ruby to do what you need
Answer to query1:
You can parametrize via Examples: table but not directly passing value using FigNewton gem because it is a .feature file not a Ruby .rb file.
Answer to query2:
How you do it:
Parametrize and Loop it on username and in you steps definition mention what to do when particular user name found. By this you can easily parametrize.
Examples:
|user|username|
|testuser1|test1|
|testuser2|test2|
Step definition
When(/^I enter "([^"]*)" and password$/) do |username|
case username
when 'test1'
on(LoginPage).user_email = FigNewton.test1_email
when 'test'
on(LoginPage).user_email = FigNewton.test2_email
end
....
....
end
You can use this DDD scenario in project whenever its needed - by using this we do not need to create multiple test cases, it will fetch data value from Example outline.
Feature file : Test case
Scenario Outline: Login to application
When I enter "username>" and "password>"
Then I click Login button
And I see user successfully logged in
Examples:
|username|password|
|abc#gmail.com|Test1234!|
|abc#yahoo.com|Test1234!|
Step definition:
When(/^I enter "([^"])" and "([^"])"$/) do |username,password|
sleep 20
on(Login).email_edit_text_element.send_keys username
on(Login).password_edit_text_element.send_keys password
end
Then(/^I click Login button$/) do
sleep 20
on(Login).login_button_element.click
end
Then(/^I see user successfully logged in$/) do
expect(on(Login).account_bg_cover_element.displayed?).to be_truthy
puts 'Login Success'
end
In ruby file, you have created methods, you are calling that methods in step definition.
It will work. make sure about the name of parameter you are passing.

Rspec test on method that yields control

I am currently writing rspec tests for already written code of a project.
The code I am trying to test is something like the following:
def foo (ip, user)
#[...]
result = ""
Net::SSH.start(ip, user) do |session|
result = session.exec!('some_command_in_linux')
end
#[...]
end
What I am trying to accomplish is effectively decouple the assignment of the result from the execution of the remote command via SSH assign a fake string to the result variable.
I have read about yield matchers, but have not succeeded in applying them to my scenario.
More specifically, I was thinking something like:
expect {|block| Net::SSH::start(ip, user, &block).to receive(ip, user, &block).and_return(sample_output)}
Any ideas?
If you don't need the block to actually run - simply ignore it. It won't run, but it would be safely ignored:
expect(Net::SSH).to receive(:start).with(ip, user).and_return(sample_output)
If you do want the block to run, use and_yield:
session = double(:session).as_null_object
expect(Net::SSH).to receive(:start).with(ip, user).and_yield(session).and_return(sample_output)
If you want the session object to behave in a certain way, you can expect it to do stuff:
session = double(:session)
expect(Net::SSH).to receive(:start).with(ip, user).and_yield(session)
expect(session).to receive(:exec!).with('some_command_in_linux').and_return(sample_output)

Reuse Cucumber steps

I want to reuse some Cucumber steps but can't seem to find the right way.
I want to write a step like:
Given /^I login with (.*) credentials$/ |type|
# do stuff with type being one of "invalid" or "valid"
end
But then have another step like:
Given /^I login successfully$
# call "Given I login with valid credentials"
end
So in testing user authentication I can use the former, but most other places, I can use the latter, and not actually have to repro code.
Is there a way to call that other step, or do I just put the logic in a helper method, and call said method from each task (basically a method extraction refactoring, which, after reading my question makes me believe that's actually the best way anyway)?
Note that the method for calling steps within steps has changed in recent versions of cucumber, which you'll see if you get an error like "WARNING: Using 'Given/When/Then' in step definitions is deprecated, use 'step' to call other steps instead:/path/to/step_definitions/foo_steps.rb:631:in `block in '
". See the cucumber wiki for details.
The gist of the change is that you should now use the step or steps methods.
When /^I make all my stuff shiny$/
step "I polish my first thing"
end
When /^I make all my stuff shiny$/
steps %Q{
When I polish my first thing
When I shine my second thing
}
end
UPDATE: The method described below has been deprecated. The recommended way to call a step from within another step now looks like this:
Given /^I login successfully$/
step "I login with valid credentials"
end
Old, deprecated method (for reference):
You can call steps from other steps like this:
Given /^I login successfully$/
Given "I login with valid credentials"
Then "I should be logged in"
end
If all of the scenarios within a feature require this (or other steps), you can also add a Background to each features, with the common steps, like so:
Background:
Given I log in with valid credentials
Scenario: Change my password
Given I am on the account page
Calling steps from step definitions is a bad practice and has some disadvantages:
If scenario will fail and there are nested step invocations, you will get only the last invoked step definition in the stack trace. It may be hard to find from which place that last stepdef was called
Call to stepdef is sometimes harder to find and read than ruby method
Ruby methods give you more power than calling steps from step defs
Aslak Hellesøy recommends to extract popular actions to World instead of reusing steps. It isolates those actions in one place, makes this code easier to find. You can extract code to usual Ruby classes or modules as well.
#/support/world_extensions.rb
module KnowsUser
def login
visit('/login')
fill_in('User name', with: user.name)
fill_in('Password', with: user.password)
click_button('Log in')
end
def user
#user ||= User.create!(:name => 'Aslak', :password => 'xyz')
end
end
World(KnowsUser)
#/step_definitions/authentication_steps.rb
When /^I login$/ do
login
end
Given /^a logged in user$/ do
login
end
Here is a useful discussion on the subject in Cucumber mailing list - link
Best wrap your steps in %{} rather than quotes. Then, you don't need to escape double quotes which you'll need to use frequently.:
Given /^I login successfully$
step %{I login with valid credentials}
end
Given /^I login with (.*) credentials$/ |type|
# do stuff with type being one of "invalid" or "valid"
end
Reuse keywords in feature file which will provide code reusability.
It is highly NOT recommended to call step defs within step defs.
I would write my feature file this way,
Scenario Outline: To check login functionality
Given I login with "<username>" and "<password>"
Then I "<may or may not>" login successfully
Examples:
|username|password|may or may not|
|paul |123$ |may |
|dave |1111 |may not |
In my step definition, (This is Java)
#Given(I login with \"([^\"]*)\" and \"([^\"]*)\"$)
public void I_login_with_and(String username, String password){
//login with username and password
}
#Then(I \"([^\"]*)\" login successfully$)
public void I_login_successully_if(String validity){
if(validity.equals("may")){
//assert for valid login
}
else
if(validity.equals("may not")){
//assert for invalid login
}
}
In this way, there is a lot of code reusability.
Your same Given and Then handles both valid and invalid scenarios.
At the same time, your feature file makes sense to the readers.

Resources