• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aws / aws-codedeploy-agent / 4600315001

pending completion
4600315001

push

github

GitHub
Updating latest version info for master branch

1129 of 2362 relevant lines covered (47.8%)

2.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

25.69
/lib/instance_agent/plugins/codedeploy/command_poller.rb
1
require 'socket'
1✔
2
require 'concurrent'
1✔
3
require 'pathname'
1✔
4
require 'instance_metadata'
1✔
5
require 'instance_agent/agent/base'
1✔
6
require_relative 'deployment_command_tracker'
1✔
7

8
module InstanceAgent
1✔
9
  module Plugins
1✔
10
    module CodeDeployPlugin
1✔
11
      class CommandPoller < InstanceAgent::Agent::Base
1✔
12

13
        VERSION = "2013-04-23"
1✔
14

15
        #Map commands to lifecycle hooks
16
        DEFAULT_HOOK_MAPPING =
17
          { "BeforeBlockTraffic"=>["BeforeBlockTraffic"],
1✔
18
            "AfterBlockTraffic"=>["AfterBlockTraffic"],
19
            "ApplicationStop"=>["ApplicationStop"],
20
            "BeforeInstall"=>["BeforeInstall"],
21
            "AfterInstall"=>["AfterInstall"],
22
            "ApplicationStart"=>["ApplicationStart"],
23
            "BeforeAllowTraffic"=>["BeforeAllowTraffic"],
24
            "AfterAllowTraffic"=>["AfterAllowTraffic"],
25
            "ValidateService"=>["ValidateService"]}
26

27
        def initialize
1✔
28
          test_profile = InstanceAgent::Config.config[:codedeploy_test_profile]
×
29
          unless ["beta", "gamma"].include?(test_profile.downcase)
×
30
            # Remove any user overrides set in the environment.
31
            # The agent should always pull credentials from the EC2 instance
32
            # profile or the credentials in the OnPremises config file.
33
            ENV['AWS_ACCESS_KEY_ID'] = nil
×
34
            ENV['AWS_SECRET_ACCESS_KEY'] = nil
×
35
            ENV['AWS_CREDENTIAL_FILE'] = nil
×
36
          end
37
          CodeDeployPlugin::OnPremisesConfig.configure
×
38
          region = ENV['AWS_REGION'] || InstanceMetadata.region
×
39
          @host_identifier = ENV['AWS_HOST_IDENTIFIER'] || InstanceMetadata.host_identifier
×
40

41
          log(:debug, "Configuring deploy control client: Region=#{region.inspect}")
×
42
          log(:debug, "Deploy control endpoint override=#{InstanceAgent::Config.config[:deploy_control_endpoint]}")
×
43
          log(:debug, "Enable auth policy = #{InstanceAgent::Config.config[:enable_auth_policy]}")
×
44

45
          @deploy_control = InstanceAgent::Plugins::CodeDeployPlugin::CodeDeployControl.new(:region => region, :logger => InstanceAgent::Log, :ssl_ca_directory => ENV['AWS_SSL_CA_DIRECTORY'])
×
46
          @deploy_control_client = @deploy_control.get_client
×
47

48
          @plugin = InstanceAgent::Plugins::CodeDeployPlugin::CommandExecutor.new(:hook_mapping => DEFAULT_HOOK_MAPPING)
×
49

50
          @thread_pool = Concurrent::ThreadPoolExecutor.new(
×
51
            #TODO: Make these values configurable in agent configuration
52
            min_threads: 1,
53
            max_threads: 16,
54
            max_queue: 0 # unbounded work queue
55
          )
56

57
          log(:debug, "Initializing Host Agent: " +
×
58
          "Host Identifier = #{@host_identifier}")
59
        end
60

61
        def validate
1✔
62
          test_profile = InstanceAgent::Config.config[:codedeploy_test_profile]
×
63
          unless ["beta", "gamma"].include?(test_profile.downcase)
×
64
            log(:debug, "Validating CodeDeploy Plugin Configuration")
×
65
            Kernel.abort "Stopping CodeDeploy agent due to SSL validation error." unless @deploy_control.validate_ssl_config
×
66
            log(:debug, "CodeDeploy Plugin Configuration is valid")
×
67
          end
68
        end
69

70
        # Called during initialization of the child process
71
        def recover_from_crash?
1✔
72
          begin
73
            if DeploymentCommandTracker.check_deployment_event_inprogress?() then
×
74
              log(:warn, "Deployment tracking file found: #{DeploymentCommandTracker.deployment_dir_path()}. The agent likely restarted while running a customer-supplied script. Failing the lifecycle event.")
×
75
              host_command_identifier = DeploymentCommandTracker.most_recent_host_command_identifier()
×
76

77
              log(:info, "Calling PutHostCommandComplete: 'Failed' #{host_command_identifier}")
