sauce 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|