Class: EY::CloudClient::Environment

Inherits:
Object
  • Object
show all
Defined in:
lib/engineyard-cloud-client/models/environment.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#accountObject

Returns the value of attribute account.



101
102
103
# File 'lib/engineyard-cloud-client/models/environment.rb', line 101

def 
  @account
end

#appsObject

Returns the value of attribute apps.



101
102
103
# File 'lib/engineyard-cloud-client/models/environment.rb', line 101

def apps
  @apps
end

Class Method Details

.all(api) ⇒ Object

Return list of all Environments linked to all current user’s accounts



21
22
23
# File 'lib/engineyard-cloud-client/models/environment.rb', line 21

def self.all(api)
  self.from_array(api, api.get("/environments", "no_instances" => "true")["environments"])
end

.by_name(api, environment_name, account_name = nil) ⇒ Object

Accepts an api object, environment name and optional account name and returns the best matching environment for the given constraints.

This is a shortcut for resolve_environments. Raises if nothing is found or if more than one environment is found.



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
# File 'lib/engineyard-cloud-client/models/environment.rb', line 45

def self.by_name(api, environment_name, =nil)
  constraints = {
    :environment_name => environment_name,
    :account_name     => ,
  }
  resolver = resolve(api, constraints)

  resolver.one_match { |match| return match  }

  resolver.no_matches do |errors, suggestions|
    message = nil
    if suggestions.any?
      message = "Suggestions found:\n"
      suggestions.sourt_by{|suggest| suggest['account_name']}.each do |suggest|
        message << "\t#{suggest['account_name']}/#{suggest['env_name']}\n"
      end
    end

    raise ResourceNotFound.new([errors,message].compact.join("\n").strip)
  end

  resolver.many_matches do |matches|
    message = "Multiple environments possible, please be more specific:\n"
    matches.sort_by {|env| env.}.each do |env|
      message << "\t#{env..name}/#{env.name}\n"
    end
    raise MultipleMatchesError.new(message)
  end
end

.create(api, attrs = {}) ⇒ Object

Usage Environment.create(api, {

app: app,                            # requires: app.id
name: 'myapp_production',
region: 'us-west-1',                 # default: us-east-1
app_server_stack_name: 'nginx_thin', # default: nginx_passenger3
framework_env: 'staging'             # default: production
cluster_configuration: {
  configuration: 'single'            # default: single, cluster, custom
}

})

NOTE: Syntax above is for Ruby 1.9. In Ruby 1.8, keys must all be strings.

TODO - allow any attribute to be sent through that the API might allow; e.g. region, ruby_version, stack_label



90
91
92
93
94
95
96
97
98
99
100
# File 'lib/engineyard-cloud-client/models/environment.rb', line 90

def self.create(api, attrs={})
  app    = attrs.delete("app")
  cluster_configuration = attrs.delete('cluster_configuration')
  raise EY::CloudClient::AttributeRequiredError.new("app", EY::CloudClient::App) unless app
  raise EY::CloudClient::AttributeRequiredError.new("name") unless attrs["name"]

  params = {"environment" => attrs.dup}
  unpack_cluster_configuration(params, cluster_configuration)
  response = api.post("/apps/#{app.id}/environments", params)
  self.from_hash(api, response['environment'])
end

.resolve(api, constraints) ⇒ Object

Return a constrained list of environments given a set of constraints like:

  • app_name: app name full or partial match string

  • account_name: account name full or partial match string

  • environment_name: environment name full or partial match string

  • remotes: An array of git remote URIs



32
33
34
35
36
37
38
# File 'lib/engineyard-cloud-client/models/environment.rb', line 32

def self.resolve(api, constraints)
  clean_constraints = constraints.reject { |k,v| v.nil? }
  params = {'constraints' => clean_constraints}
  response = api.get("/environments/resolve", params)['resolver']
  matches = from_array(api, response['matches'])
  ResolverResult.new(api, matches, response['errors'], response['suggestions'])
end

Instance Method Details

#account_nameObject



136
137
138
# File 'lib/engineyard-cloud-client/models/environment.rb', line 136

def 
   && .name
end

#add_app_environment(app_env) ⇒ Object



115
116
117
118
119
120
121
122
# File 'lib/engineyard-cloud-client/models/environment.rb', line 115

def add_app_environment(app_env)
  @app_environments ||= []
  existing_app_env = @app_environments.detect { |ae| app_env.environment == ae.environment }
  unless existing_app_env
    @app_environments << app_env
  end
  existing_app_env || app_env
end

#add_instance(opts) ⇒ Object

Throws a POST request at the API to /add_instances and adds one instance to this environment.

Usage example:

api = EY::CloudClient.new(token: ‘your token here’) env = api.environment_by_name(‘your_env_name’)

env.add_instance(role: “app”) env.add_instance(role: “util”, name: “foo”)

