p4_web_api 2014.2.0.pre2 → 2014.2.0.pre4
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 +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'
|