require 'errors' require 'faraday' require 'faraday_middleware' require 'uri' require 'helix_web_services_client/version' require 'json' # The client object handles authenticating and making calls to Helix Web Services. # # See our user guide online at: # https://swarm.workshop.perforce.com/projects/perforce-software-helix-web-services/view/main/build/doc/p4ws.html#ruby_client_sdk_overview class HelixWebServicesClient # The Helix Versioning Engine login attr_accessor :user # Upon successful login, we store the P4 ticket return value here. attr_accessor :ticket # Typically, Helix Web Services is mounted under /hws behind a reverse proxy. # If a path is specified in the originating URL, we save the prefix here, # and preprend it to every request. attr_accessor :prefix # The API level to use. Defaults to 78 (2015.1) attr_accessor :api_level # Some values to initialize are only used by this class, and are not passed # on to the Faraday initializer INITIALIZE_LOCAL_OPTIONS = [:user, :password, :ticket, :prefix, :api_level, :settings, :debug] # Client initialization can handle ensuring a valid security token to the # server. # # Any client created via new should take care to call `close()`. # # ## Available Options # # These options are used to configure the underlying Faraday connection: # # * `:url` - String base URL. # * `:params` - Hash of URI query unencoded key/value pairs. # * `:header` - Hash of unencoded HTTP header key/value pairs. # * `:request` - Hash of request options. # * `:ssl` - Hash of SSL options. # * `:proxy` - Hash of Proxy options. # # These options are specific to Helix Web Services: # # * `:user` - The Helix Versioning Engine login # * `:password` - If set, we will generate a ticket using this password during initialization # * `:ticket` - If not nil, we will use this ticket as our authentication password # * `:debug` - Add response logging if set to true # # @param options [Hash] See the section available options above def initialize(options) @api_level = options.key?(:api_level) ? options[:api_level] : '78' # Filter out options we pass to Faraday faraday_options = options.select { |k| !INITIALIZE_LOCAL_OPTIONS.include?(k) } @conn = Faraday.new(faraday_options) do |conn| conn.request :multipart conn.request :url_encoded conn.response :logger if options[:debug] conn.adapter :net_http end if options.key?(:url) url = URI(options[:url]) @prefix = url.path ? url.path : '' end @user = options[:user] if options.key?(:user) @ticket = options[:ticket] if options.key?(:ticket) if options.key?(:settings) options[:settings].each do |key, value| add_setting(key, value) end end if options.key?(:password) and @user response = @conn.post(path_for('/auth/v1/login'), user: user, password: options[:password]) assert_ok(response) @ticket = response.body end set_auth(user, ticket) unless ticket.nil? end def close end # Set an override for all requests. # # See "configuration" in HWS documentation. # # This will automatically add the right prefix for overriding an HWS setting. # # @param key {String} The setting value as indicated in documentation, e.g., `P4PORT` # @param value {String} The value to set def add_setting(key, value) # Note: Rack will automatically convert all hyphens to underscores... # but Nginx will (by default) block all underscores. Find a happy middle. @conn.headers["X-Perforce-Helix-Web-Services-#{key.gsub('_', '-')}"] = value end # Remove a setting added via add_setting def remove_setting(key) @conn.headers.delete("X-Perforce-Helix-Web-Services-#{key}") end # Provides standard I/O style interface, where when called in a block, # will automatically close() the client when done. Otherwise, your code # should call client.close() manually. def self.open(connection) client = Client.new(connection) if block_given? yield client else return client end ensure client.close if block_given? && client end # Note: this class is really just common implementation. Methods are # generally defined in other files that reopen this class. def set_auth(user, token) @conn.basic_auth(user, token) end def p4_ticket?(str) /^[a-zA-Z0-9]{32,}$/.match(str) != nil end # Runs the method against Helix Web Services, checks for errors, then parses # the JSON response. # # @param method [Symbol] HTTP method, for example, :get, :post, :delete # @param path [String] URL path part (no URI parameters) for the method # @param params [Hash] URI parameters to send in def execute_method_no_body(method, path, params = nil) response = run_method_no_body(method, path, params) JSON.parse(response.body) if response.body && !response.body.empty? end # Runs the method against Helix Web Services, checks for errors, then parses # the JSON response. # # This variation will send the body (expected to be a Hash) as JSON to the # server. # # @param method [Symbol] HTTP method, for example, :get, :post, :delete # @param path [String] URL path part (no URI parameters) for the method # @param params [Hash] URI parameters to send in # @param body [Hash] The Request content (which will be converted to JSON) def execute_method_with_body(method, path, params = nil, body = nil) response = run_method_with_body(method, path, params, body) JSON.parse(response.body) if response.body && !response.body.empty? end def run_method_no_body(method, path, params = nil) path = path_for(path) response = @conn.send(method, path, params) assert_ok(response) response end def run_method_with_body(method, path, params = nil, body = nil) if !body && params body = params params = nil end path = path_for(path) if params params_hash = Faraday::Utils::ParamsHash.new params_hash.merge!(params) path += "?#{params_hash.to_query}" end response = @conn.send(method, path, body) assert_ok(response) response end # Basically just prepends the prefix to our subpath, typically, '/p4'. def path_for(subpath) if @prefix.nil? || @prefix.empty? subpath elsif subpath.nil? or subpath.empty? @prefix else File.join(@prefix, subpath) end end # Raises an error when the response is not 200. Some errors may have # diagnostic information in the response body, so we pass that on as well def assert_ok(response) return unless response.status >= 400 if response.status == 400 begin messages = JSON.parse(response.body) rescue Exception messages = response.body end fail Errors::BadRequest.new(messages) elsif response.status == 403 fail Errors::Unauthenticated.new, 'Illegal login or password' elsif response.status == 404 fail Errors::ResourceNotFound.new, 'Required resource not found' elsif response.status == 500 && response.body messages = nil begin messages = JSON.parse(response.body) rescue Exception => e messages = response.body end fail Errors::PerforceProblem.new(messages), 'Unknown issue from the Perforce server' else fail Errors::ServerError.new, "Unknown problem. Response code: #{response.status}" end end def hve_path(subpath) "/helix_versioning_engine/v#{api_level}/#{subpath}" end # Return the product version ID of the Helix Web Services instance def version response = run_method_no_body(:get, '/status') response.headers['X-Helix-Web-Services-Version'] end end require 'helix_web_services_client/branches' require 'helix_web_services_client/changes' require 'helix_web_services_client/clients' require 'helix_web_services_client/commands' require 'helix_web_services_client/config' require 'helix_web_services_client/counters' require 'helix_web_services_client/depots' require 'helix_web_services_client/files' require 'helix_web_services_client/groups' require 'helix_web_services_client/helix_sync' require 'helix_web_services_client/jobs' require 'helix_web_services_client/labels' require 'helix_web_services_client/login' require 'helix_web_services_client/projects' require 'helix_web_services_client/protections' require 'helix_web_services_client/servers' require 'helix_web_services_client/streams' require 'helix_web_services_client/triggers' require 'helix_web_services_client/users' require 'helix_web_services_client/git_fusion_repo' require 'helix_web_services_client/git_fusion_keys' require_relative '../../helix_web_services/lib/hws_strings'
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#3 | 15745 | ptomiak |
Second part of shelved changes. Contains updated git-fusion app, changes to string encodig/decoding and updated docs. |
||
#2 | 15744 | ptomiak | Unshelve files from review 15549 to my dev branch. | ||
#1 | 15741 | ptomiak | Branch HWS for my use. | ||
//guest/perforce_software/helix-web-services/main/source/helix_web_services_client/lib/helix_web_services_client.rb | |||||
#1 | 15622 | tjuricek |
Move source code to 'source/' subdirectory of branch. build/ will remain where it is. |
||
//guest/perforce_software/helix-web-services/main/helix_web_services_client/lib/helix_web_services_client.rb | |||||
#28 | 15617 | tjuricek | Add P4PORT variable to 'remotetest' task, add 'debug' option to client. | ||
#27 | 15479 | tjuricek | Added a basic "HVE project" implementation for creating clients. | ||
#26 | 15461 | tjuricek |
Allow the settings to be specified on initialization of the client, and deal with Nginx issues and header keys. Nginx, by default, blocks all headers with underscores. Rack, on the other hand, convert hyphens to underscores. |
||
#25 | 15437 | tjuricek |
Basic "HVE Project" implementation. This will allow directories in a HVE instance to host 'projects' for use by helix sync. There are no methods defined for creating the projects, however. This does not include any specialization or testing in the Qt API yet. I may investigate creating a "higher level" interface for Qt client apps. |
||
#24 | 15297 | tjuricek |
Implement of 'cluster services' configuration. The configuration will be stored in a local JSON file, which is expected to be maintained by the systems admin. Eventually, it's expected to have this sort of thing implemented via Helix Admin. |
||
#23 | 15240 | tjuricek |
Set api level via request path on all Helix Versioning Engine methods. This will allow migration of applications to different P4D versions. Our internal methods (like project API) should attempt to handle backward compatibility similarly. P4WEBAPI-118 |
||
#22 | 15228 | tjuricek | Revise triggers implementation, tests, and documentation. | ||
#21 | 15227 | tjuricek |
Revise implementation, tests, and documentation for protections management. Remove some specs I will not be revising from the helix_web_services project. |
||
#20 | 15225 | tjuricek |
Revise counter implementation, tests, and documentation Wasn't available in the Ruby client before, so, it's now available. |
||
#19 | 15222 | tjuricek |
Revise server specs testing and documentation. Note: also fixed issues with setting P4PORT via headers. For whatever reason, the host setting doesn't seem to work like I expect it to, though P4PORT works just fine. |
||
#18 | 15211 | tjuricek | Implement tests and documentation for label spec management. | ||
#17 | 15210 | tjuricek | Implement tests and documentation for job spec management. | ||
#16 | 15209 | tjuricek | Implement tests and documentation for group spec management. | ||
#15 | 15208 | tjuricek |
Revise 'command' implementation, tests, and documentaiton. This includes a change from a command blacklist to a whitelist. See P4WEBAPI-21 |
||
#14 | 15205 | tjuricek | Implemented tests and documentation for depot spec editing. | ||
#13 | 15185 | tjuricek | Update user spec management implementation, tests, and documentation. | ||
#12 | 15132 | tjuricek | Provde a basic submit -e mechanism on classic perforce workspaces. | ||
#11 | 15110 | tjuricek | Revise changes methods for new p4 connection handling, add server specs, remove model references in client, and update asciidoc documentation. | ||
#10 | 15090 | tjuricek |
Update _proposed_ API for project services. This is *very likely* to change, and will not be implemented until reviewed. |
||
#9 | 15078 | tjuricek |
clients spec method revisions Updated some other documentation. |
||
#8 | 15077 | tjuricek |
Add new 'model' technique, revised branch spec operations, test Auth::Middleware. The Ruby client now does *not* strictly type anything, but extends OpenStruct with helper methods to help deal with inconsistent data formats. See the OpenModel class documentation for more details. The Auth::Middleware class is also *finally* implemented as well. This does not take into account all possible variations of server behavior (yet), but that will happen in follow-up work. |
||
#7 | 15059 | tjuricek | Tested authentication of the core /auth/v1/login method. | ||
#6 | 15053 | tjuricek |
Revise the client API to use the new login method. The current specs will need to be revised since data normalization is moving out of the server and into the client. |
||
#5 | 15038 | tjuricek | Document 'login' auth method and client programming overview. | ||
#4 | 13941 | tjuricek |
Re-implemented the sync project methods at the HTTP level. The Qt API is missing the "members" concept, but it's likely not quite usable just yet. It's existing logic does work, however. |
||
#3 | 13839 | tjuricek |
Conversion of the p4_project_service microservice to new monolithic system. This may not have an HTTP front end in the monolithic system. Project services are really just about how the core object model is structured. It's likely that each application will add their own wrinkles and extensions to the system, so it's unlikely we'll need a generic "project model". Exactly how extensions are registered and used is still a bit TBD at the moment. Previously they were to be registered webhooks, that model may change. Does not include tests yet. |
||
#2 | 13808 | tjuricek | Finish converting most of the core p4d API into the new monolithic system. | ||
#1 | 13799 | tjuricek |
Start with branch specs hosting in a new monolithic 'helix web services' project. Converting from a microservice to a monolithic architecture due to resource constraints at getting a deployable system running. Additionally, since it's not expected that people will upgrade often, the major benefit of microservices - being able to add services individually without affecting others - is not really a major benefit. The Ruby SDK will be consolidated into a single 'helix web services client' project. It may end up being distributed via Rubygems. This only runs branch specs at the moment. I want to get a CD pipeline setup for the monolithic server before revising more methods. |