Skip to content

Commit 6beb142

Browse files
authored
Merge pull request #265 from lc-nyovchev/issue-182
Fixes #182 Allow overriding appspec path + fallback to appspec.yml and appspec.yaml
2 parents baaa837 + aff5b11 commit 6beb142

File tree

8 files changed

+249
-41
lines changed

8 files changed

+249
-41
lines changed

lib/aws/codedeploy/local/cli_validator.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,12 @@ def validate(args)
4040
end
4141

4242
if (type == 'directory' && (uri.scheme != 'https' && uri.scheme != 's3' && File.directory?(location)))
43-
unless File.exists? "#{location}/appspec.yml"
44-
raise ValidationError.new("Expecting appspec file at location #{location}/appspec.yml but it is not found there. Please either run the CLI from within a directory containing the appspec.yml file or specify a bundle location containing an appspec.yml file in its root directory")
43+
appspec_filename = args['--appspec-filename']
44+
if !appspec_filename.nil? && !File.exists?("#{location}/#{appspec_filename}")
45+
raise ValidationError.new("Expecting appspec file at location #{location}/#{appspec_filename} but it is not found there. Please either run the CLI from within a directory containing the #{appspec_filename} file or specify a bundle location containing an #{appspec_filename} file in its root directory")
46+
end
47+
if !File.exists?("#{location}/appspec.yml") && !File.exists?("#{location}/appspec.yaml")
48+
raise ValidationError.new("Expecting appspec file at location #{location}/appspec.yml or #{location}/appspec.yaml but it is not found there. Please either run the CLI from within a directory containing the appspec.yml or appspec.yaml file or specify a bundle location containing an appspec.yml or appspec.yaml file in its root directory")
4549
end
4650
end
4751

@@ -60,7 +64,7 @@ def validate(args)
6064
end
6165

6266
def any_new_revision_event_or_install_before_download_bundle(events)
63-
events_using_new_revision.push('Install').any? do |event_not_allowed_before_download_bundle|
67+
events_using_new_revision.push('Install').any? do |event_not_allowed_before_download_bundle|
6468
events.take_while{|e| e != 'DownloadBundle'}.include? event_not_allowed_before_download_bundle
6569
end
6670
end

lib/aws/codedeploy/local/deployer.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def execute_events(args)
7676
deployment_group_id = args['--deployment-group']
7777
events = self.class.events_from_comma_separated_list(args['--events'])
7878

79-
spec = build_spec(args['--bundle-location'], args['--type'], deployment_group_id, args['--file-exists-behavior'], all_possible_lifecycle_events(events), args['--deployment-group-name'], args['--application-name'])
79+
spec = build_spec(args['--bundle-location'], args['--type'], deployment_group_id, args['--file-exists-behavior'], all_possible_lifecycle_events(events), args['--deployment-group-name'], args['--application-name'], args['--appspec-filename'])
8080

8181
command_executor = InstanceAgent::Plugins::CodeDeployPlugin::CommandExecutor.new(:hook_mapping => hook_mapping(events))
8282
all_lifecycle_events_to_execute = add_download_bundle_and_install_events(ordered_lifecycle_events(events))
@@ -126,14 +126,15 @@ def hook_mapping(events)
126126
Hash[all_events_plus_default_events_minus_required_events.map{|h|[h,[h]]}]
127127
end
128128

