aws-codedeploy-agent 0.0.1

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 (84) hide show
  1. data/.gitignore +2 -0
  2. data/CHANGES.md +3 -0
  3. data/Gemfile +13 -0
  4. data/LICENSE +177 -0
  5. data/NOTICE +2 -0
  6. data/README.md +16 -0
  7. data/aws-codedeploy-agent.gemspec +39 -0
  8. data/bin/codedeploy-agent +78 -0
  9. data/bin/codedeploy-install +15 -0
  10. data/bin/codedeploy-uninstall +13 -0
  11. data/certs/host-agent-deployment-signer-ca-chain.pem +76 -0
  12. data/conf/codedeployagent.yml +9 -0
  13. data/init.d/codedeploy-agent +61 -0
  14. data/lib/core_ext.rb +71 -0
  15. data/lib/instance_agent.rb +35 -0
  16. data/lib/instance_agent/agent/base.rb +34 -0
  17. data/lib/instance_agent/codedeploy_plugin/application_specification/ace_info.rb +133 -0
  18. data/lib/instance_agent/codedeploy_plugin/application_specification/acl_info.rb +163 -0
  19. data/lib/instance_agent/codedeploy_plugin/application_specification/application_specification.rb +142 -0
  20. data/lib/instance_agent/codedeploy_plugin/application_specification/context_info.rb +23 -0
  21. data/lib/instance_agent/codedeploy_plugin/application_specification/file_info.rb +23 -0
  22. data/lib/instance_agent/codedeploy_plugin/application_specification/linux_permission_info.rb +121 -0
  23. data/lib/instance_agent/codedeploy_plugin/application_specification/mode_info.rb +66 -0
  24. data/lib/instance_agent/codedeploy_plugin/application_specification/range_info.rb +134 -0
  25. data/lib/instance_agent/codedeploy_plugin/application_specification/script_info.rb +27 -0
  26. data/lib/instance_agent/codedeploy_plugin/codedeploy_control.rb +72 -0
  27. data/lib/instance_agent/codedeploy_plugin/command_executor.rb +357 -0
  28. data/lib/instance_agent/codedeploy_plugin/command_poller.rb +146 -0
  29. data/lib/instance_agent/codedeploy_plugin/deployment_specification.rb +150 -0
  30. data/lib/instance_agent/codedeploy_plugin/hook_executor.rb +206 -0
  31. data/lib/instance_agent/codedeploy_plugin/install_instruction.rb +374 -0
  32. data/lib/instance_agent/codedeploy_plugin/installer.rb +143 -0
  33. data/lib/instance_agent/codedeploy_plugin/request_helper.rb +28 -0
  34. data/lib/instance_agent/config.rb +43 -0
  35. data/lib/instance_agent/log.rb +3 -0
  36. data/lib/instance_agent/platform.rb +17 -0
  37. data/lib/instance_agent/platform/linux_util.rb +57 -0
  38. data/lib/instance_agent/runner/child.rb +57 -0
  39. data/lib/instance_agent/runner/master.rb +103 -0
  40. data/lib/instance_metadata.rb +47 -0
  41. data/test/certificate_helper.rb +120 -0
  42. data/test/helpers/instance_agent_helper.rb +25 -0
  43. data/test/instance_agent/agent/base_test.rb +49 -0
  44. data/test/instance_agent/codedeploy_plugin/application_specification_test.rb +1710 -0
  45. data/test/instance_agent/codedeploy_plugin/codedeploy_control_test.rb +51 -0
  46. data/test/instance_agent/codedeploy_plugin/command_executor_test.rb +513 -0
  47. data/test/instance_agent/codedeploy_plugin/command_poller_test.rb +459 -0
  48. data/test/instance_agent/codedeploy_plugin/deployment_specification_test.rb +335 -0
  49. data/test/instance_agent/codedeploy_plugin/hook_executor_test.rb +250 -0
  50. data/test/instance_agent/codedeploy_plugin/install_instruction_test.rb +566 -0
  51. data/test/instance_agent/codedeploy_plugin/installer_test.rb +519 -0
  52. data/test/instance_agent/codedeploy_plugin/request_helper_test.rb +37 -0
  53. data/test/instance_agent/config_test.rb +64 -0
  54. data/test/instance_agent/runner/child_test.rb +87 -0
  55. data/test/instance_metadata_test.rb +97 -0
  56. data/test/test_helper.rb +16 -0
  57. data/vendor/gems/.codedeploy-commands-1.0.0.created.rid +1 -0
  58. data/vendor/gems/codedeploy-commands/apis/CodeDeployCommand.api.json +372 -0
  59. data/vendor/gems/codedeploy-commands/codedeploy-commands-1.0.0.gemspec +28 -0
  60. data/vendor/gems/codedeploy-commands/lib/aws/codedeploy_commands.rb +18 -0
  61. data/vendor/gems/codedeploy-commands/lib/aws/plugins/certificate_authority.rb +12 -0
  62. data/vendor/gems/codedeploy-commands/lib/aws/plugins/deploy_control_endpoint.rb +22 -0
  63. data/vendor/gems/process_manager/README.md +1 -0
  64. data/vendor/gems/process_manager/lib/blank.rb +153 -0
  65. data/vendor/gems/process_manager/lib/core_ext.rb +73 -0
  66. data/vendor/gems/process_manager/lib/process_manager.rb +49 -0
  67. data/vendor/gems/process_manager/lib/process_manager/child.rb +119 -0
  68. data/vendor/gems/process_manager/lib/process_manager/config.rb +112 -0
  69. data/vendor/gems/process_manager/lib/process_manager/log.rb +107 -0
  70. data/vendor/gems/process_manager/lib/process_manager/master.rb +322 -0
  71. data/vendor/gems/process_manager/process_manager-0.0.13.gemspec +42 -0
  72. data/vendor/specifications/aws-sdk-core-2.0.5.gemspec +39 -0
  73. data/vendor/specifications/builder-3.2.2.gemspec +29 -0
  74. data/vendor/specifications/codedeploy-commands-1.0.0.gemspec +28 -0
  75. data/vendor/specifications/gli-2.5.6.gemspec +51 -0
  76. data/vendor/specifications/jamespath-0.5.1.gemspec +35 -0
  77. data/vendor/specifications/little-plugger-1.1.3.gemspec +32 -0
  78. data/vendor/specifications/logging-1.8.1.gemspec +44 -0
  79. data/vendor/specifications/multi_json-1.7.7.gemspec +30 -0
  80. data/vendor/specifications/multi_json-1.8.4.gemspec +30 -0
  81. data/vendor/specifications/multi_xml-0.5.5.gemspec +30 -0
  82. data/vendor/specifications/process_manager-0.0.13.gemspec +42 -0
  83. data/vendor/specifications/simple_pid-0.2.1.gemspec +28 -0
  84. metadata +377 -0
