aws-codedeploy-agent 0.0.1

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 (84) hide show
  1. data/.gitignore +2 -0
  2. data/CHANGES.md +3 -0
  3. data/Gemfile +13 -0
  4. data/LICENSE +177 -0
  5. data/NOTICE +2 -0
  6. data/README.md +16 -0
  7. data/aws-codedeploy-agent.gemspec +39 -0
  8. data/bin/codedeploy-agent +78 -0
  9. data/bin/codedeploy-install +15 -0
  10. data/bin/codedeploy-uninstall +13 -0
  11. data/certs/host-agent-deployment-signer-ca-chain.pem +76 -0
  12. data/conf/codedeployagent.yml +9 -0
  13. data/init.d/codedeploy-agent +61 -0
  14. data/lib/core_ext.rb +71 -0
  15. data/lib/instance_agent.rb +35 -0
  16. data/lib/instance_agent/agent/base.rb +34 -0
  17. data/lib/instance_agent/codedeploy_plugin/application_specification/ace_info.rb +133 -0
  18. data/lib/instance_agent/codedeploy_plugin/application_specification/acl_info.rb +163 -0
  19. data/lib/instance_agent/codedeploy_plugin/application_specification/application_specification.rb +142 -0
  20. data/lib/instance_agent/codedeploy_plugin/application_specification/context_info.rb +23 -0
  21. data/lib/instance_agent/codedeploy_plugin/application_specification/file_info.rb +23 -0
  22. data/lib/instance_agent/codedeploy_plugin/application_specification/linux_permission_info.rb +121 -0
  23. data/lib/instance_agent/codedeploy_plugin/application_specification/mode_info.rb +66 -0
  24. data/lib/instance_agent/codedeploy_plugin/application_specification/range_info.rb +134 -0
  25. data/lib/instance_agent/codedeploy_plugin/application_specification/script_info.rb +27 -0
  26. data/lib/instance_agent/codedeploy_plugin/codedeploy_control.rb +72 -0
  27. data/lib/instance_agent/codedeploy_plugin/command_executor.rb +357 -0
  28. data/lib/instance_agent/codedeploy_plugin/command_poller.rb +146 -0
  29. data/lib/instance_agent/codedeploy_plugin/deployment_specification.rb +150 -0
  30. data/lib/instance_agent/codedeploy_plugin/hook_executor.rb +206 -0
  31. data/lib/instance_agent/codedeploy_plugin/install_instruction.rb +374 -0
  32. data/lib/instance_agent/codedeploy_plugin/installer.rb +143 -0
  33. data/lib/instance_agent/codedeploy_plugin/request_helper.rb +28 -0
  34. data/lib/instance_agent/config.rb +43 -0
  35. data/lib/instance_agent/log.rb +3 -0
  36. data/lib/instance_agent/platform.rb +17 -0
  37. data/lib/instance_agent/platform/linux_util.rb +57 -0
  38. data/lib/instance_agent/runner/child.rb +57 -0
  39. data/lib/instance_agent/runner/master.rb +103 -0
  40. data/lib/instance_metadata.rb +47 -0
  41. data/test/certificate_helper.rb +120 -0
  42. data/test/helpers/instance_agent_helper.rb +25 -0
  43. data/test/instance_agent/agent/base_test.rb +49 -0
  44. data/test/instance_agent/codedeploy_plugin/application_specification_test.rb +1710 -0
  45. data/test/instance_agent/codedeploy_plugin/codedeploy_control_test.rb +51 -0
  46. data/test/instance_agent/codedeploy_plugin/command_executor_test.rb +513 -0
  47. data/test/instance_agent/codedeploy_plugin/command_poller_test.rb +459 -0
  48. data/test/instance_agent/codedeploy_plugin/deployment_specification_test.rb +335 -0
  49. data/test/instance_agent/codedeploy_plugin/hook_executor_test.rb +250 -0
  50. data/test/instance_agent/codedeploy_plugin/install_instruction_test.rb +566 -0
  51. data/test/instance_agent/codedeploy_plugin/installer_test.rb +519 -0
  52. data/test/instance_agent/codedeploy_plugin/request_helper_test.rb +37 -0
  53. data/test/instance_agent/config_test.rb +64 -0
  54. data/test/instance_agent/runner/child_test.rb +87 -0
  55. data/test/instance_metadata_test.rb +97 -0
  56. data/test/test_helper.rb +16 -0
  57. data/vendor/gems/.codedeploy-commands-1.0.0.created.rid +1 -0
  58. data/vendor/gems/codedeploy-commands/apis/CodeDeployCommand.api.json +372 -0
  59. data/vendor/gems/codedeploy-commands/codedeploy-commands-1.0.0.gemspec +28 -0
  60. data/vendor/gems/codedeploy-commands/lib/aws/codedeploy_commands.rb +18 -0
  61. data/vendor/gems/codedeploy-commands/lib/aws/plugins/certificate_authority.rb +12 -0
  62. data/vendor/gems/codedeploy-commands/lib/aws/plugins/deploy_control_endpoint.rb +22 -0
  63. data/vendor/gems/process_manager/README.md +1 -0
  64. data/vendor/gems/process_manager/lib/blank.rb +153 -0
  65. data/vendor/gems/process_manager/lib/core_ext.rb +73 -0
  66. data/vendor/gems/process_manager/lib/process_manager.rb +49 -0
  67. data/vendor/gems/process_manager/lib/process_manager/child.rb +119 -0
  68. data/vendor/gems/process_manager/lib/process_manager/config.rb +112 -0
  69. data/vendor/gems/process_manager/lib/process_manager/log.rb +107 -0
  70. data/vendor/gems/process_manager/lib/process_manager/master.rb +322 -0
  71. data/vendor/gems/process_manager/process_manager-0.0.13.gemspec +42 -0
  72. data/vendor/specifications/aws-sdk-core-2.0.5.gemspec +39 -0
  73. data/vendor/specifications/builder-3.2.2.gemspec +29 -0
  74. data/vendor/specifications/codedeploy-commands-1.0.0.gemspec +28 -0
  75. data/vendor/specifications/gli-2.5.6.gemspec +51 -0
  76. data/vendor/specifications/jamespath-0.5.1.gemspec +35 -0
  77. data/vendor/specifications/little-plugger-1.1.3.gemspec +32 -0
  78. data/vendor/specifications/logging-1.8.1.gemspec +44 -0
  79. data/vendor/specifications/multi_json-1.7.7.gemspec +30 -0
  80. data/vendor/specifications/multi_json-1.8.4.gemspec +30 -0
  81. data/vendor/specifications/multi_xml-0.5.5.gemspec +30 -0
  82. data/vendor/specifications/process_manager-0.0.13.gemspec +42 -0
  83. data/vendor/specifications/simple_pid-0.2.1.gemspec +28 -0
  84. metadata +377 -0
