Class: ElasticBeans::Application

Inherits:
Object
  • Object
show all
Defined in:
lib/elastic_beans/application.rb

Overview

An Elastic Beanstalk application which should exist.

If the application does not exist, an error will be raised when attempting to access it.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, cloudformation:, elastic_beanstalk:, s3:, sqs: nil) ⇒ Application

Returns a new instance of Application.



20
21
22
23
24
25
26
# File 'lib/elastic_beans/application.rb', line 20

def initialize(name:, cloudformation:, elastic_beanstalk:, s3:, sqs: nil)
  @name = name
  @elastic_beanstalk = elastic_beanstalk
  @s3 = s3
  @sqs = sqs
  @stack = ElasticBeans::Aws::CloudformationStack.new(name, cloudformation: cloudformation)
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



18
19
20
# File 'lib/elastic_beans/application.rb', line 18

def name
  @name
end

Instance Method Details

#bucket_nameObject

Returns the name of the S3 bucket in which to store artifacts for this application. Looks for the default Elastic Beanstalk bucket, which is of the form elasticbeanstalk-REGION-ACCOUNT_ID.

Raises an error if the bucket cannot be found.



98
99
100
101
102
103
104
105
106
107
# File 'lib/elastic_beans/application.rb', line 98

def bucket_name
  return @bucket_name if @bucket_name
  bucket = s3.list_buckets.buckets.find { |bucket| bucket.name.start_with?("elasticbeanstalk-") }
  unless bucket
    raise MissingBucketError
  end
  @bucket_name = bucket.name
rescue ::Aws::S3::Errors::AccessDenied
  raise AccessDeniedS3Error.new
end

#configuration_templatesObject

Returns an ElasticBeans::ConfigurationTemplate for each saved configuration of the Elastic Beanstalk application.

See ElasticBeans::ConfigurationTemplate::new_from_existing for details on determining appropriate types.



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/elastic_beans/application.rb', line 47

def configuration_templates
  response = elastic_beanstalk.describe_applications(application_names: [name])
  application = response.applications[0]
  unless application
    raise MissingApplicationError.new(application: self)
  end

  templates = application.configuration_templates
  templates.map { |template_name|
    ElasticBeans::ConfigurationTemplate.new_from_existing(
      template_name,
      application: self,
      elastic_beanstalk: elastic_beanstalk,
    )
  }
rescue ::Aws::ElasticBeanstalk::Errors::Throttling
  sleep 5
  retry
end

#deployed_versionObject

Returns an ElasticBeans::ApplicationVersion reflecting the deployed version of the application from the latest-updated environment.

Ignores terminated or terminating environments.



32
33
34
35
36
37
38
39
40
41
42
# File 'lib/elastic_beans/application.rb', line 32

def deployed_version
  response = elastic_beanstalk.describe_environments(application_name: name)
  live_environments = response.environments.select { |environment| environment.status !~ /Terminat/ }
  environment = live_environments.max_by(&:date_updated)
  if environment
    ElasticBeans::ApplicationVersion.new(environment.version_label, application: self, elastic_beanstalk: elastic_beanstalk)
  end
rescue ::Aws::ElasticBeanstalk::Errors::Throttling
  sleep 5
  retry
end

#deregister_command(command) ⇒ Object

Removes command metadata created by #register_command. Used by ‘beans exec –interactive` to clean up the interactive task metadata.

Raises an error if access is denied.



113
114
115
116
117
118
119
120
121
# File 'lib/elastic_beans/application.rb', line 113

def deregister_command(command)
  key = "#{command_key_prefix}#{command.id}.json"
  s3.delete_object(
    bucket: bucket_name,
    key: key,
  )
rescue ::Aws::S3::Errors::AccessDenied
  raise AccessDeniedS3Error.new(bucket: bucket_name, key: key)
end

#enqueue_command(command, delay_seconds: nil) ⇒ Object

Enqueues a one-off Exec::Command to be run on the application’s exec environment. Does not wait for action to be taken, but returns immediately after enqueuing the command.

Raises an error if the exec environment or queue cannot be found, or if access is denied.



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/elastic_beans/application.rb', line 127

def enqueue_command(command, delay_seconds: nil)
  if environments.none? { |environment| environment.is_a?(Environment::Exec) }
    raise MissingExecEnvironmentError.new(application: self)
  end

  if command.to_s == command
    command = Exec::Command.new(command_string: command)
  end
  register_command(command)

  message = {
    queue_url: exec_queue_url,
    message_body: command.to_json,
  }
  message[:delay_seconds] = delay_seconds if delay_seconds
  sqs.send_message(message)
end

#enqueued_commandsObject

Fetches up to 100 previously-enqueued commands that are running or scheduled to run. Commands are deserialized from metadata files in a well-known location in S3. The metadata is created when the command is enqueued. The instances in the exec environment update the metadata when executing a command, and remove the metadata when they are done.

Raises an error if the application does not exist.



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/elastic_beans/application.rb', line 151

