forger 1.5.4 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|