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
 
| 
         @@ -0,0 +1,73 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'p4_web_api/change_helper'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module P4WebAPI
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Add methods related to changelist resources.
         
     | 
| 
      
 5 
     | 
    
         
            +
              class App < Sinatra::Base
         
     | 
| 
      
 6 
     | 
    
         
            +
                # Convenience method to list changelist metadata with some common filtering
         
     | 
| 
      
 7 
     | 
    
         
            +
                # options.
         
     | 
| 
      
 8 
     | 
    
         
            +
                #
         
     | 
| 
      
 9 
     | 
    
         
            +
                # parameters:
         
     | 
| 
      
 10 
     | 
    
         
            +
                # - `max` = number
         
     | 
| 
      
 11 
     | 
    
         
            +
                # - `status` = pending|submitted|shelved
         
     | 
| 
      
 12 
     | 
    
         
            +
                # - `user` = perforce login
         
     | 
| 
      
 13 
     | 
    
         
            +
                # - `files` = pattern
         
     | 
| 
      
 14 
     | 
    
         
            +
                get '/v1/changes' do
         
     | 
| 
      
 15 
     | 
    
         
            +
                  max = params['max'] if params.key?('max')
         
     | 
| 
      
 16 
     | 
    
         
            +
                  status = params['status'] if params.key?('status')
         
     | 
| 
      
 17 
     | 
    
         
            +
                  user = params['user'] if params.key?('user')
         
     | 
| 
      
 18 
     | 
    
         
            +
                  files = params['files'] if params.key?('files')
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  results = nil
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  open_p4 do |p4|
         
     | 
| 
      
 23 
     | 
    
         
            +
                    args = ['changes']
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                    args.push('-m', max) if max
         
     | 
| 
      
 26 
     | 
    
         
            +
                    args.push('-s', status) if status
         
     | 
| 
      
 27 
     | 
    
         
            +
                    args.push('-u', user) if user
         
     | 
| 
      
 28 
     | 
    
         
            +
                    args.push(files) if files
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                    results = p4.run(*args)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  normalize_changes(results) if settings.normalize_output
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  results.to_json
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                # Create a new changelist with edits to multiple files.
         
     | 
| 
      
 39 
     | 
    
         
            +
                post '/v1/changes' do
         
     | 
| 
      
 40 
     | 
    
         
            +
                  description = params['Description'] || 'Edited files'
         
     | 
| 
      
 41 
     | 
    
         
            +
                  files = params['Files']
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  depot_paths = files.map { |f| f['DepotFile'] }
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  open_p4_temp_client(depot_paths) do |p4, client_root, client_name|
         
     | 
| 
      
 46 
     | 
    
         
            +
                    helper = ChangeHelper.new(p4: p4,
         
     | 
| 
      
 47 
     | 
    
         
            +
                                              client_root: client_root,
         
     | 
| 
      
 48 
     | 
    
         
            +
                                              client_name: client_name,
         
     | 
| 
      
 49 
     | 
    
         
            +
                                              description: description,
         
     | 
| 
      
 50 
     | 
    
         
            +
                                              files: files)
         
     | 
| 
      
 51 
     | 
    
         
            +
                    helper.call
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                # Uses describe to produce the list of opened files regardless of client.
         
     | 
| 
      
 56 
     | 
    
         
            +
                #
         
     | 
| 
      
 57 
     | 
    
         
            +
                # There is a little bit of an open question regarding performance. The
         
     | 
| 
      
 58 
     | 
    
         
            +
                # p4ruby usage here does *not* generate diffs of changes, which the base
         
     | 
| 
      
 59 
     | 
    
         
            +
                # command line seems to want to do. If the output accumulates a lot of RAM
         
     | 
| 
      
 60 
     | 
    
         
            +
                # for large diffs, we could be in trouble.
         
     | 
| 
      
 61 
     | 
    
         
            +
                get '/v1/changes/:change' do |change|
         
     | 
