• 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

26.96
/lib/instance_agent/plugins/codedeploy/command_executor.rb
1
require 'openssl'
1✔
2
require 'fileutils'
1✔
3
require 'aws-sdk-core'
1✔
4
require 'aws-sdk-s3'
1✔
5
require 'zlib'
1✔
6
require 'zip'
1✔
7
require 'instance_metadata'
1✔
8
require 'open-uri'
1✔
9
require 'uri'
1✔
10
require 'set'
1✔
11

12
require 'instance_agent/plugins/codedeploy/command_poller'
1✔
13
require 'instance_agent/plugins/codedeploy/deployment_specification'
1✔
14
require 'instance_agent/plugins/codedeploy/hook_executor'
1✔
15
require 'instance_agent/plugins/codedeploy/installer'
1✔
16
require 'instance_agent/string_utils'
1✔
17

18
module InstanceAgent
1✔
19
  module Plugins
1✔
20
    module CodeDeployPlugin
1✔
21
      ARCHIVES_TO_RETAIN = 5
1✔
22
      class CommandExecutor
1✔
23
        class << self
1✔
24
          attr_reader :command_methods
1✔
25
        end
26

27
        attr_reader :deployment_system
1✔
28

29
        InvalidCommandNameFailure = Class.new(Exception)
1✔
30

31
        def initialize(options = {})
1✔
32
          @deployment_system = "CodeDeploy"
×
33
          @hook_mapping = options[:hook_mapping]
×
34
          if(!@hook_mapping.nil?)
×
35
            map
×
36
          end
37
          begin
38
            max_revisions = ProcessManager::Config.config[:max_revisions]
×
39
            @archives_to_retain = max_revisions.nil?? ARCHIVES_TO_RETAIN : Integer(max_revisions)
×
40
            if @archives_to_retain < 1
×
41
              raise ArgumentError
×
42
            end
43
          rescue ArgumentError
44
            log(:error, "Invalid configuration :max_revision=#{max_revisions}")
×
45
            Platform.util.quit()
×
46
          end
47
          log(:info, "Archives to retain is: #{@archives_to_retain}}")
×
48
        end
49

50
        def self.command(name, &blk)
1✔
51
          @command_methods ||= Hash.new
2✔
52
          raise "Received command is not in PascalCase form: #{name.to_s}" unless StringUtils.is_pascal_case(name.to_s)
2✔
53
          method = StringUtils.underscore(name.to_s)
2✔
54
          @command_methods[name] = method
2✔
55

56
          define_method(method, &blk)
2✔
57
        end
58

59
        def is_command_noop?(command_name, deployment_spec)
1✔
60
          deployment_spec = InstanceAgent::Plugins::CodeDeployPlugin::DeploymentSpecification.parse(deployment_spec)
×
61

62
          # DownloadBundle and Install are never noops.
63
          return false if command_name == "Install" || command_name == "DownloadBundle"
×
64
          return true if @hook_mapping[command_name].nil?
×
65

66
          @hook_mapping[command_name].each do |lifecycle_event|
×
67
            # Although we're not executing any commands here, the HookExecutor handles
68
            # selecting the correct version of the appspec (last successful or current deployment) for us.
69
            hook_executor = create_hook_executor(lifecycle_event, deployment_spec)
×
70

71
            is_noop = hook_executor.is_noop?
×
72
            if is_noop
×
73
              log(:info, "Lifecycle event #{lifecycle_event} is a noop")
×
74
            end
75
            return false unless is_noop
×
76
          end
77

78
          log(:info, "Noop check completed for command #{command_name}, all lifecycle events are noops.")
×
79
          return true
×
80
        end
81

82
        def total_timeout_for_all_lifecycle_events(command_name, deployment_spec)
1✔
83
          parsed_spec = InstanceAgent::Plugins::CodeDeployPlugin::DeploymentSpecification.parse(deployment_spec)
×
84
          timeout_sums = ((@hook_mapping || {command_name => []})[command_name] || []).map do |lifecycle_event|
×
85
            create_hook_executor(lifecycle_event, parsed_spec).total_timeout_for_all_scripts
×
86
          end
87

88
          total_timeout = nil
×
89
          if timeout_sums.empty?
×
90
            log(:info, "Command #{command_name} has no script timeouts specified in appspec.")
