engineyard-serverside 2.0.7 → 2.1.0.pre

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.
Files changed (98) hide show
  1. data/lib/engineyard-serverside.rb +0 -1
  2. data/lib/engineyard-serverside/cli.rb +44 -42
  3. data/lib/engineyard-serverside/configuration.rb +55 -4
  4. data/lib/engineyard-serverside/dependency_manager.rb +17 -0
  5. data/lib/engineyard-serverside/dependency_manager/base.rb +65 -0
  6. data/lib/engineyard-serverside/dependency_manager/bundler.rb +124 -0
  7. data/lib/engineyard-serverside/dependency_manager/bundler_lock.rb +155 -0
  8. data/lib/engineyard-serverside/dependency_manager/legacy_helpers.rb +24 -0
  9. data/lib/engineyard-serverside/dependency_manager/npm.rb +16 -0
  10. data/lib/engineyard-serverside/deploy.rb +86 -178
  11. data/lib/engineyard-serverside/deprecation.rb +11 -1
  12. data/lib/engineyard-serverside/paths.rb +6 -0
  13. data/lib/engineyard-serverside/propagator.rb +2 -2
  14. data/lib/engineyard-serverside/rails_assets.rb +152 -0
  15. data/lib/engineyard-serverside/rails_assets/strategy.rb +197 -0
  16. data/lib/engineyard-serverside/server.rb +5 -0
  17. data/lib/engineyard-serverside/servers.rb +19 -7
  18. data/lib/engineyard-serverside/shell.rb +7 -5
  19. data/lib/engineyard-serverside/shell/command_result.rb +1 -1
  20. data/lib/engineyard-serverside/strategies/git.rb +14 -4
  21. data/lib/engineyard-serverside/task.rb +1 -0
  22. data/lib/engineyard-serverside/version.rb +1 -1
  23. data/spec/bundler_deploy_spec.rb +36 -33
  24. data/spec/configuration_spec.rb +5 -4
  25. data/spec/custom_deploy_spec.rb +11 -9
  26. data/spec/deploy_hook_spec.rb +10 -3
  27. data/spec/ey_yml_customized_deploy_spec.rb +1 -1
  28. data/spec/fixtures/lockfiles/1.0-no-bundler +1 -1
  29. data/spec/fixtures/lockfiles/1.0.0.rc.1-with-bundler +1 -1
  30. data/spec/fixtures/lockfiles/1.0.18-do_mysql +1 -1
  31. data/spec/fixtures/lockfiles/1.0.18-do_postgres +1 -1
  32. data/spec/fixtures/lockfiles/1.0.18-mysql +1 -1
  33. data/spec/fixtures/lockfiles/1.0.18-mysql2 +1 -1
  34. data/spec/fixtures/lockfiles/1.0.18-pg +1 -1
  35. data/spec/fixtures/lockfiles/1.0.6-no-bundler +2 -2
  36. data/spec/fixtures/lockfiles/1.0.6-with-any-bundler +2 -2
  37. data/spec/fixtures/lockfiles/1.0.6-with-bundler +2 -2
  38. data/spec/fixtures/lockfiles/1.3.1-rails-3.2.13 +112 -0
  39. data/spec/fixtures/repos/{assets_enabled → assets_detected}/Gemfile +1 -2
  40. data/spec/fixtures/repos/{assets_enabled → assets_detected}/Gemfile.lock +1 -3
  41. data/spec/fixtures/repos/{assets_enabled → assets_detected}/README +0 -0
  42. data/spec/fixtures/repos/assets_detected/Rakefile +5 -0
  43. data/spec/fixtures/repos/{assets_enabled → assets_detected}/app/assets/empty +0 -0
  44. data/spec/fixtures/repos/{assets_enabled → assets_detected}/config/application.rb +0 -0
  45. data/spec/fixtures/repos/assets_detected/config/ey.yml +3 -0
  46. data/spec/fixtures/repos/assets_disabled/Gemfile +1 -2
  47. data/spec/fixtures/repos/assets_disabled/Gemfile.lock +1 -3
  48. data/spec/fixtures/repos/assets_disabled/Rakefile +1 -0
  49. data/spec/fixtures/repos/assets_disabled/config/ey.yml +3 -0
  50. data/spec/fixtures/repos/assets_disabled_in_ey_yml/Gemfile +1 -2
  51. data/spec/fixtures/repos/assets_disabled_in_ey_yml/Gemfile.lock +1 -3
  52. data/spec/fixtures/repos/assets_disabled_in_ey_yml/Rakefile +1 -0
  53. data/spec/fixtures/repos/assets_disabled_in_ey_yml/config/ey.yml +1 -0
  54. data/spec/fixtures/repos/assets_enabled_all/Gemfile +1 -2
  55. data/spec/fixtures/repos/assets_enabled_all/Gemfile.lock +1 -3
  56. data/spec/fixtures/repos/assets_enabled_all/Rakefile +1 -0
  57. data/spec/fixtures/repos/assets_enabled_all/config/ey.yml +1 -0
  58. data/spec/fixtures/repos/assets_enabled_in_ey_yml/Gemfile +1 -1
  59. data/spec/fixtures/repos/assets_enabled_in_ey_yml/Gemfile.lock +1 -1
  60. data/spec/fixtures/repos/assets_enabled_in_ey_yml/Rakefile +1 -0
  61. data/spec/fixtures/repos/assets_enabled_util_only/Gemfile +1 -2
  62. data/spec/fixtures/repos/assets_enabled_util_only/Gemfile.lock +1 -3
  63. data/spec/fixtures/repos/assets_enabled_util_only/Rakefile +1 -0
  64. data/spec/fixtures/repos/assets_enabled_util_only/config/ey.yml +1 -0
  65. data/spec/fixtures/repos/assets_in_hook/Gemfile +1 -2
  66. data/spec/fixtures/repos/assets_in_hook/Gemfile.lock +1 -3
  67. data/spec/fixtures/repos/assets_in_hook/config/ey.yml +3 -0
  68. data/spec/fixtures/repos/assets_in_hook/deploy/before_compile_assets.rb +1 -1
  69. data/spec/fixtures/repos/bundle_fails/Gemfile +1 -0
  70. data/spec/fixtures/repos/bundle_fails/README +1 -0
  71. data/spec/fixtures/repos/bundle_fails/deploy/after_bundle.rb +1 -0
  72. data/spec/fixtures/repos/default/Gemfile +1 -2
  73. data/spec/fixtures/repos/default/Gemfile.lock +1 -3
  74. data/spec/fixtures/repos/default/ey.yml +3 -0
  75. data/spec/fixtures/repos/ey_yml/Gemfile +1 -1
  76. data/spec/fixtures/repos/ey_yml/Gemfile.lock +1 -1
  77. data/spec/fixtures/repos/ey_yml/config/ey.yml +11 -7
  78. data/spec/fixtures/repos/ey_yml_alt/Gemfile +1 -1
  79. data/spec/fixtures/repos/ey_yml_alt/Gemfile.lock +1 -1
  80. data/spec/fixtures/repos/no_ey_config/Gemfile +1 -2
  81. data/spec/fixtures/repos/no_ey_config/Gemfile.lock +1 -3
  82. data/spec/fixtures/repos/no_ey_config/ey.yml +3 -0
  83. data/spec/fixtures/repos/no_gemfile_lock/Gemfile +1 -2
  84. data/spec/fixtures/repos/no_gemfile_lock/ey.yml +3 -0
  85. data/spec/fixtures/repos/sqlite3/Gemfile +1 -1
  86. data/spec/fixtures/repos/sqlite3/Gemfile.lock +1 -1
  87. data/spec/lockfile_parser_spec.rb +25 -11
  88. data/spec/rails31_deploy_spec.rb +46 -5
  89. data/spec/restart_spec.rb +3 -3
  90. data/spec/services_deploy_spec.rb +89 -86
  91. data/spec/shell_spec.rb +0 -8
  92. data/spec/spec_helper.rb +81 -36
  93. data/spec/sqlite3_deploy_spec.rb +4 -5
  94. data/spec/support/integration.rb +22 -37
  95. metadata +167 -154
  96. data/lib/engineyard-serverside/lockfile_parser.rb +0 -101
  97. data/lib/engineyard-serverside/rails_asset_support.rb +0 -132
  98. data/spec/fixtures/repos/assets_enabled/Rakefile +0 -5
