Class: Rebi::Environment

Inherits:
Object
  • Object
show all
Includes:
Log
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

Methods included from Log

#colorize, #colorize_prefix, #error, #error_label, #h1, #h2, #h3, #h4, #hstatus, #log

Constructor Details

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

Returns a new instance of Environment.



47
48
49
50
51
52
53
54
55
# File 'lib/rebi/environment.rb', line 47

def initialize stage_name, env_name, client=Rebi.eb
  @stage_name = stage_name
  @env_name = env_name
  @client = client
  @s3_client = Rebi.s3
  @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.



12
13
14
# File 'lib/rebi/environment.rb', line 12

def api_data
  @api_data
end

#app_nameObject (readonly)

Returns the value of attribute app_name.



6
7
8
# File 'lib/rebi/environment.rb', line 6

def app_name
  @app_name
end

#clientObject

Returns the value of attribute client.



12
13
14
# File 'lib/rebi/environment.rb', line 12

def client
  @client
end

#configObject (readonly)

Returns the value of attribute config.



6
7
8
# File 'lib/rebi/environment.rb', line 6

def config
  @config
end

#env_nameObject (readonly)

Returns the value of attribute env_name.



6
7
8
# File 'lib/rebi/environment.rb', line 6

def env_name
  @env_name
end

#nameObject (readonly)

Returns the value of attribute name.



6
7
8
# File 'lib/rebi/environment.rb', line 6

def name
  @name
end

#s3_clientObject

Returns the value of attribute s3_client.



12
13
14
# File 'lib/rebi/environment.rb', line 12

def s3_client
  @s3_client
end

#stage_nameObject (readonly)

Returns the value of attribute stage_name.



6
7
8
# File 'lib/rebi/environment.rb', line 6

def stage_name
  @stage_name
end

Class Method Details

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

TODO



379
380
381
382
# File 'lib/rebi/environment.rb', line 379

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

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



370
371
372
373
374
375
376
# File 'lib/rebi/environment.rb', line 370

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.eb) ⇒ Object



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

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

Instance Method Details

#bucket_nameObject



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

def bucket_name
  @bucket_name ||= client.create_storage_location.s3_bucket
end

#check_createdObject



112
113
114
115
# File 'lib/rebi/environment.rb', line 112

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

#check_created!Object

refresh data



118
119
120
121
# File 'lib/rebi/environment.rb', line 118

def check_created!
  refresh
  check_created
end

#check_instance_profileObject



321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/rebi/environment.rb', line 321

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



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

def cname
  created? ? api_data.cname : nil
end

#create_app_version(opts = {}) ⇒ Object



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/rebi/environment.rb', line 182

def create_app_version opts={}
  return if opts[:settings_only]
  start = Time.now.utc
  source_bundle = Rebi::ZipHelper.new.gen(self.config, opts)
  version_label = source_bundle[:label]
  key = "#{app_name}/#{version_label}.zip"
  log("Uploading source bundle: #{version_label}.zip")
  s3_client.put_object(
    bucket: bucket_name,
    key: key,
    body: source_bundle[:file].read
    )
  log("Creating app version: #{version_label}")
  client.create_application_version({
    application_name: app_name,
    description: source_bundle[:message],
    version_label: version_label,
    source_bundle: {
      s3_bucket: bucket_name,
      s3_key: key
      }
    })
  log("App version was created in: #{Time.now.utc - start}s")
  return version_label
end

#create_defaut_profileObject



334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/rebi/environment.rb', line 334

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)


123
124
125
# File 'lib/rebi/environment.rb', line 123

def created?
  !!api_data
end

#deploy(opts = {}) ⇒ Object



237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/rebi/environment.rb', line 237

def deploy opts={}
  _run_hooks :pre
  version_label = create_app_version opts
  request_id = if created?
    update version_label, opts
  else
    init version_label, opts
  end
  log h3("DEPLOYING")
  _run_hooks :post
  watch_request request_id
  return request_id
end

#environment_variablesObject



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

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



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

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

#healthObject



85
86
87
# File 'lib/rebi/environment.rb', line 85

def health
  check_created! && api_data.health
end

#idObject



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

def id
  created? ? api_data.environment_id : nil
end

#in_updating?Boolean

Returns:

  • (Boolean)


81
82
83
# File 'lib/rebi/environment.rb', line 81

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

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



208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/rebi/environment.rb', line 208

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

  self.check_instance_profile

  self.api_data = client.create_environment _create_args(version_label, opts)

  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



177
178
179
180
# File 'lib/rebi/environment.rb', line 177

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

#log_labelObject



266
267
268
# File 'lib/rebi/environment.rb', line 266

def log_label
  name
end

#option_settingsObject



89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/rebi/environment.rb', line 89

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



172
173
174
175
# File 'lib/rebi/environment.rb', line 172

def refresh
  self.api_data = nil
  return self
end

#response_msgs(key = nil) ⇒ Object



133
134
135
136
# File 'lib/rebi/environment.rb', line 133

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

#ssh(instance_id) ⇒ Object



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/rebi/environment.rb', line 294

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



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

def status
  check_created! && api_data.status
end

#success_message?(mes) ⇒ Boolean

Returns:

  • (Boolean)


270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/rebi/environment.rb', line 270

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



251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/rebi/environment.rb', line 251

def terminate!
  check_created
  log h2("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



222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/rebi/environment.rb', line 222

def update version_label, opts={}

  raise Rebi::Error::EnvironmentInUpdating.new("Environment is in updating: #{name}") if in_updating?
  log h2("Start updating")
  start_time = Time.now

  self.api_data = client.update_environment(_update_args(version_label, opts))

  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



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

def version_label
  created? ? api_data.version_label : nil
end

#watch_request(request_id) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/rebi/environment.rb', line 138

def watch_request request_id
  return unless request_id
  log h1("WATCHING 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