Capistrano Ruby: Refactoring Reused String Values - ruby

I have a couple tasks within my deploy.rb file that execute within my theme folder.
I currently define them as follows:
namespace :deploy do
desc 'NPM Build Production'
task :npm_build_production do
on roles(:app) do
within "#{release_path}/web/app/themes/example" do
execute :npm, "install --silent --no-progress"
execute :npm, "run build:production"
end;
end
end
end
before 'deploy:updated', 'deploy:npm_build_production'
Everything is working correctly with this implementation, but since there are multiple instances where I use this path, I'd like to extract it to a symbol or variable. I am new to Ruby and am running into some issues.
I have tried using the code below, but for some reason the path ends up being wrong when executed in my tasks.
set :theme_path, -> { "#{release_path}/web/app/themes/example" }

These variables are set on different stages of implementation. for example the release_path variable is not defined in the setting variables section. Move your variable to your execution block for tasking and it will work . for example this snipped will work
namespace :deploy do
desc 'NPM Build Production'
task :npm_build_production do
on roles(:app) do
theme_path = "#{release_path}/web/app/themes/example"
within theme_path do
execute :npm, "install --silent --no-progress"
execute :npm, "run build:production"
end;
end
end
end
also you will be able to fetch some variables using this way while setting other variables like fetch(:varible_name) but I am not sure it is feasible for the release_path since it is a scoped variable
here is how you could do it with a function
def theme_path(release_path)
"#{release_path}/web/app/themes/example"
end
just call this function with your release_path variable and you will have the themepath wherever you want. inside your deployment logic.

Related

Chef - using powershell_script from within ruby_block

Is it possible to use powershell_script from within a ruby_block?
I want to run the powershell script during the convergence phase and not the compilation phase.
The current code doesn't work:
ruby_block 'ruby block so that code is run during convergence phase and not compilation phase' do
block do
buildNumber = "123"
powershell_script 'run powershell script' do
environment ({'buildNumber' => buildNumber})
code "path/to/script/script.ps1"
end
end
action :run
end
I know you can use a guard on the powershell_script outside the ruby_block to make it run during convergence but I need the local variable buildNumber that is defined inside the ruby block.
the following code worked for me:
x = Chef::Resource::PowershellScript.new('unzipper script',run_context)
x.code 'D:/git/chef/cookbooks/java-8-upgrade/unzipper.ps1'
x.environment ({'buildNumber' => buildNumber})
x.run_action :run
In the simple case, you can probably use the powershell_out helper method. For more complex cases, make a custom resource and use the normal resource. Do not use manual resource invocation (Chef::Resource::Foo.new as we explicitly do not support this and know for a fact that it breaks things).

action_class.class_eval method not working with execute resource's environment property

I have an interesting problem where I refactored a recipe by creating a Chef resource to handle some tasks I may need in other recipes. For instance, I've created the following action:
resource_name :my_command
action :run do
execute "Execute my command" do
environment ({"SETTINGS_FOLDER" => node['settings']['folder']})
command "#{command_exe} -some -params"
end
end
action_class.class_eval do
def command_exe
"#{node['command']['folder']}\\bin\\command.exe"
end
end
When I call my_command from a recipe it works as expected. However I have several other actions that this resource will implement that'll all use the same environment. So what I did was refactor the resource to look like this:
resource_name :command
action :run do
execute "Execute my command" do
environment env
command "#{command_exe} -some -params"
end
end
action_class.class_eval do
def command_exe
"#{node['command']['folder']}\\bin\\command.exe"
end
def env
{"SETTINGS_FOLDER" => node['settings']['folder']}
end
end
What happens now is, once chef-client executes the my_command resource it appears as though the SETTINGS_FOLDER environment variable on the machine winds up looking like this:
SETTINGS_FOLDER = ""C:\my\settings\folder""
Notice the doubled double-quotes? I'm not sure why this is happening, but it makes my command.exe very angry :(
The ['settings']['folder'] attribute is defined in the cookbook's attributes/default.rblike so:
default['settings']['folder'] = 'C:\\my\\settings\\folder'
My node is running chef-client 13.0.118
EDIT I think the doubled double-quotes was a red herring. I think the logger just represented the hash in that way. My new thought is that perhaps the env method is not being evaluated before being passed to the environment, but rather the function reference itself is being passed. Bear with me, Ruby isn't my first language...
The "env" method name might be a reserved word or is getting stomped later in the run. Try a different name for that method, perhaps?

Capybara Around Hook to test several envinroments

