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.
Files changed (91) hide show
  1. data/aws-codedeploy-agent.gemspec +5 -5
  2. data/certs/host-agent-deployment-signer-ca-chain.pem +30 -0
  3. data/conf/codedeployagent.yml +0 -1
  4. data/lib/instance_agent.rb +1 -13
  5. data/lib/instance_agent/agent/base.rb +38 -12
  6. data/lib/instance_agent/agent/plugin.rb +21 -0
  7. data/lib/instance_agent/config.rb +2 -1
  8. data/lib/instance_agent/platform/linux_util.rb +4 -0
  9. data/lib/instance_agent/plugins/codedeploy/application_specification/ace_info.rb +133 -0
  10. data/lib/instance_agent/plugins/codedeploy/application_specification/acl_info.rb +163 -0
  11. data/lib/instance_agent/plugins/codedeploy/application_specification/application_specification.rb +143 -0
  12. data/lib/instance_agent/plugins/codedeploy/application_specification/context_info.rb +23 -0
  13. data/lib/instance_agent/plugins/codedeploy/application_specification/file_info.rb +23 -0
  14. data/lib/instance_agent/plugins/codedeploy/application_specification/linux_permission_info.rb +121 -0
  15. data/lib/instance_agent/plugins/codedeploy/application_specification/mode_info.rb +66 -0
  16. data/lib/instance_agent/plugins/codedeploy/application_specification/range_info.rb +134 -0
  17. data/lib/instance_agent/plugins/codedeploy/application_specification/script_info.rb +27 -0
  18. data/lib/instance_agent/plugins/codedeploy/codedeploy_control.rb +100 -0
  19. data/lib/instance_agent/plugins/codedeploy/command_executor.rb +359 -0
  20. data/lib/instance_agent/plugins/codedeploy/command_poller.rb +178 -0
  21. data/lib/instance_agent/plugins/codedeploy/deployment_specification.rb +161 -0
  22. data/lib/instance_agent/plugins/codedeploy/hook_executor.rb +226 -0
  23. data/lib/instance_agent/plugins/codedeploy/install_instruction.rb +389 -0
  24. data/lib/instance_agent/plugins/codedeploy/installer.rb +147 -0
  25. data/lib/instance_agent/plugins/codedeploy/onpremise_config.rb +42 -0
  26. data/lib/instance_agent/plugins/codedeploy/register_plugin.rb +17 -0
  27. data/lib/instance_agent/runner/child.rb +20 -5
  28. data/lib/instance_agent/runner/master.rb +2 -15
  29. data/lib/instance_metadata.rb +2 -2
  30. data/test/certificate_helper.rb +1 -1
  31. data/test/helpers/instance_agent_helper.rb +1 -0
  32. data/test/instance_agent/agent/base_test.rb +16 -3
  33. data/test/instance_agent/config_test.rb +2 -1
  34. data/test/instance_agent/plugins/codedeploy/application_specification_test.rb +1713 -0
  35. data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/codedeploy_control_test.rb +1 -1
  36. data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/command_executor_test.rb +32 -9
  37. data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/command_poller_test.rb +13 -14
  38. data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/deployment_specification_test.rb +98 -25
  39. data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/hook_executor_test.rb +83 -15
  40. data/test/instance_agent/plugins/codedeploy/install_instruction_test.rb +568 -0
  41. data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/installer_test.rb +12 -9
  42. data/test/instance_agent/plugins/codedeploy/onpremise_config_test.rb +72 -0
  43. data/test/instance_agent/runner/child_test.rb +1 -1
  44. data/vendor/gems/.codedeploy-commands-1.0.0.created.rid +1 -1
  45. data/vendor/gems/codedeploy-commands/lib/aws/plugins/deploy_control_endpoint.rb +4 -0
  46. data/vendor/gems/jmespath-1.0.1/lib/jmespath.rb +41 -0
  47. data/vendor/gems/jmespath-1.0.1/lib/jmespath/caching_parser.rb +30 -0
  48. data/vendor/gems/jmespath-1.0.1/lib/jmespath/errors.rb +17 -0
  49. data/vendor/gems/jmespath-1.0.1/lib/jmespath/expr_node.rb +15 -0
  50. data/vendor/gems/jmespath-1.0.1/lib/jmespath/lexer.rb +116 -0
  51. data/vendor/gems/jmespath-1.0.1/lib/jmespath/parser.rb +347 -0
  52. data/vendor/gems/jmespath-1.0.1/lib/jmespath/runtime.rb +71 -0
  53. data/vendor/gems/jmespath-1.0.1/lib/jmespath/token.rb +41 -0
  54. data/vendor/gems/jmespath-1.0.1/lib/jmespath/token_stream.rb +60 -0
  55. data/vendor/gems/jmespath-1.0.1/lib/jmespath/tree_interpreter.rb +523 -0
  56. data/vendor/gems/jmespath-1.0.1/lib/jmespath/version.rb +3 -0
  57. data/vendor/gems/process_manager/lib/process_manager/master.rb +16 -5
  58. data/vendor/specifications/{aws-sdk-core-2.0.5.gemspec → aws-sdk-core-2.0.42.gemspec} +9 -11
  59. data/vendor/specifications/builder-3.2.2.gemspec +1 -1
  60. data/vendor/specifications/codedeploy-commands-1.0.0.gemspec +7 -6
  61. data/vendor/specifications/gli-2.5.6.gemspec +1 -1
  62. data/vendor/specifications/jmespath-1.0.1.gemspec +29 -0
  63. data/vendor/specifications/little-plugger-1.1.3.gemspec +1 -1
  64. data/vendor/specifications/logging-1.8.1.gemspec +1 -1
  65. data/vendor/specifications/multi_json-1.7.7.gemspec +1 -1
  66. data/vendor/specifications/multi_json-1.8.4.gemspec +1 -1
  67. data/vendor/specifications/multi_xml-0.5.5.gemspec +1 -1
  68. data/vendor/specifications/process_manager-0.0.13.gemspec +1 -1
  69. data/vendor/specifications/simple_pid-0.2.1.gemspec +1 -1
  70. metadata +76 -63
  71. data/lib/instance_agent/codedeploy_plugin/application_specification/ace_info.rb +0 -133
  72. data/lib/instance_agent/codedeploy_plugin/application_specification/acl_info.rb +0 -163
  73. data/lib/instance_agent/codedeploy_plugin/application_specification/application_specification.rb +0 -142
  74. data/lib/instance_agent/codedeploy_plugin/application_specification/context_info.rb +0 -23
  75. data/lib/instance_agent/codedeploy_plugin/application_specification/file_info.rb +0 -23
  76. data/lib/instance_agent/codedeploy_plugin/application_specification/linux_permission_info.rb +0 -121
  77. data/lib/instance_agent/codedeploy_plugin/application_specification/mode_info.rb +0 -66
  78. data/lib/instance_agent/codedeploy_plugin/application_specification/range_info.rb +0 -134
  79. data/lib/instance_agent/codedeploy_plugin/application_specification/script_info.rb +0 -27
  80. data/lib/instance_agent/codedeploy_plugin/codedeploy_control.rb +0 -72
  81. data/lib/instance_agent/codedeploy_plugin/command_executor.rb +0 -357
  82. data/lib/instance_agent/codedeploy_plugin/command_poller.rb +0 -170
  83. data/lib/instance_agent/codedeploy_plugin/deployment_specification.rb +0 -150
  84. data/lib/instance_agent/codedeploy_plugin/hook_executor.rb +0 -206
  85. data/lib/instance_agent/codedeploy_plugin/install_instruction.rb +0 -374
  86. data/lib/instance_agent/codedeploy_plugin/installer.rb +0 -143
  87. data/lib/instance_agent/codedeploy_plugin/request_helper.rb +0 -28
  88. data/test/instance_agent/codedeploy_plugin/application_specification_test.rb +0 -1710
  89. data/test/instance_agent/codedeploy_plugin/install_instruction_test.rb +0 -566
  90. data/test/instance_agent/codedeploy_plugin/request_helper_test.rb +0 -37
  91. data/vendor/specifications/jamespath-0.5.1.gemspec +0 -35
@@ -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