129-
def build_spec(location, bundle_type, deployment_group_id, file_exists_behavior, all_possible_lifecycle_events, deployment_group_name, application_name)
129+
def build_spec(location, bundle_type, deployment_group_id, file_exists_behavior, all_possible_lifecycle_events, deployment_group_name, application_name, appspec_filename)
130130
@deployment_id = self.class.random_deployment_id
131131
puts "Starting to execute deployment from within folder #{deployment_folder(deployment_group_id, @deployment_id)}"
132132
OpenStruct.new({
133133
:format => "TEXT/JSON",
134134
:payload => {
135135
"ApplicationId" => location,
136136
"ApplicationName" => application_name || location,
137+
"AppSpecFilename" => appspec_filename || "appspec.yml",
137138
"DeploymentGroupId" => deployment_group_id,
138139
"DeploymentGroupName" => deployment_group_name || "LocalFleet",
139140
"DeploymentId" => @deployment_id,

lib/instance_agent/plugins/codedeploy/command_executor.rb

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ def map
156156
:deployment_root_dir => deployment_root_dir(deployment_spec),
157157
:last_successful_deployment_dir => last_successful_deployment_dir(deployment_spec.deployment_group_id),
158158
:most_recent_deployment_dir => most_recent_deployment_dir(deployment_spec.deployment_group_id),
159-
:app_spec_path => app_spec_path)
159+
:app_spec_path => deployment_spec.app_spec_path)
160160
script_log.concat_log(hook_command.execute)
161161
end
162162
script_log.log
@@ -185,7 +185,7 @@ def last_successful_deployment_dir(deployment_group)
185185
return unless File.exist? last_successful_install_file_location
186186
File.open last_successful_install_file_location do |f|
187187
return f.read.chomp
188-
end
188+
end
189189
end
190190

191191
private
@@ -194,29 +194,30 @@ def most_recent_deployment_dir(deployment_group)
194194
return unless File.exist? most_recent_install_file_location
195195
File.open most_recent_install_file_location do |f|
196196
return f.read.chomp
197-
end
197+
end
198198
end
199199

200200
private
201201
def default_app_spec(deployment_spec)
202-
default_app_spec_location = File.join(archive_root_dir(deployment_spec), app_spec_path)
203-
log(:debug, "Checking for app spec in #{default_app_spec_location}")
204-
validate_app_spec_hooks(ApplicationSpecification::ApplicationSpecification.parse(File.read(default_app_spec_location)), deployment_spec.all_possible_lifecycle_events)
202+
app_spec_location = app_spec_real_path(deployment_spec)
203+
validate_app_spec_hooks(app_spec_location, deployment_spec.all_possible_lifecycle_events)
205204
end
206205

207206
private
208-
def validate_app_spec_hooks(app_spec, all_possible_lifecycle_events)
207+
def validate_app_spec_hooks(app_spec_location, all_possible_lifecycle_events)
208+
app_spec = ApplicationSpecification::ApplicationSpecification.parse(File.read(app_spec_location))
209+
app_spec_filename = File.basename(app_spec_location)
209210
unless all_possible_lifecycle_events.nil?
210211
app_spec_hooks_plus_hooks_from_mapping = app_spec.hooks.keys.to_set.merge(@hook_mapping.keys).to_a
211212
unless app_spec_hooks_plus_hooks_from_mapping.to_set.subset?(all_possible_lifecycle_events.to_set)
212213
unknown_lifecycle_events = app_spec_hooks_plus_hooks_from_mapping - all_possible_lifecycle_events
213-
raise ArgumentError.new("appspec.yml file contains unknown lifecycle events: #{unknown_lifecycle_events}")
214+
raise ArgumentError.new("#{app_spec_filename} file contains unknown lifecycle events: #{unknown_lifecycle_events}")
214215
end
215216

216217
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
217218
custom_hooks_not_found_in_appspec = custom_lifecycle_events(all_possible_lifecycle_events) - app_spec_hooks_plus_hooks_from_default_mapping
218219
unless (custom_hooks_not_found_in_appspec).empty?
219-
raise ArgumentError.new("You specified a lifecycle event which is not a default one and doesn't exist in your appspec.yml file: #{custom_hooks_not_found_in_appspec.join(',')}")
220+
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(',')}")
220221
end
221222
end
222223

@@ -297,7 +298,7 @@ def s3_options
297298
proxy_uri = nil
298299
if InstanceAgent::Config.config[:proxy_uri]
299300
proxy_uri = URI(InstanceAgent::Config.config[:proxy_uri])
300-
end
301+
end
301302
options[:http_proxy] = proxy_uri
302303

