engineyard 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/engineyard.rb CHANGED
@@ -8,6 +8,7 @@ module EY
8
8
  autoload :Error, 'engineyard/error'
9
9
  autoload :Model, 'engineyard/model'
10
10
  autoload :Repo, 'engineyard/repo'
11
+ autoload :Resolver, 'engineyard/resolver'
11
12
 
12
13
  class UI
13
14
  # stub debug outside of the CLI
@@ -30,20 +30,12 @@ module EY
30
30
  @apps ||= EY::Model::App.from_array(request('/apps')["apps"], :api => self)
31
31
  end
32
32
 
33
- def apps_for_repo(repo)
34
- apps.find_all {|a| repo.urls.include?(a.repository_uri) }
33
+ def resolver
34
+ @resolver ||= Resolver.new(self)
35
35
  end
36
36
 
37
- def app_for_repo(repo)
38
- candidates = apps_for_repo(repo)
39
- if candidates.size > 1
40
- raise EY::AmbiguousGitUriError.new(repo.urls, candidates.map{|x| x.name})
41
- end
42
- candidates.first
43
- end
44
-
45
- def app_for_repo!(repo)
46
- app_for_repo(repo) || raise(NoAppError.new(repo))
37
+ def apps_for_repo(repo)
38
+ apps.find_all {|a| repo.urls.include?(a.repository_uri) }
47
39
  end
48
40
 
49
41
  class InvalidCredentials < EY::Error; end
@@ -44,13 +44,14 @@ module EY
44
44
  :desc => "Git ref to deploy. May be a branch, a tag, or a SHA."
45
45
  method_option :app, :type => :string, :aliases => %w(-a),
46
46
  :desc => "Name of the application to deploy"
47
+ method_option :account, :type => :string, :aliases => %w(-c),
48
+ :desc => "Name of the account you want to deploy in"
47
49
  method_option :verbose, :type => :boolean, :aliases => %w(-v),
48
50
  :desc => "Be verbose"
49
51
  method_option :extra_deploy_hook_options, :type => :hash, :default => {},
50
52
  :desc => "Additional options to be made available in deploy hooks (in the 'config' hash)"
51
53
  def deploy
52
- app = fetch_app(options[:app])
53
- environment = fetch_environment(options[:environment], app)
54
+ app, environment = fetch_app_and_environment(options[:app], options[:environment], options[:account])
54
55
  environment.ignore_bad_master = options[:ignore_bad_master]
55
56
  deploy_ref = if options[:app]
56
57
  environment.resolve_branch(options[:ref], options[:ignore_default_branch]) ||
@@ -90,8 +91,8 @@ module EY
90
91
  def environments
91
92
  if options[:all] && options[:simple]
92
93
  # just put each env
93
- api.environments.each do |env|
94
- puts env.name
94
+ api.environments.each do |environment|
95
+ puts environment.name
95
96
  end
96
97
  else
97
98
  apps = get_apps(options[:all])
@@ -120,10 +121,12 @@ module EY
120
121
 
121
122
  method_option :environment, :type => :string, :aliases => %w(-e),
122
123
  :desc => "Environment to rebuild"
124
+ method_option :account, :type => :string, :aliases => %w(-c),
125
+ :desc => "Name of the account you want to rebuild in"
123
126
  def rebuild
124
- env = fetch_environment(options[:environment])
125
- EY.ui.debug("Rebuilding #{env.name}")
126
- env.rebuild
127
+ environment = fetch_environment(options[:environment], options[:account])
128
+ EY.ui.debug("Rebuilding #{environment.name}")
129
+ environment.rebuild
127
130
  end
128
131
 
129
132
  desc "rollback [--environment ENVIRONMENT]", "Rollback to the previous deploy."
@@ -136,18 +139,17 @@ module EY
136
139
  :desc => "Environment in which to roll back the application"
137
140
  method_option :app, :type => :string, :aliases => %w(-a),
138
141
  :desc => "Name of the application to roll back"