| 
      
 62 
     | 
    
         
            +
                  results = nil
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  open_p4 do |p4|
         
     | 
| 
      
 65 
     | 
    
         
            +
                    results = p4.run_describe('-S', change)
         
     | 
| 
      
 66 
     | 
    
         
            +
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                  normalize_describe(results) if settings.normalize_output
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                  results.to_json
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
              end
         
     | 
| 
      
 73 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,58 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'sinatra/base'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module P4WebAPI
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Methods for executing generic Perforce commands.
         
     | 
| 
      
 5 
     | 
    
         
            +
              #
         
     | 
| 
      
 6 
     | 
    
         
            +
              # These do not actually map to any particular 'resource', but are just a
         
     | 
| 
      
 7 
     | 
    
         
            +
              # bucket for random things you can try out.
         
     | 
| 
      
 8 
     | 
    
         
            +
              #
         
     | 
| 
      
 9 
     | 
    
         
            +
              # thj: I would prefer to remove these methods, and replace them with
         
     | 
| 
      
 10 
     | 
    
         
            +
              # 'resources' we would define for people.
         
     | 
| 
      
 11 
     | 
    
         
            +
              class App < Sinatra::Base
         
     | 
| 
      
 12 
     | 
    
         
            +
                get '/v1/commands/:cmd' do |cmd|
         
     | 
| 
      
 13 
     | 
    
         
            +
                  args = params.select { |k, _| k.start_with?('arg') }.map { |_, v| v }
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  if settings.run_get_blacklist.include?(cmd)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    halt 403, { MessageCode: 15_360,
         
     | 
| 
      
 17 
     | 
    
         
            +
                                MessageText: "#{cmd} not allowed in web api",
         
     | 
| 
      
 18 
     | 
    
         
            +
                                MessageSeverity: :ERROR }.to_json
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  results = nil
         
     | 
| 
      
 22 
     | 
    
         
            +
                  messages = nil
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  open_p4 do |p4|
         
     | 
| 
      
 25 
     | 
    
         
            +
                    results = p4.run(cmd, *args)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    messages = p4.messages
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  if messages && messages.length > 0
         
     | 
| 
      
 30 
     | 
    
         
            +
                    messages.map { |m| to_msg(m) }.to_json
         
     | 
| 
      
 31 
     | 
    
         
            +
                  elsif results
         
     | 
| 
      
 32 
     | 
    
         
            +
                    results.to_json
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                post '/v1/commands/:cmd' do |cmd|
         
     | 
| 
      
 37 
     | 
    
         
            +
                  args = params.select { |key, _| key.start_with?('arg') }.map { |_, x| x }
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  # Uses the same blacklist as GET which generally makes sense, since we'll
         
     | 
| 
      
 40 
     | 
    
         
            +
                  # only really be concerned about client workspace usage on this server.
         
     | 
| 
      
 41 
     | 
    
         
            +
                  if settings.run_get_blacklist.include?(cmd)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    halt 403, { MessageCode: 15_360,
         
     | 
| 
      
 43 
     | 
    
         
            +
                                MessageText: "#{cmd} not allowed in web api",
         
     | 
| 
      
 44 
     | 
    
         
            +
                                MessageSeverity: :ERROR }.to_json
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  messages = nil
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  open_p4 do |p4|
         
     | 
| 
      
 50 
     | 
    
         
            +
                    p4.input = filter_params(params)
         
     | 
| 
      
 51 
     | 
    
         
            +
                    p4.run(cmd, args)
         
     | 
| 
      
 52 
     | 
    
         
            +
                    messages = p4.messages
         
     | 
| 
      
 53 
     | 
    
         
            +
                  end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                  messages.map { |m| to_msg(m) }.to_json if messages
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
              end
         
     | 
| 
      
 58 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,166 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'base64'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'p4_web_api/change_helper'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'p4_web_api/p4_util'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'sinatra/base'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module P4WebAPI
         
     | 
| 
      
 7 
     | 
    
         
            +
              # Add 'file' methods
         
     | 
