sauce 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/lib/rest.rb +5 -1
- data/lib/sauce.rb +2 -1
- data/lib/tunnel.rb +141 -15
- data/test/debug.rb +26 -3
- data/test/irb_boot.rb +7 -0
- data/test/test_sauce.rb +9 -0
- metadata +3 -2
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.1
|
data/lib/rest.rb
CHANGED
@@ -7,12 +7,13 @@ module Sauce
|
|
7
7
|
class BadAccessError < StandardError; end #nodoc
|
8
8
|
class MisconfiguredError < StandardError; end #nodoc
|
9
9
|
|
10
|
-
attr_accessor :username, :access_key, :client, :api_url
|
10
|
+
attr_accessor :username, :access_key, :client, :ip, :api_url
|
11
11
|
attr_accessor :tunnels
|
12
12
|
|
13
13
|
def initialize(options)
|
14
14
|
@username = options[:username]
|
15
15
|
@access_key = options[:access_key]
|
16
|
+
@ip = options[:ip]
|
16
17
|
|
17
18
|
raise MisconfiguredError if @username.nil? or @access_key.nil?
|
18
19
|
@api_url = "https://#{@username}:#{@access_key}@saucelabs.com/rest/#{@username}/"
|
@@ -20,6 +21,9 @@ module Sauce
|
|
20
21
|
|
21
22
|
@tunnels = Sauce::Tunnel
|
22
23
|
@tunnels.client = @client
|
24
|
+
@tunnels.account = {:username => @username,
|
25
|
+
:access_key => @access_key,
|
26
|
+
:ip => @ip}
|
23
27
|
end
|
24
28
|
end
|
25
29
|
end
|
data/lib/sauce.rb
CHANGED
data/lib/tunnel.rb
CHANGED
@@ -1,19 +1,20 @@
|
|
1
|
+
require 'net/telnet'
|
2
|
+
require 'net/ssh'
|
3
|
+
require 'net/ssh/gateway'
|
4
|
+
require 'gateway_ext'
|
5
|
+
|
1
6
|
module Sauce
|
2
7
|
# Interact with a Sauce Labs tunnel as if it were a ruby object
|
3
8
|
class Tunnel
|
4
9
|
class NoIDError < StandardError; end #nodoc
|
5
10
|
|
6
|
-
|
7
|
-
attr_accessor :status, :owner, :creation_time, :id, :host
|
11
|
+
#{"Status": "running", "CreationTime": 1266545716, "ModificationTime": 1266545793, "Host": "ec2-184-73-16-13.compute-1.amazonaws.com", "LaunchTime": 1266545726, "Owner": "sgrove", "_id": "1090b4b0b50477cf40fc44c146b408a4", "Type": "tunnel", "id": "1090b4b0b50477cf40fc44c146b408a4", "DomainNames": ["sgrove.tst"]}
|
8
12
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def initialize(options)
|
15
|
-
build!(options)
|
16
|
-
end
|
13
|
+
attr_accessor :owner, :access_key, :status
|
14
|
+
attr_accessor :id, :host, :domain_names, :timeout
|
15
|
+
attr_accessor :launch_time, :created_at, :updated_at
|
16
|
+
|
17
|
+
attr_accessor :application_address, :gateway, :port, :thread
|
17
18
|
|
18
19
|
# Get the class @@client.
|
19
20
|
# TODO: Consider metaprogramming this away
|
@@ -27,6 +28,18 @@ module Sauce
|
|
27
28
|
@@client = client
|
28
29
|
end
|
29
30
|
|
31
|
+
# Get the class @@client.
|
32
|
+
# TODO: Consider metaprogramming this away
|
33
|
+
def self.account
|
34
|
+
@@account
|
35
|
+
end
|
36
|
+
|
37
|
+
# Set the class @@client.
|
38
|
+
# TODO: Consider metaprogramming this away
|
39
|
+
def self.account=(account)
|
40
|
+
@@account = account
|
41
|
+
end
|
42
|
+
|
30
43
|
def self.first
|
31
44
|
self.all.first
|
32
45
|
end
|
@@ -44,8 +57,21 @@ module Sauce
|
|
44
57
|
self.all.each { |tunnel| tunnel.destroy }
|
45
58
|
end
|
46
59
|
|
60
|
+
# Creates a new tunnel machine
|
61
|
+
def self.create(options)
|
62
|
+
response = JSON.parse @@client[:tunnels].post(options.to_json, :content_type => 'application/json')
|
63
|
+
#puts response.inspect
|
64
|
+
Tunnel.new response
|
65
|
+
end
|
66
|
+
|
67
|
+
# Creates an instance representing a machine, but does not actually create the machine. See #create for that.
|
68
|
+
def initialize(options)
|
69
|
+
build!(options)
|
70
|
+
end
|
71
|
+
|
47
72
|
# Hits the destroy url for this tunnel, and then refreshes. Keep in mind it takes some time to completely teardown a tunnel.
|
48
73
|
def destroy
|
74
|
+
close_gateway
|
49
75
|
response = @@client["tunnels/#{@id}"].delete
|
50
76
|
refresh!
|
51
77
|
end
|
@@ -53,11 +79,47 @@ module Sauce
|
|
53
79
|
# Retrieves the latest information on this tunnel from the Sauce Labs' server
|
54
80
|
def refresh!
|
55
81
|
response = JSON.parse @@client["tunnels/#{@id}"].get
|
82
|
+
#puts "\Tunnel refresh with: #{response.inspect}"
|
56
83
|
build! response
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
# Shortcut method to find out if a tunnel is marked as dead
|
88
|
+
def preparing?
|
89
|
+
refresh!
|
90
|
+
status == "new" or status == "booting"
|
91
|
+
end
|
92
|
+
|
93
|
+
# Shortcut method to find out if a tunnel is marked as dead
|
94
|
+
def halting?
|
95
|
+
refresh!
|
96
|
+
status == "halting"
|
97
|
+
end
|
98
|
+
|
99
|
+
# Shortcut method to find out if a tunnel is marked as dead
|
100
|
+
def terminated?
|
101
|
+
refresh!
|
102
|
+
status == "terminated"
|
103
|
+
end
|
104
|
+
|
105
|
+
# A tunnel is healthy if 1.) its status is "running" and 2.) It says hello
|
106
|
+
def healthy?
|
107
|
+
# TODO: Implement 3.) We can reach through and touch a service on the reverse end of the tunnel
|
108
|
+
refresh!
|
109
|
+
=begin
|
110
|
+
puts "\tRunning? #{self.status == 'running'}"
|
111
|
+
puts "\tSays hello? #{self.says_hello?}"
|
112
|
+
puts "\tThread running? #{self.still_running?}"
|
113
|
+
=end
|
114
|
+
return true if not self.terminated? and self.status == "running" and self.says_hello? and self.still_running?
|
115
|
+
return false
|
57
116
|
end
|
58
117
|
|
59
|
-
# Sauce Labs' server will say hello on port 1025 as a sanity check. If no hello, something is wrong.
|
118
|
+
# Sauce Labs' server will say hello on port 1025 as a sanity check. If no hello, something is wrong.
|
119
|
+
# TODO: Make it say hello on port 1025. Currently a hack.
|
60
120
|
def says_hello?(options={})
|
121
|
+
return false unless self.status == "running"
|
122
|
+
|
61
123
|
# TODO: Read from options if necessary
|
62
124
|
connection = {}
|
63
125
|
connection[:host] = @host
|
@@ -66,6 +128,8 @@ module Sauce
|
|
66
128
|
connection[:telnet_mode] = true
|
67
129
|
connection[:timeout] = 10
|
68
130
|
|
131
|
+
#puts "telnet: #{connection.inspect}"
|
132
|
+
|
69
133
|
host = Net::Telnet::new("Host" => connection[:host],
|
70
134
|
"Port" => connection[:port],
|
71
135
|
"Prompt" => connection[:prompt],
|
@@ -78,15 +142,77 @@ module Sauce
|
|
78
142
|
line[0,prefix.length] == prefix
|
79
143
|
end
|
80
144
|
|
145
|
+
# Debug method
|
146
|
+
def mini_repair
|
147
|
+
refresh!
|
148
|
+
if not self.terminated? and self.status == "running" and self.says_hello?
|
149
|
+
if not self.still_running?
|
150
|
+
open_gateway
|
151
|
+
return true if self.still_running?
|
152
|
+
end
|
153
|
+
end
|
154
|
+
return false
|
155
|
+
end
|
156
|
+
|
157
|
+
# Opens a reverse ssh tunnel to the tunnel machine
|
158
|
+
def open_gateway
|
159
|
+
# TODO: Make ports configurable
|
160
|
+
|
161
|
+
@gateway = Net::SSH::Gateway.new(host, owner, {:password => @@account[:access_key]})
|
162
|
+
|
163
|
+
# Notes for anyone looking at this method
|
164
|
+
# gateway.open_remote(3000, # Port where your local application is running. Usually 3000 for rails dev
|
165
|
+
# "localhost", # Hostname/local ip your rails app is running on. Usually "localhost" for rails dev
|
166
|
+
# 80, # This is the port to run your selenium tests against, i.e. :url => "http://<some_host>:80"
|
167
|
+
# # <some_host> is the DomainNames you started the tunnel machine with.
|
168
|
+
# "0.0.0.0") # Do not change this unless your god has commanded you to do so. I'm serious.
|
169
|
+
port = 3000
|
170
|
+
local_host = "localhost"
|
171
|
+
remote_port = 80
|
172
|
+
#puts "This is the experimental non-blocking gateway_open. Well done."
|
173
|
+
#puts "gateway.open_remote(#{port}, #{local_host}, #{remote_port}, 0.0.0.0)"
|
174
|
+
#gateway.open_remote(pair[0], local_host, pair[1], "0.0.0.0") do |rp, rh|
|
175
|
+
|
176
|
+
@thread = Thread.new(host, port, remote_port, owner, @@account[:access_key]) do
|
177
|
+
Thread.pass
|
178
|
+
@gateway.open_remote(3000,
|
179
|
+
"localhost",
|
180
|
+
80,
|
181
|
+
"0.0.0.0") do |rp, rh|
|
182
|
+
while true do
|
183
|
+
sleep 10
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
@thread.run
|
189
|
+
end
|
190
|
+
|
191
|
+
# Closes the reverse ssh tunnel if open
|
192
|
+
def close_gateway
|
193
|
+
@thread.kill if @thread
|
194
|
+
end
|
195
|
+
|
196
|
+
# Returns true if this tunnel has a thread that needs to be monitored
|
197
|
+
def still_running?
|
198
|
+
#puts "\t\t#{@thread.inspect}.nil?"
|
199
|
+
not @thread.nil?
|
200
|
+
end
|
201
|
+
|
81
202
|
protected
|
82
203
|
|
83
204
|
# Sets all internal variables from a hash
|
84
205
|
def build!(options)
|
206
|
+
#puts "\tBuild with #{options.inspect}"
|
85
207
|
@status = options["Status"]
|
86
|
-
@owner
|
87
|
-
@id
|
88
|
-
@id
|
89
|
-
@host
|
208
|
+
@owner = options["Owner"]
|
209
|
+
@id = options["_id"]
|
210
|
+
@id = options["id"] if @id.nil? or @id.empty?
|
211
|
+
@host = options["Host"]
|
212
|
+
@created_at = options["CreationTime"]
|
213
|
+
@updated_at = options["ModificationTime"]
|
214
|
+
@launch_time = options["LaunchTime"]
|
215
|
+
@domain_names = options["DomainNames"]
|
90
216
|
|
91
217
|
raise NoIDError if @id.nil? or @id.empty?
|
92
218
|
end
|
data/test/debug.rb
CHANGED
@@ -1,11 +1,34 @@
|
|
1
|
-
require '
|
1
|
+
require 'irb_boot'
|
2
2
|
|
3
3
|
c = Sauce::Client.new(:username => "sgrove",
|
4
|
-
:access_key => "
|
4
|
+
:access_key => "733a44e2-4226-488f-8126-6f6d59bdf386",
|
5
|
+
:ip => "67.188.3.10")
|
6
|
+
|
7
|
+
=begin
|
8
|
+
t = c.tunnels.first
|
5
9
|
|
6
10
|
c.destroy_all_tunnels
|
7
11
|
t = c.create_tunnel('DomainNames' => ["111.111.111.111"])
|
8
12
|
|
9
13
|
puts c.tunnels.inspect
|
14
|
+
=end
|
10
15
|
|
11
|
-
c.
|
16
|
+
t = c.tunnels.first
|
17
|
+
|
18
|
+
if t.nil?
|
19
|
+
puts "Starting a new tunnel"
|
20
|
+
t = c.tunnels.create("DomainNames" => ["sgrove.tst"])
|
21
|
+
until t.status == "running"
|
22
|
+
print "." ; STDOUT.flush
|
23
|
+
t.refresh!
|
24
|
+
sleep 1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
puts t.inspect
|
29
|
+
t.open_gateway
|
30
|
+
|
31
|
+
puts "Checking to see if thread is running."
|
32
|
+
puts t.still_running?
|
33
|
+
puts t.thread.inspect
|
34
|
+
t.thread.join if t.still_running?
|
data/test/irb_boot.rb
ADDED
data/test/test_sauce.rb
CHANGED
@@ -65,6 +65,15 @@ class TestSauce < Test::Unit::TestCase
|
|
65
65
|
end
|
66
66
|
|
67
67
|
assert_equal true, tunnel.says_hello?
|
68
|
+
|
69
|
+
tunnel.destroy # cleanup
|
70
|
+
end
|
71
|
+
|
72
|
+
should "not attempt to telnet if status is not running" do
|
73
|
+
tunnel = @client.tunnels.create('DomainNames' => [@ip])
|
74
|
+
|
75
|
+
tunnel.status = "booting"
|
76
|
+
assert_equal false, tunnel.says_hello?
|
68
77
|
end
|
69
78
|
|
70
79
|
def teardown
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sauce
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean Grove
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-02-
|
12
|
+
date: 2010-02-22 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -84,4 +84,5 @@ summary: Ruby access to Sauce Labs' features
|
|
84
84
|
test_files:
|
85
85
|
- test/debug.rb
|
86
86
|
- test/helper.rb
|
87
|
+
- test/irb_boot.rb
|
87
88
|
- test/test_sauce.rb
|