Note that the role for an instance MUST be either “app” or “util”. No other value is acceptable. The “name” parameter can be anything, but it only applies to utility instances.

Note also that if you add a util instance, you must specify a name. This method will raise if you don’t.



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/engineyard-cloud-client/models/environment.rb', line 265

def add_instance(opts)
  unless %w[app util].include?(opts[:role].to_s)
    # Fail immediately because we don't have valid arguments.
    raise InvalidInstanceRole, "Instance role must be one of: app, util"
  end

  # Sanitize the name to remove whitespace if there is any
  if opts[:name]
    name = opts[:name].gsub(/\s+/, '')
  end

  if opts[:role] == 'util'
    unless name && name.length > 0
      raise InvalidInstanceName, "When specifying a util instance you must also specify a name."
    end
  end

  # We know opts[:role] is right, name can be passed straight to the API.
  # Return the response body for error output, logging, etc.
  return api.post("/environments/#{id}/add_instances", :request => {
    "role" => opts[:role],
    "name" => opts[:name]
  })
end

#app_environmentsObject



124
125
126
# File 'lib/engineyard-cloud-client/models/environment.rb', line 124

def app_environments
  @app_environments ||= []
end

#attributes=(attrs) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
# File 'lib/engineyard-cloud-client/models/environment.rb', line 103

def attributes=(attrs)
      = attrs.delete('account')
  apps_attrs       = attrs.delete('apps')
  instances_attrs  = attrs.delete('instances')

  super

        if 
  set_apps      apps_attrs      if apps_attrs
  set_instances instances_attrs if instances_attrs
end

#bridgeObject



160
161
162
# File 'lib/engineyard-cloud-client/models/environment.rb', line 160

def bridge
  @bridge ||= instances.detect { |inst| inst.bridge? }
end

#bridge!(ignore_bad_bridge = false) ⇒ Object



164
165
166
167
168
169
170
171
# File 'lib/engineyard-cloud-client/models/environment.rb', line 164

def bridge!(ignore_bad_bridge = false)
  if bridge.nil?
    raise NoBridgeError.new(name)
  elsif !ignore_bad_bridge && !bridge.running?
    raise BadBridgeStatusError.new(bridge.status, api.endpoint)
  end
  bridge
end

#deploy_to_instancesObject



156
157
158
# File 'lib/engineyard-cloud-client/models/environment.rb', line 156

def deploy_to_instances
  provisioned_instances.select { |inst| inst.has_app_code? }
end

#download_recipesObject

See Recipes#download



229
230
231
# File 'lib/engineyard-cloud-client/models/environment.rb', line 229

def download_recipes
  recipes.download
end

#hierarchy_nameObject



140
141
142
# File 'lib/engineyard-cloud-client/models/environment.rb', line 140

def hierarchy_name
  [, name].join(" / ")
end

#instance_by_id(id) ⇒ Object

Gets an instance’s Amazon ID by its “id” attribute as reported by AWSM. When an instance is added via the API, the JSON that’s returned contains an “id” attribute for that instance. Developers may save that ID so they can later discover an instance’s Amazon ID. This is because, when an instance object is first created (see #add_instance above), its Amazon ID isn’t yet known. The object is created, and then later provisioned, so you can’t get an Amazon ID until after provisioning has taken place. This method allows you to send an ID to it, and then returns the instance object that corresponds to that ID, which will have an Amazon ID with it if the instance has been provisioned at the time the environment information was read.

Note that the ID passed in must be an integer.

Usage example:

api = EY::CloudClient.new(token: 'token')
env = api.environment_by_name('my_env')
env.instance_by_id(12345)
=> <EY::CloudClient::Instance ...>


312
313
314
# File 'lib/engineyard-cloud-client/models/environment.rb', line 312

def instance_by_id(id)
  instances.detect { |x| x.id == id } # ID should always be unique
end

#instancesObject



132
133
134
# File 'lib/engineyard-cloud-client/models/environment.rb', line 132

def instances
  @instances ||= request_instances
end

#instances_by_role(*roles) ⇒ Object

Simple version of select_instances that only selects roles, not names

instances_by_role(:app_master, :app) # same
instances_by_role(%w[app_master app]) # same
select_instances(app_master: true, app: true) # same


208
209
210
211
# File 'lib/engineyard-cloud-client/models/environment.rb', line 208

def instances_by_role(*roles)
  roles = roles.flatten.map(&:to_s)
  instances.select { |inst| roles.include?(inst.role.to_s) }
end

#logsObject



148
149
150
# File 'lib/engineyard-cloud-client/models/environment.rb', line 148

def logs
  Log.from_array(api, api.get("/environments/#{id}/logs")["logs"])
end

#provisioned_instancesObject



152
153
154
# File 'lib/engineyard-cloud-client/models/environment.rb', line 152

def provisioned_instances
  instances.select { |inst| inst.provisioned? }
end

#recipesObject



219
220
221
# File 'lib/engineyard-cloud-client/models/environment.rb', line 219

def recipes
  Recipes.new(api, self)
end

#remove_instance(instance) ⇒ Object

Sends a request to the API to remove the instance specified by its “provisioned_id” (Amazon ID).

Usage example:

api = EY::CloudClient.new(token: 'token')
env = api.environment_by_name('my_app_production')
bad_instance = env.instance_by_id(12345) # instance ID should be saved upon creation
env.remove_instance(bad_instance)

Warnings/caveats:

+ The API is responsible for actually removing this instance. All this

does is send an appropriate request to the API.

+ You should look carefully at the API response JSON to see whether or

not the API accepted or rejected your request. If it accepted the
request, that instance *should* be removed as soon as possible.

+ Note that this is a client that talks to an API, which talks to an

API, which talks to an API. Ultimately the IaaS provider API has the
final say on whether or not to remove an instance, so a failure there
can definitely affect how things work at every point down the line.

+ If the instance you pass in doesn’t exist in the live cloud

environment you're working on, the status should be rejected and thus
the instance won't be removed (because *that* instance isn't there).
This is important to keep in mind for scheduled/auto scaling; if
for some reason the automatically added instance is removed before
a "scale down" event that you might trigger, you may wind up with an
unknown/unexpected number of instances in your environment.

