aws-codedeploy-agent 0.0.1

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