Skip to content

Fixes #182 Allow overriding appspec path + fallback to appspec.yml and appspec.yaml #265

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions lib/aws/codedeploy/local/cli_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@ def validate(args)
end

if (type == 'directory' && (uri.scheme != 'https' && uri.scheme != 's3' && File.directory?(location)))
unless File.exists? "#{location}/appspec.yml"
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")
appspec_filename = args['--appspec-filename']
if !appspec_filename.nil? && !File.exists?("#{location}/#{appspec_filename}")
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")
end
if !File.exists?("#{location}/appspec.yml") && !File.exists?("#{location}/appspec.yaml")
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")
end
end

Expand All @@ -60,7 +64,7 @@ def validate(args)
end

def any_new_revision_event_or_install_before_download_bundle(events)
events_using_new_revision.push('Install').any? do |event_not_allowed_before_download_bundle|
events_using_new_revision.push('Install').any? do |event_not_allowed_before_download_bundle|
events.take_while{|e| e != 'DownloadBundle'}.include? event_not_allowed_before_download_bundle
end
end
Expand Down
5 changes: 3 additions & 2 deletions lib/aws/codedeploy/local/deployer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def execute_events(args)
deployment_group_id = args['--deployment-group']
events = self.class.events_from_comma_separated_list(args['--events'])

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'])
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'])

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

def build_spec(location, bundle_type, deployment_group_id, file_exists_behavior, all_possible_lifecycle_events, deployment_group_name, application_name)
def build_spec(location, bundle_type, deployment_group_id, file_exists_behavior, all_possible_lifecycle_events, deployment_group_name, application_name, appspec_filename)
@deployment_id = self.class.random_deployment_id
puts "Starting to execute deployment from within folder #{deployment_folder(deployment_group_id, @deployment_id)}"
OpenStruct.new({
:format => "TEXT/JSON",
:payload => {
"ApplicationId" => location,
"ApplicationName" => application_name || location,
"AppSpecFilename" => appspec_filename || "appspec.yml",
"DeploymentGroupId" => deployment_group_id,
"DeploymentGroupName" => deployment_group_name || "LocalFleet",
"DeploymentId" => @deployment_id,
Expand Down
57 changes: 38 additions & 19 deletions lib/instance_agent/plugins/codedeploy/command_executor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def map
:deployment_root_dir => deployment_root_dir(deployment_spec),
:last_successful_deployment_dir => last_successful_deployment_dir(deployment_spec.deployment_group_id),
:most_recent_deployment_dir => most_recent_deployment_dir(deployment_spec.deployment_group_id),
:app_spec_path => app_spec_path)
:app_spec_path => deployment_spec.app_spec_path)
script_log.concat_log(hook_command.execute)
end
script_log.log
Expand Down Expand Up @@ -185,7 +185,7 @@ def last_successful_deployment_dir(deployment_group)
return unless File.exist? last_successful_install_file_location
File.open last_successful_install_file_location do |f|
return f.read.chomp
end
end
end

private
Expand All @@ -194,29 +194,30 @@ def most_recent_deployment_dir(deployment_group)
return unless File.exist? most_recent_install_file_location
File.open most_recent_install_file_location do |f|
return f.read.chomp
end
end
end

private
def default_app_spec(deployment_spec)
default_app_spec_location = File.join(archive_root_dir(deployment_spec), app_spec_path)
log(:debug, "Checking for app spec in #{default_app_spec_location}")
validate_app_spec_hooks(ApplicationSpecification::ApplicationSpecification.parse(File.read(default_app_spec_location)), deployment_spec.all_possible_lifecycle_events)
app_spec_location = app_spec_real_path(deployment_spec)
validate_app_spec_hooks(app_spec_location, deployment_spec.all_possible_lifecycle_events)
end

private
def validate_app_spec_hooks(app_spec, all_possible_lifecycle_events)
def validate_app_spec_hooks(app_spec_location, all_possible_lifecycle_events)
app_spec = ApplicationSpecification::ApplicationSpecification.parse(File.read(app_spec_location))
app_spec_filename = File.basename(app_spec_location)
unless all_possible_lifecycle_events.nil?
app_spec_hooks_plus_hooks_from_mapping = app_spec.hooks.keys.to_set.merge(@hook_mapping.keys).to_a
unless app_spec_hooks_plus_hooks_from_mapping.to_set.subset?(all_possible_lifecycle_events.to_set)
unknown_lifecycle_events = app_spec_hooks_plus_hooks_from_mapping - all_possible_lifecycle_events
raise ArgumentError.new("appspec.yml file contains unknown lifecycle events: #{unknown_lifecycle_events}")
raise ArgumentError.new("#{app_spec_filename} file contains unknown lifecycle events: #{unknown_lifecycle_events}")
end

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
custom_hooks_not_found_in_appspec = custom_lifecycle_events(all_possible_lifecycle_events) - app_spec_hooks_plus_hooks_from_default_mapping
unless (custom_hooks_not_found_in_appspec).empty?
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(',')}")
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(',')}")
end
end

