Vagrant triggering the plugin before everything else - ruby

We are creating a plugin for authentication for our internal usage. We want to use the plugin for authentication.
Plugin checks boxes in environment and if boxes are not there it will authenticate to artifactory. Otherwise it will skip authentication.
We need to trigger plugin before vagrant starts downloading boxes. However I tried all hooks one by one.
I need to call this before vagrant downloads base images.
Plugin:
In config I receive box list to download so I can compare with the boxes already in environment
In plugin I call setup first with action hooks. (I tried all the hooks )
In setup action I call envset and call authenticate action.
However I can't get authenticate action running before vagrant starts downloading files and it fails since authentication token is not retrieved. I know ENV set gets called (I commented the line gets called in below) But the line where i call authenticate action does not get called.
plugin.rb
module VagrantPlugins
module Authenticate
class Plugin < Vagrant.plugin("2")
# Require a particular version of Vagrant
Vagrant.require_version(">= 1.5")
name "authenticate"
description <<-DESC
Automatically pops up authentication prompt for artifactory access
DESC
[:environment_plugins_loaded, :environment_load, :environment_unload, :machine_action_boot, :machine_action_config_validate, :machine_action_destroy, :machine_action_halt, :machine_action_package, :machine_action_provision, :machine_action_read_state, :machine_action_reload, :machine_action_resume, :machine_action_run_command, :machine_action_ssh, :machine_action_suspend, :machine_action_sync_folders, :machine_action_up].each do |action|
action_hook(:authenticate_provision, action) do |hook|
hook.after(Vagrant::Action::Builtin::ConfigValidate, Action::Base.setup)
end
end
config(:authenticate) do
require_relative "config"
Config
end
end
end
end
setup.rb
module VagrantPlugins
module Authenticate
module Action
class Base
# Cleanup any shared folders before destroying the VM.
# loading the datafile from disk.
def self.setup
Vagrant::Action::Builder.new.tap do |b|
#This line of code gets called
b.use Vagrant::Action::Builtin::EnvSet, authenticate: Env.new
#This line does not get called
b.use Action::Authenticate
end
end
authenticate.rb
module VagrantPlugins
module Authenticate
module Action
class Authenticate < Base
def call(env)
#Never reaches this point
puts env[:machine].config.authenticate.to_hash[:enabled]
unless env[:machine].config.authenticate.to_hash[:enabled]
#logger.info "Authenticate disabled, skipping"
return #app.call(env)
end ...
In vagrant file VagrantFile :
$vagrant_server_base_url = <Internal_address>
Vagrant.configure("2") do |config|
config.vagrant.plugins = ["vagrant-none-communicator", "vagrant-proxyconf", "vagrant-host-shell", "vagrant-authenticate"]
config.proxy.http = <proxy_address>
config.proxy.https = <proxy_address>
config.authenticate.enabled=true
config.vm.define "client" do |client|
client.vm.box = $client
client.vm.box_url = $vagrant_repo_url + $client + ".box"
client.vm.box_check_update = $box_check_update
client.vm.hostname = "client"

Related

Ruby Flexible Configuration across GEMS

How would you build a single GEM configuration that can be extended automatically if additional but optional child GEMS are included with a Ruby Application.
I am looking either for a code pattern or an existing GEM that can provide an extendable configuration system for an application I am building.
Scenario
Let's say you have a communication app that can send/receive messages and it uses some general configuration.
gem 'communication-app'
You can install any number of providers that can send/receive via different platforms.
gem 'communication-app-facebook'
gem 'communication-app-twitter'
gem 'communication-app-sms'
gem 'communication-app-email'
Each provider needs its own custom configuration. These should hang off the main configuration and can have a custom configuration graph but only be available if the plugin is installed.
Default Configuration Usage
CommsApp.configure do |config|
config.some_setting2 = 'customised setting'
end
puts CommsApp.configuration.some_setting1
puts CommsApp.configuration.some_setting2
# --> some default value 1
# --> customised setting
Example Code
module CommsApp
class << self
attr_writer :configuration
def configuration
#configuration ||= Configuration.new
end
def configure
yield(configuration)
end
end
class Configuration < BaseConfiguration
attr_accessor :some_setting1
attr_accessor :some_setting2
def initialize
super
#some_setting1 = 'some default value 1'
#some_setting2 = 'some default value 2'
end
end
end
If I try to access configuration for a child GEM that is not installed, I would expect to see some sort of error. eg.
puts CommsApp.configuration.twitter.access_token
# raises NoMethodError, "unknown method :twitter"
I have experimented with a class method on the base configuration that would allow me to attach child configuration properties for a new configuration class.
module CommsApp
class BaseConfiguration
class << self
# Attach a child configuration with it's own settings to a parent configuration
#
# #param [Class] klass_child what class would you like as the child
# #param [Class] klass_parent what class would you like to extend with a new child configuration
# #param [Symbol] accessor_name what is the name of the accessor that you are adding
def attach_to(klass_child, klass_parent, accessor_name)
# Create a memoized getter to an instance of the attaching class (:klass_child)
#
# def third_party
# #third_party ||= ThirdPartyGem::Configuration.new
# end
klass_parent.send(:define_method, accessor_name) do
return instance_variable_get("##{accessor_name}") if instance_variable_defined?("##{accessor_name}")
instance_variable_set("##{accessor_name}", klass_child.new)
end
end
end
end
end
Extended Configuration Usage
This is how the extend configuration could be used.
class ApplicationConfig < CommsApp::Configuration
attach_to(CommsApp::TwitterProvider::Configuration, self, :twitter)
attach_to(CommsApp::FacebookProvider::Configuration, self, :fb)
end
CommsApp.configuration = ApplicationConfig.new
CommsApp.configure do |config|
config.some_setting2 = 'customized setting'
config.twitter.access_token = '123'
config.fb.user_name = 'xmen'
config.fb.account_id = 'xmen123'
end
This technique works ok, but it does require a custom class to be created and configured and I want this to happen automagically if (and only if) the provider GEMs are present.
I know I can figure out how to do this, but I am wondering if there is already a pattern or GEM that is solving this particular problem.

