engineyard-serverside 2.0.7 → 2.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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