p4_web_api 2014.2.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/p4_web_api +4 -0
- data/lib/p4_web_api.rb +700 -0
- data/lib/p4_web_api/auth.rb +268 -0
- data/lib/p4_web_api/p4_error.rb +36 -0
- data/lib/p4_web_api/p4_util.rb +417 -0
- data/lib/p4_web_api/version.rb +6 -0
- metadata +249 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3a33dc12a8f5e6304cf6408d9aa711b206173a00
|
4
|
+
data.tar.gz: e9a9c2af51c18f19a22f8267cdade878f69b40e8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2864edc78108d670eaabd5e4b0e868a46950c3b2cffcb62c9920b0e09ba8c4827036359869b70ddddb4fbf1b2f4e4cc8cc7f4fa4627c6d7026897378488564f4
|
7
|
+
data.tar.gz: a1dc0543c17c29071686891eec720d25d0987aa4e5a4e9750b698cd57b8c06c474368720b94e30201de7773449a6fb5898bffb5d0b876ebe3ec18a583b88b4cc
|
data/bin/p4_web_api
ADDED
data/lib/p4_web_api.rb
ADDED
@@ -0,0 +1,700 @@
|
|
1
|
+
# Copyright (c) 2014 Perforce Software, Inc. All rights reserved.
|
2
|
+
# vim:ts=2:sw=2:et:si:ai:
|
3
|
+
# p4_web_api.rb
|
4
|
+
#
|
5
|
+
# Setup
|
6
|
+
# -----
|
7
|
+
#
|
8
|
+
# Avast, your webserver should map /p4 to the root of this application. This
|
9
|
+
# is easily done in rails with the following route:
|
10
|
+
#
|
11
|
+
# mount P4WebAPI, at: '/p4'
|
12
|
+
#
|
13
|
+
# You will need a p4_web_api.yml configuration file created as well, to indicate
|
14
|
+
# where security token data is stored, and where the Perforce server lies.
|
15
|
+
|
16
|
+
require 'sinatra/base'
|
17
|
+
require 'sinatra/json'
|
18
|
+
require 'sinatra/config_file'
|
19
|
+
require 'rack/parser'
|
20
|
+
|
21
|
+
require 'p4_web_api/version'
|
22
|
+
require 'p4_web_api/auth'
|
23
|
+
require 'p4_web_api/p4_util'
|
24
|
+
|
25
|
+
# The P4WebAPI is our namespace for the web application.
|
26
|
+
#
|
27
|
+
# See P4WebAPI::App for most of the implemented methods. In general each method
|
28
|
+
# maps to a P4Ruby call.
|
29
|
+
#
|
30
|
+
# The other modules under the P4WebAPI are generally helpers, like the
|
31
|
+
# Authentication middleware, or P4Util conventions around using P4Ruby.
|
32
|
+
module P4WebAPI
|
33
|
+
# This web application is mostly a lightweight way of connecting the tagged
|
34
|
+
# output via P4Ruby to JSON clients can consume relatively simply.
|
35
|
+
class App < Sinatra::Base
|
36
|
+
register Sinatra::ConfigFile
|
37
|
+
|
38
|
+
# Inject Rack::Parser into the middleware stack so that it
|
39
|
+
# automatically parses json bodies in post requests into the params
|
40
|
+
# array.
|
41
|
+
use Rack::Parser, content_types: {
|
42
|
+
'application/json' => proc { |body| ::MultiJson.decode body }
|
43
|
+
}
|
44
|
+
|
45
|
+
use P4WebAPI::AuthMiddleware,
|
46
|
+
unauthenticated_paths: ['/v1/sessions'],
|
47
|
+
settings: settings
|
48
|
+
|
49
|
+
# Without this set, the return content type is always text/html
|
50
|
+
before do
|
51
|
+
content_type 'application/json'
|
52
|
+
end
|
53
|
+
|
54
|
+
configure do
|
55
|
+
set(:p4, 'host' => 'localhost', 'port' => '1666')
|
56
|
+
set(:token_path, '/tmp/p4_web_api/tokens')
|
57
|
+
|
58
|
+
set(:run_get_blacklist, %w(add change changelist clean copy cstat
|
59
|
+
delete diff edit flush have integ integrate
|
60
|
+
lock login logout move reconcile rename
|
61
|
+
reopen resolve resolved revert shelve submit
|
62
|
+
sync unlock unshelve where
|
63
|
+
))
|
64
|
+
|
65
|
+
# To allow other rack middleware to decide P4_HOST and P4_PORT,
|
66
|
+
# allow_env_p4_config must be true, otherwise, we'll only use the standard
|
67
|
+
# rack configuration mechanism.
|
68
|
+
set(:allow_env_p4_config, false)
|
69
|
+
|
70
|
+
# This is odd, but with some of the project restructuring,
|
71
|
+
# this setting marked error.rb as the 'app_file'
|
72
|
+
set(:app_file, __FILE__)
|
73
|
+
|
74
|
+
# When set to true, we'll run most of the output through a 'normalization'
|
75
|
+
# set of rules. We don't do this for the run methods, but most fields
|
76
|
+
# should appear capitalized, and output dates should all show up in the
|
77
|
+
# standard epoch timestamp
|
78
|
+
set(:normalize_output, true)
|
79
|
+
|
80
|
+
set :raise_errors, :environment == :test
|
81
|
+
set :dump_errors, :environment == :development
|
82
|
+
set :show_exceptions, :environment == :development
|
83
|
+
|
84
|
+
enable :logging
|
85
|
+
|
86
|
+
config_path = if ENV.key?('P4_WEB_API_CONFIG')
|
87
|
+
ENV['P4_WEB_API_CONFIG']
|
88
|
+
else
|
89
|
+
'p4_web_api.yml'
|
90
|
+
end
|
91
|
+
|
92
|
+
config_file config_path if File.exist?(config_path)
|
93
|
+
end
|
94
|
+
|
95
|
+
P4WebAPI::Auth.validate_token_dir(settings.token_path)
|
96
|
+
|
97
|
+
helpers do
|
98
|
+
# A block helper that uses the authentication credentials to create the p4
|
99
|
+
# connection
|
100
|
+
def open_p4(&block)
|
101
|
+
options = {
|
102
|
+
user: env['AUTH_CREDENTIALS'].first,
|
103
|
+
password: P4Util.resolve_password(env, settings),
|
104
|
+
host: P4Util.resolve_host(env, settings),
|
105
|
+
port: P4Util.resolve_port(env, settings)
|
106
|
+
}
|
107
|
+
|
108
|
+
charset = P4Util.resolve_charset(env, settings)
|
109
|
+
options[:charset] = charset if charset
|
110
|
+
|
111
|
+
P4Util.open(options, &block)
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
error do
|
117
|
+
err = env['sinatra.error']
|
118
|
+
|
119
|
+
if err.is_a?(P4Exception)
|
120
|
+
# Can happen when we're not passing a block to
|
121
|
+
# open_p4. Convert to a P4WebAPI error. This is not ideal
|
122
|
+
# as it always uses the same code, but then we have no idea
|
123
|
+
# what actually happened here.
|
124
|
+
|
125
|
+
err = P4WebAPI::P4Error.default_error(err.to_s)
|
126
|
+
|
127
|
+
# Fall through...
|
128
|
+
end
|
129
|
+
|
130
|
+
if err.is_a?(P4WebAPI::P4Error)
|
131
|
+
if err.message_code == 7480 || err.message_code == 7189
|
132
|
+
halt 401
|
133
|
+
else
|
134
|
+
return {
|
135
|
+
MessageCode: err.message_code,
|
136
|
+
MessageSeverity: err.message_severity,
|
137
|
+
MessageText: err.message_text
|
138
|
+
}.to_json
|
139
|
+
end
|
140
|
+
end
|
141
|
+
fail err
|
142
|
+
end
|
143
|
+
|
144
|
+
def to_msg(message)
|
145
|
+
{
|
146
|
+
MessageCode: message.msgid,
|
147
|
+
MessageSeverity: message.severity,
|
148
|
+
MessageText: message.to_s
|
149
|
+
}
|
150
|
+
end
|
151
|
+
|
152
|
+
# Will fetch the system offset based on the server date.
|
153
|
+
#
|
154
|
+
# If allow_env_p4_config is true, we don't cache. Each request could come
|
155
|
+
# in from a new server, and the different servers may have different
|
156
|
+
# offsets.
|
157
|
+
def offset
|
158
|
+
if settings.allow_env_p4_config
|
159
|
+
fetch_offset
|
160
|
+
else
|
161
|
+
@offset ||= fetch_offset
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def fetch_offset
|
166
|
+
offset = nil
|
167
|
+
open_p4 do |p4|
|
168
|
+
results = p4.run_info
|
169
|
+
offset = P4Util.p4_date_offset(results[0]['serverDate'])
|
170
|
+
end
|
171
|
+
offset
|
172
|
+
end
|
173
|
+
|
174
|
+
@normalizers = {}
|
175
|
+
|
176
|
+
class << self
|
177
|
+
attr_accessor :normalizers
|
178
|
+
end
|
179
|
+
|
180
|
+
# It's assumed that these are typically used to find the different spec
|
181
|
+
# types within typical requests.
|
182
|
+
def method_missing(method, *args)
|
183
|
+
return unless method.to_s =~ /^normalize_(.*)/
|
184
|
+
|
185
|
+
spec_type = Regexp.last_match[1]
|
186
|
+
unless self.class.normalizers.key?(spec_type)
|
187
|
+
self.class.normalizers[spec_type] = P4Util.normalizer(spec_type, offset)
|
188
|
+
end
|
189
|
+
self.class.normalizers[spec_type].call(*args)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Authentication methods
|
193
|
+
# See also: https://confluence.perforce.com:8443/display/WS/Authentication+in+Web+Services
|
194
|
+
|
195
|
+
# Creates a sign-in session for the user.
|
196
|
+
#
|
197
|
+
# This session returns a token that should be used as the password in basic
|
198
|
+
# authentication for the user later.
|
199
|
+
post '/v1/sessions' do
|
200
|
+
login = params[:login]
|
201
|
+
password = params[:password] # may be a p4 ticket
|
202
|
+
|
203
|
+
token = nil
|
204
|
+
|
205
|
+
options = {
|
206
|
+
user: login,
|
207
|
+
password: password,
|
208
|
+
host: settings.p4['host'],
|
209
|
+
port: settings.p4['port']
|
210
|
+
}
|
211
|
+
options[:charset] = settings.p4['charset'] if settings.p4.key?('charset')
|
212
|
+
|
213
|
+
P4Util.open(options) do |p4|
|
214
|
+
token = Auth.create_session(p4, password, settings)
|
215
|
+
end
|
216
|
+
|
217
|
+
# We signal a 401 when login/passwords are generally invalid. Since this
|
218
|
+
# is an unauthenticated request, you shouldn't be able to tell if the
|
219
|
+
# login doesn't exist or the password is incorrect.
|
220
|
+
halt 401 unless token
|
221
|
+
|
222
|
+
content_type 'text/plain'
|
223
|
+
return token
|
224
|
+
end
|
225
|
+
|
226
|
+
# I'm not sure if we should ensure the token being deleted is the token
|
227
|
+
# being authenticated against. That's not being checked for the time being.
|
228
|
+
delete '/v1/sessions/:token' do |token|
|
229
|
+
P4WebAPI::Auth.delete_session(token, settings)
|
230
|
+
''
|
231
|
+
end
|
232
|
+
|
233
|
+
# Generic 'do a perforce command' methods.
|
234
|
+
#
|
235
|
+
# These do not ensure true REST-ful style behavior, but are really just
|
236
|
+
# "hey use HTTP to interact with Perforce".
|
237
|
+
|
238
|
+
get '/v1/run' do
|
239
|
+
|
240
|
+
cmd = params[:cmd]
|
241
|
+
|
242
|
+
args = params.select { |key, _| key.start_with?('arg') }
|
243
|
+
.map { |_, value| value }
|
244
|
+
|
245
|
+
if settings.run_get_blacklist.include?(cmd)
|
246
|
+
halt 403, { MessageCode: 15_360,
|
247
|
+
MessageText: "#{cmd} not allowed in web api",
|
248
|
+
MessageSeverity: :ERROR }.to_json
|
249
|
+
end
|
250
|
+
|
251
|
+
results = nil
|
252
|
+
messages = nil
|
253
|
+
|
254
|
+
open_p4 do |p4|
|
255
|
+
results = p4.run(cmd, *args)
|
256
|
+
messages = p4.messages
|
257
|
+
end
|
258
|
+
|
259
|
+
if messages && messages.length > 0
|
260
|
+
messages.map { |m| to_msg(m) }.to_json
|
261
|
+
elsif results
|
262
|
+
results.to_json
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
266
|
+
|
267
|
+
post '/v1/run' do
|
268
|
+
cmd = params[:cmd]
|
269
|
+
|
270
|
+
args = params.select { |key, _| key.start_with?('arg') }
|
271
|
+
.map { |_, value| value }
|
272
|
+
|
273
|
+
# Uses the same blacklist as GET which generally makes sense, since we'll
|
274
|
+
# only really be concerned about client workspace usage on this server.
|
275
|
+
if settings.run_get_blacklist.include?(cmd)
|
276
|
+
halt 403, { MessageCode: 15_360,
|
277
|
+
MessageText: "#{cmd} not allowed in web api",
|
278
|
+
MessageSeverity: :ERROR }.to_json
|
279
|
+
end
|
280
|
+
|
281
|
+
messages = nil
|
282
|
+
|
283
|
+
open_p4 do |p4|
|
284
|
+
p4.input = filter_params(params)
|
285
|
+
p4.run(cmd, args)
|
286
|
+
messages = p4.messages
|
287
|
+
end
|
288
|
+
|
289
|
+
messages.map { |m| to_msg(m) }.to_json if messages
|
290
|
+
end
|
291
|
+
|
292
|
+
# Convenience method to the 'run' mechanism for typical browsing purposes.
|
293
|
+
#
|
294
|
+
# Unlike just standard run commands, this will select either the 'depots' or
|
295
|
+
# 'files' and 'dirs' commands for a single directory level at a time.
|
296
|
+
|
297
|
+
# Special depots only variant
|
298
|
+
get '/v1/files' do
|
299
|
+
results = nil
|
300
|
+
|
301
|
+
open_p4 do |p4|
|
302
|
+
results = p4.run_depots
|
303
|
+
end
|
304
|
+
|
305
|
+
normalize_depots(results) if settings.normalize_output
|
306
|
+
|
307
|
+
results.to_json
|
308
|
+
end
|
309
|
+
|
310
|
+
# General browsing variation.
|
311
|
+
get '/v1/files/*' do
|
312
|
+
dirs = params[:splat].select { |x| !x.empty? }
|
313
|
+
|
314
|
+
results = nil
|
315
|
+
|
316
|
+
open_p4 do |p4|
|
317
|
+
if dirs.empty?
|
318
|
+
results = p4.run_depots
|
319
|
+
normalize_depots(results) if settings.normalize_output
|
320
|
+
else
|
321
|
+
selector = '//' + dirs.join('/') + '/*'
|
322
|
+
files_results = p4.run_files(selector)
|
323
|
+
normalize_files(files_results) if settings.normalize_output
|
324
|
+
dirs_results = p4.run_dirs(selector)
|
325
|
+
normalize_dirs(dirs_results) if settings.normalize_output
|
326
|
+
results = files_results + dirs_results
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
results.to_json
|
331
|
+
end
|
332
|
+
|
333
|
+
# Convenience method to list changelist metadata with some common filtering
|
334
|
+
# options.
|
335
|
+
#
|
336
|
+
# parameters:
|
337
|
+
# - `max` = number
|
338
|
+
# - `status` = pending|submitted|shelved
|
339
|
+
# - `user` = [login]
|
340
|
+
# - `files` = pattern
|
341
|
+
get '/v1/changes' do
|
342
|
+
max = params['max'] if params.key?('max')
|
343
|
+
status = params['status'] if params.key?('status')
|
344
|
+
user = params['user'] if params.key?('user')
|
345
|
+
files = params['files'] if params.key?('files')
|
346
|
+
|
347
|
+
results = nil
|
348
|
+
|
349
|
+
open_p4 do |p4|
|
350
|
+
args = ['changes']
|
351
|
+
|
352
|
+
args.push('-m', max) if max
|
353
|
+
args.push('-s', status) if status
|
354
|
+
args.push('-u', user) if user
|
355
|
+
args.push(files) if files
|
356
|
+
|
357
|
+
results = p4.run(*args)
|
358
|
+
end
|
359
|
+
|
360
|
+
normalize_changes(results) if settings.normalize_output
|
361
|
+
|
362
|
+
results.to_json
|
363
|
+
end
|
364
|
+
|
365
|
+
# Methods to manipulate the protections table. This blindly assumes you
|
366
|
+
# indeed have superuser privileges.
|
367
|
+
|
368
|
+
# Just list all protections
|
369
|
+
get '/v1/protections' do
|
370
|
+
protects = nil
|
371
|
+
open_p4 do |p4|
|
372
|
+
protects = p4.run_protect('-o').first
|
373
|
+
end
|
374
|
+
|
375
|
+
normalize_protections(results) if settings.normalize_output
|
376
|
+
|
377
|
+
protects.to_json
|
378
|
+
end
|
379
|
+
|
380
|
+
# Update protections
|
381
|
+
put '/v1/protections' do
|
382
|
+
protects = {
|
383
|
+
'Protections' => params['Protections']
|
384
|
+
}
|
385
|
+
|
386
|
+
open_p4 do |p4|
|
387
|
+
p4.input = protects
|
388
|
+
p4.run_protect('-i')
|
389
|
+
end
|
390
|
+
|
391
|
+
''
|
392
|
+
end
|
393
|
+
|
394
|
+
# Methods to adjust the triggers table.
|
395
|
+
|
396
|
+
get '/v1/triggers' do
|
397
|
+
triggers = nil
|
398
|
+
open_p4 do |p4|
|
399
|
+
triggers = p4.run_triggers('-o').first
|
400
|
+
end
|
401
|
+
|
402
|
+
normalize_triggers(results) if settings.normalize_output
|
403
|
+
|
404
|
+
triggers.to_json
|
405
|
+
end
|
406
|
+
|
407
|
+
# Update protections
|
408
|
+
put '/v1/triggers' do
|
409
|
+
triggers = {
|
410
|
+
'Triggers' => params['Triggers']
|
411
|
+
}
|
412
|
+
|
413
|
+
open_p4 do |p4|
|
414
|
+
p4.input = triggers
|
415
|
+
p4.run_triggers('-i')
|
416
|
+
end
|
417
|
+
|
418
|
+
''
|
419
|
+
end
|
420
|
+
|
421
|
+
# Generate CRUD methods for each 'spec' type
|
422
|
+
#
|
423
|
+
|
424
|
+
set(:is_spec) do |_x|
|
425
|
+
condition do
|
426
|
+
path_info = env['PATH_INFO']
|
427
|
+
|
428
|
+
matches = %r{^/v1/(?<spec_type>\w+)}.match(path_info)
|
429
|
+
if matches
|
430
|
+
spec_type = matches[:spec_type]
|
431
|
+
return (spec_type == 'branches' ||
|
432
|
+
spec_type == 'clients' ||
|
433
|
+
spec_type == 'depots' ||
|
434
|
+
spec_type == 'groups' ||
|
435
|
+
spec_type == 'jobs' ||
|
436
|
+
spec_type == 'labels' ||
|
437
|
+
# No protects here: the usage is pretty different from the other
|
438
|
+
# spec mechanisms
|
439
|
+
spec_type == 'servers'
|
440
|
+
# No streams, triggers, or users here: it does not work like other
|
441
|
+
# specs, so these method implementations don't quite work.
|
442
|
+
)
|
443
|
+
end
|
444
|
+
false
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
# Provide a generic collection accessor for each of the specs.
|
449
|
+
get '/v1/:spec_type', is_spec: true do |spec_type|
|
450
|
+
results = nil
|
451
|
+
|
452
|
+
open_p4 do |p4|
|
453
|
+
results = p4.run(spec_type)
|
454
|
+
end
|
455
|
+
|
456
|
+
send("normalize_#{spec_type}", results) if settings.normalize_output
|
457
|
+
P4Util.collate_group_results(results) if settings.normalize_output &&
|
458
|
+
spec_type == 'groups'
|
459
|
+
|
460
|
+
results.to_json
|
461
|
+
end
|
462
|
+
|
463
|
+
# Provide a generic output accessor for each spec
|
464
|
+
get '/v1/:spec_type/:id', is_spec: true do |spec_type, id|
|
465
|
+
results = nil
|
466
|
+
|
467
|
+
open_p4 do |p4|
|
468
|
+
results = p4.run(P4Util.singular(spec_type), '-o', id)
|
469
|
+
end
|
470
|
+
|
471
|
+
send("normalize_#{spec_type}", results) if settings.normalize_output
|
472
|
+
|
473
|
+
results[0].to_json
|
474
|
+
end
|
475
|
+
|
476
|
+
# This is our generic "add" mechanism for each type.
|
477
|
+
#
|
478
|
+
# It's assumed that the client understands the requirements of each spec
|
479
|
+
# type.
|
480
|
+
post '/v1/:spec_type', is_spec: true do |spec_type|
|
481
|
+
results = nil
|
482
|
+
|
483
|
+
open_p4 do |p4|
|
484
|
+
method_name = "save_#{P4Util.singular(spec_type)}".to_sym
|
485
|
+
results = p4.send(method_name, params)
|
486
|
+
end
|
487
|
+
|
488
|
+
# In general, the params use the name of the spec as a capitalized
|
489
|
+
# parameter in the singular form.
|
490
|
+
if spec_type == 'servers'
|
491
|
+
id_prop = 'ServerID'
|
492
|
+
else
|
493
|
+
id_prop = P4Util.singular(spec_type).capitalize
|
494
|
+
end
|
495
|
+
id = nil
|
496
|
+
if results.is_a?(Array) &&
|
497
|
+
results.length > 0 &&
|
498
|
+
/Job .* saved/.match(results[0])
|
499
|
+
# special "Job" variant to grab the ID out of the results output
|
500
|
+
id = /Job (.*) saved/.match(results[0])[1]
|
501
|
+
elsif params.key?(id_prop)
|
502
|
+
id = params[id_prop]
|
503
|
+
elsif params.key?(id_prop.to_sym)
|
504
|
+
id = params[id_prop.to_sym]
|
505
|
+
end
|
506
|
+
|
507
|
+
if id
|
508
|
+
redirect "/v1/#{spec_type}/#{id}"
|
509
|
+
else
|
510
|
+
halt 400, "Did not locate #{id_prop} in params"
|
511
|
+
end
|
512
|
+
|
513
|
+
end
|
514
|
+
|
515
|
+
# An 'update' mechanism for each spec type.
|
516
|
+
put '/v1/:spec_type/:id', is_spec: true do |spec_type, id|
|
517
|
+
open_p4 do |p4|
|
518
|
+
singular = P4Util.singular(spec_type)
|
519
|
+
spec = p4.run(singular, '-o', id)[0]
|
520
|
+
|
521
|
+
spec = spec.merge(filter_params(params))
|
522
|
+
|
523
|
+
method_name = "save_#{singular}".to_sym
|
524
|
+
p4.send(method_name, spec)
|
525
|
+
end
|
526
|
+
|
527
|
+
''
|
528
|
+
end
|
529
|
+
|
530
|
+
delete '/v1/:spec_type/:id', is_spec: true do |spec_type, id|
|
531
|
+
open_p4 do |p4|
|
532
|
+
method_name = "delete_#{P4Util.singular(spec_type)}".to_sym
|
533
|
+
p4.send(method_name, id)
|
534
|
+
end
|
535
|
+
|
536
|
+
''
|
537
|
+
end
|
538
|
+
|
539
|
+
# List all users registered in the server
|
540
|
+
get '/v1/users' do
|
541
|
+
results = nil
|
542
|
+
|
543
|
+
open_p4 do |p4|
|
544
|
+
results = p4.run_users
|
545
|
+
end
|
546
|
+
|
547
|
+
normalize_users(results) if settings.normalize_output
|
548
|
+
|
549
|
+
results.to_json
|
550
|
+
end
|
551
|
+
|
552
|
+
# Create a new user
|
553
|
+
post '/v1/users' do
|
554
|
+
open_p4 do |p4|
|
555
|
+
p4.save_user(params, '-f')
|
556
|
+
end
|
557
|
+
|
558
|
+
login = params['User']
|
559
|
+
|
560
|
+
redirect "/v1/users/#{login}"
|
561
|
+
end
|
562
|
+
|
563
|
+
get '/v1/users/:user' do |user|
|
564
|
+
results = nil
|
565
|
+
|
566
|
+
open_p4 do |p4|
|
567
|
+
results = p4.run_user('-o', user)
|
568
|
+
end
|
569
|
+
|
570
|
+
normalize_users(results) if settings.normalize_output
|
571
|
+
|
572
|
+
if results.empty?
|
573
|
+
halt 404
|
574
|
+
else
|
575
|
+
results[0].to_json
|
576
|
+
end
|
577
|
+
end
|
578
|
+
|
579
|
+
# 'Update' the user.
|
580
|
+
put '/v1/users/:user' do |user|
|
581
|
+
open_p4 do |p4|
|
582
|
+
results = p4.run_user('-o', user)
|
583
|
+
|
584
|
+
if results.empty?
|
585
|
+
halt 404
|
586
|
+
return
|
587
|
+
end
|
588
|
+
|
589
|
+
spec = results[0]
|
590
|
+
spec = spec.merge(filter_params(params))
|
591
|
+
|
592
|
+
spec['User'] = user unless spec.key?('User')
|
593
|
+
|
594
|
+
p4.save_user(spec, '-f')
|
595
|
+
end
|
596
|
+
|
597
|
+
# Avoid 'user jdoe saved' message from going out
|
598
|
+
''
|
599
|
+
end
|
600
|
+
|
601
|
+
# Delete the user.
|
602
|
+
delete '/v1/users/:user' do |user|
|
603
|
+
open_p4 do |p4|
|
604
|
+
p4.run_user('-f', '-d', user)
|
605
|
+
end
|
606
|
+
|
607
|
+
# If you're deleting yourself (ragequitting via API) remove thy session
|
608
|
+
if user == env['AUTH_CREDENTIALS'].first
|
609
|
+
token = env['AUTH_CREDENTIALS'].last
|
610
|
+
P4WebAPI::Auth.delete_session(token, settings)
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
# Stream manipulation
|
615
|
+
#
|
616
|
+
# Streams are a little different from other spec types, in that the singluar
|
617
|
+
# 'stream' is identified by a depot-style path, as opposed to kind of a
|
618
|
+
# name. (And since our paths start with two slashes, we ignore those).
|
619
|
+
|
620
|
+
get '/v1/streams' do
|
621
|
+
streams = nil
|
622
|
+
|
623
|
+
open_p4 do |p4|
|
624
|
+
streams = p4.run_streams
|
625
|
+
end
|
626
|
+
|
627
|
+
normalize_streams(streams) if settings.normalize_output
|
628
|
+
|
629
|
+
streams.to_json
|
630
|
+
end
|
631
|
+
|
632
|
+
# Creates a new stream
|
633
|
+
post '/v1/streams' do
|
634
|
+
open_p4 do |p4|
|
635
|
+
p4.save_stream(filter_params(params))
|
636
|
+
end
|
637
|
+
|
638
|
+
stream = params['Stream']
|
639
|
+
|
640
|
+
sub_path = stream[2..-1]
|
641
|
+
|
642
|
+
redirect "/v1/streams/#{sub_path}"
|
643
|
+
end
|
644
|
+
|
645
|
+
get '/v1/streams/*' do
|
646
|
+
sub_path = params[:splat].join('')
|
647
|
+
|
648
|
+
stream = "//#{sub_path}"
|
649
|
+
|
650
|
+
results = nil
|
651
|
+
|
652
|
+
open_p4 do |p4|
|
653
|
+
results = p4.run_stream('-o', stream)
|
654
|
+
end
|
655
|
+
|
656
|
+
normalize_streams(results) if settings.normalize_output
|
657
|
+
|
658
|
+
results[0].to_json
|
659
|
+
end
|
660
|
+
|
661
|
+
put '/v1/streams/*' do
|
662
|
+
sub_path = params[:splat].join('')
|
663
|
+
|
664
|
+
stream = "//#{sub_path}"
|
665
|
+
|
666
|
+
open_p4 do |p4|
|
667
|
+
spec = p4.run_stream('-o', stream)[0]
|
668
|
+
|
669
|
+
spec = spec.merge(filter_params(params))
|
670
|
+
|
671
|
+
p4.save_stream(spec, '-f')
|
672
|
+
end
|
673
|
+
|
674
|
+
''
|
675
|
+
end
|
676
|
+
|
677
|
+
delete '/v1/streams/*' do
|
678
|
+
sub_path = params[:splat].join('')
|
679
|
+
|
680
|
+
stream = "//#{sub_path}"
|
681
|
+
|
682
|
+
open_p4 do |p4|
|
683
|
+
p4.run_stream('-d', stream)
|
684
|
+
end
|
685
|
+
|
686
|
+
''
|
687
|
+
end
|
688
|
+
|
689
|
+
# Basically a "blacklist" of things we know the frameworks going to add to
|
690
|
+
# the params array we don't want to pass on to the p4 command sets for spec
|
691
|
+
# input
|
692
|
+
def filter_params(params)
|
693
|
+
params.select do |k, _v|
|
694
|
+
k != 'spec_type' && k != 'id' && k != 'splat' && k != 'captures'
|
695
|
+
end
|
696
|
+
end
|
697
|
+
|
698
|
+
run! if app_file == $PROGRAM_NAME
|
699
|
+
end
|
700
|
+
end
|