Basic Perforce provider
authorPaul Allen <pallen@perforce.com>
Wed, 11 Jun 2014 21:32:21 +0000 (22:32 +0100)
committerPaul Allen <pallen@perforce.com>
Fri, 20 Jun 2014 11:16:29 +0000 (12:16 +0100)
Supports sync and client create/update

lib/puppet/provider/vcsrepo/p4.rb [new file with mode: 0644]
lib/puppet/type/vcsrepo.rb

diff --git a/lib/puppet/provider/vcsrepo/p4.rb b/lib/puppet/provider/vcsrepo/p4.rb
new file mode 100644 (file)
index 0000000..2fdae70
--- /dev/null
@@ -0,0 +1,217 @@
+require File.join(File.dirname(__FILE__), '..', 'vcsrepo')
+
+Puppet::Type.type(:vcsrepo).provide(:p4, :parent => Puppet::Provider::Vcsrepo) do
+       desc "Supports Perforce depots"
+
+       has_features :filesystem_types, :reference_tracking, :p4_config
+       
+       def create
+               # create or update client 
+               create_client(client_name, @resource.value(:path))
+               
+               # if source provided, sync client
+               source = @resource.value(:source)
+               if source
+                       revision = @resource.value(:revision)
+                       sync_client(source, revision)
+               end
+
+               update_owner
+       end
+
+       def working_copy_exists?
+               # Check if the server is there, or raise error
+               p4(['info'], {:marshal => false})
+       
+               # Check if workspace is setup
+               args = ['where']
+               args.push(@resource.value(:path) + "...")
+               hash = p4(args, {:raise => false})
+               
+               return (hash['code'] != "error")
+       end
+
+       def exists?
+               working_copy_exists?
+       end
+
+       def destroy
+               args = ['client']
+               args.push('-d', '-f')
+               args.push(client_name)
+               p4(args)
+               FileUtils.rm_rf(@resource.value(:path))
+       end
+
+       def latest?
+               rev = self.revision
+               if rev
+                       (rev >= self.latest)
+               else
+                       true
+               end             
+       end
+
+       def latest
+               args = ['changes']
+               args.push('-m1', @resource.value(:source))
+               hash = p4(args)
+               
+               return hash['change'].to_i
+       end
+
+       def revision
+               args = ['cstat']
+               args.push(@resource.value(:source))
+               hash = p4(args)
+               
+               if hash['status'] == "have"
+                       return hash['change'].to_i
+               else
+                       return 0
+               end
+       end
+
+       def revision=(desired)
+               sync_client(@resource.value(:source), desired)
+               update_owner
+       end
+
+       private
+
+       def update_owner
+               if @resource.value(:owner) or @resource.value(:group)
+                       set_ownership
+               end
+       end
+       
+       # Sync the client workspace files to head or specified revision.
+       # Params:
+  # +source+:: Depot path to sync
+  # +revision+:: Perforce change list to sync to (optional)
+       def sync_client(source, revision)
+               notice "Syncing: #{source}"
+               args = ['sync']
+               if revision
+                       args.push(source + "@" + revision)
+               else
+                       args.push(source)
+               end
+               p4(args)
+       end
+       
+       # Returns the name of the Perforce client workspace 
+       def client_name
+               path = @resource.value(:path)
+               client = @resource.value(:p4client)
+               if not client
+                       client = "puppet-" + Digest::MD5.hexdigest(path)
+               end
+               return client
+       end
+       
+       # Create (or update) a client workspace spec.
+       # If a client name is not provided then a hash based on the path is used.
+       # Params:
+       # +client+:: Name of client workspace
+  # +path+:: The Root location of the Perforce client workspace
+       def create_client(client, path)
+               notice "Creating client: #{client}"
+               hash = parse_client(client)
+               hash['Root'] = path
+               hash['Description'] = "Generated by Puppet VCSrepo"
+               save_client(hash)
+       end
+
+
+       # Fetches a client workspace spec from Perforce and returns a hash map representation.
+  # Params:
+  # +client+:: name of the client workspace
+       def parse_client(client)
+               args = ['client']
+               args.push('-o', client)
+               hash = p4(args)
+
+               return hash
+       end
+       
+       
+       # Saves the client workspace spec from the given hash 
+  # Params:
+  # +hash+:: hash map of client spec
+       def save_client(hash)
+               spec = String.new
+               view = "\nView:\n"
+       
+               hash.each do |k,v|
+                       next if( k == "code" )
+                       if(k.to_s =~ /View/ )
+                               view += "\t#{v}\n"
+                       else
+                               spec += "#{k.to_s}: #{v.to_s}\n"
+                       end
+               end     
+               spec += view
+               
+               args = ['client']
+               args.push('-i')
+               p4(args, {:input => spec, :marshal => false})
+       end     
+       
+       def config
+               p4port = @resource.value(:p4port)
+               p4user = @resource.value(:p4user)
+               p4charset = @resource.value(:p4charset)
+               p4client = @resource.value(:p4client) || client_name
+       
+               cfg = Hash.new  
+               cfg.store 'P4USER', p4user if p4user
+               cfg.store 'P4PORT', p4port if p4port
+               cfg.store 'P4CHARSET', p4charset if p4charset
+               cfg.store 'P4CLIENT', p4client if p4client
+               
+               return cfg      
+       end
+       
+       def p4(args, options = {})
+               # Merge custom options with defaults
+               opts = { 
+                       :raise          => true,                # Raise errors
+                       :marshal        => true,                # Marshal output
+               }.merge(options)
+               
+               cmd = ['p4']
+               cmd.push '-R' if opts[:marshal]
+               cmd.push args
+               cmd_str = cmd.respond_to?(:join) ? cmd.join(' ') : cmd
+               
+               notice "environment: #{config}"
+               notice "command: #{cmd_str}"
+               
+               hash = Hash.new
+               Open3.popen3(config, cmd_str) do |i, o, e, t|
+                       # Send input stream if provided
+                       if(opts[:input])
+                               notice "input:\n" + opts[:input]
+                               i.write opts[:input]
+                               i.close
+                       end
+                       
+                       if(opts[:marshal])
+                               hash = Marshal.load(o)
+                       else
+                               hash['data'] = o.read                           
+                       end
+                       
+                       # Raise errors, Perforce or Exec
+                       if(opts[:raise])
+                               p4_err = "P4: " + hash['data'] if(hash['code'] == 'error') 
+                               raise Puppet::DevError, "#{p4_err}\n#{e.read}\nExit: #{t.value}" if(t.value != 0)
+                       end
+               end
+               
+               notice "hash: #{hash}\n"
+               return hash
+       end
+       
+end
index 0e4450b..ad964c5 100644 (file)
@@ -40,6 +40,9 @@ Puppet::Type.newtype(:vcsrepo) do
   feature :depth,
           "The provider can do shallow clones"
 
+  feature :p4_config,
+          "The provider understands Perforce Configuration"
+
   ensurable do
     attr_accessor :latest
 
@@ -209,6 +212,22 @@ Puppet::Type.newtype(:vcsrepo) do
     desc "The value to be used to do a shallow clone."
   end
 
+  newparam :p4port, :required_features => [:p4_config] do
+    desc "The Perforce P4PORT environment."
+  end
+  
+  newparam :p4user, :required_features => [:p4_config] do
+    desc "The Perforce P4USER environment."
+  end
+  
+  newparam :p4client, :required_features => [:p4_config] do
+    desc "The Perforce P4CLIENT environment."
+  end
+
+  newparam :p4charset, :required_features => [:p4_config] do
+    desc "The Perforce P4CHARSET environment."
+  end
+    
   autorequire(:package) do
     ['git', 'git-core']
   end