142
+ method_option :account, :type => :string, :aliases => %w(-c),
143
+ :desc => "Name of the account you want to roll back in"
139
144
  method_option :verbose, :type => :boolean, :aliases => %w(-v),
140
145
  :desc => "Be verbose"
141
146
  method_option :extra_deploy_hook_options, :type => :hash, :default => {},
142
147
  :desc => "Additional options to be made available in deploy hooks (in the 'config' hash)"
143
148
  def rollback
144
- app = fetch_app(options[:app])
145
- env = fetch_environment(options[:environment], app)
149
+ app, environment = fetch_app_and_environment(options[:app], options[:environment], options[:account])
146
150
 
147
- EY.ui.info("Rolling back '#{app.name}' in '#{env.name}'")
148
- if env.rollback(app,
149
- options[:extra_deploy_hook_options],
150
- options[:verbose])
151
+ EY.ui.info("Rolling back '#{app.name}' in '#{environment.name}'")
152
+ if environment.rollback(app, options[:extra_deploy_hook_options], options[:verbose])
151
153
  EY.ui.info "Rollback complete"
152
154
  else
153
155
  raise EY::Error, "Rollback failed"
@@ -167,6 +169,8 @@ module EY
167
169
  DESC
168
170
  method_option :environment, :type => :string, :aliases => %w(-e),
169
171
  :desc => "Environment to ssh into"
172
+ method_option :account, :type => :string, :aliases => %w(-c),
173
+ :desc => "Name of the account you want to deploy in"
170
174
  method_option :all, :type => :boolean, :aliases => %(-a),
171
175
  :desc => "Run command on all servers"
172
176
  method_option :app_servers, :type => :boolean,
@@ -181,13 +185,13 @@ module EY
181
185
  :desc => "Run command on the utility servers with the given names. If no names are given, run on all utility servers."
182
186
 
183
187
  def ssh(cmd=nil)
184
- env = fetch_environment_without_app(options[:environment])
185
- hosts = ssh_hosts(options, env)
188
+ environment = fetch_environment(options[:environment], options[:account])
189
+ hosts = ssh_hosts(options, environment)
186
190
 
187
191
  raise NoCommandError.new if cmd.nil? and hosts.count != 1
188
192
 
189
193
  hosts.each do |host|
190
- system "ssh #{env.username}@#{host} #{cmd}"
194
+ system "ssh #{environment.username}@#{host} #{cmd}"
191
195
  end
192
196
  end
193
197
 
@@ -203,7 +207,7 @@ module EY
203
207
  return lambda {|instance| %w(solo app_master ).include?(instance.role) }
204
208
  end
205
209
 
206
- def ssh_hosts(opts, env)
210
+ def ssh_hosts(opts, environment)
207
211
  if opts[:utilities] and not opts[:utilities].respond_to?(:include?)
208
212
  includes_everything = []
209
213
  class << includes_everything
@@ -214,8 +218,8 @@ module EY
214
218
  filter = ssh_host_filter(opts)
215
219
  end
216
220
 
217
- instances = env.instances.select {|instance| filter[instance] }
218
- raise NoInstancesError.new(env.name) if instances.empty?
221
+ instances = environment.instances.select {|instance| filter[instance] }
222
+ raise NoInstancesError.new(environment.name) if instances.empty?
219
223
  return instances.map { |instance| instance.public_hostname }
220
224
  end
221
225
  end
@@ -228,18 +232,20 @@ module EY
228
232
  DESC
229
233
  method_option :environment, :type => :string, :aliases => %w(-e),
230
234
  :desc => "Environment with the interesting logs"
235
+ method_option :account, :type => :string, :aliases => %w(-c),
236
+ :desc => "Name of the account you want to deploy in"
231
237
  def logs
232
- env = fetch_environment(options[:environment])
233
- env.logs.each do |log|
238
+ environment = fetch_environment(options[:environment], options[:account])
239
+ environment.logs.each do |log|
234
240
  EY.ui.info log.instance_name
