forger 1.5.4 → 1.6.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 +4 -4
- data/CHANGELOG.md +8 -0
- data/Gemfile.lock +8 -6
- data/forger.gemspec +1 -0
- data/lib/forger.rb +1 -0
- data/lib/forger/cli.rb +9 -0
- data/lib/forger/create.rb +22 -113
- data/lib/forger/create/info.rb +130 -0
- data/lib/forger/create/waiter.rb +70 -0
- data/lib/forger/destroy.rb +23 -0
- data/lib/forger/help/create.md +12 -2
- data/lib/forger/help/destroy.md +5 -0
- data/lib/forger/help/upload.md +7 -0
- data/lib/forger/script/upload.rb +17 -4
- data/lib/forger/setting.rb +33 -0
- data/lib/forger/template/helper/core_helper.rb +3 -1
- data/lib/forger/version.rb +1 -1
- data/spec/lib/cli_spec.rb +5 -0
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7f8632fbacedf3fef18dd35609c6be4d1562529addb42070483aabcf3440327
|
4
|
+
data.tar.gz: 97d4a488cd4a376e09888d9724a9ba4ec20c88ddb6c290aa3b5f5257e496572e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f20c8838736d1e26fa8ddc7da67aa453ab7975043e87ee1612cf4cb69f1e28ada67d2eebb507859341e387ee7a3a0864320c1fff699dcf7bd6e008750983da3
|
7
|
+
data.tar.gz: e931dad7f1c96d2915fc6cc3a3cc3c6574a9a6130a510cfe6c4809627a21f1976fbf6575fb072b197bef555938d8795187b5b49ba481ab8cb533956da5968e40
|
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,14 @@
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
This project *tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0.
|
5
5
|
|
6
|
+
## [1.6.0]
|
7
|
+
- Merge pull request #10 from tongueroo/waiter
|
8
|
+
- forger destroy command
|
9
|
+
- forger create --ssh
|
10
|
+
- forger create --wait
|
11
|
+
- refactor display logic to create/info
|
12
|
+
- add aws_profile support for s3_folder setting
|
13
|
+
|
6
14
|
## [1.5.4]
|
7
15
|
- rename FORGER_S3_ENDPOINT env variable setting
|
8
16
|
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
forger (1.5.
|
4
|
+
forger (1.5.4)
|
5
5
|
activesupport
|
6
6
|
aws-sdk-ec2
|
7
7
|
aws-sdk-s3
|
@@ -9,6 +9,7 @@ PATH
|
|
9
9
|
dotenv
|
10
10
|
filesize
|
11
11
|
hashie
|
12
|
+
memoist
|
12
13
|
render_me_pretty
|
13
14
|
thor
|
14
15
|
|
@@ -20,18 +21,18 @@ GEM
|
|
20
21
|
i18n (>= 0.7, < 2)
|
21
22
|
minitest (~> 5.1)
|
22
23
|
tzinfo (~> 1.1)
|
23
|
-
aws-partitions (1.
|
24
|
-
aws-sdk-core (3.
|
24
|
+
aws-partitions (1.82.0)
|
25
|
+
aws-sdk-core (3.20.2)
|
25
26
|
aws-partitions (~> 1.0)
|
26
27
|
aws-sigv4 (~> 1.0)
|
27
28
|
jmespath (~> 1.0)
|
28
|
-
aws-sdk-ec2 (1.
|
29
|
+
aws-sdk-ec2 (1.30.0)
|
29
30
|
aws-sdk-core (~> 3)
|
30
31
|
aws-sigv4 (~> 1.0)
|
31
32
|
aws-sdk-kms (1.5.0)
|
32
33
|
aws-sdk-core (~> 3)
|
33
34
|
aws-sigv4 (~> 1.0)
|
34
|
-
aws-sdk-s3 (1.9.
|
35
|
+
aws-sdk-s3 (1.9.1)
|
35
36
|
aws-sdk-core (~> 3)
|
36
37
|
aws-sdk-kms (~> 1)
|
37
38
|
aws-sigv4 (~> 1.0)
|
@@ -44,7 +45,7 @@ GEM
|
|
44
45
|
concurrent-ruby (1.0.5)
|
45
46
|
diff-lcs (1.3)
|
46
47
|
docile (1.1.5)
|
47
|
-
dotenv (2.
|
48
|
+
dotenv (2.4.0)
|
48
49
|
ffi (1.9.23)
|
49
50
|
filesize (0.1.1)
|
50
51
|
formatador (0.2.5)
|
@@ -76,6 +77,7 @@ GEM
|
|
76
77
|
rb-inotify (~> 0.9, >= 0.9.7)
|
77
78
|
ruby_dep (~> 1.2)
|
78
79
|
lumberjack (1.0.13)
|
80
|
+
memoist (0.16.0)
|
79
81
|
method_source (0.9.0)
|
80
82
|
minitest (5.11.3)
|
81
83
|
nenv (0.3.0)
|
data/forger.gemspec
CHANGED
data/lib/forger.rb
CHANGED
data/lib/forger/cli.rb
CHANGED
@@ -21,6 +21,9 @@ module Forger
|
|
21
21
|
option :ami_name, desc: "when specified, an ami creation script is appended to the user-data script"
|
22
22
|
option :randomize, type: :boolean, desc: "append random characters to end of name"
|
23
23
|
option :source_ami, desc: "override the source image_id in profile"
|
24
|
+
option :wait, desc: "Wait until the instance is ready and report dns name"
|
25
|
+
option :ssh, desc: "Wait until the instance is ready and ssh into instance"
|
26
|
+
option :ssh_user, default: "ec2-user", desc: "User to use to with the ssh option to log into instance"
|
24
27
|
common_options.call
|
25
28
|
def create(name)
|
26
29
|
Create.new(options.merge(name: name)).run
|
@@ -47,6 +50,12 @@ module Forger
|
|
47
50
|
Script::Upload.new(options).upload
|
48
51
|
end
|
49
52
|
|
53
|
+
desc "destroy", "Destroys instance accounting for spot request"
|
54
|
+
long_desc Help.text("destroy")
|
55
|
+
def destroy(instance_id)
|
56
|
+
Destroy.new(options).run(instance_id)
|
57
|
+
end
|
58
|
+
|
50
59
|
desc "completion *PARAMS", "Prints words for auto-completion."
|
51
60
|
long_desc Help.text("completion")
|
52
61
|
def completion(*params)
|
data/lib/forger/create.rb
CHANGED
@@ -5,6 +5,8 @@ module Forger
|
|
5
5
|
class Create < Base
|
6
6
|
autoload :Params, "forger/create/params"
|
7
7
|
autoload :ErrorMessages, "forger/create/error_messages"
|
8
|
+
autoload :Waiter, "forger/create/waiter"
|
9
|
+
autoload :Info, "forger/create/info"
|
8
10
|
|
9
11
|
include AwsService
|
10
12
|
include ErrorMessages
|
@@ -16,17 +18,21 @@ module Forger
|
|
16
18
|
sync_scripts_to_s3
|
17
19
|
|
18
20
|
puts "Creating EC2 instance #{@name.colorize(:green)}"
|
19
|
-
|
21
|
+
info = Info.new(@options, params)
|
22
|
+
info.ec2_params
|
20
23
|
if @options[:noop]
|
21
24
|
puts "NOOP mode enabled. EC2 instance not created."
|
22
25
|
return
|
23
26
|
end
|
24
27
|
resp = run_instances(params)
|
28
|
+
|
25
29
|
instance_id = resp.instances.first.instance_id
|
26
|
-
|
30
|
+
info.spot(instance_id)
|
27
31
|
puts "EC2 instance #{@name} created: #{instance_id} 🎉"
|
28
32
|
puts "Visit https://console.aws.amazon.com/ec2/home to check on the status"
|
29
|
-
|
33
|
+
info.cloudwatch(instance_id)
|
34
|
+
|
35
|
+
Waiter.new(@options.merge(instance_id: instance_id)).wait
|
30
36
|
end
|
31
37
|
|
32
38
|
def run_instances(params)
|
@@ -38,123 +44,26 @@ module Forger
|
|
38
44
|
# Configured by config/settings.yml.
|
39
45
|
# Example: config/settings.yml:
|
40
46
|
#
|
47
|
+
# Format 1: Simple String
|
48
|
+
#
|
49
|
+
# development:
|
50
|
+
# s3_folder: mybucket/path/to/folder
|
51
|
+
#
|
52
|
+
# Format 2: Hash
|
53
|
+
#
|
41
54
|
# development:
|
42
|
-
# s3_folder:
|
55
|
+
# s3_folder:
|
56
|
+
# default: mybucket/path/to/folder
|
57
|
+
# dev_profile1: mybucket/path/to/folder
|
58
|
+
# dev_profile1: another-bucket/storage/path
|
43
59
|
def sync_scripts_to_s3
|
44
|
-
|
45
|
-
|
46
|
-
end
|
60
|
+
return unless Forger.settings["s3_folder"]
|
61
|
+
Script::Upload.new(@options).run
|
47
62
|
end
|
48
63
|
|
49
64
|
# params are main derived from profile files
|
50
65
|
def params
|
51
66
|
@params ||= Params.new(@options).generate
|
52
67
|
end
|
53
|
-
|
54
|
-
def display_spot_info(instance_id)
|
55
|
-
resp = ec2.describe_instances(instance_ids: [instance_id])
|
56
|
-
spot_id = resp.reservations.first.instances.first.spot_instance_request_id
|
57
|
-
return unless spot_id
|
58
|
-
|
59
|
-
puts "Spot instance request id: #{spot_id}"
|
60
|
-
end
|
61
|
-
|
62
|
-
def display_ec2_info
|
63
|
-
puts "Using the following parameters:"
|
64
|
-
pretty_display(params)
|
65
|
-
|
66
|
-
display_launch_template
|
67
|
-
end
|
68
|
-
|
69
|
-
def display_launch_template
|
70
|
-
launch_template = params[:launch_template]
|
71
|
-
return unless launch_template
|
72
|
-
|
73
|
-
resp = ec2.describe_launch_template_versions(
|
74
|
-
launch_template_id: launch_template[:launch_template_id],
|
75
|
-
launch_template_name: launch_template[:launch_template_name],
|
76
|
-
)
|
77
|
-
versions = resp.launch_template_versions
|
78
|
-
launch_template_data = {} # combined launch_template_data
|
79
|
-
versions.sort_by { |v| v[:version_number] }.each do |v|
|
80
|
-
launch_template_data.merge!(v[:launch_template_data])
|
81
|
-
end
|
82
|
-
puts "launch template data (versions combined):"
|
83
|
-
pretty_display(launch_template_data)
|
84
|
-
rescue Aws::EC2::Errors::InvalidLaunchTemplateNameNotFoundException => e
|
85
|
-
puts "ERROR: The specified launched template #{launch_template.inspect} was not found."
|
86
|
-
puts "Please double check that it exists."
|
87
|
-
exit
|
88
|
-
end
|
89
|
-
|
90
|
-
def display_cloudwatch_info(instance_id)
|
91
|
-
return unless @options[:cloudwatch]
|
92
|
-
|
93
|
-
region = cloudwatch_log_region
|
94
|
-
stream = "#{instance_id}/var/log/cloud-init-output.log"
|
95
|
-
url = "https://#{region}.console.aws.amazon.com/cloudwatch/home?region=#{region}#logEventViewer:group=ec2;stream=#{stream}"
|
96
|
-
cw_init_log = "cw tail -f ec2 #{stream}"
|
97
|
-
puts "To view instance's cloudwatch logs visit:"
|
98
|
-
puts " #{url}"
|
99
|
-
|
100
|
-
puts " #{cw_init_log}" if ENV['FORGER_CW']
|
101
|
-
if ENV['FORGER_CW'] && @options[:auto_terminate]
|
102
|
-
cw_terminate_log = "cw tail -f ec2 #{instance_id}/var/log/auto-terminate.log"
|
103
|
-
puts " #{cw_terminate_log}"
|
104
|
-
end
|
105
|
-
|
106
|
-
puts "Note: It takes a little time for the instance to launch and report logs."
|
107
|
-
|
108
|
-
paste_command = ENV['FORGER_CW'] ? cw_init_log : url
|
109
|
-
add_to_clipboard(paste_command)
|
110
|
-
end
|
111
|
-
|
112
|
-
def add_to_clipboard(text)
|
113
|
-
return unless RUBY_PLATFORM =~ /darwin/
|
114
|
-
return unless system("type pbcopy > /dev/null")
|
115
|
-
|
116
|
-
system(%[echo "#{text}" | pbcopy])
|
117
|
-
puts "Pro tip: The CloudWatch Console Link has been added to your copy-and-paste clipboard."
|
118
|
-
end
|
119
|
-
|
120
|
-
def cloudwatch_log_region
|
121
|
-
# Highest precedence: FORGER_REGION env variable. Only really used here.
|
122
|
-
# This is useful to be able to override when running tool in codebuild.
|
123
|
-
# Codebuild can be running in different region then the region which the
|
124
|
-
# instance is launched in.
|
125
|
-
# Getting the region from the the profile and metadata doesnt work in
|
126
|
-
# this case.
|
127
|
-
if ENV['FORGER_REGION']
|
128
|
-
return ENV['FORGER_REGION']
|
129
|
-
end
|
130
|
-
|
131
|
-
# Pretty high in precedence: AWS_PROFILE and ~/.aws/config and
|
132
|
-
aws_found = system("type aws > /dev/null")
|
133
|
-
if aws_found
|
134
|
-
region = `aws configure get region`.strip
|
135
|
-
return region
|
136
|
-
end
|
137
|
-
|
138
|
-
# Assumes instance same region as the calling ec2 instance.
|
139
|
-
# It is possible for curl not to be installed.
|
140
|
-
curl_found = system("type curl > /dev/null")
|
141
|
-
if curl_found
|
142
|
-
region = `curl --connect-timeout 3 -s 169.254.169.254/latest/meta-data/placement/availability-zone | sed s'/.$//'`
|
143
|
-
return region unless region == ''
|
144
|
-
end
|
145
|
-
|
146
|
-
return 'us-east-1' # fallback default
|
147
|
-
end
|
148
|
-
|
149
|
-
def pretty_display(data)
|
150
|
-
data = data.deep_stringify_keys
|
151
|
-
|
152
|
-
if data["user_data"]
|
153
|
-
message = "base64-encoded: cat tmp/user-data.txt to view"
|
154
|
-
data["user_data"] = message
|
155
|
-
end
|
156
|
-
|
157
|
-
puts YAML.dump(data)
|
158
|
-
end
|
159
68
|
end
|
160
69
|
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
class Forger::Create
|
2
|
+
class Info
|
3
|
+
include Forger::AwsService
|
4
|
+
|
5
|
+
attr_reader :params
|
6
|
+
def initialize(options, params)
|
7
|
+
@options = options
|
8
|
+
@params = params
|
9
|
+
end
|
10
|
+
|
11
|
+
def ec2_params
|
12
|
+
puts "Using the following parameters:"
|
13
|
+
pretty_display(params)
|
14
|
+
|
15
|
+
launch_template
|
16
|
+
end
|
17
|
+
|
18
|
+
def spot(instance_id)
|
19
|
+
retries = 0
|
20
|
+
begin
|
21
|
+
resp = ec2.describe_instances(instance_ids: [instance_id])
|
22
|
+
rescue Aws::EC2::Errors::InvalidInstanceIDNotFound
|
23
|
+
retries += 1
|
24
|
+
puts "Aws::EC2::Errors::InvalidInstanceIDNotFound error. Retry: #{retries}"
|
25
|
+
sleep 2**retries
|
26
|
+
retry if retries <= 3
|
27
|
+
end
|
28
|
+
spot_id = resp.reservations.first.instances.first.spot_instance_request_id
|
29
|
+
return unless spot_id
|
30
|
+
|
31
|
+
puts "Spot instance request id: #{spot_id}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def launch_template
|
35
|
+
launch_template = params[:launch_template]
|
36
|
+
return unless launch_template
|
37
|
+
|
38
|
+
resp = ec2.describe_launch_template_versions(
|
39
|
+
launch_template_id: launch_template[:launch_template_id],
|
40
|
+
launch_template_name: launch_template[:launch_template_name],
|
41
|
+
)
|
42
|
+
versions = resp.launch_template_versions
|
43
|
+
launch_template_data = {} # combined launch_template_data
|
44
|
+
versions.sort_by { |v| v[:version_number] }.each do |v|
|
45
|
+
launch_template_data.merge!(v[:launch_template_data])
|
46
|
+
end
|
47
|
+
puts "launch template data (versions combined):"
|
48
|
+
pretty_display(launch_template_data)
|
49
|
+
rescue Aws::EC2::Errors::InvalidLaunchTemplateNameNotFoundException => e
|
50
|
+
puts "ERROR: The specified launched template #{launch_template.inspect} was not found."
|
51
|
+
puts "Please double check that it exists."
|
52
|
+
exit
|
53
|
+
end
|
54
|
+
|
55
|
+
def cloudwatch(instance_id)
|
56
|
+
return unless @options[:cloudwatch]
|
57
|
+
|
58
|
+
region = cloudwatch_log_region
|
59
|
+
stream = "#{instance_id}/var/log/cloud-init-output.log"
|
60
|
+
url = "https://#{region}.console.aws.amazon.com/cloudwatch/home?region=#{region}#logEventViewer:group=ec2;stream=#{stream}"
|
61
|
+
cw_init_log = "cw tail -f ec2 #{stream}"
|
62
|
+
puts "To view instance's cloudwatch logs visit:"
|
63
|
+
puts " #{url}"
|
64
|
+
|
65
|
+
puts " #{cw_init_log}" if show_cw
|
66
|
+
if show_cw && @options[:auto_terminate]
|
67
|
+
cw_terminate_log = "cw tail -f ec2 #{instance_id}/var/log/auto-terminate.log"
|
68
|
+
puts " #{cw_terminate_log}"
|
69
|
+
end
|
70
|
+
|
71
|
+
puts "Note: It takes a little time for the instance to launch and report logs."
|
72
|
+
|
73
|
+
paste_command = show_cw ? cw_init_log : url
|
74
|
+
add_to_clipboard(paste_command)
|
75
|
+
end
|
76
|
+
|
77
|
+
def cloudwatch_log_region
|
78
|
+
# Highest precedence: FORGER_REGION env variable. Only really used here.
|
79
|
+
# This is useful to be able to override when running tool in codebuild.
|
80
|
+
# Codebuild can be running in different region then the region which the
|
81
|
+
# instance is launched in.
|
82
|
+
# Getting the region from the the profile and metadata doesnt work in
|
83
|
+
# this case.
|
84
|
+
if ENV['FORGER_REGION']
|
85
|
+
return ENV['FORGER_REGION']
|
86
|
+
end
|
87
|
+
|
88
|
+
# Pretty high in precedence: AWS_PROFILE and ~/.aws/config and
|
89
|
+
aws_found = system("type aws > /dev/null")
|
90
|
+
if aws_found
|
91
|
+
region = `aws configure get region`.strip
|
92
|
+
return region
|
93
|
+
end
|
94
|
+
|
95
|
+
# Assumes instance same region as the calling ec2 instance.
|
96
|
+
# It is possible for curl not to be installed.
|
97
|
+
curl_found = system("type curl > /dev/null")
|
98
|
+
if curl_found
|
99
|
+
region = `curl --connect-timeout 3 -s 169.254.169.254/latest/meta-data/placement/availability-zone | sed s'/.$//'`
|
100
|
+
return region unless region == ''
|
101
|
+
end
|
102
|
+
|
103
|
+
return 'us-east-1' # fallback default
|
104
|
+
end
|
105
|
+
|
106
|
+
def pretty_display(data)
|
107
|
+
data = data.deep_stringify_keys
|
108
|
+
|
109
|
+
if data["user_data"]
|
110
|
+
message = "base64-encoded: cat tmp/user-data.txt to view"
|
111
|
+
data["user_data"] = message
|
112
|
+
end
|
113
|
+
|
114
|
+
puts YAML.dump(data)
|
115
|
+
end
|
116
|
+
|
117
|
+
def show_cw
|
118
|
+
ENV['FORGER_CW'] || system("type cw > /dev/null 2>&1")
|
119
|
+
end
|
120
|
+
|
121
|
+
def add_to_clipboard(text)
|
122
|
+
return unless RUBY_PLATFORM =~ /darwin/
|
123
|
+
return unless system("type pbcopy > /dev/null")
|
124
|
+
|
125
|
+
system(%[echo "#{text}" | pbcopy])
|
126
|
+
copy_item = show_cw ? "cw command" : "CloudWatch Console Link"
|
127
|
+
puts "Pro tip: The #{copy_item} has been added to your copy-and-paste clipboard."
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class Forger::Create
|
2
|
+
class Waiter < Forger::Base
|
3
|
+
include Forger::AwsService
|
4
|
+
|
5
|
+
def wait
|
6
|
+
@instance_id = @options[:instance_id]
|
7
|
+
handle_wait if @options[:wait]
|
8
|
+
handle_ssh if @options[:ssh]
|
9
|
+
end
|
10
|
+
|
11
|
+
def handle_wait
|
12
|
+
puts "Waiting for instance #{@instance_id} to be ready."
|
13
|
+
# https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/using-waiters.html
|
14
|
+
ec2.wait_until(:instance_running, instance_ids:[@instance_id]) do |w|
|
15
|
+
w.interval = 5
|
16
|
+
w.before_wait do |n, resp|
|
17
|
+
print '.'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
puts "" # newline
|
21
|
+
|
22
|
+
resp = ec2.describe_instances(instance_ids:[@instance_id])
|
23
|
+
i = resp.reservations.first.instances.first
|
24
|
+
puts "Instance #{@instance_id} is ready"
|
25
|
+
dns = i.public_dns_name ? i.public_dns_name : 'nil'
|
26
|
+
puts "Instance public_dns_name: #{dns}"
|
27
|
+
i
|
28
|
+
end
|
29
|
+
|
30
|
+
def handle_ssh
|
31
|
+
instance = handle_wait
|
32
|
+
unless instance.public_dns_name
|
33
|
+
puts "This instance does not have a public dns for ssh."
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
command = build_ssh_command(instance.public_dns_name)
|
38
|
+
puts "=> #{command.join(' ')}".colorize(:green)
|
39
|
+
retry_until_success(command)
|
40
|
+
Kernel.exec(*command) unless @options[:noop]
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_ssh_command(host)
|
44
|
+
user = @options[:ssh_user] || "ec2-user"
|
45
|
+
[
|
46
|
+
"ssh",
|
47
|
+
ENV['SSH_OPTIONS'],
|
48
|
+
"#{user}@#{host}"
|
49
|
+
].compact
|
50
|
+
end
|
51
|
+
|
52
|
+
def retry_until_success(*command)
|
53
|
+
retries = 0
|
54
|
+
uptime = command + ['uptime', '2>&1']
|
55
|
+
uptime = uptime.join(' ')
|
56
|
+
out = `#{uptime}`
|
57
|
+
while out !~ /load average/ do
|
58
|
+
puts "Can't ssh into the server yet. Retrying until success." if retries == 0
|
59
|
+
print '.'
|
60
|
+
retries += 1
|
61
|
+
if retries > 600 # Timeout after 10 minutes
|
62
|
+
raise "ERROR: Timeout after 600 seconds, cannot connect to the server."
|
63
|
+
end
|
64
|
+
sleep 1
|
65
|
+
out = `#{uptime}`
|
66
|
+
end
|
67
|
+
puts ""
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Forger
|
2
|
+
class Destroy < Base
|
3
|
+
include AwsService
|
4
|
+
|
5
|
+
def run(instance_id)
|
6
|
+
puts "Destroying #{instance_id}"
|
7
|
+
return if ENV['TEST']
|
8
|
+
|
9
|
+
cancel_spot_request(instance_id)
|
10
|
+
ec2.terminate_instances(instance_ids: [instance_id])
|
11
|
+
puts "Instance #{instance_id} terminated."
|
12
|
+
end
|
13
|
+
|
14
|
+
def cancel_spot_request(instance_id)
|
15
|
+
resp = ec2.describe_instances(instance_ids: [instance_id])
|
16
|
+
spot_id = resp.reservations.first.instances.first.spot_instance_request_id
|
17
|
+
|
18
|
+
return unless spot_id
|
19
|
+
ec2.cancel_spot_instance_requests(spot_instance_request_ids: [spot_id])
|
20
|
+
puts "Spot instance request #{spot_id} cancelled."
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/forger/help/create.md
CHANGED
@@ -1,7 +1,17 @@
|
|
1
1
|
Examples:
|
2
2
|
|
3
|
-
|
3
|
+
forger create my-instance
|
4
4
|
|
5
5
|
To see the snippet of code that gets added to the user-data script you can use the `--noop` option and then view the generated tmp/user-data.txt.
|
6
6
|
|
7
|
-
|
7
|
+
forger create myscript --noop
|
8
|
+
|
9
|
+
You can tell forger to wait until the instance is ready with the `--wait` option.
|
10
|
+
|
11
|
+
forger create my-instance --wait
|
12
|
+
|
13
|
+
You can also tell forger to ssh into the instance immediately after it's ready with the `--ssh` option. Examples:
|
14
|
+
|
15
|
+
forger create my-instance --ssh # default is to login as ec2-user
|
16
|
+
forger create my-instance --ssh --ssh-user ubuntu
|
17
|
+
SSH_OPTIONS "-A" forger create my-instance --ssh --ssh-user ubuntu
|
data/lib/forger/help/upload.md
CHANGED
@@ -6,5 +6,12 @@ Compiles the app/scripts and app/user-data files to the tmp folder. Then uploads
|
|
6
6
|
|
7
7
|
```yaml
|
8
8
|
development:
|
9
|
+
# Format 1: Simple String
|
9
10
|
s3_folder: my-bucket/folder # enables auto sync to s3
|
11
|
+
|
12
|
+
# Format 2: Hash
|
13
|
+
# s3_folder:
|
14
|
+
# default: mybucket/path/to/folder
|
15
|
+
# dev_profile1: mybucket/path/to/folder
|
16
|
+
# dev_profile1: another-bucket/storage/path
|
10
17
|
```
|
data/lib/forger/script/upload.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
require 'filesize'
|
2
2
|
require 'aws-sdk-s3'
|
3
3
|
require 'fileutils'
|
4
|
+
require 'memoist'
|
4
5
|
|
5
6
|
# Class for forger upload command
|
6
7
|
class Forger::Script
|
7
8
|
class Upload < Forger::Base
|
9
|
+
extend Memoist
|
10
|
+
|
8
11
|
def initialize(options={})
|
9
12
|
@options = options
|
10
13
|
@compile = @options[:compile] ? @options[:compile] : true
|
@@ -25,12 +28,23 @@ class Forger::Script
|
|
25
28
|
if @options[:noop]
|
26
29
|
puts "NOOP: Not uploading file to s3"
|
27
30
|
else
|
28
|
-
obj
|
31
|
+
upload_to_s3(obj, tarball_path)
|
29
32
|
end
|
30
33
|
time_took = pretty_time(Time.now-start_time).colorize(:green)
|
31
34
|
puts "Time to upload code to s3: #{time_took}"
|
32
35
|
end
|
33
36
|
|
37
|
+
def upload_to_s3(obj, tarball_path)
|
38
|
+
obj.upload_file(tarball_path)
|
39
|
+
rescue Aws::S3::Errors::PermanentRedirect => e
|
40
|
+
puts "ERROR: #{e.class} #{e.message}".colorize(:red)
|
41
|
+
puts "The bucket you are trying to upload to is in a different region than the region the instance is being launched in."
|
42
|
+
puts "You must configured FORGER_S3_ENDPOINT env variable to prevent this error. Example:"
|
43
|
+
puts " FORGER_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com"
|
44
|
+
puts "Check your ~/.aws/config for the region being used for the ec2 instance."
|
45
|
+
exit 1
|
46
|
+
end
|
47
|
+
|
34
48
|
def tarball_path
|
35
49
|
IO.read(SCRIPTS_INFO_PATH).strip
|
36
50
|
end
|
@@ -69,8 +83,6 @@ class Forger::Script
|
|
69
83
|
end
|
70
84
|
|
71
85
|
def s3_resource
|
72
|
-
return @s3_resource if @s3_resource
|
73
|
-
|
74
86
|
options = {}
|
75
87
|
# allow override of region for s3 client to avoid warning:
|
76
88
|
# S3 client configured for "us-east-1" but the bucket "xxx" is in "us-west-2"; Please configure the proper region to avoid multiple unnecessary redirects and signing attempts
|
@@ -79,8 +91,9 @@ class Forger::Script
|
|
79
91
|
if options[:endpoint]
|
80
92
|
options[:region] = options[:endpoint].split('.')[1]
|
81
93
|
end
|
82
|
-
|
94
|
+
Aws::S3::Resource.new(options)
|
83
95
|
end
|
96
|
+
memoize :s3_resource
|
84
97
|
|
85
98
|
# http://stackoverflow.com/questions/4175733/convert-duration-to-hoursminutesseconds-or-similar-in-rails-3-or-ruby
|
86
99
|
def pretty_time(total_seconds)
|
data/lib/forger/setting.rb
CHANGED
@@ -20,6 +20,39 @@ module Forger
|
|
20
20
|
all_envs = load_file(project_settings_path)
|
21
21
|
all_envs = merge_base(all_envs)
|
22
22
|
@@data = all_envs[Forger.env] || all_envs["base"] || {}
|
23
|
+
@@data = flatten_s3_folder(@@data)
|
24
|
+
@@data
|
25
|
+
end
|
26
|
+
|
27
|
+
# So we can access settings['s3_folder'] transparently
|
28
|
+
def flatten_s3_folder(data)
|
29
|
+
# data = data.clone
|
30
|
+
if data['s3_folder'].is_a?(Hash)
|
31
|
+
data['s3_folder'] = s3_folder
|
32
|
+
end
|
33
|
+
data
|
34
|
+
end
|
35
|
+
|
36
|
+
# Special helper method to support multiple formats for s3_folder setting.
|
37
|
+
# Format 1: Simple String
|
38
|
+
#
|
39
|
+
# development:
|
40
|
+
# s3_folder: mybucket/path/to/folder
|
41
|
+
#
|
42
|
+
# Format 2: Hash
|
43
|
+
#
|
44
|
+
# development:
|
45
|
+
# s3_folder:
|
46
|
+
# default: mybucket/path/to/folder
|
47
|
+
# dev_profile1: mybucket/path/to/folder
|
48
|
+
# dev_profile1: another-bucket/storage/path
|
49
|
+
#
|
50
|
+
def s3_folder
|
51
|
+
s3_folder = data['s3_folder']
|
52
|
+
return s3_folder if s3_folder.nil? or s3_folder.is_a?(String)
|
53
|
+
|
54
|
+
# If reach here then the s3_folder is a Hash
|
55
|
+
s3_folder[ENV['AWS_PROFILE']] || s3_folder["default"]
|
23
56
|
end
|
24
57
|
|
25
58
|
private
|
@@ -11,7 +11,9 @@ module Forger::Template::Helper::CoreHelper
|
|
11
11
|
name = File.basename(name, '.sh')
|
12
12
|
|
13
13
|
layout_path = layout_path(layout)
|
14
|
-
|
14
|
+
ext = File.extname(name)
|
15
|
+
name += ".sh" if ext.empty?
|
16
|
+
path = "#{Forger.root}/app/user-data/#{name}"
|
15
17
|
result = RenderMePretty.result(path, context: self, layout: layout_path)
|
16
18
|
# Must prepend and append scripts in user_data here because we need to
|
17
19
|
# encode the user_data script for valid yaml to load in the profile.
|
data/lib/forger/version.rb
CHANGED
data/spec/lib/cli_spec.rb
CHANGED
@@ -31,6 +31,11 @@ describe Forger::CLI do
|
|
31
31
|
expect(out).to include("Cleaning out old AMIs")
|
32
32
|
end
|
33
33
|
|
34
|
+
it "destroy" do
|
35
|
+
out = execute("exe/forger destroy i-123")
|
36
|
+
expect(out).to include("Destroying i-123")
|
37
|
+
end
|
38
|
+
|
34
39
|
commands = {
|
35
40
|
"am" => "ami",
|
36
41
|
"compile" => "--profile",
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: forger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tung Nguyen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-04-
|
11
|
+
date: 2018-04-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -136,6 +136,20 @@ dependencies:
|
|
136
136
|
- - ">="
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: memoist
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
139
153
|
- !ruby/object:Gem::Dependency
|
140
154
|
name: bundler
|
141
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -264,7 +278,10 @@ files:
|
|
264
278
|
- lib/forger/core.rb
|
265
279
|
- lib/forger/create.rb
|
266
280
|
- lib/forger/create/error_messages.rb
|
281
|
+
- lib/forger/create/info.rb
|
267
282
|
- lib/forger/create/params.rb
|
283
|
+
- lib/forger/create/waiter.rb
|
284
|
+
- lib/forger/destroy.rb
|
268
285
|
- lib/forger/dotenv.rb
|
269
286
|
- lib/forger/help.rb
|
270
287
|
- lib/forger/help/ami.md
|
@@ -273,6 +290,7 @@ files:
|
|
273
290
|
- lib/forger/help/completion.md
|
274
291
|
- lib/forger/help/completion_script.md
|
275
292
|
- lib/forger/help/create.md
|
293
|
+
- lib/forger/help/destroy.md
|
276
294
|
- lib/forger/help/upload.md
|
277
295
|
- lib/forger/help/wait/ami.md
|
278
296
|
- lib/forger/hook.rb
|