×
91
          # If any lifecycle events' scripts don't specify a timeout, don't set a value.
92
          # The default will be the maximum at the server.
93
          elsif timeout_sums.include?(nil)
×
94
            log(:info, "Command #{command_name} has at least one script that does not specify a timeout. " +
×
95
              "No timeout override will be sent.")
96
          else
97
            total_timeout = timeout_sums.reduce(0) {|running_sum, item| running_sum + item}
×
98
            log(:info, "Command #{command_name} has total script timeout #{total_timeout} in appspec.")
×
99
          end
100

101
          total_timeout
×
102
        end
103

104
        def execute_command(command, deployment_specification)
1✔
105
          method_name = command_method(command.command_name)
×
106
          log(:debug, "Command #{command.command_name} maps to method #{method_name}")
×
107

108
          deployment_specification = InstanceAgent::Plugins::CodeDeployPlugin::DeploymentSpecification.parse(deployment_specification)
×
109
          log(:debug, "Successfully parsed the deployment spec")
×
110

111
          log(:debug, "Creating deployment root directory #{deployment_root_dir(deployment_specification)}")
×
112
          FileUtils.mkdir_p(deployment_root_dir(deployment_specification))
×
113
          raise "Error creating deployment root directory #{deployment_root_dir(deployment_specification)}" if !File.directory?(deployment_root_dir(deployment_specification))
×
114

115
          send(method_name, command, deployment_specification)
×
116
        end
117

118
        def command_method(command_name)
1✔
119
          raise InvalidCommandNameFailure.new("Unsupported command type: #{command_name}.") unless self.class.command_methods.has_key?(command_name)
×
120
          self.class.command_methods[command_name]
×
121
        end
122

123
        command "DownloadBundle" do |cmd, deployment_spec|
1✔
124
          cleanup_old_archives(deployment_spec)
×
125
          log(:debug, "Executing DownloadBundle command for execution #{cmd.deployment_execution_id}")
×
126

127
          case deployment_spec.revision_source
×
128
          when 'S3'
129
            download_from_s3(
×
130
            deployment_spec,
131
            deployment_spec.bucket,
132
            deployment_spec.key,
133
            deployment_spec.version,
134
            deployment_spec.etag)
135
          when 'GitHub'
136
            download_from_github(
×
137
            deployment_spec,
138
            deployment_spec.external_account,
139
            deployment_spec.repository,
140
            deployment_spec.commit_id,
141
            deployment_spec.anonymous,
142
            deployment_spec.external_auth_token)
143
          when 'Local File'
144
            handle_local_file(
×
145
              deployment_spec,
146
              deployment_spec.local_location)
147
          when 'Local Directory'
148
            handle_local_directory(
×
149
              deployment_spec,
150
              deployment_spec.local_location)
151
          else
152
            # This should never happen since this is checked during creation of the deployment_spec object.
153
            raise "Unknown revision type '#{deployment_spec.revision_source}'"
×
154
          end
155

156
          if deployment_spec.bundle_type != 'directory'
×
157
            FileUtils.rm_rf(File.join(deployment_root_dir(deployment_spec), 'deployment-archive'))
×
158
            bundle_file = artifact_bundle(deployment_spec)
×
159

160
            unpack_bundle(cmd, bundle_file, deployment_spec)
×
161
          end
162

163
          FileUtils.mkdir_p(deployment_instructions_dir)
×
164
          log(:debug, "Instructions directory created at #{deployment_instructions_dir}")
×
165
          update_most_recent_install(deployment_spec)
×
166
          nil
×
167
        end
168

169
        command "Install" do |cmd, deployment_spec|
1✔
170
          log(:debug, "Executing Install command for execution #{cmd.deployment_execution_id}")
×
171

172
          if !File.directory?(deployment_instructions_dir)
×
173
            FileUtils.mkdir_p(deployment_instructions_dir)
×
174
            log(:debug, "Instructions directory created at #{deployment_instructions_dir}")
×
175
          end
176

177
          installer = InstanceAgent::Plugins::CodeDeployPlugin::Installer.new(:deployment_instructions_dir => deployment_instructions_dir,
×
178
          :deployment_archive_dir => archive_root_dir(deployment_spec),
179
          :file_exists_behavior => deployment_spec.file_exists_behavior)
180