def enqueued_commands
  unless exists?
    raise MissingApplicationError.new(application: self)
  end

  # Ignoring truncation for simplicity; >100 commands will just have to suffer.
  objects = s3.list_objects_v2(
    bucket: bucket_name,
    prefix: command_key_prefix,
    max_keys: 100,
  ).contents
  objects.each_with_object([]) do |object, commands|
    begin
      next unless object.key.end_with?('.json')
      begin
        s3.head_object(bucket: bucket_name, key: "#{object.key}.killed")
        next
      rescue ::Aws::S3::Errors::NotFound
        # not killed, move on
      end

      response = s3.get_object(bucket: bucket_name, key: object.key)
      commands << ElasticBeans::Exec::Command.from_json(response.body.read)
    # skip finished or invalid commands
    rescue ::Aws::S3::Errors::NoSuchKey
    rescue JSON::ParserError
    end
  end
rescue ::Aws::S3::Errors::AccessDenied
  raise AccessDeniedS3Error.new(bucket: bucket_name, key: "#{command_key_prefix}*")
end

#env_varsObject

Returns the ElasticBeans::EnvVars for this application.



68
69
70
71
72
73
74
# File 'lib/elastic_beans/application.rb', line 68

def env_vars
  unless exists?
    raise MissingApplicationError.new(application: self)
  end

  EnvVars.new(application: self, s3: s3)
end

#environmentsObject

Returns an ElasticBeans::Environment for each un-terminated environment in the Elastic Beanstalk application.

See ElasticBeans::Environment::new_from_existing for details on determining appropriate types.



79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/elastic_beans/application.rb', line 79

def environments
  response = elastic_beanstalk.describe_environments(application_name: name)
  live_environments = response.environments.select { |environment| environment.status !~ /Terminat/ }
  live_environments.map { |environment|
    ElasticBeans::Environment.new_from_existing(
      environment,
      application: self,
      elastic_beanstalk: elastic_beanstalk,
    )
  }
rescue ::Aws::ElasticBeanstalk::Errors::Throttling
  sleep 5
  retry
end

#exec_queue_urlObject

Fetches the ExecQueueUrl Output from the application CloudFormation stack. The stack must have the same name as the application.



185
186
187
# File 'lib/elastic_beans/application.rb', line 185

def exec_queue_url
  stack.stack_output("ExecQueueUrl")
end

#kill_command(command_or_id) ⇒ Object

Schedules the given ElasticBeans::Exec::Command or id for termination. Adds a special piece of metadata to kill the command and relies on the SQSConsumer to terminate it.

Raises an error if the application does not exist.



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/elastic_beans/application.rb', line 193

def kill_command(command_or_id)
  unless exists?
    raise MissingApplicationError.new(application: self)
  end

  if command_or_id.is_a?(ElasticBeans::Exec::Command)
    command_id = command_or_id.id
  else
    command_id = command_or_id
  end

  key = "#{command_key_prefix}#{command_id}.json.killed"
  s3.put_object(
    bucket: bucket_name,
    key: key,
  )
rescue ::Aws::S3::Errors::AccessDenied
  raise AccessDeniedS3Error.new(bucket: bucket_name, key: key)
end

#register_command(command) ⇒ Object

Registers command metadata so that #enqueued_commands will find it. Adds :bucket and :key keys to the command’s ElasticBeans::Exec::Command#metadata. Used by beans exec –interactive“ so that beans ps+ displays interactive tasks that are not enqueued normally.

Raises an error if access is denied.



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/elastic_beans/application.rb', line 218

def register_command(command)
  unless exists?
    raise MissingApplicationError.new(application: self)
  end

  command.[:bucket] = bucket_name
  command.[:key] = "#{command_key_prefix}#{command.id}.json"
  s3.put_object(
    bucket: bucket_name,
    key: command.[:key],
    body: command.to_json,
  )
rescue ::Aws::S3::Errors::AccessDenied
  raise AccessDeniedS3Error.new(bucket: bucket_name, key: command.[:key])
end

#versionsObject

Returns an ElasticBeans::ApplicationVersion for each version of the Elastic Beanstalk application.



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/elastic_beans/application.rb', line 235

def versions
  application_versions = []
  next_token = nil
  loop do
    begin
      response = elastic_beanstalk.describe_application_versions(application_name: name, next_token: next_token)
      next_token = response.next_token
      application_versions += response.application_versions.map { |version|
        ElasticBeans::ApplicationVersion.new_from_existing(version, application: self, elastic_beanstalk: elastic_beanstalk)
      }
      break unless next_token
    rescue ::Aws::ElasticBeanstalk::Errors::Throttling
      sleep 5
      retry
    end
  end
  application_versions
end

#worker_queue_url(queue) ⇒ Object

Returns the Worker{QUEUE}QueueUrl from the application CloudFormation stack.



255
256
257
258
259
# File 'lib/elastic_beans/application.rb', line 255

def worker_queue_url(queue)
  queue_attribute = queue.to_s.downcase
  queue_attribute[0] = queue_attribute[0].upcase
  stack.stack_output("Worker#{queue_attribute}QueueUrl")
end

#worker_queuesObject

Returns the name of each queue discovered in the application CloudFormation stack. Looks for outputs of the form Worker{QUEUE}QueueUrl.



263
264
265
266
267
268
269
270
# File 'lib/elastic_beans/application.rb', line 263

def worker_queues
  stack.stack_outputs.each_with_object([]) do |(output_key, _), acc|
    match = /\AWorker(?<queue>\w+)QueueUrl\z/.match(output_key)
    if match
      acc << match[:queue].downcase
    end
  end
end