×
78
              @deploy_control_client.put_host_command_complete(
×
79
                :command_status => "Failed",
80
                :diagnostics => {:format => "JSON", :payload => gather_diagnostics_from_failure_after_restart("Failing in-progress lifecycle event after an agent restart.")},
81
                :host_command_identifier => host_command_identifier)
82

83
              DeploymentCommandTracker.clean_ongoing_deployment_dir()
×
84
              return true
×
85
            end
86
            # We want to catch-all exceptions so that the child process always can startup succesfully.
87
          rescue Exception => e
×
88
            log(:error, "Exception thrown during restart recovery: #{e}")
×
89
            return nil
×
90
          end
91
        end
92

93
        def perform
1✔
94
          return unless command = next_command
×
95

96
          #Commands will be executed on a separate thread.
97
          begin
98
            @thread_pool.post {
×
99
              acknowledge_and_process_command(command)
×
100
            }
101
          rescue Concurrent::RejectedExecutionError
×
102
            log(:warn, 'Graceful shutdown initiated, skipping any further polling until agent restarts')
×
103
          end
104
        end
105

106
        def graceful_shutdown
1✔
107
          log(:info, "Gracefully shutting down agent child threads now, will wait up to #{ProcessManager::Config.config[:kill_agent_max_wait_time_seconds]} seconds")
×
108
          # tell the pool to shutdown in an orderly fashion, allowing in progress work to complete
109
          @thread_pool.shutdown
×
110
          # now wait for all work to complete, wait till the timeout value
111
          @thread_pool.wait_for_termination ProcessManager::Config.config[:kill_agent_max_wait_time_seconds]
×
112
          log(:info, 'All agent child threads have been shut down')
×
113
        end
114

115
        def acknowledge_and_process_command(command)
1✔
116
          begin
117
            spec = get_deployment_specification(command)
×
118
            return unless acknowledge_command(command, spec)
×
119
            process_command(command, spec)
×
120
            #Commands that throw an exception will be considered to have failed
121
          rescue Exception => e
×
122
            log(:warn, 'Calling PutHostCommandComplete: "Code Error" ')
×
123
            @deploy_control_client.put_host_command_complete(
×
124
            :command_status => "Failed",
125
            :diagnostics => {:format => "JSON", :payload => gather_diagnostics_from_error(e)},
126
            :host_command_identifier => command.host_command_identifier)
127
            raise e
×
128
          end
129
        end
130
        
131
        def process_command(command, spec)
1✔
132
          log(:debug, "Calling #{@plugin.to_s}.execute_command")
×
133
          begin
134
            deployment_id = InstanceAgent::Plugins::CodeDeployPlugin::DeploymentSpecification.parse(spec).deployment_id
×
135
            DeploymentCommandTracker.create_ongoing_deployment_tracking_file(deployment_id, command.host_command_identifier)
×
136
            #Successful commands will complete without raising an exception
137
            @plugin.execute_command(command, spec)
×
138
            
139
            log(:debug, 'Calling PutHostCommandComplete: "Succeeded"')
×
140
            @deploy_control_client.put_host_command_complete(
×
141
            :command_status => 'Succeeded',
142
            :diagnostics => {:format => "JSON", :payload => gather_diagnostics()},
143
            :host_command_identifier => command.host_command_identifier)
144
            #Commands that throw an exception will be considered to have failed
145
          rescue ScriptError => e
×
146
            log(:debug, 'Calling PutHostCommandComplete: "Code Error" ')
×
147
            @deploy_control_client.put_host_command_complete(
×
148
            :command_status => "Failed",
149
            :diagnostics => {:format => "JSON", :payload => gather_diagnostics_from_script_error(e)},
150
            :host_command_identifier => command.host_command_identifier)
151
            log(:error, "Error during perform: #{e.class} - #{e.message} - #{e.backtrace.join("\n")}")
×
152
            raise e
×
153
          rescue Exception => e
154
            log(:debug, 'Calling PutHostCommandComplete: "Code Error" ')
×
155
            @deploy_control_client.put_host_command_complete(
×
156
            :command_status => "Failed",
157
            :diagnostics => {:format => "JSON", :payload => gather_diagnostics_from_error(e)},
158
            :host_command_identifier => command.host_command_identifier)
159
            log(:error, "Error during perform: #{e.class} - #{e.message} - #{e.backtrace.join("\n")}")
×
160
            raise e
×
161
          ensure 
162
            DeploymentCommandTracker.delete_deployment_command_tracking_file(deployment_id)  
×
163
          end
164
        end
165
        
166
        private
1✔
167
        def next_command
1✔
168
          log(:debug, "Calling PollHostCommand:")
×
169
          begin
170
            output = @deploy_control_client.poll_host_command(:host_identifier => @host_identifier)
×
171
          rescue Exception => e
172
            log(:error, "Error polling for host commands: #{e.class} - #{e.message} - #{e.backtrace.join("\n")}")
×
173
            raise e
×
174
          end
