class Sauce::Connect

Constants

TIMEOUT

Attributes

access_key[RW]
error[R]
status[R]
username[RW]

Public Class Methods

connect!(*args) click to toggle source
# File lib/sauce/connect.rb, line 198
def self.connect!(*args)
  @connection = self.new(*args)
  @connection.connect
  @connection.wait_until_ready
  at_exit do
    @connection.disconnect
  end
end
ensure_connected(*args) click to toggle source
# File lib/sauce/connect.rb, line 207
def self.ensure_connected(*args)
  if @connection
    @connection.wait_until_ready
  else
    connect!(*args)
  end
end
new(options={}) click to toggle source
# File lib/sauce/connect.rb, line 14
def initialize(options={})
  @ready = false
  @status = "uninitialized"
  @error = nil

  extract_config options

  error_on_missing_creds
  error_on_missing_executable
end

Private Class Methods

cant_access_rest_api_message() click to toggle source
# File lib/sauce/connect.rb, line 244
    def self.cant_access_rest_api_message
      <<-ENDLINE
        Unable to connect to the Sauce REST API, which may interfere with
        Sauce Connect.

        This might be caused by a HTTP mocking framework like WebMock or 
        FakeWeb.  Check out 
        (https://github.com/saucelabs/sauce_ruby#network-mocking) 
        if you're using one.  Sauce Connect needs access to *.saucelabs.com,
        port 80 and port 443.
        
        You can disable network tests by setting :skip_connection_test to true in
        your Sauce.config block.
      ENDLINE
    end
plzGetSC4() click to toggle source
# File lib/sauce/connect.rb, line 260
    def self.plzGetSC4
      <<-ENDLINE
        Using Sauce Connect 3 has been deprecated.  Please set the :sauce_connect_4_executable
        option in your Sauce.config block to the path of an installation of
        Sauce Connect 4.

        You can download Sauce Connect 4 for free at
        http://docs.saucelabs.com/sauce_connect
      ENDLINE
    end
port_not_open_message() click to toggle source
# File lib/sauce/connect.rb, line 228
    def self.port_not_open_message
      <<-ENDLINE
        Unable to connect to port 443 on saucelabs.com, which may interfere with
        Sauce Connect.

        This might be caused by a HTTP mocking framework like WebMock or 
        FakeWeb.  Check out 
        (https://github.com/saucelabs/sauce_ruby#network-mocking) 
        if you're using one.  Sauce Connect needs access to *.saucelabs.com,
        port 80 and port 443.

        You can disable network tests by setting :skip_connection_test to true in
        your Sauce.config block.
      ENDLINE
    end

Public Instance Methods

cli_options() click to toggle source
# File lib/sauce/connect.rb, line 143
def cli_options
  cli_options = { readyfile: "sauce_connect.ready" }
  cli_options.merge!(@cli_options) if @cli_options
  cli_options
end
connect() click to toggle source
# File lib/sauce/connect.rb, line 88
def connect
  unless @skip_connection_test
    ensure_connection_is_possible
  end

  puts "[Sauce Connect is connecting to Sauce Labs...]"

  formatted_cli_options = array_of_formatted_cli_options_from_hash(cli_options)

  command_args = ['-u', @username, '-k', @access_key]
  command_args << formatted_cli_options

  command = "exec #{find_sauce_connect} #{command_args.join(' ')} 2>&1"

  unless @quiet
    string_arguments = formatted_cli_options.join(' ')
    puts "[Sauce Connect arguments: '#{string_arguments}' ]"
  end

  @pipe = IO.popen(command)

  @process_status = $?
  at_exit do
    Process.kill("INT", @pipe.pid)
    while @ready
      sleep 1
    end
  end

  Thread.new {
    while( (line = @pipe.gets) )
      if line =~ /remote tunnel VM is now: (.*)/
        @status = $1
      end
      if line =~/You may start your tests\./i
        @ready = true
      end
      if line =~ /- (Problem.*)$/
        @error = $1
        @quiet = false
      end
      if line =~ /== Missing requirements ==/
        @error = "Missing requirements"
        @quiet = false
      end
      if line =~/Invalid API_KEY provided/
        @error = "Invalid API_KEY provided"
        @quiet = false
      end
      $stderr.puts line unless @quiet
    end
    @ready = false
    }
end
disconnect() click to toggle source
# File lib/sauce/connect.rb, line 166
def disconnect
  if @ready
    Process.kill("INT", @pipe.pid)
    while @ready
      sleep 1
    end
  end
end
ensure_connection_is_possible() click to toggle source
# File lib/sauce/connect.rb, line 63
def ensure_connection_is_possible
  $stderr.puts "[Checking REST API is contactable...]" unless @quiet
  uri = URI("http://saucelabs.com/rest/v1/#{@username}/tunnels")
  
  response = Net::HTTP.start(uri.host, uri.port) do |http|
    request = Net::HTTP::Get.new uri.request_uri
    request.basic_auth(@username, @access_key)
    response = http.request request
  end

  unless response.kind_of? Net::HTTPOK
    $stderr.puts Sauce::Connect.cant_access_rest_api_message
    raise TunnelNotPossibleException, "Couldn't access REST API"
  end

  begin
    $stderr.puts "[Checking port 443 is open...]" unless @quiet
    socket = TCPSocket.new 'saucelabs.com', 443
  rescue SystemCallError => e
    raise e unless e.class.name.start_with? 'Errno::'
    $stderr.puts Sauce::Connect.port_not_open_message
    raise TunnelNotPossibleException, "Couldn't use port 443"
  end
end
error_on_missing_creds() click to toggle source
# File lib/sauce/connect.rb, line 47
def error_on_missing_creds
  if @username.nil?
    raise ArgumentError, "Username required to launch Sauce Connect. Please set the environment variable $SAUCE_USERNAME"
  end

  if @access_key.nil?
    raise ArgumentError, "Access key required to launch Sauce Connect. Please set the environment variable $SAUCE_ACCESS_KEY"
  end
end
error_on_missing_executable() click to toggle source
# File lib/sauce/connect.rb, line 57
def error_on_missing_executable
  if @sc4_executable.nil?
    raise TunnelNotPossibleException, Sauce::Connect.plzGetSC4
  end
end
extract_config(options) click to toggle source

extract options from the options hash with highest priority over Sauce.config but fall back on Sauce.config otherwise

# File lib/sauce/connect.rb, line 27
def extract_config options
  @username = options[:username]
  @access_key = options[:access_key]
  @cli_options = options[:connect_options]
  @sc4_executable = options[:sauce_connect_4_executable]
  @skip_connection_test = options[:skip_connection_test]
  @quiet = options[:quiet]
  @timeout = options.fetch(:timeout) { TIMEOUT }

  unless options.fetch(:skip_sauce_config, false)
    require 'sauce/config'
    @config = Sauce::Config.new(options)
    @username ||= @config.username
    @access_key ||= @config.access_key
    @cli_options ||= @config[:connect_options]
    @sc4_executable ||= @config[:sauce_connect_4_executable]
    @skip_connection_test = @config[:skip_connection_test]  
  end
end
find_sauce_connect() click to toggle source

Check whether the path, or it's bin/sc descendant, exists and is executable

# File lib/sauce/connect.rb, line 176
def find_sauce_connect
  paths = [@sc4_executable, File.join("#{@sc4_executable}", "bin", "sc")]

  sc_path = paths.find do |path|
    path_is_connect_executable? path
  end

  if sc_path.nil?
    raise TunnelNotPossibleException, "No executable found at #{sc_path}, or it can't be executed by #{Process.euid}"
  end

  return File.absolute_path sc_path
end
path_is_connect_executable?(path) click to toggle source
# File lib/sauce/connect.rb, line 190
def path_is_connect_executable? path
  absolute_path = File.absolute_path path
  return (File.exist? absolute_path) && (File.executable? absolute_path) && !(Dir.exist? absolute_path)
end
wait_until_ready() click to toggle source
# File lib/sauce/connect.rb, line 149
def wait_until_ready
  start = Time.now
  while !@ready and (Time.now-start) < @timeout and @error != "Missing requirements"
    sleep 0.5
  end

  if @error == "Missing requirements"
    raise "Missing requirements"
  end

  if !@ready
    error_message = "Sauce Connect failed to connect after #{@timeout} seconds"
    error_message << "\n(Using Sauce Connect at #{@sc4_executable}"
    raise error_message
  end
end

Private Instance Methods

array_of_formatted_cli_options_from_hash(hash) click to toggle source
# File lib/sauce/connect.rb, line 217
def array_of_formatted_cli_options_from_hash(hash)
  hash.collect do |key, value|
    opt_name = key.to_s.gsub("_", "-")
    if !value.nil?
      "--#{opt_name} #{value}"
    else
      "--#{opt_name}"
    end
  end
end