sauce 0.12.9 → 0.12.10

Sign up to get free protection for your applications and to get access to all the features.
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