How can I lazily set an environment variable using env in Chef? - ruby

I put credentials into an s3 bucket and I use a ruby block to grab them. I then want to set an environment variable such that when upstart starts a process it uses this variable. However the block of ruby runs after attributes are set so I thought using lazy would be appropriate, but it's not clear to me how to set env using lazy.
Would it be something like:
ruby_block "get-credentials" do
block do
Chef::Log.info 'Getting sdk.'
require 'aws-sdk'
Chef::Log.info 'Getting making aws s3 instance.'
s3 = AWS::S3.new
Chef::Log.info 'Getting credentials from s3.'
bar = s3.buckets['bucket-name'].objects['bar'].read
Chef::Log.info 'Got bar with length #{bar.length}'
node.set['foo']['bar'] = bar
end
action :run
end
env lazy BAR=node.set['foo']['bar']
service 'foo' do
provider Chef::Provider::Service::Upstart
action [ :enable, :start ]
end
I'm not sure. I am still looking through the documentation and experimenting but maybe someone knows. The turn around on testing different variations is taking a really long time.

The env resource only works on Windows, it has nothing to do with Linux. If you want to define environment variables for an upstart service it has to go in the upstart config, as the environment within Chef has no effect on things spawned from upstart.

Related

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.

ruby whenever gem: scoping :mailto to specific tasks

Is it possible to scope :mailto to specific tasks in schedule.rb? I'm thinking something like the following:
# pseudo-code
env MAILTO=address_1#mail.com
#task_1
env MAILTO=address_2#mail.com
#task_2
#etc
The only reference I can find in the source code is in the following test case:
should "output MAILTO environment variable" do
assert_match "MAILTO=someone#example.com", #output
end
Per this question on server overflow as well as research into cron it appears that most cron daemon packages would not support having task-relative email addresses.

Passing variables between chef resources

i would like to show you my use case and then discuss possible solutions:
Problem A:
i have 2 recipes, "a" and "b".. "a" installs some program on my file system (say at "/usr/local/bin/stuff.sh" and recipe "b" needs to run this and do something with the output.
so recipe "a" looks something like:
execute "echo 'echo stuff' > /usr/local/bin/stuff.sh"
(the script just echo(es) "stuff" to stdout)
and recipe "b" looks something like:
include_recipe "a"
var=`/usr/local/bin/stuff.sh`
(note the backquotes, var should contain stuff)
and now i need to do something with it, for instance create a user with this username. so at script "b" i add
user "#{node[:var]}"
As it happens, this doesn't work.. apparently chef runs everything that is not a resource and only then runs the resources so as soon as i run the script chef complains that it cannot compile because it first tries to run the "var=..." line at recipe "b" and fails because the "execute ..." at recipe a did not run yet and so the "stuff.sh" script does not exist yet.
Needless to say, this is extremely annoying as it breaks the "Chef runs everything in order from top to bottom" that i was promised when i started using it.
However, i am not very picky so i started looking for alternative solutions to this problem, so:
Problem B: i've run across the idea of "ruby_block". apparently, this is a resource so it will be evaluated along with the other resources. I said ok, then i'd like to create the script, get the output in a "ruby_block" and then pass it to "user". so recipe "b" now looks something like:
include_recipe "a"
ruby_block "a_block" do
block do
node.default[:var] = `/usr/local/bin/stuff.sh`
end
end
user "#{node[:var]}"
However, as it turns out the variable (var) was not passed from "ruby_block" to "user" and it remains empty. No matter what juggling i've tried to do with it i failed (or maybe i just didn't find the correct juggling method)
To the chef/ruby masters around: How do i solve Problem A? How do i solve Problem B?
You have already solved problem A with the Ruby block.
Now you have to solve problem B with a similar approach:
ruby_block "create user" do
block do
user = Chef::Resource::User.new(node[:var], run_context)
user.shell '/bin/bash' # Set parameters using this syntax
user.run_action :create
user.run_action :manage # Run multiple actions (if needed) by declaring them sequentially
end
end
You could also solve problem A by creating the file during the compile phase:
execute "echo 'echo stuff' > /usr/local/bin/stuff.sh" do
action :nothing
end.run_action(:run)
If following this course of action, make sure that:
/usr/local/bin exist during Chef's compile phase;
Either:
stuff.sh is executable; OR
Execute it through a shell (e.g.: var=`sh /usr/local/bin/stuff.sh`
The modern way to do this is to use a custom resource:
in cookbooks/create_script/resources/create_script.rb
provides :create_script
unified_mode true
property :script_name, :name_property: true
action :run do
execute "creating #{script_name}" do
command "echo 'echo stuff' > #{script_name}"
not_if { File.exist?(script_name) }
end
end
Then in recipe code:
create_script "/usr/local/bin/stuff.sh"
For the second case as written I'd avoid the use of a node variable entirely:
script_location = "/usr/local/bin/stuff.sh"
create_script script_location
# note: the user resources takes a username not a file path so the example is a bit
# strange, but that is the way the question was asked.
user script_location
If you need to move it into an attribute and call it from different recipes then there's no need for ruby_blocks or lazy:
some cookbook's attributes/default.rb file (or a policyfile, etc):
default['script_location'] = "/usr/local/bin/stuff.sh"
in recipe code or other custom resources:
create_script node['script_location']
user node['script_location']
There's no need to lazy things or use ruby_block using this approach.
There are actually a few ways to solve the issue that you're having.
The first way is to avoid the scope issues you're having in the passed blocks and do something like ths.
include_recipe "a"
this = self
ruby_block "a_block" do
block do
this.user `/usr/local/bin/stuff.sh`
end
end
Assuming that you plan on only using this once, that would work great. But if you're legitimately needing to store a variable on the node for other uses you can rely on the lazy call inside ruby to do a little work around of the issue.
include_recipe "a"
ruby_block "a_block" do
block do
node.default[:var] = `/usr/local/bin/stuff.sh`.strip
end
end
user do
username lazy { "#{node[:var]}" }
end
You'll quickly notice with Chef that it has an override for all default assumptions for cases just like this.

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