aws-codedeploy-agent 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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