Class: EC2Ctl::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/ec2ctl/client.rb

Constant Summary collapse

PingCheckError =
Class.new RuntimeError
CommandNotSucceeded =
Class.new RuntimeError
NoInstanceForExecCommand =
Class.new RuntimeError
InvalidFilter =
Class.new RuntimeError
PrivateKeyRequired =
Class.new RuntimeError
INSTANCE_IDS_LIMIT =
50
COMMAND_STATUS_FINISHED =
%w(Success TimedOut Cancelled Failed).freeze
COMMAND_STATUS_NOT_SUCCEEDED =
%w(TimedOut Cancelled Failed).freeze
SSM_DOCUMENT_NAMES =
{
  Linux:   "AWS-RunShellScript",
  Windows: "AWS-RunPowerShellScript",
}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(logger: nil, load_balancer_name: nil, platform_type: "Linux", skip_ping_check: false, commands: [], working_directory: nil, execution_timeout: 3600, skip_command_waits: false, wait_interval: 5, timeout_seconds: nil, comment: "Started at #{Time.now} (#{self.class}@#{VERSION})", output_s3_bucket_name: nil, output_s3_key_prefix: nil, service_role_arn: nil, notification_arn: nil, notification_events: nil, notification_type: nil, rolling_group_size: 1, skip_draining_waits: false, skip_inservice_waits: false, inservice_wait_timeout: 180, instance_ids: [], filters: [], attributes: [], search: [], count: false, sort: nil, private_key_file: nil) ⇒ Client

Returns a new instance of Client.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/ec2ctl/client.rb', line 23

def initialize(
  logger:                 nil,
  load_balancer_name:     nil,
  platform_type:          "Linux",
  skip_ping_check:        false,
  commands:               [],
  working_directory:      nil,
  execution_timeout:      3600,
  skip_command_waits:     false,
  wait_interval:          5,
  timeout_seconds:        nil,
  comment:                "Started at #{Time.now} (#{self.class}@#{VERSION})",
  output_s3_bucket_name:  nil,
  output_s3_key_prefix:   nil,
  service_role_arn:       nil,
  notification_arn:       nil,
  notification_events:    nil,
  notification_type:      nil,
  rolling_group_size:     1,
  skip_draining_waits:    false,
  skip_inservice_waits:   false,
  inservice_wait_timeout: 180,
  instance_ids:           [],
  filters:                [],
  attributes:             [],
  search:                 [],
  count:                  false,
  sort:                   nil,
  private_key_file:       nil
)
  @logger = logger

  @ec2_resource = Aws::EC2::Resource.new
  @elb_client   = Aws::ElasticLoadBalancing::Client.new
  @ssm_client   = Aws::SSM::Client.new

  @load_balancer_name     = load_balancer_name
  @platform_type          = platform_type
  @skip_ping_check        = skip_ping_check
  @commands               = commands
  @working_directory      = working_directory
  @execution_timeout      = execution_timeout
  @skip_command_waits     = skip_command_waits
  @wait_interval          = wait_interval
  @timeout_seconds        = timeout_seconds
  @comment                = comment
  @output_s3_bucket_name  = output_s3_bucket_name
  @output_s3_key_prefix   = output_s3_key_prefix
  @service_role_arn       = service_role_arn
  @notification_arn       = notification_arn
  @notification_events    = notification_events
  @notification_type      = notification_type
  @skip_draining_waits    = skip_draining_waits
  @skip_inservice_waits   = skip_inservice_waits
  @inservice_wait_timeout = inservice_wait_timeout
  @instance_ids           = instance_ids
  @filters                = filters
  @attributes             = attributes
  @search                 = search
  @count                  = count
  @sort                   = sort
  @private_key_file       = private_key_file

  if @load_balancer_name
    @elb_instance_ids = elb_instance_states.map(&:instance_id).select do |instance_id|
      if instance_ids.empty?
        true
      else
        instance_ids.include? instance_id
      end
    end

    @rolling_group_size = [rolling_group_size, @elb_instance_ids.size].min
  end
end

Instance Method Details

#attach_instances(instance_ids = []) ⇒ Object



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/ec2ctl/client.rb', line 234

def attach_instances(instance_ids = [])
  response = @elb_client.register_instances_with_load_balancer(
    load_balancer_name: @load_balancer_name,
    instances:          instance_ids.map {|i| {instance_id: i}},
  )

  status = {
    attached:   instance_ids,
    registered: response.instances.map(&:instance_id),
  }

  if @logger
    if @logger.debug?
      @logger.debug status: status
    else
      @logger.info attached: instance_ids
    end
  end

  status
end

#detach_instances(instance_ids = []) ⇒ Object



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/ec2ctl/client.rb', line 212

def detach_instances(instance_ids = [])
  response = @elb_client.deregister_instances_from_load_balancer(
    load_balancer_name: @load_balancer_name,
    instances:          instance_ids.map {|i| {instance_id: i}},
  )

  status = {
    detached:   instance_ids,
    registered: response.instances.map(&:instance_id),
  }

  if @logger
    if @logger.debug?
      @logger.debug status: status
    else
      @logger.info detached: instance_ids
    end
  end

  status
end

