diff --git a/lib/pdk/module/convert.rb b/lib/pdk/module/convert.rb index ee56360f6..388fc1456 100644 --- a/lib/pdk/module/convert.rb +++ b/lib/pdk/module/convert.rb @@ -148,7 +148,7 @@ def stage_changes!(context = PDK.context) module_name = new_metadata.nil? ? 'new-module' : new_metadata.data['name'] metadata_for_render = new_metadata.nil? ? {} : new_metadata.data - template_dir.render_new_module(module_name, metadata_for_render) do |relative_file_path, file_content, file_status| + template_dir.render_new_module(module_name, metadata_for_render) do |relative_file_path, file_content, file_status, file_executable| absolute_file_path = File.join(module_dir, relative_file_path) case file_status when :unmanage @@ -156,12 +156,17 @@ def stage_changes!(context = PDK.context) when :delete update_manager.remove_file(absolute_file_path) when :init - update_manager.add_file(absolute_file_path, file_content) if convert? && !PDK::Util::Filesystem.exist?(absolute_file_path) + if convert? && !PDK::Util::Filesystem.exist?(absolute_file_path) + update_manager.add_file(absolute_file_path, file_content) + update_manager.make_file_executable(absolute_file_path) if file_executable + end when :manage if PDK::Util::Filesystem.exist?(absolute_file_path) update_manager.modify_file(absolute_file_path, file_content) + update_manager.make_file_executable(absolute_file_path) if file_executable && !PDK::Util::Filesystem.executable?(absolute_file_path) else update_manager.add_file(absolute_file_path, file_content) + update_manager.make_file_executable(absolute_file_path) if file_executable end end end diff --git a/lib/pdk/module/update_manager.rb b/lib/pdk/module/update_manager.rb index 521553480..cfa468240 100644 --- a/lib/pdk/module/update_manager.rb +++ b/lib/pdk/module/update_manager.rb @@ -11,6 +11,7 @@ def initialize @modified_files = Set.new @added_files = Set.new @removed_files = Set.new + @executable_files = Set.new @diff_cache = {} end @@ -37,6 +38,13 @@ def remove_file(path) @removed_files << path end + # Store a pending file execute mode change. + # + # @param path [String] The path to the file to be made executable. + def make_file_executable(path) + @executable_files << path + end + # Generate a summary of the changes that will be applied to the module. # # @raise (see #calculate_diffs) @@ -49,7 +57,8 @@ def changes { added: @added_files, removed: @removed_files.select { |f| PDK::Util::Filesystem.exist?(f) }, - modified: @diff_cache.compact + modified: @diff_cache.compact, + 'made executable': @executable_files } end @@ -60,7 +69,8 @@ def changes def changes? !changes[:added].empty? || !changes[:removed].empty? || - changes[:modified].any? { |_, value| !value.nil? } + changes[:modified].any? { |_, value| !value.nil? } || + !changes[:'made executable'].empty? end # Check if the update manager will change the specified file upon sync. @@ -72,13 +82,15 @@ def changes? def changed?(path) changes[:added].any? { |add| add[:path] == path } || changes[:removed].include?(path) || - changes[:modified].key?(path) + changes[:modified].key?(path) || + changes[:'made executable'].include?(path) end def clear! @modified_files.clear @added_files.clear @removed_files.clear + @executable_files.clear nil end @@ -100,6 +112,10 @@ def sync_changes! files_to_write.each do |file| write_file(file[:path], file[:content]) end + + @executable_files.each do |file| + update_execute_bits(file) + end end # Remove a file from disk. @@ -215,6 +231,14 @@ def unified_diff(path, old_content, new_content, lines_of_context = 3) output.join($INPUT_RECORD_SEPARATOR) end + + # Set the execute bits on a file + def update_execute_bits(path) + require 'pdk/util/filesystem' + + PDK.logger.debug(format("making '%{path}' executable", path: path)) + PDK::Util::Filesystem.make_executable(path) + end end end end diff --git a/lib/pdk/template/renderer/v1/legacy_template_dir.rb b/lib/pdk/template/renderer/v1/legacy_template_dir.rb index 12192ec01..e581126c2 100644 --- a/lib/pdk/template/renderer/v1/legacy_template_dir.rb +++ b/lib/pdk/template/renderer/v1/legacy_template_dir.rb @@ -56,7 +56,7 @@ def config_for(dest_path, sync_config_path = nil) @config = conf_defaults @config.deep_merge!(@sync_config, knockout_prefix: '---') unless @sync_config.nil? end - file_config = @config.fetch(:global, {}) + file_config = @config.fetch('common', {}).clone file_config['module_metadata'] = @module_metadata file_config.merge!(@config.fetch(dest_path, {})) unless dest_path.nil? file_config.merge!(@config).tap do |c| diff --git a/lib/pdk/template/renderer/v1/renderer.rb b/lib/pdk/template/renderer/v1/renderer.rb index a410b84ab..8351cb81a 100644 --- a/lib/pdk/template/renderer/v1/renderer.rb +++ b/lib/pdk/template/renderer/v1/renderer.rb @@ -95,7 +95,9 @@ def render_module(options = {}) end end - yield dest_path, dest_content, dest_status + dest_executable = config['manage_execute_permissions'] && PDK::Util::Filesystem.executable?(File.join(template_loc, template_file)) + + yield dest_path, dest_content, dest_status, dest_executable end end # :nocov: diff --git a/lib/pdk/util/filesystem.rb b/lib/pdk/util/filesystem.rb index 83b8d5771..55930de57 100644 --- a/lib/pdk/util/filesystem.rb +++ b/lib/pdk/util/filesystem.rb @@ -27,6 +27,11 @@ def read_file(file, nil_on_error: false, open_args: 'r') end module_function :read_file + def make_executable(file) + FileUtils.chmod('a+x', file) + end + module_function :make_executable + # :nocov: # These methods just wrap core Ruby functionality and # can be ignored for code coverage @@ -133,6 +138,11 @@ def mv(*args, **kwargs) end end module_function :mv + + def executable?(*args) + File.executable?(*args) + end + module_function :executable? # :nocov: end end diff --git a/spec/unit/pdk/module/convert_spec.rb b/spec/unit/pdk/module/convert_spec.rb index 98b31e811..d66e96e23 100644 --- a/spec/unit/pdk/module/convert_spec.rb +++ b/spec/unit/pdk/module/convert_spec.rb @@ -88,7 +88,7 @@ def module_path(relative_path) let(:update_manager) { instance_double(PDK::Module::UpdateManager, sync_changes!: true) } let(:template_dir) { instance_double(PDK::Template::TemplateDir, metadata: {}) } let(:metadata) { instance_double(PDK::Module::Metadata, data: {}) } - let(:template_files) { { path: 'a/path/to/file', content: 'file contents', status: :manage } } + let(:template_files) { { path: 'a/path/to/file', content: 'file contents', status: :manage, executable: false } } let(:added_files) { Set.new } let(:removed_files) { Set.new } let(:modified_files) { {} } @@ -100,7 +100,7 @@ def module_path(relative_path) allow(instance).to receive(:update_metadata).with(any_args).and_return(metadata) allow(PDK::Template).to receive(:with).with(anything, anything).and_yield(template_dir) allow(PDK::Util::Git).to receive(:repo?).with(anything).and_return(true) - allow(template_dir).to receive(:render_new_module).and_yield(template_files[:path], template_files[:content], template_files[:status]) + allow(template_dir).to receive(:render_new_module).and_yield(template_files[:path], template_files[:content], template_files[:status], template_files[:executable]) allow(update_manager).to receive(:changes).and_return(changes) allow(update_manager).to receive(:changed?).with('Gemfile').and_return(false) allow(update_manager).to receive(:unlink_file).with('Gemfile.lock')