aws-codedeploy-agent 0.0.2 → 0.0.3

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