# Copyright (c) 2014 Perforce Software, Inc. All rights reserved. # vim:ts=2:sw=2:et:si:ai: # p4_web_api.rb # # Setup # ----- # # Avast, your webserver should map /p4 to the root of this application. This # is easily done in rails with the following route: # # mount P4WebAPI, at: '/p4' # # You will need a p4_web_api.yml configuration file created as well, to indicate # where security token data is stored, and where the Perforce server lies. require 'sinatra/base' require 'sinatra/json' require 'sinatra/config_file' require 'rack/parser' require 'p4_web_services_auth' require 'p4_web_api/helpers' require 'p4_web_api/p4_util' require 'p4_web_api/version' # The P4WebAPI is our namespace for the web application. # # See P4WebAPI::App for most of the implemented methods. In general each method # maps to a P4Ruby call. # # The other modules under the P4WebAPI are generally helpers, like the # Authentication middleware, or P4Util conventions around using P4Ruby. module P4WebAPI # This web application is mostly a lightweight way of connecting the tagged # output via P4Ruby to JSON clients can consume relatively simply. class App < Sinatra::Base register Sinatra::ConfigFile # Inject Rack::Parser into the middleware stack so that it # automatically parses json bodies in post requests into the params # array. use Rack::Parser, content_types: { 'application/json' => proc { |body| ::MultiJson.decode body } } use P4WebServicesAuth::AuthMiddleware, settings: settings # Without this set, the return content type is always text/html before do content_type 'application/json' end configure do set(:p4, 'host' => 'localhost', 'port' => '1666') set(:token_path, '/tmp/p4_web_api/tokens') set(:workspace_folder, '/tmp/p4_web_api/workspaces') set(:run_get_blacklist, %w(add change changelist clean copy cstat delete diff edit flush have integ integrate lock login logout move reconcile rename reopen resolve resolved revert shelve submit sync unlock unshelve where )) # To allow other rack middleware to decide P4_HOST and P4_PORT, # allow_env_p4_config must be true, otherwise, we'll only use the standard # rack configuration mechanism. set(:allow_env_p4_config, false) # This is odd, but with some of the project restructuring, # this setting marked error.rb as the 'app_file' set(:app_file, __FILE__) # When set to true, we'll run most of the output through a 'normalization' # set of rules. We don't do this for the run methods, but most fields # should appear capitalized, and output dates should all show up in the # standard epoch timestamp set(:normalize_output, true) set :raise_errors, :environment == :test set :dump_errors, :environment == :development set :show_exceptions, :environment == :development enable :logging config_path = if ENV.key?('P4_WEB_API_CONFIG') ENV['P4_WEB_API_CONFIG'] else 'p4_web_api.yml' end config_file config_path if File.exist?(config_path) end # TODO we should only run this on the first request. P4WebServicesAuth::Auth.validate_token_dir(settings.token_path) error do err = env['sinatra.error'] puts "err #{err}" if err.is_a?(P4Exception) # Can happen when we're not passing a block to # open_p4. Convert to a P4WebAPI error. This is not ideal # as it always uses the same code, but then we have no idea # what actually happened here. err = P4WebAPI::P4Error.default_error(err.to_s) # Fall through... end if err.is_a?(P4WebAPI::P4Error) if err.message_code == 7480 || err.message_code == 7189 halt 401 else return { MessageCode: err.message_code, MessageSeverity: err.message_severity, MessageText: err.message_text }.to_json end end fail err end not_found do return { # This is not an application error, so I'm using error code 0 to mean # 'use HTTP status' MessageCode: 0, MessageSeverity: 3, MessageText: 'Resource not found' }.to_json end def to_msg(message) { MessageCode: message.msgid, MessageSeverity: message.severity, MessageText: message.to_s } end helpers Helpers # Will fetch the system offset based on the server date. # # If allow_env_p4_config is true, we don't cache. Each request could come # in from a new server, and the different servers may have different # offsets. def offset if settings.allow_env_p4_config fetch_offset else @offset ||= fetch_offset end end def fetch_offset offset = nil open_p4 do |p4| results = p4.run_info offset = P4Util.p4_date_offset(results[0]['serverDate']) end offset end @normalizers = {} class << self attr_accessor :normalizers end # It's assumed that these are typically used to find the different spec # types within typical requests. def method_missing(method, *args) return unless method.to_s =~ /^normalize_(.*)/ spec_type = Regexp.last_match[1] unless self.class.normalizers.key?(spec_type) self.class.normalizers[spec_type] = P4Util.normalizer(spec_type, offset) end self.class.normalizers[spec_type].call(*args) end # Basically a "blacklist" of things we know the frameworks going to add to # the params array we don't want to pass on to the p4 command sets for spec # input def filter_params(params) params.select do |k, _v| k != 'spec_type' && k != 'id' && k != 'splat' && k != 'captures' end end run! if app_file == $PROGRAM_NAME end end # Reopen up the P4WebAPI::App class and add most of our method handling. This # is done in lieu of having multiple Sinatra apps, so we can have the same # configuration and error handling. require 'p4_web_api/app/changes' require 'p4_web_api/app/commands' require 'p4_web_api/app/files' require 'p4_web_api/app/protections' require 'p4_web_api/app/specs' require 'p4_web_api/app/streams' require 'p4_web_api/app/triggers' require 'p4_web_api/app/users'
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#5 | 13972 | tjuricek |
Removing old microservice implementations. The system is now mostly a monolith. Eventually there will be a websocket service. |
||
#4 | 13528 | tjuricek |
Moved rack and app server configuration to be managed via Salt. Also, only using a single value "url" to configure how the p4_project_services instance references the p4_web_api. And, removing the Docker setup, since that won't work for a production system. |
||
#3 | 13481 | tjuricek |
Tests for the p4 web api and p4 project services now pass against a development setup both in and out of the docker cluster. Note that configuration *has not* been finalized, so conventions to dealing with development vs production need to be organized a bit. |
||
#2 | 13439 | tjuricek |
Added a *very trivial* logIn implementation. This just makes the *current* POST request to /p4_phoenix_services/v1/sessions, which *does not* include the ability to create a host locked P4 ticket for the user. So it's not quite usable yet. But, I've made several tweaks to the API which should be stablizing. |
||
#1 | 13412 | tjuricek |
Initial version of the web-services mainline. This is a collection of several projects, that will likely often get released together, though many of them may not always be relevant. See the README for more information. |