class EY::CloudClient::Environment
Attributes
Public Class Methods
Return list of all Environments linked to all current user’s accounts
# 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
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.
# File lib/engineyard-cloud-client/models/environment.rb, line 45 def self.by_name(api, environment_name, account_name=nil) constraints = { :environment_name => environment_name, :account_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.account}.each do |env| message << "\t#{env.account.name}/#{env.name}\n" end raise MultipleMatchesError.new(message) end end
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
# 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
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
# 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
Protected Class Methods
attrs[“cluster”] can be ‘single’, ‘cluster’, or ‘custom’ attrs[“ip”] can be
* 'host' (amazon public hostname) * 'new' (Elastic IP assigned, default) * or an IP id
if ‘custom’ cluster, then…
# File lib/engineyard-cloud-client/models/environment.rb, line 420 def self.unpack_cluster_configuration(attrs, configuration) if configuration attrs["cluster_configuration"] = configuration attrs["cluster_configuration"]["configuration"] ||= 'single' attrs["cluster_configuration"]["ip_id"] = configuration.delete("ip") || 'new' # amazon public hostname; alternate is 'new' for Elastic IP # if cluster_type == 'custom' # attrs['cluster_configuration'][app_server_count] = options[:app_instances] || 2 # attrs['cluster_configuration'][db_slave_count] = options[:db_instances] || 0 # attrs['cluster_configuration'][instance_size] = options[:app_size] if options[:app_size] # attrs['cluster_configuration'][db_instance_size] = options[:db_size] if options[:db_size] # end # at end end
Public Instance Methods
# File lib/engineyard-cloud-client/models/environment.rb, line 136 def account_name account && account.name end
# 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
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.
# 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
# File lib/engineyard-cloud-client/models/environment.rb, line 124 def app_environments @app_environments ||= [] end
# File lib/engineyard-cloud-client/models/environment.rb, line 103 def attributes=(attrs) account_attrs = attrs.delete('account') apps_attrs = attrs.delete('apps') instances_attrs = attrs.delete('instances') super set_account account_attrs if account_attrs set_apps apps_attrs if apps_attrs set_instances instances_attrs if instances_attrs end
# File lib/engineyard-cloud-client/models/environment.rb, line 160 def bridge @bridge ||= instances.detect { |inst| inst.bridge? } end
# 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
# File lib/engineyard-cloud-client/models/environment.rb, line 156 def deploy_to_instances provisioned_instances.select { |inst| inst.has_app_code? } end
See Recipes#download
# File lib/engineyard-cloud-client/models/environment.rb, line 229 def download_recipes recipes.download end
# File lib/engineyard-cloud-client/models/environment.rb, line 140 def hierarchy_name [account_name, name].join(" / ") end
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 ...>
# 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
# File lib/engineyard-cloud-client/models/environment.rb, line 132 def instances @instances ||= request_instances end
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
# 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
# File lib/engineyard-cloud-client/models/environment.rb, line 148 def logs Log.from_array(api, api.get("/environments/#{id}/logs")["logs"]) end
# File lib/engineyard-cloud-client/models/environment.rb, line 152 def provisioned_instances instances.select { |inst| inst.provisioned? } end
# File lib/engineyard-cloud-client/models/environment.rb, line 219 def recipes Recipes.new(api, self) end
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.
# 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
See Recipes#run
# File lib/engineyard-cloud-client/models/environment.rb, line 224 def run_custom_recipes recipes.run end
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.
# 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
# File lib/engineyard-cloud-client/models/environment.rb, line 243 def shorten_name_for(app) name.gsub(/^#{Regexp.quote(app.name)}_/, '') end
# File lib/engineyard-cloud-client/models/environment.rb, line 144 def ssh_username=(user) self.username = user end
# File lib/engineyard-cloud-client/models/environment.rb, line 213 def update api.put("/environments/#{id}/update_instances") true # raises on failure end
See Recipes#upload
# File lib/engineyard-cloud-client/models/environment.rb, line 239 def upload_recipes(file_to_upload) recipes.upload(file_to_upload) end
# File lib/engineyard-cloud-client/models/environment.rb, line 234 def upload_recipes_at_path(recipes_path) recipes.upload_path(recipes_path) end
Protected Instance Methods
# File lib/engineyard-cloud-client/models/environment.rb, line 406 def load_instances(instances_attrs) Instance.from_array(api, instances_attrs, 'environment' => self).sort end
# File lib/engineyard-cloud-client/models/environment.rb, line 397 def request_instances if instances_count.zero? [] else instances_attrs = api.get("/environments/#{id}/instances")["instances"] load_instances(instances_attrs) end end
# File lib/engineyard-cloud-client/models/environment.rb, line 378 def set_account(account_attrs) @account = Account.from_hash(api, account_attrs) @account.add_environment(self) @account end
Creating an AppEnvironment
will come back and call add_app_environment
(above) to associate this model with the AppEnvironment
. (that’s why we don’t save anything here.)
# File lib/engineyard-cloud-client/models/environment.rb, line 387 def set_apps(apps_attrs) (apps_attrs || []).each do |app| AppEnvironment.from_hash(api, {'app' => app, 'environment' => self}) end end
# File lib/engineyard-cloud-client/models/environment.rb, line 393 def set_instances(instances_attrs) @instances = load_instances(instances_attrs) end
# File lib/engineyard-cloud-client/models/environment.rb, line 410 def sort_attributes [sort_string(account_name), sort_string(name)] end