181
          log(:debug, "Installing revision #{deployment_spec.revision} in "+
×
182
          "instance group #{deployment_spec.deployment_group_id}")
183
          installer.install(deployment_spec.deployment_group_id, default_app_spec(deployment_spec))
×
184
          update_last_successful_install(deployment_spec)
×
185
          nil
×
186
        end
187

188
        def map
1✔
189
          @hook_mapping.each_pair do |command, lifecycle_events|
×
190
            InstanceAgent::Plugins::CodeDeployPlugin::CommandExecutor.command command do |cmd, deployment_spec|
×
191
              #run the scripts
192
              script_log = InstanceAgent::Plugins::CodeDeployPlugin::ScriptLog.new
×
193
              lifecycle_events.each do |lifecycle_event|
×
194
                hook_command = create_hook_executor(lifecycle_event, deployment_spec)
×
195
                script_log.concat_log(hook_command.execute)
×
196
              end
197
              script_log.log
×
198
            end
199
          end
200
        end
201

202
        private
1✔
203
        def deployment_root_dir(deployment_spec)
1✔
204
          File.join(ProcessManager::Config.config[:root_dir], deployment_spec.deployment_group_id, deployment_spec.deployment_id)
×
205
        end
206

207
        private
1✔
208
        def deployment_instructions_dir()
1✔
209
          File.join(ProcessManager::Config.config[:root_dir], 'deployment-instructions')
×
210
        end
211

212
        private
1✔
213
        def archive_root_dir(deployment_spec)
1✔
214
          File.join(deployment_root_dir(deployment_spec), 'deployment-archive')
×
215
        end
216

217
        private
1✔
218
        def last_successful_deployment_dir(deployment_group)
1✔
219
          last_successful_install_file_location = last_successful_install_file_path(deployment_group)
×
220
          return unless File.exist? last_successful_install_file_location
×
221
          File.open last_successful_install_file_location do |f|
×
222
            return f.read.chomp
×
223
          end
224
        end
225

226
        private
1✔
227
        def most_recent_deployment_dir(deployment_group)
1✔
228
          most_recent_install_file_location = most_recent_install_file_path(deployment_group)
×
229
          return unless File.exist? most_recent_install_file_location
×
230
          File.open most_recent_install_file_location do |f|
×
231
            return f.read.chomp
×
232
          end
233
        end
234

235
        private
1✔
236
        def create_hook_executor(lifecycle_event, deployment_spec)
1✔
237
          HookExecutor.new(:lifecycle_event => lifecycle_event,
×
238
            :application_name => deployment_spec.application_name,
239
            :deployment_id => deployment_spec.deployment_id,
240
            :deployment_group_name => deployment_spec.deployment_group_name,
241
            :deployment_group_id => deployment_spec.deployment_group_id,
242
            :deployment_creator => deployment_spec.deployment_creator,
243
            :deployment_type => deployment_spec.deployment_type,
244
            :deployment_root_dir => deployment_root_dir(deployment_spec),
245
            :last_successful_deployment_dir => last_successful_deployment_dir(deployment_spec.deployment_group_id),
246
            :most_recent_deployment_dir => most_recent_deployment_dir(deployment_spec.deployment_group_id),
247
            :app_spec_path => deployment_spec.app_spec_path,
248
            :revision_envs => get_revision_envs(deployment_spec))
249
        end
250

251
        private
1✔
252
        def get_revision_envs(deployment_spec)
1✔
253
          case deployment_spec.revision_source
×
254
          when 'S3'
255
            return get_s3_envs(deployment_spec)
×
256
          when 'GitHub'
257
            return get_github_envs(deployment_spec)
×
258
          when 'Local File', 'Local Directory'
259
            return {}
×
260
          else
261
            raise "Unknown revision type '#{deployment_spec.revision_source}'"
×
262
          end
263
        end
264

265
        private
1✔
266
        def get_github_envs(deployment_spec)
1✔
267
          # TODO(CDAGENT-387): expose the repository name and account, but we'll likely need to go through AppSec before doing so.
268
          return {
269
            "BUNDLE_COMMIT" => deployment_spec.commit_id
×
270
          }
271
        end
272

273
        private
1✔
274
        def get_s3_envs(deployment_spec)