@@ -0,0 +1,146 @@
1
+ require 'instance_agent/codedeploy_plugin/command_executor'
2
+ require 'instance_metadata'
3
+ require 'socket'
4
+ require 'instance_agent/codedeploy_plugin/codedeploy_control'
5
+
6
+ module InstanceAgent
7
+ module CodeDeployPlugin
8
+ class CommandPoller < InstanceAgent::Agent::Base
9
+
10
+ VERSION = "2013-04-23"
11
+
12
+ def initialize
13
+ region = ENV['AWS_REGION'] || InstanceMetadata.region
14
+ @host_identifier = ENV['AWS_HOST_IDENTIFIER'] || InstanceMetadata.host_identifier
15
+
16
+ log(:debug, "Configuring deploy control client: Region = #{region.inspect}")
17
+ log(:debug, "Deploy control endpoint override = " + ENV['AWS_DEPLOY_CONTROL_ENDPOINT'].inspect)
18
+
19
+ @deploy_control_client = InstanceAgent::CodeDeployPlugin::CodeDeployControl.new(:region => region, :logger => InstanceAgent::Log, :ssl_ca_directory => ENV['AWS_SSL_CA_DIRECTORY']).get_client
20
+
21
+ @plugin = InstanceAgent::CodeDeployPlugin::CommandExecutor.new(
22
+ :deploy_control_client => @deploy_control_client,
23
+ :hook_mapping => create_hook_mapping)
24
+
25
+ log(:debug, "Initializing Host Agent: " +
26
+ "Host Identifier = #{@host_identifier}")
27
+ end
28
+
29
+ def create_hook_mapping
30
+ #Map commands to lifecycle hooks
31
+ { "BeforeELBRemove"=>["BeforeELBRemove"],
32
+ "AfterELBRemove"=>["AfterELBRemove"],
33
+ "ApplicationStop"=>["ApplicationStop"],
34
+ "BeforeInstall"=>["BeforeInstall"],
35
+ "AfterInstall"=>["AfterInstall"],
36
+ "ApplicationStart"=>["ApplicationStart"],
37
+ "BeforeELBAdd"=>["BeforeELBAdd"],
38
+ "AfterELBAdd"=>["AfterELBAdd"],
39
+ "ValidateService"=>["ValidateService"]}
40
+ end
41
+
42
+ def perform
43
+ return unless command = next_command
44
+ return unless acknowledge_command(command)
45
+
46
+ begin
47
+ spec = get_deployment_specification(command)
48
+ #Successful commands will complete without raising an exception
49
+ script_output = process_command(command, spec)
50
+ log(:debug, 'Calling PutHostCommandComplete: "Succeeded"')
51
+ @deploy_control_client.put_host_command_complete(
52
+ :command_status => 'Succeeded',
53
+ :diagnostics => {:format => "JSON", :payload => gather_diagnostics()},
54
+ :host_command_identifier => command.host_command_identifier)
55
+
56
+ #Commands that throw an exception will be considered to have failed
57
+ rescue ScriptError => e
58
+ log(:debug, 'Calling PutHostCommandComplete: "Code Error" ')
59
+ @deploy_control_client.put_host_command_complete(
60
+ :command_status => "Failed",
61
+ :diagnostics => {:format => "JSON", :payload => gather_diagnostics_from_script_error(e)},
62
+ :host_command_identifier => command.host_command_identifier)
63
+ raise e
64
+ rescue Exception => e
65
+ log(:debug, 'Calling PutHostCommandComplete: "Code Error" ')
66
+ @deploy_control_client.put_host_command_complete(
67
+ :command_status => "Failed",
68
+ :diagnostics => {:format => "JSON", :payload => gather_diagnostics_from_error(e)},
69
+ :host_command_identifier => command.host_command_identifier)
70
+ raise e
71
+ end
72
+ end
73
+
74
+ def next_command
75
+ log(:debug, "Calling PollHostCommand:")
76
+ output = @deploy_control_client.poll_host_command(:host_identifier => @host_identifier)
77
+ command = output.host_command
78
+ if command.nil?
79
+ log(:debug, "PollHostCommand: Host Command = nil")
80
+ else
81
+ log(:debug, "PollHostCommand: " +
82
+ "Host Identifier = #{command.host_identifier}; " +
83
+ "Host Command Identifier = #{command.host_command_identifier}; " +
84
+ "Deployment Execution ID = #{command.deployment_execution_id}; " +
85
+ "Command Name = #{command.command_name}")
86
+ raise "Host Identifier mismatch: #{@host_identifier} != #{command.host_identifier}" unless @host_identifier.include? command.host_identifier
87
+ raise "Command Name missing" if command.command_name.nil? || command.command_name.empty?
88
+ end
89
+ command
90
+ end
91
+
92
+ def acknowledge_command(command)
93
+ log(:debug, "Calling PutHostCommandAcknowledgement:")
94
+ output = @deploy_control_client.put_host_command_acknowledgement(
95
+ :diagnostics => nil,
96
+ :host_command_identifier => command.host_command_identifier)
97
+ status = output.command_status
98
+ log(:debug, "Command Status = #{status}")
99
+ true unless status == "Succeeded" || status == "Failed"
100
+ end
101
+
102
+ def get_deployment_specification(command)
103
+ log(:debug, "Calling GetDeploymentSpecification:")
104
+ output = @deploy_control_client.get_deployment_specification(
105
+ :deployment_execution_id => command.deployment_execution_id,
106
+ :host_identifier => @host_identifier)
107
+ log(:debug, "GetDeploymentSpecification: " +
108
+ "Deployment System = #{output.deployment_system}")
109
+ raise "Deployment System mismatch: #{@plugin.deployment_system} != #{output.deployment_system}" unless @plugin.deployment_system == output.deployment_system
110
+ raise "Deployment Specification missing" if output.deployment_specification.nil?
111
+ output.deployment_specification.generic_envelope
112
+ end
113
+
114
+ def process_command(command, spec)
115
+ log(:debug, "Calling #{@plugin.to_s}.execute_command")
116
+ @plugin.execute_command(command, spec)
117
+ end
118
+
119
+ private
120
+ def gather_diagnostics_from_script_error(script_error)
121
+ script_error.to_json
122
+ end
123
+
124
+ private
125
+ def gather_diagnostics_from_error(error)
126
+ begin
127
+ message = error.message || ""
128
+ raise ScriptError.new(ScriptError::UNKNOWN_ERROR_CODE, "", ScriptLog.new), message
129
+ rescue ScriptError => e
130
+ script_error = e
131
+ end
132
+ gather_diagnostics_from_script_error(script_error)
133
+ end
134
+
135
+ private
136
+ def gather_diagnostics()
137
+ begin
138
+ raise ScriptError.new(ScriptError::SUCCEEDED_CODE, "", ScriptLog.new), 'Succeeded'
139
+ rescue ScriptError => e
140
+ script_error = e
141
+ end
142
+ gather_diagnostics_from_script_error(script_error)
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,150 @@
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
@@ -0,0 +1,206 @@
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