@@ -0,0 +1,27 @@
1
+ module InstanceAgent
2
+ module CodeDeployPlugin
3
+ module ApplicationSpecification
4
+
5
+ #Helper Class for storing data parsed from hook script maps
6
+ class ScriptInfo
7
+
8
+ attr_reader :location, :runas, :timeout
9
+
10
+ def initialize(location, opts = {})
11
+ location = location.to_s
12
+ if(location.empty?)
13
+ raise AppSpecValidationException, 'Scripts need a location value'
14
+ end
15
+ @location = location
16
+ @runas = opts[:runas]
17
+ @timeout = opts[:timeout] || 3600
18
+ @timeout = @timeout.to_i
19
+ if(@timeout <= 0)
20
+ raise AppSpecValidationException, 'Timeout needs to be an integer greater than 0'
21
+ end
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,72 @@
1
+ require 'aws/codedeploy_commands'
2
+ require 'httpclient'
3
+ require 'instance_metadata'
4
+
5
+ module InstanceAgent
6
+ module CodeDeployPlugin
7
+ class CodeDeployControl
8
+
9
+ def initialize(options = {})
10
+ @options = options.update({
11
+ :http_read_timeout => InstanceAgent::Config.config[:http_read_timeout]
12
+ })
13
+
14
+ if InstanceAgent::Config.config[:log_aws_wire]
15
+ @options = options.update({
16
+ # wire logs might be huge; customers should be careful about turning them on
17
+ # allow 1GB of old wire logs in 64MB chunks
18
+ :logger => Logger.new(
19
+ File.join(InstanceAgent::Config.config[:log_dir], "#{InstanceAgent::Config.config[:program_name]}.aws_wire.log"),
20
+ 16,
21
+ 64 * 1024 * 1024),
22
+ :http_wire_trace => true})
23
+ end
24
+ end
25
+
26
+ def get_client
27
+ Aws::CodeDeployCommand::Client.new(@options)
28
+ end
29
+
30
+ def ssl_verify_peer
31
+ get_client.config.ssl_verify_peer
32
+ end
33
+
34
+ def verify_cert_fields
35
+ deploy_control_endpoint = get_client.config.endpoint
36
+ begin
37
+ cert_verifier = InstanceAgent::CodeDeployPlugin::CodeDeployControlCertVerifier.new(deploy_control_endpoint)
38
+ cert_verifier.verify_subject
39
+ rescue e
40
+ InstanceAgent::Log.error("#{self.class.to_s}: Error during certificate verification on codedeploy endpoint #{deploy_control_endpoint}")
41
+ InstanceAgent::Log.debug("#{self.class.to_s}: #{e.inspect}")
42
+ false
43
+ end
44
+ end
45
+ end
46
+
47
+ class CodeDeployControlCertVerifier
48
+
49
+ def initialize(endpoint)
50
+ client = HTTPClient.new
51
+ response = client.get(endpoint)
52
+ @cert = response.peer_cert
53
+ @region = ENV['AWS_REGION'] || InstanceMetadata.region
54
+ end
55
+
56
+ def verify_subject
57
+ InstanceAgent::Log.debug("#{self.class.to_s}: Actual certificate subject is '#{@cert.subject.to_s}'")
58
+
59
+ case @region
60
+ when 'us-east-1'
61
+ @cert.subject.to_s == "/C=US/ST=Washington/L=Seattle/O=Amazon.com, Inc./CN=codedeploy-commands.us-east-1.amazonaws.com"
62
+ when 'us-west-2'
63
+ @cert.subject.to_s == "/C=US/ST=Washington/L=Seattle/O=Amazon.com, Inc./CN=codedeploy-commands.us-west-2.amazonaws.com"
64
+ else
65
+ InstanceAgent::Log.debug("#{self.class.to_s}: Unsupported region '#{@region}'")
66
+ false
67
+ end
68
+ end
69
+
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,357 @@
1
+ require 'openssl'
2
+ require 'fileutils'
3
+ require 'instance_agent/codedeploy_plugin/installer'
4
+ require 'instance_agent/codedeploy_plugin/hook_executor'
5
+ require 'aws-sdk-core'
6
+ require 'zlib'
7
+ require 'zip'
8
+ require 'instance_metadata'
9
+ require 'open-uri'
10
+ require 'uri'
11
+
12
+ module InstanceAgent
13
+ module CodeDeployPlugin
14
+ ARCHIVES_TO_RETAIN = 5
15
+ class CommandExecutor
16
+ class << self
17
+ attr_reader :command_methods
18
+ end
19
+
20
+ attr_reader :deployment_system
21
+
22
+ InvalidCommandNameFailure = Class.new(Exception)
23
+
24
+ def initialize(options = {})
25
+ @deployment_system = "CodeDeploy"
26
+ @deploy_control_client = options[:deploy_control_client]
27
+ @hook_mapping = options[:hook_mapping]
28
+ if(!@hook_mapping.nil?)
29
+ map
30
+ end
31
+ end
32
+
33
+ def self.command(name, &blk)
34
+ @command_methods ||= Hash.new
35
+
36
+ method = Seahorse::Util.underscore(name).to_sym
37
+ @command_methods[name] = method
38
+
39
+ define_method(method, &blk)
40
+ end
41
+
42
+ def execute_command(command, deployment_specification)
43
+ method_name = command_method(command.command_name)
44
+ log(:debug, "Command #{command.command_name} maps to method #{method_name}")
45
+
46
+ deployment_specification = DeploymentSpecification.parse(deployment_specification)
47
+ log(:debug, "Successfully parsed the deployment spec")
48
+
49
+ log(:debug, "Creating deployment root directory #{deployment_root_dir(deployment_specification)}")
50
+ FileUtils.mkdir_p(deployment_root_dir(deployment_specification))
51
+ raise "Error creating deployment root directory #{deployment_root_dir(deployment_specification)}" if !File.directory?(deployment_root_dir(deployment_specification))
52
+
53
+ send(method_name, command, deployment_specification)
54
+ end
55
+
56
+ def command_method(command_name)
57
+ raise InvalidCommandNameFailure.new("Unsupported command type: #{command_name}.") unless self.class.command_methods.has_key?(command_name)
58
+ self.class.command_methods[command_name]
59
+ end
60
+
61
+ command "DownloadBundle" do |cmd, deployment_spec|
62
+ cleanup_old_archives(deployment_spec.deployment_group_id)
63
+ log(:debug, "Executing DownloadBundle command for execution #{cmd.deployment_execution_id}")
64
+
65
+ case deployment_spec.revision_source
66
+ when 'S3'
67
+ download_from_s3(
68
+ deployment_spec,
69
+ deployment_spec.bucket,
70
+ deployment_spec.key,
71
+ deployment_spec.version,
72
+ deployment_spec.etag)
73
+ when 'GitHub'
74
+ download_from_github(
75
+ deployment_spec,
76
+ deployment_spec.external_account,
77
+ deployment_spec.repository,
78
+ deployment_spec.commit_id,
79
+ deployment_spec.anonymous,
80
+ deployment_spec.external_auth_token)
81
+ else
82
+ # This should never happen since this is checked during creation of the deployment_spec object.
83
+ raise "Unknown revision type '#{deployment_spec.revision_source}'"
84
+ end
85
+
86
+ FileUtils.rm_rf(File.join(deployment_root_dir(deployment_spec), 'deployment-archive'))
87
+ bundle_file = artifact_bundle(deployment_spec)
88
+
89
+ unpack_bundle(cmd, bundle_file, deployment_spec)
90
+
91
+ nil
92
+ end
93
+
94
+ command "Install" do |cmd, deployment_spec|
95
+ log(:debug, "Executing Install command for execution #{cmd.deployment_execution_id}")
96
+
97
+ FileUtils.mkdir_p(deployment_instructions_dir)
98
+ log(:debug, "Instructions directory created at #{deployment_instructions_dir}")
99
+
100
+ installer = Installer.new(:deployment_instructions_dir => deployment_instructions_dir,
101
+ :deployment_archive_dir => archive_root_dir(deployment_spec))
102
+
103
+ log(:debug, "Installing revision #{deployment_spec.revision} in "+
104
+ "instance group #{deployment_spec.deployment_group_id}")
105
+ installer.install(deployment_spec.deployment_group_id, default_app_spec(deployment_spec))
106
+ update_last_successful_install(deployment_spec)
107
+ nil
108
+ end
109
+
110
+ def map
111
+ @hook_mapping.each_pair do |command, lifecycle_events|
112
+ InstanceAgent::CodeDeployPlugin::CommandExecutor.command command do |cmd, deployment_spec|
113
+ #run the scripts
114
+ script_log = ScriptLog.new
115
+ lifecycle_events.each do |lifecycle_event|
116
+ hook_command = HookExecutor.new(:lifecycle_event => lifecycle_event,
117
+ :deployment_root_dir => deployment_root_dir(deployment_spec),
118
+ :last_successful_deployment_dir => last_successful_deployment_dir(deployment_spec.deployment_group_id),
119
+ :app_spec_path => app_spec_path)
120
+ script_log.concat_log(hook_command.execute)
121
+ end
122
+ script_log.log
123
+ end
124
+ end
125
+ end
126
+
127
+ private
128
+ def deployment_root_dir(deployment_spec)
129
+ File.join(ProcessManager::Config.config[:root_dir], deployment_spec.deployment_group_id, deployment_spec.deployment_id)
130
+ end
131
+
132
+ private
133
+ def deployment_instructions_dir()
134
+ File.join(ProcessManager::Config.config[:root_dir], 'deployment-instructions')
135
+ end
136
+
137
+ private
138
+ def archive_root_dir(deployment_spec)
139
+ File.join(deployment_root_dir(deployment_spec), 'deployment-archive')
140
+ end
141
+
142
+ private
143
+ def last_successful_deployment_dir(deployment_group)
144
+ last_install_file_location = last_install_file_path(deployment_group)
145
+ return unless File.exist? last_install_file_location
146
+ File.open last_install_file_location do |f|
147
+ return f.read.chomp
148
+ end
149
+ end
150
+
151
+ private
152
+ def default_app_spec(deployment_spec)
153
+ default_app_spec_location = File.join(archive_root_dir(deployment_spec), app_spec_path)
154
+ log(:debug, "Checking for app spec in #{default_app_spec_location}")
155
+ app_spec = ApplicationSpecification::ApplicationSpecification.parse(File.read(default_app_spec_location))
156
+ end
157
+
158
+ private
159
+ def last_install_file_path(deployment_group)
160
+ File.join(deployment_instructions_dir, "#{deployment_group}_last_successful_install")
161
+ end
162
+
163
+ private
164
+ def download_from_s3(deployment_spec, bucket, key, version, etag)
165
+ log(:debug, "Downloading artifact bundle from bucket '#{bucket}' and key '#{key}', version '#{version}', etag '#{etag}'")
166
+ region = ENV['AWS_REGION'] || InstanceMetadata.region
167
+
168
+ if InstanceAgent::Config.config[:log_aws_wire]
169
+ s3 = Aws::S3::Client.new(
170
+ :region => region,
171
+ :ssl_ca_directory => ENV['AWS_SSL_CA_DIRECTORY'],
172
+ # wire logs might be huge; customers should be careful about turning them on
173
+ # allow 1GB of old wire logs in 64MB chunks
174
+ :logger => Logger.new(
175
+ File.join(InstanceAgent::Config.config[:log_dir], "#{InstanceAgent::Config.config[:program_name]}.aws_wire.log"),
176
+ 16,
177
+ 64 * 1024 * 1024),
178
+ :http_wire_trace => true)
179
+ else
180
+ s3 = Aws::S3::Client.new(
181
+ :region => region,
182
+ :ssl_ca_directory => ENV['AWS_SSL_CA_DIRECTORY'])
183
+ end
184
+
185
+ File.open(artifact_bundle(deployment_spec), 'wb') do |file|
186
+
187
+ if !version.nil?
188
+ object = s3.get_object({:bucket => bucket, :key => key, :version_id => version}, :target => file)
189
+ else
190
+ object = s3.get_object({:bucket => bucket, :key => key}, :target => file)
191
+ end
192
+
193
+ if(!etag.nil? && !(etag.gsub(/"/,'').eql? object.etag.gsub(/"/,'')))
194
+ msg = "Expected deployment artifact bundle etag #{etag} but was actually #{object.etag}"
195
+ log(:error, msg)
196
+ raise RuntimeError, msg
197
+ end
198
+ end
199
+ log(:debug, "Download complete from bucket #{bucket} and key #{key}")
200
+ end
201
+
202
+ private
203
+ def download_from_github(deployment_spec, account, repo, commit, anonymous, token)
204
+
205
+ retries = 0
206
+ errors = []
207
+
208
+ if InstanceAgent::Platform.util.supported_oses == 'windows'
209
+ deployment_spec.bundle_type = 'zip'
210
+ format = 'zipball'
211
+ else
212
+ deployment_spec.bundle_type = 'tar'
213
+ format = 'tarball'
214
+ end
215
+
216
+ uri = URI.parse("https://api.github.com/repos/#{account}/#{repo}/#{format}/#{commit}")
217
+ options = {:ssl_verify_mode => OpenSSL::SSL::VERIFY_PEER, :redirect => true, :ssl_ca_cert => ENV['AWS_SSL_CA_DIRECTORY']}
218
+
219
+ if anonymous
220
+ log(:debug, "Anonymous GitHub repository download requested.")
221
+ else
222
+ log(:debug, "Authenticated GitHub repository download requested.")
223
+ options.update({'Authorization' => "token #{token}"})
224
+ end
225
+
226
+ begin
227
+ # stream bundle file to disk
228
+ log(:info, "Requesting URL: '#{uri.to_s}'")
229
+ File.open(artifact_bundle(deployment_spec), 'w+b') do |file|
230
+ uri.open(options) do |github|
231
+ log(:debug, "GitHub response: '#{github.meta.to_s}'")
232
+
233
+ while (buffer = github.read(8 * 1024 * 1024))
234
+ file.write buffer
235
+ end
236
+ end
237
+ end
238
+ rescue OpenURI::HTTPError => e
239
+ log(:error, "Could not download bundle at '#{uri.to_s}'. Server returned code #{e.io.status[0]} '#{e.io.status[1]}'")
240
+ log(:debug, "Server returned error response body #{e.io.string}")
241
+ errors << "#{e.io.status[0]} '#{e.io.status[1]}'"
242
+
243
+ if retries < 3
244
+ time_to_sleep = (10 * (3 ** retries)) # 10 sec, 30 sec, 90 sec
245
+ log(:debug, "Retrying download in #{time_to_sleep} seconds.")
246
+ sleep(time_to_sleep)
247
+ retries += 1
248
+ retry
249
+ else
250
+ raise "Could not download bundle at '#{uri.to_s}' after #{retries} retries. Server returned codes: #{errors.join("; ")}."
251
+ end
252
+ end
253
+ end
254
+
255
+ private
256
+ def unpack_bundle(cmd, bundle_file, deployment_spec)
257
+ strip_leading_directory = deployment_spec.revision_source == 'GitHub'
258
+
259
+ if strip_leading_directory
260
+ # Extract to a temporary directory first so we can move the files around
261
+ dst = File.join(deployment_root_dir(deployment_spec), 'deployment-archive-temp')
262
+ actual_dst = File.join(deployment_root_dir(deployment_spec), 'deployment-archive')
263
+ FileUtils.rm_rf(dst)
264
+ else
265
+ dst = File.join(deployment_root_dir(deployment_spec), 'deployment-archive')
266
+ end
267
+
268
+ if "tar".eql? deployment_spec.bundle_type
269
+ InstanceAgent::Platform.util.extract_tar(bundle_file, dst)
270
+ elsif "tgz".eql? deployment_spec.bundle_type
271
+ InstanceAgent::Platform.util.extract_tgz(bundle_file, dst)
272
+ elsif "zip".eql? deployment_spec.bundle_type
273
+ Zip::File.open(bundle_file) do |zipfile|
274
+ zipfile.each do |f|
275
+ file_dst = File.join(dst, f.name)
276
+ FileUtils.mkdir_p(File.dirname(file_dst))
277
+ zipfile.extract(f, file_dst)
278
+ end
279
+ end
280
+ else
281
+ # If the bundle was a generated through a Sabini Repository
282
+ # it will be in tar format, and it won't have a bundle type
283
+ InstanceAgent::Platform.util.extract_tar(bundle_file, dst)
284
+ end
285
+
286
+ if strip_leading_directory
287
+ log(:info, "Stripping leading directory from archive bundle contents.")
288
+
289
+ # Find leading directory to remove
290
+ archive_root_files = Dir.entries(dst)
291
+ archive_root_files.delete_if { |name| name == '.' || name == '..' }
292
+
293
+ if (archive_root_files.size != 1)
294
+ log(:warn, "Expected archive to have a single root directory containing the actual bundle root, but it had #{archive_root_files.size} entries instead. Skipping leading directory removal and using archive as is.")
295
+ FileUtils.mv(dst, actual_dst)
296
+ return
297
+ end
298
+
299
+ nested_archive_root = File.join(dst, archive_root_files[0])
300
+ log(:debug, "Actual archive root at #{nested_archive_root}. Moving to #{actual_dst}")
301
+
302
+ FileUtils.mv(nested_archive_root, actual_dst)
303
+ FileUtils.rmdir(dst)
304
+
305
+ log(:debug, Dir.entries(actual_dst).join("; "))
306
+ end
307
+ end
308
+
309
+ private
310
+ def update_last_successful_install(deployment_spec)
311
+ File.open(last_install_file_path(deployment_spec.deployment_group_id), 'w+') do |f|
312
+ f.write deployment_root_dir(deployment_spec)
313
+ end
314
+ end
315
+
316
+ private
317
+ def cleanup_old_archives(deployment_group)
318
+ deployment_archives = Dir[File.join(ProcessManager::Config.config[:root_dir], deployment_group, '*')]
319
+ extra = deployment_archives.size - ARCHIVES_TO_RETAIN
320
+ return unless extra > 0
321
+
322
+ # Never remove the last successful deployment
323
+ last_success = last_successful_deployment_dir(deployment_group)
324
+ deployment_archives.delete(last_success)
325
+
326
+ # Sort oldest -> newest, take first `extra` elements
327
+ oldest_extra = deployment_archives.sort_by{ |f| File.mtime(f) }.take(extra)
328
+
329
+ # Absolute path takes care of relative root directories
330
+ directories = oldest_extra.map{ |f| File.absolute_path(f) }
331
+ FileUtils.rm_rf(directories)
332
+
333
+ end
334
+
335
+ private
336
+ def artifact_bundle(deployment_spec)
337
+ File.join(deployment_root_dir(deployment_spec), 'bundle.tar')
338
+ end
339
+
340
+ private
341
+ def app_spec_path
342
+ 'appspec.yml'
343
+ end
344
+
345
+ private
346
+ def description
347
+ self.class.to_s
348
+ end
349
+
350
+ private
351
+ def log(severity, message)
352
+ raise ArgumentError, "Unknown severity #{severity.inspect}" unless InstanceAgent::Log::SEVERITIES.include?(severity.to_s)
353
+ InstanceAgent::Log.send(severity.to_sym, "#{description}: #{message}")
354
+ end
355
+ end
356
+ end
357
+ end