1✔
275
          return {
276
            "BUNDLE_BUCKET" => deployment_spec.bucket,
×
277
            "BUNDLE_KEY" => deployment_spec.key,
278
            "BUNDLE_VERSION" => deployment_spec.version,
279
            "BUNDLE_ETAG" => deployment_spec.etag
280
          }
281
        end
282

283
        private
1✔
284
        def default_app_spec(deployment_spec)
1✔
285
          app_spec_location = app_spec_real_path(deployment_spec)
×
286
          validate_app_spec_hooks(app_spec_location, deployment_spec.all_possible_lifecycle_events)
×
287
        end
288

289
        private
1✔
290
        def validate_app_spec_hooks(app_spec_location, all_possible_lifecycle_events)
1✔
291
          app_spec = ApplicationSpecification::ApplicationSpecification.parse(File.read(app_spec_location))
×
292
          app_spec_filename = File.basename(app_spec_location)
×
293
          unless all_possible_lifecycle_events.nil?
×
294
            app_spec_hooks_plus_hooks_from_mapping = app_spec.hooks.keys.to_set.merge(@hook_mapping.keys).to_a
×
295
            unless app_spec_hooks_plus_hooks_from_mapping.to_set.subset?(all_possible_lifecycle_events.to_set)
×
296
              unknown_lifecycle_events = app_spec_hooks_plus_hooks_from_mapping - all_possible_lifecycle_events
×
297
              raise ArgumentError.new("#{app_spec_filename} file contains unknown lifecycle events: #{unknown_lifecycle_events}")
×
298
            end
299

300
            app_spec_hooks_plus_hooks_from_default_mapping = app_spec.hooks.keys.to_set.merge(InstanceAgent::Plugins::CodeDeployPlugin::CommandPoller::DEFAULT_HOOK_MAPPING.keys).to_a
×
301
            custom_hooks_not_found_in_appspec = custom_lifecycle_events(all_possible_lifecycle_events) - app_spec_hooks_plus_hooks_from_default_mapping
×
302
            unless (custom_hooks_not_found_in_appspec).empty?
×
303
              raise ArgumentError.new("You specified a lifecycle event which is not a default one and doesn't exist in your #{app_spec_filename} file: #{custom_hooks_not_found_in_appspec.join(',')}")
×
304
            end
305
          end
306

307
          app_spec
×
308
        end
309

310
        def custom_lifecycle_events(all_possible_lifecycle_events)
1✔
311
          all_possible_lifecycle_events - AWS::CodeDeploy::Local::Deployer::DEFAULT_ORDERED_LIFECYCLE_EVENTS
×
312
        end
313

314
        private
1✔
315
        def last_successful_install_file_path(deployment_group)
1✔
316
          File.join(deployment_instructions_dir, "#{deployment_group}_last_successful_install")
×
317
        end
318

319
        private
1✔
320
        def most_recent_install_file_path(deployment_group)
1✔
321
          File.join(deployment_instructions_dir, "#{deployment_group}_most_recent_install")
×
322
        end
323

324
        private
1✔
325
        def download_from_s3(deployment_spec, bucket, key, version, etag)
1✔
326
          log(:info, "Downloading artifact bundle from bucket '#{bucket}' and key '#{key}', version '#{version}', etag '#{etag}'")
×
327
          options = s3_options()
×
328
          s3 = Aws::S3::Client.new(options)
×
329

330
          File.open(artifact_bundle(deployment_spec), 'wb') do |file|
×
331

332
          begin
333
            if !version.nil?
×
334
              object = s3.get_object({:bucket => bucket, :key => key, :version_id => version}, :target => file)
×
335
            else
336
              object = s3.get_object({:bucket => bucket, :key => key}, :target => file)
×
337
            end
338
          rescue Seahorse::Client::NetworkingError => e
339
            if e.message.include? "unable to connect to"
×
340
              if InstanceAgent::Config.config[:use_fips_mode]
×
341
                raise $!, "#{$!}. Check that Fips exists in #{options[:region]}. Or, try using s3 endpoint override.", $!.backtrace
×
342
              else
343
                raise $!, "#{$!}. Try using s3 endpoint override.", $!.backtrace
×
344
              end
345
            else
346
              raise
×
347
            end
348
          end
349

350
            if(!etag.nil? && !(etag.gsub(/"/,'').eql? object.etag.gsub(/"/,'')))
×
351
              msg = "Expected deployment artifact bundle etag #{etag} but was actually #{object.etag}"
