From 1a9cd4383d5f4ee08e25940756a6373957cd6690 Mon Sep 17 00:00:00 2001 From: jordanbreen28 Date: Mon, 8 Jul 2024 14:17:41 +0000 Subject: [PATCH] (CAT-1896) - Use puppet-modulebuilder for build functionality This commit updates the pdk to use the module builder gem for its build functionality. The PDK build code had largely duplicated this, therefore we were maintaining two codebases with very similiar purpose. --- lib/pdk/cli/build.rb | 9 +- lib/pdk/cli/release.rb | 1 - lib/pdk/module/build.rb | 302 ------------------- lib/pdk/module/release.rb | 13 +- pdk.gemspec | 3 + spec/unit/pdk/cli/build_spec.rb | 27 +- spec/unit/pdk/module/build_spec.rb | 434 --------------------------- spec/unit/pdk/module/release_spec.rb | 30 +- 8 files changed, 57 insertions(+), 762 deletions(-) delete mode 100644 lib/pdk/module/build.rb delete mode 100644 spec/unit/pdk/module/build_spec.rb diff --git a/lib/pdk/cli/build.rb b/lib/pdk/cli/build.rb index f5e1061bf..d96305967 100644 --- a/lib/pdk/cli/build.rb +++ b/lib/pdk/cli/build.rb @@ -12,7 +12,6 @@ module CLI option nil, 'force', 'Skips the prompts and builds the module package.' run do |opts, _args, _cmd| - require 'pdk/module/build' require 'pdk/module/metadata' require 'pdk/cli/util' @@ -37,7 +36,11 @@ module CLI end end - builder = PDK::Module::Build.new(opts) + # build module + require 'puppet/modulebuilder' + module_dir = PDK::Util::Filesystem.expand_path(Dir.pwd) + target_dir = PDK::Util::Filesystem.expand_path(opts[:'target-dir']) + builder = Puppet::Modulebuilder::Builder.new(module_dir, target_dir, PDK.logger) unless opts[:force] if builder.package_already_exists? @@ -49,7 +52,7 @@ module CLI end end - unless builder.module_pdk_compatible? + unless PDK::Util.module_pdk_compatible?(module_dir) PDK.logger.info 'This module is not compatible with PDK, so PDK can not validate or test this build. ' \ 'Unvalidated modules may have errors when uploading to the Forge. ' \ 'To make this module PDK compatible and use validate features, cancel the build and run `pdk convert`.' diff --git a/lib/pdk/cli/release.rb b/lib/pdk/cli/release.rb index 7349e27ef..c6b7f4ee4 100755 --- a/lib/pdk/cli/release.rb +++ b/lib/pdk/cli/release.rb @@ -3,7 +3,6 @@ require 'pdk/util/bundler' require 'pdk/cli/util/interview' require 'pdk/util/changelog_generator' -require 'pdk/module/build' module PDK module CLI diff --git a/lib/pdk/module/build.rb b/lib/pdk/module/build.rb deleted file mode 100644 index 109536a8a..000000000 --- a/lib/pdk/module/build.rb +++ /dev/null @@ -1,302 +0,0 @@ -require 'pdk' - -module PDK - module Module - class Build - def self.invoke(options = {}) - new(options).build - end - - attr_reader :module_dir, :target_dir - - def initialize(options = {}) - @module_dir = PDK::Util::Filesystem.expand_path(options[:module_dir] || Dir.pwd) - @target_dir = PDK::Util::Filesystem.expand_path(options[:'target-dir'] || File.join(module_dir, 'pkg')) - end - - # Read and parse the values from metadata.json for the module that is - # being built. - # - # @return [Hash{String => Object}] The hash of metadata values. - def metadata - require 'pdk/module/metadata' - - @metadata ||= PDK::Module::Metadata.from_file(File.join(module_dir, 'metadata.json')).data - end - - # Return the path where the built package file will be written to. - def package_file - @package_file ||= File.join(target_dir, "#{release_name}.tar.gz") - end - - # Build a module package from a module directory. - # - # @return [String] The path to the built package file. - def build - create_build_dir - - stage_module_in_build_dir - build_package - - package_file - ensure - cleanup_build_dir - end - - # Verify if there is an existing package in the target directory and prompts - # the user if they want to overwrite it. - def package_already_exists? - PDK::Util::Filesystem.exist?(package_file) - end - - # Check if the module is PDK Compatible. If not, then prompt the user if - # they want to run PDK Convert. - def module_pdk_compatible? - ['pdk-version', 'template-url'].any? { |key| metadata.key?(key) } - end - - # Return the path to the temporary build directory, which will be placed - # inside the target directory and match the release name (see #release_name). - def build_dir - @build_dir ||= File.join(target_dir, release_name) - end - - # Create a temporary build directory where the files to be included in - # the package will be staged before building the tarball. - # - # If the directory already exists, remove it first. - def create_build_dir - cleanup_build_dir - - PDK::Util::Filesystem.mkdir_p(build_dir) - end - - # Remove the temporary build directory and all its contents from disk. - # - # @return nil. - def cleanup_build_dir - PDK::Util::Filesystem.rm_rf(build_dir, secure: true) - end - - # Combine the module name and version into a Forge-compatible dash - # separated string. - # - # @return [String] The module name and version, joined by a dash. - def release_name - @release_name ||= [ - metadata['name'], - metadata['version'] - ].join('-') - end - - # Iterate through all the files and directories in the module and stage - # them into the temporary build directory (unless ignored). - # - # @return nil - def stage_module_in_build_dir - require 'find' - - Find.find(module_dir) do |path| - next if path == module_dir - - ignored_path?(path) ? Find.prune : stage_path(path) - end - end - - # Stage a file or directory from the module into the build directory. - # - # @param path [String] The path to the file or directory. - # - # @return nil. - def stage_path(path) - require 'pathname' - - relative_path = Pathname.new(path).relative_path_from(Pathname.new(module_dir)) - dest_path = File.join(build_dir, relative_path) - - validate_path_encoding!(relative_path.to_path) - - if PDK::Util::Filesystem.directory?(path) - PDK::Util::Filesystem.mkdir_p(dest_path, mode: PDK::Util::Filesystem.stat(path).mode) - elsif PDK::Util::Filesystem.symlink?(path) - warn_symlink(path) - else - validate_ustar_path!(relative_path.to_path) - PDK::Util::Filesystem.cp(path, dest_path, preserve: true) - end - rescue ArgumentError => e - raise PDK::CLI::ExitWithError, format('%{message} Rename the file or exclude it from the package ' \ - 'by adding it to the .pdkignore file in your module.', message: e.message) - end - - # Check if the given path matches one of the patterns listed in the - # ignore file. - # - # @param path [String] The path to be checked. - # - # @return [Boolean] true if the path matches and should be ignored. - def ignored_path?(path) - path = "#{path}/" if PDK::Util::Filesystem.directory?(path) - - !ignored_files.match_paths([path], module_dir).empty? - end - - # Warn the user about a symlink that would have been included in the - # built package. - # - # @param path [String] The relative or absolute path to the symlink. - # - # @return nil. - def warn_symlink(path) - require 'pathname' - - symlink_path = Pathname.new(path) - module_path = Pathname.new(module_dir) - - PDK.logger.warn format('Symlinks in modules are not supported and will not be included in the package. Please investigate symlink %{from} -> %{to}.', - from: symlink_path.relative_path_from(module_path), to: symlink_path.realpath.relative_path_from(module_path)) - end - - # Checks if the path length will fit into the POSIX.1-1998 (ustar) tar - # header format. - # - # POSIX.1-2001 (which allows paths of infinite length) was adopted by GNU - # tar in 2004 and is supported by minitar 0.7 and above. Unfortunately - # much of the Puppet ecosystem still uses minitar 0.6.1. - # - # POSIX.1-1998 tar format does not allow for paths greater than 256 bytes, - # or paths that can't be split into a prefix of 155 bytes (max) and - # a suffix of 100 bytes (max). - # - # This logic was pretty much copied from the private method - # {Archive::Tar::Minitar::Writer#split_name}. - # - # @param path [String] the relative path to be added to the tar file. - # - # @raise [ArgumentError] if the path is too long or could not be split. - # - # @return [nil] - def validate_ustar_path!(path) - raise ArgumentError, format("The path '%{path}' is longer than 256 bytes.", path: path) if path.bytesize > 256 - - if path.bytesize <= 100 - prefix = '' - else - parts = path.split(File::SEPARATOR) - newpath = parts.pop - nxt = '' - - loop do - nxt = parts.pop || '' - break if newpath.bytesize + 1 + nxt.bytesize >= 100 - - newpath = File.join(nxt, newpath) - end - - prefix = File.join(*parts, nxt) - path = newpath - end - - return unless path.bytesize > 100 || prefix.bytesize > 155 - - raise ArgumentError, - format("'%{path}' could not be split at a directory separator into two " \ - 'parts, the first having a maximum length of 155 bytes and the ' \ - 'second having a maximum length of 100 bytes.', path: path) - end - - # Checks if the path contains any non-ASCII characters. - # - # Java will throw an error when it encounters a path containing - # characters that are not supported by the hosts locale. In order to - # maximise compatibility we limit the paths to contain only ASCII - # characters, which should be part of any locale character set. - # - # @param path [String] the relative path to be added to the tar file. - # - # @raise [ArgumentError] if the path contains non-ASCII characters. - # - # @return [nil] - def validate_path_encoding!(path) - return unless /[^\x00-\x7F]/.match?(path) - - raise ArgumentError, format("'%{path}' can only include ASCII characters in its path or " \ - 'filename in order to be compatible with a wide range of hosts.', path: path) - end - - # Creates a gzip compressed tarball of the build directory. - # - # If the destination package already exists, it will be removed before - # creating the new tarball. - # - # @return nil. - def build_package - require 'zlib' - require 'minitar' - require 'find' - - PDK::Util::Filesystem.rm_f(package_file) - - Dir.chdir(target_dir) do - gz = Zlib::GzipWriter.new(File.open(package_file, 'wb')) # rubocop:disable PDK/FileOpen - tar = Minitar::Output.new(gz) - Find.find(release_name) do |entry| - entry_meta = { - name: entry - } - - orig_mode = PDK::Util::Filesystem.stat(entry).mode - min_mode = Minitar.dir?(entry) ? 0o755 : 0o644 - - entry_meta[:mode] = orig_mode | min_mode - - PDK.logger.debug(format('Updated permissions of packaged \'%{entry}\' to %{new_mode}', entry: entry, new_mode: (entry_meta[:mode] & 0o7777).to_s(8))) if entry_meta[:mode] != orig_mode - - Minitar.pack_file(entry_meta, tar) - end - ensure - tar.close - end - end - - # Select the most appropriate ignore file in the module directory. - # - # In order of preference, we first try `.pdkignore`, then `.pmtignore` - # and finally `.gitignore`. - # - # @return [String] The path to the file containing the patterns of file - # paths to ignore. - def ignore_file - @ignore_file ||= [ - File.join(module_dir, '.pdkignore'), - File.join(module_dir, '.pmtignore'), - File.join(module_dir, '.gitignore') - ].find { |file| PDK::Util::Filesystem.file?(file) && PDK::Util::Filesystem.readable?(file) } - end - - # Instantiate a new PathSpec class and populate it with the pattern(s) of - # files to be ignored. - # - # @return [PathSpec] The populated ignore path matcher. - def ignored_files - require 'pdk/module' - require 'pathspec' - - @ignored_files ||= - begin - ignored = if ignore_file.nil? - PathSpec.new - else - PathSpec.new(PDK::Util::Filesystem.read_file(ignore_file, open_args: 'rb:UTF-8')) - end - - ignored = ignored.add("/#{File.basename(target_dir)}/") if File.realdirpath(target_dir).start_with?(File.realdirpath(module_dir)) - - PDK::Module::DEFAULT_IGNORED.each { |r| ignored.add(r) } - - ignored - end - end - end - end -end diff --git a/lib/pdk/module/release.rb b/lib/pdk/module/release.rb index d4609a84f..8c81b3c15 100644 --- a/lib/pdk/module/release.rb +++ b/lib/pdk/module/release.rb @@ -1,5 +1,6 @@ require 'pdk' require 'uri' +require 'puppet/modulebuilder' module PDK module Module @@ -95,7 +96,7 @@ def write_module_metadata! def default_package_filename return @default_tarball_filename unless @default_tarball_filename.nil? - builder = PDK::Module::Build.new(module_dir: module_path) + builder = Puppet::Modulebuilder::Builder.new(module_path, nil, PDK.logger) @default_tarball_filename = builder.package_file end @@ -135,8 +136,11 @@ def run_dependency_checker(_opts) end # @return [String] Path to the built tarball - def run_build(opts) - PDK::Module::Build.invoke(opts.dup) + def run_build(_opts) + module_dir = PDK::Util::Filesystem.expand_path(@module_path || Dir.pwd) + target_dir = File.join(module_dir, 'pkg') + builder = Puppet::Modulebuilder::Builder.new(module_dir, target_dir, PDK.logger) + builder.build end def run_publish(_opts, tarball_path) @@ -238,8 +242,7 @@ def forge_compatible? def pdk_compatible? return @pdk_compatible unless @pdk_compatible.nil? - builder = PDK::Module::Build.new(module_dir: module_path) - @pdk_compatible = builder.module_pdk_compatible? + @pdk_compatible = PDK::Util.module_pdk_compatible?(module_path) end # :nocov: diff --git a/pdk.gemspec b/pdk.gemspec index 769cea6b2..496570c56 100644 --- a/pdk.gemspec +++ b/pdk.gemspec @@ -41,6 +41,9 @@ Gem::Specification.new do |spec| # json-schema and deps spec.add_runtime_dependency 'json-schema', '~> 4.0' + #  PDK build + spec.add_runtime_dependency 'puppet-modulebuilder', '~> 1.0' + # Other deps spec.add_runtime_dependency 'deep_merge', '~> 1.2.2' spec.add_runtime_dependency 'diff-lcs', '>= 1.5.0' diff --git a/spec/unit/pdk/cli/build_spec.rb b/spec/unit/pdk/cli/build_spec.rb index b1753482e..c82c82125 100644 --- a/spec/unit/pdk/cli/build_spec.rb +++ b/spec/unit/pdk/cli/build_spec.rb @@ -1,5 +1,6 @@ require 'spec_helper' require 'pdk/cli' +require 'puppet/modulebuilder' describe 'PDK::CLI build' do let(:help_text) { a_string_matching(/^USAGE\s+pdk build/m) } @@ -24,8 +25,8 @@ context 'when run from inside a module' do before do allow(PDK::Util).to receive(:module_root).and_return('/path/to/test/module') - allow(PDK::Module::Metadata).to receive(:from_file).with('metadata.json').and_return(mock_metadata_obj) - allow(PDK::Module::Build).to receive(:new).with(anything).and_return(mock_builder) + allow(PDK::Module::Metadata).to receive(:from_file).with(/metadata.json/).and_return(mock_metadata_obj) + allow(Puppet::Modulebuilder::Builder).to receive(:new).and_return(mock_builder) end let(:command_opts) { [] } @@ -46,9 +47,8 @@ let(:package_path) { File.join(Dir.pwd, 'pkg', 'testuser-testmodule-2.3.4.tar.gz') } let(:mock_builder) do instance_double( - PDK::Module::Build, + Puppet::Modulebuilder::Builder, build: true, - module_pdk_compatible?: true, package_already_exists?: false, package_file: package_path ) @@ -56,8 +56,9 @@ context 'and the module contains incomplete metadata' do before do + allow(PDK::Util).to receive(:module_pdk_compatible?).and_return(true) allow(mock_metadata_obj).to receive_messages(forge_ready?: false, missing_fields: ['operatingsystem_support', 'source']) - allow(PDK::Module::Build).to receive(:new).with(any_args).and_return(mock_builder) + allow(Puppet::Modulebuilder::Builder).to receive(:new).with(any_args).and_return(mock_builder) end context 'with default options' do @@ -84,8 +85,8 @@ include_context 'exits cleanly' before do - allow(mock_metadata_obj).to receive(:forge_ready?).and_return(true) - allow(PDK::Module::Build).to receive(:new).with(any_args).and_return(mock_builder) + allow(PDK::Util).to receive_messages(module_pdk_compatible?: true, forge_ready?: true) + allow(Puppet::Modulebuilder::Builder).to receive(:new).with(any_args).and_return(mock_builder) end it 'does not interview the user' do @@ -98,7 +99,8 @@ include_context 'exits cleanly' it 'invokes the builder with the default target directory' do - expect(PDK::Module::Build).to receive(:new).with(hash_with_defaults_including('target-dir': File.join(Dir.pwd, 'pkg'))).and_return(mock_builder) + allow(PDK::Util).to receive(:module_pdk_compatible?).and_return(true) + expect(Puppet::Modulebuilder::Builder).to receive(:new).with(Dir.pwd, File.join(Dir.pwd, 'pkg'), anything).and_return(mock_builder) end end @@ -108,13 +110,15 @@ let(:command_opts) { ['--target-dir', '/tmp/pdk_builds'] } it 'invokes the builder with the specified target directory' do - expect(PDK::Module::Build).to receive(:new).with(hash_including('target-dir': '/tmp/pdk_builds')).and_return(mock_builder) + allow(PDK::Util).to receive(:module_pdk_compatible?).and_return(true) + expect(Puppet::Modulebuilder::Builder).to receive(:new).with(Dir.pwd, '/tmp/pdk_builds', anything).and_return(mock_builder) end end context 'package already exists in the target dir' do before do - allow(mock_builder).to receive_messages(package_already_exists?: true, module_pdk_compatible?: true) + allow(mock_builder).to receive_messages(package_already_exists?: true) + allow(PDK::Util).to receive(:module_pdk_compatible?).and_return(true) end context 'user chooses to continue' do @@ -148,7 +152,8 @@ context 'and module is not pdk compatible' do before do - allow(mock_builder).to receive_messages(package_already_exists?: false, module_pdk_compatible?: false) + allow(mock_builder).to receive_messages(package_already_exists?: false) + allow(PDK::Util).to receive(:module_pdk_compatible?).and_return(false) end context 'user chooses to continue' do diff --git a/spec/unit/pdk/module/build_spec.rb b/spec/unit/pdk/module/build_spec.rb deleted file mode 100644 index 38183945d..000000000 --- a/spec/unit/pdk/module/build_spec.rb +++ /dev/null @@ -1,434 +0,0 @@ -require 'spec_helper' -require 'pdk/module/build' -require 'pathspec' - -describe PDK::Module::Build do - subject { described_class.new(initialize_options) } - - let(:initialize_options) { {} } - let(:root_dir) { Gem.win_platform? ? 'C:/' : '/' } - - shared_context 'with mock metadata' do - let(:mock_metadata) { PDK::Module::Metadata.new('name' => 'my-module') } - - before do - allow(PDK::Module::Metadata).to receive(:from_file).with(anything).and_return(mock_metadata) - end - end - - describe '.invoke' do - it 'creates a new PDK::Module::Build instance and calls #build' do - build_double = instance_double(described_class, build: true) - - expect(described_class).to receive(:new).with({ module_dir: 'test' }).and_return(build_double) - expect(build_double).to receive(:build) - - described_class.invoke(module_dir: 'test') - end - end - - describe '#initialize' do - before do - allow(Dir).to receive(:pwd).and_return(pwd) - end - - let(:pwd) { File.join(root_dir, 'path', 'to', 'module') } - - context 'by default' do - it 'uses the current working directory as the module directory' do - expect(subject).to have_attributes(module_dir: pwd) - end - - it 'places the built packages in the pkg directory in the module' do - expect(subject).to have_attributes(target_dir: File.join(pwd, 'pkg')) - end - end - - context 'if module_dir has been customised' do - let(:initialize_options) do - { - module_dir: File.join(root_dir, 'some', 'other', 'module') - } - end - - it 'uses the provided path as the module directory' do - expect(subject).to have_attributes(module_dir: initialize_options[:module_dir]) - end - - it 'places the built packages in the pkg directory in the module' do - expect(subject).to have_attributes(target_dir: File.join(initialize_options[:module_dir], 'pkg')) - end - end - - context 'if target_dir has been customised' do - let(:initialize_options) do - { - 'target-dir': File.join(root_dir, 'tmp') - } - end - - it 'uses the current working directory as the module directory' do - expect(subject).to have_attributes(module_dir: pwd) - end - - it 'places the built packages in the provided path' do - expect(subject).to have_attributes(target_dir: initialize_options[:'target-dir']) - end - end - - context 'if both module_dir and target_dir have been customised' do - let(:initialize_options) do - { - 'target-dir': File.join(root_dir, 'var', 'cache'), - module_dir: File.join(root_dir, 'tmp', 'git', 'my-module') - } - end - - it 'uses the provided module_dir path as the module directory' do - expect(subject).to have_attributes(module_dir: initialize_options[:module_dir]) - end - - it 'places the built packages in the provided target_dir path' do - expect(subject).to have_attributes(target_dir: initialize_options[:'target-dir']) - end - end - end - - describe '#metadata' do - subject { described_class.new.metadata } - - include_context 'with mock metadata' - - it { is_expected.to be_a(Hash) } - it { is_expected.to include('name' => 'my-module', 'version' => '0.1.0') } - end - - describe '#release_name' do - subject { described_class.new.release_name } - - include_context 'with mock metadata' - - it { is_expected.to eq('my-module-0.1.0') } - end - - describe '#package_file' do - subject { described_class.new('target-dir': target_dir).package_file } - - let(:target_dir) { File.join(root_dir, 'tmp') } - - include_context 'with mock metadata' - - it { is_expected.to eq(File.join(target_dir, 'my-module-0.1.0.tar.gz')) } - end - - describe '#build_dir' do - subject { described_class.new('target-dir': target_dir).build_dir } - - let(:target_dir) { File.join(root_dir, 'tmp') } - - include_context 'with mock metadata' - - it { is_expected.to eq(File.join(target_dir, 'my-module-0.1.0')) } - end - - describe '#stage_module_in_build_dir' do - let(:instance) { described_class.new(module_dir: module_dir) } - let(:module_dir) { File.join(root_dir, 'tmp', 'my-module') } - - before do - allow(instance).to receive(:ignored_files).and_return(PathSpec.new("/spec/\n")) - allow(Find).to receive(:find).with(module_dir).and_yield(found_file) - end - - after do - instance.stage_module_in_build_dir - end - - context 'when it finds a non-ignored path' do - let(:found_file) { File.join(module_dir, 'metadata.json') } - - it 'stages the path into the build directory' do - expect(instance).to receive(:stage_path).with(found_file) - end - end - - context 'when it finds an ignored path' do - let(:found_file) { File.join(module_dir, 'spec', 'spec_helper.rb') } - - it 'does not stage the path' do - expect(Find).to receive(:prune) - expect(instance).not_to receive(:stage_path).with(found_file) - end - end - - context 'when it finds the module directory itself' do - let(:found_file) { module_dir } - - it 'does not stage the path' do - expect(instance).not_to receive(:stage_path).with(module_dir) - end - end - end - - describe '#stage_path' do - let(:instance) { described_class.new(module_dir: module_dir) } - let(:module_dir) { File.join(root_dir, 'tmp', 'my-module') } - let(:path_to_stage) { File.join(module_dir, 'test') } - let(:path_in_build_dir) { File.join(module_dir, 'pkg', release_name, 'test') } - let(:release_name) { 'my-module-0.0.1' } - - before do - allow(instance).to receive(:release_name).and_return(release_name) - end - - context 'when the path contains non-ASCII characters' do - RSpec.shared_examples 'a failing path' do |relative_path| - let(:path) do - File.join(module_dir, relative_path).force_encoding(Encoding.find('filesystem')).encode('utf-8', invalid: :replace) - end - - before do - allow(PDK::Util::Filesystem).to receive(:directory?).with(path).and_return(true) - allow(PDK::Util::Filesystem).to receive(:symlink?).with(path).and_return(false) - allow(PDK::Util::Filesystem).to receive(:cp).with(path, anything, anything).and_return(true) - end - - it 'exits with an error' do - expect do - instance.stage_path(path) - end.to raise_error(PDK::CLI::ExitWithError, /can only include ASCII characters/) - end - end - - include_examples 'a failing path', "strange_unicode_\u{000100}" - include_examples 'a failing path', "\300\271to" - end - - context 'when the path is a directory' do - before do - allow(PDK::Util::Filesystem).to receive(:directory?).with(path_to_stage).and_return(true) - allow(PDK::Util::Filesystem).to receive(:stat).with(path_to_stage).and_return(instance_double(File::Stat, mode: 0o100755)) - end - - it 'creates the directory in the build directory' do - expect(PDK::Util::Filesystem).to receive(:mkdir_p).with(path_in_build_dir, mode: 0o100755) - instance.stage_path(path_to_stage) - end - end - - context 'when the path is a symlink' do - before do - allow(PDK::Util::Filesystem).to receive(:directory?).with(path_to_stage).and_return(false) - allow(PDK::Util::Filesystem).to receive(:symlink?).with(path_to_stage).and_return(true) - end - - it 'warns the user about the symlink and skips over it' do - expect(instance).to receive(:warn_symlink).with(path_to_stage) - expect(PDK::Util::Filesystem).not_to receive(:mkdir_p).with(any_args) - expect(PDK::Util::Filesystem).not_to receive(:cp).with(any_args) - instance.stage_path(path_to_stage) - end - end - - context 'when the path is a regular file' do - before do - allow(PDK::Util::Filesystem).to receive(:directory?).with(path_to_stage).and_return(false) - allow(PDK::Util::Filesystem).to receive(:symlink?).with(path_to_stage).and_return(false) - end - - it 'copies the file into the build directory, preserving the permissions' do - expect(PDK::Util::Filesystem).to receive(:cp).with(path_to_stage, path_in_build_dir, preserve: true) - instance.stage_path(path_to_stage) - end - - context 'when the path is too long' do - let(:path_to_stage) { File.join(*['thing'] * 30) } - - it 'exits with an error' do - expect do - instance.stage_path(path_to_stage) - end.to raise_error(PDK::CLI::ExitWithError) - end - end - end - end - - describe '#path_too_long?' do - subject(:instance) { described_class.new } - - good_paths = [ - File.join('a' * 155, 'b' * 100), - File.join('a' * 151, *['qwer'] * 19, 'bla'), - File.join('/', 'a' * 49, 'b' * 50), - File.join('a' * 49, "#{'b' * 50}x"), - File.join("#{'a' * 49}x", 'b' * 50) - ] - - bad_paths = { - File.join('a' * 152, 'b' * 11, 'c' * 93) => /longer than 256/i, - File.join('a' * 152, 'b' * 10, 'c' * 92) => /could not be split/i, - File.join('a' * 162, 'b' * 10) => /could not be split/i, - File.join('a' * 10, 'b' * 110) => /could not be split/i, - 'a' * 114 => /could not be split/i - } - - good_paths.each do |path| - context "when checking '#{path}'" do - it 'does not raise an error' do - expect { instance.validate_ustar_path!(path) }.not_to raise_error - end - end - end - - bad_paths.each do |path, err| - context "when checking '#{path}'" do - it 'raises an ArgumentError' do - expect { instance.validate_ustar_path!(path) }.to raise_error(ArgumentError, err) - end - end - end - end - - describe '#validate_path_encoding!' do - subject(:instance) { described_class.new } - - context 'when passed a path containing only ASCII characters' do - it 'does not raise an error' do - expect do - instance.validate_path_encoding!(File.join('path', 'to', 'file')) - end.not_to raise_error - end - end - - context 'when passed a path containing non-ASCII characters' do - it 'raises an error' do - expect do - instance.validate_path_encoding!(File.join('path', "\330\271to", 'file')) - end.to raise_error(ArgumentError, /can only include ASCII characters/) - end - end - end - - describe '#ignored_path?' do - let(:instance) { described_class.new(module_dir: module_dir) } - let(:ignore_patterns) do - [ - '/vendor/', - 'foo' - ] - end - let(:module_dir) { File.join(root_dir, 'tmp', 'my-module') } - - before do - allow(instance).to receive(:ignored_files).and_return(PathSpec.new(ignore_patterns.join("\n"))) - end - - it 'returns false for paths not matched by the patterns' do - expect(instance).not_to be_ignored_path(File.join(module_dir, 'bar')) - end - - it 'returns true for paths matched by the patterns' do - expect(instance).to be_ignored_path(File.join(module_dir, 'foo')) - end - - it 'returns true for children of ignored parent directories' do - expect(instance).to be_ignored_path(File.join(module_dir, 'vendor', 'test')) - end - end - - describe '#ignore_file' do - subject { described_class.new(module_dir: module_dir).ignore_file } - - let(:module_dir) { File.join(root_dir, 'tmp', 'my-module') } - let(:possible_files) do - [ - '.pdkignore', - '.pmtignore', - '.gitignore' - ] - end - let(:available_files) { [] } - - before do - available_files.each do |file| - file_path = File.join(module_dir, file) - - allow(PDK::Util::Filesystem).to receive(:file?).with(file_path).and_return(true) - allow(PDK::Util::Filesystem).to receive(:readable?).with(file_path).and_return(true) - end - - (possible_files - available_files).each do |file| - file_path = File.join(module_dir, file) - - allow(PDK::Util::Filesystem).to receive(:file?).with(file_path).and_return(false) - allow(PDK::Util::Filesystem).to receive(:readable?).with(file_path).and_return(false) - end - end - - context 'when none of the possible ignore files are present' do - it { is_expected.to be_nil } - end - - context 'when .gitignore is present' do - let(:available_files) { ['.gitignore'] } - - it 'returns the path to the .gitignore file' do - expect(subject).to eq(File.join(module_dir, '.gitignore')) - end - - context 'and .pmtignore is present' do - let(:available_files) { ['.gitignore', '.pmtignore'] } - - it 'returns the path to the .pmtignore file' do - expect(subject).to eq(File.join(module_dir, '.pmtignore')) - end - - context 'and .pdkignore is present' do - let(:available_files) { possible_files } - - it 'returns the path to the .pdkignore file' do - expect(subject).to eq(File.join(module_dir, '.pdkignore')) - end - end - end - end - end - - describe '#ignored_files' do - subject { instance.ignored_files } - - let(:module_dir) { File.join(root_dir, 'tmp', 'my-module') } - let(:instance) { described_class.new(module_dir: module_dir) } - - before do - allow(File).to receive(:realdirpath) { |path| path } - end - - context 'when no ignore file is present in the module' do - before do - allow(instance).to receive(:ignore_file).and_return(nil) - end - - it 'returns a PathSpec object with the target dir' do - expect(subject).to be_a(PathSpec) - expect(subject).not_to be_empty - expect(subject).to match('pkg/') - end - end - - context 'when an ignore file is present in the module' do - before do - ignore_file_path = File.join(module_dir, '.pdkignore') - ignore_file_content = "/vendor/\n" - - allow(instance).to receive(:ignore_file).and_return(ignore_file_path) - allow(PDK::Util::Filesystem).to receive(:read_file).with(ignore_file_path, anything).and_return(ignore_file_content) - end - - it 'returns a PathSpec object populated by the ignore file' do - expect(subject).to be_a(PathSpec) - expect(subject).to have_attributes(specs: array_including(an_instance_of(PathSpec::GitIgnoreSpec))) - end - end - end -end diff --git a/spec/unit/pdk/module/release_spec.rb b/spec/unit/pdk/module/release_spec.rb index 964cb46dc..84ea60170 100644 --- a/spec/unit/pdk/module/release_spec.rb +++ b/spec/unit/pdk/module/release_spec.rb @@ -1,5 +1,6 @@ require 'spec_helper' require 'pdk/module/release' +require 'puppet/modulebuilder' describe PDK::Module::Release do let(:module_path) { nil } @@ -27,7 +28,6 @@ allow(PDK::Util).to receive_messages(find_upwards: nil, in_module_root?: true) allow(Dir).to receive(:pwd).and_return(module_root) allow(PDK::Util::ChangelogGenerator).to receive(:changelog_content).and_return('This is a changelog') - allow(PDK::Module::Metadata).to receive(:from_file).and_return(mock_metadata_object) end @@ -48,7 +48,12 @@ end describe '#run' do + let(:builder) { double(Puppet::Modulebuilder::Builder, package_file: 'package.tar.gz') } # rubocop:disable RSpec/VerifiedDoubles + before do + logger = Logger.new(File.open(File::NULL, 'w')) # rubocop:disable PDK/FileOpen + allow(PDK).to receive(:logger).and_return(logger) + allow(Puppet::Modulebuilder::Builder).to receive(:new).and_return(builder) # Stop any of the actual worker methods from running allow(instance).to receive(:run_validations) allow(instance).to receive(:run_documentation) @@ -56,6 +61,11 @@ allow(instance).to receive(:run_build) allow(instance).to receive(:run_publish) allow(PDK::Util::ChangelogGenerator).to receive(:generate_changelog) + allow(builder).to receive_messages(file_directory?: true, file_readable?: true) + allow(builder).to receive(:source) + allow(builder).to receive(:metadata) do |instance| + instance.instance_variable_set(:@metadata, metadata_hash) + end end context 'when skipping everything' do @@ -220,10 +230,10 @@ end describe '#default_package_filename' do - let(:builder) { double(PDK::Module::Build, package_file: 'package.tar.gz') } # rubocop:disable RSpec/VerifiedDoubles + let(:builder) { double(Puppet::Modulebuilder::Builder, package_file: 'package.tar.gz') } # rubocop:disable RSpec/VerifiedDoubles - it 'calls PDK::Module::Build' do - expect(PDK::Module::Build).to receive(:new).with(module_dir: module_root).and_return(builder) + it 'calls Puppet::Modulebuilder::Builder' do + expect(Puppet::Modulebuilder::Builder).to receive(:new).with(module_root, nil, logger).and_return(builder) expect(instance.default_package_filename).to eq('package.tar.gz') end end @@ -301,8 +311,16 @@ end describe '#run_build' do - it 'calls PDK::Module::Build.invoke' do - expect(PDK::Module::Build).to receive(:invoke) + let(:builder) { double(Puppet::Modulebuilder::Builder) } # rubocop:disable RSpec/VerifiedDoubles + + before do + logger = Logger.new(File.open(File::NULL, 'w')) # rubocop:disable PDK/FileOpen + allow(PDK).to receive(:logger).and_return(logger) + allow(Puppet::Modulebuilder::Builder).to receive(:new).and_return(builder) + end + + it 'calls Puppet::Modulebuilder::Builder.build' do + expect(builder).to receive(:build) instance.run_build(options) end end