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..0ea7d31d7 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 @@ -77,7 +78,7 @@ def run # Use the default as a last resort package_file = default_package_filename if package_file.nil? else - package_file = run_build(options) + package_file = run_build end run_publish(options.dup, package_file) unless skip_publish? @@ -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 + 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/lib/pdk/util.rb b/lib/pdk/util.rb index 9b8e7de2d..6c5c17dee 100644 --- a/lib/pdk/util.rb +++ b/lib/pdk/util.rb @@ -32,6 +32,7 @@ module Util def exit_process(exit_code) exit exit_code end + module_function :exit_process # :nocov: # Searches upwards from current working directory for the given target file. 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..eb941c4c6 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,9 +311,17 @@ end describe '#run_build' do - it 'calls PDK::Module::Build.invoke' do - expect(PDK::Module::Build).to receive(:invoke) - instance.run_build(options) + 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 end end