×
352
              log(:error, msg)
×
353
              raise RuntimeError, msg
×
354
            end
355
          end
356
          log(:info, "Download complete from bucket #{bucket} and key #{key}")
×
357
        end
358

359
        public
1✔
360
        def s3_options
1✔
361
          options = {}
×
362
          options[:ssl_ca_directory] = ENV['AWS_SSL_CA_DIRECTORY']
×
363
          options[:signature_version] = 'v4'
×
364

365
          region = ENV['AWS_REGION'] || InstanceMetadata.region
×
366
          options[:region] = region
×
367

368
          if !InstanceAgent::Config.config[:s3_endpoint_override].to_s.empty?
×
369
            ProcessManager::Log.debug("using s3 override endpoint #{InstanceAgent::Config.config[:s3_endpoint_override]}")
×
370
            options[:endpoint] = URI(InstanceAgent::Config.config[:s3_endpoint_override])
×
371
          elsif InstanceAgent::Config.config[:use_fips_mode]
×
372
            ProcessManager::Log.debug("using fips endpoint")
×
373
            # There was a recent change to S3 client to decompose the region and use a FIPS endpoint is "fips-" is appended
374
            # to the region. However, this is such a recent change that we cannot rely on the latest version of the SDK to be loaded.
375
            # For now, the endpoint will be set directly if FIPS is active but can switch to the S3 method once we have broader support.
376
            # options[:region] = "fips-#{region}"
377
            options[:endpoint] = "https://s3-fips.#{region}.amazonaws.com"
×
378
          end
379
          proxy_uri = nil
×
380
          if InstanceAgent::Config.config[:proxy_uri]
×
381
            proxy_uri = URI(InstanceAgent::Config.config[:proxy_uri])
×
382
          end
383
          options[:http_proxy] = proxy_uri
×
384

385
          if InstanceAgent::Config.config[:log_aws_wire]
×
386
            # wire logs might be huge; customers should be careful about turning them on
387
            # allow 1GB of old wire logs in 64MB chunks
388
            options[:logger] = Logger.new(
×
389
                File.join(InstanceAgent::Config.config[:log_dir], "#{InstanceAgent::Config.config[:program_name]}.aws_wire.log"),
390
                16,
391
                64 * 1024 * 1024)
392
            options[:http_wire_trace] = true
×
393
          end
394

395
          options
×
396
        end
397

398
        private
1✔
399
        def download_from_github(deployment_spec, account, repo, commit, anonymous, token)
1✔
400

401
          retries = 0
×
402
          errors = []
×
403

404
          unless (deployment_spec.bundle_type)
×
405
            if InstanceAgent::Platform.util.supported_oses.include? 'windows'
×
406
              deployment_spec.bundle_type = 'zip'
×
407
            else
408
              deployment_spec.bundle_type = 'tar'
×
409
            end
410
          end
411

412
          if deployment_spec.bundle_type == 'zip'
×
413
            format = 'zipball'
×
414
          elsif deployment_spec.bundle_type == 'tar'
×
415
            format = 'tarball'
×
416
          else
417
            raise ArgumentError.new("GitHub revision specified with bundle_type other than zip or tar [bundle_type=#{deployment_spec.bundle_type}")
×
418
          end
419

420
          uri = URI.parse("https://api.github.com/repos/#{account}/#{repo}/#{format}/#{commit}")
×
421
          options = {:ssl_verify_mode => OpenSSL::SSL::VERIFY_PEER, :redirect => true, :ssl_ca_cert => ENV['AWS_SSL_CA_DIRECTORY']}
×
422

423
          if anonymous
×
424
            log(:debug, "Anonymous GitHub repository download requested.")
×
425
          else
426
            log(:debug, "Authenticated GitHub repository download requested.")
×
427
            options.update({'Authorization' => "token #{token}"})
×
428
          end
429

430
          begin
431
            # stream bundle file to disk
432
            log(:info, "Requesting URL: '#{uri.to_s}'")
×
433
            File.open(artifact_bundle(deployment_spec), 'w+b') do |file|
×
434
              uri.open(options) do |github|
×
435
                log(:debug, "GitHub response: '#{github.meta.to_s}'")
×
436

437
                while (buffer = github.read(8 * 1024 * 1024))
×
438
                  file.write buffer