235
241
 
236
242
  if log.main
237
- EY.ui.info "Main logs for #{env.name}:"
243
+ EY.ui.info "Main logs for #{environment.name}:"
238
244
  EY.ui.say log.main
239
245
  end
240
246
 
241
247
  if log.custom
242
- EY.ui.info "Custom logs for #{env.name}:"
248
+ EY.ui.info "Custom logs for #{environment.name}:"
243
249
  EY.ui.say log.custom
244
250
  end
245
251
  end
@@ -10,8 +10,10 @@ module EY
10
10
 
11
11
  method_option :environment, :type => :string, :aliases => %w(-e),
12
12
  :desc => "Environment in which to apply recipes"
13
+ method_option :account, :type => :string, :aliases => %w(-c),
14
+ :desc => "Name of the account you want to deploy in"
13
15
  def apply
14
- environment = fetch_environment_without_app(options[:environment])
16
+ environment = fetch_environment(options[:environment], options[:account])
15
17
  environment.run_custom_recipes
16
18
  EY.ui.say "Uploaded recipes started for #{environment.name}"
17
19
  end
@@ -25,8 +27,10 @@ module EY
25
27
 
26
28
  method_option :environment, :type => :string, :aliases => %w(-e),
27
29
  :desc => "Environment that will receive the recipes"
30
+ method_option :account, :type => :string, :aliases => %w(-c),
31
+ :desc => "Name of the account you want to deploy in"
28
32
  def upload
29
- environment = fetch_environment_without_app(options[:environment])
33
+ environment = fetch_environment(options[:environment], options[:account])
30
34
  environment.upload_recipes
31
35
  EY.ui.say "Recipes uploaded successfully for #{environment.name}"
32
36
  end
@@ -41,8 +45,10 @@ module EY
41
45
  DESC
42
46
  method_option :environment, :type => :string, :aliases => %w(-e),
43
47
  :desc => "Environment for which to download the recipes"
48
+ method_option :account, :type => :string, :aliases => %w(-c),
49
+ :desc => "Name of the account you want to deploy in"
44
50
  def download
45
- environment = fetch_environment_without_app(options[:environment])
51
+ environment = fetch_environment(options[:environment], options[:account])
46
52
  environment.download_recipes
47
53
  EY.ui.say "Recipes downloaded successfully for #{environment.name}"
48
54
  end
@@ -67,7 +67,7 @@ module EY
67
67
  end
68
68
  else
69
69
  apps.each do |app|
70
- puts app.name
70
+ puts "#{app.name} (#{app.account.name})"
71
71
  if app.environments.any?
72
72
  app.environments.each do |env|
73
73
  short_name = env.shorten_name_for(app)
@@ -9,9 +9,10 @@ module EY
9
9
  :desc => "Name of the application whose maintenance page will be removed"
10
10
  method_option :verbose, :type => :boolean, :aliases => %w(-v),
11
11
  :desc => "Be verbose"
12
+ method_option :account, :type => :string, :aliases => %w(-c),
13
+ :desc => "Name of the account you want to deploy in"
12
14
  def enable
13
- app = fetch_app(options[:app])
14
- environment = fetch_environment(options[:environment], app)
15
+ app, environment = fetch_app_and_environment(options[:app], options[:environment], options[:account])
15
16
  EY.ui.info "Taking down maintenance page for '#{app.name}' in '#{environment.name}'"
16
17
  environment.take_down_maintenance_page(app, options[:verbose])
17
18
  end
@@ -35,9 +36,10 @@ module EY
35
36
  :desc => "Name of the application whose maintenance page will be put up"
36
37
  method_option :verbose, :type => :boolean, :aliases => %w(-v),
37
38
  :desc => "Be verbose"
39
+ method_option :account, :type => :string, :aliases => %w(-c),
40
+ :desc => "Name of the account you want to deploy in"
38
41
  def disable
