From 565f88d0ef53270b7cdcc63cf824393c22eb48f0 Mon Sep 17 00:00:00 2001 From: Glenn Sarti Date: Tue, 14 Jan 2020 14:38:45 +0800 Subject: [PATCH] (PDK-1557) Detect Control Repositories Previously the PDK treats everything as a module. This commit begins the work to add Control Repo support by detecting Control Repositories and Bolt Project directories. --- lib/pdk.rb | 2 + lib/pdk/bolt.rb | 19 +++++ lib/pdk/control_repo.rb | 45 +++++++++++ spec/unit/pdk/bolt_spec.rb | 63 +++++++++++++++ spec/unit/pdk/control_repo_spec.rb | 120 +++++++++++++++++++++++++++++ 5 files changed, 249 insertions(+) create mode 100644 lib/pdk/bolt.rb create mode 100644 lib/pdk/control_repo.rb create mode 100644 spec/unit/pdk/bolt_spec.rb create mode 100644 spec/unit/pdk/control_repo_spec.rb diff --git a/lib/pdk.rb b/lib/pdk.rb index 7ada9c491..ff2ccca2c 100644 --- a/lib/pdk.rb +++ b/lib/pdk.rb @@ -3,7 +3,9 @@ module PDK autoload :Analytics, 'pdk/analytics' autoload :AnswerFile, 'pdk/answer_file' + autoload :Bolt, 'pdk/bolt' autoload :Config, 'pdk/config' + autoload :ControlRepo, 'pdk/control_repo' autoload :Generate, 'pdk/generate' autoload :Logger, 'pdk/logger' autoload :Module, 'pdk/module' diff --git a/lib/pdk/bolt.rb b/lib/pdk/bolt.rb new file mode 100644 index 000000000..dcbe4f30c --- /dev/null +++ b/lib/pdk/bolt.rb @@ -0,0 +1,19 @@ +require 'pdk' + +module PDK + module Bolt + # Returns true or false depending on if any of the common files and directories in + # a Bolt Project are found in the specified directory. If a directory is not specified, + # the current working directory is used. + # + # @see https://puppet.com/docs/bolt/latest/bolt_project_directories.html + # + # @return [boolean] True if any bolt specific files or directories are present + # + def bolt_project_root?(path = Dir.pwd) + return true if File.basename(path) == 'Boltdir' && PDK::Util::Filesystem.directory?(path) + PDK::Util::Filesystem.file?(File.join(path, 'bolt.yaml')) + end + module_function :bolt_project_root? + end +end diff --git a/lib/pdk/control_repo.rb b/lib/pdk/control_repo.rb new file mode 100644 index 000000000..c57404347 --- /dev/null +++ b/lib/pdk/control_repo.rb @@ -0,0 +1,45 @@ +require 'pdk' + +module PDK + module ControlRepo + CONTROL_REPO_FILES = %w[environment.conf Puppetfile].freeze + + # Returns path to the root of the Control Repo being worked on. + # + # An environment.conf is required for a Control Repo, whereas Puppetfile is + # optional. Note that a Bolt Project can also be a Control Repo. + # + # @see https://puppet.com/docs/pe/latest/control_repo.html + # + # @param strict_check [Boolean] When strict_check is true, only return the path + # if the Control Repo is strictly _only_ a control repository. For example, + # not also a Puppet Bolt project directory Default is false. + # + # @return [String, nil] Fully qualified base path to Control Repo, or nil if + # the current working dir does not appear to be within a Control Repo. + def find_control_repo_root(strict_check = false) + environment_conf_path = PDK::Util.find_upwards('environment.conf') + path = if environment_conf_path + File.dirname(environment_conf_path) + elsif control_repo_root?(Dir.pwd) + Dir.pwd + else + nil + end + return path if path.nil? || !strict_check + PDK::Bolt.bolt_project_root?(path) ? nil : path + end + module_function :find_control_repo_root + + # Returns true or false depending on if any of the common files in a Control Repo + # are found in the specified directory. If a directory is not specified, the current + # working directory is used. + # + # @return [boolean] True if any folders from CONTROL_REPO_FILES are found in the current dir, + # false otherwise. + def control_repo_root?(path = Dir.pwd) + CONTROL_REPO_FILES.any? { |file| PDK::Util::Filesystem.file?(File.join(path, file)) } + end + module_function :control_repo_root? + end +end diff --git a/spec/unit/pdk/bolt_spec.rb b/spec/unit/pdk/bolt_spec.rb new file mode 100644 index 000000000..6671cfb22 --- /dev/null +++ b/spec/unit/pdk/bolt_spec.rb @@ -0,0 +1,63 @@ +require 'spec_helper' +require 'pdk/util' + +describe PDK::Bolt do + describe '.bolt_project_root?' do + # We use NUL here because that should never be a valid directory name. But it will work with RSpec mocking. + let(:test_path) { '\x00path/test' } + + before(:each) do + allow(PDK::Util::Filesystem).to receive(:file?).and_call_original + end + + # Directories which indicate a bolt project + %w[ + Boltdir + ].each do |testcase| + it "detects the directory #{testcase} as being the root of a bolt project" do + path = File.join(test_path, testcase) + allow(PDK::Util::Filesystem).to receive(:directory?).with(path).and_return(true) + expect(described_class.bolt_project_root?(path)).to eq(true) + end + end + + # Directories which do not indicate a bolt project + %w[ + boltdir + Boltdir/something + ].each do |testcase| + it "detects the directory #{testcase} as not being the root of a bolt project" do + path = File.join(test_path, testcase) + allow(PDK::Util::Filesystem).to receive(:directory?).with(path).and_return(true) + expect(described_class.bolt_project_root?(path)).to eq(false) + end + end + + # Files which indicate a bolt project + %w[ + bolt.yaml + ].each do |testcase| + it "detects ./#{testcase} as being in the root of a bolt project" do + allow(PDK::Util::Filesystem).to receive(:file?).with(File.join(test_path, testcase)).and_return(true) + expect(described_class.bolt_project_root?(test_path)).to eq(true) + end + end + + # Files which do not indicate a bolt project + %w[ + Puppetfile + environment.conf + metadata.json + ].each do |testcase| + it "detects ./#{testcase} as not being in the root of a bolt project" do + allow(PDK::Util::Filesystem).to receive(:file?).with(File.join(test_path, testcase)).and_return(true) + expect(described_class.bolt_project_root?(test_path)).to eq(false) + end + end + + it 'uses the current directory if a directory is not specified' do + expect(PDK::Util::Filesystem).to receive(:file?) { |path| expect(path).to start_with(Dir.pwd) }.at_least(:once) + described_class.bolt_project_root? + end + end +end diff --git a/spec/unit/pdk/control_repo_spec.rb b/spec/unit/pdk/control_repo_spec.rb new file mode 100644 index 000000000..f0dcc4e06 --- /dev/null +++ b/spec/unit/pdk/control_repo_spec.rb @@ -0,0 +1,120 @@ +require 'spec_helper' +require 'pdk/util' + +describe PDK::ControlRepo do + RSpec.shared_examples 'a discoverable control repo' do + before(:each) do + allow(PDK::Util).to receive(:find_upwards).with('environment.conf').and_return(environment_path) + allow(described_class).to receive(:control_repo_root?).and_return(control_repo_root) + end + + context 'when a environment.conf file can be found upwards' do + let(:environment_path) { '/path/to/the/repo/environment.conf' } + let(:control_repo_root) { true } + + it 'returns the path to the directory containing the environment.conf file' do + is_expected.to eq(File.dirname(environment_path)) + end + end + + context 'when a environment.conf file could not be found but control repo files can' do + let(:environment_path) { nil } + let(:control_repo_root) { true } + + it { is_expected.to eq(Dir.pwd) } + end + + context 'when a environment.conf file and control repo files could not be found' do + let(:environment_path) { nil } + let(:control_repo_root) { false } + + it { is_expected.to be_nil } + end + end + + describe '.control_repo_root' do + context 'with strict_check set to false' do + subject { described_class.find_control_repo_root(false) } + + it_behaves_like 'a discoverable control repo' + end + + context 'with strict_check set to true but is not a Bolt project dir' do + subject { described_class.find_control_repo_root(true) } + + before(:each) do + allow(PDK::Bolt).to receive(:bolt_project_root?).and_return(false) + end + + it_behaves_like 'a discoverable control repo' + end + + context 'with strict_check set to true and is also a Bolt project dir' do + subject { described_class.find_control_repo_root(true) } + + before(:each) do + allow(PDK::Util).to receive(:find_upwards).with('environment.conf').and_return(environment_path) + allow(described_class).to receive(:control_repo_root?).and_return(control_repo_root) + allow(PDK::Bolt).to receive(:bolt_project_root?).and_return(true) + end + + context 'when a environment.conf file can be found upwards' do + let(:environment_path) { '/path/to/the/repo/environment.conf' } + let(:control_repo_root) { true } + + it { is_expected.to be_nil } + end + + context 'when a environment.conf file could not be found but control repo files can' do + let(:environment_path) { nil } + let(:control_repo_root) { true } + + it { is_expected.to be_nil } + end + + context 'when a environment.conf file and control repo files could not be found' do + let(:environment_path) { nil } + let(:control_repo_root) { false } + + it { is_expected.to be_nil } + end + end + end + + describe '.control_repo_root?' do + # We use NUL here because that should never be a valid directory name. But it will work with RSpec mocking. + let(:test_path) { '\x00path/test' } + + before(:each) do + allow(PDK::Util::Filesystem).to receive(:file?).and_call_original + end + + # Files which indicate a control repo + %w[ + Puppetfile + environment.conf + ].each do |testcase| + it "detects #{testcase} as being in the root of a control repo" do + allow(PDK::Util::Filesystem).to receive(:file?).with(File.join(test_path, testcase)).and_return(true) + expect(described_class.control_repo_root?(test_path)).to eq(true) + end + end + + # Files which do not indicate a control repo + %w[ + puppetfile + Environment.conf + Gemfile + ].each do |testcase| + it "detects #{testcase} as not being in the root of a control repo" do + allow(PDK::Util::Filesystem).to receive(:file?).with(File.join(test_path, testcase)).and_return(true) + expect(described_class.control_repo_root?(test_path)).to eq(false) + end + end + + it 'uses the current directory if a directory is not specified' do + expect(PDK::Util::Filesystem).to receive(:file?) { |path| expect(path).to start_with(Dir.pwd) }.at_least(:once) + described_class.control_repo_root? + end + end +end