×
439
                end
440
              end
441
            end
442
          rescue OpenURI::HTTPError => e
×
443
            log(:error, "Could not download bundle at '#{uri.to_s}'. Server returned code #{e.io.status[0]} '#{e.io.status[1]}'")
×
444
            log(:debug, "Server returned error response body #{e.io.string}")
×
445
            errors << "#{e.io.status[0]} '#{e.io.status[1]}'"
×
446

447
            if retries < 3
×
448
              time_to_sleep = (10 * (3 ** retries)) # 10 sec, 30 sec, 90 sec
×
449
              log(:info, "Retrying download in #{time_to_sleep} seconds.")
×
450
              sleep(time_to_sleep)
×
451
              retries += 1
×
452
              retry
×
453
            else
454
              raise "Could not download bundle at '#{uri.to_s}' after #{retries} retries. Server returned codes: #{errors.join("; ")}."
×
455
            end
456
          end
457
        end
458

459
        private
1✔
460
        def handle_local_file(deployment_spec, local_location)
1✔
461
          # Symlink local file to the location where download is expected to go
462
          bundle_file = artifact_bundle(deployment_spec)
×
463
          log(:info, "Handle local file #{bundle_file}")
×
464
          begin
465
            File.symlink local_location, bundle_file
×
466
          rescue
467
            #Symlinking fails on windows, copying recursively instead
468
            FileUtils.cp_r local_location, bundle_file
×
469
          end
470
        end
471

472
        private
1✔
473
        def handle_local_directory(deployment_spec, local_location)
1✔
474
          # Copy local directory to the location where a file would have been extracted
475
          # We copy instead of symlinking in order to preserve revision history
476
          log(:info, "Handle local directory #{local_location}")
×
477
          FileUtils.cp_r local_location, archive_root_dir(deployment_spec)
×
478
        end
479

480
        private
1✔
481
        def unpack_bundle(cmd, bundle_file, deployment_spec)
1✔
482
          dst = File.join(deployment_root_dir(deployment_spec), 'deployment-archive')
×
483

484
          if "tar".eql? deployment_spec.bundle_type
×
485
            InstanceAgent::Platform.util.extract_tar(bundle_file, dst)
×
486
          elsif "tgz".eql? deployment_spec.bundle_type
×
487
            InstanceAgent::Platform.util.extract_tgz(bundle_file, dst)
×
488
          elsif "zip".eql? deployment_spec.bundle_type
×
489
            begin
490
              InstanceAgent::Platform.util.extract_zip(bundle_file, dst)
×
491
            rescue Exception => e
492
              if e.message == "Error extracting zip archive: 50"
×
493
                FileUtils.remove_dir(dst)
×
494
                # http://infozip.sourceforge.net/FAQ.html#error-codes
495
                msg = "The disk is (or was) full during extraction."
×
496
                log(:warn, msg)
×
497
                raise msg
×
498
              end
499
              log(:warn, "#{e.message}, with default system unzip util. Hence falling back to ruby unzip to mitigate any partially unzipped or skipped zip files.")
×
500
              Zip::File.open(bundle_file) do |zipfile|
×
501
                zipfile.each do |f|
×
502
                  file_dst = File.join(dst, f.name)
×
503
                  FileUtils.mkdir_p(File.dirname(file_dst))
×
504
                  zipfile.extract(f, file_dst) { true }
×
505
                end
506
              end
507
            end
508
          else
509
            InstanceAgent::Platform.util.extract_tar(bundle_file, dst)
×
510
          end
511

512
          archive_root_files = Dir.entries(dst)
×
513
          archive_root_files.delete_if { |name| name == '.' || name == '..' }
×
514

515
          # If the top level of the archive is a directory that contains an appspec,
516
          # strip that before giving up
517
          if ((archive_root_files.size == 1) &&
×
518
              File.directory?(File.join(dst, archive_root_files[0])) &&
519
              Dir.entries(File.join(dst, archive_root_files[0])).grep(/appspec/i).any?)
520
            log(:info, "Stripping leading directory from archive bundle contents.")
×
521
            # Move the unpacked files to a temporary location
522
            tmp_dst = File.join(deployment_root_dir(deployment_spec), 'deployment-archive-temp')
×
523
            FileUtils.rm_rf(tmp_dst)
×
524
            FileUtils.mv(dst, tmp_dst)