Expand Down Expand Up @@ -297,7 +298,7 @@ def s3_options
proxy_uri = nil
if InstanceAgent::Config.config[:proxy_uri]
proxy_uri = URI(InstanceAgent::Config.config[:proxy_uri])
end
end
options[:http_proxy] = proxy_uri

if InstanceAgent::Config.config[:log_aws_wire]
Expand All @@ -309,10 +310,10 @@ def s3_options
64 * 1024 * 1024)
options[:http_wire_trace] = true
end
options
end

options
end

private
def download_from_github(deployment_spec, account, repo, commit, anonymous, token)

Expand Down Expand Up @@ -425,7 +426,7 @@ def unpack_bundle(cmd, bundle_file, deployment_spec)

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

log(:debug, Dir.entries(dst).join("; "))
log(:debug, Dir.entries(dst).join("; "))
end
end

Expand All @@ -456,7 +457,7 @@ def update_most_recent_install(deployment_spec)
File.open(most_recent_install_file_path(deployment_spec.deployment_group_id), 'w+') do |f|
f.write deployment_root_dir(deployment_spec)
end
end
end

private
def cleanup_old_archives(deployment_spec)
Expand All @@ -468,7 +469,7 @@ def cleanup_old_archives(deployment_spec)

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

extra = full_path_deployment_archives.size - @archives_to_retain + 1
return unless extra > 0

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

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

end
Expand All @@ -496,6 +497,24 @@ def app_spec_path
'appspec.yml'
end

# Checks for existence the possible extensions of the app_spec_path (.yml and .yaml)
private
def app_spec_real_path(deployment_spec)
app_spec_param_location = File.join(archive_root_dir(deployment_spec), deployment_spec.app_spec_path)
app_spec_yaml_location = File.join(archive_root_dir(deployment_spec), "appspec.yaml")
app_spec_yml_location = File.join(archive_root_dir(deployment_spec), "appspec.yml")
if File.exist? app_spec_param_location
log(:debug, "Using appspec file #{app_spec_param_location}")
app_spec_param_location
elsif File.exist? app_spec_yaml_location
log(:debug, "Using appspec file #{app_spec_yaml_location}")
app_spec_yaml_location
else
log(:debug, "Using appspec file #{app_spec_yml_location}")
app_spec_yml_location
end
end

private
def description
self.class.to_s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class DeploymentSpecification
attr_accessor :external_account, :repository, :commit_id, :anonymous, :external_auth_token
attr_accessor :file_exists_behavior
attr_accessor :local_location, :all_possible_lifecycle_events
attr_accessor :app_spec_path
class << self
attr_accessor :cert_store
end
Expand Down Expand Up @@ -47,6 +48,12 @@ def initialize(data)
@deployment_creator = data["DeploymentCreator"] || "user"
@deployment_type = data["DeploymentType"] || "IN_PLACE"

if property_set?(data, "AppSpecFilename")
@app_spec_path = data["AppSpecFilename"]
else
@app_spec_path = "appspec.yml"
end

raise 'Must specify a revison' unless data["Revision"]
@revision_source = data["Revision"]["RevisionType"]
raise 'Must specify a revision source' unless @revision_source
Expand Down Expand Up @@ -99,7 +106,7 @@ def initialize(data)
end
# Decrypts the envelope /deployment specs
# Params:
# envelope: deployment specification thats to be cheked and decrypted
# envelope: deployment specification that's to be checked and decrypted
def self.parse(envelope)
raise 'Provided deployment spec was nil' if envelope.nil?

Expand Down
22 changes: 19 additions & 3 deletions spec/aws/codedeploy/local/cli_validator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
end
end

context 'when loction is directory but appspec is missing' do
context 'when location is directory but appspec is missing' do
let(:args) do
{"--bundle-location"=>FAKE_DIRECTORY,
"--type"=>'directory'}
Expand All @@ -96,7 +96,23 @@
allow(File).to receive(:exists?).with(FAKE_DIRECTORY).and_return(true)
allow(File).to receive(:directory?).with(FAKE_DIRECTORY).and_return(true)
expect(File).to receive(:exists?).with("#{FAKE_DIRECTORY}/appspec.yml").and_return(false)
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")
expect(File).to receive(:exists?).with("#{FAKE_DIRECTORY}/appspec.yaml").and_return(false)
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")
end
end

context 'when location is directory and --appspec-filename is specified (but not existing)' do
let(:args) do
{"--bundle-location"=>FAKE_DIRECTORY,
"--type"=>'directory',
"--appspec-filename"=>"appspec-override.yaml"}
end

it 'throws a ValidationError' do
allow(File).to receive(:exists?).with(FAKE_DIRECTORY).and_return(true)
allow(File).to receive(:directory?).with(FAKE_DIRECTORY).and_return(true)
expect(File).to receive(:exists?).with("#{FAKE_DIRECTORY}/appspec-override.yaml").and_return(false)
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")
end
end

Expand Down Expand Up @@ -225,7 +241,7 @@
allow(File).to receive(:directory?).with(FAKE_DIRECTORY).and_return(true)
expect(File).to receive(:exists?).with("#{FAKE_DIRECTORY}/appspec.yml").and_return(true)
expect(validator.validate(args)).to equal(args)
end
end
end
end
end
Loading