sauce 0.12.9 → 0.12.10

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.
Files changed (3) hide show
  1. data/VERSION +1 -1
  2. data/support/sauce_connect +48 -37
  3. metadata +4 -4
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.12.9
1
+ 0.12.10
@@ -27,6 +27,7 @@ import time
27
27
  import platform
28
28
  import tempfile
29
29
  import string
30
+ from base64 import b64encode
30
31
  from collections import defaultdict
31
32
  from contextlib import closing
32
33
  from functools import wraps
@@ -37,7 +38,7 @@ except ImportError:
37
38
  import simplejson as json # Python 2.5 dependency
38
39
 
39
40
  NAME = "sauce_connect"
40
- RELEASE = 21
41
+ RELEASE = 25
41
42
  DISPLAY_VERSION = "%s release %s" % (NAME, RELEASE)
42
43
  PRODUCT_NAME = u"Sauce Connect"
43
44
  VERSIONS_URL = "http://saucelabs.com/versions.json"
@@ -59,6 +60,12 @@ is_openbsd = platform.system().lower() == "openbsd"
59
60
  logger = logging.getLogger(NAME)
60
61
 
61
62
 
63
+ class DeleteRequest(urllib2.Request):
64
+
65
+ def get_method(self):
66
+ return "DELETE"
67
+
68
+
62
69
  class HTTPResponseError(Exception):
63
70
 
64
71
  def __init__(self, msg):
@@ -84,18 +91,19 @@ class TunnelMachine(object):
84
91
 
85
92
  _host_search = re.compile("//([^/]+)").search
86
93
 
87
- def __init__(self, rest_url, user, password, domains, metadata=None):
94
+ def __init__(self, rest_url, user, password, domains, ssh_port, metadata=None):
88
95
  self.user = user
89
96
  self.password = password
90
97
  self.domains = set(domains)
98
+ self.ssh_port = ssh_port
91
99
  self.metadata = metadata or dict()
92
100
 
93
101
  self.reverse_ssh = None
94
102
  self.is_shutdown = False
95
103
  self.base_url = "%(rest_url)s/%(user)s/tunnels" % locals()
96
104
  self.rest_host = self._host_search(rest_url).group(1)
97
- self.basic_auth_header = {"Authorization": "Basic %s" %
98
- ("%s:%s" % (user, password)).encode("base64").strip()}
105
+ self.basic_auth_header = {"Authorization": "Basic %s"
106
+ % b64encode("%s:%s" % (user, password))}
99
107
 
100
108
  self._set_urlopen(user, password)
101
109
 
@@ -111,9 +119,7 @@ class TunnelMachine(object):
111
119
  "help@saucelabs.com.")
112
120
 
113
121
  def _set_urlopen(self, user, password):
114
- # always send Basic Auth header for GET and POST
115
- # NOTE: we directly construct the header because it is more reliable
116
- # and more efficient than HTTPBasicAuthHandler and we always need it
122
+ # always send Basic Auth header (HTTPBasicAuthHandler was unreliable)
117
123
  opener = urllib2.build_opener()
118
124
  opener.addheaders = self.basic_auth_header.items()
119
125
  self.urlopen = opener.open
@@ -155,21 +161,6 @@ class TunnelMachine(object):
155
161
  raise HTTPResponseError(resp.msg)
156
162
  return json.loads(resp.read())
157
163
 
158
- @_retry_rest_api
159
- def _get_delete_doc(self, url):
160
- # urllib2 doesn support the DELETE method (lame), so we build our own
161
- if self.base_url.startswith("https"):
162
- make_conn = httplib.HTTPSConnection
163
- else:
164
- make_conn = httplib.HTTPConnection
165
- with closing(make_conn(self.rest_host)) as conn:
166
- conn.request(method="DELETE", url=url,
167
- headers=self.basic_auth_header)
168
- resp = conn.getresponse()
169
- if resp.reason != "OK":
170
- raise HTTPResponseError(resp.reason)
171
- return json.loads(resp.read())
172
-
173
164
  def _provision_tunnel(self):
174
165
  # Shutdown any tunnel using a requested domain
175
166
  kill_list = set()
@@ -186,7 +177,7 @@ class TunnelMachine(object):
186
177
  logger.debug(
187
178
  "Shutting down old tunnel host: %s" % tunnel_id)