| 
      
 8 
     | 
    
         
            +
              #
         
     | 
| 
      
 9 
     | 
    
         
            +
              # 'files' are actually a combination of path metadata. There are three
         
     | 
| 
      
 10 
     | 
    
         
            +
              # major kinds of file resources: depots, dirs, and files. The true file
         
     | 
| 
      
 11 
     | 
    
         
            +
              # resources can be a combination of details, along with the file content.
         
     | 
| 
      
 12 
     | 
    
         
            +
              class App < Sinatra::Base
         
     | 
| 
      
 13 
     | 
    
         
            +
                # Special depots only variant to match no path
         
     | 
| 
      
 14 
     | 
    
         
            +
                get '/v1/files' do
         
     | 
| 
      
 15 
     | 
    
         
            +
                  results = nil
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  open_p4 do |p4|
         
     | 
| 
      
 18 
     | 
    
         
            +
                    results = p4.run_depots
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  normalize_depots(results) if settings.normalize_output
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  results.to_json
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                # General browsing variation.
         
     | 
| 
      
 27 
     | 
    
         
            +
                #
         
     | 
| 
      
 28 
     | 
    
         
            +
                # Since we want this to be able to fetch file content in the case you
         
     | 
| 
      
 29 
     | 
    
         
            +
                # specify a file, versus a directory listing, we actually execute the
         
     | 
| 
      
 30 
     | 
    
         
            +
                # 'p4 files' command on the file. If we get a single result back, we then
         
     | 
| 
      
 31 
     | 
    
         
            +
                # add the base64'd content. If the file does not exist, we treat it like
         
     | 
| 
      
 32 
     | 
    
         
            +
                # a directory request. Thus, you'll never really get a 404, just an empty
         
     | 
| 
      
 33 
     | 
    
         
            +
                # array.
         
     | 
| 
      
 34 
     | 
    
         
            +
                get '/v1/files/*' do
         
     | 
| 
      
 35 
     | 
    
         
            +
                  dirs = params[:splat].select { |x| !x.empty? }
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  results = nil
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  open_p4 do |p4|
         
     | 
| 
      
 40 
     | 
    
         
            +
                    if dirs.empty?
         
     | 
| 
      
 41 
     | 
    
         
            +
                      results = p4.run_depots
         
     | 
| 
      
 42 
     | 
    
         
            +
                      normalize_depots(results) if settings.normalize_output
         
     | 
| 
      
 43 
     | 
    
         
            +
                    else
         
     | 
| 
      
 44 
     | 
    
         
            +
                      file_selector = '//' + dirs.join('/')
         
     | 
| 
      
 45 
     | 
    
         
            +
                      files_results = nil
         
     | 
| 
      
 46 
     | 
    
         
            +
                      p4.at_exception_level(P4::RAISE_NONE) do
         
     | 
| 
      
 47 
     | 
    
         
            +
                        files_results = p4.run_files(file_selector)
         
     | 
| 
      
 48 
     | 
    
         
            +
                      end
         
     | 
| 
      
 49 
     | 
    
         
            +
                      files_results = [] unless files_results
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                      if files_results.length == 1 &&
         
     | 
| 
      
 52 
     | 
    
         
            +
                         files_results.first.key?('depotFile') &&
         
     | 
| 
      
 53 
     | 
    
         
            +
                         files_results.first['depotFile'] == file_selector
         
     | 
| 
      
 54 
     | 
    
         
            +
                        # Treat request like a single file GET
         
     | 
| 
      
 55 
     | 
    
         
            +
                        normalize_files(files_results) if settings.normalize_output
         
     | 
| 
      
 56 
     | 
    
         
            +
                        results = files_results[0]
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                        print_results = p4.run_print(file_selector)
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                        results['Content'] = Base64.encode64(print_results[1])
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                      else
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                        # Treat request like a directory list
         
     | 
