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,178 @@
|
|
1
|
+
require 'instance_metadata'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
module InstanceAgent
|
5
|
+
module Plugins
|
6
|
+
module CodeDeployPlugin
|
7
|
+
class CommandPoller < InstanceAgent::Agent::Base
|
8
|
+
|
9
|
+
VERSION = "2013-04-23"
|
10
|
+
def initialize
|
11
|
+
CodeDeployPlugin::OnPremisesConfig.configure
|
12
|
+
region = ENV['AWS_REGION'] || InstanceMetadata.region
|
13
|
+
@host_identifier = ENV['AWS_HOST_IDENTIFIER'] || InstanceMetadata.host_identifier
|
14
|
+
|
15
|
+
log(:debug, "Configuring deploy control client: Region = #{region.inspect}")
|
16
|
+
log(:debug, "Deploy control endpoint override = " + ENV['AWS_DEPLOY_CONTROL_ENDPOINT'].inspect)
|
17
|
+
|
18
|
+
@deploy_control = InstanceAgent::Plugins::CodeDeployPlugin::CodeDeployControl.new(:region => region, :logger => InstanceAgent::Log, :ssl_ca_directory => ENV['AWS_SSL_CA_DIRECTORY'])
|
19
|
+
@deploy_control_client = @deploy_control.get_client
|
20
|
+
|
21
|
+
@plugin = InstanceAgent::Plugins::CodeDeployPlugin::CommandExecutor.new(:hook_mapping => create_hook_mapping)
|
22
|
+
|
23
|
+
log(:debug, "Initializing Host Agent: " +
|
24
|
+
"Host Identifier = #{@host_identifier}")
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_hook_mapping
|
28
|
+
#Map commands to lifecycle hooks
|
29
|
+
{ "BeforeELBRemove"=>["BeforeELBRemove"],
|
30
|
+
"AfterELBRemove"=>["AfterELBRemove"],
|
31
|
+
"ApplicationStop"=>["ApplicationStop"],
|
32
|
+
"BeforeInstall"=>["BeforeInstall"],
|
33
|
+
"AfterInstall"=>["AfterInstall"],
|
34
|
+
"ApplicationStart"=>["ApplicationStart"],
|
35
|
+
"BeforeELBAdd"=>["BeforeELBAdd"],
|
36
|
+
"AfterELBAdd"=>["AfterELBAdd"],
|
37
|
+
"ValidateService"=>["ValidateService"]}
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate
|
41
|
+
test_profile = InstanceAgent::Config.config[:codedeploy_test_profile]
|
42
|
+
unless ["beta", "gamma"].include?(test_profile.downcase)
|
43
|
+
log(:debug, "Validating CodeDeploy Plugin Configuration")
|
44
|
+
Kernel.abort "Stopping CodeDeploy agent due to SSL validation error." unless @deploy_control.validate_ssl_config
|
45
|
+
log(:debug, "CodeDeploy Plugin Configuration is valid")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def perform
|
50
|
+
return unless command = next_command
|
51
|
+
return unless acknowledge_command(command)
|
52
|
+
|
53
|
+
begin
|
54
|
+
spec = get_deployment_specification(command)
|
55
|
+
#Successful commands will complete without raising an exception
|
56
|
+
script_output = process_command(command, spec)
|
57
|
+
log(:debug, 'Calling PutHostCommandComplete: "Succeeded"')
|
58
|
+
@deploy_control_client.put_host_command_complete(
|
59
|
+
:command_status => 'Succeeded',
|
60
|
+
:diagnostics => {:format => "JSON", :payload => gather_diagnostics()},
|
61
|
+
:host_command_identifier => command.host_command_identifier)
|
62
|
+
|
63
|
+
#Commands that throw an exception will be considered to have failed
|
64
|
+
rescue ScriptError => e
|
65
|
+
log(:debug, 'Calling PutHostCommandComplete: "Code Error" ')
|
66
|
+
@deploy_control_client.put_host_command_complete(
|
67
|
+
:command_status => "Failed",
|
68
|
+
:diagnostics => {:format => "JSON", :payload => gather_diagnostics_from_script_error(e)},
|
69
|
+
:host_command_identifier => command.host_command_identifier)
|
70
|
+
raise e
|
71
|
+
rescue Exception => e
|
72
|
+
log(:debug, 'Calling PutHostCommandComplete: "Code Error" ')
|
73
|
+
@deploy_control_client.put_host_command_complete(
|
74
|
+
:command_status => "Failed",
|
75
|
+
:diagnostics => {:format => "JSON", :payload => gather_diagnostics_from_error(e)},
|
76
|
+
:host_command_identifier => command.host_command_identifier)
|
77
|
+
raise e
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def next_command
|
82
|
+
log(:debug, "Calling PollHostCommand:")
|
83
|
+
output = @deploy_control_client.poll_host_command(:host_identifier => @host_identifier)
|
84
|
+
command = output.host_command
|
85
|
+
if command.nil?
|
86
|
+
log(:debug, "PollHostCommand: Host Command = nil")
|
87
|
+
else
|
88
|
+
log(:debug, "PollHostCommand: " +
|
89
|
+
"Host Identifier = #{command.host_identifier}; " +
|
90
|
+
"Host Command Identifier = #{command.host_command_identifier}; " +
|
91
|
+
"Deployment Execution ID = #{command.deployment_execution_id}; " +
|
92
|
+
"Command Name = #{command.command_name}")
|
93
|
+
raise "Host Identifier mismatch: #{@host_identifier} != #{command.host_identifier}" unless @host_identifier.include? command.host_identifier
|
94
|
+
raise "Command Name missing" if command.command_name.nil? || command.command_name.empty?
|
95
|
+
end
|
96
|
+
command
|
97
|
+
end
|
98
|
+
|
99
|
+
def acknowledge_command(command)
|
100
|
+
log(:debug, "Calling PutHostCommandAcknowledgement:")
|
101
|
+
output = @deploy_control_client.put_host_command_acknowledgement(
|
102
|
+
:diagnostics => nil,
|
103
|
+
:host_command_identifier => command.host_command_identifier)
|
104
|
+
status = output.command_status
|
105
|
+
log(:debug, "Command Status = #{status}")
|
106
|
+
|
107
|
+
if status == 'Succeeded' || status == 'Failed'
|
108
|
+
log(:debug, "Calling PutHostCommandComplete: \"#{status}\" ")
|
109
|
+
@deploy_control_client.put_host_command_complete(
|
110
|
+
:command_status => status,
|
111
|
+
:diagnostics => {:format => "JSON", :payload => gather_diagnostics_from_acknowledge(status)},
|
112
|
+
:host_command_identifier => command.host_command_identifier)
|
113
|
+
return false
|
114
|
+
end
|
115
|
+
|
116
|
+
return true
|
117
|
+
end
|
118
|
+
|
119
|
+
def get_deployment_specification(command)
|
120
|
+
log(:debug, "Calling GetDeploymentSpecification:")
|
121
|
+
output = @deploy_control_client.get_deployment_specification(
|
122
|
+
:deployment_execution_id => command.deployment_execution_id,
|
123
|
+
:host_identifier => @host_identifier)
|
124
|
+
log(:debug, "GetDeploymentSpecification: " +
|
125
|
+
"Deployment System = #{output.deployment_system}")
|
126
|
+
raise "Deployment System mismatch: #{@plugin.deployment_system} != #{output.deployment_system}" unless @plugin.deployment_system == output.deployment_system
|
127
|
+
raise "Deployment Specification missing" if output.deployment_specification.nil?
|
128
|
+
output.deployment_specification.generic_envelope
|
129
|
+
end
|
130
|
+
|
131
|
+
def process_command(command, spec)
|
132
|
+
log(:debug, "Calling #{@plugin.to_s}.execute_command")
|
133
|
+
@plugin.execute_command(command, spec)
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
def gather_diagnostics_from_script_error(script_error)
|
138
|
+
script_error.to_json
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
def gather_diagnostics_from_error(error)
|
143
|
+
begin
|
144
|
+
message = error.message || ""
|
145
|
+
raise ScriptError.new(ScriptError::UNKNOWN_ERROR_CODE, "", ScriptLog.new), message
|
146
|
+
rescue ScriptError => e
|
147
|
+
script_error = e
|
148
|
+
end
|
149
|
+
gather_diagnostics_from_script_error(script_error)
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
def gather_diagnostics()
|
154
|
+
begin
|
155
|
+
raise ScriptError.new(ScriptError::SUCCEEDED_CODE, "", ScriptLog.new), 'Succeeded'
|
156
|
+
rescue ScriptError => e
|
157
|
+
script_error = e
|
158
|
+
end
|
159
|
+
gather_diagnostics_from_script_error(script_error)
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
def gather_diagnostics_from_acknowledge(status)
|
164
|
+
begin
|
165
|
+
if status == 'Succeeded'
|
166
|
+
raise ScriptError.new(ScriptError::SUCCEEDED_CODE, "", ScriptLog.new), 'Succeeded'
|
167
|
+
else
|
168
|
+
raise ScriptError.new(ScriptError::UNKNOWN_ERROR_CODE, "", ScriptLog.new), 'Failed'
|
169
|
+
end
|
170
|
+
rescue ScriptError => e
|
171
|
+
script_error = e
|
172
|
+
end
|
173
|
+
gather_diagnostics_from_script_error(script_error)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'instance_metadata'
|
3
|
+
|
4
|
+
module InstanceAgent
|
5
|
+
module Plugins
|
6
|
+
module CodeDeployPlugin
|
7
|
+
class DeploymentSpecification
|
8
|
+
attr_accessor :deployment_id, :deployment_group_id, :deployment_group_name, :revision, :revision_source, :application_name
|
9
|
+
attr_accessor :bucket, :key, :bundle_type, :version, :etag
|
10
|
+
attr_accessor :external_account, :repository, :commit_id, :anonymous, :external_auth_token
|
11
|
+
class << self
|
12
|
+
attr_accessor :cert_store
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.init_cert_store(ca_chain_path)
|
16
|
+
@cert_store = OpenSSL::X509::Store.new
|
17
|
+
begin
|
18
|
+
@cert_store.add_file ca_chain_path
|
19
|
+
rescue OpenSSL::X509::StoreError => e
|
20
|
+
raise "Could not load certificate store '#{ca_chain_path}'.\nCaused by: #{e.inspect}"
|
21
|
+
end
|
22
|
+
return @cert_store
|
23
|
+
end
|
24
|
+
|
25
|
+
@cert_store = init_cert_store(File.expand_path('../../../../certs/host-agent-deployment-signer-ca-chain.pem', File.dirname(__FILE__)))
|
26
|
+
|
27
|
+
def initialize(data)
|
28
|
+
raise 'Deployment Spec has no DeploymentId' unless property_set?(data, "DeploymentId")
|
29
|
+
raise 'Deployment Spec has no DeploymentGroupId' unless property_set?(data, "DeploymentGroupId")
|
30
|
+
raise 'Deployment Spec has no DeploymentGroupName' unless property_set?(data, "DeploymentGroupName")
|
31
|
+
raise 'Deployment Spec has no ApplicationName' unless property_set?(data, "ApplicationName")
|
32
|
+
|
33
|
+
@application_name = data["ApplicationName"]
|
34
|
+
@deployment_group_name = data["DeploymentGroupName"]
|
35
|
+
|
36
|
+
if data["DeploymentId"].start_with?("arn:")
|
37
|
+
@deployment_id = getDeploymentIdFromArn(data["DeploymentId"])
|
38
|
+
else
|
39
|
+
@deployment_id = data["DeploymentId"]
|
40
|
+
end
|
41
|
+
@deployment_group_id = data["DeploymentGroupId"]
|
42
|
+
|
43
|
+
raise 'Must specify a revison' unless data["Revision"]
|
44
|
+
@revision_source = data["Revision"]["RevisionType"]
|
45
|
+
raise 'Must specify a revision source' unless @revision_source
|
46
|
+
|
47
|
+
case @revision_source
|
48
|
+
when 'S3'
|
49
|
+
@revision = data["Revision"]["S3Revision"]
|
50
|
+
raise 'S3Revision in Deployment Spec must specify Bucket, Key and BundleType' unless valid_s3_revision?(@revision)
|
51
|
+
raise 'BundleType in S3Revision must be tar, tgz or zip' unless valid_bundle_type?(@revision)
|
52
|
+
|
53
|
+
@bucket = @revision["Bucket"]
|
54
|
+
@key = @revision["Key"]
|
55
|
+
@bundle_type = @revision["BundleType"]
|
56
|
+
@version = @revision["Version"]
|
57
|
+
@etag = @revision["ETag"]
|
58
|
+
when 'GitHub'
|
59
|
+
@revision = data["Revision"]["GitHubRevision"]
|
60
|
+
raise 'GitHubRevision in Deployment Spec must specify Account, Repository and CommitId' unless valid_github_revision?(revision)
|
61
|
+
@external_account = revision["Account"]
|
62
|
+
@repository = revision["Repository"]
|
63
|
+
@commit_id = revision["CommitId"]
|
64
|
+
@external_auth_token = data["GitHubAccessToken"]
|
65
|
+
@anonymous = @external_auth_token.nil?
|
66
|
+
else
|
67
|
+
raise 'Exactly one of S3Revision or GitHubRevision must be specified'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.parse(envelope)
|
72
|
+
raise 'Provided deployment spec was nil' if envelope.nil?
|
73
|
+
|
74
|
+
case envelope.format
|
75
|
+
when "PKCS7/JSON"
|
76
|
+
pkcs7 = OpenSSL::PKCS7.new(envelope.payload)
|
77
|
+
|
78
|
+
# The PKCS7_NOCHAIN flag tells OpenSSL to ignore any PKCS7 CA chain that might be attached
|
79
|
+
# to the message directly and use the certificates from provided one only for validating the.
|
80
|
+
# signer's certificate.
|
81
|
+
#
|
82
|
+
# However, it will allow use the PKCS7 signer certificate provided to validate the signature.
|
83
|
+
#
|
84
|
+
# http://www.openssl.org/docs/crypto/PKCS7_verify.html#VERIFY_PROCESS
|
85
|
+
#
|
86
|
+
# The ruby wrapper returns true if OpenSSL returns 1
|
87
|
+
raise "Validation of PKCS7 signed message failed" unless pkcs7.verify([], @cert_store, nil, OpenSSL::PKCS7::NOCHAIN)
|
88
|
+
|
89
|
+
signer_certs = pkcs7.certificates
|
90
|
+
raise "Validation of PKCS7 signed message failed" unless signer_certs.size == 1
|
91
|
+
raise "Validation of PKCS7 signed message failed" unless verify_pkcs7_signer_cert(signer_certs[0])
|
92
|
+
|
93
|
+
deployment_spec = JSON.parse(pkcs7.data)
|
94
|
+
|
95
|
+
sanitized_spec = deployment_spec.clone
|
96
|
+
sanitized_spec["GitHubAccessToken"] &&= "REDACTED"
|
97
|
+
InstanceAgent::Log.debug("#{self.to_s}: Parse: #{sanitized_spec}")
|
98
|
+
|
99
|
+
return new(deployment_spec)
|
100
|
+
else
|
101
|
+
raise "Unsupported DeploymentSpecification format: #{envelope.format}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
def property_set?(propertyHash, property)
|
107
|
+
propertyHash.has_key?(property) && !propertyHash[property].nil? && !propertyHash[property].empty?
|
108
|
+
end
|
109
|
+
|
110
|
+
def valid_s3_revision?(revision)
|
111
|
+
revision.nil? || %w(Bucket Key BundleType).all? { |k| revision.has_key?(k) }
|
112
|
+
end
|
113
|
+
|
114
|
+
def valid_github_revision?(revision)
|
115
|
+
required_fields = %w(Account Repository CommitId)
|
116
|
+
if !(revision.nil? || revision['Anonymous'].nil? || revision['Anonymous'])
|
117
|
+
required_fields << 'OAuthToken'
|
118
|
+
end
|
119
|
+
revision.nil? || required_fields.all? { |k| revision.has_key?(k) }
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
def valid_bundle_type?(revision)
|
124
|
+
revision.nil? || %w(tar zip tgz).any? { |k| revision["BundleType"] == k }
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.verify_pkcs7_signer_cert(cert)
|
128
|
+
@@region ||= ENV['AWS_REGION'] || InstanceMetadata.region
|
129
|
+
|
130
|
+
# Do some minimal cert pinning
|
131
|
+
case InstanceAgent::Config.config()[:codedeploy_test_profile]
|
132
|
+
when 'beta', 'gamma'
|
133
|
+
cert.subject.to_s == "/C=US/ST=Washington/L=Seattle/O=Amazon.com, Inc./CN=codedeploy-signer-integ.amazonaws.com"
|
134
|
+
when 'prod'
|
135
|
+
case @@region
|
136
|
+
when 'us-east-1'
|
137
|
+
cert.subject.to_s == "/C=US/ST=Washington/L=Seattle/O=Amazon.com, Inc./CN=codedeploy-signer-us-east-1.amazonaws.com"
|
138
|
+
when 'us-west-2'
|
139
|
+
cert.subject.to_s == "/C=US/ST=Washington/L=Seattle/O=Amazon.com, Inc./CN=codedeploy-signer-us-west-2.amazonaws.com"
|
140
|
+
when 'eu-west-1'
|
141
|
+
cert.subject.to_s == "/C=US/ST=Washington/L=Seattle/O=Amazon.com, Inc./CN=codedeploy-signer-eu-west-1.amazonaws.com"
|
142
|
+
when 'ap-southeast-2'
|
143
|
+
cert.subject.to_s == "/C=US/ST=Washington/L=Seattle/O=Amazon.com, Inc./CN=codedeploy-signer-ap-southeast-2.amazonaws.com"
|
144
|
+
else
|
145
|
+
raise "Unknown region '#{@region}'"
|
146
|
+
end
|
147
|
+
else
|
148
|
+
raise "Unknown profile '#{Config.config()[:codedeploy_test_profile]}'"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
def getDeploymentIdFromArn(arn)
|
154
|
+
# example arn format: "arn:aws:codedeploy:us-east-1:123412341234:deployment/12341234-1234-1234-1234-123412341234"
|
155
|
+
arn.split(":", 6)[5].split("/",2)[1]
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'open3'
|
3
|
+
require 'json'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
module InstanceAgent
|
7
|
+
module Plugins
|
8
|
+
module CodeDeployPlugin
|
9
|
+
class ScriptLog
|
10
|
+
attr_reader :log
|
11
|
+
def append_to_log(log_entry)
|
12
|
+
log_entry ||= ""
|
13
|
+
@log ||= []
|
14
|
+
@log.push(log_entry)
|
15
|
+
|
16
|
+
index = @log.size
|
17
|
+
remaining_buffer = 2048
|
18
|
+
|
19
|
+
while (index > 0 && (remaining_buffer - @log[index-1].length) > 0)
|
20
|
+
index = index - 1
|
21
|
+
remaining_buffer = remaining_buffer - @log[index-1].length
|
22
|
+
end
|
23
|
+
|
24
|
+
if index > 0
|
25
|
+
@log = @log.drop(index)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def concat_log(log_entries)
|
30
|
+
log_entries ||= []
|
31
|
+
log_entries.each do |log_entry|
|
32
|
+
append_to_log(log_entry)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class ScriptError < StandardError
|
38
|
+
attr_reader :error_code, :script_name, :log
|
39
|
+
|
40
|
+
SUCCEEDED_CODE = 0
|
41
|
+
SCRIPT_MISSING_CODE = 1
|
42
|
+
SCRIPT_EXECUTABILITY_CODE = 2
|
43
|
+
SCRIPT_TIMED_OUT_CODE = 3
|
44
|
+
SCRIPT_FAILED_CODE = 4
|
45
|
+
UNKNOWN_ERROR_CODE = 5
|
46
|
+
def initialize(error_code, script_name, log)
|
47
|
+
@error_code = error_code
|
48
|
+
@script_name = script_name
|
49
|
+
@log = log
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_json
|
53
|
+
log = @log.log || []
|
54
|
+
log = log.join("")
|
55
|
+
{'error_code' => @error_code, 'script_name' => @script_name, 'message' => message, 'log' => log}.to_json
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class HookExecutor
|
60
|
+
|
61
|
+
LAST_SUCCESSFUL_DEPLOYMENT = "OldOrIgnore"
|
62
|
+
CURRENT = "New"
|
63
|
+
def initialize(arguments = {})
|
64
|
+
#check arguments
|
65
|
+
raise "Lifecycle Event Required " if arguments[:lifecycle_event].nil?
|
66
|
+
raise "Deployment ID required " if arguments[:deployment_id].nil?
|
67
|
+
raise "Deployment Root Directory Required " if arguments[:deployment_root_dir].nil?
|
68
|
+
raise "App Spec Path Required " if arguments[:app_spec_path].nil?
|
69
|
+
raise "Application name required" if arguments[:application_name].nil?
|
70
|
+
raise "Deployment Group name required" if arguments[:deployment_group_name].nil?
|
71
|
+
@lifecycle_event = arguments[:lifecycle_event]
|
72
|
+
@deployment_id = arguments[:deployment_id]
|
73
|
+
@application_name = arguments[:application_name]
|
74
|
+
@deployment_group_name = arguments[:deployment_group_name]
|
75
|
+
select_correct_deployment_root_dir(arguments[:deployment_root_dir], arguments[:last_successful_deployment_dir])
|
76
|
+
return if @deployment_root_dir.nil?
|
77
|
+
@deployment_archive_dir = File.join(@deployment_root_dir, 'deployment-archive')
|
78
|
+
@app_spec_path = arguments[:app_spec_path]
|
79
|
+
parse_app_spec
|
80
|
+
@hook_logging_mutex = Mutex.new
|
81
|
+
@script_log = ScriptLog.new
|
82
|
+
@child_envs={'LIFECYCLE_EVENT' => @lifecycle_event.to_s,
|
83
|
+
'DEPLOYMENT_ID' => @deployment_id.to_s,
|
84
|
+
'APPLICATION_NAME' => @application_name,
|
85
|
+
'DEPLOYMENT_GROUP_NAME' => @deployment_group_name}
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
def execute
|
90
|
+
return if @app_spec.nil?
|
91
|
+
if (hooks = @app_spec.hooks[@lifecycle_event]) &&
|
92
|
+
!hooks.empty?
|
93
|
+
create_script_log_file_if_needed do |script_log_file|
|
94
|
+
log_script("LifecycleEvent - " + @lifecycle_event + "\n", script_log_file)
|
95
|
+
hooks.each do |script|
|
96
|
+
if(!File.exist?(script_absolute_path(script)))
|
97
|
+
raise ScriptError.new(ScriptError::SCRIPT_MISSING_CODE, script.location, @script_log), 'Script does not exist at specified location: ' + script.location
|
98
|
+
elsif(!InstanceAgent::Platform.util.script_executable?(script_absolute_path(script)))
|
99
|
+
log :warn, 'Script at specified location: ' + script.location + ' is not executable. Trying to make it executable.'
|
100
|
+
begin
|
101
|
+
FileUtils.chmod("+x", script_absolute_path(script))
|
102
|
+
rescue
|
103
|
+
raise ScriptError.new(ScriptError::SCRIPT_EXECUTABILITY_CODE, script.location, @script_log), 'Unable to set script at specified location: ' + script.location + ' as executable'
|
104
|
+
end
|
105
|
+
end
|
106
|
+
begin
|
107
|
+
execute_script(script, script_log_file)
|
108
|
+
rescue Timeout::Error
|
109
|
+
raise ScriptError.new(ScriptError::SCRIPT_TIMED_OUT_CODE, script.location, @script_log), 'Script at specified location: ' +script.location + ' failed to complete in '+script.timeout.to_s+' seconds'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
@script_log.log
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
def execute_script(script, script_log_file)
|
119
|
+
script_command = InstanceAgent::Platform.util.prepare_script_command(script, script_absolute_path(script))
|
120
|
+
log_script("Script - " + script.location + "\n", script_log_file)
|
121
|
+
exit_status = 1
|
122
|
+
signal = nil
|
123
|
+
|
124
|
+
if !InstanceAgent::Platform.util.supports_process_groups?
|
125
|
+
# The Windows port doesn't emulate process groups so don't try to use them here
|
126
|
+
open3_options = {}
|
127
|
+
signal = 'KILL' #It is up to the script to handle killing child processes it spawns.
|
128
|
+
else
|
129
|
+
open3_options = {:pgroup => true}
|
130
|
+
signal = '-TERM' #kill the process group instead of pid
|
131
|
+
end
|
132
|
+
|
133
|
+
Open3.popen3(@child_envs, script_command, open3_options) do |stdin, stdout, stderr, wait_thr|
|
134
|
+
stdin.close
|
135
|
+
stdout_thread = Thread.new{stdout.each_line { |line| log_script("[stdout]" + line.to_s, script_log_file)}}
|
136
|
+
stderr_thread = Thread.new{stderr.each_line { |line| log_script("[stderr]" + line.to_s, script_log_file)}}
|
137
|
+
if !wait_thr.join(script.timeout)
|
138
|
+
Process.kill(signal, wait_thr.pid)
|
139
|
+
raise Timeout::Error
|
140
|
+
end
|
141
|
+
stdout_thread.join
|
142
|
+
stderr_thread.join
|
143
|
+
exit_status = wait_thr.value.exitstatus
|
144
|
+
end
|
145
|
+
if(exit_status != 0)
|
146
|
+
script_error = 'Script at specified location: ' + script.location + ' failed with exit code ' + exit_status.to_s
|
147
|
+
if(!script.runas.nil?)
|
148
|
+
script_error = 'Script at specified location: ' + script.location + ' run as user ' + script.runas + ' failed with exit code ' + exit_status.to_s
|
149
|
+
end
|
150
|
+
raise ScriptError.new(ScriptError::SCRIPT_FAILED_CODE, script.location, @script_log), script_error
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
def create_script_log_file_if_needed
|
156
|
+
script_log_file_location = File.join(@deployment_root_dir, 'logs/scripts.log')
|
157
|
+
if(!File.exists?(script_log_file_location))
|
158
|
+
unless File.directory?(File.dirname(script_log_file_location))
|
159
|
+
FileUtils.mkdir_p(File.dirname(script_log_file_location))
|
160
|
+
end
|
161
|
+
script_log_file = File.open(script_log_file_location, 'w')
|
162
|
+
else
|
163
|
+
script_log_file = File.open(script_log_file_location, 'a')
|
164
|
+
end
|
165
|
+
yield(script_log_file)
|
166
|
+
ensure
|
167
|
+
script_log_file.close unless script_log_file.nil?
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
def script_absolute_path(script)
|
172
|
+
File.join(@deployment_archive_dir, script.location)
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
def parse_app_spec
|
177
|
+
app_spec_location = File.join(@deployment_archive_dir, @app_spec_path)
|
178
|
+
log(:debug, "Checking for app spec in #{app_spec_location}")
|
179
|
+
@app_spec = ApplicationSpecification::ApplicationSpecification.parse(File.read(app_spec_location))
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
def select_correct_deployment_root_dir(current_deployment_root_dir, last_successful_deployment_root_dir)
|
184
|
+
@deployment_root_dir = current_deployment_root_dir
|
185
|
+
hook_deployment_mapping = mapping_between_hooks_and_deployments
|
186
|
+
if(hook_deployment_mapping[@lifecycle_event] == LAST_SUCCESSFUL_DEPLOYMENT && !File.exist?(File.join(@deployment_root_dir, 'deployment-archive')))
|
187
|
+
@deployment_root_dir = last_successful_deployment_root_dir
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
def mapping_between_hooks_and_deployments
|
193
|
+
{"BeforeELBRemove"=>LAST_SUCCESSFUL_DEPLOYMENT,
|
194
|
+
"AfterELBRemove"=>LAST_SUCCESSFUL_DEPLOYMENT,
|
195
|
+
"ApplicationStop"=>LAST_SUCCESSFUL_DEPLOYMENT,
|
196
|
+
"BeforeInstall"=>CURRENT,
|
197
|
+
"AfterInstall"=>CURRENT,
|
198
|
+
"ApplicationStart"=>CURRENT,
|
199
|
+
"BeforeELBAdd"=>CURRENT,
|
200
|
+
"AfterELBAdd"=>CURRENT,
|
201
|
+
"ValidateService"=>CURRENT}
|
202
|
+
end
|
203
|
+
|
204
|
+
private
|
205
|
+
def description
|
206
|
+
self.class.to_s
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
def log(severity, message)
|
211
|
+
raise ArgumentError, "Unknown severity #{severity.inspect}" unless InstanceAgent::Log::SEVERITIES.include?(severity.to_s)
|
212
|
+
InstanceAgent::Log.send(severity.to_sym, "#{description}: #{message}")
|
213
|
+
end
|
214
|
+
|
215
|
+
private
|
216
|
+
def log_script(message, script_log_file)
|
217
|
+
@hook_logging_mutex.synchronize do
|
218
|
+
@script_log.append_to_log(message)
|
219
|
+
script_log_file.write(Time.now.to_s[0..-7] + ' ' + message)
|
220
|
+
script_log_file.flush
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|