p4_web_api 2014.2.0.pre2 → 2014.2.0.pre4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/p4_web_api +8 -2
- data/lib/p4_web_api.rb +27 -670
- data/lib/p4_web_api/app/changes.rb +73 -0
- data/lib/p4_web_api/app/commands.rb +58 -0
- data/lib/p4_web_api/app/files.rb +166 -0
- data/lib/p4_web_api/app/protections.rb +33 -0
- data/lib/p4_web_api/app/sessions.rb +45 -0
- data/lib/p4_web_api/app/specs.rb +114 -0
- data/lib/p4_web_api/app/streams.rb +76 -0
- data/lib/p4_web_api/app/triggers.rb +31 -0
- data/lib/p4_web_api/app/users.rb +78 -0
- data/lib/p4_web_api/auth.rb +3 -3
- data/lib/p4_web_api/change_helper.rb +149 -0
- data/lib/p4_web_api/helpers.rb +84 -0
- data/lib/p4_web_api/p4_util.rb +90 -1
- data/lib/p4_web_api/version.rb +1 -1
- metadata +13 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7928b3bca740696b9fb622b838ad6dfa0eb06857
|
4
|
+
data.tar.gz: abca39306fb28d24b7c986f19d11dcf041973904
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f8f49dec8c3fcb072354ae86c4ce6e3f5ea46ece96c4a5052e8a24a7cf43499332374149dd8792302c5152026d2d908f20bac6ce4e1306025342a78c3207c21
|
7
|
+
data.tar.gz: b1f84f8cc8e6c1d192b769ca9066d415571349d3f182b49c492605e5c58b3b37511f9e5565dfb8ee584105014d3d1f0f30cdd73a65048b16e84ec4c46bdd6b36
|
data/bin/p4_web_api
CHANGED
@@ -1,4 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
|
2
|
+
|
3
|
+
ENV['RACK_ENV'] = 'production' unless ENV.key?('RACK_ENV')
|
4
|
+
|
5
|
+
lib_dir = File.expand_path('../../lib', __FILE__)
|
6
|
+
$LOAD_PATH.unshift(lib_dir)
|
7
|
+
|
8
|
+
require 'p4_web_api'
|
9
|
+
|
4
10
|
P4WebAPI::App.run!
|
data/lib/p4_web_api.rb
CHANGED
@@ -18,9 +18,10 @@ require 'sinatra/json'
|
|
18
18
|
require 'sinatra/config_file'
|
19
19
|
require 'rack/parser'
|
20
20
|
|
21
|
-
require 'p4_web_api/version'
|
22
21
|
require 'p4_web_api/auth'
|
22
|
+
require 'p4_web_api/helpers'
|
23
23
|
require 'p4_web_api/p4_util'
|
24
|
+
require 'p4_web_api/version'
|
24
25
|
|
25
26
|
# The P4WebAPI is our namespace for the web application.
|
26
27
|
#
|
@@ -95,84 +96,6 @@ module P4WebAPI
|
|
95
96
|
|
96
97
|
P4WebAPI::Auth.validate_token_dir(settings.token_path)
|
97
98
|
|
98
|
-
helpers do
|
99
|
-
# A block helper that uses the authentication credentials to create the p4
|
100
|
-
# connection
|
101
|
-
def open_p4(&block)
|
102
|
-
options = {
|
103
|
-
user: env['AUTH_CREDENTIALS'].first,
|
104
|
-
password: P4Util.resolve_password(env, settings),
|
105
|
-
host: P4Util.resolve_host(env, settings),
|
106
|
-
port: P4Util.resolve_port(env, settings)
|
107
|
-
}
|
108
|
-
|
109
|
-
charset = P4Util.resolve_charset(env, settings)
|
110
|
-
options[:charset] = charset if charset
|
111
|
-
|
112
|
-
P4Util.open(options, &block)
|
113
|
-
end
|
114
|
-
|
115
|
-
# Block helper to open a p4 handle with a temporary client workspace.
|
116
|
-
# The client workspace will map the series of depot path expressions
|
117
|
-
# directly into the client workspace.
|
118
|
-
#
|
119
|
-
# The depot_paths are generally expected to be complete file path
|
120
|
-
# expressions, e.g., '//depot/dir1/dir2/file'. They'll be mapped directly
|
121
|
-
# to the temporary working area: '//client/depot/dir/1/dir2/file'
|
122
|
-
#
|
123
|
-
# The block handler here will be called with the arguments (p4, root),
|
124
|
-
# where root is the temporary client's root directory.
|
125
|
-
def open_p4_temp_client(depot_paths, &block)
|
126
|
-
open_p4 do |p4|
|
127
|
-
name = (0...8).map { (65 + rand(26)).chr }.join
|
128
|
-
dir = init_temp_workspace_dir(name)
|
129
|
-
init_temp_client(p4, name, dir, depot_paths)
|
130
|
-
|
131
|
-
ex = nil
|
132
|
-
begin
|
133
|
-
block.call(p4, dir)
|
134
|
-
rescue StandardError => e
|
135
|
-
ex = e
|
136
|
-
end
|
137
|
-
|
138
|
-
delete_temp_client(p4, name, dir)
|
139
|
-
|
140
|
-
fail ex unless ex.nil?
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
def init_temp_workspace_dir(name)
|
146
|
-
dir = File.join(settings.workspace_folder, name)
|
147
|
-
unless Dir.exist?(dir)
|
148
|
-
FileUtils.mkpath(dir)
|
149
|
-
FileUtils.chmod(0700, dir)
|
150
|
-
end
|
151
|
-
dir
|
152
|
-
end
|
153
|
-
|
154
|
-
def init_temp_client(p4, name, dir, depot_paths)
|
155
|
-
spec = p4.fetch_client
|
156
|
-
spec._root = dir
|
157
|
-
spec._client = name
|
158
|
-
spec._description = 'p4_web_api temp client'
|
159
|
-
|
160
|
-
spec._view = depot_paths.map do |path|
|
161
|
-
stripped = path.gsub(/^[\/]*/, '')
|
162
|
-
"\"//#{stripped}\" \"//#{name}/#{stripped}\""
|
163
|
-
end
|
164
|
-
|
165
|
-
p4.save_client(spec)
|
166
|
-
p4.client = name
|
167
|
-
|
168
|
-
p4.run_sync('//...')
|
169
|
-
end
|
170
|
-
|
171
|
-
def delete_temp_client(p4, name, dir)
|
172
|
-
p4.run_client('-d', '-f', name)
|
173
|
-
FileUtils.rmtree(dir)
|
174
|
-
end
|
175
|
-
|
176
99
|
error do
|
177
100
|
err = env['sinatra.error']
|
178
101
|
|
@@ -201,6 +124,16 @@ module P4WebAPI
|
|
201
124
|
fail err
|
202
125
|
end
|
203
126
|
|
127
|
+
not_found do
|
128
|
+
return {
|
129
|
+
# This is not an application error, so I'm using error code 0 to mean
|
130
|
+
# 'use HTTP status'
|
131
|
+
MessageCode: 0,
|
132
|
+
MessageSeverity: 3,
|
133
|
+
MessageText: 'Resource not found'
|
134
|
+
}.to_json
|
135
|
+
end
|
136
|
+
|
204
137
|
def to_msg(message)
|
205
138
|
{
|
206
139
|
MessageCode: message.msgid,
|
@@ -209,6 +142,8 @@ module P4WebAPI
|
|
209
142
|
}
|
210
143
|
end
|
211
144
|
|
145
|
+
helpers Helpers
|
146
|
+
|
212
147
|
# Will fetch the system offset based on the server date.
|
213
148
|
#
|
214
149
|
# If allow_env_p4_config is true, we don't cache. Each request could come
|
@@ -249,597 +184,6 @@ module P4WebAPI
|
|
249
184
|
self.class.normalizers[spec_type].call(*args)
|
250
185
|
end
|
251
186
|
|
252
|
-
# Authentication methods
|
253
|
-
# See also: https://confluence.perforce.com:8443/display/WS/Authentication+in+Web+Services
|
254
|
-
|
255
|
-
# Creates a sign-in session for the user.
|
256
|
-
#
|
257
|
-
# This session returns a token that should be used as the password in basic
|
258
|
-
# authentication for the user later.
|
259
|
-
post '/v1/sessions' do
|
260
|
-
login = params[:login]
|
261
|
-
password = params[:password] # may be a p4 ticket
|
262
|
-
|
263
|
-
token = nil
|
264
|
-
|
265
|
-
options = {
|
266
|
-
user: login,
|
267
|
-
password: password,
|
268
|
-
host: settings.p4['host'],
|
269
|
-
port: settings.p4['port']
|
270
|
-
}
|
271
|
-
options[:charset] = settings.p4['charset'] if settings.p4.key?('charset')
|
272
|
-
|
273
|
-
P4Util.open(options) do |p4|
|
274
|
-
token = Auth.create_session(p4, password, settings)
|
275
|
-
end
|
276
|
-
|
277
|
-
# We signal a 401 when login/passwords are generally invalid. Since this
|
278
|
-
# is an unauthenticated request, you shouldn't be able to tell if the
|
279
|
-
# login doesn't exist or the password is incorrect.
|
280
|
-
halt 401 unless token
|
281
|
-
|
282
|
-
content_type 'text/plain'
|
283
|
-
return token
|
284
|
-
end
|
285
|
-
|
286
|
-
# I'm not sure if we should ensure the token being deleted is the token
|
287
|
-
# being authenticated against. That's not being checked for the time being.
|
288
|
-
delete '/v1/sessions/:token' do |token|
|
289
|
-
P4WebAPI::Auth.delete_session(token, settings)
|
290
|
-
''
|
291
|
-
end
|
292
|
-
|
293
|
-
# Generic 'do a perforce command' methods.
|
294
|
-
#
|
295
|
-
# These do not ensure true REST-ful style behavior, but are really just
|
296
|
-
# "hey use HTTP to interact with Perforce".
|
297
|
-
|
298
|
-
get '/v1/run' do
|
299
|
-
|
300
|
-
cmd = params[:cmd]
|
301
|
-
|
302
|
-
args = params.select { |key, _| key.start_with?('arg') }
|
303
|
-
.map { |_, value| value }
|
304
|
-
|
305
|
-
if settings.run_get_blacklist.include?(cmd)
|
306
|
-
halt 403, { MessageCode: 15_360,
|
307
|
-
MessageText: "#{cmd} not allowed in web api",
|
308
|
-
MessageSeverity: :ERROR }.to_json
|
309
|
-
end
|
310
|
-
|
311
|
-
results = nil
|
312
|
-
messages = nil
|
313
|
-
|
314
|
-
open_p4 do |p4|
|
315
|
-
results = p4.run(cmd, *args)
|
316
|
-
messages = p4.messages
|
317
|
-
end
|
318
|
-
|
319
|
-
if messages && messages.length > 0
|
320
|
-
messages.map { |m| to_msg(m) }.to_json
|
321
|
-
elsif results
|
322
|
-
results.to_json
|
323
|
-
end
|
324
|
-
|
325
|
-
end
|
326
|
-
|
327
|
-
post '/v1/run' do
|
328
|
-
cmd = params[:cmd]
|
329
|
-
|
330
|
-
args = params.select { |key, _| key.start_with?('arg') }
|
331
|
-
.map { |_, value| value }
|
332
|
-
|
333
|
-
# Uses the same blacklist as GET which generally makes sense, since we'll
|
334
|
-
# only really be concerned about client workspace usage on this server.
|
335
|
-
if settings.run_get_blacklist.include?(cmd)
|
336
|
-
halt 403, { MessageCode: 15_360,
|
337
|
-
MessageText: "#{cmd} not allowed in web api",
|
338
|
-
MessageSeverity: :ERROR }.to_json
|
339
|
-
end
|
340
|
-
|
341
|
-
messages = nil
|
342
|
-
|
343
|
-
open_p4 do |p4|
|
344
|
-
p4.input = filter_params(params)
|
345
|
-
p4.run(cmd, args)
|
346
|
-
messages = p4.messages
|
347
|
-
end
|
348
|
-
|
349
|
-
messages.map { |m| to_msg(m) }.to_json if messages
|
350
|
-
end
|
351
|
-
|
352
|
-
# Convenience method to the 'run' mechanism for typical browsing purposes.
|
353
|
-
#
|
354
|
-
# Unlike just standard run commands, this will select either the 'depots' or
|
355
|
-
# 'files' and 'dirs' commands for a single directory level at a time.
|
356
|
-
|
357
|
-
# Special depots only variant
|
358
|
-
get '/v1/files' do
|
359
|
-
results = nil
|
360
|
-
|
361
|
-
open_p4 do |p4|
|
362
|
-
results = p4.run_depots
|
363
|
-
end
|
364
|
-
|
365
|
-
normalize_depots(results) if settings.normalize_output
|
366
|
-
|
367
|
-
results.to_json
|
368
|
-
end
|
369
|
-
|
370
|
-
# General browsing variation.
|
371
|
-
get '/v1/files/*' do
|
372
|
-
dirs = params[:splat].select { |x| !x.empty? }
|
373
|
-
|
374
|
-
results = nil
|
375
|
-
|
376
|
-
open_p4 do |p4|
|
377
|
-
if dirs.empty?
|
378
|
-
results = p4.run_depots
|
379
|
-
normalize_depots(results) if settings.normalize_output
|
380
|
-
else
|
381
|
-
selector = '//' + dirs.join('/') + '/*'
|
382
|
-
files_results = p4.run_files(selector)
|
383
|
-
normalize_files(files_results) if settings.normalize_output
|
384
|
-
dirs_results = p4.run_dirs(selector)
|
385
|
-
normalize_dirs(dirs_results) if settings.normalize_output
|
386
|
-
results = files_results + dirs_results
|
387
|
-
end
|
388
|
-
end
|
389
|
-
|
390
|
-
results.to_json
|
391
|
-
end
|
392
|
-
|
393
|
-
# Convenience method to list changelist metadata with some common filtering
|
394
|
-
# options.
|
395
|
-
#
|
396
|
-
# parameters:
|
397
|
-
# - `max` = number
|
398
|
-
# - `status` = pending|submitted|shelved
|
399
|
-
# - `user` = [login]
|
400
|
-
# - `files` = pattern
|
401
|
-
get '/v1/changes' do
|
402
|
-
max = params['max'] if params.key?('max')
|
403
|
-
status = params['status'] if params.key?('status')
|
404
|
-
user = params['user'] if params.key?('user')
|
405
|
-
files = params['files'] if params.key?('files')
|
406
|
-
|
407
|
-
results = nil
|
408
|
-
|
409
|
-
open_p4 do |p4|
|
410
|
-
args = ['changes']
|
411
|
-
|
412
|
-
args.push('-m', max) if max
|
413
|
-
args.push('-s', status) if status
|
414
|
-
args.push('-u', user) if user
|
415
|
-
args.push(files) if files
|
416
|
-
|
417
|
-
results = p4.run(*args)
|
418
|
-
end
|
419
|
-
|
420
|
-
normalize_changes(results) if settings.normalize_output
|
421
|
-
|
422
|
-
results.to_json
|
423
|
-
end
|
424
|
-
|
425
|
-
# Methods to manipulate the protections table. This blindly assumes you
|
426
|
-
# indeed have superuser privileges.
|
427
|
-
|
428
|
-
# Just list all protections
|
429
|
-
get '/v1/protections' do
|
430
|
-
protects = nil
|
431
|
-
open_p4 do |p4|
|
432
|
-
protects = p4.run_protect('-o').first
|
433
|
-
end
|
434
|
-
|
435
|
-
normalize_protections(results) if settings.normalize_output
|
436
|
-
|
437
|
-
protects.to_json
|
438
|
-
end
|
439
|
-
|
440
|
-
# Update protections
|
441
|
-
put '/v1/protections' do
|
442
|
-
protects = {
|
443
|
-
'Protections' => params['Protections']
|
444
|
-
}
|
445
|
-
|
446
|
-
open_p4 do |p4|
|
447
|
-
p4.input = protects
|
448
|
-
p4.run_protect('-i')
|
449
|
-
end
|
450
|
-
|
451
|
-
''
|
452
|
-
end
|
453
|
-
|
454
|
-
# Methods to adjust the triggers table.
|
455
|
-
|
456
|
-
get '/v1/triggers' do
|
457
|
-
triggers = nil
|
458
|
-
open_p4 do |p4|
|
459
|
-
triggers = p4.run_triggers('-o').first
|
460
|
-
end
|
461
|
-
|
462
|
-
normalize_triggers(results) if settings.normalize_output
|
463
|
-
|
464
|
-
triggers.to_json
|
465
|
-
end
|
466
|
-
|
467
|
-
# Update protections
|
468
|
-
put '/v1/triggers' do
|
469
|
-
triggers = {
|
470
|
-
'Triggers' => params['Triggers']
|
471
|
-
}
|
472
|
-
|
473
|
-
open_p4 do |p4|
|
474
|
-
p4.input = triggers
|
475
|
-
p4.run_triggers('-i')
|
476
|
-
end
|
477
|
-
|
478
|
-
''
|
479
|
-
end
|
480
|
-
|
481
|
-
# Generate CRUD methods for each 'spec' type
|
482
|
-
#
|
483
|
-
|
484
|
-
set(:is_spec) do |_x|
|
485
|
-
condition do
|
486
|
-
path_info = env['PATH_INFO']
|
487
|
-
|
488
|
-
matches = %r{^/v1/(?<spec_type>\w+)}.match(path_info)
|
489
|
-
if matches
|
490
|
-
spec_type = matches[:spec_type]
|
491
|
-
return (spec_type == 'branches' ||
|
492
|
-
spec_type == 'clients' ||
|
493
|
-
spec_type == 'depots' ||
|
494
|
-
spec_type == 'groups' ||
|
495
|
-
spec_type == 'jobs' ||
|
496
|
-
spec_type == 'labels' ||
|
497
|
-
# No protects here: the usage is pretty different from the other
|
498
|
-
# spec mechanisms
|
499
|
-
spec_type == 'servers'
|
500
|
-
# No streams, triggers, or users here: it does not work like other
|
501
|
-
# specs, so these method implementations don't quite work.
|
502
|
-
)
|
503
|
-
end
|
504
|
-
false
|
505
|
-
end
|
506
|
-
end
|
507
|
-
|
508
|
-
# Provide a generic collection accessor for each of the specs.
|
509
|
-
get '/v1/:spec_type', is_spec: true do |spec_type|
|
510
|
-
results = nil
|
511
|
-
|
512
|
-
open_p4 do |p4|
|
513
|
-
results = p4.run(spec_type)
|
514
|
-
end
|
515
|
-
|
516
|
-
send("normalize_#{spec_type}", results) if settings.normalize_output
|
517
|
-
P4Util.collate_group_results(results) if settings.normalize_output &&
|
518
|
-
spec_type == 'groups'
|
519
|
-
|
520
|
-
results.to_json
|
521
|
-
end
|
522
|
-
|
523
|
-
# Provide a generic output accessor for each spec
|
524
|
-
get '/v1/:spec_type/:id', is_spec: true do |spec_type, id|
|
525
|
-
results = nil
|
526
|
-
|
527
|
-
open_p4 do |p4|
|
528
|
-
results = p4.run(P4Util.singular(spec_type), '-o', id)
|
529
|
-
end
|
530
|
-
|
531
|
-
send("normalize_#{spec_type}", results) if settings.normalize_output
|
532
|
-
|
533
|
-
results[0].to_json
|
534
|
-
end
|
535
|
-
|
536
|
-
# This is our generic "add" mechanism for each type.
|
537
|
-
#
|
538
|
-
# It's assumed that the client understands the requirements of each spec
|
539
|
-
# type.
|
540
|
-
post '/v1/:spec_type', is_spec: true do |spec_type|
|
541
|
-
results = nil
|
542
|
-
|
543
|
-
open_p4 do |p4|
|
544
|
-
method_name = "save_#{P4Util.singular(spec_type)}".to_sym
|
545
|
-
results = p4.send(method_name, params)
|
546
|
-
end
|
547
|
-
|
548
|
-
# In general, the params use the name of the spec as a capitalized
|
549
|
-
# parameter in the singular form.
|
550
|
-
if spec_type == 'servers'
|
551
|
-
id_prop = 'ServerID'
|
552
|
-
else
|
553
|
-
id_prop = P4Util.singular(spec_type).capitalize
|
554
|
-
end
|
555
|
-
id = nil
|
556
|
-
if results.is_a?(Array) &&
|
557
|
-
results.length > 0 &&
|
558
|
-
/Job .* saved/.match(results[0])
|
559
|
-
# special "Job" variant to grab the ID out of the results output
|
560
|
-
id = /Job (.*) saved/.match(results[0])[1]
|
561
|
-
elsif params.key?(id_prop)
|
562
|
-
id = params[id_prop]
|
563
|
-
elsif params.key?(id_prop.to_sym)
|
564
|
-
id = params[id_prop.to_sym]
|
565
|
-
end
|
566
|
-
|
567
|
-
if id
|
568
|
-
redirect "/v1/#{spec_type}/#{id}"
|
569
|
-
else
|
570
|
-
halt 400, "Did not locate #{id_prop} in params"
|
571
|
-
end
|
572
|
-
|
573
|
-
end
|
574
|
-
|
575
|
-
# An 'update' mechanism for each spec type.
|
576
|
-
put '/v1/:spec_type/:id', is_spec: true do |spec_type, id|
|
577
|
-
open_p4 do |p4|
|
578
|
-
singular = P4Util.singular(spec_type)
|
579
|
-
spec = p4.run(singular, '-o', id)[0]
|
580
|
-
|
581
|
-
spec = spec.merge(filter_params(params))
|
582
|
-
|
583
|
-
method_name = "save_#{singular}".to_sym
|
584
|
-
p4.send(method_name, spec)
|
585
|
-
end
|
586
|
-
|
587
|
-
''
|
588
|
-
end
|
589
|
-
|
590
|
-
delete '/v1/:spec_type/:id', is_spec: true do |spec_type, id|
|
591
|
-
open_p4 do |p4|
|
592
|
-
method_name = "delete_#{P4Util.singular(spec_type)}".to_sym
|
593
|
-
p4.send(method_name, id)
|
594
|
-
end
|
595
|
-
|
596
|
-
''
|
597
|
-
end
|
598
|
-
|
599
|
-
# List all users registered in the server
|
600
|
-
get '/v1/users' do
|
601
|
-
results = nil
|
602
|
-
|
603
|
-
open_p4 do |p4|
|
604
|
-
results = p4.run_users
|
605
|
-
end
|
606
|
-
|
607
|
-
normalize_users(results) if settings.normalize_output
|
608
|
-
|
609
|
-
results.to_json
|
610
|
-
end
|
611
|
-
|
612
|
-
# Create a new user
|
613
|
-
post '/v1/users' do
|
614
|
-
open_p4 do |p4|
|
615
|
-
p4.save_user(params, '-f')
|
616
|
-
end
|
617
|
-
|
618
|
-
login = params['User']
|
619
|
-
|
620
|
-
redirect "/v1/users/#{login}"
|
621
|
-
end
|
622
|
-
|
623
|
-
get '/v1/users/:user' do |user|
|
624
|
-
results = nil
|
625
|
-
|
626
|
-
open_p4 do |p4|
|
627
|
-
results = p4.run_user('-o', user)
|
628
|
-
end
|
629
|
-
|
630
|
-
normalize_users(results) if settings.normalize_output
|
631
|
-
|
632
|
-
if results.empty?
|
633
|
-
halt 404
|
634
|
-
else
|
635
|
-
results[0].to_json
|
636
|
-
end
|
637
|
-
end
|
638
|
-
|
639
|
-
# 'Update' the user.
|
640
|
-
put '/v1/users/:user' do |user|
|
641
|
-
open_p4 do |p4|
|
642
|
-
results = p4.run_user('-o', user)
|
643
|
-
|
644
|
-
if results.empty?
|
645
|
-
halt 404
|
646
|
-
return
|
647
|
-
end
|
648
|
-
|
649
|
-
spec = results[0]
|
650
|
-
spec = spec.merge(filter_params(params))
|
651
|
-
|
652
|
-
spec['User'] = user unless spec.key?('User')
|
653
|
-
|
654
|
-
p4.save_user(spec, '-f')
|
655
|
-
end
|
656
|
-
|
657
|
-
# Avoid 'user jdoe saved' message from going out
|
658
|
-
''
|
659
|
-
end
|
660
|
-
|
661
|
-
# Delete the user.
|
662
|
-
delete '/v1/users/:user' do |user|
|
663
|
-
open_p4 do |p4|
|
664
|
-
p4.run_user('-f', '-d', user)
|
665
|
-
end
|
666
|
-
|
667
|
-
# If you're deleting yourself (ragequitting via API) remove thy session
|
668
|
-
if user == env['AUTH_CREDENTIALS'].first
|
669
|
-
token = env['AUTH_CREDENTIALS'].last
|
670
|
-
P4WebAPI::Auth.delete_session(token, settings)
|
671
|
-
end
|
672
|
-
end
|
673
|
-
|
674
|
-
# Stream manipulation
|
675
|
-
#
|
676
|
-
# Streams are a little different from other spec types, in that the singluar
|
677
|
-
# 'stream' is identified by a depot-style path, as opposed to kind of a
|
678
|
-
# name. (And since our paths start with two slashes, we ignore those).
|
679
|
-
|
680
|
-
get '/v1/streams' do
|
681
|
-
streams = nil
|
682
|
-
|
683
|
-
open_p4 do |p4|
|
684
|
-
streams = p4.run_streams
|
685
|
-
end
|
686
|
-
|
687
|
-
normalize_streams(streams) if settings.normalize_output
|
688
|
-
|
689
|
-
streams.to_json
|
690
|
-
end
|
691
|
-
|
692
|
-
# Creates a new stream
|
693
|
-
post '/v1/streams' do
|
694
|
-
open_p4 do |p4|
|
695
|
-
p4.save_stream(filter_params(params))
|
696
|
-
end
|
697
|
-
|
698
|
-
stream = params['Stream']
|
699
|
-
|
700
|
-
sub_path = stream[2..-1]
|
701
|
-
|
702
|
-
redirect "/v1/streams/#{sub_path}"
|
703
|
-
end
|
704
|
-
|
705
|
-
get '/v1/streams/*' do
|
706
|
-
sub_path = params[:splat].join('')
|
707
|
-
|
708
|
-
stream = "//#{sub_path}"
|
709
|
-
|
710
|
-
results = nil
|
711
|
-
|
712
|
-
open_p4 do |p4|
|
713
|
-
results = p4.run_stream('-o', stream)
|
714
|
-
end
|
715
|
-
|
716
|
-
normalize_streams(results) if settings.normalize_output
|
717
|
-
|
718
|
-
results[0].to_json
|
719
|
-
end
|
720
|
-
|
721
|
-
put '/v1/streams/*' do
|
722
|
-
sub_path = params[:splat].join('')
|
723
|
-
|
724
|
-
stream = "//#{sub_path}"
|
725
|
-
|
726
|
-
open_p4 do |p4|
|
727
|
-
spec = p4.run_stream('-o', stream)[0]
|
728
|
-
|
729
|
-
spec = spec.merge(filter_params(params))
|
730
|
-
|
731
|
-
p4.save_stream(spec, '-f')
|
732
|
-
end
|
733
|
-
|
734
|
-
''
|
735
|
-
end
|
736
|
-
|
737
|
-
delete '/v1/streams/*' do
|
738
|
-
sub_path = params[:splat].join('')
|
739
|
-
|
740
|
-
stream = "//#{sub_path}"
|
741
|
-
|
742
|
-
open_p4 do |p4|
|
743
|
-
p4.run_stream('-d', stream)
|
744
|
-
end
|
745
|
-
|
746
|
-
''
|
747
|
-
end
|
748
|
-
|
749
|
-
# The print method uses the print command underneath to output file content.
|
750
|
-
#
|
751
|
-
# Unlike most of the other methods, this does not output application/json.
|
752
|
-
# If the type contains 'text' we set the Content-Type to 'text/plain',
|
753
|
-
# otherwise it's 'application/octet-stream'.
|
754
|
-
get '/v1/print/*' do
|
755
|
-
path = params[:splat].join('')
|
756
|
-
path = "//#{path}"
|
757
|
-
|
758
|
-
results = nil
|
759
|
-
open_p4 do |p4|
|
760
|
-
results = p4.run_print(path)
|
761
|
-
end
|
762
|
-
|
763
|
-
file_type = results[0]
|
764
|
-
content = results[1]
|
765
|
-
|
766
|
-
if file_type['type'] =~ /text/
|
767
|
-
content_type 'text/plain'
|
768
|
-
else
|
769
|
-
content_type 'application/octet-stream'
|
770
|
-
end
|
771
|
-
|
772
|
-
content
|
773
|
-
end
|
774
|
-
|
775
|
-
# The upload method adds new file revisions.
|
776
|
-
#
|
777
|
-
# This is a multipart method that accepts and array of files, plus,
|
778
|
-
# a 'mappings' array to indicate the target depot path for each file.
|
779
|
-
# This should be a JSON encoded array of strings.
|
780
|
-
#
|
781
|
-
# An optional 'description' attribute can describe the changes to be made.
|
782
|
-
post '/v1/upload' do
|
783
|
-
# if mappings[] and files[] do not have equivalent lengths, the error
|
784
|
-
# might be kind of lame.
|
785
|
-
mappings = params[:mappings]
|
786
|
-
files = []
|
787
|
-
mappings.each_index { |idx| files << params["file_#{idx}".to_sym] }
|
788
|
-
|
789
|
-
open_p4_temp_client(mappings) do |p4, root|
|
790
|
-
message = params[:description] || 'Uploaded files'
|
791
|
-
change_id = init_changelist(p4, message)
|
792
|
-
|
793
|
-
# Information used to determine if the new rev should be an add or edit
|
794
|
-
files_results = p4.run_files(mappings)
|
795
|
-
|
796
|
-
(0...mappings.length).each do |idx|
|
797
|
-
type = existing_path?(files_results, mappings[idx]) ? 'edit' : 'add'
|
798
|
-
|
799
|
-
if type == 'edit'
|
800
|
-
mark_change('edit', p4, change_id, root, mappings[idx])
|
801
|
-
save_content(root, mappings[idx], files[idx])
|
802
|
-
else
|
803
|
-
save_content(root, mappings[idx], files[idx])
|
804
|
-
mark_change('add', p4, change_id, root, mappings[idx])
|
805
|
-
end
|
806
|
-
end
|
807
|
-
|
808
|
-
p4.run_submit('-c', change_id)
|
809
|
-
end
|
810
|
-
end
|
811
|
-
|
812
|
-
def init_changelist(p4, description)
|
813
|
-
change_spec = p4.fetch_change
|
814
|
-
change_spec._description = description
|
815
|
-
results = p4.save_change(change_spec)
|
816
|
-
results[0].gsub(/^Change (\d+) created./, '\1')
|
817
|
-
end
|
818
|
-
|
819
|
-
def save_content(root, depot_path, file)
|
820
|
-
local_file = local_path(depot_path, root)
|
821
|
-
dir = File.dirname(local_file)
|
822
|
-
FileUtils.mkpath(dir) unless Dir.exist?(dir)
|
823
|
-
|
824
|
-
IO.write(local_file, file[:tempfile].read)
|
825
|
-
end
|
826
|
-
|
827
|
-
def existing_path?(existing_results, depot_path)
|
828
|
-
existing_results.any? do |result|
|
829
|
-
result['depotFile'] == depot_path
|
830
|
-
end
|
831
|
-
end
|
832
|
-
|
833
|
-
def mark_change(type, p4, change_id, root, depot_path)
|
834
|
-
local_file = local_path(depot_path, root)
|
835
|
-
p4.run(type, '-c', change_id, local_file)
|
836
|
-
end
|
837
|
-
|
838
|
-
def local_path(depot_path, root)
|
839
|
-
stripped = depot_path.gsub(/^\/+/, '')
|
840
|
-
File.join(root, stripped)
|
841
|
-
end
|
842
|
-
|
843
187
|
# Basically a "blacklist" of things we know the frameworks going to add to
|
844
188
|
# the params array we don't want to pass on to the p4 command sets for spec
|
845
189
|
# input
|
@@ -852,3 +196,16 @@ module P4WebAPI
|
|
852
196
|
run! if app_file == $PROGRAM_NAME
|
853
197
|
end
|
854
198
|
end
|
199
|
+
|
200
|
+
# Reopen up the P4WebAPI::App class and add most of our method handling. This
|
201
|
+
# is done in lieu of having multiple Sinatra apps, so we can have the same
|
202
|
+
# configuration and error handling.
|
203
|
+
require 'p4_web_api/app/changes'
|
204
|
+
require 'p4_web_api/app/commands'
|
205
|
+
require 'p4_web_api/app/files'
|
206
|
+
require 'p4_web_api/app/protections'
|
207
|
+
require 'p4_web_api/app/sessions'
|
208
|
+
require 'p4_web_api/app/specs'
|
209
|
+
require 'p4_web_api/app/streams'
|
210
|
+
require 'p4_web_api/app/triggers'
|
211
|
+
require 'p4_web_api/app/users'
|