cfn_manage 0.1.3 → 0.2.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/lib/alarm_start_stop_handler.rb +39 -0
- data/lib/asg_start_stop_handler.rb +70 -0
- data/lib/cf_start_stop_environment.rb +34 -235
- data/lib/ec2_start_stop_handler.rb +41 -0
- data/lib/rds_start_stop_handler.rb +128 -0
- data/lib/start_stop_handler_factory.rb +32 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 69073fce6ae1e187369356d37b9180d21560b3de
|
4
|
+
data.tar.gz: 4d73c20f1873ec4abab56bf6b82bf429b6920995
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78e8e4f22bbc90e8ff23a6f9c0a521c5ef58085856ca85eccf8f52e8903b8121251105673d2e959a431be3d8eac9cb7ba0a46d3aa8dcf719948d8565f4f59c9b
|
7
|
+
data.tar.gz: c6fa186c4c1c14b39d54febf5c36b7e316b20ae2ed61e31da2aee16c7b5a2fbf0ae079a6d11447e06e5eefb198cb26952d05de98842f314a67ed9fa49bcada02
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative '../lib/aws_credentials'
|
2
|
+
|
3
|
+
module Base2
|
4
|
+
|
5
|
+
class AlarmStartStopHandler
|
6
|
+
|
7
|
+
def initialize(alarm_name)
|
8
|
+
@alarm_id = alarm_name
|
9
|
+
credentials = Base2::AWSCredentials.get_session_credentials("startstopalarm_#{@asg_name}")
|
10
|
+
@cwclient = Aws::CloudWatch::Client.new()
|
11
|
+
if credentials != nil
|
12
|
+
@cwclient = Aws::CloudWatch::Client.new(credentials: credentials)
|
13
|
+
end
|
14
|
+
|
15
|
+
@cwresource = Aws::CloudWatch::Resource.new(client: @cwclient)
|
16
|
+
@alarm = @cwresource.alarm(alarm_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def start(configuration)
|
20
|
+
if @alarm.actions_enabled
|
21
|
+
$log.info("Alarm #{@alarm.alarm_arn} actions already enabled")
|
22
|
+
return
|
23
|
+
end
|
24
|
+
$log.info("Enabling alarm #{@alarm.alarm_arn}")
|
25
|
+
@alarm.enable_actions({})
|
26
|
+
end
|
27
|
+
|
28
|
+
def stop
|
29
|
+
if not @alarm.actions_enabled
|
30
|
+
$log.info("Alarm #{@alarm.alarm_arn} actions already disabled")
|
31
|
+
return {}
|
32
|
+
end
|
33
|
+
$log.info("Disabling actions on alarm #{@alarm.alarm_arn}")
|
34
|
+
@alarm.disable_actions({})
|
35
|
+
return {}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require_relative '../lib/aws_credentials'
|
2
|
+
|
3
|
+
module Base2
|
4
|
+
|
5
|
+
class AsgStartStopHandler
|
6
|
+
|
7
|
+
def initialize(asg_id)
|
8
|
+
@asg_name = asg_id
|
9
|
+
|
10
|
+
credentials = Base2::AWSCredentials.get_session_credentials("stopasg_#{@asg_name}")
|
11
|
+
@asg_client = Aws::AutoScaling::Client.new()
|
12
|
+
if credentials != nil
|
13
|
+
@asg_client = Aws::AutoScaling::Client.new(credentials: credentials)
|
14
|
+
end
|
15
|
+
|
16
|
+
asg_details = @asg_client.describe_auto_scaling_groups(
|
17
|
+
auto_scaling_group_names: [@asg_name]
|
18
|
+
)
|
19
|
+
if asg_details.auto_scaling_groups.size() == 0
|
20
|
+
raise "Couldn't find ASG #{@asg_name}"
|
21
|
+
end
|
22
|
+
@asg = asg_details.auto_scaling_groups[0]
|
23
|
+
end
|
24
|
+
|
25
|
+
def stop
|
26
|
+
# check if already stopped
|
27
|
+
if @asg.min_size == @asg.max_size and @asg.max_size == @asg.desired_capacity and @asg.min_size == 0
|
28
|
+
$log.info("ASG #{@asg_name} already stopped")
|
29
|
+
# nil and false configurations are not saved
|
30
|
+
return nil
|
31
|
+
else
|
32
|
+
# store asg configuration to S3
|
33
|
+
configuration = {
|
34
|
+
desired_capacity: @asg.desired_capacity,
|
35
|
+
min_size: @asg.min_size,
|
36
|
+
max_size: @asg.max_size
|
37
|
+
}
|
38
|
+
|
39
|
+
$log.info("Setting desired capacity to 0/0/0 for ASG #{@asg.auto_scaling_group_name}A")
|
40
|
+
# set asg configuration to 0/0/0
|
41
|
+
puts @asg.auto_scaling_group_name
|
42
|
+
@asg_client.update_auto_scaling_group({
|
43
|
+
auto_scaling_group_name: "#{@asg.auto_scaling_group_name}",
|
44
|
+
min_size: 0,
|
45
|
+
max_size: 0,
|
46
|
+
desired_capacity: 0
|
47
|
+
})
|
48
|
+
return configuration
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
def start(configuration)
|
54
|
+
if configuration.nil?
|
55
|
+
$log.warn("No configuration found for #{@asg_name}, skipping..")
|
56
|
+
return
|
57
|
+
end
|
58
|
+
$log.info("Starting ASG #{@asg_name} with following configuration\n#{configuration}")
|
59
|
+
|
60
|
+
# restore asg sizes
|
61
|
+
@asg_client.update_auto_scaling_group({
|
62
|
+
auto_scaling_group_name: @asg_name,
|
63
|
+
min_size: configuration['min_size'],
|
64
|
+
max_size: configuration['max_size'],
|
65
|
+
desired_capacity: configuration['desired_capacity']
|
66
|
+
})
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -3,6 +3,7 @@ require_relative '../lib/cf_common'
|
|
3
3
|
require_relative '../lib/aws_credentials'
|
4
4
|
require 'json'
|
5
5
|
require 'yaml'
|
6
|
+
require_relative '../lib/start_stop_handler_factory'
|
6
7
|
|
7
8
|
module Base2
|
8
9
|
module CloudFormation
|
@@ -14,20 +15,14 @@ module Base2
|
|
14
15
|
@s3_bucket = nil
|
15
16
|
@credentials = nil
|
16
17
|
@dry_run = false
|
17
|
-
@@supported_start_stop_resources = {
|
18
|
-
'AWS::AutoScaling::AutoScalingGroup' => 'start_stop_asg',
|
19
|
-
'AWS::RDS::DBInstance' => 'start_stop_rds',
|
20
|
-
'AWS::EC2::Instance' => 'start_stop_ec2'
|
21
|
-
}
|
22
18
|
|
23
19
|
@@resource_start_priorities = {
|
24
20
|
'AWS::RDS::DBInstance' => '100',
|
25
21
|
'AWS::AutoScaling::AutoScalingGroup' => '200',
|
26
|
-
'AWS::EC2::Instance' => '200'
|
22
|
+
'AWS::EC2::Instance' => '200',
|
23
|
+
'AWS::CloudWatch::Alarm' => '300'
|
27
24
|
}
|
28
25
|
|
29
|
-
@environment_resources = nil
|
30
|
-
|
31
26
|
def initialize()
|
32
27
|
@environment_resources = []
|
33
28
|
@s3_client = Aws::S3::Client.new()
|
@@ -35,7 +30,7 @@ module Base2
|
|
35
30
|
@cf_client = Aws::CloudFormation::Client.new()
|
36
31
|
@credentials = Base2::AWSCredentials.get_session_credentials('start_stop_environment')
|
37
32
|
if not @credentials.nil?
|
38
|
-
@cf_client =
|
33
|
+
@cf_client = Aws::CloudFormation::Client.new(credentials: @credentials)
|
39
34
|
end
|
40
35
|
@dry_run = (ENV.key?('DRY_RUN') and ENV['DRY_RUN'] == '1')
|
41
36
|
end
|
@@ -45,7 +40,7 @@ module Base2
|
|
45
40
|
$log.info("Starting environment #{stack_name}")
|
46
41
|
Common.visit_stack(@cf_client, stack_name, method(:collect_resources), true)
|
47
42
|
do_start_assets
|
48
|
-
configuration = {stack_running: true}
|
43
|
+
configuration = { stack_running: true }
|
49
44
|
save_item_configuration("environment-data/stack-data/#{stack_name}", configuration) unless @dry_run
|
50
45
|
$log.info("Environment #{stack_name} started")
|
51
46
|
end
|
@@ -55,7 +50,7 @@ module Base2
|
|
55
50
|
$log.info("Stopping environment #{stack_name}")
|
56
51
|
Common.visit_stack(@cf_client, stack_name, method(:collect_resources), true)
|
57
52
|
do_stop_assets
|
58
|
-
configuration = {stack_running: false}
|
53
|
+
configuration = { stack_running: false }
|
59
54
|
save_item_configuration("environment-data/stack-data/#{stack_name}", configuration) unless @dry_run
|
60
55
|
$log.info("Environment #{stack_name} stopped")
|
61
56
|
end
|
@@ -63,13 +58,17 @@ module Base2
|
|
63
58
|
|
64
59
|
def do_stop_assets
|
65
60
|
# sort start resource by priority
|
66
|
-
@environment_resources = @environment_resources.sort_by { |k| k[:priority]}.reverse
|
61
|
+
@environment_resources = @environment_resources.sort_by { |k| k[:priority] }.reverse
|
67
62
|
|
68
63
|
@environment_resources.each do |resource|
|
69
64
|
$log.info("Stopping resource #{resource[:id]}")
|
70
65
|
# just print out information if running a dry run, otherwise start assets
|
71
66
|
if not @dry_run
|
72
|
-
|
67
|
+
configuration = resource[:handler].stop()
|
68
|
+
if configuration.class == Hash
|
69
|
+
s3_prefix = "environment_data/resource/#{resource[:id]}"
|
70
|
+
save_item_configuration(s3_prefix, configuration)
|
71
|
+
end
|
73
72
|
else
|
74
73
|
$log.info("Dry run enabled, skipping stop start\nFollowing resource would be stopped: #{resource[:id]}")
|
75
74
|
$log.debug("Resource type: #{resource[:type]}\n\n")
|
@@ -77,16 +76,20 @@ module Base2
|
|
77
76
|
end
|
78
77
|
end
|
79
78
|
|
80
|
-
|
81
79
|
def do_start_assets
|
82
80
|
# sort start resource by priority
|
83
|
-
@environment_resources = @environment_resources.sort_by { |k| k[:priority]}
|
81
|
+
@environment_resources = @environment_resources.sort_by { |k| k[:priority] }
|
84
82
|
|
85
83
|
@environment_resources.each do |resource|
|
86
84
|
$log.info("Starting resource #{resource[:id]}")
|
87
85
|
# just print out information if running a dry run, otherwise start assets
|
88
86
|
if not @dry_run
|
89
|
-
|
87
|
+
# read configuration
|
88
|
+
s3_prefix = "environment_data/resource/#{resource[:id]}"
|
89
|
+
configuration = get_object_configuration(s3_prefix)
|
90
|
+
|
91
|
+
# start
|
92
|
+
resource[:handler].start(configuration)
|
90
93
|
else
|
91
94
|
$log.info("Dry run enabled, skipping actual start\nFollowing resource would be started: #{resource[:id]}")
|
92
95
|
$log.debug("Resource type: #{resource[:type]}\n\n")
|
@@ -97,235 +100,28 @@ module Base2
|
|
97
100
|
def collect_resources(stack_name)
|
98
101
|
resrouces = @cf_client.describe_stack_resources(stack_name: stack_name)
|
99
102
|
resrouces['stack_resources'].each do |resource|
|
100
|
-
|
101
|
-
|
103
|
+
start_stop_handler = nil
|
104
|
+
begin
|
105
|
+
start_stop_handler = Base2::StartStopHandlerFactory.get_start_stop_handler(
|
106
|
+
resource['resource_type'],
|
107
|
+
resource['physical_resource_id']
|
108
|
+
)
|
109
|
+
rescue Exception => e
|
110
|
+
$log.error("Error creating start-stop handler for resource of type #{resource['resource_type']}" +
|
111
|
+
"and with id #{resource['physical_resource_id']}:#{e}")
|
112
|
+
end
|
113
|
+
if not start_stop_handler.nil?
|
102
114
|
resource_id = resource['physical_resource_id']
|
103
|
-
|
104
115
|
@environment_resources << {
|
105
116
|
id: resource_id,
|
106
117
|
priority: @@resource_start_priorities[resource['resource_type']],
|
107
|
-
|
118
|
+
handler: start_stop_handler,
|
108
119
|
type: resource['resource_type']
|
109
120
|
}
|
110
121
|
end
|
111
122
|
end
|
112
123
|
end
|
113
124
|
|
114
|
-
def start_stop_ec2(cmd, instance_id)
|
115
|
-
credentials = Base2::AWSCredentials.get_session_credentials("stoprun_#{instance_id}")
|
116
|
-
ec2_client = Aws::EC2::Client.new(credentials: credentials)
|
117
|
-
|
118
|
-
begin
|
119
|
-
instance = Aws::EC2::Resource.new(client: ec2_client).instance(instance_id)
|
120
|
-
|
121
|
-
if cmd == 'stop'
|
122
|
-
if %w(stopped stopping).include?(instance.state.name)
|
123
|
-
$log.info("Instance #{instance_id} already stopping or stopped")
|
124
|
-
return
|
125
|
-
end
|
126
|
-
$log.info("Stopping instance #{instance_id}")
|
127
|
-
instance.stop()
|
128
|
-
end
|
129
|
-
|
130
|
-
if cmd == 'start'
|
131
|
-
if %w(running).include?(instance.state.name)
|
132
|
-
$log.info("Instance #{instance_id} already running")
|
133
|
-
return
|
134
|
-
end
|
135
|
-
$log.info("Starting instance #{instance_id}")
|
136
|
-
instance.start()
|
137
|
-
end
|
138
|
-
rescue => e
|
139
|
-
$log.error("Failed execution #{cmd} on instance #{instance_id}:\n#{e}")
|
140
|
-
end
|
141
|
-
|
142
|
-
end
|
143
|
-
|
144
|
-
def start_stop_asg(cmd, asg_name)
|
145
|
-
|
146
|
-
# read asg data
|
147
|
-
credentials = Base2::AWSCredentials.get_session_credentials("stopasg_#{asg_name}")
|
148
|
-
asg_client = Aws::AutoScaling::Client.new()
|
149
|
-
if credentials != nil
|
150
|
-
asg_client = Aws::AutoScaling::Client.new(credentials: credentials)
|
151
|
-
end
|
152
|
-
|
153
|
-
asg_details = asg_client.describe_auto_scaling_groups(
|
154
|
-
auto_scaling_group_names: [asg_name]
|
155
|
-
)
|
156
|
-
if asg_details.auto_scaling_groups.size() == 0
|
157
|
-
raise "Couldn't find ASG #{asg_name}"
|
158
|
-
end
|
159
|
-
asg = asg_details.auto_scaling_groups[0]
|
160
|
-
s3_prefix = "environment-data/asg-data/#{asg_name}"
|
161
|
-
case cmd
|
162
|
-
when 'start'
|
163
|
-
|
164
|
-
# retrieve asg params from s3
|
165
|
-
configuration = self.get_object_configuration(s3_prefix)
|
166
|
-
if configuration.nil?
|
167
|
-
$log.warn("No configuration found for #{asg_name}, skipping..")
|
168
|
-
return
|
169
|
-
end
|
170
|
-
$log.info("Starting ASG #{asg_name} with following configuration\n#{configuration}")
|
171
|
-
|
172
|
-
# restore asg sizes
|
173
|
-
asg_client.update_auto_scaling_group({
|
174
|
-
auto_scaling_group_name: asg_name,
|
175
|
-
min_size: configuration['min_size'],
|
176
|
-
max_size: configuration['max_size'],
|
177
|
-
desired_capacity: configuration['desired_capacity']
|
178
|
-
})
|
179
|
-
|
180
|
-
when 'stop'
|
181
|
-
# check if already stopped
|
182
|
-
if asg.min_size == asg.max_size and asg.max_size == asg.desired_capacity and asg.min_size == 0
|
183
|
-
$log.info("ASG #{asg_name} already stopped")
|
184
|
-
else
|
185
|
-
# store asg configuration to S3
|
186
|
-
configuration = {
|
187
|
-
desired_capacity: asg.desired_capacity,
|
188
|
-
min_size: asg.min_size,
|
189
|
-
max_size: asg.max_size
|
190
|
-
}
|
191
|
-
self.save_item_configuration(s3_prefix, configuration)
|
192
|
-
|
193
|
-
$log.info("Setting desired capacity to 0/0/0 for ASG #{asg_name}")
|
194
|
-
# set asg configuration to 0/0/0
|
195
|
-
asg_client.update_auto_scaling_group({
|
196
|
-
auto_scaling_group_name: asg_name,
|
197
|
-
min_size: 0,
|
198
|
-
max_size: 0,
|
199
|
-
desired_capacity: 0
|
200
|
-
})
|
201
|
-
end
|
202
|
-
# TODO wait for operation to complete (optionally)
|
203
|
-
end
|
204
|
-
|
205
|
-
|
206
|
-
end
|
207
|
-
|
208
|
-
def start_stop_rds(cmd, instance_id)
|
209
|
-
credentials = Base2::AWSCredentials.get_session_credentials("startstoprds_#{instance_id}")
|
210
|
-
rds_client = Aws::RDS::Client.new()
|
211
|
-
if credentials != nil
|
212
|
-
rds_client = Aws::RDS::Client.new(credentials: credentials)
|
213
|
-
end
|
214
|
-
rds = Aws::RDS::Resource.new(client: rds_client)
|
215
|
-
rds_instance = rds.db_instance(instance_id)
|
216
|
-
s3_prefix = "environment-data/rds-data/#{instance_id}"
|
217
|
-
case cmd
|
218
|
-
when 'start'
|
219
|
-
if rds_instance.db_instance_status == 'available'
|
220
|
-
$log.info("RDS Instance #{instance_id} is already in available state")
|
221
|
-
return
|
222
|
-
end
|
223
|
-
|
224
|
-
#retrieve multi-az data from S3
|
225
|
-
configuration = get_object_configuration(s3_prefix)
|
226
|
-
if configuration.nil?
|
227
|
-
$log.warning("No configuration found for #{rds_instance}, skipping..")
|
228
|
-
return
|
229
|
-
end
|
230
|
-
|
231
|
-
# start rds instance
|
232
|
-
if rds_instance.db_instance_status == 'stopped'
|
233
|
-
$log.info("Starting db instance #{instance_id}")
|
234
|
-
rds_client.start_db_instance({ db_instance_identifier: instance_id })
|
235
|
-
|
236
|
-
# wait instance to become available
|
237
|
-
$log.info("Waiting db instance to become available #{instance_id}")
|
238
|
-
wait_rds_instance_states(rds_client, instance_id, %w(starting available))
|
239
|
-
else
|
240
|
-
wait_rds_instance_states(rds_client, instance_id, %w(available))
|
241
|
-
end
|
242
|
-
|
243
|
-
# convert rds instance to mutli-az if required
|
244
|
-
if configuration['is_multi_az']
|
245
|
-
$log.info("Converting to Multi-AZ instance after start (instance #{instance_id})")
|
246
|
-
set_rds_instance_multi_az(rds_instance, true)
|
247
|
-
end
|
248
|
-
|
249
|
-
when 'stop'
|
250
|
-
# store mutli-az data to S3
|
251
|
-
if rds_instance.db_instance_status != 'available'
|
252
|
-
$log.warn("RDS Instance #{instance_id} not in available state, and thus can not be stopped")
|
253
|
-
$log.warn("RDS Instance #{instance_id} state: #{rds_instance.db_instance_status}")
|
254
|
-
return
|
255
|
-
end
|
256
|
-
|
257
|
-
if rds_instance.db_instance_status == 'stopped'
|
258
|
-
$log.info("RDS Instance #{instance_id} is already stopped")
|
259
|
-
return
|
260
|
-
end
|
261
|
-
|
262
|
-
# RDS stop start does not support Aurora yet. Ignore if engine is aurora
|
263
|
-
if rds_instance.engine == 'aurora'
|
264
|
-
$log.info("RDS Instance #{instance_id} engine is aurora and cannot be stoped yet...")
|
265
|
-
return
|
266
|
-
end
|
267
|
-
|
268
|
-
configuration = {
|
269
|
-
is_multi_az: rds_instance.multi_az
|
270
|
-
}
|
271
|
-
save_item_configuration(s3_prefix, configuration)
|
272
|
-
|
273
|
-
#check if mutli-az RDS. if so, convert to single-az
|
274
|
-
if rds_instance.multi_az
|
275
|
-
$log.info("Converting to Non-Multi-AZ instance before stop (instance #{instance_id}")
|
276
|
-
set_rds_instance_multi_az(rds_instance, false)
|
277
|
-
end
|
278
|
-
|
279
|
-
# stop rds instance and wait for it to be fully stopped
|
280
|
-
$log.info("Stopping instance #{instance_id}")
|
281
|
-
rds_client.stop_db_instance({ db_instance_identifier: instance_id })
|
282
|
-
$log.info("Waiting db instance to be stopped #{instance_id}")
|
283
|
-
wait_rds_instance_states(rds_client, instance_id, %w(stopping stopped))
|
284
|
-
end
|
285
|
-
end
|
286
|
-
|
287
|
-
def set_rds_instance_multi_az(rds_instance, multi_az)
|
288
|
-
if rds_instance.multi_az == multi_az
|
289
|
-
$log.info("Rds instance #{rds_instance.db_instance_identifier} already multi-az=#{multi_az}")
|
290
|
-
return
|
291
|
-
end
|
292
|
-
rds_instance.modify({ multi_az: multi_az, apply_immediately: true })
|
293
|
-
# allow half an hour for instance to be converted
|
294
|
-
wait_states = %w(modifying available)
|
295
|
-
wait_rds_instance_states(rds_instance, wait_states)
|
296
|
-
end
|
297
|
-
|
298
|
-
def wait_rds_instance_states(client, rds_instance_id, wait_states)
|
299
|
-
wait_states.each do |state|
|
300
|
-
# reached state must be steady, at least a minute. Modifying an instance to/from MultiAZ can't be shorter
|
301
|
-
# than 40 seconds, hence steady count is 4
|
302
|
-
state_count = 0
|
303
|
-
steady_count = 4
|
304
|
-
attempts = 0
|
305
|
-
rds = Aws::RDS::Resource.new(client: client)
|
306
|
-
until attempts == (max_attempts = 60*6) do
|
307
|
-
instance = rds.db_instance(rds_instance_id)
|
308
|
-
$log.info("Instance #{instance.db_instance_identifier} state: #{instance.db_instance_status}, waiting for #{state}")
|
309
|
-
|
310
|
-
if instance.db_instance_status == "#{state}"
|
311
|
-
state_count = state_count + 1
|
312
|
-
$log.info("#{state_count}/#{steady_count}")
|
313
|
-
else
|
314
|
-
state_count = 0
|
315
|
-
end
|
316
|
-
break if state_count == steady_count
|
317
|
-
attempts = attempts + 1
|
318
|
-
sleep(15)
|
319
|
-
end
|
320
|
-
|
321
|
-
if attempts == max_attempts
|
322
|
-
$log.error("RDS Database Instance #{rds_instance_id} did not enter #{state} state, however continuing operations...")
|
323
|
-
end
|
324
|
-
|
325
|
-
end
|
326
|
-
end
|
327
|
-
|
328
|
-
|
329
125
|
def get_object_configuration(s3_prefix)
|
330
126
|
configuration = nil
|
331
127
|
begin
|
@@ -358,6 +154,9 @@ module Base2
|
|
358
154
|
})
|
359
155
|
end
|
360
156
|
end
|
157
|
+
|
158
|
+
private :do_stop_assets, :do_start_assets, :collect_resources, :get_object_configuration, :save_item_configuration
|
159
|
+
|
361
160
|
end
|
362
161
|
end
|
363
162
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative '../lib/aws_credentials'
|
2
|
+
|
3
|
+
module Base2
|
4
|
+
|
5
|
+
class Ec2StartStopHandler
|
6
|
+
|
7
|
+
@instance
|
8
|
+
|
9
|
+
def initialize(instance_id)
|
10
|
+
credentials = Base2::AWSCredentials.get_session_credentials("stoprun_#{instance_id}")
|
11
|
+
ec2_client = Aws::EC2::Client.new(credentials: credentials)
|
12
|
+
@instance = Aws::EC2::Resource.new(client: ec2_client).instance(instance_id)
|
13
|
+
@instance_id = instance_id
|
14
|
+
end
|
15
|
+
|
16
|
+
def start(configuration)
|
17
|
+
if %w(running).include?(@instance.state.name)
|
18
|
+
$log.info("Instance #{@instance_id} already running")
|
19
|
+
return
|
20
|
+
end
|
21
|
+
$log.info("Starting instance #{@instance_id}")
|
22
|
+
@instance.start()
|
23
|
+
end
|
24
|
+
|
25
|
+
def stop
|
26
|
+
if %w(stopped stopping).include?(@instance.state.name)
|
27
|
+
$log.info("Instance #{@instance_id} already stopping or stopped")
|
28
|
+
return
|
29
|
+
end
|
30
|
+
$log.info("Stopping instance #{@instance_id}")
|
31
|
+
@instance.stop()
|
32
|
+
|
33
|
+
# empty configuration for ec2 instances
|
34
|
+
return {}
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require_relative '../lib/aws_credentials'
|
2
|
+
|
3
|
+
module Base2
|
4
|
+
|
5
|
+
class RdsStartStopHandler
|
6
|
+
|
7
|
+
def initialize(instance_id)
|
8
|
+
@instance_id = instance_id
|
9
|
+
|
10
|
+
credentials = Base2::AWSCredentials.get_session_credentials("startstoprds_#{instance_id}")
|
11
|
+
@rds_client = Aws::RDS::Client.new()
|
12
|
+
if credentials != nil
|
13
|
+
@rds_client = Aws::RDS::Client.new(credentials: credentials)
|
14
|
+
end
|
15
|
+
rds = Aws::RDS::Resource.new(client: @rds_client)
|
16
|
+
@rds_instance = rds.db_instance(instance_id)
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def start(configuration)
|
21
|
+
if @rds_instance.db_instance_status == 'available'
|
22
|
+
$log.info("RDS Instance #{@instance_id} is already in available state")
|
23
|
+
return
|
24
|
+
end
|
25
|
+
|
26
|
+
# start rds instance
|
27
|
+
if @rds_instance.db_instance_status == 'stopped'
|
28
|
+
$log.info("Starting db instance #{@instance_id}")
|
29
|
+
@rds_client.start_db_instance({ db_instance_identifier: @instance_id })
|
30
|
+
|
31
|
+
# wait instance to become available
|
32
|
+
$log.info("Waiting db instance to become available #{@instance_id}")
|
33
|
+
wait_rds_instance_states( %w(starting available))
|
34
|
+
else
|
35
|
+
wait_rds_instance_states( %w(available))
|
36
|
+
end
|
37
|
+
|
38
|
+
# convert rds instance to mutli-az if required
|
39
|
+
if configuration['is_multi_az']
|
40
|
+
$log.info("Converting to Multi-AZ instance after start (instance #{@instance_id})")
|
41
|
+
set_rds_instance_multi_az( true)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def stop
|
46
|
+
|
47
|
+
configuration = {
|
48
|
+
is_multi_az: @rds_instance.multi_az
|
49
|
+
}
|
50
|
+
# RDS stop start does not support Aurora yet. Ignore if engine is aurora
|
51
|
+
if @rds_instance.engine == 'aurora'
|
52
|
+
$log.info("RDS Instance #{instance_id} engine is aurora and cannot be stoped yet...")
|
53
|
+
return configuration
|
54
|
+
end
|
55
|
+
|
56
|
+
# check if available
|
57
|
+
if @rds_instance.db_instance_status != 'available'
|
58
|
+
$log.warn("RDS Instance #{@instance_id} not in available state, and thus can not be stopped")
|
59
|
+
$log.warn("RDS Instance #{@instance_id} state: #{@rds_instance.db_instance_status}")
|
60
|
+
return configuration
|
61
|
+
end
|
62
|
+
|
63
|
+
# check if already stopped
|
64
|
+
if @rds_instance.db_instance_status == 'stopped'
|
65
|
+
$log.info("RDS Instance #{@instance_id} is already stopped")
|
66
|
+
return configuration
|
67
|
+
end
|
68
|
+
|
69
|
+
#check if mutli-az RDS. if so, convert to single-az
|
70
|
+
if @rds_instance.multi_az
|
71
|
+
$log.info("Converting to Non-Multi-AZ instance before stop (instance #{@instance_id}")
|
72
|
+
set_rds_instance_multi_az(false)
|
73
|
+
end
|
74
|
+
|
75
|
+
# stop rds instance and wait for it to be fully stopped
|
76
|
+
$log.info("Stopping instance #{@instance_id}")
|
77
|
+
@rds_client.stop_db_instance({ db_instance_identifier: @instance_id })
|
78
|
+
$log.info("Waiting db instance to be stopped #{@instance_id}")
|
79
|
+
wait_rds_instance_states(%w(stopping stopped))
|
80
|
+
|
81
|
+
return configuration
|
82
|
+
end
|
83
|
+
|
84
|
+
def set_rds_instance_multi_az(multi_az)
|
85
|
+
if @rds_instance.multi_az == multi_az
|
86
|
+
$log.info("Rds instance #{@rds_instance.db_instance_identifier} already multi-az=#{multi_az}")
|
87
|
+
return
|
88
|
+
end
|
89
|
+
@rds_instance.modify({ multi_az: multi_az, apply_immediately: true })
|
90
|
+
# allow half an hour for instance to be converted
|
91
|
+
wait_states = %w(modifying available)
|
92
|
+
self.wait_rds_instance_states( wait_states)
|
93
|
+
end
|
94
|
+
|
95
|
+
def wait_rds_instance_states(wait_states)
|
96
|
+
wait_states.each do |state|
|
97
|
+
# reached state must be steady, at least a minute. Modifying an instance to/from MultiAZ can't be shorter
|
98
|
+
# than 40 seconds, hence steady count is 4
|
99
|
+
state_count = 0
|
100
|
+
steady_count = 4
|
101
|
+
attempts = 0
|
102
|
+
rds = Aws::RDS::Resource.new(client: @rds_client)
|
103
|
+
until attempts == (max_attempts = 60*6) do
|
104
|
+
instance = rds.db_instance(@instance_id)
|
105
|
+
$log.info("Instance #{instance.db_instance_identifier} state: #{instance.db_instance_status}, waiting for #{state}")
|
106
|
+
|
107
|
+
if instance.db_instance_status == "#{state}"
|
108
|
+
state_count = state_count + 1
|
109
|
+
$log.info("#{state_count}/#{steady_count}")
|
110
|
+
else
|
111
|
+
state_count = 0
|
112
|
+
end
|
113
|
+
break if state_count == steady_count
|
114
|
+
attempts = attempts + 1
|
115
|
+
sleep(15)
|
116
|
+
end
|
117
|
+
|
118
|
+
if attempts == max_attempts
|
119
|
+
$log.error("RDS Database Instance #{@instance_id} did not enter #{state} state, however continuing operations...")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
private :set_rds_instance_multi_az, :wait_rds_instance_states
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative '../lib/asg_start_stop_handler'
|
2
|
+
require_relative '../lib/ec2_start_stop_handler'
|
3
|
+
require_relative '../lib/rds_start_stop_handler'
|
4
|
+
require_relative '../lib/alarm_start_stop_handler'
|
5
|
+
|
6
|
+
module Base2
|
7
|
+
|
8
|
+
class StartStopHandlerFactory
|
9
|
+
|
10
|
+
# Factory method to get start/stop handler based on CloudFormation
|
11
|
+
# resource type. If resource_id passed in does not exist, it is
|
12
|
+
# very likely that exception will be raised
|
13
|
+
def self.get_start_stop_handler(resource_type, resource_id)
|
14
|
+
case resource_type
|
15
|
+
when 'AWS::AutoScaling::AutoScalingGroup'
|
16
|
+
return Base2::AsgStartStopHandler.new(resource_id)
|
17
|
+
|
18
|
+
when 'AWS::EC2::Instance'
|
19
|
+
return Base2::Ec2StartStopHandler.new(resource_id)
|
20
|
+
|
21
|
+
when 'AWS::RDS::DBInstance'
|
22
|
+
return Base2::RdsStartStopHandler.new(resource_id)
|
23
|
+
|
24
|
+
when 'AWS::CloudWatch::Alarm'
|
25
|
+
return Base2::AlarmStartStopHandler.new(resource_id)
|
26
|
+
|
27
|
+
else
|
28
|
+
return nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cfn_manage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Base2Services
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2017-
|
12
|
+
date: 2017-10-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: aws-sdk
|
@@ -41,10 +41,15 @@ files:
|
|
41
41
|
- bin/cfn_manage
|
42
42
|
- bin/cfn_manage.rb
|
43
43
|
- bin/usage.txt
|
44
|
+
- lib/alarm_start_stop_handler.rb
|
45
|
+
- lib/asg_start_stop_handler.rb
|
44
46
|
- lib/aws_credentials.rb
|
45
47
|
- lib/cf_common.rb
|
46
48
|
- lib/cf_progress_tracker.rb
|
47
49
|
- lib/cf_start_stop_environment.rb
|
50
|
+
- lib/ec2_start_stop_handler.rb
|
51
|
+
- lib/rds_start_stop_handler.rb
|
52
|
+
- lib/start_stop_handler_factory.rb
|
48
53
|
homepage: https://github.com/base2Services/cfn-library/blob/master/README.md
|
49
54
|
licenses:
|
50
55
|
- MIT
|