Merge pull request #46 from Paulche/master
authorAaron Stone <aaron@serendipity.cx>
Wed, 17 Jul 2013 21:25:51 +0000 (14:25 -0700)
committerAaron Stone <aaron@serendipity.cx>
Wed, 17 Jul 2013 21:25:51 +0000 (14:25 -0700)
Enable unlimited nesting for submodule updating

14 files changed:
.travis.yml [new file with mode: 0644]
Gemfile
Gemfile.lock
Modulefile
README.GIT.markdown
README.HG.markdown
README.markdown
lib/puppet/provider/vcsrepo/bzr.rb
lib/puppet/provider/vcsrepo/git.rb
lib/puppet/provider/vcsrepo/hg.rb
lib/puppet/provider/vcsrepo/svn.rb
lib/puppet/type/vcsrepo.rb
spec/unit/puppet/provider/vcsrepo/bzr_spec.rb
spec/unit/puppet/provider/vcsrepo/git_spec.rb

diff --git a/.travis.yml b/.travis.yml
new file mode 100644 (file)
index 0000000..f349b8d
--- /dev/null
@@ -0,0 +1,25 @@
+---
+branches:
+  only:
+  - master
+notifications:
+  email: false
+language: ruby
+script: 'bundle exec rake spec'
+after_success:
+- git clone -q git://github.com/puppetlabs/ghpublisher.git .forge-releng
+- .forge-releng/publish
+rvm:
+- 1.8.7
+- 1.9.3
+env:
+  matrix:
+  - PUPPET_VERSION=2.6.18
+  - PUPPET_VERSION=2.7.21
+  - PUPPET_VERSION=3.1.1
+  global:
+  - PUBLISHER_LOGIN=puppetlabs
+  - secure: |-
+      ZiIkYd9+CdPzpwSjFPnVkCx1FIlipxpbdyD33q94h2Tj5zXjNb1GXizVy0NR
+      kVxGhU5Ld8y9z8DTqKRgCI1Yymg3H//OU++PKLOQj/X5juWVR4URBNPeBOzu
+      IJBDl1MADKA4i1+jAZPpz4mTvTtKS4pWKErgCSmhSfsY1hs7n6c=
diff --git a/Gemfile b/Gemfile
index cde1d14..9b6706d 100644 (file)
--- a/Gemfile
+++ b/Gemfile
@@ -1,5 +1,5 @@
-source :rubygems
+source 'https://rubygems.org'
 gem 'rake', '~> 0.8.7'
-gem 'rspec', '~> 1.2.9'
-gem 'mocha', '~> 0.12.7', :require => false
+gem 'rspec', '~> 1.3'
+gem 'mocha', '~> 0.12.9', :require => false
 gem 'puppet', '~> 2.7'
index 210a2dd..ce22804 100644 (file)
@@ -1,20 +1,20 @@
 GEM
-  remote: http://rubygems.org/
+  remote: https://rubygems.org/
   specs:
-    facter (1.6.13)
+    facter (1.7.2)
     metaclass (0.0.1)
-    mocha (0.12.7)
+    mocha (0.12.10)
       metaclass (~> 0.0.1)
-    puppet (2.7.19)
+    puppet (2.7.22)
       facter (~> 1.5)
     rake (0.8.7)
-    rspec (1.2.9)
+    rspec (1.3.2)
 
 PLATFORMS
   ruby
 
 DEPENDENCIES
-  mocha (~> 0.12.7)
+  mocha (~> 0.12.9)
   puppet (~> 2.7)
   rake (~> 0.8.7)
-  rspec (~> 1.2.9)
+  rspec (~> 1.3)
index e5e1f8a..5d95183 100644 (file)
@@ -1,2 +1,4 @@
 name 'puppetlabs/vcsrepo'