175
          command = output.host_command
×
176
          if command.nil?
×
177
            log(:debug, "PollHostCommand: Host Command =  nil")
×
178
          else
179
            log(:debug, "PollHostCommand: "  +
×
180
            "Host Identifier = #{command.host_identifier}; "  +
181
            "Host Command Identifier = #{command.host_command_identifier}; "  +
182
            "Deployment Execution ID = #{command.deployment_execution_id}; "  +
183
            "Command Name = #{command.command_name}")
184
            raise "Host Identifier mismatch: #{@host_identifier} != #{command.host_identifier}" unless @host_identifier.include? command.host_identifier
×
185
            raise "Command Name missing" if command.command_name.nil? || command.command_name.empty?
×
186
          end
187
          command
×
188
        end
189

190
        private
1✔
191
        def get_ack_diagnostics(command, spec)
1✔
192
          is_command_noop = @plugin.is_command_noop?(command.command_name, spec)
×
193
          return {:format => "JSON", :payload => {'IsCommandNoop' => is_command_noop}.to_json()}
×
194
        end
195

196
        private
1✔
197
        def acknowledge_command(command, spec)
1✔
198
          ack_diagnostics = get_ack_diagnostics(command, spec)
×
199

200
          log(:debug, "Calling PutHostCommandAcknowledgement:")
×
201
          output =  @deploy_control_client.put_host_command_acknowledgement(
×
202
          :diagnostics => ack_diagnostics,
203
          :host_command_identifier => command.host_command_identifier)
204
          status = output.command_status
×
205
          log(:debug, "Command Status = #{status}")
×
206

207
          if status == "Failed" then
×
208
            log(:info, "Received Failed for command #{command.command_name}, checking whether command is a noop...")
×
209
            complete_if_noop_command(command)
×
210
          end
211
          true unless status == "Succeeded" || status == "Failed"
×
212
        end
213

214
        private
1✔
215
        def complete_if_noop_command(command)
1✔
216
          spec = get_deployment_specification(command)
×
217

218
          if @plugin.is_command_noop?(command.command_name, spec) then
×
219
            log(:debug, 'Calling PutHostCommandComplete: "Succeeded"')
×
220
            @deploy_control_client.put_host_command_complete(
×
221
            :command_status => 'Succeeded',
222
            :diagnostics => {:format => "JSON", :payload => gather_diagnostics("CompletedNoopCommand")},
223
            :host_command_identifier => command.host_command_identifier)
224
          end
225
        end
226

227
        private
1✔
228
        def get_deployment_specification(command)
1✔
229
          log(:debug, "Calling GetDeploymentSpecification:")
×
230
          output =  @deploy_control_client.get_deployment_specification(
×
231
          :deployment_execution_id => command.deployment_execution_id,
232
          :host_identifier => @host_identifier)
233
          log(:debug, "GetDeploymentSpecification: " +
×
234
          "Deployment System = #{output.deployment_system}")
235
          raise "Deployment System mismatch: #{@plugin.deployment_system} != #{output.deployment_system}" unless @plugin.deployment_system == output.deployment_system
×
236
          raise "Deployment Specification missing" if output.deployment_specification.nil?
×
237
          output.deployment_specification.generic_envelope
×
238
        end
239

240
        private
1✔
241
        def gather_diagnostics_from_script_error(script_error)
1✔
242
          return script_error.to_json
×
243
          rescue Exception => e
244
            return {'error_code' => "Unknown", 'script_name' => script_error.script_name, 'message' => "Attempting minimal diagnostics", 'log' => "Exception #{e.class} occured"}.to_json
×
245
        end
246

247
        private
1✔
248
        def gather_diagnostics_from_error(error)
1✔
249
          begin
250
            message = error.message || ""
×
251
            raise ScriptError.new(ScriptError::UNKNOWN_ERROR_CODE, "", ScriptLog.new), message
×
252
          rescue ScriptError => e
253
            script_error = e
×
254
          end
255
          gather_diagnostics_from_script_error(script_error)
×
256
        end
257

258
        private
1✔
259
        def gather_diagnostics_from_failure_after_restart(msg = "")
1✔
260
          begin
261
            raise ScriptError.new(ScriptError::FAILED_AFTER_RESTART_CODE, "", ScriptLog.new), "Failed: #{msg}"
×
262
          rescue ScriptError => e
263
            script_error = e
×
264
          end
265
          gather_diagnostics_from_script_error(script_error)
×
266
        end
267

268
        private
1✔
269
        def gather_diagnostics(msg = "")
1✔
270
          begin
271
            raise ScriptError.new(ScriptError::SUCCEEDED_CODE, "", ScriptLog.new), "Succeeded: #{msg}"
×
272
          rescue ScriptError => e
273
            script_error = e
×
274
          end
275
          gather_diagnostics_from_script_error(script_error)
×
276
        end
277
      end
278
    end
279
  end
280
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc