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.
- data/.gitignore +2 -0
- data/CHANGES.md +3 -0
- data/Gemfile +13 -0
- data/LICENSE +177 -0
- data/NOTICE +2 -0
- data/README.md +16 -0
- data/aws-codedeploy-agent.gemspec +39 -0
- data/bin/codedeploy-agent +78 -0
- data/bin/codedeploy-install +15 -0
- data/bin/codedeploy-uninstall +13 -0
- data/certs/host-agent-deployment-signer-ca-chain.pem +76 -0
- data/conf/codedeployagent.yml +9 -0
- data/init.d/codedeploy-agent +61 -0
- data/lib/core_ext.rb +71 -0
- data/lib/instance_agent.rb +35 -0
- data/lib/instance_agent/agent/base.rb +34 -0
- data/lib/instance_agent/codedeploy_plugin/application_specification/ace_info.rb +133 -0
- data/lib/instance_agent/codedeploy_plugin/application_specification/acl_info.rb +163 -0
- data/lib/instance_agent/codedeploy_plugin/application_specification/application_specification.rb +142 -0
- data/lib/instance_agent/codedeploy_plugin/application_specification/context_info.rb +23 -0
- data/lib/instance_agent/codedeploy_plugin/application_specification/file_info.rb +23 -0
- data/lib/instance_agent/codedeploy_plugin/application_specification/linux_permission_info.rb +121 -0
- data/lib/instance_agent/codedeploy_plugin/application_specification/mode_info.rb +66 -0
- data/lib/instance_agent/codedeploy_plugin/application_specification/range_info.rb +134 -0
- data/lib/instance_agent/codedeploy_plugin/application_specification/script_info.rb +27 -0
- data/lib/instance_agent/codedeploy_plugin/codedeploy_control.rb +72 -0
- data/lib/instance_agent/codedeploy_plugin/command_executor.rb +357 -0
- data/lib/instance_agent/codedeploy_plugin/command_poller.rb +146 -0
- data/lib/instance_agent/codedeploy_plugin/deployment_specification.rb +150 -0
- data/lib/instance_agent/codedeploy_plugin/hook_executor.rb +206 -0
- data/lib/instance_agent/codedeploy_plugin/install_instruction.rb +374 -0
- data/lib/instance_agent/codedeploy_plugin/installer.rb +143 -0
- data/lib/instance_agent/codedeploy_plugin/request_helper.rb +28 -0
- data/lib/instance_agent/config.rb +43 -0
- data/lib/instance_agent/log.rb +3 -0
- data/lib/instance_agent/platform.rb +17 -0
- data/lib/instance_agent/platform/linux_util.rb +57 -0
- data/lib/instance_agent/runner/child.rb +57 -0
- data/lib/instance_agent/runner/master.rb +103 -0
- data/lib/instance_metadata.rb +47 -0
- data/test/certificate_helper.rb +120 -0
- data/test/helpers/instance_agent_helper.rb +25 -0
- data/test/instance_agent/agent/base_test.rb +49 -0
- data/test/instance_agent/codedeploy_plugin/application_specification_test.rb +1710 -0
- data/test/instance_agent/codedeploy_plugin/codedeploy_control_test.rb +51 -0
- data/test/instance_agent/codedeploy_plugin/command_executor_test.rb +513 -0
- data/test/instance_agent/codedeploy_plugin/command_poller_test.rb +459 -0
- data/test/instance_agent/codedeploy_plugin/deployment_specification_test.rb +335 -0
- data/test/instance_agent/codedeploy_plugin/hook_executor_test.rb +250 -0
- data/test/instance_agent/codedeploy_plugin/install_instruction_test.rb +566 -0
- data/test/instance_agent/codedeploy_plugin/installer_test.rb +519 -0
- data/test/instance_agent/codedeploy_plugin/request_helper_test.rb +37 -0
- data/test/instance_agent/config_test.rb +64 -0
- data/test/instance_agent/runner/child_test.rb +87 -0
- data/test/instance_metadata_test.rb +97 -0
- data/test/test_helper.rb +16 -0
- data/vendor/gems/.codedeploy-commands-1.0.0.created.rid +1 -0
- data/vendor/gems/codedeploy-commands/apis/CodeDeployCommand.api.json +372 -0
- data/vendor/gems/codedeploy-commands/codedeploy-commands-1.0.0.gemspec +28 -0
- data/vendor/gems/codedeploy-commands/lib/aws/codedeploy_commands.rb +18 -0
- data/vendor/gems/codedeploy-commands/lib/aws/plugins/certificate_authority.rb +12 -0
- data/vendor/gems/codedeploy-commands/lib/aws/plugins/deploy_control_endpoint.rb +22 -0
- data/vendor/gems/process_manager/README.md +1 -0
- data/vendor/gems/process_manager/lib/blank.rb +153 -0
- data/vendor/gems/process_manager/lib/core_ext.rb +73 -0
- data/vendor/gems/process_manager/lib/process_manager.rb +49 -0
- data/vendor/gems/process_manager/lib/process_manager/child.rb +119 -0
- data/vendor/gems/process_manager/lib/process_manager/config.rb +112 -0
- data/vendor/gems/process_manager/lib/process_manager/log.rb +107 -0
- data/vendor/gems/process_manager/lib/process_manager/master.rb +322 -0
- data/vendor/gems/process_manager/process_manager-0.0.13.gemspec +42 -0
- data/vendor/specifications/aws-sdk-core-2.0.5.gemspec +39 -0
- data/vendor/specifications/builder-3.2.2.gemspec +29 -0
- data/vendor/specifications/codedeploy-commands-1.0.0.gemspec +28 -0
- data/vendor/specifications/gli-2.5.6.gemspec +51 -0
- data/vendor/specifications/jamespath-0.5.1.gemspec +35 -0
- data/vendor/specifications/little-plugger-1.1.3.gemspec +32 -0
- data/vendor/specifications/logging-1.8.1.gemspec +44 -0
- data/vendor/specifications/multi_json-1.7.7.gemspec +30 -0
- data/vendor/specifications/multi_json-1.8.4.gemspec +30 -0
- data/vendor/specifications/multi_xml-0.5.5.gemspec +30 -0
- data/vendor/specifications/process_manager-0.0.13.gemspec +42 -0
- data/vendor/specifications/simple_pid-0.2.1.gemspec +28 -0
- 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
|