188
179
  url = "%s/%s" % (self.base_url, tunnel_id)
189
- doc = self._get_delete_doc(url)
180
+ doc = self._get_doc(DeleteRequest(url=url))
190
181
  if not doc.get('ok'):
191
182
  logger.warning("Old tunnel host failed to shutdown?")
192
183
  continue
@@ -201,7 +192,8 @@ class TunnelMachine(object):
201
192
  # Request a tunnel machine
202
193
  headers = {"Content-Type": "application/json"}
203
194
  data = json.dumps(dict(DomainNames=list(self.domains),
204
- Metadata=self.metadata))
195
+ Metadata=self.metadata,
196
+ SSHPort=self.ssh_port))
205
197
  req = urllib2.Request(url=self.base_url, headers=headers, data=data)
206
198
  doc = self._get_doc(req)
207
199
  if doc.get('error'):
@@ -243,9 +235,10 @@ class TunnelMachine(object):
243
235
  logger.debug("Tunnel host ID: %s" % self.id)
244
236
 
245
237
  try:
246
- doc = self._get_delete_doc(self.url)
247
- except TunnelMachineError:
238
+ doc = self._get_doc(DeleteRequest(url=self.url))
239
+ except TunnelMachineError, e:
248
240
  logger.warning("Unable to shut down tunnel host")
241
+ logger.debug("Shut down failed because: %s", str(e))
249
242
  self.is_shutdown = True # fuhgeddaboudit
250
243
  return
251
244
  assert doc.get('ok')
@@ -350,13 +343,14 @@ class ReverseSSHError(Exception):
350
343
 
351
344
  class ReverseSSH(object):
352
345
 
