1 require File.join(File.dirname(__FILE__), '..', 'vcsrepo')
3 Puppet::Type.type(:vcsrepo).provide(:p4, :parent => Puppet::Provider::Vcsrepo) do
4 desc "Supports Perforce depots"
6 has_features :filesystem_types, :reference_tracking, :p4config
9 # create or update client
10 create_client(client_name)
12 # if source provided, sync client
13 source = @resource.value(:source)
15 revision = @resource.value(:revision)
16 sync_client(source, revision)
22 def working_copy_exists?
23 # Check if the server is there, or raise error
24 p4(['info'], {:marshal => false})
26 # Check if workspace is setup
28 args.push(@resource.value(:path) + "...")
29 hash = p4(args, {:raise => false})
31 return (hash['code'] != "error")
41 args.push(client_name)
43 FileUtils.rm_rf(@resource.value(:path))
57 args.push('-m1', @resource.value(:source))
60 return hash['change'].to_i
65 args.push(@resource.value(:source))
66 hash = p4(args, {:marshal => false})
67 hash = marshal_cstat(hash)
70 if hash && hash['code'] != 'error'
71 hash['data'].each do |c|
72 if c['status'] == 'have'
73 change = c['change'].to_i
74 revision = change if change > revision
81 def revision=(desired)
82 sync_client(@resource.value(:source), desired)
89 if @resource.value(:owner) or @resource.value(:group)
94 # Sync the client workspace files to head or specified revision.
96 # +source+:: Depot path to sync
97 # +revision+:: Perforce change list to sync to (optional)
98 def sync_client(source, revision)
99 Puppet.debug "Syncing: #{source}"
102 args.push(source + "@#{revision}")
109 # Returns the name of the Perforce client workspace
111 p4config = @resource.value(:p4config)
113 # default (generated) client name
114 path = @resource.value(:path)
115 host = Facter.value('hostname')
116 default = "puppet-" + Digest::MD5.hexdigest(path + host)
118 # check config for client name
120 if p4config && File.file?(p4config)
121 open(p4config) do |f|
122 m = f.grep(/^P4CLIENT=/).pop
123 p = /^P4CLIENT=(.*)$/
124 set_client = p.match(m)[1] if m
128 return set_client || ENV['P4CLIENT'] || default
131 # Create (or update) a client workspace spec.
132 # If a client name is not provided then a hash based on the path is used.
134 # +client+:: Name of client workspace
135 # +path+:: The Root location of the Perforce client workspace
136 def create_client(client)
137 Puppet.debug "Creating client: #{client}"
140 hash = parse_client(client)
141 hash['Root'] = @resource.value(:path)
142 hash['Description'] = "Generated by Puppet VCSrepo"
144 # check is source is a Stream
145 source = @resource.value(:source)
147 parts = source.split(/\//)
148 if parts && parts.length >= 4
149 source = "//" + parts[2] + "/" + parts[3]
150 streams = p4(['streams', source], {:raise => false})
151 if streams['code'] == "stat"
152 hash['Stream'] = streams['Stream']
153 notice "Streams" + streams['Stream'].inspect
163 # Fetches a client workspace spec from Perforce and returns a hash map representation.
165 # +client+:: name of the client workspace
166 def parse_client(client)
168 args.push('-o', client)
175 # Saves the client workspace spec from the given hash
177 # +hash+:: hash map of client spec
178 def save_client(hash)
182 hash.keys.sort.each do |k|
184 next if( k == "code" )
185 if(k.to_s =~ /View/ )
188 spec += "#{k.to_s}: #{v.to_s}\n"
195 p4(args, {:input => spec, :marshal => false})
198 # Sets Perforce Configuration environment.
199 # P4CLIENT generated, but overwitten if defined in config.
201 p4config = @resource.value(:p4config)
204 cfg.store 'P4CONFIG', p4config if p4config
205 cfg.store 'P4CLIENT', client_name
209 def p4(args, options = {})
210 # Merge custom options with defaults
212 :raise => true, # Raise errors
213 :marshal => true, # Marshal output
217 cmd.push '-R' if opts[:marshal]
219 cmd_str = cmd.respond_to?(:join) ? cmd.join(' ') : cmd
221 Puppet.debug "environment: #{config}"
222 Puppet.debug "command: #{cmd_str}"
225 Open3.popen3(config, cmd_str) do |i, o, e, t|
226 # Send input stream if provided
228 Puppet.debug "input:\n" + opts[:input]
234 hash = Marshal.load(o)
236 hash['data'] = o.read
239 # Raise errors, Perforce or Exec
240 if(opts[:raise] && !e.eof && t.value != 0)
241 raise Puppet::Error, "\nP4: #{e.read}"
243 if(opts[:raise] && hash['code'] == 'error' && t.value != 0)
244 raise Puppet::Error, "\nP4: #{hash['data']}"
248 Puppet.debug "hash: #{hash}\n"
252 # helper method as cstat does not Marshal
253 def marshal_cstat(hash)
259 data.each_line do |l|
260 p = /^\.\.\. (.*) (.*)$/
273 hash.store 'code', code
274 hash.store 'data', list