@@ -6,10 +6,20 @@ module EY
6
6
  $stderr.puts "DEPRECATION WARNING: #{msg}\n\t#{caller(2).first}"
7
7
  end
8
8
 
9
+ def self.deprecated_task(receiver, old_task, new_task)
10
+ if receiver.respond_to?(old_task)
11
+ deprecation_warning("Task ##{old_task} has been renamed to ##{new_task}.")
12
+ end
13
+ end
14
+
9
15
  def self.const_missing(const)
10
- if const == :LoggedOutput
16
+ case const
17
+ when :LoggedOutput
11
18
  EY::Serverside.deprecation_warning("EY::Serverside::LoggedOutput has been deprecated. Use EY::Serverside::Shell::Helpers instead.")
12
19
  EY::Serverside::Shell::Helpers
20
+ when :LockfileParser
21
+ EY::Serverside.deprecation_warning("EY::Serverside::LockfileParser has been deprecated. Use EY::Serverside::DependencyManager::BundlerLock::Lockfile instead.")
22
+ EY::Serverside::DependencyManager::BundlerLock::Lockfile
13
23
  else
14
24
  super
15
25
  end
@@ -70,6 +70,7 @@ module EY
70
70
  def_path :enabled_maintenance_page, [:shared_system, 'maintenance.html']