-version '0.1.1'
+version '0.1.2'
+summary 'Manage repositories from various version control systems'
+description 'Manage repositories from various version control systems'
index d6b8afe..846bdcc 100644 (file)
@@ -76,9 +76,15 @@ Keep the repository at the latest revision (note: this will always overwrite loc
 For sources that use SSH (eg, `username@server:...`)
 ----------------------------------------------------
 
-Manage your SSH keys with Puppet and use `require` in your `vcsrepo`
-to ensure they are present.  For more information, see the `require`
-metaparameter documentation[1].
+If your SSH key is associated with a user, simply fill the `user` parameter to use his keys.
+
+Example:
+
+    user => 'toto'  # will use toto's $HOME/.ssh setup
+
+
+Otherwise, manage your SSH keys with Puppet and use `require` in your `vcsrepo` to ensure they are present.
+For more information, see the `require` metaparameter documentation[1].
 
 More Examples
 -------------
index b1680c8..55ceef4 100644 (file)
@@ -27,7 +27,7 @@ For a specific changeset, use `revision`:
     vcsrepo { "/path/to/repo":
         ensure   => present,
         provider => hg,
-        source   => "http://hg.example.com/myrepo"
+        source   => "http://hg.example.com/myrepo",
         revision => '21ea4598c962'
     }
 
@@ -36,10 +36,28 @@ You can also set `revision` to a tag:
     vcsrepo { "/path/to/repo":
         ensure   => present,
         provider => hg,
-        source   => "http://hg.example.com/myrepo"
+        source   => "http://hg.example.com/myrepo",
         revision => '1.1.2'
     }
 
+Check out as a user:
+
+    vcsrepo { "/path/to/repo":
+        ensure   => present,
+        provider => hg,
+        source   => "http://hg.example.com/myrepo",
+        user     => 'user'
+    }
+
+Specify an SSH identity key:
+
+    vcsrepo { "/path/to/repo":
+        ensure   => present,
+        provider => hg,
+        source   => "ssh://hg@hg.example.com/myrepo",
+        identity => "/home/user/.ssh/id_dsa,
+    }
+
 For sources that use SSH (eg, `ssh://...`)
 ------------------------------------------
 
index 823e044..8487256 100644 (file)
@@ -1,6 +1,8 @@
 vcsrepo
 =======
 
+[![Build Status](https://travis-ci.org/puppetlabs/puppetlabs-vcsrepo.png?branch=master)](https://travis-ci.org/puppetlabs/puppetlabs-vcsrepo)
+
 Purpose
 -------
 
index 6169929..6688ce8 100644 (file)
@@ -45,7 +45,25 @@ Puppet::Type.type(:vcsrepo).provide(:bzr, :parent => Puppet::Provider::Vcsrepo)
   end
 
   def revision=(desired)
-    bzr('update', '-r', desired, @resource.value(:path))
+    at_path do
+      begin
+        bzr('update', '-r', desired)
+      rescue Puppet::ExecutionFailure
+        bzr('update', '-r', desired, ':parent')
+      end
+    end
+  end
+
+  def latest
+    at_path do
+      bzr('version-info', ':parent')[/^revision-id:\s+(\S+)/, 1]
+    end
+  end
+
+  def latest?
+    at_path do
+      return self.revision == self.latest
+    end
   end
 
   private
index 66c4d07..d971e90 100644 (file)
@@ -5,7 +5,7 @@ Puppet::Type.type(:vcsrepo).provide(:git, :parent => Puppet::Provider::Vcsrepo)
 
   ##TODO modify the commands below so that the su - is included
   optional_commands :git => 'git',
-                    :su => 'su'
+                    :su  => 'su'
   has_features :bare_repositories, :reference_tracking, :ssh_identity, :multiple_remotes, :user
 
   def create
@@ -54,9 +54,22 @@ Puppet::Type.type(:vcsrepo).provide(:git, :parent => Puppet::Provider::Vcsrepo)
     return current unless @resource.value(:revision)
 
     if tag_revision?(@resource.value(:revision))
-      canonical = at_path { git_with_identity('show', @resource.value(:revision)).scan(/commit (.*)/).to_s }
+      canonical = at_path { git_with_identity('show', @resource.value(:revision)).scan(/^commit (.*)/).to_s }
     else
-      canonical = at_path { git_with_identity('rev-parse', @resource.value(:revision)).chomp }
+      # if it's not a tag, look for it as a local ref
+      canonical = at_path { git_with_identity('rev-parse', '--revs-only', @resource.value(:revision)).chomp }
+      if canonical.empty?
+        # git rev-parse executed properly but didn't find the ref;
+        # look for it in the remote
+        remote_ref = at_path { git_with_identity('ls-remote', '--heads', '--tags', @resource.value(:remote), @resource.value(:revision)).chomp }
+        if remote_ref.empty?
+          fail("#{@resource.value(:revision)} is not a local or remote ref")
+        end
+
+        # $ git ls-remote --heads --tags origin feature/cvs 
+        # 7d4244b35e72904e30130cad6d2258f901c16f1a     refs/heads/feature/cvs
+        canonical = remote_ref.split.first
+      end
     end
 
     if current == canonical
@@ -93,8 +106,18 @@ 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.origin.url')
+    unless @resource.value(:source).nil?
+      if current.nil? or current.strip != @resource.value(:source)
+        git_with_identity('config', 'remote.origin.url', @resource.value(:source))
+      end
+    end
+  end
+
   def update_references
     at_path do
+      update_remote_origin_url
       git_with_identity('fetch', @resource.value(:remote))
       git_with_identity('fetch', '--tags', @resource.value(:remote))
       update_owner_and_excludes
@@ -115,7 +138,9 @@ Puppet::Type.type(:vcsrepo).provide(:git, :parent => Puppet::Provider::Vcsrepo)
     end
     if !File.exist?(File.join(@resource.value(:path), '.git'))
       args.push(source, path)
-      git_with_identity(*args)
+      Dir.chdir("/") do
+        git_with_identity(*args)
+      end
     else
       notice "Repo has already been cloned"
     end
@@ -141,6 +166,7 @@ Puppet::Type.type(:vcsrepo).provide(:git, :parent => Puppet::Provider::Vcsrepo)
     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'
@@ -247,6 +273,7 @@ Puppet::Type.type(:vcsrepo).provide(:git, :parent => Puppet::Provider::Vcsrepo)
       create
     end
     at_path do
+      update_remote_origin_url
       git_with_identity('fetch', @resource.value(:remote))
       git_with_identity('fetch', '--tags', @resource.value(:remote))
     end
@@ -276,7 +303,7 @@ Puppet::Type.type(:vcsrepo).provide(:git, :parent => Puppet::Provider::Vcsrepo)
     if @resource.value(:identity)
       Tempfile.open('git-helper') do |f|
         f.puts '#!/bin/sh'
-        f.puts "exec ssh -oStrictHostKeyChecking=no -oPasswordAuthentication=no -oKbdInteractiveAuthentication=no -oChallengeResponseAuthentication=no -i #{@resource.value(:identity)} $*"
+        f.puts "exec ssh -oStrictHostKeyChecking=no -oPasswordAuthentication=no -oKbdInteractiveAuthentication=no -oChallengeResponseAuthentication=no -oConnectTimeout=120 -i #{@resource.value(:identity)} $*"
         f.close
 
         FileUtils.chmod(0755, f.path)
index 67a2a82..4886b7a 100644 (file)
@@ -3,8 +3,9 @@ require File.join(File.dirname(__FILE__), '..', 'vcsrepo')
 Puppet::Type.type(:vcsrepo).provide(:hg, :parent => Puppet::Provider::Vcsrepo) do
   desc "Supports Mercurial repositories"
 
-  optional_commands   :hg => 'hg'
-  has_features :reference_tracking
+  optional_commands :hg => 'hg',
+                    :su => 'su'
+  has_features :reference_tracking, :ssh_identity, :user
 
   def create
     if !@resource.value(:source)
@@ -36,7 +37,7 @@ Puppet::Type.type(:vcsrepo).provide(:hg, :parent => Puppet::Provider::Vcsrepo) d
   def latest
     at_path do
       begin
-        hg('incoming', '--branch', '.', '--newest-first', '--limit', '1')[/^changeset:\s+(?:-?\d+):(\S+)/m, 1]
+        hg_wrapper('incoming', '--branch', '.', '--newest-first', '--limit', '1')[/^changeset:\s+(?:-?\d+):(\S+)/m, 1]
       rescue Puppet::ExecutionFailure
         # If there are no new changesets, return the current nodeid
         self.revision
@@ -46,11 +47,11 @@ Puppet::Type.type(:vcsrepo).provide(:hg, :parent => Puppet::Provider::Vcsrepo) d
 
   def revision
     at_path do
-      current = hg('parents')[/^changeset:\s+(?:-?\d+):(\S+)/m, 1]
+      current = hg_wrapper('parents')[/^changeset:\s+(?:-?\d+):(\S+)/m, 1]
       desired = @resource.value(:revision)
       if desired
         # Return the tag name if it maps to the current nodeid
-        mapped = hg('tags')[/^#{Regexp.quote(desired)}\s+\d+:(\S+)/m, 1]
+        mapped = hg_wrapper('tags')[/^#{Regexp.quote(desired)}\s+\d+:(\S+)/m, 1]
         if current == mapped
           desired
         else
@@ -65,15 +66,15 @@ Puppet::Type.type(:vcsrepo).provide(:hg, :parent => Puppet::Provider::Vcsrepo) d
   def revision=(desired)
     at_path do
       begin
-        hg('pull')
+        hg_wrapper('pull')
       rescue
       end
       begin
-        hg('merge')
+        hg_wrapper('merge')
       rescue Puppet::ExecutionFailure
         # If there's nothing to merge, just skip
       end
-      hg('update', '--clean', '-r', desired)
+      hg_wrapper('update', '--clean', '-r', desired)
     end
     update_owner
   end
@@ -81,7 +82,7 @@ Puppet::Type.type(:vcsrepo).provide(:hg, :parent => Puppet::Provider::Vcsrepo) d
   private
 
   def create_repository(path)
-    hg('init', path)
+    hg_wrapper('init', path)
   end
 
   def clone_repository(revision)
@@ -91,7 +92,7 @@ Puppet::Type.type(:vcsrepo).provide(:hg, :parent => Puppet::Provider::Vcsrepo) d
     end
     args.push(@resource.value(:source),
               @resource.value(:path))
-    hg(*args)
+    hg_wrapper(*args)
   end
 
   def update_owner
@@ -100,4 +101,15 @@ Puppet::Type.type(:vcsrepo).provide(:hg, :parent => Puppet::Provider::Vcsrepo) d
     end
   end
 
+  def hg_wrapper(*args)
+    if @resource.value(:identity)
+      args += ["--ssh", "ssh -oStrictHostKeyChecking=no -oPasswordAuthentication=no -oKbdInteractiveAuthentication=no -oChallengeResponseAuthentication=no -i #{@resource.value(:identity)}"]
+    end
+    if @resource.value(:user)
+      args.map! { |a| if a =~ /\s/ then "'#{a}'" else a end }  # Adds quotes to arguments with whitespaces.
+      su(@resource.value(:user), '-c', "hg #{args.join(' ')}")
+    else
+      hg(*args)
+    end
+  end
 end
index b2a8880..2dc0fd1 100644 (file)
@@ -4,7 +4,7 @@ Puppet::Type.type(:vcsrepo).provide(:svn, :parent => Puppet::Provider::Vcsrepo)
   desc "Supports Subversion repositories"
 
   optional_commands :svn      => 'svn',
-           :svnadmin => 'svnadmin'
+                    :svnadmin => 'svnadmin'
 
   has_features :filesystem_types, :reference_tracking, :basic_auth
 
@@ -48,6 +48,11 @@ Puppet::Type.type(:vcsrepo).provide(:svn, :parent => Puppet::Provider::Vcsrepo)
       args.push('--password', @resource.value(:basic_auth_password))
       args.push('--no-auth-cache')
     end
+
+    if @resource.value(:force)
+      args.push('--force')
+    end
+
     return args
   end
 
index 4b53235..45ac455 100644 (file)
@@ -38,16 +38,16 @@ Puppet::Type.newtype(:vcsrepo) do
       @should ||= []
 
       case should
-        when :present
-          return true unless [:absent, :purged, :held].include?(is)
-        when :latest
-          if is == :latest
-            return true
-          else
-            return false
-          end
-               when :bare
-                 return is == :bare
+      when :present
+        return true unless [:absent, :purged, :held].include?(is)
+      when :latest
+        if is == :latest
+          return true
+        else
+          return false
+        end
+      when :bare
+        return is == :bare
       end
     end
 
@@ -57,7 +57,7 @@ Puppet::Type.newtype(:vcsrepo) do
     end
 
     newvalue :bare, :required_features => [:bare_repositories] do
-         if !provider.exists?
+      if !provider.exists?
         provider.create
       end
     end
index e1804de..c0231e9 100644 (file)
@@ -81,9 +81,9 @@ describe_provider :vcsrepo, :bzr, :resource => {:path => '/tmp/vcsrepo'} do
 
   describe "setting the revision property" do
     it "should use 'bzr update -r' with the revision" do
-      revision = 'somerev'
-      provider.expects(:bzr).with('update', '-r', revision, resource.value(:path))
-      provider.revision = revision
+      expects_chdir
+      provider.expects(:bzr).with('update', '-r', 'somerev')
+      provider.revision = 'somerev'
     end
   end
 
index c878941..68b6c0a 100644 (file)
@@ -8,6 +8,7 @@ describe_provider :vcsrepo, :git, :resource => {:path => '/tmp/vcsrepo'} do
         context "with a revision that is a remote branch", :resource => {:revision => 'only/remote'} do
           it "should execute 'git clone' and 'git checkout -b'" do
             provider.expects(:git).with('clone', resource.value(:source), resource.value(:path))
+            expects_chdir('/')
             expects_chdir
             provider.expects(:update_submodules)
             provider.expects(:git).with('branch', '-a').returns(resource.value(:revision))
@@ -18,6 +19,7 @@ describe_provider :vcsrepo, :git, :resource => {:path => '/tmp/vcsrepo'} do
         context "with a revision that is not a remote branch", :resource => {:revision => 'a-commit-or-tag'} do
           it "should execute 'git clone' and 'git reset --hard'" do
             provider.expects(:git).with('clone', resource.value(:source), resource.value(:path))
+            expects_chdir('/')
             expects_chdir
             provider.expects(:update_submodules)
             provider.expects(:git).with('branch', '-a').returns(resource.value(:revision))
@@ -127,9 +129,10 @@ describe_provider :vcsrepo, :git, :resource => {:path => '/tmp/vcsrepo'} do
 
       context "when its SHA is not different than the current SHA" do
         it "should return the ref" do
+          provider.expects(:git).with('config', 'remote.origin.url').returns('')
           provider.expects(:git).with('fetch', 'origin') # FIXME
           provider.expects(:git).with('fetch', '--tags', 'origin')
-          provider.expects(:git).with('rev-parse', resource.value(:revision)).returns('currentsha')
+          provider.expects(:git).with('rev-parse', '--revs-only', resource.value(:revision)).returns('currentsha')
           provider.expects(:git).with('tag', '-l').returns("Hello")
           provider.revision.should == resource.value(:revision)
         end
@@ -137,13 +140,52 @@ describe_provider :vcsrepo, :git, :resource => {:path => '/tmp/vcsrepo'} do
 
       context "when its SHA is different than the current SHA" do
         it "should return the current SHA" do
+          provider.expects(:git).with('config', 'remote.origin.url').returns('')
           provider.expects(:git).with('fetch', 'origin') # FIXME
           provider.expects(:git).with('fetch', '--tags', 'origin')
-          provider.expects(:git).with('rev-parse', resource.value(:revision)).returns('othersha')
+          provider.expects(:git).with('rev-parse', '--revs-only', resource.value(:revision)).returns('othersha')
           provider.expects(:git).with('tag', '-l').returns("Hello")
           provider.revision.should == 'currentsha'
         end
       end
+
+      context "when its a ref to a remote head" do
+        it "should return the revision" do
+          provider.expects(:git).with('config', 'remote.origin.url').returns('')
+          provider.expects(:git).with('fetch', 'origin') # FIXME
+          provider.expects(:git).with('fetch', '--tags', 'origin')
+          provider.expects(:git).with('tag', '-l').returns("Hello")
+          provider.expects(:git).with('rev-parse', '--revs-only', resource.value(:revision)).returns('')
+          provider.expects(:git).with('ls-remote', '--heads', '--tags', 'origin', resource.value(:revision)).returns("newsha refs/heads/#{resource.value(:revision)}")
+          provider.revision.should == 'currentsha'
+        end
+      end
+
+      context "when its a ref to non existant remote head" do
+        it "should fail" do
+          provider.expects(:git).with('config', 'remote.origin.url').returns('')
+          provider.expects(:git).with('fetch', 'origin') # FIXME
+          provider.expects(:git).with('fetch', '--tags', 'origin')
+          provider.expects(:git).with('tag', '-l').returns("Hello")
+          provider.expects(:git).with('rev-parse', '--revs-only', resource.value(:revision)).returns('')
+          provider.expects(:git).with('ls-remote', '--heads', '--tags', 'origin', resource.value(:revision)).returns('')
+          expect { provider.revision }.should raise_error(Puppet::Error, /not a local or remote ref$/)
+        end
+      end
+
+      context "when the source is modified" do
+        resource_with :source => 'git://git@foo.com/bar.git' do
+          it "should update the origin url" do
+            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('fetch', 'origin') # FIXME
+            provider.expects(:git).with('fetch', '--tags', 'origin')
+            provider.expects(:git).with('rev-parse', '--revs-only', resource.value(:revision)).returns('currentsha')
+            provider.expects(:git).with('tag', '-l').returns("Hello")
+            provider.revision.should == resource.value(:revision)
+          end
+        end
+      end
     end
   end
 
@@ -186,6 +228,7 @@ describe_provider :vcsrepo, :git, :resource => {:path => '/tmp/vcsrepo'} do
   context "updating references" do
     it "should use 'git fetch --tags'" do
       expects_chdir
+      provider.expects(:git).with('config', 'remote.origin.url').returns('')
       provider.expects(:git).with('fetch', 'origin')
       provider.expects(:git).with('fetch', '--tags', 'origin')
       provider.update_references