From deb1df82a34d8cca69bc190477839c41c46ab6ec Mon Sep 17 00:00:00 2001 From: Bernd Ahlers Date: Wed, 13 Jul 2022 23:09:19 +0200 Subject: [PATCH 1/4] Add "epoch" field to the recipe class Adjust the Package::Version class to handle an epoch that is set in the version string and as recipe field. An epoch in the version string has precedence over the recipe field for backward compatibility. If the epoch is set in both, the version string and the epoch field, we throw an exception because we cannot select on value automatically. Refs #210 --- lib/fpm/cookery/package/version.rb | 18 ++++++++++++++++++ lib/fpm/cookery/recipe.rb | 3 ++- spec/package_version_spec.rb | 18 ++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/fpm/cookery/package/version.rb b/lib/fpm/cookery/package/version.rb index d7638b78..efb7cf4f 100644 --- a/lib/fpm/cookery/package/version.rb +++ b/lib/fpm/cookery/package/version.rb @@ -1,3 +1,6 @@ +require 'fpm/cookery/exceptions' +require 'fpm/cookery/log' + module FPM module Cookery module Package @@ -28,6 +31,21 @@ def initialize(recipe, target, config) @config = config @revision = recipe.revision @version, @epoch = split_version(@recipe.version) + + if !@epoch.nil? and !recipe.epoch.nil? + # If the epoch is defined in the version string and set in the + # epoch field, we don't know what to choose. + message = "The \"epoch\" is defined in the recipe's version (#{@recipe.version}) AND epoch (#{@recipe.epoch}) fields" + Log.error message + raise Error::Misconfiguration, message + end + + # The epoch in the version string has precedence over the #epoch + # attribute in the recipe. (backward compatibility) + @epoch = recipe.epoch if @epoch.nil? + + # Ensure that epoch is always a string + @epoch = @epoch.to_s unless @epoch.nil? end def vendor diff --git a/lib/fpm/cookery/recipe.rb b/lib/fpm/cookery/recipe.rb index 0dd13b65..c4c255d3 100644 --- a/lib/fpm/cookery/recipe.rb +++ b/lib/fpm/cookery/recipe.rb @@ -34,7 +34,8 @@ class BaseRecipe :revision, :section, :sha1, :sha256, :sha512, :spec, :vendor, :version, :pre_install, :post_install, :pre_uninstall, :post_uninstall, :license, :omnibus_package, :omnibus_dir, :chain_package, - :default_prefix, :docker, :docker_image, :dockerfile + :default_prefix, :docker, :docker_image, :dockerfile, + :epoch attr_rw_list :build_depends, :config_files, :conflicts, :depends, :exclude, :patches, :provides, :replaces, :omnibus_recipes, diff --git a/spec/package_version_spec.rb b/spec/package_version_spec.rb index c01510ff..f1f0dfbe 100644 --- a/spec/package_version_spec.rb +++ b/spec/package_version_spec.rb @@ -85,6 +85,24 @@ expect(version.epoch).to eq(nil) end end + + context 'with epoch set in recipe' do + it 'returns the recipe epoch' do + recipe.version = '1.2.3' + recipe.epoch = 2 + + expect(version.epoch).to eq('2') # Must be a string + end + end + + context 'with epoch set in recipe version and epoch' do + it 'raises an Misconfiguration error' do + recipe.version = '3:1.2.3' + recipe.epoch = 2 + + expect { version.epoch }.to raise_error(FPM::Cookery::Error::Misconfiguration) + end + end end describe '#to_s' do From dac4928bcfefdda4af9e8e550f345d4ab4b299ad Mon Sep 17 00:00:00 2001 From: Bernd Ahlers Date: Wed, 13 Jul 2022 23:43:11 +0200 Subject: [PATCH 2/4] Add package_name_format field to recipe Setting the field to a format string value changes the filename of the created package. If unset, FPM chooses its default format. See the following code section for available format strings: https://github.com/jordansissel/fpm/blob/c40f6818f88a6dd3df7bc19b6007b03a716949f8/lib/fpm/package.rb#L355-L365 Fixes #210 --- lib/fpm/cookery/packager.rb | 9 +++++---- lib/fpm/cookery/recipe.rb | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/fpm/cookery/packager.rb b/lib/fpm/cookery/packager.rb index 070ff8aa..e3830cfd 100644 --- a/lib/fpm/cookery/packager.rb +++ b/lib/fpm/cookery/packager.rb @@ -213,18 +213,19 @@ def build_package(recipe, config) output = input.convert(output_class) + output_filename = output.to_s(recipe.package_name_format) recipe.run_lifecycle_hook(:before_package_create, output) begin - output.output(output.to_s) + output.output(output_filename) recipe.run_lifecycle_hook(:after_package_create, output) rescue FPM::Package::FileAlreadyExists - Log.info "Removing existing package file: #{output.to_s}" - FileUtils.rm_f(output.to_s) + Log.info "Removing existing package file: #{output_filename}" + FileUtils.rm_f(output_filename) retry ensure input.cleanup if input output.cleanup if output - Log.info "Created package: #{File.join(Dir.pwd, output.to_s)}" + Log.info "Created package: #{File.join(Dir.pwd, output_filename)}" end end end diff --git a/lib/fpm/cookery/recipe.rb b/lib/fpm/cookery/recipe.rb index c4c255d3..14827541 100644 --- a/lib/fpm/cookery/recipe.rb +++ b/lib/fpm/cookery/recipe.rb @@ -35,7 +35,7 @@ class BaseRecipe :pre_install, :post_install, :pre_uninstall, :post_uninstall, :license, :omnibus_package, :omnibus_dir, :chain_package, :default_prefix, :docker, :docker_image, :dockerfile, - :epoch + :package_name_format, :epoch attr_rw_list :build_depends, :config_files, :conflicts, :depends, :exclude, :patches, :provides, :replaces, :omnibus_recipes, From 4f13d4c4bb9b3de2bd8dc1b8453101471a03722d Mon Sep 17 00:00:00 2001 From: Bernd Ahlers Date: Wed, 13 Jul 2022 23:47:51 +0200 Subject: [PATCH 3/4] Add example for the package_name_format field --- recipes/facter/recipe.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/recipes/facter/recipe.rb b/recipes/facter/recipe.rb index 898a51f2..317f770d 100644 --- a/recipes/facter/recipe.rb +++ b/recipes/facter/recipe.rb @@ -1,4 +1,7 @@ class FacterRubyGem < FPM::Cookery::RubyGemRecipe name 'facter' version '1.6.16' + epoch 2 + + package_name_format 'NAME_EPOCH:FULLVERSION_ARCH.EXTENSION' end From 920edbc2acc779f8a678627a8acdcd7014a472cf Mon Sep 17 00:00:00 2001 From: Bernd Ahlers Date: Wed, 13 Jul 2022 23:48:29 +0200 Subject: [PATCH 4/4] Introduce two new package file creation lifecycle hooks - Add "before_package_file_create" - Add "after_package_file_create" - Deprecate "before_package_create" - Deprecate "after_package_create" The new hooks take the package filename as first parameter. With the introduction of the package_name_format recipe field, the package filename cannot be computed with the FPM::Package object alone anymore. We now pass in the filename directly so consumers don't have to compute the filename anymore. We could have passed in the recipe object or the package name format so consumers could compute the filename based on that, but that would require deeper knowledge of the FPM::Package object. --- lib/fpm/cookery/lifecycle_hooks.rb | 24 ++++++++++++++++++ lib/fpm/cookery/packager.rb | 4 +-- spec/lifecycle_hooks_spec.rb | 40 ++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 spec/lifecycle_hooks_spec.rb diff --git a/lib/fpm/cookery/lifecycle_hooks.rb b/lib/fpm/cookery/lifecycle_hooks.rb index 09b9d009..6dca171b 100644 --- a/lib/fpm/cookery/lifecycle_hooks.rb +++ b/lib/fpm/cookery/lifecycle_hooks.rb @@ -6,6 +6,18 @@ module LifecycleHooks def run_lifecycle_hook(hook_name, *args) Log.debug("Run lifecycle hook: #{hook_name} (args: #{args.inspect})") self.__send__(hook_name, *args) + + # Backward compatibility for users of deprecated lifecycle hooks + case hook_name + when :before_package_file_create + unless self.__send__(:before_package_create, args[1]) == :UNUSED + Log.deprecated("Switch from \"before_package_create\" lifecycle hook to \"before_package_file_create\"") + end + when :after_package_file_create + unless self.__send__(:after_package_create, args[1]) == :UNUSED + Log.deprecated("Switch from \"after_package_create\" lifecycle hook to \"after_package_file_create\"") + end + end end def before_dependency_installation @@ -39,12 +51,24 @@ def before_install def after_install end + # Gets a the output filename and the FPM::Package object as argument. + def before_package_file_create(filename, package) + end + + # Gets a the output filename and the FPM::Package object as argument. + def after_package_file_create(filename, package) + end + # Gets a FPM::Package object as argument. + # @deprecated Use #after_package_file_create. def before_package_create(package) + :UNUSED end # Gets a FPM::Package object as argument. + # @deprecated Use #after_package_file_create. def after_package_create(package) + :UNUSED end end end diff --git a/lib/fpm/cookery/packager.rb b/lib/fpm/cookery/packager.rb index e3830cfd..ade5d460 100644 --- a/lib/fpm/cookery/packager.rb +++ b/lib/fpm/cookery/packager.rb @@ -214,10 +214,10 @@ def build_package(recipe, config) output = input.convert(output_class) output_filename = output.to_s(recipe.package_name_format) - recipe.run_lifecycle_hook(:before_package_create, output) + recipe.run_lifecycle_hook(:before_package_file_create, output_filename, output) begin output.output(output_filename) - recipe.run_lifecycle_hook(:after_package_create, output) + recipe.run_lifecycle_hook(:after_package_file_create, output_filename, output) rescue FPM::Package::FileAlreadyExists Log.info "Removing existing package file: #{output_filename}" FileUtils.rm_f(output_filename) diff --git a/spec/lifecycle_hooks_spec.rb b/spec/lifecycle_hooks_spec.rb new file mode 100644 index 00000000..70976d36 --- /dev/null +++ b/spec/lifecycle_hooks_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' +require 'fpm/cookery/lifecycle_hooks' + +describe 'LifecycleHooks' do + let(:object) do + Class.new do + attr_accessor :canary + + include FPM::Cookery::LifecycleHooks + + def before_package_create(package) + self.canary = package + end + + def after_package_create(package) + self.canary = package + end + end.new + end + + let(:canary) { Object.new } + + describe 'backward compatibility' do + describe 'Running :before_package_file_create hook' do + it 'calls the deprecated :before_package_create hook' do + object.run_lifecycle_hook(:before_package_file_create, 'filename', canary) + + expect(object.canary).to eq(canary) + end + end + + describe 'Running :after_package_file_create hook' do + it 'calls the deprecated :after_package_create hook' do + object.run_lifecycle_hook(:after_package_file_create, 'filename', canary) + + expect(object.canary).to eq(canary) + end + end + end +end