Cucumber : I want to send report on email after my all the scenario get executed, Is there any method like 'AfterAll' which can be used in hooks.rb

I have created mail function to send my report
class Email
include PageObject
require 'mail'
def mailsender
Mail.defaults do
delivery_method :smtp,{
address: "smtp.gmail.com",
openssl_verify_mode: "none",
port: 587,
domain: 'gmail.com',
user_name: 'xxxxxxxx#gmail.com' ,
password: '*******' ,
authentication: 'plain'
}
end
Mail.deliver do
from 'xxxxxxx.com'
to 'xxxxx#test.com'
subject 'Execution report'
body 'PFA'
add_file 'Automation_report.html'
end
end
end
I want this function will execute after all the scenario get executed.
This is my hook file
# frozen_string_literal: true
require watir
Before do |scenario|
DataMagic.load_for_scenario(scenario)
#browser = Watir::Browser.new :chrome
#browser.driver.manage.window.maximize
end
After do |scenario|
if scenario.failed?
screenshot = "./screenshot.png"
#browser.driver.save_screenshot(screenshot)
embed(screenshot, "image/png",)
end
#browser.close
end
If I use this function in After do then it sends the email every time after each scenario get executed
You can use the at_exit in the hooks.rb file.
at_exit do
# your email logic goes here
end
Additional notes: After hook will execute after each scenario that's the reason why it will send the email after each scenario executed. On the other hand at_exit hook will execute only after all the scenarios are executed.
You can directly implement the email logic in the at_exit hook. If you want to call mailsender method and not able to access it in the at_exit hook then you can create the email class/module as shown below.
consider that you have Email module under GenericModules
module GenericModules
module Email
def mailsender
# implement your logic here
end
end
end
And then add Email module to the world in the env.rb as shown below.
World(GenericModules::Email)
Now you should be able to access the method even in at_exit hook.
You can use the #AfterAll hook which is available with Cucumber v7.x.
Carefully upgrade your cucumber packages in your pom.xml.
Refer: https://cucumber.io/docs/cucumber/api/#hooks and check Global Hooks.

Using RSpec with Factory Girl and get a can't define singleton error

