diff options
-rw-r--r-- | README.markdown | 28 | ||||
-rw-r--r-- | lib/puppet/provider/vcsrepo/git.rb | 74 | ||||
-rw-r--r-- | lib/puppet/type/vcsrepo.rb | 9 | ||||
-rw-r--r-- | spec/acceptance/clone_repo_spec.rb | 13 | ||||
-rw-r--r-- | spec/acceptance/modules_753_spec.rb | 68 | ||||
-rw-r--r-- | spec/unit/puppet/provider/vcsrepo/git_spec.rb | 67 |
6 files changed, 200 insertions, 59 deletions
diff --git a/README.markdown b/README.markdown index cefa50b..34a51e2 100644 --- a/README.markdown +++ b/README.markdown @@ -145,6 +145,29 @@ To keep the repository at the latest revision (**WARNING:** this will always ove revision => 'master', } +To clone the repository but skip initialiazing submodules, + + vcsrepo { "/path/to/repo": + ensure => latest, + provider => git, + source => 'git://example.com/repo.git', + submodules => false, + } + +##### Using multiple remotes with a repository +Instead of specifying a single string in the 'source' property, you can specify a hash with multiple name => URL mappings, + + vcsrepo { "/path/to/repo": + ensure => present, + provider => git, + source => { + "origin" => "https://github.com/puppetlabs/puppetlabs-vcsrepo.git", + "other_remote" => "https://github.com/other_user/puppetlabs-vcsrepo.git" + }, + } + +It is important to note that you must specify a mapping for the remote that is specified in the 'remote' property - this is set to 'origin' by default. + #####Sources that use SSH When your source uses SSH, such as 'username@server:…', you can manage your SSH keys with Puppet using the [require](http://docs.puppetlabs.com/references/stable/metaparameter.html#require) metaparameter in `vcsrepo` to ensure they are present. @@ -483,6 +506,7 @@ The vcsrepo module is slightly unusual in that it is simply a type and providers * `ssh_identity` - The provider supports a configurable SSH identity file. (Available with `git` and `hg`.) * `user` - The provider can run as a different user. (Available with `git`, `hg` and `cvs`.) * `p4config` - The provider support setting the P4CONFIG environment. (Available with `p4`.) +* `submodules` - The provider supports repository submodules which can be optionally initialized. (Available with `git`.) ####Parameters @@ -511,9 +535,9 @@ The vcsrepo module is slightly unusual in that it is simply a type and providers ####Features and Parameters by Provider #####`git` -**Features**: `bare_repositories`, `depth`, `multiple_remotes`, `reference_tracking`, `ssh_identity`, `user` +**Features**: `bare_repositories`, `depth`, `multiple_remotes`, `reference_tracking`, `ssh_identity`, `user`, `submodules` -**Parameters**: `depth`, `ensure`, `excludes`, `force`, `group`, `identity`, `owner`, `path`, `provider`, `remote`, `revision`, `source`, `user` +**Parameters**: `depth`, `ensure`, `excludes`, `force`, `group`, `identity`, `owner`, `path`, `provider`, `remote`, `revision`, `source`, `user`, `submodules` #####`bzr` **Features**: `reference_tracking` diff --git a/lib/puppet/provider/vcsrepo/git.rb b/lib/puppet/provider/vcsrepo/git.rb index b1e556d..9d3f7f3 100644 --- a/lib/puppet/provider/vcsrepo/git.rb +++ b/lib/puppet/provider/vcsrepo/git.rb @@ -5,7 +5,7 @@ Puppet::Type.type(:vcsrepo).provide(:git, :parent => Puppet::Provider::Vcsrepo) commands :git => 'git' - has_features :bare_repositories, :reference_tracking, :ssh_identity, :multiple_remotes, :user, :depth + has_features :bare_repositories, :reference_tracking, :ssh_identity, :multiple_remotes, :user, :depth, :submodules def create if @resource.value(:revision) and @resource.value(:ensure) == :bare @@ -14,13 +14,16 @@ Puppet::Type.type(:vcsrepo).provide(:git, :parent => Puppet::Provider::Vcsrepo) if !@resource.value(:source) init_repository(@resource.value(:path)) else - clone_repository(@resource.value(:source), @resource.value(:path)) + clone_repository(default_url, @resource.value(:path)) + update_remotes + if @resource.value(:revision) checkout end - if @resource.value(:ensure) != :bare + if @resource.value(:ensure) != :bare && @resource.value(:submodules) == :true update_submodules end + end update_owner_and_excludes end @@ -82,9 +85,25 @@ Puppet::Type.type(:vcsrepo).provide(:git, :parent => Puppet::Provider::Vcsrepo) bare_git_config_exists? && !working_copy_exists? 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 + if @resource.value(:source).is_a?(Hash) + if @resource.value(:source).has_key?(@resource.value(:remote)) + @resource.value(:source)[@resource.value(:remote)] + else + fail("You must specify the URL for #{@resource.value(:remote)} in the :source hash") + end + else + @resource.value(:source) + end + end + def working_copy_exists? if @resource.value(:source) and File.exists?(File.join(@resource.value(:path), '.git', 'config')) - File.readlines(File.join(@resource.value(:path), '.git', 'config')).grep(/#{@resource.value(:source)}/).any? + File.readlines(File.join(@resource.value(:path), '.git', 'config')).grep(/#{default_url}/).any? else File.directory?(File.join(@resource.value(:path), '.git')) end @@ -94,18 +113,53 @@ Puppet::Type.type(:vcsrepo).provide(:git, :parent => Puppet::Provider::Vcsrepo) working_copy_exists? || bare_exists? end - def update_remote_origin_url - current = git_with_identity('config', "remote.#{@resource.value(:remote)}.url") - unless @resource.value(:source).nil? - if current.nil? or current.strip != @resource.value(:source) - git_with_identity('config', "remote.#{@resource.value(:remote)}.url", @resource.value(:source)) + def update_remote_url(remote_name, remote_url) + do_update = false + current = git_with_identity('config', '-l') + + unless remote_url.nil? + # Check if remote exists at all, regardless of URL. + # If remote doesn't exist, add it + if not current.include? "remote.#{remote_name}.url" + git_with_identity('remote','add', remote_name, remote_url) + return true + + # If remote exists, but URL doesn't match, update URL + elsif not current.include? "remote.#{remote_name}.url=#{remote_url}" + git_with_identity('remote','set-url', remote_name, remote_url) + return true + else + return false end end + + end + + def update_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 @resource.value(:source).is_a?(Hash) + @resource.value(:source).each do |remote_name, remote_url| + at_path { do_update |= update_remote_url(remote_name, remote_url) } + end + else + at_path { do_update |= update_remote_url(@resource.value(:remote), @resource.value(:source)) } + end + + # If at least one remote was added or updated, then we must + # call the 'git remote update' command + if do_update == true + at_path { git_with_identity('remote','update') } + end + end def update_references at_path do - update_remote_origin_url + update_remotes git_with_identity('fetch', @resource.value(:remote)) git_with_identity('fetch', '--tags', @resource.value(:remote)) update_owner_and_excludes diff --git a/lib/puppet/type/vcsrepo.rb b/lib/puppet/type/vcsrepo.rb index b8836d4..3bf4029 100644 --- a/lib/puppet/type/vcsrepo.rb +++ b/lib/puppet/type/vcsrepo.rb @@ -43,6 +43,9 @@ Puppet::Type.newtype(:vcsrepo) do feature :p4config, "The provider understands Perforce Configuration" + feature :submodules, + "The repository contains submodules which can be optionally initialized" + ensurable do attr_accessor :latest @@ -205,6 +208,12 @@ Puppet::Type.newtype(:vcsrepo) do desc "The Perforce P4CONFIG environment." end + newparam :submodules, :required_features => [:submodules] do + desc "Initialize and update each submodule in the repository." + newvalues(:true, :false) + defaultto true + end + autorequire(:package) do ['git', 'git-core'] end diff --git a/spec/acceptance/clone_repo_spec.rb b/spec/acceptance/clone_repo_spec.rb index 4e9293b..2cca061 100644 --- a/spec/acceptance/clone_repo_spec.rb +++ b/spec/acceptance/clone_repo_spec.rb @@ -327,21 +327,18 @@ describe 'clones a remote repo' do end context 'and noop' do - let(:repo_name) do - 'testrepo_already_exists' - end before(:all) do - shell("mkdir #{tmpdir}/#{repo_name}") - shell("cd #{tmpdir}/#{repo_name} && git init") - shell("cd #{tmpdir}/#{repo_name} && touch a && git add a && git commit -m 'a'") + shell("mkdir #{tmpdir}/testrepo_already_exists") + shell("cd #{tmpdir}/testrepo_already_exists && git init") + shell("cd #{tmpdir}/testrepo_already_exists && touch a && git add a && git commit -m 'a'") end after(:all) do - shell("rm -rf #{tmpdir}/#{repo_name}") + shell("rm -rf #{tmpdir}/testrepo_already_exists") end it 'applies the manifest' do pp = <<-EOS - vcsrepo { "#{tmpdir}/#{repo_name}": + vcsrepo { "#{tmpdir}/testrepo_already_exists": ensure => present, source => "file://#{tmpdir}/testrepo.git", provider => git, diff --git a/spec/acceptance/modules_753_spec.rb b/spec/acceptance/modules_753_spec.rb new file mode 100644 index 0000000..e4e332b --- /dev/null +++ b/spec/acceptance/modules_753_spec.rb @@ -0,0 +1,68 @@ +require 'spec_helper_acceptance' + +tmpdir = default.tmpdir('vcsrepo') + +describe 'clones a remote repo' do + before(:all) do + my_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) + shell("mkdir -p #{tmpdir}") # win test + end + + after(:all) do + shell("rm -rf #{tmpdir}/vcsrepo") + end + + context 'clone with single remote' do + it 'clones from default remote' do + pp = <<-EOS + vcsrepo { "#{tmpdir}/vcsrepo": + ensure => present, + provider => git, + source => "https://github.com/puppetlabs/puppetlabs-vcsrepo.git", + } + EOS + + apply_manifest(pp, :catch_failures => true) + + end + + it "git config output should contain the remote" do + shell("/usr/bin/git config -l -f #{tmpdir}/vcsrepo/.git/config") do |r| + expect(r.stdout).to match(/remote.origin.url=https:\/\/github.com\/puppetlabs\/puppetlabs-vcsrepo.git/) + end + end + + after(:all) do + shell("rm -rf #{tmpdir}/vcsrepo") + end + + end + + context 'clone with multiple remotes' do + it 'clones from default remote and adds 2 remotes to config file' do + pp = <<-EOS + vcsrepo { "#{tmpdir}/vcsrepo": + ensure => present, + provider => git, + source => {"origin" => "https://github.com/puppetlabs/puppetlabs-vcsrepo.git", "test1" => "https://github.com/puppetlabs/puppetlabs-vcsrepo.git"}, + } + EOS + + apply_manifest(pp, :catch_failures => true) + + end + + it "git config output should contain the remotes" do + shell("/usr/bin/git config -l -f #{tmpdir}/vcsrepo/.git/config") do |r| + expect(r.stdout).to match(/remote.origin.url=https:\/\/github.com\/puppetlabs\/puppetlabs-vcsrepo.git/) + expect(r.stdout).to match(/remote.test1.url=https:\/\/github.com\/puppetlabs\/puppetlabs-vcsrepo.git/) + end + end + + after(:all) do + shell("rm -rf #{tmpdir}/vcsrepo") + end + + end + +end diff --git a/spec/unit/puppet/provider/vcsrepo/git_spec.rb b/spec/unit/puppet/provider/vcsrepo/git_spec.rb index edc7202..d0153a1 100644 --- a/spec/unit/puppet/provider/vcsrepo/git_spec.rb +++ b/spec/unit/puppet/provider/vcsrepo/git_spec.rb @@ -35,6 +35,7 @@ branches Dir.expects(:chdir).with('/tmp/test').at_least_once.yields provider.expects(:git).with('clone', resource.value(:source), resource.value(:path)) provider.expects(:update_submodules) + provider.expects(:update_remote_url).with("origin", resource.value(:source)).returns false provider.expects(:git).with('branch', '-a').returns(branch_a_list(resource.value(:revision))) provider.expects(:git).with('checkout', '--force', resource.value(:revision)) provider.create @@ -48,6 +49,7 @@ branches Dir.expects(:chdir).with('/tmp/test').at_least_once.yields provider.expects(:git).with('clone', '--origin', 'not_origin', resource.value(:source), resource.value(:path)) provider.expects(:update_submodules) + provider.expects(:update_remote_url).with("not_origin", resource.value(:source)).returns false provider.expects(:git).with('branch', '-a').returns(branch_a_list(resource.value(:revision))) provider.expects(:git).with('checkout', '--force', resource.value(:revision)) provider.create @@ -62,6 +64,7 @@ branches Dir.expects(:chdir).with('/tmp/test').at_least_once.yields provider.expects(:git).with('clone', '--depth', '1', resource.value(:source), resource.value(:path)) provider.expects(:update_submodules) + provider.expects(:update_remote_url).with("origin", resource.value(:source)).returns false provider.expects(:git).with('branch', '-a').returns(branch_a_list(resource.value(:revision))) provider.expects(:git).with('checkout', '--force', resource.value(:revision)) provider.create @@ -75,6 +78,7 @@ branches Dir.expects(:chdir).with('/tmp/test').at_least_once.yields provider.expects(:git).with('clone', resource.value(:source), resource.value(:path)) provider.expects(:update_submodules) + provider.expects(:update_remote_url).with("origin", resource.value(:source)).returns false provider.expects(:git).with('branch', '-a').returns(branch_a_list(resource.value(:revision))) provider.expects(:git).with('checkout', '--force', resource.value(:revision)) provider.create @@ -84,6 +88,7 @@ branches resource.delete(:revision) provider.expects(:git).with('clone', resource.value(:source), resource.value(:path)) provider.expects(:update_submodules) + provider.expects(:update_remotes) provider.create end end @@ -100,6 +105,7 @@ branches resource[:ensure] = :bare resource.delete(:revision) provider.expects(:git).with('clone', '--bare', resource.value(:source), resource.value(:path)) + provider.expects(:update_remotes) provider.create end end @@ -171,6 +177,7 @@ branches provider.destroy provider.expects(:git).with('clone',resource.value(:source), resource.value(:path)) provider.expects(:update_submodules) + provider.expects(:update_remote_url).with("origin", resource.value(:source)).returns false provider.expects(:git).with('branch', '-a').returns(branch_a_list(resource.value(:revision))) provider.expects(:git).with('checkout', '--force', resource.value(:revision)) provider.create @@ -211,6 +218,7 @@ branches context "when its SHA is not different than the current SHA" do it "should return the ref" do provider.expects(:git).with('rev-parse', resource.value(:revision)).returns('currentsha') + provider.expects(:update_remotes) expect(provider.revision).to eq(resource.value(:revision)) end end @@ -218,6 +226,7 @@ branches context "when its SHA is different than the current SHA" do it "should return the current SHA" do provider.expects(:git).with('rev-parse', resource.value(:revision)).returns('othersha') + provider.expects(:update_remotes) expect(provider.revision).to eq(resource.value(:revision)) end end @@ -226,6 +235,7 @@ branches it "should return the revision" do provider.stubs(:git).with('branch', '-a').returns(" remotes/origin/#{resource.value(:revision)}") provider.expects(:git).with('rev-parse', "origin/#{resource.value(:revision)}").returns("newsha") + provider.expects(:update_remotes) expect(provider.revision).to eq(resource.value(:revision)) end end @@ -234,7 +244,7 @@ branches it "should fail" do provider.expects(:git).with('branch', '-a').returns(branch_a_list) provider.expects(:git).with('rev-parse', '--revs-only', resource.value(:revision)).returns('') - + provider.expects(:update_remotes) expect { provider.revision }.to raise_error(Puppet::Error, /not a local or remote ref$/) end end @@ -242,12 +252,26 @@ branches context "when the source is modified" do it "should update the origin url" do resource[:source] = 'git://git@foo.com/bar.git' - provider.expects(:git).with('config', 'remote.origin.url').returns('old') - provider.expects(:git).with('config', 'remote.origin.url', 'git://git@foo.com/bar.git') + provider.expects(:git).with('config', '-l').returns("remote.origin.url=git://git@foo.com/foo.git\n") + provider.expects(:git).with('remote', 'set-url', 'origin', 'git://git@foo.com/bar.git') + provider.expects(:git).with('remote','update') + provider.expects(:git).with('rev-parse', resource.value(:revision)).returns('currentsha') + expect(provider.revision).to eq(resource.value(:revision)) + end + end + + context "when multiple sources are modified" do + it "should update the urls" do + resource[:source] = {"origin" => "git://git@foo.com/bar.git", "new_remote" => "git://git@foo.com/baz.git"} + provider.expects(:git).at_least_once.with('config', '-l').returns("remote.origin.url=git://git@foo.com/foo.git\n", "remote.origin.url=git://git@foo.com/bar.git\n") + provider.expects(:git).with('remote', 'set-url', 'origin', 'git://git@foo.com/bar.git') + provider.expects(:git).with('remote', 'add', 'new_remote', 'git://git@foo.com/baz.git') + provider.expects(:git).with('remote','update') provider.expects(:git).with('rev-parse', resource.value(:revision)).returns('currentsha') expect(provider.revision).to eq(resource.value(:revision)) end end + end context "setting the revision property" do @@ -291,48 +315,13 @@ branches it "should use 'git fetch --tags'" do resource.delete(:source) expects_chdir - provider.expects(:git).with('config', 'remote.origin.url').returns('') + provider.expects(:git).with('config', '-l').returns("remote.origin.url=git://git@foo.com/foo.git\n") provider.expects(:git).with('fetch', 'origin') provider.expects(:git).with('fetch', '--tags', 'origin') provider.update_references end end - context "checking if revision" do - before do - expects_chdir - provider.expects(:git).with('branch', '-a').returns(fixture(:git_branch_a)) - end - context "is a local branch" do - context "when it's listed in 'git branch -a'" do - it "should return true" do - resource[:revision] = 'feature/foo' - expect(provider).to be_local_branch_revision - end - end - context "when it's not listed in 'git branch -a'" do - it "should return false" do - resource[:revision] = 'feature/notexist' - expect(provider).not_to be_local_branch_revision - end - end - end - context "is a remote branch" do - context "when it's listed in 'git branch -a' with an 'origin/' prefix" do - it "should return true" do - resource[:revision] = 'only/remote' - expect(provider).to be_remote_branch_revision - end - end - context "when it's not listed in 'git branch -a' with an 'origin/' prefix" do - it "should return false" do - resource[:revision] = 'only/local' - expect(provider).not_to be_remote_branch_revision - end - end - end - end - describe 'latest?' do context 'when true' do it do |