×
525

526
            # Move the top level directory to the intended location
527
            nested_archive_root = File.join(tmp_dst, archive_root_files[0])
×
528
            log(:debug, "Actual archive root at #{nested_archive_root}. Moving to #{dst}")
×
529
            FileUtils.mv(nested_archive_root, dst)
×
530
            FileUtils.rmdir(tmp_dst)
×
531

532
            log(:debug, Dir.entries(dst).join("; "))
×
533
          end
534
        end
535

536
        private
1✔
537
        def update_last_successful_install(deployment_spec)
1✔
538
          File.open(last_successful_install_file_path(deployment_spec.deployment_group_id), 'w+') do |f|
×
539
            f.write deployment_root_dir(deployment_spec)
×
540
          end
541
        end
542

543
        private
1✔
544
        def update_most_recent_install(deployment_spec)
1✔
545
          File.open(most_recent_install_file_path(deployment_spec.deployment_group_id), 'w+') do |f|
×
546
            f.write deployment_root_dir(deployment_spec)
×
547
          end
548
        end
549

550
        private
1✔
551
        def cleanup_old_archives(deployment_spec)
1✔
552
          deployment_group = deployment_spec.deployment_group_id
×
553
          deployment_archives = Dir.entries(File.join(ProcessManager::Config.config[:root_dir], deployment_group))
×
554
          # remove . and ..
555
          deployment_archives.delete(".")
×
556
          deployment_archives.delete("..")
×
557

558
          full_path_deployment_archives = deployment_archives.map{ |f| File.join(ProcessManager::Config.config[:root_dir], deployment_group, f)}
×
559
          full_path_deployment_archives.delete(deployment_root_dir(deployment_spec))
×
560

561
          extra = full_path_deployment_archives.size - @archives_to_retain + 1
×
562
          return unless extra > 0
×
563

564
          # Never remove the last successful deployment
565
          last_success = last_successful_deployment_dir(deployment_group)
×
566
          full_path_deployment_archives.delete(last_success)
×
567

568
          # Sort oldest -> newest, take first `extra` elements
569
          oldest_extra = full_path_deployment_archives.sort_by{ |f| File.mtime(f) }.take(extra)
×
570

571
          # Absolute path takes care of relative root directories
572
          directories = oldest_extra.map{ |f| File.absolute_path(f) }
×
573
          log(:debug, "Delete Files #{directories}")
×
574
          InstanceAgent::Platform.util.delete_dirs_command(directories)
×
575

576
        end
577

578
        private
1✔
579
        def artifact_bundle(deployment_spec)
1✔
580
          File.join(deployment_root_dir(deployment_spec), 'bundle.tar')
×
581
        end
582

583
        private
1✔
584
        def app_spec_path
1✔
585
          'appspec.yml'
×
586
        end
587

588
        # Checks for existence the possible extensions of the app_spec_path (.yml and .yaml)
589
        private
1✔
590
        def app_spec_real_path(deployment_spec)
1✔
591
          app_spec_param_location = File.join(archive_root_dir(deployment_spec), deployment_spec.app_spec_path)
×
592
          app_spec_yaml_location = File.join(archive_root_dir(deployment_spec), "appspec.yaml")
×
593
          app_spec_yml_location = File.join(archive_root_dir(deployment_spec), "appspec.yml")
×
594
          if File.exist? app_spec_param_location
×
595
            log(:debug, "Using appspec file #{app_spec_param_location}")
×
596
            app_spec_param_location
×
597
          elsif File.exist? app_spec_yaml_location
×
598
            log(:debug, "Using appspec file #{app_spec_yaml_location}")
×
599
            app_spec_yaml_location
×
600
          else
601
            log(:debug, "Using appspec file #{app_spec_yml_location}")
×
602
            app_spec_yml_location
×
603
          end
604
        end
605

606
        private
1✔
607
        def description
1✔
608
          self.class.to_s
×
609
        end
610

611
        private
1✔
612
        def log(severity, message)
1✔
613
          raise ArgumentError, "Unknown severity #{severity.inspect}" unless InstanceAgent::Log::SEVERITIES.include?(severity.to_s)
×
614
          InstanceAgent::Log.send(severity.to_sym, "#{description}: #{message}")
×
615
        end
616
      end
617
    end
618
  end
619
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