diff --git a/lib/pdk/util/template_uri.rb b/lib/pdk/util/template_uri.rb index 0e89b0001..778f1d5df 100644 --- a/lib/pdk/util/template_uri.rb +++ b/lib/pdk/util/template_uri.rb @@ -7,7 +7,7 @@ class TemplateURI PACKAGED_TEMPLATE_KEYWORD = 'pdk-default'.freeze DEPRECATED_TEMPLATE_URL = 'https://github.com/puppetlabs/pdk-module-template'.freeze - PDK_TEMPLATE_URL = 'https://github.com/puppetlabs/pdk-templates'.freeze + PDK_TEMPLATE_URL = 'https://github.com/silug/pdk-templates'.freeze LEGACY_PACKAGED_TEMPLATE_PATHS = { 'windows' => 'file:///C:/Program Files/Puppet Labs/DevelopmentKit/share/cache/pdk-templates.git', diff --git a/lib/pdk/version.rb b/lib/pdk/version.rb index 81e9f8902..fbc09d0db 100644 --- a/lib/pdk/version.rb +++ b/lib/pdk/version.rb @@ -1,4 +1,4 @@ module PDK VERSION = '3.3.0'.freeze - TEMPLATE_REF = '3.3.0.1'.freeze + TEMPLATE_REF = 'facterdb-3'.freeze end diff --git a/spec/unit/pdk/generate/module_spec.rb b/spec/unit/pdk/generate/module_spec.rb deleted file mode 100644 index c60c0631a..000000000 --- a/spec/unit/pdk/generate/module_spec.rb +++ /dev/null @@ -1,819 +0,0 @@ -require 'spec_helper' -require 'tempfile' -require 'stringio' -require 'tty/prompt/test' -require 'pdk/generate/module' -require 'addressable' - -shared_context 'allow summary to be printed to stdout' do - before do - allow($stdout).to receive(:puts).with(a_string_matching(/\A-+\Z/)) - allow($stdout).to receive(:puts).with('SUMMARY') - allow($stdout).to receive(:puts).with(a_string_matching(/\A\{.+\}\Z/m)) - allow($stdout).to receive(:puts).with(no_args) - end -end - -shared_context 'mock template dir' do - let(:test_template_path) { instance_double(Pathname, mkpath: true, to_path: '/a/path') } - let(:template_dir) { PDK::Template::TemplateDir.new(nil, nil, pdk_context, renderer) } - let(:renderer) { instance_double(PDK::Template::Renderer::AbstractRenderer) } - - before do - allow(PDK::Template).to receive(:with).with(anything, anything).and_yield(template_dir) - allow(renderer).to receive(:render).and_yield(*yielded_file) - - allow(test_template_path).to receive(:+).with(anything).and_return(test_template_path) - allow(test_template_path).to receive_messages(dirname: test_template_path, relative?: true) - # TODO: This is overkill. e.g. this breaks using 'require 'pry'; binding.pry'. - allow(Pathname).to receive(:new).with(anything).and_return(test_template_path) - allow(PDK::Util::Filesystem).to receive(:write_file) - .with(test_template_path, anything) - allow(PDK::Util::Git).to receive(:repo?).with(anything).and_return(true) - end -end - -shared_context 'mock metadata.json' do - before do - allow(PDK::Util::Filesystem).to receive(:write_file) - .with(a_string_matching(/metadata\.json\Z/), anything) - end -end - -describe PDK::Generate::Module do - include_context 'mock configuration' - let(:pdk_context) { PDK::Context::None.new(nil) } - - describe '.invoke' do - let(:target_dir) { PDK::Util::Filesystem.expand_path('/path/to/target/module') } - let(:invoke_opts) do - { - target_dir: target_dir, - module_name: 'foo', - 'skip-interview': true - } - end - - before do - allow(PDK::Util::Bundler).to receive(:ensure_bundle!) - allow(Dir).to receive(:chdir).with(target_dir).and_yield - end - - context 'when the target module directory already exists' do - it 'raises a FatalError' do - allow(PDK::Util::Filesystem).to receive(:exist?).with(target_dir).and_return(true) - expect(logger).not_to receive(:info).with(a_string_matching(/generated at path/i)) - expect(logger).not_to receive(:info).with(a_string_matching(/In your new module directory, add classes with the 'pdk new class' command/i)) - - expect do - described_class.invoke(module_name: 'foo', target_dir: target_dir) - end.to raise_error(PDK::CLI::ExitWithError, /destination directory '.+' already exists/i) - end - end - - context 'when the target module directory does not exist' do - include_context 'mock template dir' - include_context 'mock metadata.json' - - let(:temp_target_dir) { '/path/to/temp/dir' } - let(:target_parent_writeable) { true } - let(:yielded_file) { ['test_file_path', 'test_file_content', :manage] } - - before do - allow(PDK::Util::Filesystem).to receive(:exist?).with(target_dir).and_return(false) - allow(PDK::Util).to receive(:make_tmpdir_name).with(anything).and_return(temp_target_dir) - allow(PDK::Util::Filesystem).to receive(:mv).with(temp_target_dir, target_dir) - allow(PDK::Util::Version).to receive(:version_string).and_return('0.0.0') - allow(described_class).to receive(:prepare_module_directory).with(temp_target_dir) - allow(PDK::Util::Filesystem).to receive(:write_file).with(/pdk-test-writable/, anything) { raise Errno::EACCES unless target_parent_writeable } - allow(PDK::Util::Filesystem).to receive(:rm_f).with(/pdk-test-writable/) - allow(PDK::Util).to receive_messages(module_root: nil, package_install?: false) - end - - context 'when the parent directory of the target is not writable' do - let(:target_parent_writeable) { false } - - it 'raises a FatalError' do - expect(logger).not_to receive(:info).with(a_string_matching(/generated at path/i)) - expect(logger).not_to receive(:info).with(a_string_matching(/In your new module directory, add classes with the 'pdk new class' command/i)) - - expect do - described_class.invoke(invoke_opts) - end.to raise_error(PDK::CLI::FatalError, /you do not have permission to write to/i) - end - end - - it 'generates the new module in a temporary directory' do - expect(described_class).to receive(:prepare_module_directory).with(temp_target_dir) - described_class.invoke(invoke_opts) - end - - context 'when the module template contains template files' do - let(:content) { 'test_file_content' } - let(:yielded_file) { ['test_file_path', content, :manage] } - - it 'writes the rendered files from the template to the temporary directory' do - allow(PDK::Util::Filesystem).to receive(:write_file) - .with(test_template_path, content) - - described_class.invoke(invoke_opts) - end - end - - context 'when the module template contains unmanaged template files' do - let(:content) { 'test_file_content' } - let(:yielded_file) { ['test_file_path', content, :unmanage] } - - it 'writes the rendered files from the template to the temporary directory' do - expect(PDK::Util::Filesystem).not_to receive(:write_file).with(test_template_path, content) - - described_class.invoke(invoke_opts) - end - end - - context 'when the module template contains files with delete option set' do - let(:content) { 'test_file_content' } - let(:yielded_file) { ['test_file_path', content, :delete] } - - it 'does not writes the deleted files from the template to the temporary directory' do - expect(PDK::Util::Filesystem).not_to receive(:write_file) - .with(test_template_path, anything) - - described_class.invoke(invoke_opts) - end - end - - context 'when the template dir generates metadata about itself' do - let(:template_metadata) do - { - 'template-url' => 'test_template_url', - 'template-ref' => 'test_template_ref' - } - end - - before do - allow(template_dir).to receive(:metadata).and_return(template_metadata) - end - - it 'includes details about the template in the generated metadata.json' do - include_metadata = satisfy do |content| - metadata = JSON.parse(content) - template_metadata.all? do |key, _| - metadata[key] == template_metadata[key] - end - end - - expect(PDK::Util::Filesystem).to receive(:write_file) - .with(a_string_matching(/metadata\.json\Z/), include_metadata) - - described_class.invoke(invoke_opts) - end - end - - it 'moves the temporary directory to the target directory when done' do - expect(PDK::Util::Filesystem).to receive(:mv).with(temp_target_dir, target_dir) - described_class.invoke(invoke_opts) - end - - it 'prepares the bundler environment so that it is ready immediately' do - allow(PDK::Util::Filesystem).to receive(:mv).with(temp_target_dir, target_dir).and_return(true) - expect(PDK::Util::Bundler).to receive(:ensure_bundle!) - described_class.invoke(invoke_opts) - end - - context 'when the move to the target directory fails due to invalid permissions' do - before do - allow(PDK::Util::Filesystem).to receive(:mv).with(temp_target_dir, target_dir).and_raise(Errno::EACCES, 'permission denied') - end - - it 'raises a FatalError' do - expect(logger).not_to receive(:info).with(a_string_matching(/generated at path/i)) - expect(logger).not_to receive(:info).with(a_string_matching(/In your new module directory, add classes with the 'pdk new class' command/i)) - - expect do - described_class.invoke(invoke_opts) - end.to raise_error(PDK::CLI::FatalError, /failed to move .+: permission denied/i) - end - end - - context 'when a template-url is supplied on the command line' do - let(:default_template_url) { 'https://github.com/puppetlabs/pdk-templates' } - - before do - allow(PDK::Util::Filesystem).to receive(:mv).with(temp_target_dir, target_dir).and_return(0) - allow(PDK::Util).to receive(:default_template_uri).and_return(Addressable::URI.parse(default_template_url)) - end - - it 'uses that template to generate the module' do - expect(PDK::Template).to receive(:with).with(Addressable::URI.parse('cli-template#main'), anything).and_yield(template_dir) - expect(logger).to receive(:info).with(a_string_matching(/generated at path/i)) - expect(logger).to receive(:info).with(a_string_matching(/In your module directory, add classes with the 'pdk new class' command/i)) - - described_class.invoke(invoke_opts.merge('template-url': 'cli-template')) - end - - it 'takes precedence over the template-url answer' do - PDK.config.set(['user', 'module_defaults', 'template-url'], 'answer-template') - expect(PDK::Template).to receive(:with).with(Addressable::URI.parse('cli-template#main'), anything).and_yield(template_dir) - described_class.invoke(invoke_opts.merge('template-url': 'cli-template')) - end - - it 'saves the template-url and template-ref to the answer file if it is not the default template' do - described_class.invoke(invoke_opts.merge('template-url': 'cli-template')) - expect(PDK.config.get(['user', 'module_defaults', 'template-url'])).to eq(Addressable::URI.parse('cli-template#main').to_s) - end - - it 'saves the template-url and template-ref to the answer file if it is not the default ref' do - described_class.invoke(invoke_opts.merge('template-url': default_template_url, 'template-ref': '1.2.3')) - expect(PDK.config.get(['user', 'module_defaults', 'template-url'])).to eq("#{default_template_url}#1.2.3") - end - - it 'clears the saved template-url answer if it is the default template' do - described_class.invoke(invoke_opts.merge('template-url': default_template_url)) - expect(PDK.config.get(['user', 'module_defaults', 'template-url'])).to be_nil - end - end - - context 'when a template-url is not supplied on the command line' do - before do - allow(PDK::Util::Filesystem).to receive(:mv).with(temp_target_dir, target_dir).and_return(0) - allow(PDK::Util).to receive(:development_mode?).and_return(true) - end - - context 'and a template-url answer exists' do - it 'uses the template-url from the answer file to generate the module' do - PDK.config.set(['user', 'module_defaults', 'template-url'], 'answer-template') - expect(PDK::Template).to receive(:with).with(Addressable::URI.parse('answer-template'), anything).and_yield(template_dir) - expect(logger).to receive(:info).with(a_string_matching(/generated at path/i)) - expect(logger).to receive(:info).with(a_string_matching(/In your module directory, add classes with the 'pdk new class' command/i)) - - described_class.invoke(invoke_opts) - end - end - - context 'and no template-url answer exists' do - context 'and pdk is installed from packages' do - before do - allow(PDK::Util).to receive_messages(package_install?: true, package_cachedir: '/tmp/package/cache') - end - - it 'uses the vendored template url' do - template_uri = "file:///tmp/package/cache/pdk-templates.git##{PDK::Util::TemplateURI.default_template_ref}" - expect(PDK::Template).to receive(:with).with(Addressable::URI.parse(template_uri), anything).and_yield(template_dir) - before = PDK.config.get(['user', 'module_defaults', 'template-url']) - - described_class.invoke(invoke_opts) - expect(PDK.config.get(['user', 'module_defaults', 'template-url'])).to eq(before) - end - end - - context 'and pdk is not installed from packages' do - before do - allow(PDK::Util).to receive(:package_install?).and_return(false) - end - - it 'uses the default template to generate the module' do - expect(PDK::Template).to receive(:with).with(any_args).and_yield(template_dir) - before = PDK.config.get(['user', 'module_defaults', 'template-url']) - - described_class.invoke(invoke_opts) - expect(PDK.config.get(['user', 'module_defaults', 'template-url'])).to eq(before) - end - end - end - end - end - end - - describe '.module_interview' do - subject(:answers) do - interview_metadata - PDK.config.get(['user', 'module_defaults']) - end - - let(:interview_metadata) do - metadata = PDK::Module::Metadata.new - metadata.update!(default_metadata) - described_class.module_interview(metadata, options) - metadata.data - end - let(:module_name) { 'bar' } - let(:default_metadata) { {} } - let(:options) { { module_name: module_name } } - - before do - prompt = TTY::Prompt::Test.new - allow(TTY::Prompt).to receive(:new).and_return(prompt) - prompt.input << ("#{responses.join("\r")}\r") - prompt.input.rewind - - allow($stdout).to receive(:puts).with(a_string_matching(/manually updating the metadata.json file/m)) - allow($stdout).to receive(:puts).with(a_string_matching(/ask you \d+ questions/)) - allow($stdout).to receive(:puts).with(no_args) - allow(PDK::Util::Filesystem).to receive(:file?).with('metadata.json').and_return(false) - end - - context 'when only interviewing for specific missing fields' do - let(:options) do - { only_ask: ['source'] } - end - - let(:default_metadata) do - { - 'name' => 'test-module' - } - end - - let(:responses) do - [ - 'https://something', - 'yes' - ] - end - - before do - allow(PDK::Util::Filesystem).to receive(:file?).with('metadata.json').and_return(true) - end - - it 'populates the metadata object based on user input' do - expected_metadata = PDK::Module::Metadata.new.update!(default_metadata).data.dup - expected_metadata['source'] = 'https://something' - - expect(interview_metadata).to eq(expected_metadata) - end - - context 'and the module name contains underscores' do - let(:default_metadata) do - { - 'name' => 'test-long_module_name' - } - end - - it 'does not reinterview for the module name' do - expected_metadata = PDK::Module::Metadata.new.update!(default_metadata).data.dup - expected_metadata['source'] = 'https://something' - - expect(interview_metadata).to eq(expected_metadata) - end - end - end - - context 'with --full-interview' do - let(:options) { { module_name: module_name, 'full-interview': true } } - - context 'when provided answers to all the questions' do - include_context 'allow summary to be printed to stdout' - - let(:responses) do - [ - 'foo', - '2.2.0', - 'William Hopper', - 'Apache-2.0', - '', - 'A simple module to do some stuff.', - 'github.com/whopper/bar', - 'forge.puppet.com/whopper/bar', - 'tickets.foo.com/whopper/bar', - 'yes' - ] - end - - # rubocop:disable RSpec/ExampleLength - it 'populates the Metadata object based on user input' do - expect(interview_metadata).to include( - 'name' => 'foo-bar', - 'version' => '2.2.0', - 'author' => 'William Hopper', - 'license' => 'Apache-2.0', - 'summary' => 'A simple module to do some stuff.', - 'source' => 'github.com/whopper/bar', - 'project_page' => 'forge.puppet.com/whopper/bar', - 'issues_url' => 'tickets.foo.com/whopper/bar', - 'operatingsystem_support' => [ - { - 'operatingsystem' => 'CentOS', - 'operatingsystemrelease' => ['7', '8', '9'] - }, - { - 'operatingsystem' => 'OracleLinux', - 'operatingsystemrelease' => ['7'] - }, - { - 'operatingsystem' => 'RedHat', - 'operatingsystemrelease' => ['7', '8', '9'] - }, - { - 'operatingsystem' => 'Scientific', - 'operatingsystemrelease' => ['7'] - }, - { - 'operatingsystem' => 'Rocky', - 'operatingsystemrelease' => ['8'] - }, - { - 'operatingsystem' => 'AlmaLinux', - 'operatingsystemrelease' => ['8'] - }, - { - 'operatingsystem' => 'Debian', - 'operatingsystemrelease' => ['10', '11', '12'] - }, - { - 'operatingsystem' => 'Ubuntu', - 'operatingsystemrelease' => ['18.04', '20.04', '22.04'] - }, - { - 'operatingsystem' => 'windows', - 'operatingsystemrelease' => ['2019', '2022', '10', '11'] - } - ] - ) - end - # rubocop:enable RSpec/ExampleLength - - it 'saves the forge username to the answer file' do - expect(answers['forge_username']).to eq('foo') - end - - it 'saves the module author to the answer file' do - expect(answers['author']).to eq('William Hopper') - end - - it 'saves the license to the answer file' do - expect(answers['license']).to eq('Apache-2.0') - end - end - - context 'when the user chooses the default values for everything' do - include_context 'allow summary to be printed to stdout' - - let(:options) { { module_name: 'bar', username: 'defaultauthor', 'full-interview': true } } - let(:default_metadata) do - { - 'author' => 'defaultauthor', - 'version' => '0.0.1', - 'summary' => 'default summary', - 'source' => 'default source', - 'license' => 'default license' - } - end - - let(:responses) do - [ - '', - '', - '', - '', - '', - '', - '', - '', - '' - ] - end - - it 'populates the interview question defaults with existing metadata values' do - expect(interview_metadata).to include( - 'name' => 'defaultauthor-bar', - 'version' => '0.0.1', - 'author' => 'defaultauthor', - 'license' => 'default license', - 'summary' => 'default summary', - 'source' => 'default source' - ) - end - - it 'saves the forge username to the answer file' do - expect(answers['forge_username']).to eq('defaultauthor') - end - - it 'saves the module author to the answer file' do - expect(answers['author']).to eq('defaultauthor') - end - - it 'saves the license to the answer file' do - expect(answers['license']).to eq('default license') - end - end - end - - context 'when there is no module_name provided' do - include_context 'allow summary to be printed to stdout' - - let(:options) { { license: 'MIT' } } - let(:responses) do - [ - 'mymodule', - 'myforgename', - 'William Hopper', - '', - 'yes' - ] - end - - it 'populates the Metadata object based on user input for both module name and forge name' do - expect(interview_metadata).to include( - 'name' => 'myforgename-mymodule', - 'version' => '0.1.0', - 'author' => 'William Hopper', - 'license' => 'MIT', - 'summary' => '', - 'source' => '', - 'project_page' => nil, - 'issues_url' => nil - ) - end - end - - context 'when the user provides the license as a command line option' do - include_context 'allow summary to be printed to stdout' - - let(:options) { { module_name: module_name, license: 'MIT' } } - let(:responses) do - [ - 'foo', - 'William Hopper', - '', - 'yes' - ] - end - - it 'populates the Metadata object based on user input' do - expect(interview_metadata).to include( - 'name' => 'foo-bar', - 'version' => '0.1.0', - 'author' => 'William Hopper', - 'license' => 'MIT', - 'summary' => '', - 'source' => '', - 'project_page' => nil, - 'issues_url' => nil - ) - end - - it 'saves the forge username to the answer file' do - expect(answers['forge_username']).to eq('foo') - end - - it 'saves the module author to the answer file' do - expect(answers['author']).to eq('William Hopper') - end - - it 'saves the license to the answer file' do - expect(answers['license']).to eq('MIT') - end - end - - context 'when the user cancels the interview' do - let(:responses) do - [ - "foo\003" # \003 being the equivalent to the user hitting Ctrl-C - ] - end - - it 'exits cleanly' do - allow(logger).to receive(:info).with(a_string_matching(/interview cancelled/i)) - expect { interview_metadata }.to exit_zero - end - end - - context 'when the user does not confirm the metadata' do - include_context 'allow summary to be printed to stdout' - - let(:responses) do - [ - 'foo', - 'William Hopper', - 'Apache-2.0', - '', - 'no' - ] - end - - it 'exits cleanly' do - allow(logger).to receive(:info).with(a_string_matching(/Process cancelled; exiting./i)) - expect { interview_metadata }.to exit_zero - end - end - - context 'when the user does not confirm with yes or no' do - include_context 'allow summary to be printed to stdout' - - let(:responses) do - [ - 'foo', - 'William Hopper', - 'Apache-2.0', - '', - 'test', # incorrect confirmation - 'yes' # reattempted confirmation - ] - end - - it 'reattempts the confirmation' do - allow($stdout).to receive(:puts).and_call_original - expect { interview_metadata }.not_to raise_error - - expect(interview_metadata).to include( - 'name' => 'foo-bar', - 'version' => '0.1.0', - 'author' => 'William Hopper', - 'license' => 'Apache-2.0', - 'summary' => '', - 'source' => '', - 'project_page' => nil, - 'issues_url' => nil - ) - end - end - - context 'when the user selects operating systems' do - include_context 'allow summary to be printed to stdout' - - let(:responses) do - [ - 'foo', - 'William Hopper', - 'Apache-2.0', - "\e[A 1 ", # \e[A == up arrow - 'yes' - ] - end - - it 'includes the modified operatingsystem_support value in the metadata' do - allow($stdout).to receive(:puts).and_call_original - expect { interview_metadata }.not_to raise_error - - expect(interview_metadata).to include( - 'name' => 'foo-bar', - 'version' => '0.1.0', - 'author' => 'William Hopper', - 'license' => 'Apache-2.0', - 'summary' => '', - 'source' => '', - 'project_page' => nil, - 'issues_url' => nil - ) - - expect(interview_metadata['operatingsystem_support']).not_to be_nil - - [ - { 'operatingsystem' => 'Debian', 'operatingsystemrelease' => ['10', '11', '12'] }, - { 'operatingsystem' => 'Ubuntu', 'operatingsystemrelease' => ['18.04', '20.04', '22.04'] }, - { 'operatingsystem' => 'windows', 'operatingsystemrelease' => ['2019', '2022', '10', '11'] }, - { 'operatingsystem' => 'Solaris', 'operatingsystemrelease' => ['11'] } - ].each do |expected_os| - expect(interview_metadata['operatingsystem_support']).to include(expected_os) - end - end - end - end - - describe '.prepare_metadata' do - subject(:metadata) { described_class.prepare_metadata(options) } - - before do - allow(described_class).to receive(:username_from_login).and_return('testlogin') - end - - let(:options) { { module_name: 'baz' } } - - context 'when provided :skip-interview => true' do - let(:options) { { module_name: 'baz', 'skip-interview': true } } - - it 'does not perform the module interview' do - expect(described_class).not_to receive(:module_interview) - - metadata - end - end - - context 'when there are no saved answers' do - before do - allow(described_class).to receive(:module_interview).with(any_args) - end - - it 'guesses the forge username from the system login' do - expect(metadata.data).to include('name' => 'testlogin-baz') - end - - it 'sets the version number to a 0.x release' do - expect(metadata.data).to include('version' => a_string_starting_with('0.')) - end - - it 'has no dependencies' do - expect(metadata.data).to include( - 'dependencies' => [] - ) - end - end - - context 'when an answer file exists with answers' do - before do - allow(described_class).to receive(:module_interview).with(any_args) - - PDK.config.set(['user', 'module_defaults', 'forge_username'], 'testuser123') - PDK.config.set(['user', 'module_defaults', 'license'], 'MIT') - PDK.config.set(['user', 'module_defaults', 'author'], 'Test User') - end - - it 'uses the saved forge_username answer' do - expect(metadata.data).to include('name' => 'testuser123-baz') - end - - it 'uses the saved author answer' do - expect(metadata.data).to include('author' => 'Test User') - end - - it 'uses the saved license answer' do - expect(metadata.data).to include('license' => 'MIT') - end - - context 'and the user specifies a license as a command line option' do - let(:options) { { module_name: 'baz', license: 'Apache-2.0' } } - - it 'prefers the license specified on the command line over the saved license answer' do - expect(metadata.data).to include('license' => 'Apache-2.0') - end - end - end - end - - describe '.prepare_module_directory' do - let(:path) { 'test123' } - - it 'creates a skeleton directory structure' do - expect(PDK::Util::Filesystem).to receive(:mkdir_p).with(File.join(path, 'examples')) - expect(PDK::Util::Filesystem).to receive(:mkdir_p).with(File.join(path, 'files')) - expect(PDK::Util::Filesystem).to receive(:mkdir_p).with(File.join(path, 'manifests')) - expect(PDK::Util::Filesystem).to receive(:mkdir_p).with(File.join(path, 'templates')) - expect(PDK::Util::Filesystem).to receive(:mkdir_p).with(File.join(path, 'tasks')) - - described_class.prepare_module_directory(path) - end - - context 'when it fails to create a directory' do - before do - allow(PDK::Util::Filesystem).to receive(:mkdir_p).with(anything).and_raise(SystemCallError, 'some message') - end - - it 'raises a FatalError' do - expect do - described_class.prepare_module_directory(path) - end.to raise_error(PDK::CLI::FatalError, /unable to create directory.+some message/i) - end - end - end - - describe '.username_from_login' do - subject { described_class.username_from_login } - - before do - allow(Etc).to receive(:getlogin).and_return(login) - end - - context 'when the login is entirely alphanumeric' do - let(:login) { 'testuser123' } - - it 'returns the unaltered login' do - expect(subject).to eq(login) - end - end - - context 'when Etc.getlogin returns nil' do - let(:login) { nil } - - it 'warns the user and returns the string "username"' do - expect(subject).to eq('username') - end - end - - context 'when the login contains some non-alphanumeric characters' do - let(:login) { 'test_user' } - - it 'warns the user and returns the login with the characters removed' do - expect(logger).to receive(:debug).with(a_string_matching(/not a valid forge username/i)) - expect(subject).to eq('testuser') - end - end - - context 'when the login contains some upper case characters' do - let(:login) { 'Administrator' } - - it 'warns the user and returns the login with the characters downcased' do - expect(logger).to receive(:debug).with(a_string_matching(/not a valid forge username/i)) - expect(subject).to eq('administrator') - end - end - - context 'when the login contains only non-alphanumeric characters' do - let(:login) { 'Αρίσταρχος ό Σάμιος' } - - it 'warns the user and returns the string "username"' do - expect(logger).to receive(:debug).with(a_string_matching(/not a valid forge username/i)) - expect(subject).to eq('username') - end - end - end -end diff --git a/spec/unit/pdk/module/update_spec.rb b/spec/unit/pdk/module/update_spec.rb deleted file mode 100644 index 833f4b0dc..000000000 --- a/spec/unit/pdk/module/update_spec.rb +++ /dev/null @@ -1,449 +0,0 @@ -require 'spec_helper' -require 'pdk/module/update' - -describe PDK::Module::Update do - let(:module_root) { File.join('path', 'to', 'update') } - let(:options) { {} } - let(:mock_metadata) do - instance_double( - PDK::Module::Metadata, - data: { - 'name' => 'mock-module', - 'template-url' => template_url, - 'template-ref' => template_ref - } - ) - end - let(:template_url) { 'https://github.com/puppetlabs/pdk-templates' } - let(:template_ref) { nil } - - def module_path(relative_path) - File.join(module_root, relative_path) - end - - shared_context 'with mock metadata' do - before do - allow(PDK::Module::Metadata).to receive(:from_file).with(module_path('metadata.json')).and_return(mock_metadata) - end - end - - describe '#pinned_to_puppetlabs_template_tag?' do - subject { instance.pinned_to_puppetlabs_template_tag? } - - let(:instance) { described_class.new(module_root, options) } - - include_context 'with mock metadata' - - context 'when running from a package install' do - include_context 'packaged install' - - before do - allow(PDK::Util).to receive(:development_mode?).and_return(false) - end - - context 'and the template-url is set to the pdk-default keyword' do - let(:template_url) { 'pdk-default#1.0.0' } - - before do - allow(PDK::Util::Git).to receive(:tag?).with(any_args).and_return(true) - end - - it { is_expected.to be_falsey } - end - - context 'and the template-url is not set to the pdk-default keyword' do - let(:template_url) { 'https://github.com/puppetlabs/pdk-templates' } - - context 'and the url fragment is set to a tag name' do - let(:template_url) { "#{super()}#1.0.0" } - - before do - allow(PDK::Util::Git).to receive(:tag?).with(*template_url.split('#')).and_return(true) - end - - it { is_expected.to be_truthy } - end - - context 'and the url fragment is set to the latest template tag' do - let(:template_url) { super() + "##{PDK::TEMPLATE_REF}" } - - before do - allow(PDK::Util::Git).to receive(:tag?).with(*template_url.split('#')).and_return(true) - end - - it { is_expected.to be_falsey } - end - - context 'and the url fragment is not set to a tag name' do - let(:template_url) { "#{super()}#my_branch" } - - before do - allow(PDK::Util::Git).to receive(:tag?).with(*template_url.split('#')).and_return(false) - end - - it { is_expected.to be_falsey } - end - end - end - - context 'when not running from a package install' do - include_context 'not packaged install' - - context 'and the template-url is set to the pdk-default keyword' do - let(:template_url) { 'pdk-default#1.0.0' } - - before do - allow(PDK::Util::Git).to receive(:tag?).with(any_args).and_return(true) - end - - it { is_expected.to be_truthy } - end - - context 'and the template-url is not set to the pdk-default keyword' do - let(:template_url) { 'https://github.com/puppetlabs/pdk-templates' } - - context 'and the url fragment is set to a tag name' do - let(:template_url) { "#{super()}#1.0.0" } - - before do - allow(PDK::Util::Git).to receive(:tag?).with(*template_url.split('#')).and_return(true) - end - - it { is_expected.to be_truthy } - end - - context 'and the url fragment is set to the latest template tag' do - let(:template_url) { super() + "##{PDK::TEMPLATE_REF}" } - - before do - allow(PDK::Util::Git).to receive(:tag?).with(*template_url.split('#')).and_return(true) - end - - it { is_expected.to be_falsey } - end - - context 'and the url fragment is not set to a tag name' do - let(:template_url) { "#{super()}#my_branch" } - - before do - allow(PDK::Util::Git).to receive(:tag?).with(*template_url.split('#')).and_return(false) - end - - it { is_expected.to be_falsey } - end - end - end - end - - describe '#run' do - let(:instance) { described_class.new(module_root, options) } - let(:template_ref) { '1.3.2-0-g1234567' } - let(:changes) { true } - - include_context 'with mock metadata' - - before do - allow(instance).to receive(:stage_changes!) - allow(instance).to receive(:print_summary) - allow(instance).to receive(:new_version).and_return('1.4.0') - allow(instance).to receive(:print_result) - allow(PDK::Util::Git).to receive(:tag?).with(String, String).and_return(true) - allow(instance.update_manager).to receive(:sync_changes!) - allow(instance.update_manager).to receive(:changes?).and_return(changes) - allow(instance.update_manager).to receive(:unlink_file).with('Gemfile.lock') - allow(instance.update_manager).to receive(:unlink_file).with(File.join('.bundle', 'config')) - allow(PDK::Util::Bundler).to receive(:ensure_bundle!) - end - - after do - instance.run - end - - context 'when the version is the same' do - let(:options) { { noop: true } } - - before do - allow(instance).to receive(:current_version).and_return('1.4.0') - end - - context 'but there are changes' do - let(:changes) { true } - - it 'does add debug message' do - expect(logger).to receive(:debug).with(a_string_matching(/This module is already up to date with version 1.4.0 of the template/i)) - end - - it 'doesn\'t add report with no changes' do - expect(PDK::Report.default_target).not_to receive(:puts).with(a_string_matching(/No changes required./i)) - end - end - - context 'but there are no changes' do - let(:changes) { false } - - it 'does add debug message' do - expect(logger).to receive(:debug).with(a_string_matching(/This module is already up to date with version 1.4.0 of the template/)) - end - - it 'does add report with no changes' do - expect(PDK::Report.default_target).to receive(:puts).with(a_string_matching(/No changes required./i)) - end - end - end - - context 'when using the default template' do - let(:options) { { noop: true } } - let(:template_url) { PDK::Util::TemplateURI.default_template_uri.metadata_format } - - it 'refers to the template as the default template' do - expect(logger).to receive(:info).with(a_string_matching(/using the default template/i)) - end - end - - context 'when using a custom template' do - let(:options) { { noop: true } } - let(:template_url) { 'https://my/custom/template' } - - it 'refers to the template by its URL or path' do - expect(logger).to receive(:info).with(a_string_matching(/using the template at #{Regexp.escape(template_url)}/i)) - end - end - - context 'when running in noop mode' do - let(:options) { { noop: true } } - - it 'does not prompt the user to make the changes' do - expect(PDK::CLI::Util).not_to receive(:prompt_for_yes) - end - - it 'does not sync the pending changes' do - expect(instance.update_manager).not_to receive(:sync_changes!) - end - end - - context 'when not running in noop mode' do - context 'with force' do - let(:options) { { force: true } } - - it 'does not prompt the user to make the changes' do - expect(PDK::CLI::Util).not_to receive(:prompt_for_yes) - end - - it 'syncs the pending changes' do - expect(instance.update_manager).to receive(:sync_changes!) - end - end - - context 'without force' do - it 'prompts the user to make the changes' do - expect(PDK::CLI::Util).to receive(:prompt_for_yes) - end - - context 'if the user chooses to continue' do - before do - allow(PDK::CLI::Util).to receive(:prompt_for_yes).and_return(true) - end - - it 'syncs the pending changes' do - expect(instance.update_manager).to receive(:sync_changes!) - end - - it 'prints the result' do - expect(instance).to receive(:print_result) - end - end - - context 'if the user chooses not to continue' do - before do - allow(PDK::CLI::Util).to receive(:prompt_for_yes).and_return(false) - end - - it 'does not sync the pending changes' do - expect(instance.update_manager).not_to receive(:sync_changes!) - end - - it 'does not print the result' do - expect(instance).not_to receive(:print_result) - end - end - end - end - end - - describe '#module_metadata' do - subject(:result) { described_class.new(module_root, options).module_metadata } - - context 'when the metadata.json can be read' do - include_context 'with mock metadata' - - it 'returns the metadata object' do - expect(subject).to eq(mock_metadata) - end - end - - context 'when the metadata.json can not be read' do - before do - allow(PDK::Module::Metadata).to receive(:from_file).with(module_path('metadata.json')).and_raise(ArgumentError, 'some error') - end - - it 'raises an ExitWithError exception' do - expect { -> { result }.call }.to raise_error(PDK::CLI::ExitWithError, /some error/i) - end - end - end - - describe '#template_uri' do - subject { described_class.new(module_root, options).template_uri.to_s } - - include_context 'with mock metadata' - - it 'returns the template-url value from the module metadata' do - expect(subject).to eq('https://github.com/puppetlabs/pdk-templates') - end - end - - describe '#current_version' do - subject { described_class.new(module_root, options).current_version } - - include_context 'with mock metadata' - - context 'when the template-ref describes a git tag' do - let(:template_ref) { '1.3.2-0-g07678c8' } - - it 'returns the tag name' do - expect(subject).to eq('1.3.2') - end - end - - context 'when the template-ref describes a branch commit' do - let(:template_ref) { 'heads/main-4-g1234abc' } - - it 'returns the branch name and the commit SHA' do - expect(subject).to eq('main@1234abc') - end - end - end - - describe '#new_version' do - subject { described_class.new(module_root, options).new_version } - - include_context 'with mock metadata' - - context 'when the default_template_ref specifies a tag' do - before do - allow(PDK::Util).to receive(:development_mode?).and_return(false) - end - - it 'returns the tag name' do - expect(subject).to eq(PDK::TEMPLATE_REF) - end - end - - context 'when the default_template_ref specifies a branch head' do - before do - stub_const('PDK::TEMPLATE_REF', '2.7.1') - allow(PDK::Util::Git).to receive(:ls_remote) - .with(template_url, 'main') - .and_return('3cdd84e8f0aae30bf40d15556482fc8752899312') - end - - include_context 'with mock metadata' - let(:template_ref) { 'main-0-g07678c8' } - - it 'returns the branch name and the commit SHA' do - expect(subject).to eq('main@3cdd84e') - end - end - end - - describe '#new_template_version' do - subject { described_class.new(module_root, options).new_template_version } - - include_context 'with mock metadata' - - let(:module_template_ref) { '0.0.1' } - let(:module_template_uri) do - instance_double( - PDK::Util::TemplateURI, - default?: true, - bare_uri: 'https://github.com/puppetlabs/pdk-templates', - uri_fragment: module_template_ref - ) - end - let(:template_url) { "https://github.com/puppetlabs/pdk-templates##{module_template_ref}" } - - before do - allow(PDK::Util::TemplateURI).to receive(:new).and_call_original - allow(PDK::Util::TemplateURI).to receive(:new).with(template_url).and_return(module_template_uri) - end - - context 'when a template-ref is specified' do - let(:options) { { 'template-ref': 'my-custom-branch' } } - - it 'returns the specified template-ref value' do - expect(subject).to eq('my-custom-branch') - end - end - - context 'when template-ref is not specified' do - context 'and the module is using the default template' do - before do - allow(module_template_uri).to receive(:default?).and_return(true) - end - - context 'and the ref of the template is a tag' do - before do - allow(PDK::Util::Git).to receive(:tag?).with(String, module_template_ref).and_return(true) - end - - context 'and PDK is running from a package install' do - before do - allow(PDK::Util).to receive_messages(package_install?: true, package_cachedir: File.join('package', 'cachedir')) - allow(PDK::Util::Version).to receive(:git_ref).and_return('1234acb') - end - - it 'returns the default ref' do - expect(subject).to eq(PDK::Util::TemplateURI.default_template_ref) - end - end - - context 'and PDK is not running from a package install' do - before do - allow(PDK::Util).to receive(:package_install?).and_return(false) - end - - it 'returns the ref from the metadata' do - expect(subject).to eq(template_url.split('#').last) - end - end - end - - context 'but the ref of the template is not a tag' do - before do - allow(PDK::Util::Git).to receive(:tag?).with(String, module_template_ref).and_return(false) - end - - it 'returns the ref from the metadata' do - expect(subject).to eq(template_url.split('#').last) - end - end - end - - context 'but the module is not using the default template' do - before do - allow(module_template_uri).to receive(:default?).and_return(false) - end - - it 'returns the ref stored in the template_url metadata' do - expect(subject).to eq(template_url.split('#').last) - end - end - end - end - - describe '#convert?' do - subject { described_class.new(module_root).convert? } - - it { is_expected.to be_falsey } - end -end diff --git a/spec/unit/pdk/util/template_uri_spec.rb b/spec/unit/pdk/util/template_uri_spec.rb deleted file mode 100644 index 95ddc31f3..000000000 --- a/spec/unit/pdk/util/template_uri_spec.rb +++ /dev/null @@ -1,699 +0,0 @@ -require 'spec_helper' -require 'pdk/util/template_uri' -require 'addressable' - -describe PDK::Util::TemplateURI do - subject(:template_uri) do - described_class.new(opts_or_uri) - end - - include_context 'mock configuration' - - before do - PDK.config.set(['user', 'module_defaults', 'template-url'], nil) - allow(PDK::Util).to receive_messages(module_root: nil, package_install?: false) - end - - describe '.new' do - context 'with a string' do - context 'that contains a valid URI' do - let(:opts_or_uri) { 'https://github.com/my/pdk-templates.git#custom' } - - it 'can return a string for storing' do - expect(template_uri.to_s).to eq('https://github.com/my/pdk-templates.git#custom') - end - end - - context 'that contains the default template keyword' do - let(:opts_or_uri) { 'pdk-default#1.2.3' } - - before do - allow(PDK::Util).to receive(:package_install?).and_return(false) - end - - it 'converts the keyword to the default template URI' do - expect(template_uri.to_s).to eq('https://github.com/puppetlabs/pdk-templates#1.2.3') - end - end - - context 'that contains an invalid URI' do - let(:opts_or_uri) { 'https://' } - - it 'raises a FatalError' do - expect do - template_uri - end.to raise_error(PDK::CLI::FatalError, /initialization with a non-uri string/i) - end - end - end - - context 'with an Addressable::URI' do - let(:opts_or_uri) { Addressable::URI.parse('https://github.com/my/pdk-templates.git#custom') } - - it 'can return a string for storing' do - expect(template_uri.to_s).to eq('https://github.com/my/pdk-templates.git#custom') - end - end - - context 'with a PDK::Util::TemplateURI' do - let(:opts_or_uri) { described_class.new('https://example.com/my/template') } - - it 'can return a string for storing' do - expect(template_uri.to_s).to eq(opts_or_uri.to_s) - end - end - - context 'with options' do - let(:opts_or_uri) do - { - 'template-url': 'https://github.com/my/pdk-templates.git', - 'template-ref': 'custom' - } - end - - it 'can return a string for storing' do - allow(PDK::Util::Git).to receive(:repo?).with(anything).and_return(true) - expect(template_uri.to_s).to eq('https://github.com/my/pdk-templates.git#custom') - end - end - - context 'combinations of answers, options, and defaults' do - let(:module_root) { '/path/to/module' } - let(:pdk_version) { '1.2.3' } - let(:template_url) { 'metadata-templates' } - let(:template_ref) { nil } - let(:mock_metadata) do - instance_double( - PDK::Module::Metadata, - data: { - 'pdk-version' => pdk_version, - 'template-url' => template_url, - 'template-ref' => template_ref - } - ) - end - - let(:opts_or_uri) { {} } - let(:default_uri) { "#{described_class.default_template_uri}##{described_class.default_template_ref}" } - - before do - allow(PDK::Util::Git).to receive(:repo?).with(anything).and_return(true) - allow(PDK::Util).to receive_messages(module_root: module_root, development_mode?: false) - end - - context 'when passed no options' do - context 'and there are no metadata or answers' do - before do - allow(PDK::Util::Filesystem).to receive(:file?).with(File.join(module_root, 'metadata.json')).and_return(false) - end - - it 'returns the default template' do - expect(template_uri.to_s).to eq(default_uri) - end - end - - context 'and there are only answers' do - before do - PDK.config.set(['user', 'module_defaults', 'template-url'], 'answer-templates') - allow(PDK::Util::Filesystem).to receive(:file?).with(File.join(module_root, 'metadata.json')).and_return(false) - end - - it 'returns the answers template' do - expect(template_uri.to_s).to eq('answer-templates') - end - - context 'and the answer file template is invalid' do - before do - allow(described_class).to receive(:valid_template?).with(anything).and_call_original - allow(described_class).to receive(:valid_template?).with({ uri: anything, type: anything, allow_fallback: true }).and_return(false) - end - - it 'returns the default template' do - expect(template_uri.to_s).to eq(default_uri) - end - end - end - - context 'and there are metadata and answers' do - before do - PDK.config.set(['user', 'module_defaults', 'template-url'], 'answer-templates') - end - - it 'returns the metadata template' do - allow(PDK::Module::Metadata).to receive(:from_file).with('/path/to/module/metadata.json').and_return(mock_metadata) - allow(PDK::Util::Filesystem).to receive(:file?).with('/path/to/module/metadata.json').and_return(true) - allow(PDK::Util::Filesystem).to receive(:file?).with(/PDK_VERSION/).and_return(true) - expect(template_uri.to_s).to eq('metadata-templates') - end - end - end - - context 'when there are metadata and answers' do - before do - PDK.config.set(['user', 'module_defaults', 'template-url'], 'answer-templates') - allow(PDK::Util::Filesystem).to receive(:file?).with(File.join(PDK::Util.module_root, 'metadata.json')).and_return(true) - allow(PDK::Module::Metadata).to receive(:from_file).with(File.join(PDK::Util.module_root, 'metadata.json')).and_return(mock_metadata) - end - - context 'and passed template-url' do - let(:opts_or_uri) { { 'template-url': 'cli-templates' } } - - it 'returns the specified template' do - expect(template_uri.to_s).to eq('cli-templates#main') - end - end - - context 'and passed windows template-url' do - let(:opts_or_uri) { { 'template-url': 'C:\\cli-templates' } } - - it 'returns the specified template' do - allow(Gem).to receive(:win_platform?).and_return(true) - expect(template_uri.to_s).to eq('C:\\cli-templates#main') - end - end - - context 'and passed template-url and template-ref' do - let(:opts_or_uri) { { 'template-url': 'cli-templates', 'template-ref': 'cli-ref' } } - - it 'returns the specified template and ref' do - uri = Addressable::URI.parse('cli-templates') - uri.fragment = 'cli-ref' - expect(template_uri.to_s).to eq(uri.to_s) - end - end - end - end - end - - describe '.bare_uri' do - context 'when the uri has a fragment' do - let(:opts_or_uri) { 'https://github.com/my/pdk-templates.git#custom' } - - it 'returns just the url portion' do - expect(template_uri.bare_uri).to eq 'https://github.com/my/pdk-templates.git' - end - end - - context 'when the uri has no fragment' do - let(:opts_or_uri) { 'https://github.com/my/pdk-templates.git' } - - it 'returns just the url portion' do - expect(template_uri.bare_uri).to eq 'https://github.com/my/pdk-templates.git' - end - end - - context 'when the uri is an absolute path' do - context 'on linux' do - let(:opts_or_uri) { '/my/pdk-templates.git#custom' } - - it 'returns url portion' do - allow(Gem).to receive(:win_platform?).and_return(false) - expect(template_uri.bare_uri).to eq '/my/pdk-templates.git' - end - end - - context 'on windows' do - let(:opts_or_uri) { '/C:/my/pdk-templates.git#custom' } - - it 'returns url portion' do - allow(Gem).to receive(:win_platform?).and_return(true) - expect(template_uri.bare_uri).to eq 'C:/my/pdk-templates.git' - end - end - end - end - - describe '.uri_fragment' do - context 'when the uri has a fragment' do - let(:opts_or_uri) { 'https://github.com/my/pdk-templates.git#custom' } - - it 'returns just the fragment portion' do - expect(template_uri.uri_fragment).to eq 'custom' - end - end - - context 'when the uri has no fragment' do - let(:opts_or_uri) { 'https://github.com/my/pdk-templates.git' } - - it 'returns the default ref' do - expect(template_uri.uri_fragment).to eq(described_class.default_template_ref(opts_or_uri)) - end - end - end - - describe '.shell_path' do - context 'when the uri has a schema' do - context 'on linux' do - let(:opts_or_uri) { 'file:///my/pdk-templates.git#fragment' } - - it 'returns the path' do - allow(Gem).to receive(:win_platform?).and_return(false) - expect(template_uri.shell_path).to eq '/my/pdk-templates.git' - end - end - - context 'on windows' do - let(:opts_or_uri) { 'file:///C:/my/pdk-templates.git#fragment' } - - it 'returns the path' do - allow(Gem).to receive(:win_platform?).and_return(true) - expect(template_uri.shell_path).to eq 'C:/my/pdk-templates.git' - end - end - end - - context 'when the uri is just an absolute path' do - context 'on linux' do - let(:opts_or_uri) { '/my/pdk-templates.git#custom' } - - it 'returns url portion' do - allow(Gem).to receive(:win_platform?).and_return(false) - expect(template_uri.shell_path).to eq '/my/pdk-templates.git' - end - end - - context 'on windows' do - let(:opts_or_uri) { '/C:/my/pdk-templates.git#custom' } - - it 'returns url portion' do - allow(Gem).to receive(:win_platform?).and_return(true) - expect(template_uri.shell_path).to eq 'C:/my/pdk-templates.git' - end - end - end - end - - describe '.default_template_uri' do - subject(:default_uri) { described_class.default_template_uri } - - context 'when it is a package install' do - before do - allow(PDK::Util).to receive(:package_install?).and_return(true) - end - - it 'returns the file template repo' do - allow(PDK::Util).to receive(:package_cachedir).and_return('/path/to/pdk') - expect(default_uri.to_s).to eq('file:///path/to/pdk/pdk-templates.git') - end - end - - context 'when it is not a package install' do - before do - allow(PDK::Util).to receive(:package_install?).and_return(false) - end - - it 'returns puppetlabs template url' do - expect(default_uri.to_s).to eq('https://github.com/puppetlabs/pdk-templates') - end - end - end - - describe '.default_template_ref' do - subject { described_class.default_template_ref(uri) } - - before do - allow(PDK::Util).to receive(:development_mode?).and_return(development_mode) - end - - context 'with a custom template repo' do - let(:uri) { described_class.new('https://github.com/my/template') } - - context 'in development mode' do - let(:development_mode) { true } - - it 'returns main' do - expect(subject).to eq('main') - end - end - - context 'not in development mode' do - let(:development_mode) { false } - - it 'returns main' do - expect(subject).to eq('main') - end - end - end - - context 'with the default template repo' do - let(:uri) { described_class.default_template_uri } - - context 'not in development mode' do - let(:development_mode) { false } - - it 'returns the built-in TEMPLATE_REF' do - expect(subject).to eq(PDK::TEMPLATE_REF) - end - end - - context 'in development mode' do - let(:development_mode) { true } - - it 'returns main' do - expect(subject).to eq('main') - end - end - end - - context 'with an explicit nil template' do - let(:uri) { nil } - - context 'not in development mode' do - let(:development_mode) { false } - - it 'returns the built-in TEMPLATE_REF' do - expect(subject).to eq(PDK::TEMPLATE_REF) - end - end - - context 'in development mode' do - let(:development_mode) { true } - - it 'returns main' do - expect(subject).to eq('main') - end - end - end - end - - describe '.templates' do - subject { described_class.templates(options) } - - let(:options) { {} } - - context 'when provided a template-url' do - subject(:cli_template_uri) { described_class.templates('template-url': template_url).first[:uri] } - - context 'that is a ssh:// URL without a port' do - let(:template_url) { 'ssh://git@github.com/1234/repo.git' } - - it 'parses into an Addressable::URI without port set' do - expect(cli_template_uri).to have_attributes( - scheme: 'ssh', - user: 'git', - host: 'github.com', - port: nil, - path: '/1234/repo.git' - ) - end - end - - context 'that is a ssh:// URL with a port' do - let(:template_url) { 'ssh://git@github.com:1234/user/repo.git' } - - it 'parses into an Addressable::URI with port set' do - expect(cli_template_uri).to have_attributes( - scheme: 'ssh', - user: 'git', - host: 'github.com', - port: 1234, - path: '/user/repo.git' - ) - end - end - - context 'that is a SCP style URL with a non-numeric relative path' do - let(:template_url) { 'git@github.com:user/repo.git' } - - it 'parses into an Addressable::URI without port set' do - expect(cli_template_uri).to have_attributes( - scheme: 'ssh', - user: 'git', - host: 'github.com', - port: nil, - path: '/user/repo.git' - ) - end - end - - context 'that is a SCP style URL with a numeric relative path' do - let(:template_url) { 'git@github.com:1234/repo.git' } - - it 'parses the numeric part as part of the path' do - expect(cli_template_uri).to have_attributes( - scheme: 'ssh', - user: 'git', - host: 'github.com', - port: nil, - path: '/1234/repo.git' - ) - end - end - end - - context 'when the answers file has saved template-url value' do - before do - PDK.config.set(['user', 'module_defaults', 'template-url'], answers_template_url) - end - - context 'that is the deprecated pdk-module-template' do - let(:answers_template_url) { 'https://github.com/puppetlabs/pdk-module-template' } - - it 'converts it to the new default template URL' do - expect(subject).to include( - type: 'PDK answers', - uri: Addressable::URI.parse('https://github.com/puppetlabs/pdk-templates'), - allow_fallback: true - ) - end - end - - context 'that contains any other URL' do - let(:answers_template_url) { 'https://github.com/my/pdk-template' } - - it 'uses the template as specified' do - expect(subject).to include( - type: 'PDK answers', - uri: Addressable::URI.parse(answers_template_url), - allow_fallback: true - ) - end - end - end - - context 'when the answers file has no saved template-url value' do - before do - PDK.config.set(['user', 'module_defaults', 'template-url'], nil) - end - - it 'does not include a PDK answers template option' do - expect(subject).not_to include(type: 'PDK answers', uri: anything, allow_fallback: true) - end - end - - context 'when the metadata contains a template-url' do - let(:mock_metadata) do - instance_double( - PDK::Module::Metadata, - data: { - 'pdk-version' => PDK::VERSION, - 'template-url' => metadata_url - } - ) - end - - before do - allow(PDK::Util).to receive_messages(module_root: '/path/to/module', development_mode?: false) - allow(PDK::Module::Metadata).to receive(:from_file).with('/path/to/module/metadata.json').and_return(mock_metadata) - allow(PDK::Util::Filesystem).to receive(:file?).with('/path/to/module/metadata.json').and_return(true) - allow(PDK::Util::Filesystem).to receive(:file?).with(/PDK_VERSION/).and_return(true) - end - - context 'that is a pdk-default keyword' do - let(:metadata_url) { 'pdk-default#main' } - let(:expected_uri) { described_class.default_template_addressable_uri.tap { |obj| obj.fragment = 'main' } } - - it 'converts the keyword to the default template' do - expect(subject).to include( - type: 'metadata.json', - uri: expected_uri, - allow_fallback: true - ) - end - end - - context 'that is an SCP style URL' do - let(:metadata_url) { 'git@github.com:puppetlabs/pdk-templates.git' } - - it 'converts the URL to and ssh:// URI' do - expect(subject).to include( - type: 'metadata.json', - uri: Addressable::URI.new( - scheme: 'ssh', - user: 'git', - host: 'github.com', - path: '/puppetlabs/pdk-templates.git' - ), - allow_fallback: true - ) - end - end - end - end - - describe '.valid_template?' do - subject(:return_val) { described_class.valid_template?(template) } - - context 'when passed nil' do - let(:template) { nil } - - it { is_expected.to be_falsey } - end - - context 'when passed a param that is not a Hash' do - let(:template) { 'https://github.com/my/template' } - - it { is_expected.to be_falsey } - end - - context 'when passed a param that is a Hash' do - let(:template) { { allow_fallback: true } } - - context 'with a nil :uri' do - let(:template) { super().merge(uri: nil) } - - it { is_expected.to be_falsey } - end - - context 'and the :uri value is not an Addressable::URI' do - let(:template) { super().merge(uri: 'https://github.com/my/template') } - - it { is_expected.to be_falsey } - end - - context 'and the :uri value is an Addressable::URI' do - let(:template) { super().merge(uri: Addressable::URI.parse('/path/to/a/template')) } - - context 'that points to a git repository' do - before do - allow(PDK::Util::Git).to receive(:repo?).with('/path/to/a/template').and_return(true) - end - - it { is_expected.to be_truthy } - end - - context 'that does not point to a git repository' do - before do - allow(PDK::Util::Git).to receive(:repo?).with('/path/to/a/template').and_return(false) - end - - def allow_template_dir(root, valid) - # Note this are Template V1 directories. V2, and so on, may have different requirements - allow(PDK::Util::Filesystem).to receive(:directory?).with("#{root}/moduleroot").and_return(valid) - allow(PDK::Util::Filesystem).to receive(:directory?).with("#{root}/moduleroot_init").and_return(valid) - end - - context 'but does point to a directory' do - before do - allow(PDK::Util::Filesystem).to receive(:directory?).with('/path/to/a/template').and_return(true) - end - - context 'that contains a valid template' do - before do - allow_template_dir('/path/to/a/template', true) - end - - it { is_expected.to be_truthy } - end - - context 'that does not contain a valid template' do - before do - allow_template_dir('/path/to/a/template', false) - end - - it { is_expected.to be_falsey } - end - end - - context 'and the param Hash sets :allow_fallback => false' do - let(:template) { super().merge(allow_fallback: false) } - - it 'raises a FatalError' do - expect { return_val }.to raise_error(PDK::CLI::FatalError, /unable to find a valid template/i) - end - end - end - end - end - end - - describe '.packaged_template?' do - subject { described_class.packaged_template?(path) } - - context 'when the path is windows default' do - let(:path) { 'file:///C:/Program Files/Puppet Labs/DevelopmentKit/share/cache/pdk-templates.git' } - - it { is_expected.to be_truthy } - end - - context 'when the path is posix default' do - let(:path) { 'file:///opt/puppetlabs/pdk/share/cache/pdk-templates.git' } - - it { is_expected.to be_truthy } - end - - context 'when the path is the default template keyword' do - let(:path) { described_class::PACKAGED_TEMPLATE_KEYWORD } - - it { is_expected.to be_truthy } - end - - context 'when the path is not a default' do - let(:path) { File.join('a', 'custom', 'path') } - - it { is_expected.to be_falsey } - end - end - - describe '#metadata_format' do - subject { described_class.new(url).metadata_format } - - context 'when running PDK from a package' do - before do - allow(PDK::Util).to receive_messages(package_install?: true, pdk_package_basedir: '/opt/puppetlabs/pdk') - end - - context 'and using the packaged windows template' do - let(:url) { "#{described_class::LEGACY_PACKAGED_TEMPLATE_PATHS['windows']}#main" } - - it { is_expected.to eq('pdk-default#main') } - end - - context 'and using the packaged linux template' do - let(:url) { "#{described_class::LEGACY_PACKAGED_TEMPLATE_PATHS['linux']}#something" } - - it { is_expected.to eq('pdk-default#something') } - end - - context 'and using the packaged osx template' do - let(:url) { "#{described_class::LEGACY_PACKAGED_TEMPLATE_PATHS['macos']}#else" } - - it { is_expected.to eq('pdk-default#else') } - end - end - - context 'when not running PDK from a package' do - before do - allow(PDK::Util).to receive(:package_install?).and_return(false) - end - - context 'and using the packaged windows template' do - let(:url) { "#{described_class::LEGACY_PACKAGED_TEMPLATE_PATHS['windows']}#main" } - - it { is_expected.to eq('https://github.com/puppetlabs/pdk-templates#main') } - end - - context 'and using the packaged linux template' do - let(:url) { "#{described_class::LEGACY_PACKAGED_TEMPLATE_PATHS['linux']}#something" } - - it { is_expected.to eq('https://github.com/puppetlabs/pdk-templates#something') } - end - - context 'and using the packaged osx template' do - let(:url) { "#{described_class::LEGACY_PACKAGED_TEMPLATE_PATHS['macos']}#else" } - - it { is_expected.to eq('https://github.com/puppetlabs/pdk-templates#else') } - end - end - end -end