353
- def __init__(self, tunnel, host, ports, tunnel_ports,
346
+ def __init__(self, tunnel, host, ports, tunnel_ports, ssh_port,
354
347
  use_ssh_config=False, debug=False):
355
348
  self.tunnel = tunnel
356
349
  self.host = host
357
350
  self.ports = ports
358
351
  self.tunnel_ports = tunnel_ports
359
352
  self.use_ssh_config = use_ssh_config
353
+ self.ssh_port = ssh_port
360
354
  self.debug = debug
361
355
 
362
356
  self.proc = None
@@ -388,8 +382,8 @@ class ReverseSSH(object):
388
382
  def get_plink_command(self):
389
383
  """Return the Windows SSH command."""
390
384
  verbosity = "-v" if self.debug else ""
391
- return ("plink\plink %s -l %s -pw %s -N %s %s"
392
- % (verbosity, self.tunnel.user, self.tunnel.password,
385
+ return ("plink\plink %s -P %s -l %s -pw %s -N %s %s"
386
+ % (verbosity, self.ssh_port, self.tunnel.user, self.tunnel.password,
393
387
  self._dash_Rs, self.tunnel.host))
394
388
 
395
389
  def get_expect_script(self):
@@ -402,8 +396,8 @@ class ReverseSSH(object):
402
396
  config_file = "" if self.use_ssh_config else "-F /dev/null"
403
397
  host_ip = socket.gethostbyname(self.tunnel.host)
404
398
  script = (
405
- "spawn ssh %s %s -p 22 -l %s -o ServerAliveInterval=%s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -N %s %s;"
406
- % (verbosity, config_file, self.tunnel.user,
399
+ "spawn ssh %s %s -p %s -l %s -o ServerAliveInterval=%s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -N %s %s;"
400
+ % (verbosity, config_file, self.ssh_port, self.tunnel.user,
407
401
  HEALTH_CHECK_INTERVAL, self._dash_Rs, self.tunnel.host) +
408
402
  "expect *password:;send -- %s\\r;" % self.tunnel.password +
409
403
  "expect -timeout -1 timeout")
@@ -433,9 +427,9 @@ class ReverseSSH(object):
433
427
 
434
428
  # setup recurring healthchecks
435
429
  forwarded_health = HealthChecker(self.host, self.ports)
436
- tunnel_health = HealthChecker(host=self.tunnel.host, ports=[22],
430
+ tunnel_health = HealthChecker(host=self.tunnel.host, ports=[self.ssh_port],
437
431
  fail_msg="!! Your tests may fail because your network can not get "
438
- "to the tunnel host (%s:%d)." % (self.tunnel.host, 22))
432
+ "to the tunnel host (%s:%d)." % (self.tunnel.host, self.ssh_port))
439
433
 
440
434
  start_time = int(time.time())
441
435
  while self.proc.poll() is None:
@@ -719,6 +713,8 @@ Performance tip:
719
713
  help=optparse.SUPPRESS_HELP)
720
714
  og.add_option("--allow-unclean-exit", action="store_true", default=False,
721
715
  help=optparse.SUPPRESS_HELP)
716
+ og.add_option("--ssh-port", default=22, type="int",
717
+ help=optparse.SUPPRESS_HELP)
722
718
  op.add_option_group(og)
723
719
 
724
720
  og = optparse.OptionGroup(op, "Script debugging options")
@@ -730,6 +726,20 @@ Performance tip:
730
726
 
731
727
  (options, args) = op.parse_args()
732
728
 
729
+ # check ports are numbers
730
+ try:
731
+ map(int, options.ports)
732
+ map(int, options.tunnel_ports)
733
+ except ValueError:
734
+ sys.stderr.write("Error: Ports must be integers\n\n")
735
+ print "Help with options -t and -p:"
736
+ print " All ports must be integers. You used:"
737
+ if options.ports:
738
+ print " -p", " -p ".join(options.ports)
739
+ if options.tunnel_ports:
740
+ print " -t", " -t ".join(options.tunnel_ports)
741
+ raise SystemExit(1)
742
+
733
743
  # default to 80 and default to matching host ports with tunnel ports
734
744
  if not options.ports and not options.tunnel_ports:
735
745
  options.ports = ["80"]
@@ -830,6 +840,7 @@ def run(options, dependency_versions=None):
830
840
  print "| Contact us: http://saucelabs.com/forums |"
831
841
  print "-----------------------------------------------------"
832
842
  logger.info("/ Starting \\")
843
+ logger.info('Please wait for "You may start your tests" to start your tests.')
833
844
  logger.info("%s" % DISPLAY_VERSION)
834
845
  check_version()
835
846
 
@@ -861,7 +872,8 @@ def run(options, dependency_versions=None):
861
872
  for attempt in xrange(1, RETRY_BOOT_MAX + 1):
862
873
  try:
863
874
  tunnel = TunnelMachine(options.rest_url, options.user,
864
- options.api_key, options.domains, metadata)
875
+ options.api_key, options.domains,
876
+ options.ssh_port, metadata)
865
877
  except TunnelMachineError, e:
866
878
  logger.error(e)
867
879
  peace_out(returncode=1) # exits
@@ -881,6 +893,7 @@ def run(options, dependency_versions=None):
881
893
 
882
894
  ssh = ReverseSSH(tunnel=tunnel, host=options.host,
883
895
  ports=options.ports, tunnel_ports=options.tunnel_ports,
896
+ ssh_port=options.ssh_port,
884
897
  use_ssh_config=options.use_ssh_config,
885
898
  debug=options.debug_ssh)
886
899
  try:
@@ -891,9 +904,7 @@ def run(options, dependency_versions=None):
891
904
 
892
905
 
893
906
  def main():
894
- # more complicated so this works on old Python
895
- pyver = float("%s.%s" % tuple(platform.python_version().split('.')[:2]))
896
- if pyver < 2.5:
907
+ if map(int, platform.python_version_tuple ()) < [2, 5]:
897
908
  print "%s requires Python 2.5 (2006) or newer." % PRODUCT_NAME
898
909
  raise SystemExit(1)
899
910
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sauce
3
3
  version: !ruby/object:Gem::Version
4
- hash: 61
4
+ hash: 59
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 12
9
- - 9
10
- version: 0.12.9
9
+ - 10
10
+ version: 0.12.10
11
11
  platform: ruby
12
12
  authors:
13
13
  - Sean Grove
@@ -17,7 +17,7 @@ autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
19
 
20
- date: 2011-01-20 00:00:00 -03:00
20
+ date: 2011-01-21 00:00:00 -03:00
21
21
  default_executable: sauce
22
22
  dependencies:
23
23
  - !ruby/object:Gem::Dependency