require 'hws_settings' require 'p4_error' require 'uri' require 'rbconfig' module P4Util # Create a p4 connection using our Rack environment setup. # # This will seed options to a call to open() via attributes set on env: # # - `hws_settings`, which contains things like P4PORT # - `AUTH_CREDENTIALS`, which might be used if this is called after our Auth # middleware (or we've stored these credentials in a session, etc) def self.open_from_env(env) options = {} if env.key?('hws_settings') hws_settings = env['hws_settings'] port = nil if hws_settings.P4URL p4_url = URI(hws_settings.P4URL) options[:port] = "#{p4_url.host}:#{p4_url.port}" end if hws_settings.P4HOST options[:host] = hws_settings.P4HOST end if hws_settings.P4PORT # Apparently, the P4API will ports with 'rsh:' to mean "I'm going # to have a way to launch random things for you". It's great. And by # great I mean a another fantastic feature that makes no sense to anyone # who's not debugging a local client application. # # See netportparser.cc for how the P4API parses out the transport type. if hws_settings.P4PORT.downcase.start_with?('rsh:') || hws_settings.P4PORT.downcase.start_with?('jsh:') fail P4Error.new(0, 3, 'Do not use rsh: or jsh: P4PORT values') end options[:port] = hws_settings.P4PORT end if hws_settings.P4CHARSET options[:charset] = hws_settings.P4CHARSET end if hws_settings.P4APILEVEL options[:api_level] = Integer(hws_settings.P4APILEVEL) end if hws_settings.P4PASSWD options[:password] = hws_settings.P4PASSWD end end if env.key?('AUTH_CREDENTIALS') options[:user] = env['AUTH_CREDENTIALS'].first if !options.key?(:password) or options[:password].nil? options[:password] = env['AUTH_CREDENTIALS'].last end end options[:client] = 'INVALID' self.open(options) end # Creates your p4 connection using some common forms. # # If you call open with a block, this will call connect before your block # executes, and disconnect afterwards. # # If you do not call open with a block, it is up to the caller to connect # and disconnect. (It's assumed you are calling w/o a block because you want # to manage when the connection actually needs to happen.) def self.open(options = {}) p4 = create_p4(options) # Again, if we're calling using the block, we'll connect and disconnect. # Otherwise, just return the created p4 object. if block_given? begin p4.connect yield p4 rescue P4Exception => ex puts "trace:" ex.backtrace.each { |l| puts "\t#{l}" } raise make_p4_error(p4) end else return p4 end ensure p4.disconnect if block_given? && p4 && p4.connected? end PROPERTIES = [ :password, :port, :user, :api_level, :charset, :client, :host, :handler, :maxlocktime, :maxresults, :maxscanrows, :prog, :ticketfile ] def self.create_p4(options) p4 = P4.new init_p4(p4) PROPERTIES.each do|key| p4.method(key.to_s + '=').call(options[key]) if options[key] end # From the WTF department, if you think you don't want a charset set, # make damn sure P4Ruby isn't going to get confused. p4.charset = nil if p4.charset == 'none' # Make P4Ruby only raise exceptions if there are errors. Warnings # (such as 'no such file(s)' don't get the same treatment. p4.exception_level = P4::RAISE_ERRORS p4 end # Before we do anything, clear out any environment variables that may have # leaked in from your environment # # Some of our HTTP APIs (e.g, Git Fusion) *do* utilize environment setups # for a system user, that may be installed alongside this API. We want this # system to always explicitly state which configuration to use. (So, it's # not just a concern of our development environments.) def self.init_p4(p4) p4.client = 'invalid' p4.port = '' p4.host = '' p4.password = '' # Disable the use of ticket files. # It might happen that an admin may decide to use the p4 command line # client to log in using the same system user we're running web services # as. If we happen to point to that file (say, via defaults) this will # use the valid ticket in the file instead of what has been specified # via headers. if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ p4.ticket_file = 'nul' p4.enviro_file = 'nul' else p4.ticket_file = '/dev/null' p4.enviro_file = '/dev/null' end if ENV['P4TRUST'].nil? ENV['P4TRUST'] = HWSSettings.system.P4TRUST end end def self.make_p4_error(p4) if p4.messages && p4.messages.first m = p4.messages.first P4Error.new(m.msgid, m.severity, m.to_s) else P4Error.default_error($ERROR_INFO.to_s) end end def self.create_temp_client(p4) name = (0...8).map { (65 + rand(26)).chr }.join p4_root = init_temp_workspace_dir(name) init_temp_client(name, p4, p4_root) p4_root end # Generates a stream client based on an existing stream def self.create_temp_stream_client_duplicate(p4, src_client) name = (0...8).map { (65 + rand(26)).chr }.join p4_root = init_temp_workspace_dir(name) p4.client = name spec = p4.fetch_client spec._root = p4_root spec._client = name spec._stream = src_client._stream spec._view = nil p4.save_client(spec) p4_root end private def self.init_temp_workspace_dir(name) dir = File.join(HWSSettings.system.WORKSPACE_DIR, name) unless Dir.exist?(dir) FileUtils.mkpath(dir) FileUtils.chmod(0700, dir) end dir end # Creates a temporary "wide open" client def self.init_temp_client(name, p4, p4_root) p4.client = name spec = p4.fetch_client spec._root = p4_root spec._client = name # When there's one depot, the view generated maps //depot/... //view/... # instead of //depot/... //view/depot/... like it will with multiple depots. if (spec._view.length == 1) spec._view = ["//depot/... //#{name}/depot/..."] end # This will explode if another client is using this name (should be rare). # *Supposedly* it will automatically clean up too. p4.save_client(spec, '-x') end end