forger 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.gitmodules +0 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +147 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +136 -0
- data/Guardfile +19 -0
- data/LICENSE.txt +22 -0
- data/README.md +249 -0
- data/Rakefile +6 -0
- data/docs/example/.env +2 -0
- data/docs/example/.env.development +2 -0
- data/docs/example/.env.production +3 -0
- data/docs/example/app/scripts/hello.sh +3 -0
- data/docs/example/app/user-data/bootstrap.sh +35 -0
- data/docs/example/config/development.yml +8 -0
- data/docs/example/profiles/default.yml +11 -0
- data/docs/example/profiles/spot.yml +20 -0
- data/exe/forger +14 -0
- data/forger.gemspec +38 -0
- data/lib/forger.rb +29 -0
- data/lib/forger/ami.rb +10 -0
- data/lib/forger/aws_service.rb +7 -0
- data/lib/forger/base.rb +42 -0
- data/lib/forger/clean.rb +13 -0
- data/lib/forger/cleaner.rb +5 -0
- data/lib/forger/cleaner/ami.rb +45 -0
- data/lib/forger/cli.rb +67 -0
- data/lib/forger/command.rb +67 -0
- data/lib/forger/completer.rb +161 -0
- data/lib/forger/completer/script.rb +6 -0
- data/lib/forger/completer/script.sh +10 -0
- data/lib/forger/config.rb +20 -0
- data/lib/forger/core.rb +51 -0
- data/lib/forger/create.rb +155 -0
- data/lib/forger/create/error_messages.rb +58 -0
- data/lib/forger/create/params.rb +106 -0
- data/lib/forger/dotenv.rb +30 -0
- data/lib/forger/help.rb +9 -0
- data/lib/forger/help/ami.md +13 -0
- data/lib/forger/help/clean/ami.md +22 -0
- data/lib/forger/help/compile.md +5 -0
- data/lib/forger/help/completion.md +22 -0
- data/lib/forger/help/completion_script.md +3 -0
- data/lib/forger/help/create.md +7 -0
- data/lib/forger/help/upload.md +10 -0
- data/lib/forger/help/wait/ami.md +12 -0
- data/lib/forger/hook.rb +33 -0
- data/lib/forger/profile.rb +64 -0
- data/lib/forger/script.rb +46 -0
- data/lib/forger/script/compile.rb +40 -0
- data/lib/forger/script/compress.rb +62 -0
- data/lib/forger/script/templates/ami_creation.sh +12 -0
- data/lib/forger/script/templates/auto_terminate.sh +11 -0
- data/lib/forger/script/templates/auto_terminate_after_timeout.sh +5 -0
- data/lib/forger/script/templates/cloudwatch.sh +3 -0
- data/lib/forger/script/templates/extract_aws_ec2_scripts.sh +48 -0
- data/lib/forger/script/upload.rb +99 -0
- data/lib/forger/scripts/auto_terminate.sh +14 -0
- data/lib/forger/scripts/auto_terminate/after_timeout.sh +18 -0
- data/lib/forger/scripts/auto_terminate/functions.sh +130 -0
- data/lib/forger/scripts/auto_terminate/functions/amazonlinux2.sh +10 -0
- data/lib/forger/scripts/auto_terminate/functions/ubuntu.sh +11 -0
- data/lib/forger/scripts/auto_terminate/setup.sh +31 -0
- data/lib/forger/scripts/cloudwatch.sh +24 -0
- data/lib/forger/scripts/cloudwatch/configure.sh +84 -0
- data/lib/forger/scripts/cloudwatch/install.sh +3 -0
- data/lib/forger/scripts/cloudwatch/install/amazonlinux2.sh +4 -0
- data/lib/forger/scripts/cloudwatch/install/ubuntu.sh +23 -0
- data/lib/forger/scripts/cloudwatch/service.sh +3 -0
- data/lib/forger/scripts/cloudwatch/service/amazonlinux2.sh +11 -0
- data/lib/forger/scripts/cloudwatch/service/ubuntu.sh +8 -0
- data/lib/forger/scripts/shared/functions.sh +78 -0
- data/lib/forger/setting.rb +52 -0
- data/lib/forger/template.rb +13 -0
- data/lib/forger/template/context.rb +32 -0
- data/lib/forger/template/helper.rb +17 -0
- data/lib/forger/template/helper/ami_helper.rb +33 -0
- data/lib/forger/template/helper/core_helper.rb +127 -0
- data/lib/forger/template/helper/partial_helper.rb +71 -0
- data/lib/forger/template/helper/script_helper.rb +53 -0
- data/lib/forger/template/helper/ssh_key_helper.rb +21 -0
- data/lib/forger/version.rb +3 -0
- data/lib/forger/wait.rb +12 -0
- data/lib/forger/waiter.rb +5 -0
- data/lib/forger/waiter/ami.rb +61 -0
- data/spec/fixtures/demo_project/config/settings.yml +22 -0
- data/spec/fixtures/demo_project/config/test.yml +9 -0
- data/spec/fixtures/demo_project/profiles/default.yml +33 -0
- data/spec/lib/cli_spec.rb +41 -0
- data/spec/lib/params_spec.rb +71 -0
- data/spec/spec_helper.rb +33 -0
- metadata +354 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
module Forger::Template::Helper::PartialHelper
|
2
|
+
def partial_exist?(path)
|
3
|
+
path = partial_path_for(path)
|
4
|
+
path = auto_add_format(path)
|
5
|
+
path && File.exist?(path)
|
6
|
+
end
|
7
|
+
|
8
|
+
# The partial's path is a relative path given without the extension and
|
9
|
+
#
|
10
|
+
# Example:
|
11
|
+
# Given: file in app/partials/mypartial.sh
|
12
|
+
# The path should be: mypartial
|
13
|
+
#
|
14
|
+
# If the user specifies the extension then use that instead of auto-adding
|
15
|
+
# the detected format.
|
16
|
+
def partial(path,vars={}, options={})
|
17
|
+
path = partial_path_for(path)
|
18
|
+
path = auto_add_format(path)
|
19
|
+
|
20
|
+
result = RenderMePretty.result(path, context: self)
|
21
|
+
result = indent(result, options[:indent]) if options[:indent]
|
22
|
+
if options[:indent]
|
23
|
+
# Add empty line at beginning because empty lines gets stripped during
|
24
|
+
# processing anyway. This allows the user to call partial without having
|
25
|
+
# to put the partial call at very beginning of the line.
|
26
|
+
# This only should happen if user is using indent option.
|
27
|
+
["\n", result].join("\n")
|
28
|
+
else
|
29
|
+
result
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# add indentation
|
34
|
+
def indent(text, indentation_amount)
|
35
|
+
text.split("\n").map do |line|
|
36
|
+
" " * indentation_amount + line
|
37
|
+
end.join("\n")
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def partial_path_for(path)
|
42
|
+
"#{Forger.root}/app/partials/#{path}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def auto_add_format(path)
|
46
|
+
# Return immediately if user provided explicit extension
|
47
|
+
extension = File.extname(path) # current extension
|
48
|
+
return path if !extension.empty?
|
49
|
+
|
50
|
+
# Else let's auto detect
|
51
|
+
paths = Dir.glob("#{path}.*")
|
52
|
+
|
53
|
+
if paths.size == 1 # non-ambiguous match
|
54
|
+
return paths.first
|
55
|
+
end
|
56
|
+
|
57
|
+
if paths.size > 1 # ambiguous match
|
58
|
+
puts "ERROR: Multiple possible partials found:".colorize(:red)
|
59
|
+
paths.each do |path|
|
60
|
+
puts " #{path}"
|
61
|
+
end
|
62
|
+
puts "Please specify an extension in the name to remove the ambiguity.".colorize(:green)
|
63
|
+
exit 1
|
64
|
+
end
|
65
|
+
|
66
|
+
# Account for case when user wants to include a file with no extension at all
|
67
|
+
return path if File.exist?(path) && !File.directory?(path)
|
68
|
+
|
69
|
+
path # original path if this point is reached
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Forger::Template::Helper::ScriptHelper
|
2
|
+
# Bash code that is meant to included in user-data
|
3
|
+
def extract_scripts(options={})
|
4
|
+
check_s3_folder_settings!
|
5
|
+
|
6
|
+
settings_options = settings["extract_scripts"] || {}
|
7
|
+
options = settings_options.merge(options)
|
8
|
+
# defaults also here in case they are removed from settings
|
9
|
+
to = options[:to] || "/opt"
|
10
|
+
user = options[:as] || "ec2-user"
|
11
|
+
|
12
|
+
if Dir.glob("#{Forger.root}/app/scripts*").empty?
|
13
|
+
puts "WARN: you are using the extract_scripts helper method but you do not have any app/scripts.".colorize(:yellow)
|
14
|
+
calling_line = caller[0].split(':')[0..1].join(':')
|
15
|
+
puts "Called from: #{calling_line}"
|
16
|
+
return ""
|
17
|
+
end
|
18
|
+
|
19
|
+
<<-BASH_CODE
|
20
|
+
# Generated from the forger extract_scripts helper.
|
21
|
+
# Downloads scripts from s3, extract them, and setup.
|
22
|
+
mkdir -p #{to}
|
23
|
+
aws s3 cp #{scripts_s3_path} #{to}/
|
24
|
+
(
|
25
|
+
cd #{to}
|
26
|
+
tar zxf #{to}/#{scripts_name}
|
27
|
+
chmod -R a+x #{to}/scripts
|
28
|
+
chown -R #{user}:#{user} #{to}/scripts
|
29
|
+
)
|
30
|
+
BASH_CODE
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def check_s3_folder_settings!
|
35
|
+
return if settings["s3_folder"]
|
36
|
+
|
37
|
+
puts "Helper method called that requires the s3_folder to be set at:"
|
38
|
+
lines = caller.reject { |l| l =~ %r{lib/forger} } # hide internal forger trace
|
39
|
+
puts " #{lines[0]}"
|
40
|
+
|
41
|
+
puts "Please configure your config/settings.yml with an s3_folder.".colorize(:red)
|
42
|
+
exit 1
|
43
|
+
end
|
44
|
+
|
45
|
+
def scripts_name
|
46
|
+
File.basename(scripts_s3_path)
|
47
|
+
end
|
48
|
+
|
49
|
+
def scripts_s3_path
|
50
|
+
upload = Forger::Script::Upload.new
|
51
|
+
upload.s3_dest
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Forger::Template::Helper::SshKeyHelper
|
2
|
+
def add_ssh_key(user="ec2-user")
|
3
|
+
key_path = "#{ENV['HOME']}/.ssh/id_rsa.pub"
|
4
|
+
if File.exist?(key_path)
|
5
|
+
public_key = IO.read(key_path).strip
|
6
|
+
end
|
7
|
+
if public_key
|
8
|
+
<<-SCRIPT
|
9
|
+
# Automatically add user's public key from #{key_path}
|
10
|
+
cp /home/#{user}/.ssh/authorized_keys{,.bak}
|
11
|
+
echo #{public_key} >> /home/#{user}/.ssh/authorized_keys
|
12
|
+
chown #{user}:#{user} /home/#{user}/.ssh/authorized_keys
|
13
|
+
SCRIPT
|
14
|
+
else
|
15
|
+
<<-SCRIPT
|
16
|
+
# WARN: unable to find a ~/.ssh/id_rsa.pub locally on your machine. user: #{ENV['USER']}
|
17
|
+
# Unable to automatically add the public key
|
18
|
+
SCRIPT
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/forger/wait.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
module Forger
|
2
|
+
autoload :Waiter, 'forger/waiter'
|
3
|
+
|
4
|
+
class Wait < Command
|
5
|
+
desc "ami", "Wait until AMI available."
|
6
|
+
long_desc Help.text("wait:ami")
|
7
|
+
option :timeout, type: :numeric, default: 1800, desc: "Timeout in seconds."
|
8
|
+
def ami(name)
|
9
|
+
Waiter::Ami.new(options.merge(name: name)).wait
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Forger::Waiter
|
2
|
+
class Ami < Forger::Base
|
3
|
+
include Forger::AwsService
|
4
|
+
|
5
|
+
def wait
|
6
|
+
delay = 30
|
7
|
+
timeout = @options[:timeout]
|
8
|
+
max_attempts = timeout / delay
|
9
|
+
current_time = 0
|
10
|
+
|
11
|
+
puts "Waiting for #{@options[:name]} to be available. Delay: #{delay}s. Timeout: #{timeout}s"
|
12
|
+
puts "Current time: #{Time.now}"
|
13
|
+
return if ENV['TEST']
|
14
|
+
|
15
|
+
# Using while loop because of issues with ruby's Timeout module
|
16
|
+
# http://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/
|
17
|
+
detected = detect_ami
|
18
|
+
until detected || current_time > timeout
|
19
|
+
print '.'
|
20
|
+
sleep delay
|
21
|
+
current_time += 30
|
22
|
+
detected = detect_ami
|
23
|
+
end
|
24
|
+
puts
|
25
|
+
|
26
|
+
if current_time > timeout
|
27
|
+
puts "ERROR: Timeout. Unable to detect and available ami: #{@options[:name]}"
|
28
|
+
exit 1
|
29
|
+
else
|
30
|
+
puts "Found available AMI: #{@options[:name]}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
# Using custom detect_ami instead of ec2.wait_until(:image_availalbe, ...)
|
36
|
+
# because we start checking for the ami even before we've called
|
37
|
+
# create_ami. We start checking right after we launch the instance
|
38
|
+
# which will create the ami at the end.
|
39
|
+
def detect_ami(owners=["self"])
|
40
|
+
images = ec2.describe_images(
|
41
|
+
owners: owners,
|
42
|
+
filters: filters
|
43
|
+
).images
|
44
|
+
detected = images.first
|
45
|
+
!!detected
|
46
|
+
end
|
47
|
+
|
48
|
+
def filters
|
49
|
+
name_is_ami_id = @options[:name] =~ /^ami-/
|
50
|
+
|
51
|
+
filters = [{name: "state", values: ["available"]}]
|
52
|
+
filters << if name_is_ami_id
|
53
|
+
{name: "image-id", values: [@options[:name]]}
|
54
|
+
else
|
55
|
+
{name: "name", values: [@options[:name]]}
|
56
|
+
end
|
57
|
+
|
58
|
+
filters
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
---
|
2
|
+
# Settings control internal forger behavior.
|
3
|
+
# Settings are different from the config files. The config files are meant to
|
4
|
+
# expose config variables that you can use in your ERB code.
|
5
|
+
# There are separate files to separate user defined variables and internal
|
6
|
+
# setting configs.
|
7
|
+
development:
|
8
|
+
# By setting s3_folder, forger will automatically tarball and upload your scripts
|
9
|
+
# to set. You then can then use the extract_scripts helper method to download
|
10
|
+
# the scripts onto the server.
|
11
|
+
# s3_folder: mybucket/path/to/folder # simple string
|
12
|
+
# compile_clean: true # uncomment to clean
|
13
|
+
# extract_scripts:
|
14
|
+
# to: "/opt"
|
15
|
+
# as: "ec2-user"
|
16
|
+
# aws_profiles:
|
17
|
+
# - profile1
|
18
|
+
|
19
|
+
production:
|
20
|
+
# s3_folder: mybucket/folder
|
21
|
+
# aws_profiles:
|
22
|
+
# - profile2
|
@@ -0,0 +1,33 @@
|
|
1
|
+
---
|
2
|
+
# image_id: ami-97785bed # Amazon Linux AMI
|
3
|
+
image_id: ami-4fffc834 # Amazon Lambda AMI
|
4
|
+
# https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html
|
5
|
+
instance_type: t2.medium
|
6
|
+
key_name: default
|
7
|
+
max_count: 1
|
8
|
+
min_count: 1
|
9
|
+
user_data:
|
10
|
+
iam_instance_profile:
|
11
|
+
name: DevLinux
|
12
|
+
# public network settings
|
13
|
+
security_group_ids: <%= config["security_group_ids"].inspect %>
|
14
|
+
subnet_id: <%= config["subnets"].shuffle.first %>
|
15
|
+
block_device_mappings:
|
16
|
+
- device_name: /dev/xvda
|
17
|
+
ebs:
|
18
|
+
volume_size: 20
|
19
|
+
instance_market_options:
|
20
|
+
market_type: spot
|
21
|
+
# https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_LaunchTemplateSpotMarketOptionsRequest.html
|
22
|
+
# https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/EC2/Types/SpotMarketOptions.html
|
23
|
+
spot_options:
|
24
|
+
max_price: "0.018" # $0.020/hr = $14.40/mo
|
25
|
+
# $0.018/hr = $12.96/mo
|
26
|
+
# valid combinations:
|
27
|
+
# spot_instance_type: persistent
|
28
|
+
# instance_interruption_behavior: hibernate
|
29
|
+
# or
|
30
|
+
# spot_instance_type: one-time
|
31
|
+
# More info: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-interruptions.html
|
32
|
+
spot_instance_type: persistent
|
33
|
+
instance_interruption_behavior: hibernate
|
@@ -0,0 +1,41 @@
|
|
1
|
+
describe Forger::CLI do
|
2
|
+
before(:all) do
|
3
|
+
@args = "--noop"
|
4
|
+
end
|
5
|
+
|
6
|
+
describe "forger" do
|
7
|
+
it "create" do
|
8
|
+
out = execute("exe/forger create server #{@args}")
|
9
|
+
expect(out).to include("Creating EC2 instance")
|
10
|
+
end
|
11
|
+
|
12
|
+
it "ami" do
|
13
|
+
out = execute("exe/forger ami myimage #{@args}")
|
14
|
+
expect(out).to include("Creating EC2 instance")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "wait ami" do
|
18
|
+
out = execute("exe/forger wait ami myimage")
|
19
|
+
expect(out).to include("Waiting for")
|
20
|
+
end
|
21
|
+
|
22
|
+
it "clean ami" do
|
23
|
+
out = execute("exe/forger clean ami imagebasename")
|
24
|
+
expect(out).to include("Cleaning out old AMIs")
|
25
|
+
end
|
26
|
+
|
27
|
+
commands = {
|
28
|
+
"am" => "ami",
|
29
|
+
"compile" => "--profile",
|
30
|
+
"create -" => "--profile",
|
31
|
+
"create" => "name",
|
32
|
+
"create name --" => "--profile",
|
33
|
+
}
|
34
|
+
commands.each do |command, expected_word|
|
35
|
+
it "completion #{command}" do
|
36
|
+
out = execute("exe/forger completion #{command}")
|
37
|
+
expect(out).to include(expected_word) # only checking for one word for simplicity
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
describe Forger::Create::Params do
|
2
|
+
let(:param) { Forger::Create::Params.new(name: "myserver") }
|
3
|
+
|
4
|
+
context "completely empty" do
|
5
|
+
it '#upsert_name_tag!' do
|
6
|
+
params = {}
|
7
|
+
result = param.upsert_name_tag!(params)
|
8
|
+
# puts "params: #{params.inspect}" # uncomment to see and debug
|
9
|
+
expect(result).to eq(
|
10
|
+
{"tag_specifications"=>
|
11
|
+
[{"resource_type"=>"instance",
|
12
|
+
"tags"=>[{"key"=>"Name", "value"=>"myserver"}]}]}
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "empty tag_specifications" do
|
18
|
+
it '#upsert_name_tag!' do
|
19
|
+
params = {"tag_specifications" => []}
|
20
|
+
result = param.upsert_name_tag!(params)
|
21
|
+
# puts "params: #{params.inspect}" # uncomment to see and debug
|
22
|
+
expect(result).to eq(
|
23
|
+
{"tag_specifications"=>
|
24
|
+
[{"resource_type"=>"instance",
|
25
|
+
"tags"=>[{"key"=>"Name", "value"=>"myserver"}]}]}
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "contains 1 instance with name" do
|
31
|
+
it '#upsert_name_tag!' do
|
32
|
+
params = { "tag_specifications" =>
|
33
|
+
[{
|
34
|
+
"resource_type"=>"instance",
|
35
|
+
"tags"=> [{"key"=>"Name", "value"=>"override-myserver"} ]
|
36
|
+
}]
|
37
|
+
}
|
38
|
+
result = param.upsert_name_tag!(params)
|
39
|
+
# puts "params: #{params.inspect}" # uncomment to see and debug
|
40
|
+
expect(result).to eq(
|
41
|
+
{"tag_specifications"=>
|
42
|
+
[{"resource_type"=>"instance",
|
43
|
+
"tags"=>[{"key"=>"Name", "value"=>"override-myserver"}]}]}
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "contains 1 instance with non-name tag" do
|
49
|
+
it '#upsert_name_tag!' do
|
50
|
+
params = { "tag_specifications" =>
|
51
|
+
[{
|
52
|
+
"resource_type"=>"instance",
|
53
|
+
"tags"=> [{"key"=>"Os", "value"=>"amazonlinux"} ]
|
54
|
+
}]
|
55
|
+
}
|
56
|
+
result = param.upsert_name_tag!(params)
|
57
|
+
# puts "params: #{params.inspect}" # uncomment to see and debug
|
58
|
+
expect(result).to eq(
|
59
|
+
{ "tag_specifications" =>
|
60
|
+
[{
|
61
|
+
"resource_type"=>"instance",
|
62
|
+
"tags"=> [
|
63
|
+
{"key"=>"Os", "value"=>"amazonlinux"},
|
64
|
+
{"key"=>"Name", "value"=>"myserver"},
|
65
|
+
]
|
66
|
+
}]
|
67
|
+
}
|
68
|
+
)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
ENV["TEST"] = "1"
|
2
|
+
ENV["AWS_EC2_ENV"] = "test"
|
3
|
+
ENV["AWS_EC2_ROOT"] = "spec/fixtures/demo_project"
|
4
|
+
# Ensures aws api never called. Fixture home folder does not contain ~/.aws/credentails
|
5
|
+
ENV['HOME'] = "spec/fixtures/home"
|
6
|
+
|
7
|
+
# CodeClimate test coverage: https://docs.codeclimate.com/docs/configuring-test-coverage
|
8
|
+
# require 'simplecov'
|
9
|
+
# SimpleCov.start
|
10
|
+
|
11
|
+
require "pp"
|
12
|
+
require "byebug"
|
13
|
+
root = File.expand_path("../", File.dirname(__FILE__))
|
14
|
+
require "#{root}/lib/forger"
|
15
|
+
|
16
|
+
module Helper
|
17
|
+
def execute(cmd)
|
18
|
+
puts "Running: #{cmd}" if show_command?
|
19
|
+
out = `#{cmd}`
|
20
|
+
puts out if show_command?
|
21
|
+
out
|
22
|
+
end
|
23
|
+
|
24
|
+
# Added SHOW_COMMAND because DEBUG is also used by other libraries like
|
25
|
+
# bundler and it shows its internal debugging logging also.
|
26
|
+
def show_command?
|
27
|
+
ENV['DEBUG'] || ENV['SHOW_COMMAND']
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
RSpec.configure do |c|
|
32
|
+
c.include Helper
|
33
|
+
end
|