| 
      
 65 
     | 
    
         
            +
                        selector = '//' + dirs.join('/') + '/*'
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                        files_results = p4.run_files('-e', selector)
         
     | 
| 
      
 68 
     | 
    
         
            +
                        normalize_files(files_results) if settings.normalize_output
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                        dirs_results = p4.run_dirs(selector)
         
     | 
| 
      
 71 
     | 
    
         
            +
                        normalize_dirs(dirs_results) if settings.normalize_output
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                        results = files_results + dirs_results
         
     | 
| 
      
 74 
     | 
    
         
            +
                      end
         
     | 
| 
      
 75 
     | 
    
         
            +
                    end
         
     | 
| 
      
 76 
     | 
    
         
            +
                  end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                  results.to_json
         
     | 
| 
      
 79 
     | 
    
         
            +
                end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                # File upload mechanism
         
     | 
| 
      
 82 
     | 
    
         
            +
                #
         
     | 
| 
      
 83 
     | 
    
         
            +
                # This allows for multi-file patching based on particular directory level.
         
     | 
| 
      
 84 
     | 
    
         
            +
                # Because sinatra doesn't really support JSON array bodies, this must be
         
     | 
| 
      
 85 
     | 
    
         
            +
                # specified via a 'Files' parameter. If that parameter exists, we consider
         
     | 
| 
      
 86 
     | 
    
         
            +
                # this to be a directory upload.
         
     | 
| 
      
 87 
     | 
    
         
            +
                #
         
     | 
| 
      
 88 
     | 
    
         
            +
                # If this is a directory upload, we expect the following parameters on each
         
     | 
| 
      
 89 
     | 
    
         
            +
                # array object:
         
     | 
| 
      
 90 
     | 
    
         
            +
                #
         
     | 
| 
      
 91 
     | 
    
         
            +
                # - 'Content' - The base64 content
         
     | 
| 
      
 92 
     | 
    
         
            +
                # - 'DepotFile' - the *relative* path from the main splat
         
     | 
| 
      
 93 
     | 
    
         
            +
                #
         
     | 
| 
      
 94 
     | 
    
         
            +
                # Otherwise, we mostly just care about the 'Content'
         
     | 
| 
      
 95 
     | 
    
         
            +
                # fields for single file uploads.
         
     | 
| 
      
 96 
     | 
    
         
            +
                #
         
     | 
| 
      
 97 
     | 
    
         
            +
                # In both cases, a 'Description' field can be used to indicate a release
         
     | 
| 
      
 98 
     | 
    
         
            +
                # message.
         
     | 
| 
      
 99 
     | 
    
         
            +
                patch '/v1/files/*' do
         
     | 
| 
      
 100 
     | 
    
         
            +
                  path_parts = params[:splat].select { |x| !x.empty? }
         
     | 
| 
      
 101 
     | 
    
         
            +
                  description = params['Description'] || 'Uploaded files'
         
     | 
| 
      
 102 
     | 
    
         
            +
                  is_dir = params.key?('Files')
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                  files = nil
         
     | 
| 
      
 105 
     | 
    
         
            +
                  if is_dir
         
     | 
| 
      
 106 
     | 
    
         
            +
                    # TODO: 'clean' the directory path, avoiding refs like '...'
         
     | 
| 
      
 107 
     | 
    
         
            +
                    dir_root = "//#{path_parts.join('/')}"
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                    files = params['Files'].map do |f|
         
     | 
| 
      
 110 
     | 
    
         
            +
                      {
         
     | 
| 
      
 111 
     | 
    
         
            +
                        'DepotFile' => "#{dir_root}/#{f['DepotFile']}",
         
     | 
| 
      
 112 
     | 
    
         
            +
                        'Content' => f['Content']
         
     | 
| 
      
 113 
     | 
    
         
            +
                      }
         
     | 
| 
      
 114 
     | 
    
         
            +
                    end
         
     | 
| 
      
 115 
     | 
    
         
            +
                  else
         
     | 
| 
      
 116 
     | 
    
         
            +
                    # TODO: 'sanitize' this file path
         
     | 
