Class: Rebi::Environment

Inherits:
Object
  • Object
show all
Defined in:
lib/rebi/environment.rb

Constant Summary collapse

RESPONSES =
{
    'event.createstarting': 'createEnvironment is starting.',
    'event.terminatestarting': 'terminateEnvironment is starting.',
    'event.updatestarting': 'Environment update is starting.',
    'event.redmessage': 'Environment health has been set to RED',
    'event.commandfailed': 'Command failed on instance',
    'event.launchfailed': 'Failed to launch',
    'event.deployfailed': 'Failed to deploy application.',
    'event.redtoyellowmessage': 'Environment health has transitioned from YELLOW to RED',
    'event.yellowmessage': 'Environment health has been set to YELLOW',
    'event.greenmessage': 'Environment health has been set to GREEN',
    'event.launchsuccess': 'Successfully launched environment:',
    'event.launchbad': 'Create environment operation is complete, but with errors',
    'event.updatebad': 'Update environment operation is complete, but with errors.',
    'git.norepository': 'Error: Not a git repository (or any of the parent directories): .git',
    'env.updatesuccess': 'Environment update completed successfully.',
    'env.cnamenotavailable': 'DNS name \([^ ]+\) is not available.',
    'env.nameexists': 'Environment [^ ]+ already exists.',
    'app.deletesuccess': 'The application has been deleted successfully.',
    'app.exists': 'Application {app-name} already exists.',
    'app.notexists': 'No Application named {app-name} found.',
    'logs.pulled': 'Pulled logs for environment instances.',
    'logs.successtail': 'Successfully finished tailing',
    'logs.successbundle': 'Successfully finished bundling',
    'env.terminated': 'terminateEnvironment completed successfully.',
    'env.invalidstate': 'Environment named {env-name} is in an invalid state for this operation. Must be Ready.',
    'loadbalancer.notfound': 'There is no ACTIVE Load Balancer named',
    'ec2.sshalreadyopen': 'the specified rule "peer: 0.0.0.0/0, TCP, from port: 22, to port: 22,',
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(stage_name, env_name, client = Rebi.client) ⇒ Environment

Returns a new instance of Environment.



44
45
46
47
48
49
50
51
# File 'lib/rebi/environment.rb', line 44

def initialize stage_name, env_name, client=Rebi.client
  @stage_name = stage_name
  @env_name = env_name
  @client = client
  @config = Rebi.config.environment(stage_name, env_name)
  @app_name = @config.app_name
  @api_data
end

Instance Attribute Details

#api_dataObject

Returns the value of attribute api_data.



10
11
12
# File 'lib/rebi/environment.rb', line 10

def api_data
  @api_data
end

#app_nameObject (readonly)

Returns the value of attribute app_name.



4
5
6
# File 'lib/rebi/environment.rb', line 4

def app_name
  @app_name
end

#clientObject

Returns the value of attribute client.



10
11
12
# File 'lib/rebi/environment.rb', line 10

def client
  @client
end

#configObject (readonly)

Returns the value of attribute config.



4
5
6
# File 'lib/rebi/environment.rb', line 4

def config
  @config
end

#env_nameObject (readonly)

Returns the value of attribute env_name.



4
5
6
# File 'lib/rebi/environment.rb', line 4

def env_name
  @env_name
end

#nameObject (readonly)

Returns the value of attribute name.



4
5
6
# File 'lib/rebi/environment.rb', line 4

def name
  @name
end

#stage_nameObject (readonly)

Returns the value of attribute stage_name.



4
5
6
# File 'lib/rebi/environment.rb', line 4

def stage_name
  @stage_name
end

Class Method Details

.all(app_name, client = Rebi.client) ⇒ Object

TODO



393
394
395
396
# File 'lib/rebi/environment.rb', line 393

def self.all app_name, client=Rebi.client
  client.describe_environments(application_name: app_name,
                               include_deleted: false).environments
end

.create(stage_name, env_name, version_label, client) ⇒ Object



384
385
386
387
388
389
390
# File 'lib/rebi/environment.rb', line 384

def self.create stage_name, env_name, version_label, client
  env =  new stage_name, env_name, client
  raise Rebi::Error::EnvironmentExisted.new if env.created?

  env.init version_label
  return env
end

.get(stage_name, env_name, client = Rebi.client) ⇒ Object



398
399
400
401
# File 'lib/rebi/environment.rb', line 398

def self.get stage_name, env_name, client=Rebi.client
  env = new stage_name, env_name, client
  return env.created? ? env : nil
end

Instance Method Details

#check_createdObject



104
105
106
107
# File 'lib/rebi/environment.rb', line 104

def check_created
  raise Rebi::Error::EnvironmentNotExisted.new("#{name} not exists") unless created?
  return created?
end

#check_created!Object

refresh data



110
111
112
113
# File 'lib/rebi/environment.rb', line 110

def check_created!
  refresh
  check_created
end

#check_instance_profileObject



335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/rebi/environment.rb', line 335

def check_instance_profile
  iam = Rebi.iam
  begin
    iam.get_instance_profile({
      instance_profile_name: config.instance_profile
      })
    return true
  rescue Aws::IAM::Errors::NoSuchEntity => e
    raise e unless config.default_instance_profile?
    self.create_defaut_profile
  end
end

#cnameObject



57
58
59
# File 'lib/rebi/environment.rb', line 57

def cname
  created? ? api_data.cname : nil
end

#create_defaut_profileObject



348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/rebi/environment.rb', line 348

def create_defaut_profile
  iam = Rebi.iam
  profile = role = Rebi::ConfigEnvironment::DEFAULT_IAM_INSTANCE_PROFILE
  iam.create_instance_profile({
    instance_profile_name: profile
    })

  document = <<-JSON
{
  "Version":"2008-10-17",
  "Statement":[
  {
    "Effect":"Allow",
    "Principal":{
      "Service":["ec2.amazonaws.com"]
    },
    "Action":["sts:AssumeRole"]
  }
  ]
}
  JSON
  begin
    iam.create_role({
      role_name: role,
      assume_role_policy_document: document
      })
  rescue Aws::IAM::Errors::EntityAlreadyExists
  end

  iam.add_role_to_instance_profile({
    instance_profile_name: profile,
    role_name: role
    })

end

#created?Boolean

Returns:

  • (Boolean)


115
116
117
# File 'lib/rebi/environment.rb', line 115

def created?
  !!api_data
end

#deploy(version_label, opts = {}) ⇒ Object



231
232
233
234
235
236
237
238
# File 'lib/rebi/environment.rb', line 231

def deploy version_label, opts={}
  request_id = if created?
    update version_label, opts
  else
    init version_label, opts
  end
  return request_id
end

#environment_variablesObject



96
97
98
99
100
101
102
# File 'lib/rebi/environment.rb', line 96

def environment_variables
  option_settings.select do |o|
      o[:namespace] == config.ns(:app_env)
  end.map do |o|
        [o[:option_name], o[:value]]
  end.to_h.with_indifferent_access
end

#events(start_time = Time.now, request_id = nil) ⇒ Object



154
155
156
157
158
159
160
# File 'lib/rebi/environment.rb', line 154

def events start_time=Time.now, request_id=nil
  client.describe_events({
    application_name: app_name,
    environment_name: name,
    start_time: start_time,
  }.merge( request_id ? {request_id: request_id} : {})).events
end

#gen_deploy_optsObject



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/rebi/environment.rb', line 283

def gen_deploy_opts
  to_deploy = []
  to_remove = []
  env_only = []
  config.opts_array.each do |o|
    o = o.deep_dup

    if o[:namespace] == config.ns(:app_env)
      if o[:value].blank?
        o.delete(:value)
        to_remove << o
        next
      else
        env_only << o
      end
    end
    to_deploy << o
  end
  return {
    option_settings: to_deploy,
    options_to_remove:  to_remove,
    env_only: env_only,
  }
end

#healthObject



77
78
79
# File 'lib/rebi/environment.rb', line 77

def health
  check_created! && api_data.health
end

#idObject



65
66
67
# File 'lib/rebi/environment.rb', line 65

def id
  created? ? api_data.environment_id : nil
end

#in_updating?Boolean

Returns:

  • (Boolean)


73
74
75
# File 'lib/rebi/environment.rb', line 73

def in_updating?
  !!status.match("ing$")
end

#init(version_label, opts = {}) ⇒ Object



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/rebi/environment.rb', line 172

def init version_label, opts={}
  log("Creating new environment")
  start_time = Time.now

  self.check_instance_profile

  self.api_data = client.create_environment({
      application_name: config.app_name,
      environment_name: config.name,
      version_label: version_label,
      tier: config.tier,
      description: config.description,
      option_settings: config.opts_array,
    }.merge(
      config.worker? ? {} : { cname_prefix: config.cname_prefix }
    ).merge(config.platform_arn ? { platform_arn: config.platform_arn } : { solution_stack_name: config.solution_stack_name })
  )

  request_id = events(start_time).select do |e|
    e.message.match(response_msgs('event.createstarting'))
  end.map(&:request_id).first
  return request_id
end

#instance_idsObject



167
168
169
170
# File 'lib/rebi/environment.rb', line 167

def instance_ids
  resp = client.describe_environment_resources environment_name: self.name
  resp.environment_resources.instances.map(&:id).sort
end

#log(mes) ⇒ Object



255
256
257
# File 'lib/rebi/environment.rb', line 255

def log mes
  Rebi.log(mes, name)
end

#option_settingsObject



81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/rebi/environment.rb', line 81

def option_settings
  check_created!
  client.describe_configuration_settings({
    application_name: app_name,
    environment_name: name
  }).configuration_settings.first.option_settings.map do |o|
    {
      namespace: o.namespace,
      value: o.value,
      resource_name: o.resource_name,
      option_name: o.option_name,
    }.with_indifferent_access
  end
end

#refreshObject



162
163
164
165
# File 'lib/rebi/environment.rb', line 162

def refresh
  self.api_data = nil
  return self
end

#response_msgs(key = nil) ⇒ Object



125
126
127
128
# File 'lib/rebi/environment.rb', line 125

def response_msgs key=nil
  @response_msgs ||= RESPONSES.with_indifferent_access
  return key ? @response_msgs[key] : @response_msgs
end

#ssh(instance_id) ⇒ Object



308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/rebi/environment.rb', line 308

def ssh instance_id
  raise "Invalid instance_id" unless self.instance_ids.include?(instance_id)


  instance  = Rebi.ec2.describe_instance instance_id

  raise Rebi::Error::EC2NoKey.new unless instance.key_name.present?
  raise Rebi::Error::EC2NoIP.new unless instance.public_ip_address.present?


  Rebi.ec2.authorize_ssh instance_id do
    user = "ec2-user"
    key_file = "~/.ssh/#{instance.key_name}.pem"
    raise Rebi::Error::KeyFileNotFound unless File.exists? File.expand_path(key_file)
    cmd = "ssh -i #{key_file} #{user}@#{instance.public_ip_address}"
    log cmd

    begin
      Subprocess.check_call(['ssh', '-i', key_file,  "#{user}@#{instance.public_ip_address}"])
    rescue Subprocess::NonZeroExit => e
      log e.message
    end

  end

end

#statusObject



69
70
71
# File 'lib/rebi/environment.rb', line 69

def status
  check_created! && api_data.status
end

#success_message?(mes) ⇒ Boolean

Returns:

  • (Boolean)


259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/rebi/environment.rb', line 259

def success_message? mes
  return true if [
    'event.greenmessage',
    'event.launchsuccess',
    'logs.pulled',
    'env.terminated',
    'env.updatesuccess',
    'app.deletesuccess',
  ].map{|k| response_msgs(k)}.any?{|s| mes.match(s)}

  if [
        'event.redmessage',
        'event.launchbad',
        'event.updatebad',
        'event.commandfailed',
        'event.launchfailed',
        'event.deployfailed',
      ].map {|k| response_msgs(k)}.any? {|s| mes.match(s)}
    raise Rebi::Error::ServiceError.new(mes)
  end

  return false
end

#terminate!Object



240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/rebi/environment.rb', line 240

def terminate!
  check_created
  log("Start terminating")
  client.terminate_environment({
    environment_name: name,
    environment_id: id,
    })
  start_time = Time.now

  request_id = events(start_time).select do |e|
    e.message.match(response_msgs('event.updatestarting'))
  end.map(&:request_id).first
  return request_id
end

#update(version_label, opts = {}) ⇒ Object



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/rebi/environment.rb', line 196

def update version_label, opts={}

  raise Rebi::Error::EnvironmentInUpdating.new(name) if in_updating?
  log("Start updating")
  start_time = Time.now
  deploy_opts = gen_deploy_opts
  deploy_args = {
    application_name: config.app_name,
    environment_name: config.name,
    version_label: version_label,
    description: config.description,
  }

  if opts[:include_settings] || opts[:settings_only]
    deploy_args.merge!({
      option_settings: deploy_opts[:option_settings],
      options_to_remove: deploy_opts[:options_to_remove],
    })
    deploy_args.delete(:version_label) if opts[:settings_only]
  else
    deploy_args.merge!({
      option_settings: deploy_opts[:env_only],
      options_to_remove: deploy_opts[:options_to_remove],
    })
  end

  self.api_data = client.update_environment(deploy_args)

  request_id = events(start_time).select do |e|
    e.message.match(response_msgs('event.updatestarting'))
  end.map(&:request_id).first

  return request_id
end

#version_labelObject



61
62
63
# File 'lib/rebi/environment.rb', line 61

def version_label
  created? ? api_data.version_label : nil
end

#watch_request(request_id) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/rebi/environment.rb', line 130

def watch_request request_id
  check_created!
  start = Time.now
  finished = false
  last_time = Time.now - 30.minute
  thread = Thread.new do
    while (start + Rebi.config.timeout) > Time.now && !finished
      events(last_time, request_id).reverse.each do |e|
        finished ||= success_message?(e.message)
        last_time = [last_time + 1.second, e.event_date + 1.second].max
        log(e.message)
      end
      sleep(5) unless finished
    end
    log ("Timeout") unless finished
  end
  begin
    thread.join
  rescue Interrupt
    log("Interrupt")
  end
  return thread
end