I'm using Factory Girl to generate test date in order to mock a connection to an ldap server (because I don't want the application to make a real ldap connection each time in order to successfully run my tests).
I want to mock this connection and confirm that this occurred successfully.
My factory girl data is:
/factories/connections.rb
FactoryGirl.define do
factory :connection do
host "just-ln1-wdc03.company.qube"
port 389
base "DC=company,DC=qube"
username "cat#company.qube"
password "cats123"
end
end
and my Rspec test is:
authenticate_spec.rb
require 'spec_helper'
describe "#is_authenticated_by_ldap" do
let(:connection) {FactoryGirl.build(:connection)}
it "queries ldap server and returns success" do
allow(:connection).to receive(:is_authenticated_by_ldap).with("cat#company.qube", "cats123").and_return(true)
end
end
spec_helper.rb
require 'factory_girl'
require_relative './factories/connections.rb'
RSpec.configure do|config|
...
authenticate.rb
def is_authenticated_by_ldap(credentials)
full_username = credentials[0] + EXTENSION #EXTENSION = "company.com"
ldap = Net::LDAP.new :host => "just-ln1-wdc03.company.qube", # your LDAP host name or IP goes here,
:port => 389, # your LDAP host port goes here,
# :encryption => :simple_tls,
:base => "DC=company,DC=qube", # the base of your AD tree goes here,
:auth => {
:method => :simple,
:username => full_username, # a user w/sufficient privileges to read from AD goes here,
:password => credentials[1] # the user's password goes here
}
is_authorized = ldap.bind
return is_authorized
end
parameters passed to the is_authenticated_by_ldap method are essentially username and password effectively credentials[0] and credentials[1] respectively.
When I run this rspec spec/authenticate_spec.rb, I receive the following error:
Authenticate
←[32m authenticates a user on qube server←[0m
#is_authenticated_by_ldap
←[31m queries ldap server and returns success (FAILED - 1)←[0m
Failures:
1) Authenticate#is_authenticated_by_ldap queries ldap server and returns success
←[31mFailure/Error: allow(:connection).to receive(:is_authenticated_by_ldap).with("cats#company.qube", "cats123").and_return(true)←[0m
←[31mTypeError:←[0m
←[31m can't define singleton←[0m
UPDATE
I believe I have answered my own question and have posted an explanation here as someone else may have this problem and seek a solution like I did:
RSpec 3.0 has a feature called an instance double which takes a class name or object as it's first argument and then verifies that any methods being stubbed would be present and execute on an instance of that class.
So in my case because I had an Authenticate module which handled my authentication feature, I created a dummy class in my Rspec test, which also included the said module.(see code below)
authenticate_spec.rb
require 'spec_helper'
class Dummy
include Authenticate
end
describe Authenticate do
let(:dummy_class) {Dummy.new.authenticate}
let(:credentials) {FactoryGirl.build_stubbed(:credentials)}
it "authenticates a user on qube server" do
expect(:dummy_class).to be
end
describe "#is_authenticated_by_ldap" do
it "queries ldap server and returns successful connection" do
dummy = instance_double("Dummy")
allow(dummy).to receive(:is_authenticated_by_ldap).with(:credentials).and_return(true)
expect(dummy.is_authenticated_by_ldap(:credentials)).to be(true)
end
end
end
#spec/factories/credentials.rb
FactoryGirl.define do
factory :credentials do
username "cats#company.com"
password "cats123"
end
end
and defined test data using factory_girl (username and password) to pass as a parameter of the is_authenticated_by_ldap method.(this method confirms a successful connection to the ldap server or not). Using RSpec 3.0 syntax, if you use stubs/mocks, the allow method with a receive matcher, tells the fake object to return a value in response to a given message/method. In my case, I would expect the test to pass (successful ldap connection) or fail (unsuccessful ldap connection). To confirm that my mock worked correctly and wasn't a false positive, test data defined by factory girl gem(and not existing on the ldap server) was used and the test passed. I also commented out the source code for the method and reran the test and it failed as expected.

Can't access current_user inside .new do block in the ApplicationController

I'm using devise and the bitbucket api gem and I have a method in my ApplicationController which creates an instance so I can make API calls. To do that, it tries to read the token and secret from the current_user.
This works fine with hardcoded token and secret strings, I'm also able to do puts current_user.inspect before the do block, and that all works fine. I'm also sure that bb_token and bb_secret exist (I'm able to call puts on them individually).
But once I try to create my bitbucket instance, it can't read current_user anymore. Any ideas?
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user
def bitbucket
puts "token----------"
puts current_user
#bitbucket = BitBucket.new do |config|
config.oauth_token = current_user.bb_token # replaceing this with hardcoded string works
config.oauth_secret = current_user.bb_secret # replaceing this with hardcoded string works
config.client_id = 'xx'
config.client_secret = 'yy'
config.adapter = :net_http
end
end
end
And the error:
NameError (undefined local variable or method `current_user' for #<BitBucket::Client:0x007fbebc92f540>):
app/controllers/application_controller.rb:12:in `block in bitbucket'
app/controllers/application_controller.rb:11:in `bitbucket'
It seems block passed to BitBucket.new is executed in context of new BitBucket::Client instance (BitBucket.new is really BitBucket::Client.new, according to this).
A glance to the source confirms this supposition.
If you want to pass current_user, you can recall that the blocks are closures, so they keep the context in which they are defined. So you can do something like this:
def bitbucket
# (...)
user = current_user # local variable assignment
#bitbucket = BitBucket.new do |config|
config.oauth_token = user.bb_token # it works because user is local variable and the block is closure
# (...)
end
end
Inside BitBucket.new do..end block,self is set to config. But current_user is not a instance method of BitBucket class. Thus a valid error is thrown.

Remove default route logging in Sinatra app

I have a Sintra app with a /health/api endpoint, described below, that gets called many times a second by my load balancers. I would like to remove the default logging for only this route, or conversely print to the logs only the endpoints I care about.
get '/health/api' do
# Health Check
'I keep quiet'
end
get '/members' do
# get members data
'This request gets logged'
end
You cannot configure the default Rack::CommonLogger to print only some requests, and keep quiet on others. But you can disable the default CommonLogger and use your own with filtering capabilities instead:
require 'rubygems'
require 'rack'
require 'sinatra'
LOGGING_BLACKLIST = ['/health/api']
class FilteredCommonLogger < Rack::CommonLogger
def call(env)
if filter_log(env)
# default CommonLogger behaviour: log and move on
super
else
# pass request to next component without logging
#app.call(env)
end
end
# return true if request should be logged
def filter_log(env)
!LOGGING_BLACKLIST.include?(env['PATH_INFO'])
end
end
disable :logging
use FilteredCommonLogger
get '/members' do
# get members data
'This request gets logged'
end
get '/health/api' do
# Health Check
'I keep quiet'
end
You can obviously also write your custom logger that only logs "whitelisted" requests.

Resources