summaryrefslogtreecommitdiffstats
path: root/lib/puppet/provider
diff options
context:
space:
mode:
authorJames Turnbull <james@lovedthanlost.net>2011-05-23 06:19:28 +1000
committerJames Turnbull <james@lovedthanlost.net>2011-05-23 17:50:56 +1000
commit8f0cecfb46aface34d5189e60e924c9bfa7f4b13 (patch)
tree6487481913e1dbd135ee40a8841ee924bbed5e93 /lib/puppet/provider
parentb87a1dea704ed981f2f0af728afac2c63e87b5a8 (diff)
downloadpuppet-8f0cecfb46aface34d5189e60e924c9bfa7f4b13.tar.gz
puppet-8f0cecfb46aface34d5189e60e924c9bfa7f4b13.tar.xz
puppet-8f0cecfb46aface34d5189e60e924c9bfa7f4b13.zip
Added the vcsrepo type and providers to the core
Diffstat (limited to 'lib/puppet/provider')
-rw-r--r--lib/puppet/provider/vcsrepo.rb34
-rw-r--r--lib/puppet/provider/vcsrepo/bzr.rb64
-rw-r--r--lib/puppet/provider/vcsrepo/cvs.rb80
-rw-r--r--lib/puppet/provider/vcsrepo/git.rb264
-rw-r--r--lib/puppet/provider/vcsrepo/hg.rb101
-rw-r--r--lib/puppet/provider/vcsrepo/svn.rb82
6 files changed, 625 insertions, 0 deletions
diff --git a/lib/puppet/provider/vcsrepo.rb b/lib/puppet/provider/vcsrepo.rb
new file mode 100644
index 000000000..2c026ba86
--- /dev/null
+++ b/lib/puppet/provider/vcsrepo.rb
@@ -0,0 +1,34 @@
+require 'tmpdir'
+require 'digest/md5'
+require 'fileutils'
+
+# Abstract
+class Puppet::Provider::Vcsrepo < Puppet::Provider
+
+ private
+
+ def set_ownership
+ owner = @resource.value(:owner) || nil
+ group = @resource.value(:group) || nil
+ FileUtils.chown_R(owner, group, @resource.value(:path))
+ end
+
+ def path_exists?
+ File.directory?(@resource.value(:path))
+ end
+
+ # Note: We don't rely on Dir.chdir's behavior of automatically returning the
+ # value of the last statement -- for easier stubbing.
+ def at_path(&block) #:nodoc:
+ value = nil
+ Dir.chdir(@resource.value(:path)) do
+ value = yield
+ end
+ value
+ end
+
+ def tempdir
+ @tempdir ||= File.join(Dir.tmpdir, 'vcsrepo-' + Digest::MD5.hexdigest(@resource.value(:path)))
+ end
+
+end
diff --git a/lib/puppet/provider/vcsrepo/bzr.rb b/lib/puppet/provider/vcsrepo/bzr.rb
new file mode 100644
index 000000000..a0605624b
--- /dev/null
+++ b/lib/puppet/provider/vcsrepo/bzr.rb
@@ -0,0 +1,64 @@
+require File.join(File.dirname(__FILE__), '..', 'vcsrepo')
+
+Puppet::Type.type(:vcsrepo).provide(:bzr, :parent => Puppet::Provider::Vcsrepo) do
+ desc "Supports Bazaar repositories"
+
+ commands :bzr => 'bzr'
+ defaultfor :bzr => :exists
+ has_features :reference_tracking
+
+ def create
+ if !@resource.value(:source)
+ create_repository(@resource.value(:path))
+ else
+ clone_repository(@resource.value(:revision))
+ end
+ end
+
+ def exists?
+ File.directory?(File.join(@resource.value(:path), '.bzr'))
+ end
+
+ def destroy
+ FileUtils.rm_rf(@resource.value(:path))
+ end
+
+ def revision
+ at_path do
+ current_revid = bzr('version-info')[/^revision-id:\s+(\S+)/, 1]
+ desired = @resource.value(:revision)
+ begin
+ desired_revid = bzr('revision-info', desired).strip.split(/\s+/).last
+ rescue Puppet::ExecutionFailure
+ # Possible revid available during update (but definitely not current)
+ desired_revid = nil
+ end
+ if current_revid == desired_revid
+ desired
+ else
+ current_revid
+ end
+ end
+ end
+
+ def revision=(desired)
+ bzr('update', '-r', desired, @resource.value(:path))
+ end
+
+ private
+
+ def create_repository(path)
+ bzr('init', path)
+ end
+
+ def clone_repository(revision)
+ args = ['branch']
+ if revision
+ args.push('-r', revision)
+ end
+ args.push(@resource.value(:source),
+ @resource.value(:path))
+ bzr(*args)
+ end
+
+end
diff --git a/lib/puppet/provider/vcsrepo/cvs.rb b/lib/puppet/provider/vcsrepo/cvs.rb
new file mode 100644
index 000000000..e82c23afe
--- /dev/null
+++ b/lib/puppet/provider/vcsrepo/cvs.rb
@@ -0,0 +1,80 @@
+require File.join(File.dirname(__FILE__), '..', 'vcsrepo')
+
+Puppet::Type.type(:vcsrepo).provide(:cvs, :parent => Puppet::Provider::Vcsrepo) do
+ desc "Supports CVS repositories/workspaces"
+
+ commands :cvs => 'cvs'
+ defaultfor :cvs => :exists
+ has_features :gzip_compression, :reference_tracking
+
+ def create
+ if !@resource.value(:source)
+ create_repository(@resource.value(:path))
+ else
+ checkout_repository
+ end
+ end
+
+ def exists?
+ if @resource.value(:source)
+ directory = File.join(@resource.value(:path), 'CVS')
+ else
+ directory = File.join(@resource.value(:path), 'CVSROOT')
+ end
+ File.directory?(directory)
+ end
+
+ def destroy
+ FileUtils.rm_rf(@resource.value(:path))
+ end
+
+ def revision
+ if File.exist?(tag_file)
+ contents = File.read(tag_file)
+ # Note: Doesn't differentiate between N and T entries
+ contents[1..-1]
+ else
+ 'MAIN'
+ end
+ end
+
+ def revision=(desired)
+ at_path do
+ cvs('update', '-r', desired, '.')
+ end
+ end
+
+ private
+
+ def tag_file
+ File.join(@resource.value(:path), 'CVS', 'Tag')
+ end
+
+ def checkout_repository
+ dirname, basename = File.split(@resource.value(:path))
+ Dir.chdir(dirname) do
+ args = ['-d', @resource.value(:source)]
+ if @resource.value(:compression)
+ args.push('-z', @resource.value(:compression))
+ end
+ args.push('checkout', '-d', basename, module_name)
+ cvs(*args)
+ end
+ if @resource.value(:revision)
+ self.revision = @resource.value(:revision)
+ end
+ end
+
+ # When the source:
+ # * Starts with ':' (eg, :pserver:...)
+ def module_name
+ if (source = @resource.value(:source))
+ source[0, 1] == ':' ? File.basename(source) : '.'
+ end
+ end
+
+ def create_repository(path)
+ cvs('-d', path, 'init')
+ end
+
+end
diff --git a/lib/puppet/provider/vcsrepo/git.rb b/lib/puppet/provider/vcsrepo/git.rb
new file mode 100644
index 000000000..fa7e492cf
--- /dev/null
+++ b/lib/puppet/provider/vcsrepo/git.rb
@@ -0,0 +1,264 @@
+require File.join(File.dirname(__FILE__), '..', 'vcsrepo')
+
+Puppet::Type.type(:vcsrepo).provide(:git, :parent => Puppet::Provider::Vcsrepo) do
+ desc "Supports Git repositories"
+
+ ##TODO modify the commands below so that the su - is included
+ commands :git => 'git'
+ defaultfor :git => :exists
+ has_features :bare_repositories, :reference_tracking
+
+ def create
+ if !@resource.value(:source)
+ init_repository(@resource.value(:path))
+ else
+ clone_repository(@resource.value(:source), @resource.value(:path))
+ if @resource.value(:revision)
+ if @resource.value(:ensure) == :bare
+ notice "Ignoring revision for bare repository"
+ else
+ checkout_or_reset
+ end
+ end
+ if @resource.value(:ensure) != :bare
+ update_submodules
+ end
+ end
+ update_owner_and_excludes
+ end
+
+ def destroy
+ FileUtils.rm_rf(@resource.value(:path))
+ end
+
+ def latest?
+ at_path do
+ return self.revision == self.latest
+ end
+ end
+
+ def latest
+ branch = on_branch?
+ if branch == 'master'
+ return get_revision('origin/HEAD')
+ else
+ return get_revision('origin/%s' % branch)
+ end
+ end
+
+ def revision
+ update_references
+ current = at_path { git('rev-parse', 'HEAD') }
+ canonical = at_path { git('rev-parse', @resource.value(:revision)) }
+ if current == canonical
+ @resource.value(:revision)
+ else
+ current
+ end
+ end
+
+ def revision=(desired)
+ checkout_or_reset(desired)
+ if local_branch_revision?(desired)
+ # reset instead of pull to avoid merge conflicts. assuming remote is
+ # authoritative.
+ # might be worthwhile to have an allow_local_changes param to decide
+ # whether to reset or pull when we're ensuring latest.
+ at_path { git('reset', '--hard', "origin/#{desired}") }
+ end
+ if @resource.value(:ensure) != :bare
+ update_submodules
+ end
+ update_owner_and_excludes
+ end
+
+ def bare_exists?
+ bare_git_config_exists? && !working_copy_exists?
+ end
+
+ def working_copy_exists?
+ File.directory?(File.join(@resource.value(:path), '.git'))
+ end
+
+ def exists?
+ working_copy_exists? || bare_exists?
+ end
+
+ def update_references
+ at_path do
+ git('fetch', '--tags', 'origin')
+ end
+ end
+
+ private
+
+ def bare_git_config_exists?
+ File.exist?(File.join(@resource.value(:path), 'config'))
+ end
+
+ def clone_repository(source, path)
+ check_force
+ args = ['clone']
+ if @resource.value(:ensure) == :bare
+ args << '--bare'
+ end
+ if !File.exist?(File.join(@resource.value(:path), '.git'))
+ args.push(source, path)
+ git(*args)
+ else
+ notice "Repo has already been cloned"
+ end
+ end
+
+ def check_force
+ if path_exists?
+ if @resource.value(:force)
+ notice "Removing %s to replace with vcsrepo." % @resource.value(:path)
+ destroy
+ else
+ raise Puppet::Error, "Could not create repository (non-repository at path)"
+ end
+ end
+ end
+
+ def init_repository(path)
+ check_force
+ 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))
+ args = ['init']
+ if @resource.value(:ensure) == :bare
+ args << '--bare'
+ end
+ at_path do
+ git(*args)
+ end
+ end
+ end
+
+ # Convert working copy to bare
+ #
+ # Moves:
+ # <path>/.git
+ # to:
+ # <path>/
+ def convert_working_copy_to_bare
+ 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))
+ end
+
+ # Convert bare to working copy
+ #
+ # Moves:
+ # <path>/
+ # to:
+ # <path>/.git
+ 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_in?(File.join(@resource.value(:path), '.git'))
+ reset('HEAD')
+ git('checkout', '-f')
+ update_owner_and_excludes
+ end
+ end
+
+ def commits_in?(dot_git)
+ Dir.glob(File.join(dot_git, 'objects/info/*'), File::FNM_DOTMATCH) do |e|
+ return true unless %w(. ..).include?(File::basename(e))
+ end
+ false
+ end
+
+ def checkout_or_reset(revision = @resource.value(:revision))
+ if local_branch_revision?
+ reset(revision)
+ elsif tag_revision?
+ at_path { git('checkout', '-b', revision) }
+ elsif remote_branch_revision?
+ at_path { git('checkout', '-b', revision, '--track', "origin/#{revision}") }
+ end
+ end
+
+ def reset(desired)
+ at_path do
+ git('reset', '--hard', desired)
+ end
+ end
+
+ def update_submodules
+ at_path do
+ git('submodule', 'init')
+ git('submodule', 'update')
+ git('submodule', 'foreach', 'git', 'submodule', 'init')
+ git('submodule', 'foreach', 'git', 'submodule', 'update')
+ end
+ end
+
+ def remote_branch_revision?(revision = @resource.value(:revision))
+ # git < 1.6 returns 'origin/#{revision}'
+ # git 1.6+ returns 'remotes/origin/#{revision}'
+ at_path { branches.grep /(remotes\/)?origin\/#{revision}/ }
+ end
+
+ def local_branch_revision?(revision = @resource.value(:revision))
+ at_path { branches.include?(revision) }
+ end
+
+ def tag_revision?(revision = @resource.value(:revision))
+ at_path { tags.include?(revision) }
+ end
+
+ def branches
+ at_path { git('branch', '-a') }.gsub('*', ' ').split(/\n/).map { |line| line.strip }
+ end
+
+ def on_branch?
+ at_path { git('branch', '-a') }.split(/\n/).grep(/\*/).to_s.gsub('*', '').strip
+ end
+
+ def tags
+ at_path { git('tag', '-l') }.split(/\n/).map { |line| line.strip }
+ end
+
+ def set_excludes
+ at_path { open('.git/info/exclude', 'w') { |f| @resource.value(:excludes).each { |ex| f.write(ex + "\n") }}}
+ end
+
+ def get_revision(rev)
+ if !working_copy_exists?
+ create
+ end
+ at_path do
+ git('fetch', 'origin')
+ git('fetch', '--tags', 'origin')
+ end
+ current = at_path { git('rev-parse', rev).strip }
+ if @resource.value(:revision)
+ if local_branch_revision?
+ canonical = at_path { git('rev-parse', @resource.value(:revision)).strip }
+ elsif remote_branch_revision?
+ canonical = at_path { git('rev-parse', 'origin/' + @resource.value(:revision)).strip }
+ end
+ current = @resource.value(:revision) if current == canonical
+ end
+ return current
+ end
+
+ def update_owner_and_excludes
+ if @resource.value(:owner) or @resource.value(:group)
+ set_ownership
+ end
+ if @resource.value(:excludes)
+ set_excludes
+ end
+ end
+end
diff --git a/lib/puppet/provider/vcsrepo/hg.rb b/lib/puppet/provider/vcsrepo/hg.rb
new file mode 100644
index 000000000..f96758612
--- /dev/null
+++ b/lib/puppet/provider/vcsrepo/hg.rb
@@ -0,0 +1,101 @@
+require File.join(File.dirname(__FILE__), '..', 'vcsrepo')
+
+Puppet::Type.type(:vcsrepo).provide(:hg, :parent => Puppet::Provider::Vcsrepo) do
+ desc "Supports Mercurial repositories"
+
+ commands :hg => 'hg'
+ defaultfor :hg => :exists
+ has_features :reference_tracking
+
+ def create
+ if !@resource.value(:source)
+ create_repository(@resource.value(:path))
+ else
+ clone_repository(@resource.value(:revision))
+ end
+ update_owner
+ end
+
+ def working_copy_exists?
+ File.directory?(File.join(@resource.value(:path), '.hg'))
+ end
+
+ def exists?
+ working_copy_exists?
+ end
+
+ def destroy
+ FileUtils.rm_rf(@resource.value(:path))
+ end
+
+ def latest?
+ at_path do
+ return self.revision == self.latest
+ end
+ end
+
+ def latest
+ at_path do
+ begin
+ hg('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
+ end
+ end
+ end
+
+ def revision
+ at_path do
+ current = hg('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]
+ if current == mapped
+ desired
+ else
+ current
+ end
+ else
+ current
+ end
+ end
+ end
+
+ def revision=(desired)
+ at_path do
+ hg('pull')
+ begin
+ hg('merge')
+ rescue Puppet::ExecutionFailure
+ # If there's nothing to merge, just skip
+ end
+ hg('update', '--clean', '-r', desired)
+ end
+ update_owner
+ end
+
+ private
+
+ def create_repository(path)
+ hg('init', path)
+ end
+
+ def clone_repository(revision)
+ args = ['clone']
+ if revision
+ args.push('-u', revision)
+ end
+ args.push(@resource.value(:source),
+ @resource.value(:path))
+ hg(*args)
+ end
+
+ def update_owner
+ if @resource.value(:owner) or @resource.value(:group)
+ set_ownership
+ end
+ end
+
+end
diff --git a/lib/puppet/provider/vcsrepo/svn.rb b/lib/puppet/provider/vcsrepo/svn.rb
new file mode 100644
index 000000000..680188c1e
--- /dev/null
+++ b/lib/puppet/provider/vcsrepo/svn.rb
@@ -0,0 +1,82 @@
+require File.join(File.dirname(__FILE__), '..', 'vcsrepo')
+
+Puppet::Type.type(:vcsrepo).provide(:svn, :parent => Puppet::Provider::Vcsrepo) do
+ desc "Supports Subversion repositories"
+
+ commands :svn => 'svn',
+ :svnadmin => 'svnadmin'
+
+ defaultfor :svn => :exists
+ has_features :filesystem_types, :reference_tracking
+
+ def create
+ if !@resource.value(:source)
+ create_repository(@resource.value(:path))
+ else
+ checkout_repository(@resource.value(:source),
+ @resource.value(:path),
+ @resource.value(:revision))
+ end
+ end
+
+ def working_copy_exists?
+ File.directory?(File.join(@resource.value(:path), '.svn'))
+ end
+
+ def exists?
+ working_copy_exists?
+ end
+
+ def destroy
+ FileUtils.rm_rf(@resource.value(:path))
+ end
+
+ def latest?
+ at_path do
+ if self.revision < self.latest then
+ return false
+ else
+ return true
+ end
+ end
+ end
+
+ def latest
+ at_path do
+ svn('info', '-r', 'HEAD')[/^Revision:\s+(\d+)/m, 1]
+ end
+ end
+
+ def revision
+ at_path do
+ svn('info')[/^Revision:\s+(\d+)/m, 1]
+ end
+ end
+
+ def revision=(desired)
+ at_path do
+ svn('update', '-r', desired)
+ end
+ end
+
+ private
+
+ def checkout_repository(source, path, revision = nil)
+ args = ['checkout']
+ if revision
+ args.push('-r', revision)
+ end
+ args.push(source, path)
+ svn(*args)
+ end
+
+ def create_repository(path)
+ args = ['create']
+ if @resource.value(:fstype)
+ args.push('--fs-type', @resource.value(:fstype))
+ end
+ args << path
+ svnadmin(*args)
+ end
+
+end