changeset 402:08801667f00f

Switch to urllib2 in slave so that we can support basic and digest authentication.
author cmlenz
date Fri, 03 Aug 2007 14:44:29 +0000
parents a10942252ebc
children b187f0894838
files bitten/master.py bitten/slave.py
diffstat 2 files changed, 50 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/bitten/master.py
+++ b/bitten/master.py
@@ -241,9 +241,11 @@
                 listener.build_completed(build)
 
         body = 'Build step processed'
-        req.send_response(200)
+        req.send_response(201)
         req.send_header('Content-Type', 'text/plain')
         req.send_header('Content-Length', str(len(body)))
+        req.send_header('Location', req.abs_href.builds(build.id, 'steps',
+                        stepname))
         req.write(body)
         raise RequestDone
 
--- a/bitten/slave.py
+++ b/bitten/slave.py
@@ -10,7 +10,7 @@
 """Implementation of the build slave."""
 
 from datetime import datetime
-import httplib
+import urllib2
 import logging
 import os
 import platform
@@ -19,7 +19,6 @@
 except NameError:
     from sets import Set as set
 import shutil
-import socket
 import tempfile
 import time
 import urlparse
@@ -32,11 +31,23 @@
 log = logging.getLogger('bitten.slave')
 
 
+class SaneHTTPErrorProcessor(urllib2.HTTPErrorProcessor):
+    "The HTTPErrorProcessor defined in urllib needs some love."
+
+    def http_response(self, request, response):
+        code, msg, hdrs = response.code, response.msg, response.info()
+        if code >= 300:
+            response = self.parent.error(
+                'http', request, response, code, msg, hdrs)
+        return response
+
+
 class BuildSlave(object):
     """BEEP initiator implementation for the build slave."""
 
     def __init__(self, url, name=None, config=None, dry_run=False,
-                 work_dir=None, keep_files=False, single_build=False):
+                 work_dir=None, keep_files=False, single_build=False,
+                 username=None, password=None):
         """Create the build slave instance.
         
         @param url: The URL of the build master
@@ -50,6 +61,9 @@
             execution should be kept when done
         @param single_build: Whether this slave should exit after completing a 
             single build, or continue processing builds forever
+        @param username: the username to use when authentication against the
+            build master is requested
+        @param password: the password to use when authentication is needed
         """
         self.url = url
         if name is None:
@@ -65,29 +79,29 @@
         self.keep_files = keep_files
         self.single_build = single_build
 
-    def request(self, method, url, body=None, headers=None):
-        scheme, host, path, query, fragment = urlparse.urlsplit(url)
-        scheme = (scheme or 'http').lower()
+        self.opener = urllib2.build_opener(SaneHTTPErrorProcessor)
+        password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
+        if username and password:
+            password_mgr.add_password(None, url, username, password)
+        self.opener.add_handler(urllib2.HTTPBasicAuthHandler(password_mgr))
+        self.opener.add_handler(urllib2.HTTPDigestAuthHandler(password_mgr))
 
-        if scheme == 'https':
-            conn = httplib.HTTPSConnection(host)
-        else:
-            conn = httplib.HTTPConnection(host)
-
-        if headers is None:
-            headers = {}
-        if body is None:
-            body = ''
-        headers['Content-Length'] = len(body)
-        conn.request(method.upper(), path, body, headers)
-        return conn.getresponse()
+    def request(self, method, url, body=None, headers=None):
+        req = urllib2.Request(url, body, headers or {})
+        try:
+            return self.opener.open(req)
+        except urllib2.HTTPError, e:
+            if e.code >= 300:
+                log.warn('Server returned error %d: %s', e.code, e.msg)
+                raise
+            return e
 
     def run(self):
         while True:
             try:
                 try:
                     self._create_build()
-                except socket.error, e:
+                except urllib2.URLError, e:
                     log.error(e)
                     raise ExitSlave()
             except ExitSlave:
@@ -120,19 +134,19 @@
             'Content-Type': 'application/x-bitten+xml'
         })
 
-        if resp.status == 201:
-            self._initiate_build(resp.getheader('location'))
-        elif resp.status == 204:
+        if resp.code == 201:
+            self._initiate_build(resp.info().get('location'))
+        elif resp.code == 204:
             log.info(resp.read())
         else:
-            log.error('Unexpected response (%d): %s', resp.status, resp.reason)
+            log.error('Unexpected response (%d %s)', resp.code, resp.msg)
             raise ExitSlave()
 
     def _initiate_build(self, build_url):
         build_id = int(build_url.split('/')[-1])
         log.info('Build %s pending at %s', build_id, build_url)
         resp = self.request('GET', build_url)
-        if resp.status == 200:
+        if resp.code == 200:
             xml = xmlio.parse(resp)
             basedir = os.path.join(self.work_dir, 'build_%d' % build_id)
             if not os.path.exists(basedir):
@@ -148,7 +162,7 @@
                     log.info('Exiting after single build completed.')
                     raise ExitSlave()
         else:
-            log.error('Unexpected response (%d): %s', resp.status, resp.reason)
+            log.error('Unexpected response (%d): %s', resp.code, resp.msg)
             raise ExitSlave()
 
     def _execute_build(self, build_url, recipe):
@@ -190,8 +204,8 @@
         resp = self.request('POST', build_url + '/steps/', str(xml), {
             'Content-Type': 'application/x-bitten+xml'
         })
-        if resp.status != 201:
-            log.error('Unexpected response (%d): %s', resp.status, resp.reason)
+        if resp.code != 201:
+            log.error('Unexpected response (%d): %s', resp.code, resp.msg)
 
         return not failed or step.onerror != 'fail'
 
@@ -230,6 +244,10 @@
     parser.add_option('-s', '--single', action='store_true',
                       dest='single_build',
                       help='exit after completing a single build')
+    parser.add_option('-u', '--user', dest='username',
+                      help='the username to use for authentication')
+    parser.add_option('-p', '--password', dest='password',
+                      help='the password to use when authenticating')
     parser.set_defaults(dry_run=False, keep_files=False,
                         loglevel=logging.WARNING, single_build=False)
     options, args = parser.parse_args()
@@ -256,7 +274,8 @@
     slave = BuildSlave(url, name=options.name, config=options.config,
                        dry_run=options.dry_run, work_dir=options.work_dir,
                        keep_files=options.keep_files,
-                       single_build=options.single_build)
+                       single_build=options.single_build,
+                       username=options.username, password=options.password)
     try:
         try:
             slave.run()
Copyright (C) 2012-2017 Edgewall Software