71
71
  def_path :shared_assets, [:shared, 'assets']
72
72
  def_path :bundled_gems, [:shared, 'bundled_gems']
73
+ def_path :shared_services_yml, [:shared_config, 'ey_services_config_deploy.yml']
73
74
  def_path :ruby_version, [:bundled_gems, 'RUBY_VERSION']
74
75
  def_path :system_version, [:bundled_gems, 'SYSTEM_VERSION']
75
76
  def_path :latest_revision, [:latest_release, 'REVISION']
@@ -127,6 +128,11 @@ module EY
127
128
  end
128
129
  end
129
130
 
131
+ def previous_revision
132
+ rel = previous_release(active_release)
133
+ rel && rel.join('REVISION')
134
+ end
135
+
130
136
  # deploy_root/releases/<latest timestamp>
131
137
  def latest_release
132
138
  all_releases.last
@@ -67,8 +67,8 @@ module EY
67
67
 
68
68
  def propagate
69
69
  shell.status "Propagating #{About.name_with_version} to #{count_servers(servers)}."
70
- servers.run_on_each { |server| shell.logged_system(scp_command(server)) }
71
- servers.run_on_each { |server| shell.logged_system(server.command_on_server('sudo sh -l -c', install_command)) }
70
+ servers.run_on_each(shell) { |server| shell.logged_system(scp_command(server)) }
71
+ servers.run_on_each(shell) { |server| shell.logged_system(server.command_on_server('sudo sh -l -c', install_command)) }
72
72
  end
73
73
  end
74
74
  end