39
- app = fetch_app(options[:app])
40
- environment = fetch_environment(options[:environment], app)
42
+ app, environment = fetch_app_and_environment(options[:app], options[:environment], options[:account])
41
43
  EY.ui.info "Putting up maintenance page for '#{app.name}' in '#{environment.name}'"
42
44
  environment.put_up_maintenance_page(app, options[:verbose])
43
45
  end
@@ -8,8 +8,14 @@ module EY
8
8
  We are working on letting you specify the account name to resolve this ambiguity.
9
9
  MSG
10
10
 
11
- def named(name)
12
- candidates = find_all {|x| x.name == name }
11
+ def named(name, account_name=nil)
12
+ candidates = find_all do |x|
13
+ if account_name
14
+ x.name == name && x.account.name == account_name
15
+ else
16
+ x.name == name
17
+ end
18
+ end
13
19
  if candidates.size > 1
14
20
  raise ambiguous_error(name, candidates.map {|e| e.name}, COLLAB_MESSAGE )
15
21
  end
@@ -7,6 +7,10 @@ module EY
7
7
  end
8
8
  end
9
9
 
10
+ class ResolveError < EY::Error; end
11
+ class NoMatchesError < ResolveError; end
12
+ class MultipleMatchesError < ResolveError; end
13
+
10
14
  class NoCommandError < EY::Error
11
15
  def initialize
12
16
  super "Must specify a command to run via ssh"
@@ -41,18 +45,6 @@ module EY
41
45
  end
42
46
  end
43
47
 
44
- class AmbiguousGitUriError < EY::Error
45
- def initialize(uris, matches)
46
- message = "The following Git Remote urls match multiple Applications in AppCloud\n"
47
- uris.each { |uri| message << "\t#{uri}\n" }
48
-
49
- message << "Please use -a <appname> to specify one of the following applications:\n"
50
- matches.each { |app| message << "\t#{app}\n" }
51
-
52
- super message
53
- end
54
- end
55
-
56
48
  class NoAppMasterError < EY::Error
57
49
  def initialize(env_name)
58
50
  super "The environment '#{env_name}' does not have a master instance."
@@ -84,7 +76,13 @@ module EY
84
76
  def initialize(environments)
85
77
  message = "The repository url in this directory is ambiguous.\n"
86
78
  message << "Please use -e <envname> to specify one of the following environments:\n"
87
- environments.each { |env| message << "\t#{env.name}\n" }
79
+ environments.sort do |a, b|
80
+ if a.account == b.account
81
+ a.name <=> b.name
82
+ else
83
+ a.account.name <=> b.account.name
84
+ end
85
+ end.each { |env| message << "\t#{env.name} (#{env.account.name})\n" }
88
86
  super message
89
87
  end
90
88
  end
@@ -110,15 +108,15 @@ module EY
110
108
 
111
109
  class BranchMismatchError < EY::Error
112
110
  def initialize(default_branch, branch)
113
- super %|Your deploy branch is set to "#{default_branch}".\n| +
114
- %|If you want to deploy branch "#{branch}", use --ignore-default_branch.|
111
+ super(%|Your deploy branch is set to "#{default_branch}".\n| +
112
+ %|If you want to deploy branch "#{branch}", use --ignore-default_branch.|)
115
113
  end
116
114
  end
117
115
 
118
116
  class DeployArgumentError < EY::Error
119
117
  def initialize
120
- super %("deploy" was called incorrectly. Call as "deploy [--environment <env>] [--ref <branch|tag|ref>]"\n) +
121
- %|You can set default environments and branches in ey.yml|
118
+ super(%("deploy" was called incorrectly. Call as "deploy [--environment <env>] [--ref <branch|tag|ref>]"\n) +
119
+ %|You can set default environments and branches in ey.yml|)
122
120
  end
123
121
  end
124
122
  end
@@ -1,9 +1,10 @@
1
1
  module EY
2
2
  module Model
