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.
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