+ Only works for app/util instances. Raises an error if you pass one

that isn't valid.


347
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
# File 'lib/engineyard-cloud-client/models/environment.rb', line 347

def remove_instance(instance)
  unless instance
    raise ArgumentError, "A argument of type Instance was expected. Got #{instance.inspect}"
  end

  # Check to make sure that we have a valid instance role here first.
  unless %w[app util].include?(instance.role)
    raise InvalidInstanceRole, "Removing instances is only supported for app, util instances"
  end

  # Check to be sure that instance is actually provisioned
  # TODO: Rip out the amazon_id stuff when we have IaaS agnosticism nailed down
  unless instance.amazon_id && instance.provisioned?
    raise InstanceNotProvisioned, "Instance is not provisioned or is in unusual state."
  end

  response = api.post("/environments/#{id}/remove_instances", :request => {
    :provisioned_id => instance.amazon_id,
    :role => instance.role,
    :name => instance.name
  })

  # Reset instances so they are fresh if they are requested again.
  @instances = nil

  # Return the response.
  return response
end

#run_custom_recipesObject

See Recipes#run



224
225
226
# File 'lib/engineyard-cloud-client/models/environment.rb', line 224

def run_custom_recipes
  recipes.run
end

#select_instances(options) ⇒ Object

Select instances by role, with optional name constraints.

Select the “master” app instance: select_instances(app_master: true)

Select the “master” db instance on a solo or multi-instance env: select_instances(solo: true, db_master: true)

Select app, app_master, or utils (only if they are named resque or redis): select_instances(app_master: true, app: true, util: %w[resque redis])

Select all instances (same as the method #instances): select_instances(all: true)

See #instances_by_role for a simpler interface.



188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/engineyard-cloud-client/models/environment.rb', line 188

def select_instances(options)
  instances_by_role(options.keys).select do |inst|
    # get the value of the string/symbol key that matches without losing nil/false values
    val = options.fetch(inst.role.to_sym) { options.fetch(inst.role.to_s, false) }

    case val
    when true, false then val
    when inst.name   then true
    when nil, ''     then [nil, ''].include?(inst.name)
    when Array       then val.include?(inst.name)
    else                  false
    end
  end
end

#shorten_name_for(app) ⇒ Object



243
244
245
# File 'lib/engineyard-cloud-client/models/environment.rb', line 243

def shorten_name_for(app)
  name.gsub(/^#{Regexp.quote(app.name)}_/, '')
end

#ssh_username=(user) ⇒ Object



144
145
146
# File 'lib/engineyard-cloud-client/models/environment.rb', line 144

def ssh_username=(user)
  self.username = user
end

#updateObject Also known as: rebuild



213
214
215
216
# File 'lib/engineyard-cloud-client/models/environment.rb', line 213

def update
  api.put("/environments/#{id}/update_instances")
  true # raises on failure
end

#upload_recipes(file_to_upload) ⇒ Object

See Recipes#upload



239
240
241
# File 'lib/engineyard-cloud-client/models/environment.rb', line 239

def upload_recipes(file_to_upload)
  recipes.upload(file_to_upload)
end

#upload_recipes_at_path(recipes_path) ⇒ Object

See Recipes#upload_path



234
235
236
# File 'lib/engineyard-cloud-client/models/environment.rb', line 234

def upload_recipes_at_path(recipes_path)
  recipes.upload_path(recipes_path)
end