3
- autoload :ApiStruct, 'engineyard/model/api_struct'
4
- autoload :App, 'engineyard/model/app'
5
- autoload :Environment, 'engineyard/model/environment'
6
- autoload :Log, 'engineyard/model/log'
7
- autoload :Instance, 'engineyard/model/instance'
3
+ autoload :ApiStruct, 'engineyard/model/api_struct'
4
+ autoload :Account, 'engineyard/model/account'
5
+ autoload :App, 'engineyard/model/app'
6
+ autoload :Environment, 'engineyard/model/environment'
7
+ autoload :Log, 'engineyard/model/log'
8
+ autoload :Instance, 'engineyard/model/instance'
8
9
  end
9
10
  end
@@ -0,0 +1,8 @@
1
+ require 'escape'
2
+
3
+ module EY
4
+ module Model
5
+ class Account < ApiStruct.new(:id, :name)
6
+ end
7
+ end
8
+ end
@@ -1,10 +1,11 @@
1
1
  module EY
2
2
  module Model
3
- class App < ApiStruct.new(:name, :repository_uri, :environments, :api)
3
+ class App < ApiStruct.new(:id, :account, :name, :repository_uri, :environments, :api)
4
4
 
5
5
  def self.from_hash(hash)
6
6
  super.tap do |app|
7
7
  app.environments = Environment.from_array(app.environments, :api => app.api)
8
+ app.account = Account.from_hash(app.account)
8
9
  end
9
10
  end
10
11
 
@@ -1,6 +1,6 @@
1
1
  module EY
2
2
  module Model
3
- class Environment < ApiStruct.new(:id, :name, :framework_env, :instances, :instances_count, :apps, :app_master, :username, :stack_name, :api)
3
+ class Environment < ApiStruct.new(:id, :account, :name, :framework_env, :instances, :instances_count, :apps, :app_master, :username, :stack_name, :api)
4
4
 
5
5
  attr_accessor :ignore_bad_master
6
6
 
@@ -8,6 +8,7 @@ module EY
8
8
  super.tap do |env|
9
9
  env.username = hash['ssh_username']
10
10
  env.apps = App.from_array(env.apps, :api => env.api)
11
+ env.account = Account.from_hash(env.account)
11
12
  env.instances = Instance.from_array(hash['instances'], :environment => env)
12
13
  env.app_master = Instance.from_hash(env.app_master.merge(:environment => env)) if env.app_master
13
14
  end
@@ -7,8 +7,12 @@ module EY
7
7
  @path = path
8
8
  end
9
9
 
10
+ def exists?
11
+ File.directory?(File.join(@path, ".git"))
12
+ end
13
+
10
14
  def current_branch
11
- if File.directory?(File.join(@path, ".git"))
15
+ if exists?
12
16
  head = File.read(File.join(@path, ".git/HEAD")).chomp
13
17
  if head.gsub!("ref: refs/heads/", "")
14
18
  head