#ec2_executeObject



132
133
134
135
# File 'lib/ec2ctl/client.rb', line 132

def ec2_execute
  ping_check ec2_instances.map(&:instance_id) unless @skip_ping_check
  execute_commands ec2_instances.map(&:instance_id)
end

#ec2_listObject



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/ec2ctl/client.rb', line 99

def ec2_list
  if @logger
    if @logger.debug?
      @logger.debug ec2_instances:       ec2_instances
      @logger.debug ssm_instance_states: ssm_instance_states(ec2_instances.map(&:instance_id))
    else
      ec2_instances_summary = if @count
        @attributes.each_with_object Hash.new do |attribute, attributes_memo|
          attribute_hash = ec2_instances.each_with_object Hash.new(0) do |instance, instances_memo|
            instances_memo[query_instance_attribute(instance, attribute)] += 1
          end

          attributes_memo[attribute] = attribute_hash
        end
      else
        ec2_instances.each_with_object Array.new do |instance, instances_memo|
          instance_summary = @attributes.each_with_object Hash.new do |attribute, attributes_memo|
            attributes_memo[attribute] = query_instance_attribute(instance, attribute)
          end

          instances_memo.push instance_summary
        end
      end

      @logger.info ec2_instances_summary: ec2_instances_summary
    end
  end

  {
    ec2_instances: ec2_instances,
  }
end

#elb_attachObject



177
178
179
# File 'lib/ec2ctl/client.rb', line 177

def elb_attach
  attach_instances @instance_ids
end

#elb_detachObject



181
182
183
# File 'lib/ec2ctl/client.rb', line 181

def elb_detach
  detach_instances @instance_ids
end

#elb_executeObject



185
186
187
188
# File 'lib/ec2ctl/client.rb', line 185

def elb_execute
  ping_check @elb_instance_ids unless @skip_ping_check
  execute_commands @elb_instance_ids
end

#elb_gracefulObject



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/ec2ctl/client.rb', line 190

def elb_graceful
  ping_check @elb_instance_ids unless @skip_ping_check

  @elb_instance_ids.each_slice(@rolling_group_size).to_a.tap do |instance_id_groups|
    instance_id_groups.each.with_index do |instance_id_group, group_index|
      @logger.info(progress: {
        completed: @rolling_group_size * group_index,
        remaining: @elb_instance_ids.size - @rolling_group_size * group_index,
        total:     @elb_instance_ids.size,
      }) if @logger

      detach_instances instance_id_group
      wait_draining instance_id_group unless @skip_draining_waits
      execute_commands instance_id_group
      attach_instances instance_id_group
      wait_inservice instance_id_group unless @skip_inservice_waits
    end

    @logger.info "Everything done!" if @logger
  end
end

#elb_listObject



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/ec2ctl/client.rb', line 137

def elb_list
  if @logger
    if @logger.debug?
      @logger.debug load_balancer_descriptions: load_balancer_descriptions
    else
      @logger.info load_balancer_descriptions_summary: load_balancer_descriptions.map {|lb|
        {
          load_balancer_name: lb.load_balancer_name,
          dns_name:           lb.dns_name,
          instances:          lb.instances.size,
        }
      }
    end
  end

  {load_balancer_descriptions: load_balancer_descriptions}
end

#elb_statusObject



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/ec2ctl/client.rb', line 155

def elb_status
  load_balancer_description = load_balancer_descriptions.first

  if @logger
    @logger.info elb_instance_counts: elb_instance_counts
    @logger.info elb_instance_states: elb_instance_states

    instance_ids = elb_instance_states.map(&:instance_id)

    @logger.debug ssm_instance_ping_counts:  ssm_instance_ping_counts(instance_ids)
    @logger.debug ssm_instance_states:       ssm_instance_states(instance_ids)
    @logger.debug load_balancer_description: load_balancer_description
    @logger.debug load_balancer_attributes:  load_balancer_attributes
  end

  {
    load_balancer_description: load_balancer_description,
    load_balancer_attributes:  load_balancer_attributes,
    elb_instance_states:       elb_instance_states,
  }
end

#wait_draining(instance_ids = []) ⇒ Object



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/ec2ctl/client.rb', line 256

def wait_draining(instance_ids = [])
  connection_draining = load_balancer_attributes.connection_draining

  unless connection_draining.enabled
    @logger.info wait_draining_timeout: "Disabled.".freeze if @logger
    return
  end

  if @logger
    @logger.debug detached_instances: elb_instance_states!(instance_ids)
    @logger.info wait_draining_timeout: connection_draining.timeout
  end

  sleep connection_draining.timeout
end

#wait_inservice(instance_ids = []) ⇒ Object



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/ec2ctl/client.rb', line 272

def wait_inservice(instance_ids = [])
  s = elb_instance_states! instance_ids

  @logger.info wait_instance_inservice: s if @logger

  Timeout.timeout @inservice_wait_timeout do
    loop do
      sleep @wait_interval

      s = elb_instance_states! instance_ids
      @logger.debug wait_instance_inservice: s if @logger

      break if s.all? {|i| i.state == "InService".freeze}
    end
  end

  @logger.info wait_instance_inservice: s if @logger
end