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.
- data/aws-codedeploy-agent.gemspec +5 -5
- data/certs/host-agent-deployment-signer-ca-chain.pem +30 -0
- data/conf/codedeployagent.yml +0 -1
- data/lib/instance_agent.rb +1 -13
- data/lib/instance_agent/agent/base.rb +38 -12
- data/lib/instance_agent/agent/plugin.rb +21 -0
- data/lib/instance_agent/config.rb +2 -1
- data/lib/instance_agent/platform/linux_util.rb +4 -0
- data/lib/instance_agent/plugins/codedeploy/application_specification/ace_info.rb +133 -0
- data/lib/instance_agent/plugins/codedeploy/application_specification/acl_info.rb +163 -0
- data/lib/instance_agent/plugins/codedeploy/application_specification/application_specification.rb +143 -0
- data/lib/instance_agent/plugins/codedeploy/application_specification/context_info.rb +23 -0
- data/lib/instance_agent/plugins/codedeploy/application_specification/file_info.rb +23 -0
- data/lib/instance_agent/plugins/codedeploy/application_specification/linux_permission_info.rb +121 -0
- data/lib/instance_agent/plugins/codedeploy/application_specification/mode_info.rb +66 -0
- data/lib/instance_agent/plugins/codedeploy/application_specification/range_info.rb +134 -0
- data/lib/instance_agent/plugins/codedeploy/application_specification/script_info.rb +27 -0
- data/lib/instance_agent/plugins/codedeploy/codedeploy_control.rb +100 -0
- data/lib/instance_agent/plugins/codedeploy/command_executor.rb +359 -0
- data/lib/instance_agent/plugins/codedeploy/command_poller.rb +178 -0
- data/lib/instance_agent/plugins/codedeploy/deployment_specification.rb +161 -0
- data/lib/instance_agent/plugins/codedeploy/hook_executor.rb +226 -0
- data/lib/instance_agent/plugins/codedeploy/install_instruction.rb +389 -0
- data/lib/instance_agent/plugins/codedeploy/installer.rb +147 -0
- data/lib/instance_agent/plugins/codedeploy/onpremise_config.rb +42 -0
- data/lib/instance_agent/plugins/codedeploy/register_plugin.rb +17 -0
- data/lib/instance_agent/runner/child.rb +20 -5
- data/lib/instance_agent/runner/master.rb +2 -15
- data/lib/instance_metadata.rb +2 -2
- data/test/certificate_helper.rb +1 -1
- data/test/helpers/instance_agent_helper.rb +1 -0
- data/test/instance_agent/agent/base_test.rb +16 -3
- data/test/instance_agent/config_test.rb +2 -1
- data/test/instance_agent/plugins/codedeploy/application_specification_test.rb +1713 -0
- data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/codedeploy_control_test.rb +1 -1
- data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/command_executor_test.rb +32 -9
- data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/command_poller_test.rb +13 -14
- data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/deployment_specification_test.rb +98 -25
- data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/hook_executor_test.rb +83 -15
- data/test/instance_agent/plugins/codedeploy/install_instruction_test.rb +568 -0
- data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/installer_test.rb +12 -9
- data/test/instance_agent/plugins/codedeploy/onpremise_config_test.rb +72 -0
- data/test/instance_agent/runner/child_test.rb +1 -1
- data/vendor/gems/.codedeploy-commands-1.0.0.created.rid +1 -1
- data/vendor/gems/codedeploy-commands/lib/aws/plugins/deploy_control_endpoint.rb +4 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath.rb +41 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/caching_parser.rb +30 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/errors.rb +17 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/expr_node.rb +15 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/lexer.rb +116 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/parser.rb +347 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/runtime.rb +71 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/token.rb +41 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/token_stream.rb +60 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/tree_interpreter.rb +523 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/version.rb +3 -0
- data/vendor/gems/process_manager/lib/process_manager/master.rb +16 -5
- data/vendor/specifications/{aws-sdk-core-2.0.5.gemspec → aws-sdk-core-2.0.42.gemspec} +9 -11
- data/vendor/specifications/builder-3.2.2.gemspec +1 -1
- data/vendor/specifications/codedeploy-commands-1.0.0.gemspec +7 -6
- data/vendor/specifications/gli-2.5.6.gemspec +1 -1
- data/vendor/specifications/jmespath-1.0.1.gemspec +29 -0
- data/vendor/specifications/little-plugger-1.1.3.gemspec +1 -1
- data/vendor/specifications/logging-1.8.1.gemspec +1 -1
- data/vendor/specifications/multi_json-1.7.7.gemspec +1 -1
- data/vendor/specifications/multi_json-1.8.4.gemspec +1 -1
- data/vendor/specifications/multi_xml-0.5.5.gemspec +1 -1
- data/vendor/specifications/process_manager-0.0.13.gemspec +1 -1
- data/vendor/specifications/simple_pid-0.2.1.gemspec +1 -1
- metadata +76 -63
- data/lib/instance_agent/codedeploy_plugin/application_specification/ace_info.rb +0 -133
- data/lib/instance_agent/codedeploy_plugin/application_specification/acl_info.rb +0 -163
- data/lib/instance_agent/codedeploy_plugin/application_specification/application_specification.rb +0 -142
- data/lib/instance_agent/codedeploy_plugin/application_specification/context_info.rb +0 -23
- data/lib/instance_agent/codedeploy_plugin/application_specification/file_info.rb +0 -23
- data/lib/instance_agent/codedeploy_plugin/application_specification/linux_permission_info.rb +0 -121
- data/lib/instance_agent/codedeploy_plugin/application_specification/mode_info.rb +0 -66
- data/lib/instance_agent/codedeploy_plugin/application_specification/range_info.rb +0 -134
- data/lib/instance_agent/codedeploy_plugin/application_specification/script_info.rb +0 -27
- data/lib/instance_agent/codedeploy_plugin/codedeploy_control.rb +0 -72
- data/lib/instance_agent/codedeploy_plugin/command_executor.rb +0 -357
- data/lib/instance_agent/codedeploy_plugin/command_poller.rb +0 -170
- data/lib/instance_agent/codedeploy_plugin/deployment_specification.rb +0 -150
- data/lib/instance_agent/codedeploy_plugin/hook_executor.rb +0 -206
- data/lib/instance_agent/codedeploy_plugin/install_instruction.rb +0 -374
- data/lib/instance_agent/codedeploy_plugin/installer.rb +0 -143
- data/lib/instance_agent/codedeploy_plugin/request_helper.rb +0 -28
- data/test/instance_agent/codedeploy_plugin/application_specification_test.rb +0 -1710
- data/test/instance_agent/codedeploy_plugin/install_instruction_test.rb +0 -566
- data/test/instance_agent/codedeploy_plugin/request_helper_test.rb +0 -37
- 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
|