diff options
| author | James Turnbull <james@lovedthanlost.net> | 2011-05-23 06:19:28 +1000 |
|---|---|---|
| committer | James Turnbull <james@lovedthanlost.net> | 2011-05-23 17:50:56 +1000 |
| commit | 8f0cecfb46aface34d5189e60e924c9bfa7f4b13 (patch) | |
| tree | 6487481913e1dbd135ee40a8841ee924bbed5e93 /lib/puppet/provider | |
| parent | b87a1dea704ed981f2f0af728afac2c63e87b5a8 (diff) | |
| download | puppet-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.rb | 34 | ||||
| -rw-r--r-- | lib/puppet/provider/vcsrepo/bzr.rb | 64 | ||||
| -rw-r--r-- | lib/puppet/provider/vcsrepo/cvs.rb | 80 | ||||
| -rw-r--r-- | lib/puppet/provider/vcsrepo/git.rb | 264 | ||||
| -rw-r--r-- | lib/puppet/provider/vcsrepo/hg.rb | 101 | ||||
| -rw-r--r-- | lib/puppet/provider/vcsrepo/svn.rb | 82 |
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 |