@@ -0,0 +1,152 @@
1
+ require 'engineyard-serverside/rails_assets/strategy'
2
+ require 'forwardable'
3
+
4
+ module EY
5
+ module Serverside
6
+ class RailsAssets
7
+ extend Forwardable
8
+
9
+ def self.detect_and_compile(*args)
10
+ new(*args).detect_and_compile
11
+ end
12
+
13
+ attr_reader :config, :shell, :runner
14
+
15
+ def initialize(config, shell, runner)
16
+ @config, @shell, @runner = config, shell, runner
17
+ end
18
+
19
+ def_delegators :config,
20
+ :paths, :asset_dependencies, :asset_roles,
21
+ :framework_envs, :precompile_assets?, :skip_precompile_assets?,
22
+ :precompile_unchanged_assets?, :precompile_assets_task
23
+
24
+ def detect_and_compile
25
+ runner.roles asset_roles do
26
+ if precompile_assets?
27
+ if precompile_unchanged_assets?
28
+ shell.status "Precompiling assets without change detection. (precompile_unchanged_assets: true)"
29
+ run_precompile_assets_task
30
+ elsif reuse_assets?
31
+ shell.status "Reusing existing assets. (configured asset_dependencies unchanged from #{previous_revision[0,7]}..#{active_revision[0,7]})"
32
+ asset_strategy.reuse
33
+ else
34
+ shell.status "Precompiling assets. (precompile_assets: true)"
35
+ run_precompile_assets_task
36
+ end
37
+ elsif skip_precompile_assets?
38
+ shell.status "Skipping asset precompilation. (precompile_assets: false)"
39
+ elsif !application_rb_path.readable? || !app_assets_path.directory?
40
+ # Not a Rails app. Ignore assets completely.
41
+ elsif app_disables_assets?
42
+ shell.status "Skipping asset precompilation. ('config/application.rb' disables assets.)"
43
+ elsif paths.public_assets.exist?
44
+ shell.status "Skipping asset precompilation. ('public/assets' directory already exists.)"
45
+ else
46
+ precompile_detected_assets
47
+ end
48
+ end
49
+ end
50
+
51
+ def run_precompile_assets_task
52
+ asset_strategy.prepare do
53
+ cd = "cd #{paths.active_release}"
54
+ task = "PATH=#{paths.binstubs}:$PATH #{framework_envs} rake #{precompile_assets_task} RAILS_GROUPS=assets"
55
+ runner.run "#{cd} && #{task}"
56
+ end
57
+ end
58
+
59
+ def previous_revision
60
+ @previous_revision ||= config.previous_revision
61
+ end
62
+
63
+ def active_revision
64
+ @active_revision ||= config.active_revision
65
+ end
66
+
67
+ # Note on reusing assets when assets may fail silently:
68
+ # It's difficult and error prone to reuse assets that may have failed
69
+ # silently in the previous deploy. If the assets are unchanged during
70
+ # this deploy, but failed last deploy, we would incorrectly reuse
71
+ # silentely failed assets. Only reusing when assets are enabled
72
+ # ensures that existing assets were successful.
73
+ def reuse_assets?
74
+ previous_revision &&
75
+ active_revision &&
76
+ runner.strategy.same?(previous_revision, active_revision, asset_dependencies)
77
+ end
78
+
79
+ def precompile_detected_assets
80
+ shell.status "Precompiling assets. ('#{app_assets}' exists, 'public/assets' not found, not disabled in config.)"
81
+ if !runner.dependency_manager.rails_version
82
+ shell.warning "Precompiling assets even though Rails was not bundled."
83
+ end
84
+
85
+ run_precompile_assets_task
86
+
87
+ shell.warning <<-WARN
88
+ Inferred asset compilation succeeded, but failures may be silently ignored!
89
+
90
+ ACTION REQUIRED: Add precompile_assets option to ey.yml.
91
+ precompile_assets: true # precompile assets when asset changes detected
92
+ WARN
93
+ rescue EY::Serverside::RemoteFailure => e
94
+ # If we are implicitly precompiling, we want to fail non-destructively
95
+ # because we don't know if the rake task exists or if the user
96
+ # actually intended for assets to be compiled.
97
+ if e.to_s =~ /Don't know how to build task '#{precompile_assets_task}'/
98
+ shell.warning <<-WARN
99
+ Asset precompilation detected but compilation failure ignored!
100
+ Rake task '#{precompile_assets_task}' was not found.
101
+
102
+ ACTION REQUIRED: Add precompile_assets option to ey.yml.
103
+ precompile_assets: false # disable assets to avoid this error.
104
+ WARN
105
+ else
106
+ shell.error <<-ERROR
107
+ Asset precompilation detected but compilation failed!
108
+
109
+ ACTION REQUIRED: Add precompile_assets option to ey.yml.
110
+ precompile_assets: true # precompile assets when asset changes detected
111
+ precompile_assets: false # disable asset compilation.
112
+ ERROR
113
+ raise
114
+ end
115
+ end
116
+
117
+ def app_disables_assets?
118
+ application_rb_path.open do |fd|
119
+ fd.grep(/^[^#]*config\.assets\.enabled\s*=\s*(false|nil)/).any?
120
+ end
121
+ end
122
+
123
+ # This check is very expensive, and has been deemed not worth the time.
124
+ # Leaving this here in case someone comes up with a faster way.
125
+ #
126
+ # Runs 'rake -T' to see if there is an assets:precompile task.
127
+ def app_has_asset_task?
128
+ # We just run this locally on the app master; everybody else should
129
+ # have the same code anyway.
130
+ task_check = "PATH=#{paths.binstubs}:$PATH #{framework_envs} rake -T #{precompile_assets_task} | grep '#{precompile_assets_task}'"
131
+ cmd = "cd #{paths.active_release} && #{task_check}"
132
+ shell.logged_system(cmd).success?
133
+ end
134
+
135
+ def application_rb_path
136
+ paths.active_release.join('config','application.rb')
137
+ end
138
+
139
+ def app_assets
140
+ File.join('app','assets')
141
+ end
142
+
143
+ def app_assets_path
144
+ paths.active_release.join(app_assets)
145
+ end
146
+
147
+ def asset_strategy
148
+ @asset_strategy ||= RailsAssets::Strategy.fetch(config.asset_strategy, paths, runner)
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,197 @@
1
+ require 'yaml'
2
+
3
+ module EY
4
+ module Serverside
5
+ class RailsAssets
6
+ module Strategy
7
+ def self.all
8
+ {
9
+ 'shared' => Shared,
10
+ 'cleaning' => Cleaning,
11
+ 'private' => Private,
12
+ 'shifting' => Shifting,
13
+ }
14
+ end
15
+
16
+ def self.fetch(name, *args)
17
+ (all[name.to_s] || Shifting).new(*args)
18
+ end
19
+
20
+
21
+ # Precompile assets fresh every time. Shared assets are not symlinked
22
+ # and assets stay with the release that compiled them. The assets of
23
+ # the previous deploy are symlinked as into the current deploy to
24
+ # prevent errors during deploy.
25
+ #
26
+ # When no assets changes are detected, the deploy uses rsync to copy
27
+ # the previous release's assets into the current assets directory.
28
+ class Private
29
+ attr_reader :paths, :runner
30
+
31
+ def initialize(paths, runner)
32
+ @paths = paths
33
+ @runner = runner
34
+ end
35
+
36
+ def reuse
37
+ run("mkdir -p #{paths.public_assets} && rsync -aq #{previous_assets_path}/ #{paths.public_assets}")
38
+ end
39
+
40
+ # ?ink the previous assets into the new public/last_assets/assets
41
+ # to prevent missing assets during deploy.
42
+ #
43
+ # This results in the directory structure:
44
+ # deploy_root/current/public/last_assets/assets -> deploy_root/releases/<prev>/public/assets
45
+ def prepare
46
+ last = paths.public.join('last_assets')
47
+ run "mkdir -p #{last} && ln -nfs #{previous_assets_path} #{last.join('assets')}"
48
+ yield
49
+ end
50
+
51
+ protected
52
+
53
+ def run(cmd)
54
+ runner.run(cmd)
55
+ end
56
+
57
+ def previous_assets_path
58
+ paths.previous_release(paths.active_release).join('public','assets')
59
+ end
60
+ end
61
+
62
+ # Basic shared assets.
63
+ # Precompiled assets go into a single shared assets directory. The
64
+ # assets directory is never cleaned, so a deploy hook should be used
65
+ # to clean assets appropriately.
66
+ #
67
+ # When no assets changes are detected, shared directory is only
68
+ # symlinked and precompile task is not run.
69
+ class Shared
70
+ attr_reader :paths, :runner
71
+ def initialize(paths, runner)
72
+ @paths = paths
73
+ @runner = runner
74
+ end
75
+
76
+ def reuse
77
+ run "mkdir -p #{shared_assets_path} && ln -nfs #{shared_assets_path} #{paths.public}"
78
+ end
79
+
80
+ def prepare
81
+ reuse
82
+ yield
83
+ end
84
+
85
+ protected
86
+
87
+ def run(cmd)
88
+ runner.run(cmd)
89
+ end
90
+
91
+ def shared_assets_path
92
+ paths.shared_assets
93
+ end
94
+ end
95
+
96
+ # Precompiled assets are shared across all deploys like Shared.
97
+ # Before compiling the active deploying assets, all assets that are not
98
+ # referenced by the manifest.yml from the previous deploy are removed.
99
+ # After cleaning, the new assets are compiled over the top. The result
100
+ # is an assets dir that contains the last assets and the current assets.
101
+ #
102
+ # When no assets changes are detected, shared directory is only
103
+ # symlinked and cleaning and precompile tasks are not run.
104
+ class Cleaning < Shared
105
+ def prepare
106
+ reuse
107
+ remove_old_assets
108
+ yield
109
+ rescue
110
+ # how do you restore back to the old assets if some have been overwritten?
111
+ # probably just deploy again I suppose.
112
+ raise
113
+ end
114
+
115
+ protected
116
+
117
+ def remove_old_assets
118
+ return unless manifest_path.readable?
119
+
120
+ Dir.chdir(shared_assets_path)
121
+
122
+ all_assets_on_disk = Dir.glob(shared_assets_path.join('**','*.*').to_s) - [manifest_path.to_s]
123
+ $stderr.puts "all_assets_on_disk #{all_assets_on_disk.inspect}"
124
+ assets_on_disk = all_assets_on_disk.reject {|a| a =~ /\.gz$/}
125
+ $stderr.puts "assets_on_disk #{assets_on_disk.inspect}"
126
+ assets_in_manifest = YAML.load_file(manifest_path.to_s).values
127
+ $stderr.puts "assets_in_manifest #{assets_in_manifest.inspect}"
128
+
129
+ remove_assets = []
130
+ (assets_on_disk - assets_in_manifest).each do |asset|
131
+ remove_assets << "'#{asset}'"
132
+ remove_assets << "'#{asset}.gz'" if all_assets_on_disk.include?("#{asset}.gz")
133
+ end
134
+ run("rm -rf #{remove_assets.join(' ')}")
135
+ end
136
+
137
+ def manifest_path
138
+ shared_assets_path.join('manifest.yml')
139
+ end
140
+ end
141
+
142
+ # The default behavior and the one used since the beginning of asset
143
+ # support in engineyard-serverside. Assets are compiled into a fresh
144
+ # shared directory. Previous shared assets are shifted to a last_assets
145
+ # directory to prevent errors during deploy.
146
+ #
147
+ # When no assets changes are detected, the two shared directories are
148
+ # symlinked into the active release without any changes.
149
+ class Shifting < Shared
150
+ # link shared/assets and shared/last_assets into public
151
+ def reuse
152
+ run "mkdir -p #{shared_assets_path} #{last_assets_path} && #{link_assets}"
153
+ end
154
+
155
+ def prepare
156
+ shift_existing_assets
157
+ yield
158
+ rescue
159
+ unshift_existing_assets
160
+ raise
161
+ end
162
+
163
+ protected
164
+
165
+ def last_assets_path
166
+ paths.shared.join('last_assets')
167
+ end
168
+
169
+ # If there are current shared assets, move them under a 'last_assets' directory.
170
+ #
171
+ # To support operations like Unicorn's hot reload, it is useful to have
172
+ # the prior release's assets as well. Otherwise, while a deploy is running,
173
+ # clients may request stale assets that you just deleted.
174
+ # Making use of this requires a properly-configured front-end HTTP server.
175
+ #
176
+ # Note: This results in the directory structure:
177
+ # deploy_root/current/public/assets -> deploy_root/shared/assets
178
+ # deploy_root/current/public/last_assets -> deploy_root/shared/last_assets
179
+ # where last_assets has an assets dir under it.
180
+ # deploy_root/shared/last_assets/assets
181
+ def shift_existing_assets
182
+ run "rm -rf #{last_assets_path} && mkdir -p #{shared_assets_path} #{last_assets_path} && mv #{shared_assets_path} #{last_assets_path.join('assets')} && mkdir -p #{shared_assets_path} && #{link_assets}"
183
+ end
184
+
185
+ # Restore shared/last_assets to shared/assets and relink them to the app public
186
+ def unshift_existing_assets
187
+ run "rm -rf #{shared_assets_path} && mv #{last_assets_path.join('assets')} #{shared_assets_path} && mkdir -p #{last_assets_path} && #{link_assets}"
188
+ end
189
+
190
+ def link_assets
191
+ "ln -nfs #{shared_assets_path} #{last_assets_path} #{paths.public}"
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
@@ -12,6 +12,11 @@ module EY
12
12
  "#{user}@#{hostname}"
13
13
  end
14
14
 
15
+ def inspect
16
+ name_s = name && ":#{name}"
17
+ "#{hostname}(#{role}#{name_s})"
18
+ end
19
+
15
20
  def role
16
21
  roles.first
17
22
  end
@@ -64,17 +64,17 @@ module EY
64
64
 
65
65
  # Run a command on this set of servers.
66
66
  def run(shell, cmd, &block)
67
- run_on_each do |server|
67
+ run_on_each(shell) do |server|
68
68
  exec_cmd = server.command_on_server('sh -l -c', cmd, &block)
69
- shell.logged_system(exec_cmd)
69
+ shell.logged_system(exec_cmd, server)
70
70
  end
71
71
  end
72
72
 
73
73
  # Run a sudo command on this set of servers.
74
74
  def sudo(shell, cmd, &block)
75
- run_on_each do |server|
75
+ run_on_each(shell) do |server|
76
76
  exec_cmd = server.command_on_server('sudo sh -l -c', cmd, &block)
77
- shell.logged_system(exec_cmd)
77
+ shell.logged_system(exec_cmd, server)
78
78
  end
79
79
  end
80
80
 
@@ -93,12 +93,24 @@ module EY
93
93
  # Makes a theard for each server and executes the block,
94
94
  # Assumes that the return value of the block is a CommandResult
95
95
  # and ensures that all the command results were successful.
96
- def run_on_each(&block)
96
+ def run_on_each(shell, &block)
97
97
  results = map_in_parallel(&block)
98
98
  failures = results.reject {|result| result.success? }
99
+
99
100
  if failures.any?
100
- message = failures.map { |f| f.inspect }.join("\n")
101
- raise EY::Serverside::RemoteFailure.new(failures)
101
+ commands = failures.map { |f| f.command }.uniq
102
+ servers = failures.map { |f| f.server }.compact.map { |s| s.inspect }
103
+ outputs = failures.map { |f| f.output }.uniq
104
+ message = "The following command#{commands.size == 1 ? '' : 's'} failed"
105
+ if servers.any?
106
+ message << " on server#{servers.size == 1 ? '' : 's'} [#{servers.join(', ')}]"
107
+ end
108
+ message << "\n\n"
109
+ commands.each do |cmd|
110
+ message << "$ #{cmd}\n"
111
+ end
112
+ message << "\n" << outputs.join("\n\n") << "\n"
113
+ raise EY::Serverside::RemoteFailure.new(message)
102
114
  end
103
115
  end
104
116