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.
- 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
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
require 'openssl'
|
|
2
|
-
require 'instance_metadata'
|
|
3
|
-
|
|
4
|
-
module InstanceAgent
|
|
5
|
-
module CodeDeployPlugin
|
|
6
|
-
class DeploymentSpecification
|
|
7
|
-
attr_accessor :deployment_id, :deployment_group_id, :revision, :revision_source
|
|
8
|
-
attr_accessor :bucket, :key, :bundle_type, :version, :etag
|
|
9
|
-
attr_accessor :external_account, :repository, :commit_id, :anonymous, :external_auth_token
|
|
10
|
-
class << self
|
|
11
|
-
attr_accessor :cert_store
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def self.init_cert_store(ca_chain_path)
|
|
15
|
-
@cert_store = OpenSSL::X509::Store.new
|
|
16
|
-
begin
|
|
17
|
-
@cert_store.add_file ca_chain_path
|
|
18
|
-
rescue OpenSSL::X509::StoreError => e
|
|
19
|
-
raise "Could not load certificate store '#{ca_chain_path}'.\nCaused by: #{e.inspect}"
|
|
20
|
-
end
|
|
21
|
-
return @cert_store
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
@cert_store = init_cert_store(File.expand_path('../../../certs/host-agent-deployment-signer-ca-chain.pem', File.dirname(__FILE__)))
|
|
25
|
-
|
|
26
|
-
def initialize(data)
|
|
27
|
-
raise 'Deployment Spec has no DeploymentId' unless property_set?(data, "DeploymentId")
|
|
28
|
-
raise 'Deployment Spec has no DeploymentGroupId' unless property_set?(data, "DeploymentGroupId")
|
|
29
|
-
|
|
30
|
-
if data["DeploymentId"].start_with?("arn:")
|
|
31
|
-
@deployment_id = getDeploymentIdFromArn(data["DeploymentId"])
|
|
32
|
-
else
|
|
33
|
-
@deployment_id = data["DeploymentId"]
|
|
34
|
-
end
|
|
35
|
-
@deployment_group_id = data["DeploymentGroupId"]
|
|
36
|
-
|
|
37
|
-
raise 'Must specify a revison' unless data["Revision"]
|
|
38
|
-
@revision_source = data["Revision"]["RevisionType"]
|
|
39
|
-
raise 'Must specify a revision source' unless @revision_source
|
|
40
|
-
|
|
41
|
-
case @revision_source
|
|
42
|
-
when 'S3'
|
|
43
|
-
@revision = data["Revision"]["S3Revision"]
|
|
44
|
-
raise 'S3Revision in Deployment Spec must specify Bucket, Key and BundleType' unless valid_s3_revision?(@revision)
|
|
45
|
-
raise 'BundleType in S3Revision must be tar, tgz or zip' unless valid_bundle_type?(@revision)
|
|
46
|
-
|
|
47
|
-
@bucket = @revision["Bucket"]
|
|
48
|
-
@key = @revision["Key"]
|
|
49
|
-
@bundle_type = @revision["BundleType"]
|
|
50
|
-
@version = @revision["Version"]
|
|
51
|
-
@etag = @revision["ETag"]
|
|
52
|
-
when 'GitHub'
|
|
53
|
-
@revision = data["Revision"]["GitHubRevision"]
|
|
54
|
-
raise 'GitHubRevision in Deployment Spec must specify Account, Repository and CommitId' unless valid_github_revision?(revision)
|
|
55
|
-
@external_account = revision["Account"]
|
|
56
|
-
@repository = revision["Repository"]
|
|
57
|
-
@commit_id = revision["CommitId"]
|
|
58
|
-
@external_auth_token = data["GitHubAccessToken"]
|
|
59
|
-
@anonymous = @external_auth_token.nil?
|
|
60
|
-
else
|
|
61
|
-
raise 'Exactly one of S3Revision or GitHubRevision must be specified'
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def self.parse(envelope)
|
|
66
|
-
raise 'Provided deployment spec was nil' if envelope.nil?
|
|
67
|
-
|
|
68
|
-
case envelope.format
|
|
69
|
-
when "PKCS7/JSON"
|
|
70
|
-
pkcs7 = OpenSSL::PKCS7.new(envelope.payload)
|
|
71
|
-
|
|
72
|
-
# The PKCS7_NOCHAIN flag tells OpenSSL to ignore any PKCS7 CA chain that might be attached
|
|
73
|
-
# to the message directly and use the certificates from provided one only for validating the.
|
|
74
|
-
# signer's certificate.
|
|
75
|
-
#
|
|
76
|
-
# However, it will allow use the PKCS7 signer certificate provided to validate the signature.
|
|
77
|
-
#
|
|
78
|
-
# http://www.openssl.org/docs/crypto/PKCS7_verify.html#VERIFY_PROCESS
|
|
79
|
-
#
|
|
80
|
-
# The ruby wrapper returns true if OpenSSL returns 1
|
|
81
|
-
raise "Validation of PKCS7 signed message failed" unless pkcs7.verify([], @cert_store, nil, OpenSSL::PKCS7::NOCHAIN)
|
|
82
|
-
|
|
83
|
-
signer_certs = pkcs7.certificates
|
|
84
|
-
raise "Validation of PKCS7 signed message failed" unless signer_certs.size == 1
|
|
85
|
-
raise "Validation of PKCS7 signed message failed" unless verify_pkcs7_signer_cert(signer_certs[0])
|
|
86
|
-
|
|
87
|
-
deployment_spec = JSON.parse(pkcs7.data)
|
|
88
|
-
|
|
89
|
-
sanitized_spec = deployment_spec.clone
|
|
90
|
-
sanitized_spec["GitHubAccessToken"] &&= "REDACTED"
|
|
91
|
-
InstanceAgent::Log.debug("#{self.to_s}: Parse: #{sanitized_spec}")
|
|
92
|
-
|
|
93
|
-
return new(deployment_spec)
|
|
94
|
-
else
|
|
95
|
-
raise "Unsupported DeploymentSpecification format: #{envelope.format}"
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
private
|
|
100
|
-
|
|
101
|
-
def property_set?(propertyHash, property)
|
|
102
|
-
propertyHash.has_key?(property) && !propertyHash[property].nil? && !propertyHash[property].empty?
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def valid_s3_revision?(revision)
|
|
106
|
-
revision.nil? || %w(Bucket Key BundleType).all? { |k| revision.has_key?(k) }
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def valid_github_revision?(revision)
|
|
110
|
-
required_fields = %w(Account Repository CommitId)
|
|
111
|
-
if !(revision.nil? || revision['Anonymous'].nil? || revision['Anonymous'])
|
|
112
|
-
required_fields << 'OAuthToken'
|
|
113
|
-
end
|
|
114
|
-
revision.nil? || required_fields.all? { |k| revision.has_key?(k) }
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
private
|
|
118
|
-
|
|
119
|
-
def valid_bundle_type?(revision)
|
|
120
|
-
revision.nil? || %w(tar zip tgz).any? { |k| revision["BundleType"] == k }
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def self.verify_pkcs7_signer_cert(cert)
|
|
124
|
-
@@region ||= ENV['AWS_REGION'] || InstanceMetadata.region
|
|
125
|
-
|
|
126
|
-
case InstanceAgent::Config.config()[:codedeploy_test_profile]
|
|
127
|
-
when 'prod'
|
|
128
|
-
case @@region
|
|
129
|
-
when 'us-east-1'
|
|
130
|
-
cert.subject.to_s == "/C=US/ST=Washington/L=Seattle/O=Amazon.com, Inc./CN=codedeploy-signer-us-east-1.amazonaws.com"
|
|
131
|
-
when 'us-west-2'
|
|
132
|
-
cert.subject.to_s == "/C=US/ST=Washington/L=Seattle/O=Amazon.com, Inc./CN=codedeploy-signer-us-west-2.amazonaws.com"
|
|
133
|
-
else
|
|
134
|
-
raise "Unknown region '#{@region}'"
|
|
135
|
-
end
|
|
136
|
-
else
|
|
137
|
-
raise "Unknown profile '#{Config.config()[:codedeploy_test_profile]}'"
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
private
|
|
142
|
-
|
|
143
|
-
def getDeploymentIdFromArn(arn)
|
|
144
|
-
# example arn format: "arn:aws:codedeploy:us-east-1:123412341234:deployment/12341234-1234-1234-1234-123412341234"
|
|
145
|
-
arn.split(":", 6)[5].split("/",2)[1]
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
end
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
require 'timeout'
|
|
2
|
-
require 'open3'
|
|
3
|
-
require 'json'
|
|
4
|
-
require 'fileutils'
|
|
5
|
-
|
|
6
|
-
module InstanceAgent
|
|
7
|
-
module CodeDeployPlugin
|
|
8
|
-
class ScriptLog
|
|
9
|
-
attr_reader :log
|
|
10
|
-
|
|
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
|
-
|
|
47
|
-
def initialize(error_code, script_name, log)
|
|
48
|
-
@error_code = error_code
|
|
49
|
-
@script_name = script_name
|
|
50
|
-
@log = log
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def to_json
|
|
54
|
-
log = @log.log || []
|
|
55
|
-
log = log.join("")
|
|
56
|
-
{'error_code' => @error_code, 'script_name' => @script_name, 'message' => message, 'log' => log}.to_json
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
class HookExecutor
|
|
61
|
-
|
|
62
|
-
LAST_SUCCESSFUL_DEPLOYMENT = "OldOrIgnore"
|
|
63
|
-
CURRENT = "New"
|
|
64
|
-
|
|
65
|
-
def initialize(arguments = {})
|
|
66
|
-
#check arguments
|
|
67
|
-
raise "Lifecycle Event Required " if arguments[:lifecycle_event].nil?
|
|
68
|
-
raise "Deployment Root Directory Required " if arguments[:deployment_root_dir].nil?
|
|
69
|
-
raise "App Spec Path Required " if arguments[:app_spec_path].nil?
|
|
70
|
-
@lifecycle_event = arguments[:lifecycle_event]
|
|
71
|
-
select_correct_deployment_root_dir(arguments[:deployment_root_dir], arguments[:last_successful_deployment_dir])
|
|
72
|
-
return if @deployment_root_dir.nil?
|
|
73
|
-
@deployment_archive_dir = File.join(@deployment_root_dir, 'deployment-archive')
|
|
74
|
-
@app_spec_path = arguments[:app_spec_path]
|
|
75
|
-
parse_app_spec
|
|
76
|
-
@hook_logging_mutex = Mutex.new
|
|
77
|
-
@script_log = ScriptLog.new
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def execute
|
|
81
|
-
return if @app_spec.nil?
|
|
82
|
-
if (hooks = @app_spec.hooks[@lifecycle_event]) &&
|
|
83
|
-
!hooks.empty?
|
|
84
|
-
create_script_log_file_if_needed do |script_log_file|
|
|
85
|
-
log_script("LifecycleEvent - " + @lifecycle_event + "\n", script_log_file)
|
|
86
|
-
hooks.each do |script|
|
|
87
|
-
if(!File.exist?(script_absolute_path(script)))
|
|
88
|
-
raise ScriptError.new(ScriptError::SCRIPT_MISSING_CODE, script.location, @script_log), 'Script does not exist at specified location: ' + script.location
|
|
89
|
-
elsif(!InstanceAgent::Platform.util.script_executable?(script_absolute_path(script)))
|
|
90
|
-
log :warn, 'Script at specified location: ' + script.location + ' is not executable. Trying to make it executable.'
|
|
91
|
-
begin
|
|
92
|
-
FileUtils.chmod("+x", script_absolute_path(script))
|
|
93
|
-
rescue
|
|
94
|
-
raise ScriptError.new(ScriptError::SCRIPT_EXECUTABILITY_CODE, script.location, @script_log), 'Unable to set script at specified location: ' + script.location + ' as executable'
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
begin
|
|
98
|
-
execute_script(script, script_log_file)
|
|
99
|
-
rescue Timeout::Error
|
|
100
|
-
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'
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
@script_log.log
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
private
|
|
109
|
-
def execute_script(script, script_log_file)
|
|
110
|
-
script_command = InstanceAgent::Platform.util.prepare_script_command(script, script_absolute_path(script))
|
|
111
|
-
log_script("Script - " + script.location + "\n", script_log_file)
|
|
112
|
-
exit_status = 1
|
|
113
|
-
Open3.popen3(script_command, :pgroup => true) do |stdin, stdout, stderr, wait_thr|
|
|
114
|
-
stdin.close
|
|
115
|
-
stdout_thread = Thread.new{stdout.each_line { |line| log_script("[stdout]" + line.to_s, script_log_file)}}
|
|
116
|
-
stderr_thread = Thread.new{stderr.each_line { |line| log_script("[stderr]" + line.to_s, script_log_file)}}
|
|
117
|
-
if !wait_thr.join(script.timeout)
|
|
118
|
-
Process.kill('-TERM', wait_thr.pid) #kill the process group instead of pid
|
|
119
|
-
raise Timeout::Error
|
|
120
|
-
end
|
|
121
|
-
stdout_thread.join
|
|
122
|
-
stderr_thread.join
|
|
123
|
-
exit_status = wait_thr.value.exitstatus
|
|
124
|
-
end
|
|
125
|
-
if(exit_status != 0)
|
|
126
|
-
script_error = 'Script at specified location: ' + script.location + ' failed with exit code ' + exit_status.to_s
|
|
127
|
-
if(!script.runas.nil?)
|
|
128
|
-
script_error = 'Script at specified location: ' + script.location + ' run as user ' + script.runas + ' failed with exit code ' + exit_status.to_s
|
|
129
|
-
end
|
|
130
|
-
raise ScriptError.new(ScriptError::SCRIPT_FAILED_CODE, script.location, @script_log), script_error
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
private
|
|
136
|
-
def create_script_log_file_if_needed
|
|
137
|
-
script_log_file_location = File.join(@deployment_root_dir, 'logs/scripts.log')
|
|
138
|
-
if(!File.exists?(script_log_file_location))
|
|
139
|
-
unless File.directory?(File.dirname(script_log_file_location))
|
|
140
|
-
FileUtils.mkdir_p(File.dirname(script_log_file_location))
|
|
141
|
-
end
|
|
142
|
-
script_log_file = File.open(script_log_file_location, 'w')
|
|
143
|
-
else
|
|
144
|
-
script_log_file = File.open(script_log_file_location, 'a')
|
|
145
|
-
end
|
|
146
|
-
yield(script_log_file)
|
|
147
|
-
ensure
|
|
148
|
-
script_log_file.close unless script_log_file.nil?
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
private
|
|
152
|
-
def script_absolute_path(script)
|
|
153
|
-
File.join(@deployment_archive_dir, script.location)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
private
|
|
157
|
-
def parse_app_spec
|
|
158
|
-
app_spec_location = File.join(@deployment_archive_dir, @app_spec_path)
|
|
159
|
-
log(:debug, "Checking for app spec in #{app_spec_location}")
|
|
160
|
-
@app_spec = ApplicationSpecification::ApplicationSpecification.parse(File.read(app_spec_location))
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
private
|
|
164
|
-
def select_correct_deployment_root_dir(current_deployment_root_dir, last_successful_deployment_root_dir)
|
|
165
|
-
@deployment_root_dir = current_deployment_root_dir
|
|
166
|
-
hook_deployment_mapping = mapping_between_hooks_and_deployments
|
|
167
|
-
if(hook_deployment_mapping[@lifecycle_event] == LAST_SUCCESSFUL_DEPLOYMENT && !File.exist?(File.join(@deployment_root_dir, 'deployment-archive')))
|
|
168
|
-
@deployment_root_dir = last_successful_deployment_root_dir
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
private
|
|
173
|
-
def mapping_between_hooks_and_deployments
|
|
174
|
-
{"BeforeELBRemove"=>LAST_SUCCESSFUL_DEPLOYMENT,
|
|
175
|
-
"AfterELBRemove"=>LAST_SUCCESSFUL_DEPLOYMENT,
|
|
176
|
-
"ApplicationStop"=>LAST_SUCCESSFUL_DEPLOYMENT,
|
|
177
|
-
"BeforeInstall"=>CURRENT,
|
|
178
|
-
"AfterInstall"=>CURRENT,
|
|
179
|
-
"ApplicationStart"=>CURRENT,
|
|
180
|
-
"BeforeELBAdd"=>CURRENT,
|
|
181
|
-
"AfterELBAdd"=>CURRENT,
|
|
182
|
-
"ValidateService"=>CURRENT}
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
private
|
|
186
|
-
def description
|
|
187
|
-
self.class.to_s
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
private
|
|
191
|
-
def log(severity, message)
|
|
192
|
-
raise ArgumentError, "Unknown severity #{severity.inspect}" unless InstanceAgent::Log::SEVERITIES.include?(severity.to_s)
|
|
193
|
-
InstanceAgent::Log.send(severity.to_sym, "#{description}: #{message}")
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
private
|
|
197
|
-
def log_script(message, script_log_file)
|
|
198
|
-
@hook_logging_mutex.synchronize do
|
|
199
|
-
@script_log.append_to_log(message)
|
|
200
|
-
script_log_file.write(Time.now.to_s[0..-7] + ' ' + message)
|
|
201
|
-
script_log_file.flush
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
end
|
|
206
|
-
end
|
|
@@ -1,374 +0,0 @@
|
|
|
1
|
-
require 'etc'
|
|
2
|
-
require 'fileutils'
|
|
3
|
-
require 'json'
|
|
4
|
-
|
|
5
|
-
module InstanceAgent
|
|
6
|
-
module CodeDeployPlugin
|
|
7
|
-
class InstallInstruction
|
|
8
|
-
def self.generate_commands_from_file(file)
|
|
9
|
-
name = File.basename(file.path)
|
|
10
|
-
file = File.open(file.path, 'r')
|
|
11
|
-
contents = file.read
|
|
12
|
-
file.close
|
|
13
|
-
if name =~ /^*-install.json/
|
|
14
|
-
parse_install_commands(contents)
|
|
15
|
-
elsif name =~ /^*-cleanup/
|
|
16
|
-
parse_remove_commands(contents)
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def self.parse_install_commands(contents)
|
|
21
|
-
instructions = JSON.parse(contents)['instructions']
|
|
22
|
-
commands = []
|
|
23
|
-
instructions.each do |mapping|
|
|
24
|
-
case mapping['type']
|
|
25
|
-
when "copy"
|
|
26
|
-
commands << CopyCommand.new(mapping["source"], mapping["destination"])
|
|
27
|
-
when "mkdir"
|
|
28
|
-
commands << MakeDirectoryCommand.new(mapping["directory"])
|
|
29
|
-
when "chmod"
|
|
30
|
-
commands << ChangeModeCommand.new(mapping['file'], mapping['mode'])
|
|
31
|
-
when "chown"
|
|
32
|
-
commands << ChangeOwnerCommand.new(mapping['file'], mapping['owner'], mapping['group'])
|
|
33
|
-
when "setfacl"
|
|
34
|
-
commands << ChangeAclCommand.new(mapping['file'], InstanceAgent::CodeDeployPlugin::ApplicationSpecification::AclInfo.new(mapping['acl']))
|
|
35
|
-
when "semanage"
|
|
36
|
-
if !mapping['context']['role'].nil?
|
|
37
|
-
raise "Attempt to set role on object, not supported"
|
|
38
|
-
end
|
|
39
|
-
commands << ChangeContextCommand.new(mapping['file'], InstanceAgent::CodeDeployPlugin::ApplicationSpecification::ContextInfo.new(mapping['context']))
|
|
40
|
-
else
|
|
41
|
-
raise "Unknown command: #{mapping}"
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
commands
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def self.parse_remove_commands(contents)
|
|
48
|
-
return [] if contents.empty?
|
|
49
|
-
#remove the unfinished paths
|
|
50
|
-
lines = contents.lines.to_a
|
|
51
|
-
if lines.last[lines.last.length-1] != "\n"
|
|
52
|
-
lines.pop
|
|
53
|
-
end
|
|
54
|
-
commands = []
|
|
55
|
-
lines.each do |command|
|
|
56
|
-
if command.start_with?("semanage\0")
|
|
57
|
-
commands << RemoveContextCommand.new(command.split("\0",2)[1].strip)
|
|
58
|
-
else
|
|
59
|
-
commands << RemoveCommand.new(command.strip)
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
commands.reverse
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def self.generate_instructions()
|
|
66
|
-
command_builder = CommandBuilder.new()
|
|
67
|
-
yield(command_builder)
|
|
68
|
-
command_builder
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
class CommandBuilder
|
|
73
|
-
attr_reader :command_array
|
|
74
|
-
def initialize()
|
|
75
|
-
@command_array = []
|
|
76
|
-
@copy_targets = Hash.new
|
|
77
|
-
@mkdir_targets = Set.new
|
|
78
|
-
@permission_targets = Set.new
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def copy(source, destination)
|
|
82
|
-
destination = sanitize_dir_path(destination)
|
|
83
|
-
log(:debug, "Copying #{source} to #{destination}")
|
|
84
|
-
raise "Duplicate copy instruction to #{destination} from #{source} and #{@copy_targets[destination]}" if @copy_targets.has_key?(destination)
|
|
85
|
-
raise "Duplicate copy instruction to #{destination} from #{source} which is already being installed as a directory" if @mkdir_targets.include?(destination)
|
|
86
|
-
@command_array << CopyCommand.new(source, destination)
|
|
87
|
-
@copy_targets[destination] = source
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def mkdir(destination)
|
|
91
|
-
destination = sanitize_dir_path(destination)
|
|
92
|
-
log(:debug, "Making directory #{destination}")
|
|
93
|
-
raise "Duplicate mkdir instruction for #{destination} which is already being copied from #{@copy_targets[destination]}" if @copy_targets.has_key?(destination)
|
|
94
|
-
@command_array << MakeDirectoryCommand.new(destination) unless @mkdir_targets.include?(destination)
|
|
95
|
-
@mkdir_targets.add(destination)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def set_permissions(object, permission)
|
|
99
|
-
object = sanitize_dir_path(object)
|
|
100
|
-
log(:debug, "Setting permissions on #{object}")
|
|
101
|
-
raise "Duplicate permission setting instructions for #{object}" if @permission_targets.include?(object)
|
|
102
|
-
@permission_targets.add(object)
|
|
103
|
-
|
|
104
|
-
if !permission.mode.nil?
|
|
105
|
-
log(:debug, "Setting mode on #{object}")
|
|
106
|
-
@command_array << ChangeModeCommand.new(object, permission.mode.mode)
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
if !permission.acls.nil?
|
|
110
|
-
log(:debug, "Setting acl on #{object}")
|
|
111
|
-
@command_array << ChangeAclCommand.new(object, permission.acls)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
if !permission.context.nil?
|
|
115
|
-
log(:debug, "Setting context on #{object}")
|
|
116
|
-
@command_array << ChangeContextCommand.new(object, permission.context)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
if !permission.owner.nil? || !permission.group.nil?
|
|
120
|
-
log(:debug, "Setting ownership of #{object}")
|
|
121
|
-
@command_array << ChangeOwnerCommand.new(object, permission.owner, permission.group)
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def copying_file?(file)
|
|
126
|
-
file = sanitize_dir_path(file)
|
|
127
|
-
log(:debug, "Checking for #{file} in #{@copy_targets.keys.inspect}")
|
|
128
|
-
@copy_targets.has_key?(file)
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def making_directory?(dir)
|
|
132
|
-
dir = sanitize_dir_path(dir)
|
|
133
|
-
log(:debug, "Checking for #{dir} in #{@mkdir_targets.inspect}")
|
|
134
|
-
@mkdir_targets.include?(dir)
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def find_matches(permission)
|
|
138
|
-
log(:debug, "Finding matches for #{permission.object}")
|
|
139
|
-
matches = []
|
|
140
|
-
if permission.type.include?("file")
|
|
141
|
-
@copy_targets.keys.each do |object|
|
|
142
|
-
log(:debug, "Checking #{object}")
|
|
143
|
-
if (permission.matches_pattern?(object) && !permission.matches_except?(object))
|
|
144
|
-
log(:debug, "Found match #{object}")
|
|
145
|
-
permission.validate_file_acl(object)
|
|
146
|
-
matches << object
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
if permission.type.include?("directory")
|
|
151
|
-
@mkdir_targets.each do |object|
|
|
152
|
-
log(:debug, "Checking #{object}")
|
|
153
|
-
if (permission.matches_pattern?(object) && !permission.matches_except?(object))
|
|
154
|
-
log(:debug, "Found match #{object}")
|
|
155
|
-
matches << object
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
matches
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def to_json
|
|
163
|
-
command_json = @command_array.map(&:to_h)
|
|
164
|
-
{"instructions" => command_json}.to_json
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
def each(&block)
|
|
168
|
-
@command_array.each(&block)
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
private
|
|
172
|
-
def sanitize_dir_path(path)
|
|
173
|
-
File.expand_path(path)
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
private
|
|
177
|
-
def description
|
|
178
|
-
self.class.to_s
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
private
|
|
182
|
-
def log(severity, message)
|
|
183
|
-
raise ArgumentError, "Unknown severity #{severity.inspect}" unless InstanceAgent::Log::SEVERITIES.include?(severity.to_s)
|
|
184
|
-
InstanceAgent::Log.send(severity.to_sym, "#{description}: #{message}")
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
class RemoveCommand
|
|
189
|
-
def initialize(location)
|
|
190
|
-
@file_path = location
|
|
191
|
-
end
|
|
192
|
-
def execute
|
|
193
|
-
#If the file doesn't exist the command is ignored
|
|
194
|
-
if File.exist?(@file_path)
|
|
195
|
-
if File.directory?(@file_path)
|
|
196
|
-
# TODO (AWSGLUE-713): handle the exception if the directory is non-empty;
|
|
197
|
-
# this might mean the customer has put files in this directory and we should
|
|
198
|
-
# probably ignore the error and move on
|
|
199
|
-
FileUtils.rmdir(@file_path)
|
|
200
|
-
else
|
|
201
|
-
FileUtils.rm(@file_path)
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
class CopyCommand
|
|
208
|
-
attr_reader :destination, :source
|
|
209
|
-
def initialize(source, destination)
|
|
210
|
-
@source = source
|
|
211
|
-
@destination = destination
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
def execute(cleanup_file)
|
|
215
|
-
raise "File already exists at #{@destination}" if File.exists?(@destination)
|
|
216
|
-
cleanup_file.puts(@destination)
|
|
217
|
-
if File.symlink?(@source)
|
|
218
|
-
FileUtils.symlink(File.readlink(@source), @destination)
|
|
219
|
-
else
|
|
220
|
-
FileUtils.copy(@source, @destination, :preserve => true)
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
def to_h
|
|
225
|
-
{"type" => "copy", "source" => @source, "destination" => @destination}
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
class MakeDirectoryCommand
|
|
230
|
-
def initialize(destination)
|
|
231
|
-
@directory = destination
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
def execute(cleanup_file)
|
|
235
|
-
raise "File already exists at #{@directory}" if
|
|
236
|
-
File.exists?(@directory)
|
|
237
|
-
FileUtils.mkdir(@directory)
|
|
238
|
-
cleanup_file.puts(@directory)
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
def to_h
|
|
242
|
-
{"type" => "mkdir", "directory" => @directory}
|
|
243
|
-
end
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
class ChangeModeCommand
|
|
247
|
-
def initialize(object, mode)
|
|
248
|
-
@object = object
|
|
249
|
-
@mode = mode
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
def execute(cleanup_file)
|
|
253
|
-
File.chmod(@mode.to_i(8), @object)
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
def to_h
|
|
257
|
-
{"type" => "chmod", "mode" => @mode, "file" => @object}
|
|
258
|
-
end
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
class ChangeAclCommand
|
|
262
|
-
def initialize(object, acl)
|
|
263
|
-
@object = object
|
|
264
|
-
@acl = acl
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
def execute(cleanup_file)
|
|
268
|
-
begin
|
|
269
|
-
get_full_acl
|
|
270
|
-
acl = @acl.get_acl.join(",")
|
|
271
|
-
if !system("setfacl --set #{acl} #{@object}")
|
|
272
|
-
raise "Unable to set acl correctly: setfacl --set #{acl} #{@object}, exit code: #{$?}"
|
|
273
|
-
end
|
|
274
|
-
ensure
|
|
275
|
-
clear_full_acl
|
|
276
|
-
end
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
def clear_full_acl
|
|
280
|
-
@acl.clear_additional
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
def get_full_acl()
|
|
284
|
-
perm = "%o" % File.stat(@object).mode
|
|
285
|
-
perm = perm[-3,3]
|
|
286
|
-
@acl.add_ace(":#{perm[0]}")
|
|
287
|
-
@acl.add_ace("g::#{perm[1]}")
|
|
288
|
-
@acl.add_ace("o::#{perm[2]}")
|
|
289
|
-
if @acl.has_base_named? && !@acl.has_base_mask?
|
|
290
|
-
@acl.add_ace("m::#{perm[1]}")
|
|
291
|
-
end
|
|
292
|
-
if @acl.has_default?
|
|
293
|
-
if !@acl.has_default_user?
|
|
294
|
-
@acl.add_ace("d::#{perm[0]}")
|
|
295
|
-
end
|
|
296
|
-
if !@acl.has_default_group?
|
|
297
|
-
@acl.add_ace("d:g::#{perm[1]}")
|
|
298
|
-
end
|
|
299
|
-
if !@acl.has_default_other?
|
|
300
|
-
@acl.add_ace("d:o:#{perm[2]}")
|
|
301
|
-
end
|
|
302
|
-
if @acl.has_default_named? && !@acl.has_default_mask?
|
|
303
|
-
@acl.add_ace(@acl.get_default_group_ace.sub("group:","mask"))
|
|
304
|
-
end
|
|
305
|
-
end
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
def to_h
|
|
309
|
-
{"type" => "setfacl", "acl" => @acl.get_acl, "file" => @object}
|
|
310
|
-
end
|
|
311
|
-
end
|
|
312
|
-
|
|
313
|
-
class ChangeOwnerCommand
|
|
314
|
-
def initialize(object, owner, group)
|
|
315
|
-
@object = object
|
|
316
|
-
@owner = owner
|
|
317
|
-
@group = group
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
def execute(cleanup_file)
|
|
321
|
-
ownerid = Etc.getpwnam(@owner).uid if @owner
|
|
322
|
-
groupid = Etc.getgrnam(@group).gid if @group
|
|
323
|
-
File.chown(ownerid, groupid, @object)
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
def to_h
|
|
327
|
-
{"type" => "chown", "owner" => @owner, "group" => @group, "file" => @object}
|
|
328
|
-
end
|
|
329
|
-
end
|
|
330
|
-
|
|
331
|
-
class ChangeContextCommand
|
|
332
|
-
def initialize(object, context)
|
|
333
|
-
@object = object
|
|
334
|
-
@context = context
|
|
335
|
-
end
|
|
336
|
-
|
|
337
|
-
def execute(cleanup_file)
|
|
338
|
-
if !@context.role.nil?
|
|
339
|
-
raise "Attempt to set role on object, not supported"
|
|
340
|
-
end
|
|
341
|
-
args = "-t #{@context.type}"
|
|
342
|
-
if (!@context.user.nil?)
|
|
343
|
-
args = "-s #{@context.user} " + args
|
|
344
|
-
end
|
|
345
|
-
if (!@context.range.nil?)
|
|
346
|
-
args = args + " -r #{@context.range.get_range}"
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
-
object = File.realpath(@object)
|
|
350
|
-
if !system("semanage fcontext -a #{args} #{object}")
|
|
351
|
-
raise "Unable to set context: semanage fcontext -a #{args} #{object}, exit code: #{$?}"
|
|
352
|
-
end
|
|
353
|
-
if !system("restorecon -v #{object}")
|
|
354
|
-
raise "Unable to apply context: restorcecon -v #{object}, exit code: #{$?}"
|
|
355
|
-
end
|
|
356
|
-
cleanup_file.puts("semanage\0#{object}")
|
|
357
|
-
end
|
|
358
|
-
|
|
359
|
-
def to_h
|
|
360
|
-
{"type" => "semanage", "context" => {"user" => @context.user, "role" => @context.role, "type" => @context.type, "range" => @context.range.get_range}, "file" => @object}
|
|
361
|
-
end
|
|
362
|
-
end
|
|
363
|
-
|
|
364
|
-
class RemoveContextCommand
|
|
365
|
-
def initialize(object)
|
|
366
|
-
@object = object
|
|
367
|
-
end
|
|
368
|
-
|
|
369
|
-
def execute
|
|
370
|
-
system("semanage fcontext -d #{@object}")
|
|
371
|
-
end
|
|
372
|
-
end
|
|
373
|
-
end
|
|
374
|
-
end
|