| 
      
 117 
     | 
    
         
            +
                    files = [
         
     | 
| 
      
 118 
     | 
    
         
            +
                      {
         
     | 
| 
      
 119 
     | 
    
         
            +
                        'DepotFile' => "//#{path_parts.join('/')}",
         
     | 
| 
      
 120 
     | 
    
         
            +
                        'Content' => params[:Content]
         
     | 
| 
      
 121 
     | 
    
         
            +
                      }
         
     | 
| 
      
 122 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 123 
     | 
    
         
            +
                  end
         
     | 
| 
      
 124 
     | 
    
         
            +
                  files.each { |f| f['Action'] = 'upload' }
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                  depot_paths = files.map { |f| f['DepotFile'] }
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                  open_p4_temp_client(depot_paths) do |p4, client_root, client_name|
         
     | 
| 
      
 129 
     | 
    
         
            +
                    helper = ChangeHelper.new(p4: p4,
         
     | 
| 
      
 130 
     | 
    
         
            +
                                              client_root: client_root,
         
     | 
| 
      
 131 
     | 
    
         
            +
                                              client_name: client_name,
         
     | 
| 
      
 132 
     | 
    
         
            +
                                              description: description,
         
     | 
| 
      
 133 
     | 
    
         
            +
                                              files: files)
         
     | 
| 
      
 134 
     | 
    
         
            +
                    helper.call
         
     | 
| 
      
 135 
     | 
    
         
            +
                  end
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                  ''
         
     | 
| 
      
 138 
     | 
    
         
            +
                end
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
                # Delete a single file.
         
     | 
| 
      
 141 
     | 
    
         
            +
                delete '/v1/files/*' do
         
     | 
| 
      
 142 
     | 
    
         
            +
                  description = params['Description'] || 'Deleting file'
         
     | 
| 
      
 143 
     | 
    
         
            +
                  path_parts = params[:splat].select { |x| !x.empty? }
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                  # TODO: 'sanitize' this file path?
         
     | 
| 
      
 146 
     | 
    
         
            +
                  file_path = "//#{path_parts.join('/')}"
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
                  open_p4_temp_client([file_path]) do |p4|
         
     | 
| 
      
 149 
     | 
    
         
            +
                    change_id = P4Util.init_changelist(p4, description)
         
     | 
| 
      
 150 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 151 
     | 
    
         
            +
                      p4.run_delete('-c', change_id, file_path)
         
     | 
| 
      
 152 
     | 
    
         
            +
                      p4.run_submit('-c', change_id)
         
     | 
| 
      
 153 
     | 
    
         
            +
                    rescue StandardError => ex
         
     | 
| 
      
 154 
     | 
    
         
            +
                      p4.at_exeception_level(P4::RAISE_NONE) do
         
     | 
| 
      
 155 
     | 
    
         
            +
                        p4.run_change('-d', '-f', change_id)
         
     | 
| 
      
 156 
     | 
    
         
            +
                        if P4Util.error?(p4)
         
     | 
| 
      
 157 
     | 
    
         
            +
                          puts "possible issues deleting change #{change_id}: " \
         
     | 
| 
      
 158 
     | 
    
         
            +
                                 "#{p4.messages}"
         
     | 
| 
      
 159 
     | 
    
         
            +
                        end
         
     | 
| 
      
 160 
     | 
    
         
            +
                      end
         
     | 
| 
      
 161 
     | 
    
         
            +
                      raise ex
         
     | 
| 
      
 162 
     | 
    
         
            +
                    end
         
     | 
| 
      
 163 
     | 
    
         
            +
                  end
         
     | 
| 
      
 164 
     | 
    
         
            +
                end
         
     | 
| 
      
 165 
     | 
    
         
            +
              end
         
     | 
| 
      
 166 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,33 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module P4WebAPI
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Methods to manipulate protections table.
         
     | 
| 
      
 3 
     | 
    
         
            +
              #
         
     | 
| 
      
 4 
     | 
    
         
            +
              # The 'protections' resource in our system is the complete list, so you don't
         
     | 
| 
      
 5 
     | 
    
         
            +
              # fetch or manipulate any single 'protection'. It's all or nothing.
         
     | 
| 
      
 6 
     | 
    
         
            +
              class App < Sinatra::Base
         
     | 
| 
      
 7 
     | 
    
         
            +
                # Just list all protections
         
     | 
| 
      
 8 
     | 
    
         
            +
                get '/v1/protections' do
         
     | 
| 
      
 9 
     | 
    
         
            +
                  protects = nil
         
     | 
| 
      
 10 
     | 
    
         
            +
                  open_p4 do |p4|
         
     | 
| 
      
 11 
     | 
    
         
            +
                    protects = p4.run_protect('-o').first
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  normalize_protections(results) if settings.normalize_output
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  protects.to_json
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                # Update protections
         
     | 
| 
      
 20 
     | 
    
         
            +
                put '/v1/protections' do
         
     | 
| 
      
 21 
     | 
    
         
            +
                  protects = {
         
     | 
| 
      
 22 
     | 
    
         
            +
                    'Protections' => params['Protections']
         
     | 
| 
      
 23 
     | 
    
         
            +
                  }
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  open_p4 do |p4|
         
     | 
| 
      
 26 
     | 
    
         
            +
                    p4.input = protects
         
     | 
| 
      
 27 
     | 
    
         
            +
                    p4.run_protect('-i')
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  ''
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,45 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'p4_web_api/auth'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module P4WebAPI
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Authentication methods
         
     | 
| 
      
 5 
     | 
    
         
            +
              # See also: https://confluence.perforce.com:8443/display/WS/Authentication+in+Web+Services
         
     | 
| 
      
 6 
     | 
    
         
            +
              class App < Sinatra::Base
         
     | 
| 
      
 7 
     | 
    
         
            +
                # Creates a sign-in session for the user.
         
     | 
| 
      
 8 
     | 
    
         
            +
                #
         
     | 
| 
      
 9 
     | 
    
         
            +
                # This session returns a token that should be used as the password in basic
         
     | 
| 
      
 10 
     | 
    
         
            +
                # authentication for the user later.
         
     | 
| 
      
 11 
     | 
    
         
            +
                post '/v1/sessions' do
         
     | 
| 
      
 12 
     | 
    
         
            +
                  user = params[:user]
         
     | 
| 
      
 13 
     | 
    
         
            +
                  password = params[:password] # may be a p4 ticket
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  token = nil
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  options = {
         
     | 
| 
      
 18 
     | 
    
         
            +
                    user: user,
         
     | 
| 
      
 19 
     | 
    
         
            +
                    password: password,
         
     | 
| 
      
 20 
     | 
    
         
            +
                    host: settings.p4['host'],
         
     | 
| 
      
 21 
     | 
    
         
            +
                    port: settings.p4['port']
         
     | 
| 
      
 22 
     | 
    
         
            +
                  }
         
     | 
| 
      
 23 
     | 
    
         
            +
                  options[:charset] = settings.p4['charset'] if settings.p4.key?('charset')
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  P4Util.open(options) do |p4|
         
     | 
| 
      
 26 
     | 
    
         
            +
                    token = Auth.create_session(p4, password, settings)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  # We signal a 401 when login/passwords are generally invalid. Since this
         
     | 
| 
      
 30 
     | 
    
         
            +
                  # is an unauthenticated request, you shouldn't be able to tell if the
         
     | 
| 
      
 31 
     | 
    
         
            +
                  # login doesn't exist or the password is incorrect.
         
     | 
| 
      
 32 
     | 
    
         
            +
                  halt 401 unless token
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  content_type 'text/plain'
         
     | 
| 
      
 35 
     | 
    
         
            +
                  return token
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                # I'm not sure if we should ensure the token being deleted is the token
         
     | 
| 
      
 39 
     | 
    
         
            +
                # being authenticated against. That's not being checked for the time being.
         
     | 
| 
      
 40 
     | 
    
         
            +
                delete '/v1/sessions/:token' do |token|
         
     | 
| 
      
 41 
     | 
    
         
            +
                  P4WebAPI::Auth.delete_session(token, settings)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  ''
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
              end
         
     | 
| 
      
 45 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,114 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module P4WebAPI
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Generic CRUD behavior for most of our spec types.
         
     | 
| 
      
 3 
     | 
    
         
            +
              class App < Sinatra::Base
         
     | 
| 
      
 4 
     | 
    
         
            +
                set(:is_spec) do |_x|
         
     | 
| 
      
 5 
     | 
    
         
            +
                  condition do
         
     | 
| 
      
 6 
     | 
    
         
            +
                    path_info = env['PATH_INFO']
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                    matches = %r{^/v1/(?<spec_type>\w+)}.match(path_info)
         
     | 
| 
      
 9 
     | 
    
         
            +
                    if matches
         
     | 
| 
      
 10 
     | 
    
         
            +
                      spec_type = matches[:spec_type]
         
     | 
| 
      
 11 
     | 
    
         
            +
                      return (spec_type == 'branches' ||
         
     | 
| 
      
 12 
     | 
    
         
            +
                        spec_type == 'clients' ||
         
     | 
| 
      
 13 
     | 
    
         
            +
                        spec_type == 'depots' ||
         
     | 
| 
      
 14 
     | 
    
         
            +
                        spec_type == 'groups' ||
         
     | 
| 
      
 15 
     | 
    
         
            +
                        spec_type == 'jobs' ||
         
     | 
| 
      
 16 
     | 
    
         
            +
                        spec_type == 'labels' ||
         
     | 
| 
      
 17 
     | 
    
         
            +
                        spec_type == 'servers'
         
     | 
| 
      
 18 
     | 
    
         
            +
                      )
         
     | 
| 
      
 19 
     | 
    
         
            +
                    end
         
     | 
| 
      
 20 
     | 
    
         
            +
                    false
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                # Provide a generic collection accessor for each of the specs.
         
     | 
| 
      
 25 
     | 
    
         
            +
                get '/v1/:spec_type', is_spec: true do |spec_type|
         
     | 
| 
      
 26 
     | 
    
         
            +
                  results = nil
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  open_p4 do |p4|
         
     | 
| 
      
 29 
     | 
    
         
            +
                    results = p4.run(spec_type)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  send("normalize_#{spec_type}", results) if settings.normalize_output
         
     | 
| 
      
 33 
     | 
    
         
            +
                  P4Util.collate_group_results(results) if settings.normalize_output &&
         
     | 
| 
      
 34 
     | 
    
         
            +
                                                           spec_type == 'groups'
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  results.to_json
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                # Provide a generic output accessor for each spec
         
     | 
| 
      
 40 
     | 
    
         
            +
                get '/v1/:spec_type/:id', is_spec: true do |spec_type, id|
         
     | 
| 
      
 41 
     | 
    
         
            +
                  results = nil
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  open_p4 do |p4|
         
     | 
| 
      
 44 
     | 
    
         
            +
                    results = p4.run(P4Util.singular(spec_type), '-o', id)
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  send("normalize_#{spec_type}", results) if settings.normalize_output
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  results[0].to_json
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                # This is our generic "add" mechanism for each type.
         
     | 
| 
      
 53 
     | 
    
         
            +
                #
         
     | 
| 
      
 54 
     | 
    
         
            +
                # It's assumed that the client understands the requirements of each spec
         
     | 
| 
      
 55 
     | 
    
         
            +
                # type.
         
     | 
| 
      
 56 
     | 
    
         
            +
                post '/v1/:spec_type', is_spec: true do |spec_type|
         
     | 
| 
      
 57 
     | 
    
         
            +
                  results = nil
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  open_p4 do |p4|
         
     | 
| 
      
 60 
     | 
    
         
            +
                    method_name = "save_#{P4Util.singular(spec_type)}".to_sym
         
     | 
| 
      
 61 
     | 
    
         
            +
                    results = p4.send(method_name, params)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  # In general, the params use the name of the spec as a capitalized
         
     | 
| 
      
 65 
     | 
    
         
            +
                  # parameter in the singular form.
         
     | 
| 
      
 66 
     | 
    
         
            +
                  if spec_type == 'servers'
         
     | 
| 
      
 67 
     | 
    
         
            +
                    id_prop = 'ServerID'
         
     | 
| 
      
 68 
     | 
    
         
            +
                  else
         
     | 
| 
      
 69 
     | 
    
         
            +
                    id_prop = P4Util.singular(spec_type).capitalize
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
                  id = nil
         
     | 
| 
      
 72 
     | 
    
         
            +
                  if results.is_a?(Array) &&
         
     | 
| 
      
 73 
     | 
    
         
            +
                     results.length > 0 &&
         
     | 
| 
      
 74 
     | 
    
         
            +
                     /Job .* saved/.match(results[0])
         
     | 
| 
      
 75 
     | 
    
         
            +
                    # special "Job" variant to grab the ID out of the results output
         
     | 
| 
      
 76 
     | 
    
         
            +
                    id = /Job (.*) saved/.match(results[0])[1]
         
     | 
| 
      
 77 
     | 
    
         
            +
                  elsif params.key?(id_prop)
         
     | 
| 
      
 78 
     | 
    
         
            +
                    id = params[id_prop]
         
     | 
| 
      
 79 
     | 
    
         
            +
                  elsif params.key?(id_prop.to_sym)
         
     | 
| 
      
 80 
     | 
    
         
            +
                    id = params[id_prop.to_sym]
         
     | 
| 
      
 81 
     | 
    
         
            +
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                  if id
         
     | 
| 
      
 84 
     | 
    
         
            +
                    redirect "/v1/#{spec_type}/#{id}"
         
     | 
| 
      
 85 
     | 
    
         
            +
                  else
         
     | 
| 
      
 86 
     | 
    
         
            +
                    halt 400, "Did not locate #{id_prop} in params"
         
     | 
| 
      
 87 
     | 
    
         
            +
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
                end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                # An 'update' mechanism for each spec type.
         
     | 
| 
      
 91 
     | 
    
         
            +
                patch '/v1/:spec_type/:id', is_spec: true do |spec_type, id|
         
     | 
| 
      
 92 
     | 
    
         
            +
                  open_p4 do |p4|
         
     | 
| 
      
 93 
     | 
    
         
            +
                    singular = P4Util.singular(spec_type)
         
     | 
| 
      
 94 
     | 
    
         
            +
                    spec = p4.run(singular, '-o', id)[0]
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                    spec = spec.merge(filter_params(params))
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                    method_name = "save_#{singular}".to_sym
         
     | 
| 
      
 99 
     | 
    
         
            +
                    p4.send(method_name, spec)
         
     | 
| 
      
 100 
     | 
    
         
            +
                  end
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                  ''
         
     | 
| 
      
 103 
     | 
    
         
            +
                end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                delete '/v1/:spec_type/:id', is_spec: true do |spec_type, id|
         
     | 
| 
      
 106 
     | 
    
         
            +
                  open_p4 do |p4|
         
     | 
| 
      
 107 
     | 
    
         
            +
                    method_name = "delete_#{P4Util.singular(spec_type)}".to_sym
         
     | 
| 
      
 108 
     | 
    
         
            +
                    p4.send(method_name, id)
         
     | 
| 
      
 109 
     | 
    
         
            +
                  end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                  ''
         
     | 
| 
      
 112 
     | 
    
         
            +
                end
         
     | 
| 
      
 113 
     | 
    
         
            +
              end
         
     | 
| 
      
 114 
     | 
    
         
            +
            end
         
     |