I'm writing some tests for a webpage that I'd like to run in several environments. The idea is that the test will run in one, then repeat in the next. The two environments are preview and uat.
I've written an Around hook to set the environment variables. Below:
Around do |scenario, block|
def test_envs
chosen_env = ENV['test_env'] || 'preview'
chosen_env.split(',').map(&:strip)
end
test_envs.each do |test_env|
$base_url = "https://#{test_env}.webpage.com"
end
block.call
end
I have then written a method to execute the navigation step:
def navigate_to(path)
visit $base_url + path
end
My Scenario step_definition is:
navigate_to '/login'
The tests will work in either environment, Preview by default or UAT if I set test_env=uat
However, I was aiming to set test_env=preview,uat and have them run consecutively in both environments.
Is there something obvious that I've missed here?
Thanks
If I'm understanding you correctly, it's the 'parallel' aspect that you're asking about.
Rspec can be used with parallel tests (the parallel_tests gem) but I wouldn't be so sure that calling something like 3.times { blk.call } in an around hook will run each block in parallel.
An alternative may be do so some metaprogramming with your example definitions, i.e.
test_envs.each do |env_name|
it "does something in #{env_name}" do
# do something with the specific environment
end
end
Now, I haven't actually used this gem and I don't know for sure it would work. I think the simplest solution may be to just write a wrapper script to call the tests
# run_tests.rb
environments = ENV["TEST_ENV"]&.split(",") || []\
filename = ENV["filename"]
environments.each do |env_name|
Thread.new do
system <<-SH
env TEST_ENV=#{env_name} bundle exec rspec #{filename}
SH
end
end
Running it like env TEST_ENV=foo,bar ruby run_tests.rb would call the following commands in their own threads:
env TEST_ENV=foo bundle exec rspec
env TEST_ENV=bar bundle exec rspec
I like this approach because it means you don't have to touch your existing test code.

Capistrano 3 / SSHKit write to a file in custom task

I want mark the current deployed directory with my release number.
I tried this approach:
Get locally the app version, store it into a variable, and on the remote host, store it in a file.
namespace :deploy do
desc "Set a release number as the app version"
task :mark_release do
release_number = `git describe`
on roles(:web) do
execute("echo #{release_number} > #{current_path}/RELEASE")
end
end
end
The problem is, when I run this via:
cap deploy:mark_release
the command look like this:
echo v9.3.0-254-g178d1f8; > /foo/bar/current/RELEASE
The semicolon is making trouble. and my RELEASE file is of course empty.
I think it is due to some escaping made by SSHKit.
Any clues ?
I managed it:
1) I took the release number from the repo directory on the machine
2) I wrote it with a stream to a file via the upload! method
namespace :deploy do
desc "Set a release number as the app version"
task :mark_release do
on roles(:web) do
within "/foo/bar/repo/" do
upload! StringIO.new(capture(:git, "describe")), "#{current_path}/RELEASE"
end
end
end
end
Here is the solution that I came up with which doesn't require uploading a local file. It goes to the repo path to execute the git command to extract the version and then redirects the output to file. The file can then be read by the Rails app. The execute requires the different parameters to be passed in separately. https://github.com/capistrano/sshkit#the-command-map has more info about the command map and why it's needed, due to the problem of escaping and whitespace.
namespace :deploy do
before :restart, :add_revision_file
task :add_revision_file do
on roles(:app) do
within repo_path do
execute(:git, :'rev-parse', :'--short', :'HEAD', ">#{release_path}/REVISION")
end
end
end
end
Use SSHKit::Command
SSHKit::Command.new("echo #{release_number} > #{current_path}/RELEASE")

Why cant I access a variable outside a Capistrano task

I have want to set this deferred variable in Capistrano which depends on some variable I set during calling the task
set(:installation_dir) do
if target == "staging"
"/some/path"
else
"/some/other/path"
end
end
task :foo do
p "INSTALLATION_DIR >>> #{installation_dir}"
end
If running the task this error happens
Hector:monitoring-agent robertj$ cap foo -s target=development
/Users/robertj/.rvm/gems/ruby-2.0.0-p0#pf/gems/capistrano-2.15.4/lib/capistrano/configuration/variables.rb:122:in
`method_missing_with_variables': undefined local variable or method `target' for
#<Capistrano::Configuration:0x007fd6a22f9100> (NameError)
This is making me mad. Why doesnt Capistrano 2.x have a simple way to access variables where ever I call.
Looks like, fetch do what you want
p "INSTALLATION_DIR >>> #{fetch(:installation_dir)}"
When you run it like that, you're setting an environment variable. To use it in your Capistrano script you need to set :target, ENV['target'].

Resources