class Sauce::Connect

Constants

TIMEOUT

Attributes

error[R]
status[R]

Public Class Methods

connect!(*args) click to toggle source
# File lib/sauce/connect.rb, line 173
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 182
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
  @quiet = options[:quiet]
  @timeout = options.fetch(:timeout) { TIMEOUT }
  @config = Sauce::Config.new(options)
  @skip_connection_test = @config[:skip_connection_test]
  @cli_options = @config[:connect_options]
  @sc4_executable = @config[:sauce_connect_4_executable]

  if @config.username.nil?
    raise ArgumentError, "Username required to launch Sauce Connect. Please set the environment variable $SAUCE_USERNAME"
  end

  if @config.access_key.nil?
    raise ArgumentError, "Access key required to launch Sauce Connect. Please set the environment variable $SAUCE_ACCESS_KEY"
  end

  if @sc4_executable.nil?
    raise TunnelNotPossibleException, Sauce::Connect.plzGetSC4
  end
end

Private Class Methods

cant_access_rest_api_message() click to toggle source
# File lib/sauce/connect.rb, line 215
    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 231
    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 199
    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 118
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 63
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', @config.username, '-k', @config.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 =~ /Tunnel remote VM is (.*) (\.\.|at)/
        @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 141
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 38
def ensure_connection_is_possible
  $stderr.puts "[Checking REST API is contactable...]" unless @quiet
  uri = URI("http://saucelabs.com/rest/v1/#{@config[:username]}/tunnels")
  
  response = Net::HTTP.start(uri.host, uri.port) do |http|
    request = Net::HTTP::Get.new uri.request_uri
    request.basic_auth(@config[:username], @config[: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
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 151
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 165
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 124
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 192
def array_of_formatted_cli_options_from_hash(hash)
  hash.collect do |key, value|
    opt_name = key.to_s.gsub("_", "-")
    "--#{opt_name} #{value}"
  end
end