@@ -0,0 +1,122 @@
1
+ module EY
2
+ class Resolver
3
+ attr_reader :api
4
+
5
+ def initialize(api)
6
+ @api = api
7
+ end
8
+
9
+ def environment(options)
10
+ raise ArgumentError if options[:app_name]
11
+ candidates, account_candidates, app_candidates, environment_candidates = filter_candidates(options)
12
+
13
+ environments = candidates.map{ |c| [c[:account_name], c[:environment_name]] }.uniq.map do |account_name, environment_name|
14
+ api.environments.named(environment_name, account_name)
15
+ end
16
+
17
+ if environments.empty?
18
+ if options[:environment_name]
19
+ raise EY::NoEnvironmentError.new(options[:environment_name])
20
+ else
21
+ raise EY::NoAppError.new(options[:repo])
22
+ end
23
+ elsif environments.size > 1
24
+ if options[:environment_name]
25
+ message = "Multiple environments possible, please be more specific:\n\n"
26
+ candidates.map{|e| [e[:account_name], e[:environment_name]]}.uniq.each do |account_name, environment_name|
27
+ message << "\t#{environment_name} # ey <command> --environment='#{environment_name}' --account='#{account_name}'\n"
28
+ end
29
+ raise MultipleMatchesError.new(message)
30
+ else
31
+ raise EY::AmbiguousEnvironmentGitUriError.new(environments)
32
+ end
33
+ end
34
+ environments.first
35
+ end
36
+
37
+ def app_and_environment(options)
38
+ candidates, account_candidates, app_candidates, environment_candidates = filter_candidates(options)
39
+
40
+ if candidates.empty?
41
+ if account_candidates.empty? && options[:account_name]
42
+ raise NoMatchesError.new("There were no accounts that matched #{options[:account_name]}")
43
+ elsif app_candidates.empty?
44
+ if options[:app_name]
45
+ raise InvalidAppError.new(options[:app_name])
46
+ else
47
+ raise NoAppError.new(options[:repo])
48
+ end
49
+ elsif environment_candidates.empty?
50
+ raise NoEnvironmentError.new(options[:environment_name])
51
+ else
52
+ message = "The matched apps & environments do not correspond with each other.\n"
53
+ message << "Applications:\n"
54
+ app_candidates.map{|ad| [ad[:account_name], ad[:app_name]]}.uniq.each do |account_name, app_name|
55
+ app = api.apps.named(app_name, account_name)
56
+ message << "\t#{app.name}\n"
57
+ app.environments.each do |env|
58
+ message << "\t\t#{env.name} # ey <command> -e #{env.name} -a #{app.name}\n"
59
+ end
60
+ end
61
+ end
62
+ raise NoMatchesError.new(message)
63
+ elsif candidates.size > 1
64
+ message = "Multiple app deployments possible, please be more specific:\n\n"
65
+ candidates.map{|c| [c[:account_name], c[:app_name]]}.uniq.each do |account_name, app_name|
66
+ message << "#{app_name}\n"
67
+ candidates.select {|x| x[:app_name] == app_name && x[:account_name] == account_name}.map{|x| x[:environment_name]}.uniq.each do |env_name|
68
+ message << "\t#{env_name} # ey <command> --environment='#{env_name}' --app='#{app_name}' --account='#{account_name}'\n"
69
+ end
70
+ end
71
+ raise MultipleMatchesError.new(message)
72
+ end
73
+ result = candidates.first
74
+ [api.apps.named(result[:app_name], result[:account_name]), api.environments.named(result[:environment_name], result[:account_name])]
75
+ end
76
+
77
+ private
78
+
79
+ def app_deployments
80
+ @app_deployments ||= api.apps.map do |app|
81
+ app.environments.map do |environment|
82
+ {
83
+ :app_name => app.name,
84
+ :repository_uri => app.repository_uri,
85
+ :environment_name => environment.name,
86
+ :account_name => app.account.name,
87
+ }
88
+ end
89
+ end.flatten
90
+ end
91
+
92
+ def filter_candidates(options)
93
+ raise ArgumentError if options.empty?
94
+
95
+ candidates = app_deployments
96
+
97
+ account_candidates = filter_candidates_by(:account_name, options, candidates)
98
+
99
+ app_candidates = if options[:app_name]
100
+ filter_candidates_by(:app_name, options, candidates)
101
+ elsif options[:repo]
102
+ candidates.select {|c| options[:repo].urls.include?(c[:repository_uri]) }
103
+ else
104
+ candidates
105
+ end
106
+
107
+ environment_candidates = filter_candidates_by(:environment_name, options, candidates)
108
+ candidates = app_candidates & environment_candidates & account_candidates
109
+ [candidates, account_candidates, app_candidates, environment_candidates]
110
+ end
111
+
112
+ def filter_candidates_by(type, options, candidates)
113
+ if options[type] && candidates.any?{|c| c[type] == options[type] }
114
+ candidates.select {|c| c[type] == options[type] }
115
+ elsif options[type]
116
+ candidates.select {|c| c[type][options[type]] }
117
+ else
118
+ candidates
119
+ end
120
+ end
121
+ end
122
+ end