diff --git a/lib/puppet/provider/vcsrepo/git.rb b/lib/puppet/provider/vcsrepo/git.rb index 764edc4..cc22bd5 100644 --- a/lib/puppet/provider/vcsrepo/git.rb +++ b/lib/puppet/provider/vcsrepo/git.rb @@ -1,610 +1,609 @@ # frozen_string_literal: true require File.join(File.dirname(__FILE__), '..', 'vcsrepo') Puppet::Type.type(:vcsrepo).provide(:git, parent: Puppet::Provider::Vcsrepo) do desc 'Supports Git repositories' - has_command(:git, 'git') do - environment('HOME' => ENV['HOME']) - end - has_features :bare_repositories, :reference_tracking, :ssh_identity, :multiple_remotes, :user, :depth, :branch, :submodules def create check_force if @resource.value(:revision) && ensure_bare_or_mirror? raise("Cannot set a revision (#{@resource.value(:revision)}) on a bare repository") end if !@resource.value(:source) if @resource.value(:ensure) == :mirror raise('Cannot init repository with mirror option, try bare instead') end init_repository else clone_repository(default_url, @resource.value(:path)) update_remotes(@resource.value(:source)) set_mirror if @resource.value(:ensure) == :mirror && @resource.value(:source).is_a?(Hash) if @resource.value(:revision) checkout end if !ensure_bare_or_mirror? && @resource.value(:submodules) == :true update_submodules end end update_owner_and_excludes end def destroy FileUtils.rm_rf(@resource.value(:path)) end # Checks to see if the current revision is equal to the revision on the # remote (whether on a branch, tag, or reference) # # @return [Boolean] Returns true if the repo is on the latest revision def latest? revision == latest_revision end # Just gives the `should` value that we should be setting the repo to if # latest? returns false # # @return [String] Returns the target sha/tag/branch def latest if !@resource.value(:revision) && (branch = on_branch?) branch else @resource.value(:revision) end end # Get the current revision of the repo (tag/branch/sha) # # @return [String] Returns the branch/tag if the current sha matches the # remote; otherwise returns the current sha. def revision # HEAD is the default, but lets just be explicit here. get_revision('HEAD') end # Is passed the desired reference, whether a tag, rev, or branch. Should # handle transitions from a rev/branch/tag to a rev/branch/tag. Detached # heads should be treated like bare revisions. # # @param [String] desired The desired revision to which the repo should be # set. def revision=(desired) # just checkout tags and shas; fetch has already happened so they should be updated. checkout(desired) # branches require more work. if local_branch_revision?(desired) # reset instead of pull to avoid merge conflicts. assuming remote is # updated and authoritative. # TODO might be worthwhile to have an allow_local_changes param to decide # whether to reset or pull when we're ensuring latest. if @resource.value(:source) at_path { git_with_identity('reset', '--hard', "#{@resource.value(:remote)}/#{desired}") } else at_path { git_with_identity('reset', '--hard', desired.to_s) } end end # TODO: Would this ever reach here if it is bare? if !ensure_bare_or_mirror? && @resource.value(:submodules) == :true update_submodules end update_owner_and_excludes end def bare_exists? bare_git_config_exists? && !working_copy_exists? end def ensure_bare_or_mirror? [:bare, :mirror].include? @resource.value(:ensure) end # If :source is set to a hash (for supporting multiple remotes), # we search for the URL for :remote. If it doesn't exist, # we throw an error. If :source is just a string, we use that # value for the default URL. def default_url return @resource.value(:source) unless @resource.value(:source).is_a?(Hash) return @resource.value(:source)[@resource.value(:remote)] if @resource.value(:source).key?(@resource.value(:remote)) raise("You must specify the URL for remote '#{@resource.value(:remote)}' in the :source hash") end def working_copy_exists? # NOTE: a change in the `default_url` will tell the type that this repo # doesn't exist (i.e. it triggers a "not the same repository" error). # Thus, changing the `source` property from a string to a string (which # changes the origin url), or if the @resource.value(:remote)'s url is # changed, the provider will require force. return false unless File.directory?(File.join(@resource.value(:path), '.git')) at_path do if @resource.value(:source) begin - return git('config', '--get', "remote.#{@resource.value(:remote)}.url").chomp == default_url + return git_with_identity('config', '--get', "remote.#{@resource.value(:remote)}.url").chomp == default_url rescue Puppet::ExecutionFailure return false end else begin - git('status') + git_with_identity('status') return true rescue Puppet::ExecutionFailure return false end end end end def exists? working_copy_exists? || bare_exists? end def remove_remote(remote) at_path do git_with_identity('remote', 'remove', remote) end end def update_remote_url(remote_name, remote_url) current = git_with_identity('config', '-l') return if remote_url.nil? # Check if remote exists at all, regardless of URL. # If remote doesn't exist, add it if !current.include? "remote.#{remote_name}.url" git_with_identity('remote', 'add', remote_name, remote_url) true # If remote exists, but URL doesn't match, update URL elsif !current.include? "remote.#{remote_name}.url=#{remote_url}" git_with_identity('remote', 'set-url', remote_name, remote_url) true else false end end def source at_path do - remotes = git('remote').split("\n") + remotes = git_with_identity('remote').split("\n") - return git('config', '--get', "remote.#{remotes[0]}.url").chomp if remotes.size == 1 + return git_with_identity('config', '--get', "remote.#{remotes[0]}.url").chomp if remotes.size == 1 Hash[remotes.map do |remote| - [remote, git('config', '--get', "remote.#{remote}.url").chomp] + [remote, git_with_identity('config', '--get', "remote.#{remote}.url").chomp] end] end end def source=(desired) # NOTE: a change in the `default_url` will tell the type that this repo # doesn't exist (i.e. it triggers a "not the same repository" error). # Thus, a change from a string to a string (which changes the origin url), # or if the @resource.value(:remote)'s url is changed, the provider will # require force, without ever reaching this block. The recreation is # duplicated here in case something changes in the `working_copy_exists?` # logic. current = source if current.is_a?(Hash) current.each_key do |remote| remove_remote(remote) if desired.is_a?(Hash) && !desired.key?(remote) remove_remote(remote) if desired.is_a?(String) && remote != @resource.value(:remote) end end if current.is_a?(String) && desired.is_a?(String) create # recreate else update_remotes(desired) end end def update_remotes(remotes) do_update = false # If supplied source is a hash of remote name and remote url pairs, then # we loop around the hash. Otherwise, we assume single url specified # in source property if remotes.is_a?(Hash) remotes.keys.sort.each do |remote_name| remote_url = remotes[remote_name] at_path { do_update |= update_remote_url(remote_name, remote_url) } end else at_path { do_update |= update_remote_url(@resource.value(:remote), remotes) } end # If at least one remote was added or updated, then we must # call the 'git remote update' command at_path { git_with_identity('remote', 'update') } if do_update == true end def update_references at_path do git_with_identity('fetch', @resource.value(:remote)) git_with_identity('fetch', '--tags', @resource.value(:remote)) update_owner_and_excludes end end # Convert working copy to bare # # Moves: # /.git # to: # / # and sets core.bare=true, and calls `set_mirror` if appropriate def convert_working_copy_to_bare return unless working_copy_exists? && !bare_exists? notice 'Converting working copy repository to bare repository' FileUtils.mv(File.join(@resource.value(:path), '.git'), tempdir) FileUtils.rm_rf(@resource.value(:path)) FileUtils.mv(tempdir, @resource.value(:path)) at_path do - git('config', '--local', '--bool', 'core.bare', 'true') + exec_git('config', '--local', '--bool', 'core.bare', 'true') return unless @resource.value(:ensure) == :mirror raise('Cannot have empty repository that is also a mirror.') unless @resource.value(:source) set_mirror end end # Convert bare to working copy # # Moves: # / # to: # /.git # and sets core.bare=false, and calls `set_no_mirror` if appropriate def convert_bare_to_working_copy notice 'Converting bare repository to working copy repository' FileUtils.mv(@resource.value(:path), tempdir) FileUtils.mkdir(@resource.value(:path)) FileUtils.mv(tempdir, File.join(@resource.value(:path), '.git')) if commits? at_path do - git('config', '--local', '--bool', 'core.bare', 'false') + exec_git('config', '--local', '--bool', 'core.bare', 'false') reset('HEAD') git_with_identity('checkout', '--force') update_owner_and_excludes end end set_no_mirror if mirror? end def mirror? at_path do begin - git('config', '--get-regexp', 'remote\..*\.mirror') + git_with_identity('config', '--get-regexp', 'remote\..*\.mirror') return true rescue Puppet::ExecutionFailure return false end end end def set_mirror at_path do if @resource.value(:source).is_a?(String) - git('config', "remote.#{@resource.value(:remote)}.mirror", 'true') + git_with_identity('config', "remote.#{@resource.value(:remote)}.mirror", 'true') else @resource.value(:source).each_key do |remote| - git('config', "remote.#{remote}.mirror", 'true') + git_with_identity('config', "remote.#{remote}.mirror", 'true') end end end end def set_no_mirror at_path do if @resource.value(:source).is_a?(String) begin - git('config', '--unset', "remote.#{@resource.value(:remote)}.mirror") + exec_git('config', '--unset', "remote.#{@resource.value(:remote)}.mirror") rescue Puppet::ExecutionFailure next end else @resource.value(:source).each_key do |remote| begin - git('config', '--unset', "remote.#{remote}.mirror") + exec_git('config', '--unset', "remote.#{remote}.mirror") rescue Puppet::ExecutionFailure next end end end end end private # @!visibility private def bare_git_config_exists? return false unless File.exist?(File.join(@resource.value(:path), 'config')) begin - at_path { git('config', '--list', '--file', 'config') } + at_path { git_with_identity('config', '--list', '--file', 'config') } true rescue Puppet::ExecutionFailure false end end # @!visibility private def clone_repository(source, path) args = ['clone'] if @resource.value(:depth) && @resource.value(:depth).to_i > 0 args.push('--depth', @resource.value(:depth).to_s) if @resource.value(:revision) && !@resource.value(:branch) args.push('--branch', @resource.value(:revision).to_s) end end if @resource.value(:branch) args.push('--branch', @resource.value(:branch).to_s) end case @resource.value(:ensure) when :bare then args << '--bare' when :mirror then args << '--mirror' end if @resource.value(:remote) != 'origin' args.push('--origin', @resource.value(:remote)) end if !working_copy_exists? args.push(source, path) Dir.chdir('/') do git_with_identity(*args) end else notice 'Repo has already been cloned' end end # @!visibility private def init_repository if @resource.value(:ensure) == :bare && working_copy_exists? convert_working_copy_to_bare elsif @resource.value(:ensure) == :present && bare_exists? convert_bare_to_working_copy else # normal init FileUtils.mkdir(@resource.value(:path)) FileUtils.chown(@resource.value(:user), nil, @resource.value(:path)) if @resource.value(:user) args = ['init'] if @resource.value(:ensure) == :bare args << '--bare' end at_path do git_with_identity(*args) end end end # @!visibility private def commits? at_path do begin commits = git_with_identity('rev-list', '--all', '--count').to_i rescue Puppet::ExecutionFailure commits = 0 end return commits > 0 end end # Will checkout a rev/branch/tag using the locally cached versions. Does not # handle upstream branch changes # @!visibility private def checkout(revision = @resource.value(:revision)) keep_local_changes = @resource.value(:keep_local_changes) stash if keep_local_changes == :true if !local_branch_revision?(revision) && remote_branch_revision?(revision) # non-locally existant branches (perhaps switching to a branch that has never been checked out) at_path { git_with_identity('checkout', '--force', '-b', revision, '--track', "#{@resource.value(:remote)}/#{revision}") } else # tags, locally existant branches (perhaps outdated), and shas at_path { git_with_identity('checkout', '--force', revision) } end unstash if keep_local_changes == :true end # @!visibility private def reset(desired) at_path do git_with_identity('reset', '--hard', desired) end end # @!visibility private def update_submodules at_path do git_with_identity('submodule', 'update', '--init', '--recursive') end end # Determins if the branch exists at the upstream but has not yet been locally committed # @!visibility private def remote_branch_revision?(revision = @resource.value(:revision)) # git < 1.6 returns '#{@resource.value(:remote)}/#{revision}' # git 1.6+ returns 'remotes/#{@resource.value(:remote)}/#{revision}' branch = at_path { branches.grep %r{(remotes/)?#{@resource.value(:remote)}/#{revision}$} } branch unless branch.empty? end # Determins if the branch is already cached locally # @!visibility private def local_branch_revision?(revision = @resource.value(:revision)) at_path { branches.include?(revision) } end # @!visibility private def tag_revision?(revision = @resource.value(:revision)) at_path { tags.include?(revision) } end # @!visibility private def branches at_path { git_with_identity('branch', '--no-color', '-a') }.tr('*', ' ').split(%r{\n}).map { |line| line.strip } end # git < 2.4 returns 'detached from' # git 2.4+ returns 'HEAD detached at' # @!visibility private def on_branch? at_path do matches = git_with_identity('branch', '--no-color', '-a').match %r{\*\s+(.*)} matches[1] unless %r{(\(detached from|\(HEAD detached at|\(no branch)}.match?(matches[1]) end end # @!visibility private def tags at_path { git_with_identity('tag', '-l') }.split(%r{\n}).map { |line| line.strip } end # @!visibility private def set_excludes # Excludes may be an Array or a String. at_path do open('.git/info/exclude', 'w') do |f| if @resource.value(:excludes).respond_to?(:each) @resource.value(:excludes).each { |ex| f.puts ex } else f.puts @resource.value(:excludes) end end end end # @!visibility private def stash at_path { git_with_identity('stash', 'save') } end # @!visibility private def unstash at_path { git_with_identity('stash', 'pop') } end # Finds the latest revision or sha of the current branch if on a branch, or # of HEAD otherwise. # @note Calls create which can forcibly destroy and re-clone the repo if # force => true # @see get_revision # # @!visibility private # @return [String] Returns the output of get_revision def latest_revision # TODO: Why is create called here anyway? create if @resource.value(:force) && working_copy_exists? create unless working_copy_exists? branch = on_branch? return get_revision("#{@resource.value(:remote)}/#{branch}") if branch get_revision end # Returns the current revision given if the revision is a tag or branch and # matches the current sha. If the current sha does not match the sha of a tag # or branch, then it will just return the sha (ie, is not in sync) # # @!visibility private # # @param [String] rev The revision of which to check if it is current # @return [String] Returns the tag/branch of the current repo if it's up to # date; otherwise returns the sha of the requested revision. def get_revision(rev = 'HEAD') unless @resource.value(:source) status = at_path { git_with_identity('status') } is_it_new = status =~ %r{Initial commit|No commits yet} if is_it_new status =~ %r{On branch (.*)} branch = Regexp.last_match(1) return branch end end current = at_path { git_with_identity('rev-parse', rev).strip } if @resource.value(:revision) == current # if already pointed at desired revision, it must be a SHA, so just return it return current end if @resource.value(:source) update_references end if @resource.value(:revision) canonical = if tag_revision? # git-rev-parse will give you the hash of the tag object itself rather # than the commit it points to by default. Using tag^0 will return the # actual commit. at_path { git_with_identity('rev-parse', "#{@resource.value(:revision)}^0").strip } elsif local_branch_revision? at_path { git_with_identity('rev-parse', @resource.value(:revision)).strip } elsif remote_branch_revision? at_path { git_with_identity('rev-parse', "#{@resource.value(:remote)}/#{@resource.value(:revision)}").strip } else # look for a sha (could match invalid shas) at_path { git_with_identity('rev-parse', '--revs-only', @resource.value(:revision)).strip } end raise("#{@resource.value(:revision)} is not a local or remote ref") if canonical.nil? || canonical.empty? current = @resource.value(:revision) if current == canonical end current end # @!visibility private def update_owner_and_excludes if @resource.value(:owner) || @resource.value(:group) set_ownership end set_excludes if @resource.value(:excludes) end def git_version - git('--version').match(%r{[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?})[0] + exec_git('--version').match(%r{[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?})[0] end # @!visibility private def git_with_identity(*args) if @resource.value(:trust_server_cert) == :true git_ver = git_version git_ver_err = "Can't set sslVerify to false, the -c parameter is not supported in Git #{git_ver}. Please install Git 1.7.2 or higher." return raise(git_ver_err) unless Gem::Version.new(git_ver) >= Gem::Version.new('1.7.2') args.unshift('-c', 'http.sslVerify=false') end if @resource.value(:identity) - Tempfile.open('git-helper', Puppet[:statedir]) do |f| - f.puts '#!/bin/sh' - f.puts 'SSH_AUTH_SOCKET=' - f.puts 'export SSH_AUTH_SOCKET' - f.puts 'exec ssh -oStrictHostKeyChecking=no -oPasswordAuthentication=no -oKbdInteractiveAuthentication=no ' \ - "-oChallengeResponseAuthentication=no -oConnectTimeout=120 -i #{@resource.value(:identity)} $*" - f.close + ssh_opts = { + IgnoreUnknown: 'IdentityAgent', + IdentitiesOnly: 'yes', + IdentityAgent: 'none', + PasswordAuthentication: 'no', + KbdInteractiveAuthentication: 'no', + } + ssh_command = "ssh -i #{@resource.value(:identity)} " + ssh_command += ssh_opts.map { |option, value| "-o \"#{option} #{value}\"" }.join ' ' - FileUtils.chmod(0o755, f.path) + env_git_ssh_command_save = ENV['GIT_SSH_COMMAND'] + ENV['GIT_SSH_COMMAND'] = ssh_command - env_git_ssh_save = ENV['GIT_SSH'] - env_git_ssh_command_save = ENV['GIT_SSH_COMMAND'] + ret = exec_git(*args) - ENV['GIT_SSH'] = f.path - ENV['GIT_SSH_COMMAND'] = nil # Unset GIT_SSH_COMMAND environment variable + ENV['GIT_SSH_COMMAND'] = env_git_ssh_command_save - ret = git(*args) - - ENV['GIT_SSH'] = env_git_ssh_save - ENV['GIT_SSH_COMMAND'] = env_git_ssh_command_save + ret + else + exec_git(*args) + end + end - return ret - end - elsif @resource.value(:user) && @resource.value(:user) != Facter['id'].value + # Execute git with the given args, running it as the user specified. + def exec_git(*args) + exec_args = { failonfail: true, combine: true } + if @resource.value(:user) && @resource.value(:user) != Facter['id'].value env = Etc.getpwnam(@resource.value(:user)) - Puppet::Util::Execution.execute("git #{args.join(' ')}", uid: @resource.value(:user), failonfail: true, custom_environment: { 'HOME' => env['dir'] }, combine: true) - else - git(*args) + exec_args[:custom_environment] = { 'HOME' => env['dir'] } + exec_args[:uid] = @resource.value(:user) end + Puppet::Util::Execution.execute([:git, args], **exec_args) end end diff --git a/spec/acceptance/clone_repo_spec.rb b/spec/acceptance/clone_repo_spec.rb index 9ec77a4..1773d2e 100644 --- a/spec/acceptance/clone_repo_spec.rb +++ b/spec/acceptance/clone_repo_spec.rb @@ -1,554 +1,565 @@ # frozen_string_literal: true require 'spec_helper_acceptance' tmpdir = '/tmp/vcsrepo' describe 'clones a remote repo' do before(:all) do my_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) run_shell("rm -rf #{tmpdir}") bolt_upload_file("#{my_root}/acceptance/files", tmpdir, 'create_git_repo.sh') run_shell("cd #{tmpdir} && ./create_git_repo.sh") end after(:all) do run_shell("rm -rf #{tmpdir}/testrepo") run_shell("rm -rf #{tmpdir}/testrepo_mirror_repo") end context 'with get the current main HEAD' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", } MANIFEST it 'clones a repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo/.git") do it { is_expected.to be_directory } end describe file("#{tmpdir}/testrepo/.git/HEAD") do it { is_expected.to contain 'ref: refs/heads/main' } end end context 'with using a https source on github', unless: only_supports_weak_encryption do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/httpstestrepo": ensure => present, provider => git, source => "https://github.com/puppetlabs/puppetlabs-vcsrepo.git", } MANIFEST it 'clones a repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/httpstestrepo/.git") do it { is_expected.to be_directory } end describe file("#{tmpdir}/httpstestrepo/.git/HEAD") do it { is_expected.to contain 'ref: refs/heads/main' } end end context 'with using a commit SHA' do let(:sha) do run_shell("git --git-dir=#{tmpdir}/testrepo.git rev-list HEAD | tail -1").stdout.chomp end after(:all) do run_shell("rm -rf #{tmpdir}/testrepo_sha") end it 'clones a repo' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_sha": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", revision => "#{sha}", } MANIFEST # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_sha/.git") do it { is_expected.to be_directory } end describe file("#{tmpdir}/testrepo_sha/.git/HEAD") do it { is_expected.to contain sha } end end context 'with using a tag' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_tag": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", revision => '0.0.2', } MANIFEST it 'clones a repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_tag/.git") do it { is_expected.to be_directory } end it 'has the tag as the HEAD' do run_shell("git --git-dir=#{tmpdir}/testrepo_tag/.git name-rev HEAD | grep '0.0.2'") end end context 'with using a branch name' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_branch": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", revision => 'a_branch', } MANIFEST it 'clones a repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_branch/.git") do it { is_expected.to be_directory } end describe file("#{tmpdir}/testrepo_branch/.git/HEAD") do it { is_expected.to contain 'ref: refs/heads/a_branch' } end end context 'with ensure latest with branch specified' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_latest": ensure => latest, provider => git, source => "file://#{tmpdir}/testrepo.git", revision => 'a_branch', } MANIFEST it 'clones a repo' do # Run it twice and test for idempotency idempotent_apply(pp) end it 'verifies the HEAD commit SHA on remote and local match' do remote_commit = run_shell("git ls-remote file://#{tmpdir}/testrepo_latest HEAD | head -1").stdout local_commit = run_shell("git --git-dir=#{tmpdir}/testrepo_latest/.git rev-parse HEAD").stdout.chomp expect(remote_commit).to include(local_commit) end end context 'with ensure latest with branch unspecified' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_latest": ensure => latest, provider => git, source => "file://#{tmpdir}/testrepo.git", } MANIFEST it 'clones a repo' do # Run it twice and test for idempotency idempotent_apply(pp) end it 'verifies the HEAD commit SHA on remote and local match' do remote_commit = run_shell("git ls-remote file://#{tmpdir}/testrepo_latest HEAD | head -1").stdout local_commit = run_shell("git --git-dir=#{tmpdir}/testrepo_latest/.git rev-parse HEAD").stdout.chomp expect(remote_commit).to include(local_commit) end end context 'with with shallow clone' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_shallow": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", depth => '1', } MANIFEST it 'does a shallow clone' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_shallow/.git/shallow") do it { is_expected.to be_file } end end context 'with path is not empty and not a repository' do before(:all) do run_shell("mkdir #{tmpdir}/not_a_repo", acceptable_exit_codes: [0, 1]) run_shell("touch #{tmpdir}/not_a_repo/file1.txt", acceptable_exit_codes: [0, 1]) end pp = <<-MANIFEST vcsrepo { "#{tmpdir}/not_a_repo": ensure => present, provider => git source => "file://#{tmpdir}/testrepo.git", } MANIFEST it 'raises an exception' do apply_manifest(pp, expect_failures: true) end end context 'with with an owner' do pp = <<-MANIFEST user { 'vagrant': ensure => present, } MANIFEST apply_manifest(pp, catch_failures: true) pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_owner": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", owner => 'vagrant', } MANIFEST it 'clones a repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_owner") do it { is_expected.to be_directory } it { is_expected.to be_owned_by 'vagrant' } end end context 'with with a group' do pp = <<-MANIFEST group { 'vagrant': ensure => present, } MANIFEST apply_manifest(pp, catch_failures: true) pp = <<-MANIFEST vcsrepo { "/#{tmpdir}/testrepo_group": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", group => 'vagrant', } MANIFEST it 'clones a repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_group") do it { is_expected.to be_directory } it { is_expected.to be_grouped_into 'vagrant' } end end context 'with with excludes' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_excludes": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", excludes => ['exclude1.txt', 'exclude2.txt'], } MANIFEST it 'clones a repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_excludes/.git/info/exclude") do subject { super().content } it { is_expected.to match %r{exclude1.txt} } end describe file("#{tmpdir}/testrepo_excludes/.git/info/exclude") do subject { super().content } it { is_expected.to match %r{exclude2.txt} } end end context 'with with force' do before(:all) do run_shell("mkdir -p #{tmpdir}/testrepo_force/folder") run_shell("touch #{tmpdir}/testrepo_force/temp.txt") end it 'applies the manifest' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_force": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", force => true, } MANIFEST # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_force/folder") do it { is_expected.not_to be_directory } end describe file("#{tmpdir}/testrepo_force/temp.txt") do it { is_expected.not_to be_file } end describe file("#{tmpdir}/testrepo_force/.git") do it { is_expected.to be_directory } end context 'with and noop' do before(:all) do run_shell("mkdir #{tmpdir}/testrepo_already_exists") run_shell("cd #{tmpdir}/testrepo_already_exists && git init") run_shell("cd #{tmpdir}/testrepo_already_exists && touch a && git add a && git commit -m 'a'") end after(:all) do run_shell("rm -rf #{tmpdir}/testrepo_already_exists") end pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_already_exists": ensure => present, source => "file://#{tmpdir}/testrepo.git", provider => git, force => true, noop => true, } MANIFEST it 'applies the manifest' do apply_manifest(pp, catch_changes: true) end end end context 'with as a user' do before(:all) do run_shell("chmod 707 #{tmpdir}") pp = <<-MANIFEST group { 'testuser': ensure => present, } user { 'testuser': ensure => present, groups => 'testuser', } MANIFEST apply_manifest(pp, catch_failures: true) end pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_user": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", user => 'testuser', } MANIFEST it 'applies the manifest' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_user") do it { is_expected.to be_directory } it { is_expected.to be_owned_by 'testuser' } end describe file("#{tmpdir}/testrepo_user") do it { is_expected.to be_directory } it { is_expected.to be_grouped_into 'testuser' } end after(:all) do pp = 'user { "testuser": ensure => absent }' apply_manifest(pp, catch_failures: true) end end context 'with non-origin remote name' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_remote": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", remote => 'testorigin', } MANIFEST it 'applies the manifest' do # Run it twice and test for idempotency idempotent_apply(pp) end it 'remote name is "testorigin"' do run_shell("git --git-dir=#{tmpdir}/testrepo_remote/.git remote | grep 'testorigin'") end end context 'with as a user with ssh - includes special characters' do before(:all) do # create user pp = <<-MANIFEST group { 'testuser-ssh': ensure => present, } user { 'testuser-ssh': ensure => present, groups => 'testuser-ssh', managehome => true, } MANIFEST apply_manifest(pp, catch_failures: true) # create ssh keys run_shell('mkdir -p /home/testuser-ssh/.ssh') run_shell('ssh-keygen -q -t rsa -f /home/testuser-ssh/.ssh/id_rsa -N ""') + # add localhost to known_hosts + run_shell('rm /home/testuser-ssh/.ssh/known_hosts', expect_failures: true) + run_shell('ssh-keyscan localhost >> /home/testuser-ssh/.ssh/known_hosts') + # copy public key to authorized_keys run_shell('cat /home/testuser-ssh/.ssh/id_rsa.pub > /home/testuser-ssh/.ssh/authorized_keys') run_shell('echo -e "Host localhost\n\tStrictHostKeyChecking no\n" > /home/testuser-ssh/.ssh/config') run_shell('chown -R testuser-ssh:testuser-ssh /home/testuser-ssh/.ssh') end pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_user_ssh": ensure => present, provider => git, source => "git+ssh://testuser-ssh@localhost#{tmpdir}/testrepo.git", user => 'testuser-ssh', } MANIFEST it 'applies the manifest' do # Run it twice and test for idempotency idempotent_apply(pp) end after(:all) do pp = <<-MANIFEST user { 'testuser-ssh': ensure => absent, managehome => true, } MANIFEST sleep 10 apply_manifest(pp, catch_failures: true) end end context 'with using an identity file' do before(:all) do # create user pp = <<-MANIFEST + group { 'testuser-ssh': + ensure => present, + } user { 'testuser-ssh': ensure => present, + groups => 'testuser-ssh', managehome => true, } MANIFEST apply_manifest(pp, catch_failures: true) # create ssh keys run_shell('mkdir -p /home/testuser-ssh/.ssh') run_shell('ssh-keygen -q -t rsa -f /home/testuser-ssh/.ssh/id_rsa -N ""') + # add localhost to known_hosts + run_shell('ssh-keyscan localhost > /home/testuser-ssh/.ssh/known_hosts') + # copy public key to authorized_keys run_shell('cat /home/testuser-ssh/.ssh/id_rsa.pub > /home/testuser-ssh/.ssh/authorized_keys') - run_shell('echo -e "Host localhost\n\tStrictHostKeyChecking no\n" > /home/testuser-ssh/.ssh/config') run_shell('chown -R testuser-ssh:testuser-ssh /home/testuser-ssh/.ssh') end pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_user_ssh_id": ensure => present, provider => git, source => "testuser-ssh@localhost:#{tmpdir}/testrepo.git", identity => '/home/testuser-ssh/.ssh/id_rsa', + user => 'testuser-ssh', } MANIFEST it 'applies the manifest' do # Run it twice and test for idempotency idempotent_apply(pp) end end context 'with bare repo' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_bare_repo": ensure => bare, provider => git, source => "file://#{tmpdir}/testrepo.git", } MANIFEST it 'creates a bare repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_bare_repo/config") do it { is_expected.to contain 'bare = true' } end describe file("#{tmpdir}/testrepo_bare_repo/.git") do it { is_expected.not_to be_directory } end describe file("#{tmpdir}/testrepo_bare_repo/HEAD") do it { is_expected.to contain 'ref: refs/heads/main' } end end context 'with mirror repo' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_mirror_repo": ensure => mirror, provider => git, source => "file://#{tmpdir}/testrepo.git", } MANIFEST it 'creates a mirror repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_mirror_repo/config") do it { is_expected.to contain 'bare = true' } it { is_expected.to contain 'mirror = true' } end describe file("#{tmpdir}/testrepo_mirror_repo/.git") do it { is_expected.not_to be_directory } end describe file("#{tmpdir}/testrepo_mirror_repo/HEAD") do it { is_expected.to contain 'ref: refs/heads/main' } end end end diff --git a/spec/unit/puppet/provider/vcsrepo/git_spec.rb b/spec/unit/puppet/provider/vcsrepo/git_spec.rb index 9e0ec2f..866e29b 100644 --- a/spec/unit/puppet/provider/vcsrepo/git_spec.rb +++ b/spec/unit/puppet/provider/vcsrepo/git_spec.rb @@ -1,590 +1,580 @@ # frozen_string_literal: true require 'spec_helper' describe Puppet::Type.type(:vcsrepo).provider(:git) do def branch_a_list(include_branch = nil?) < 'git://git@foo.com/bar.git', 'other' => 'git://git@foo.com/baz.git' } resource.delete(:revision) expect(Dir).to receive(:chdir).with('/').once.and_yield - expect(provider).to receive(:git).with('clone', '--mirror', resource.value(:source)['origin'], resource.value(:path)) + expect(provider).to receive(:exec_git).with('clone', '--mirror', resource.value(:source)['origin'], resource.value(:path)) expect(provider).to receive(:update_remotes) expect_chdir - expect(provider).to receive(:git).with('config', 'remote.origin.mirror', 'true') - expect(provider).to receive(:git).with('config', 'remote.other.mirror', 'true') + expect(provider).to receive(:exec_git).with('config', 'remote.origin.mirror', 'true') + expect(provider).to receive(:exec_git).with('config', 'remote.other.mirror', 'true') provider.create end end end context 'when with an ensure of mirror - when the path is a working copy repository' do it 'clones overtop it using force' do resource[:force] = true expect(Dir).to receive(:chdir).with('/').once.and_yield expect(Dir).to receive(:chdir).with('/tmp/test').at_least(:once).and_yield expect(provider).to receive(:path_exists?).and_return(true) expect(provider).to receive(:path_empty?).and_return(false) provider.destroy - expect(provider).to receive(:git).with('clone', resource.value(:source), resource.value(:path)) + expect(provider).to receive(:exec_git).with('clone', resource.value(:source), resource.value(:path)) expect(provider).to receive(:update_submodules) expect(provider).to receive(:update_remote_url).with('origin', resource.value(:source)).and_return false - expect(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(branch_a_list(resource.value(:revision))) - expect(provider).to receive(:git).with('checkout', '--force', resource.value(:revision)) + expect(provider).to receive(:exec_git).with('branch', '--no-color', '-a').and_return(branch_a_list(resource.value(:revision))) + expect(provider).to receive(:exec_git).with('checkout', '--force', resource.value(:revision)) provider.create end end context 'when with an ensure of mirror - when the path is not empty and not a repository' do it 'raises an exception' do expect(provider).to receive(:path_exists?).and_return(true) expect(provider).to receive(:path_empty?).and_return(false) expect { provider.create }.to raise_error(Puppet::Error) end end context 'when converting repo type' do context 'when with working copy to bare' do it 'converts the repo' do resource[:ensure] = :bare expect(provider).to receive(:working_copy_exists?).and_return(true) expect(provider).to receive(:bare_exists?).and_return(false) expect(FileUtils).to receive(:mv).and_return(true) expect(FileUtils).to receive(:rm_rf).and_return(true) expect(FileUtils).to receive(:mv).and_return(true) expect_chdir - expect(provider).to receive(:git).with('config', '--local', '--bool', 'core.bare', 'true') + expect(provider).to receive(:exec_git).with('config', '--local', '--bool', 'core.bare', 'true') provider.instance_eval { convert_working_copy_to_bare } end end context 'when with working copy to mirror' do it 'converts the repo' do resource[:ensure] = :mirror + expect_chdir expect(provider).to receive(:working_copy_exists?).and_return(true) expect(provider).to receive(:bare_exists?).and_return(false) expect(FileUtils).to receive(:mv).and_return(true) expect(FileUtils).to receive(:rm_rf).and_return(true) expect(FileUtils).to receive(:mv).and_return(true) - expect_chdir - expect(provider).to receive(:git).with('config', '--local', '--bool', 'core.bare', 'true') - expect(provider).to receive(:git).with('config', 'remote.origin.mirror', 'true') + expect(provider).to receive(:exec_git).with('config', '--local', '--bool', 'core.bare', 'true') + expect(provider).to receive(:exec_git).with('config', 'remote.origin.mirror', 'true') provider.instance_eval { convert_working_copy_to_bare } end end context 'when with bare copy to working copy' do it 'converts the repo' do expect(FileUtils).to receive(:mv).and_return(true) expect(FileUtils).to receive(:mkdir).and_return(true) expect(FileUtils).to receive(:mv).and_return(true) expect_chdir expect(provider).to receive(:commits?).and_return(true) # If you forget to stub these out you lose 3 hours of rspec work. - expect(provider).to receive(:git) + expect(provider).to receive(:exec_git) .with('config', '--local', '--bool', 'core.bare', 'false').and_return(true) expect(provider).to receive(:reset).with('HEAD').and_return(true) expect(provider).to receive(:git_with_identity).with('checkout', '--force').and_return(true) expect(provider).to receive(:update_owner_and_excludes).and_return(true) expect(provider).to receive(:mirror?).and_return(false) provider.instance_eval { convert_bare_to_working_copy } end end context 'when with mirror to working copy' do it 'converts the repo' do expect(FileUtils).to receive(:mv).and_return(true) expect(FileUtils).to receive(:mkdir).and_return(true) expect(FileUtils).to receive(:mv).and_return(true) expect_chdir expect(provider).to receive(:commits?).and_return(true) - expect(provider).to receive(:git) + expect(provider).to receive(:exec_git) .with('config', '--local', '--bool', 'core.bare', 'false').and_return(true) expect(provider).to receive(:reset).with('HEAD').and_return(true) expect(provider).to receive(:git_with_identity).with('checkout', '--force').and_return(true) expect(provider).to receive(:update_owner_and_excludes).and_return(true) - expect(provider).to receive(:git).with('config', '--unset', 'remote.origin.mirror') + expect(provider).to receive(:exec_git).with('config', '--unset', 'remote.origin.mirror') expect(provider).to receive(:mirror?).and_return(true) provider.instance_eval { convert_bare_to_working_copy } end end end context 'when destroying' do it 'removes the directory' do expect_rm_rf provider.destroy end end context 'when checking the revision property' do before(:each) do expect_chdir('/tmp/test') resource[:source] = 'http://example.com' - allow(provider).to receive(:git).with('config', 'remote.origin.url').and_return('') - allow(provider).to receive(:git).with('fetch', 'origin') # FIXME - allow(provider).to receive(:git).with('fetch', '--tags', 'origin') - allow(provider).to receive(:git).with('rev-parse', 'HEAD').and_return('currentsha') - allow(provider).to receive(:git).with('tag', '-l').and_return('Hello') + allow(provider).to receive(:exec_git).with('config', 'remote.origin.url').and_return('') + allow(provider).to receive(:exec_git).with('fetch', 'origin') # FIXME + allow(provider).to receive(:exec_git).with('fetch', '--tags', 'origin') + allow(provider).to receive(:exec_git).with('rev-parse', 'HEAD').and_return('currentsha') + allow(provider).to receive(:exec_git).with('tag', '-l').and_return('Hello') end context 'when its a SHA and is not different than the current SHA' do it 'and_return the current SHA' do resource[:revision] = 'currentsha' - allow(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(branch_a_list) - expect(provider).to receive(:git).with('rev-parse', '--revs-only', resource.value(:revision)).never + allow(provider).to receive(:exec_git).with('branch', '--no-color', '-a').and_return(branch_a_list) + expect(provider).to receive(:exec_git).with('rev-parse', '--revs-only', resource.value(:revision)).never expect(provider).to receive(:update_references).never expect(provider.revision).to eq(resource.value(:revision)) end end context 'when its a SHA and is different than the current SHA' do it 'and_return the current SHA' do resource[:revision] = 'othersha' - allow(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(branch_a_list) - expect(provider).to receive(:git).with('rev-parse', '--revs-only', resource.value(:revision)).and_return('othersha') + allow(provider).to receive(:exec_git).with('branch', '--no-color', '-a').and_return(branch_a_list) + expect(provider).to receive(:exec_git).with('rev-parse', '--revs-only', resource.value(:revision)).and_return('othersha') expect(provider).to receive(:update_references) expect(provider.revision).to eq('currentsha') end end context 'when its a local branch and is not different than the current SHA' do it 'and_return the ref' do resource[:revision] = 'localbranch' - allow(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(branch_a_list('localbranch')) - expect(provider).to receive(:git).with('rev-parse', resource.value(:revision)).and_return('currentsha') + allow(provider).to receive(:exec_git).with('branch', '--no-color', '-a').and_return(branch_a_list('localbranch')) + expect(provider).to receive(:exec_git).with('rev-parse', resource.value(:revision)).and_return('currentsha') expect(provider).to receive(:update_references) expect(provider.revision).to eq(resource.value(:revision)) end end context 'when its a local branch and is different than the current SHA' do it 'and_return the current SHA' do resource[:revision] = 'localbranch' - allow(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(branch_a_list('localbranch')) - expect(provider).to receive(:git).with('rev-parse', resource.value(:revision)).and_return('othersha') + allow(provider).to receive(:exec_git).with('branch', '--no-color', '-a').and_return(branch_a_list('localbranch')) + expect(provider).to receive(:exec_git).with('rev-parse', resource.value(:revision)).and_return('othersha') expect(provider).to receive(:update_references) expect(provider.revision).to eq('currentsha') end end context 'when its a ref to a remote head' do it 'and_return the ref' do resource[:revision] = 'remotebranch' - allow(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(" remotes/origin/#{resource.value(:revision)}") - expect(provider).to receive(:git).with('rev-parse', "origin/#{resource.value(:revision)}").and_return('currentsha') + allow(provider).to receive(:exec_git).with('branch', '--no-color', '-a').and_return(" remotes/origin/#{resource.value(:revision)}") + expect(provider).to receive(:exec_git).with('rev-parse', "origin/#{resource.value(:revision)}").and_return('currentsha') expect(provider).to receive(:update_references) expect(provider.revision).to eq(resource.value(:revision)) end end context 'when its a ref to non existant remote head' do it 'fails' do resource[:revision] = 'remotebranch' - allow(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(branch_a_list) - expect(provider).to receive(:git).with('rev-parse', '--revs-only', resource.value(:revision)).and_return('') + allow(provider).to receive(:exec_git).with('branch', '--no-color', '-a').and_return(branch_a_list) + expect(provider).to receive(:exec_git).with('rev-parse', '--revs-only', resource.value(:revision)).and_return('') expect(provider).to receive(:update_references) expect { provider.revision }.to raise_error(RuntimeError, %r{not a local or remote ref$}) end end context "when there's no source" do it 'and_return the revision' do resource[:revision] = 'localbranch' resource.delete(:source) - allow(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(branch_a_list('localbranch')) + allow(provider).to receive(:exec_git).with('branch', '--no-color', '-a').and_return(branch_a_list('localbranch')) expect(provider).to receive(:update_references).never - expect(provider).to receive(:git).with('status') - expect(provider).to receive(:git).with('rev-parse', resource.value(:revision)).and_return('currentsha') + expect(provider).to receive(:exec_git).with('status') + expect(provider).to receive(:exec_git).with('rev-parse', resource.value(:revision)).and_return('currentsha') expect(provider.revision).to eq(resource.value(:revision)) end end end context 'when setting the revision property' do before(:each) do expect_chdir end context "when it's an existing local branch" do it "uses 'git fetch' and 'git reset'" do resource[:revision] = 'feature/foo' expect(provider).to receive(:update_submodules) - expect(provider).to receive(:git).with('branch', '--no-color', '-a').at_least(:once).and_return(branch_a_list(resource.value(:revision))) - expect(provider).to receive(:git).with('checkout', '--force', resource.value(:revision)) - expect(provider).to receive(:git).with('reset', '--hard', "origin/#{resource.value(:revision)}") + expect(provider).to receive(:exec_git).with('branch', '--no-color', '-a').at_least(:once).and_return(branch_a_list(resource.value(:revision))) + expect(provider).to receive(:exec_git).with('checkout', '--force', resource.value(:revision)) + expect(provider).to receive(:exec_git).with('reset', '--hard', "origin/#{resource.value(:revision)}") provider.revision = resource.value(:revision) end end context "when it's a remote branch" do it "uses 'git fetch' and 'git reset'" do resource[:revision] = 'only/remote' expect(provider).to receive(:update_submodules) - expect(provider).to receive(:git).with('branch', '--no-color', '-a').at_least(:once).and_return(resource.value(:revision)) - expect(provider).to receive(:git).with('checkout', '--force', resource.value(:revision)) - expect(provider).to receive(:git).with('reset', '--hard', "origin/#{resource.value(:revision)}") + expect(provider).to receive(:exec_git).with('branch', '--no-color', '-a').at_least(:once).and_return(resource.value(:revision)) + expect(provider).to receive(:exec_git).with('checkout', '--force', resource.value(:revision)) + expect(provider).to receive(:exec_git).with('reset', '--hard', "origin/#{resource.value(:revision)}") provider.revision = resource.value(:revision) end end context "when it's a commit or tag" do it "uses 'git fetch' and 'git reset'" do resource[:revision] = 'a-commit-or-tag' - expect(provider).to receive(:git).with('branch', '--no-color', '-a').once.and_return(fixture(:git_branch_a)) - expect(provider).to receive(:git).with('checkout', '--force', resource.value(:revision)) - expect(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(fixture(:git_branch_a)) - expect(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(fixture(:git_branch_a)) - expect(provider).to receive(:git).with('submodule', 'update', '--init', '--recursive') + expect(provider).to receive(:exec_git).with('branch', '--no-color', '-a').once.and_return(fixture(:git_branch_a)) + expect(provider).to receive(:exec_git).with('checkout', '--force', resource.value(:revision)) + expect(provider).to receive(:exec_git).with('branch', '--no-color', '-a').and_return(fixture(:git_branch_a)) + expect(provider).to receive(:exec_git).with('branch', '--no-color', '-a').and_return(fixture(:git_branch_a)) + expect(provider).to receive(:exec_git).with('submodule', 'update', '--init', '--recursive') provider.revision = resource.value(:revision) end end context 'when ignoring local changes' do it "uses 'git stash'" do resource[:revision] = 'a-commit-or-tag' resource[:keep_local_changes] = true - expect(provider).to receive(:git).with('stash', 'save') - expect(provider).to receive(:git).with('branch', '--no-color', '-a').once.and_return(fixture(:git_branch_a)) - expect(provider).to receive(:git).with('checkout', '--force', resource.value(:revision)) - expect(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(fixture(:git_branch_a)) - expect(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(fixture(:git_branch_a)) - expect(provider).to receive(:git).with('submodule', 'update', '--init', '--recursive') - expect(provider).to receive(:git).with('stash', 'pop') + expect(provider).to receive(:exec_git).with('stash', 'save') + expect(provider).to receive(:exec_git).with('branch', '--no-color', '-a').once.and_return(fixture(:git_branch_a)) + expect(provider).to receive(:exec_git).with('checkout', '--force', resource.value(:revision)) + expect(provider).to receive(:exec_git).with('branch', '--no-color', '-a').and_return(fixture(:git_branch_a)) + expect(provider).to receive(:exec_git).with('branch', '--no-color', '-a').and_return(fixture(:git_branch_a)) + expect(provider).to receive(:exec_git).with('submodule', 'update', '--init', '--recursive') + expect(provider).to receive(:exec_git).with('stash', 'pop') provider.revision = resource.value(:revision) end end end context 'when checking the source property' do before(:each) do - expect_chdir('/tmp/test') - allow(provider).to receive(:git).with('config', 'remote.origin.url').and_return('') - allow(provider).to receive(:git).with('fetch', 'origin') # FIXME - allow(provider).to receive(:git).with('fetch', '--tags', 'origin') - allow(provider).to receive(:git).with('rev-parse', 'HEAD').and_return('currentsha') - allow(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(branch_a_list(resource.value(:revision))) - allow(provider).to receive(:git).with('tag', '-l').and_return('Hello') - end - - context "when there's a single remote 'origin'" do - it 'and_return the URL for the remote' do - resource[:source] = 'http://example.com' - expect(provider).to receive(:git).with('remote').and_return("origin\n") - expect(provider).to receive(:git).with('config', '--get', 'remote.origin.url').and_return('http://example.com') - expect(provider.source).to eq(resource.value(:source)) - end + allow(provider).to receive(:exec_git).with('config', 'remote.origin.url').and_return('') + allow(provider).to receive(:exec_git).with('fetch', 'origin') # FIXME + allow(provider).to receive(:exec_git).with('fetch', '--tags', 'origin') + allow(provider).to receive(:exec_git).with('rev-parse', 'HEAD').and_return('currentsha') + allow(provider).to receive(:exec_git).with('branch', '--no-color', '-a').and_return(branch_a_list(resource.value(:revision))) + allow(provider).to receive(:exec_git).with('tag', '-l').and_return('Hello') end context "when there's more than one remote" do it 'and_return the remotes as a hash' do resource[:source] = { 'origin' => 'git://git@foo.com/bar.git', 'other' => 'git://git@foo.com/baz.git' } - expect(provider).to receive(:git).with('remote').and_return("origin\nother\n") - expect(provider).to receive(:git).with('config', '--get', 'remote.origin.url').and_return('git://git@foo.com/bar.git') - expect(provider).to receive(:git).with('config', '--get', 'remote.other.url').and_return('git://git@foo.com/baz.git') + expect_chdir + expect(provider).to receive(:exec_git).with('remote').and_return("origin\nother\n") + expect(provider).to receive(:exec_git).with('config', '--get', 'remote.origin.url').and_return('git://git@foo.com/bar.git') + expect(provider).to receive(:exec_git).with('config', '--get', 'remote.other.url').and_return('git://git@foo.com/baz.git') expect(provider.source).to eq(resource.value(:source)) end end end context 'when updating remotes' do context 'when with string to string' do it 'fails' do resource[:source] = 'git://git@foo.com/bar.git' resource[:force] = false expect(provider).to receive(:source).and_return('git://git@foo.com/foo.git') expect(provider).to receive(:path_exists?).and_return(true) expect(provider).to receive(:path_empty?).and_return(false) expect { provider.source = resource.value(:source) }.to raise_error(Puppet::Error) end end context 'when with hash to hash' do it 'adds any new remotes, update any existing remotes, remove deleted remotes' do expect_chdir resource[:source] = { 'origin' => 'git://git@foo.com/bar.git', 'new_remote' => 'git://git@foo.com/baz.git' } expect(provider).to receive(:source).and_return( 'origin' => 'git://git@foo.com/foo.git', 'old_remote' => 'git://git@foo.com/old.git', ) - expect(provider).to receive(:git).once.with('config', '-l').and_return("remote.old_remote.url=git://git@foo.com/old.git\n", "remote.origin.url=git://git@foo.com/foo.git\n") - expect(provider).to receive(:git).with('remote', 'remove', 'old_remote') - expect(provider).to receive(:git).with('remote', 'set-url', 'origin', 'git://git@foo.com/bar.git') - expect(provider).to receive(:git).with('remote', 'add', 'new_remote', 'git://git@foo.com/baz.git') - expect(provider).to receive(:git).with('remote', 'update') + expect(provider).to receive(:exec_git).once.with('config', '-l').and_return("remote.old_remote.url=git://git@foo.com/old.git\n", "remote.origin.url=git://git@foo.com/foo.git\n") + expect(provider).to receive(:exec_git).with('remote', 'remove', 'old_remote') + expect(provider).to receive(:exec_git).with('remote', 'set-url', 'origin', 'git://git@foo.com/bar.git') + expect(provider).to receive(:exec_git).with('remote', 'add', 'new_remote', 'git://git@foo.com/baz.git') + expect(provider).to receive(:exec_git).with('remote', 'update') provider.source = resource.value(:source) end end context 'when with string to hash' do it 'adds any new remotes, update origin remote' do expect_chdir resource[:source] = { 'origin' => 'git://git@foo.com/bar.git', 'new_remote' => 'git://git@foo.com/baz.git' } expect(provider).to receive(:source).and_return('git://git@foo.com/foo.git') - expect(provider).to receive(:git).at_least(:once).with('config', '-l').and_return("remote.origin.url=git://git@foo.com/foo.git\n") - expect(provider).to receive(:git).with('remote', 'set-url', 'origin', 'git://git@foo.com/bar.git') - expect(provider).to receive(:git).with('remote', 'add', 'new_remote', 'git://git@foo.com/baz.git') - expect(provider).to receive(:git).with('remote', 'update') + expect(provider).to receive(:exec_git).at_least(:once).with('config', '-l').and_return("remote.origin.url=git://git@foo.com/foo.git\n") + expect(provider).to receive(:exec_git).with('remote', 'set-url', 'origin', 'git://git@foo.com/bar.git') + expect(provider).to receive(:exec_git).with('remote', 'add', 'new_remote', 'git://git@foo.com/baz.git') + expect(provider).to receive(:exec_git).with('remote', 'update') provider.source = resource.value(:source) end end context 'when with hash to string' do it 'updates origin remote, remove deleted remotes' do expect_chdir resource[:source] = 'git://git@foo.com/baz.git' expect(provider).to receive(:source).and_return( 'origin' => 'git://git@foo.com/foo.git', 'old_remote' => 'git://git@foo.com/old.git', ) - expect(provider).to receive(:git).with('remote', 'remove', 'old_remote') - expect(provider).to receive(:git).with('config', '-l').at_most(:twice).and_return("remote.origin.url=git://git@foo.com/foo.git\n", "remote.other.url=git://git@foo.com/bar.git\n") - expect(provider).to receive(:git).with('remote', 'set-url', 'origin', 'git://git@foo.com/baz.git') - expect(provider).to receive(:git).with('remote', 'update') + expect(provider).to receive(:exec_git).with('remote', 'remove', 'old_remote') + expect(provider).to receive(:exec_git).with('config', '-l').at_most(:twice).and_return("remote.origin.url=git://git@foo.com/foo.git\n", "remote.other.url=git://git@foo.com/bar.git\n") + expect(provider).to receive(:exec_git).with('remote', 'set-url', 'origin', 'git://git@foo.com/baz.git') + expect(provider).to receive(:exec_git).with('remote', 'update') provider.source = resource.value(:source) end end end # rubocop:enable RSpec/ExampleLength context 'when updating references' do it "uses 'git fetch --tags'" do resource.delete(:source) expect_chdir - expect(provider).to receive(:git).with('fetch', 'origin') - expect(provider).to receive(:git).with('fetch', '--tags', 'origin') + expect(provider).to receive(:exec_git).with('fetch', 'origin') + expect(provider).to receive(:exec_git).with('fetch', '--tags', 'origin') provider.update_references end end describe 'latest?' do context 'when true' do it do expect(provider).to receive(:revision).and_return('testrev') expect(provider).to receive(:latest_revision).and_return('testrev') expect(provider).to be_latest end end context 'when false' do it do expect(provider).to receive(:revision).and_return('main') expect(provider).to receive(:latest_revision).and_return('testrev') expect(provider).not_to be_latest end end end describe 'trust_server_cert' do context 'when true' do before :each do resource[:trust_server_cert] = true end it 'raises error with git 1.7.0' do - allow(provider).to receive(:git).with('--version').and_return '1.7.0' + allow(provider).to receive(:exec_git).with('--version').and_return '1.7.0' expect { provider.create }.to raise_error RuntimeError, %r{Can't set sslVerify to false} end it 'compiles with git 2.13.0' do resource[:revision] = 'only/remote' expect(Dir).to receive(:chdir).with('/').once.and_yield expect(Dir).to receive(:chdir).with('/tmp/test').at_least(:once).and_yield - expect(provider).to receive(:git).with('-c', 'http.sslVerify=false', 'clone', resource.value(:source), resource.value(:path)) + expect(provider).to receive(:exec_git).with('-c', 'http.sslVerify=false', 'clone', resource.value(:source), resource.value(:path)) expect(provider).to receive(:update_submodules) expect(provider).to receive(:update_remote_url).with('origin', resource.value(:source)).and_return false - expect(provider).to receive(:git).with('-c', 'http.sslVerify=false', 'branch', '--no-color', '-a').and_return(branch_a_list(resource.value(:revision))) - expect(provider).to receive(:git).with('-c', 'http.sslVerify=false', 'checkout', '--force', resource.value(:revision)) + expect(provider).to receive(:exec_git).with('-c', 'http.sslVerify=false', 'branch', '--no-color', '-a').and_return(branch_a_list(resource.value(:revision))) + expect(provider).to receive(:exec_git).with('-c', 'http.sslVerify=false', 'checkout', '--force', resource.value(:revision)) - allow(provider).to receive(:git).with('--version').and_return '2.13.0' + allow(provider).to receive(:exec_git).with('--version').and_return '2.13.0' expect { provider.create }.not_to raise_error end end end context 'owner' do it 'without excludes run FileUtils.chown_R' do resource[:owner] = 'john' expect_chdir expect(FileUtils).to receive(:chown_R).with('john', nil, '/tmp/test') - expect(provider).to receive(:git).with('fetch', 'origin') - expect(provider).to receive(:git).with('fetch', '--tags', 'origin') + expect(provider).to receive(:exec_git).with('fetch', 'origin') + expect(provider).to receive(:exec_git).with('fetch', '--tags', 'origin') provider.update_references end it 'with excludes run filtered chown_R' do resource[:owner] = 'john' resource[:excludes] = ['bzr', 'cvs', 'hg', 'p4', 'svn'] expect_chdir filtered_files = ['git/a', 'git/b'] expect(provider).to receive(:files).and_return(filtered_files) expect(FileUtils).to receive(:chown).with('john', nil, filtered_files) expect(provider).to receive(:set_excludes) - expect(provider).to receive(:git).with('fetch', 'origin') - expect(provider).to receive(:git).with('fetch', '--tags', 'origin') + expect(provider).to receive(:exec_git).with('fetch', 'origin') + expect(provider).to receive(:exec_git).with('fetch', '--tags', 'origin') provider.update_references end end end