lib/engineyard-serverside/configuration.rb in engineyard-serverside-2.0.0.pre3 vs lib/engineyard-serverside/configuration.rb in engineyard-serverside-2.0.0.pre4
- old
+ new
@@ -1,56 +1,124 @@
require 'json'
require 'thor'
require 'pp'
+require 'engineyard-serverside/paths'
module EY
module Serverside
class Deploy::Configuration
- DEFAULT_CONFIG = Thor::CoreExt::HashWithIndifferentAccess.new({
- "branch" => "master",
- "strategy" => "Git",
- "bundle_without" => "test development",
- })
+ include Paths::LegacyHelpers
- attr_writer :release_path
+ # Defines a fetch method for the specified key.
+ # If no default and no block is specified, it means the key is required
+ # and if it's accessed without a value, it should raise.
+ def self.def_option(name, default=nil, key=nil, &block)
+ key ||= name.to_s
+ if block_given?
+ define_method(name) { fetch(key) {instance_eval(&block)} }
+ else
+ define_method(name) { fetch(key, default) }
+ end
+ end
- def initialize(options={})
- opts = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
- @release_path = opts['release_path']
+ # Calls def_option and adds a name? predicate method
+ def self.def_boolean_option(name, default=nil, &block)
+ def_option(name, default, &block)
+ alias_method(:"#{name}?", name)
+ end
+
+ # Required options do not have a default value.
+ # An option being required does not mean that it is always supplied,
+ # it just means that if it is accessed and it does not exist, an error
+ # will be raised instead of returning a nil default value.
+ def self.def_required_option(name, key=nil)
+ key ||= name.to_s
+ define_method(name) do
+ fetch(key) { raise "Required configuration option not found: #{key.inspect}" }
+ end
+ end
+
+ def_required_option :app
+ def_required_option :environment_name
+ def_required_option :account_name
+ def_required_option :framework_env
+ def_required_option :instances
+ def_required_option :instance_roles
+ def_required_option :instance_names
+
+ def_option :repo, nil
+ def_option :migrate, nil
+ def_option :precompile_assets, nil
+ def_option :stack, nil
+ def_option :strategy, 'Git'
+ def_option :branch, 'master'
+ def_option :bundle_without, 'test development'
+ def_option :current_roles, []
+ def_option :copy_exclude, []
+ def_option(:user) { ENV['USER'] }
+ def_option(:group) { user }
+
+ def_boolean_option :verbose, false
+ def_boolean_option :ignore_database_adapter_warning, false
+ def_boolean_option :maintenance_on_migrate, true
+ def_boolean_option(:maintenance_on_restart) { required_downtime_stack? }
+
+ alias app_name app
+ alias environment framework_env # legacy because it would be nice to have less confusion around "environment"
+ alias migration_command migrate
+
+ def initialize(options)
+ opts = string_keys(options)
config = JSON.parse(opts.delete("config") || "{}")
- @configs = [config, opts] # low to high priority
+ append_config_source opts # high priority
+ append_config_source config # lower priority
end
- def configuration
- @configuration ||= @configs.inject(DEFAULT_CONFIG) {|low,high| low.merge(high)}
+ def string_keys(hsh)
+ hsh.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
end
- alias :c :configuration # FIXME: awful, but someone is probably using it :(
- # Delegate to the configuration objects
- def method_missing(meth, *args, &blk)
- configuration.key?(meth.to_s) ? configuration[meth.to_s] : super
+ def append_config_source(config_source)
+ @config_sources ||= []
+ @config_sources.unshift(config_source.dup)
+ reload_configuration!
end
- def respond_to?(meth, include_private=false)
- configuration.key?(meth.to_s) ? true : super
+ def configuration
+ @configuration ||= @config_sources.inject({}) {|low,high| low.merge(high)}
end
+ # FIXME: single letter variable is of very questionable value
+ alias :c :configuration
+ # reset cached configuration hash
+ def reload_configuration!
+ @configuration = nil
+ end
+
def load_ey_yml_data(data, shell)
environments = data['environments']
- if environments && (env_data = environments[environment_name])
+ if environments && environments[environment_name]
shell.substatus "ey.yml configuration loaded for environment #{environment_name.inspect}."
- shell.debug "#{environment_name}: #{env_data.pretty_inspect}"
- @configuration = nil # reset cached configuration hash
- @configs.unshift(env_data) # insert just above default configuration
+
+ env_data = string_keys(environments[environment_name])
+ shell.debug "#{environment_name}:\n#{env_data.pretty_inspect}"
+
+ append_config_source(string_keys(env_data)) # insert at lowest priority so as not to disturb important config
true
else
shell.info "No matching ey.yml configuration found for environment #{environment_name.inspect}."
shell.debug "ey.yml:\n#{data.pretty_inspect}"
false
end
end
+ # Fetch a key from the config.
+ # You must supply either a default second argument, or a default block
+ def fetch(key, *args, &block)
+ configuration.fetch(key.to_s, *args, &block)
+ end
+
def [](key)
if respond_to?(key.to_sym)
send(key.to_sym)
else
configuration[key]
@@ -59,205 +127,107 @@
def has_key?(key)
respond_to?(key.to_sym) || configuration.has_key?(key)
end
+ # Delegate to the configuration objects
+ def method_missing(meth, *args, &blk)
+ configuration.key?(meth.to_s) ? configuration.fetch(meth.to_s) : super
+ end
+
+ def respond_to?(meth, include_private=false)
+ configuration.key?(meth.to_s) || super
+ end
+
def to_json
configuration.to_json
end
def node
EY::Serverside.node
end
- def verbose
- configuration['verbose']
- end
-
- def app
- configuration['app'].to_s
- end
- alias app_name app
-
- def environment_name
- configuration['environment_name'].to_s
- end
-
- def account_name
- configuration['account_name'].to_s
- end
-
- def ssh_identity_file
- "~/.ssh/#{c.app}-deploy-key"
- end
-
def strategy_class
EY::Serverside::Strategies.const_get(strategy)
end
- def revision
- IO.read(File.join(latest_release, 'REVISION'))
+ def paths
+ @paths ||= Paths.new({
+ :home => configuration['home_path'],
+ :app_name => app_name,
+ :deploy_root => configuration['deploy_to'],
+ :active_release => configuration['release_path'],
+ :repository_cache => configuration['repository_cache'],
+ })
end
- def repository_cache
- configuration['repository_cache'] || File.join(deploy_to, 'shared', 'cached-copy')
+ def rollback_paths!
+ rollback_paths = paths.rollback
+ if rollback_paths
+ @paths = rollback_paths
+ true
+ else
+ false
+ end
end
- def deploy_to
- configuration['deploy_to'] || "/data/#{app}"
+ def ruby_version_command
+ "ruby -v"
end
- def migrate?
- !!configuration['migrate']
+ def system_version_command
+ "uname -m"
end
- def migration_command
- configuration['migrate'] == "migrate" ? DEFAULT_CONFIG["migrate"] : configuration['migrate']
+ def migrate?
+ !!migration_command
end
- def bundle_without
- configuration['bundle_without']
- end
-
- def user
- configuration['user'] || ENV['USER']
- end
-
- def group
- configuration['group'] || user
- end
-
def role
node['instance_role']
end
- def current_roles
- configuration['current_roles'] || []
- end
-
def current_role
current_roles.first
end
- def copy_exclude
- @copy_exclude ||= Array(configuration.fetch("copy_exclude", []))
- end
-
- def environment
- configuration['framework_env']
- end
-
- def latest_release
- all_releases.last
- end
-
- def previous_release(current=latest_release)
- index = all_releases.index(current)
- all_releases[index-1]
- end
-
- def all_releases
- Dir.glob("#{release_dir}/*").sort
- end
-
- def binstubs_path
- release_path + '/ey_bundler_binstubs'
- end
-
def framework_env_names
%w[RAILS_ENV RACK_ENV NODE_ENV MERB_ENV]
end
def framework_envs
- framework_env_names.map { |e| "#{e}=#{environment}" }.join(' ')
+ framework_env_names.map { |e| "#{e}=#{framework_env}" }.join(' ')
end
def set_framework_envs
framework_env_names.each { |e| ENV[e] = environment }
end
- def current_path
- File.join(deploy_to, "current")
- end
-
- def shared_path
- File.join(deploy_to, "shared")
- end
-
- def bundled_gems_path
- File.join(shared_path, "bundled_gems")
- end
-
- def gemfile_path
- File.join(release_path, "Gemfile")
- end
-
- def ruby_version_file
- File.join(bundled_gems_path, "RUBY_VERSION")
- end
-
- def ruby_version_command
- "ruby -v"
- end
-
- def system_version_file
- File.join(bundled_gems_path, "SYSTEM_VERSION")
- end
-
- def system_version_command
- "uname -m"
- end
-
- def release_dir
- File.join(deploy_to, "releases")
- end
-
- def failed_release_dir
- File.join(deploy_to, "releases_failed")
- end
-
- def release_path
- @release_path ||= File.join(release_dir, Time.now.utc.strftime("%Y%m%d%H%M%S"))
- end
-
def precompile_assets_inferred?
!precompile_assets? && !skip_precompile_assets?
end
def precompile_assets?
- configuration['precompile_assets'] == true
+ precompile_assets == true
end
def skip_precompile_assets?
- configuration['precompile_assets'] == false
+ precompile_assets == false
end
+ # Assume downtime required if stack is not specified (nil) just in case.
def required_downtime_stack?
- %w[ nginx_mongrel glassfish ].include? stack
+ [nil, 'nginx_mongrel', 'glassfish'].include? stack
end
- def enable_maintenance_page_on_restart?
- configuration.fetch('maintenance_on_restart', required_downtime_stack?)
- end
-
- def enable_maintenance_page_on_migrate?
- configuration.fetch('maintenance_on_migrate', true)
- end
-
+ # Enable if stack requires it or if overridden in the ey.yml config.
def enable_maintenance_page?
- enable_maintenance_page_on_restart? || (migrate? && enable_maintenance_page_on_migrate?)
+ maintenance_on_restart? || (migrate? && maintenance_on_migrate?)
end
- def maintenance_page_enabled_path
- File.join(shared_path, "system", "maintenance.html")
- end
-
- def exclusions
- copy_exclude.map { |e| %|--exclude="#{e}"| }.join(' ')
- end
-
- def ignore_database_adapter_warning?
- configuration.fetch('ignore_database_adapter_warning', false)
+ # We disable the maintenance page if we would have enabled.
+ def disable_maintenance_page?
+ enable_maintenance_page?
end
end
end
end