303304
if InstanceAgent::Config.config[:log_aws_wire]
@@ -309,10 +310,10 @@ def s3_options
309310
64 * 1024 * 1024)
310311
options[:http_wire_trace] = true
311312
end
312-
313-
options
314-
end
315-
313+
314+
options
315+
end
316+
316317
private
317318
def download_from_github(deployment_spec, account, repo, commit, anonymous, token)
318319

@@ -425,7 +426,7 @@ def unpack_bundle(cmd, bundle_file, deployment_spec)
425426

426427
# If the top level of the archive is a directory that contains an appspec,
427428
# strip that before giving up
428-
if ((archive_root_files.size == 1) &&
429+
if ((archive_root_files.size == 1) &&
429430
File.directory?(File.join(dst, archive_root_files[0])) &&
430431
Dir.entries(File.join(dst, archive_root_files[0])).grep(/appspec/i).any?)
431432
log(:info, "Stripping leading directory from archive bundle contents.")
@@ -440,7 +441,7 @@ def unpack_bundle(cmd, bundle_file, deployment_spec)
440441
FileUtils.mv(nested_archive_root, dst)
441442
FileUtils.rmdir(tmp_dst)
442443

443-
log(:debug, Dir.entries(dst).join("; "))
444+
log(:debug, Dir.entries(dst).join("; "))
444445
end
445446
end
446447

@@ -456,7 +457,7 @@ def update_most_recent_install(deployment_spec)
456457
File.open(most_recent_install_file_path(deployment_spec.deployment_group_id), 'w+') do |f|
457458
f.write deployment_root_dir(deployment_spec)
458459
end
459-
end
460+
end
460461

461462
private
462463
def cleanup_old_archives(deployment_spec)
@@ -468,7 +469,7 @@ def cleanup_old_archives(deployment_spec)
468469

469470
full_path_deployment_archives = deployment_archives.map{ |f| File.join(ProcessManager::Config.config[:root_dir], deployment_group, f)}
470471
full_path_deployment_archives.delete(deployment_root_dir(deployment_spec))
471-
472+
472473
extra = full_path_deployment_archives.size - @archives_to_retain + 1
473474
return unless extra > 0
474475

@@ -481,7 +482,7 @@ def cleanup_old_archives(deployment_spec)
481482

482483
# Absolute path takes care of relative root directories
483484
directories = oldest_extra.map{ |f| File.absolute_path(f) }
484-
log(:debug,"Delete Files #{directories}" )
485+
log(:debug, "Delete Files #{directories}")
485486
InstanceAgent::Platform.util.delete_dirs_command(directories)
486487

487488
end
@@ -496,6 +497,24 @@ def app_spec_path
496497
'appspec.yml'
497498
end
498499

500+
# Checks for existence the possible extensions of the app_spec_path (.yml and .yaml)
501+
private
502+
def app_spec_real_path(deployment_spec)
503+
app_spec_param_location = File.join(archive_root_dir(deployment_spec), deployment_spec.app_spec_path)
504+
app_spec_yaml_location = File.join(archive_root_dir(deployment_spec), "appspec.yaml")
505+
app_spec_yml_location = File.join(archive_root_dir(deployment_spec), "appspec.yml")
506+
if File.exist? app_spec_param_location
507+
log(:debug, "Using appspec file #{app_spec_param_location}")
508+
app_spec_param_location
509+
elsif File.exist? app_spec_yaml_location
510+
log(:debug, "Using appspec file #{app_spec_yaml_location}")
511+
app_spec_yaml_location
512+
else
513+
log(:debug, "Using appspec file #{app_spec_yml_location}")
514+
app_spec_yml_location
515+
end
516+
end
517+
499518
private
500519
def description
501520
self.class.to_s

lib/instance_agent/plugins/codedeploy/deployment_specification.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class DeploymentSpecification
1313
attr_accessor :external_account, :repository, :commit_id, :anonymous, :external_auth_token
1414
attr_accessor :file_exists_behavior
1515
attr_accessor :local_location, :all_possible_lifecycle_events
16+
attr_accessor :app_spec_path
1617
class << self
1718
attr_accessor :cert_store
1819
end
@@ -47,6 +48,12 @@ def initialize(data)
4748
@deployment_creator = data["DeploymentCreator"] || "user"
4849
@deployment_type = data["DeploymentType"] || "IN_PLACE"
4950

51+
if property_set?(data, "AppSpecFilename")
52+
@app_spec_path = data["AppSpecFilename"]
53+
else
54+
@app_spec_path = "appspec.yml"
55+
end
56+
5057
raise 'Must specify a revison' unless data["Revision"]
5158
@revision_source = data["Revision"]["RevisionType"]
5259
raise 'Must specify a revision source' unless @revision_source
@@ -99,7 +106,7 @@ def initialize(data)
99106
end
100107
# Decrypts the envelope /deployment specs
101108
# Params:
102-
# envelope: deployment specification thats to be cheked and decrypted
109+
# envelope: deployment specification that's to be checked and decrypted
103110
def self.parse(envelope)
104111
raise 'Provided deployment spec was nil' if envelope.nil?
105112

spec/aws/codedeploy/local/cli_validator_spec.rb

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
end
8787
end
8888

89-
context 'when loction is directory but appspec is missing' do
89+
context 'when location is directory but appspec is missing' do
9090
let(:args) do
9191
{"--bundle-location"=>FAKE_DIRECTORY,
9292
"--type"=>'directory'}
@@ -96,7 +96,23 @@
9696
allow(File).to receive(:exists?).with(FAKE_DIRECTORY).and_return(true)
9797
allow(File).to receive(:directory?).with(FAKE_DIRECTORY).and_return(true)
9898
expect(File).to receive(:exists?).with("#{FAKE_DIRECTORY}/appspec.yml").and_return(false)
99-
expect{validator.validate(args)}.to raise_error(AWS::CodeDeploy::Local::CLIValidator::ValidationError, "Expecting appspec file at location #{FAKE_DIRECTORY}/appspec.yml but it is not found there. Please either run the CLI from within a directory containing the appspec.yml file or specify a bundle location containing an appspec.yml file in its root directory")
99+
expect(File).to receive(:exists?).with("#{FAKE_DIRECTORY}/appspec.yaml").and_return(false)
100+
expect{validator.validate(args)}.to raise_error(AWS::CodeDeploy::Local::CLIValidator::ValidationError, "Expecting appspec file at location #{FAKE_DIRECTORY}/appspec.yml or #{FAKE_DIRECTORY}/appspec.yaml but it is not found there. Please either run the CLI from within a directory containing the appspec.yml or appspec.yaml file or specify a bundle location containing an appspec.yml or appspec.yaml file in its root directory")
101+
end
102+
end
103+
104+
context 'when location is directory and --appspec-filename is specified (but not existing)' do
105+
let(:args) do
106+
{"--bundle-location"=>FAKE_DIRECTORY,
107+
"--type"=>'directory',
108+
"--appspec-filename"=>"appspec-override.yaml"}
109+
end
110+
111+
it 'throws a ValidationError' do
112+
allow(File).to receive(:exists?).with(FAKE_DIRECTORY).and_return(true)
113+
allow(File).to receive(:directory?).with(FAKE_DIRECTORY).and_return(true)
114+
expect(File).to receive(:exists?).with("#{FAKE_DIRECTORY}/appspec-override.yaml").and_return(false)
115+
expect{validator.validate(args)}.to raise_error(AWS::CodeDeploy::Local::CLIValidator::ValidationError, "Expecting appspec file at location #{FAKE_DIRECTORY}/appspec-override.yaml but it is not found there. Please either run the CLI from within a directory containing the appspec-override.yaml file or specify a bundle location containing an appspec-override.yaml file in its root directory")
100116
end
101117
end
102118

@@ -225,7 +241,7 @@
225241
allow(File).to receive(:directory?).with(FAKE_DIRECTORY).and_return(true)
226242
expect(File).to receive(:exists?).with("#{FAKE_DIRECTORY}/appspec.yml").and_return(true)
227243
expect(validator.validate(args)).to equal(args)
228-
end
244+
end
229245
end
230246
end
231247
end

0 commit comments

Comments
 (0)