aws-codedeploy-agent 0.0.2 → 0.0.3

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 (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