depot.py revision 54
3177N/A#!/usr/bin/python
20N/A#
20N/A# CDDL HEADER START
20N/A#
20N/A# The contents of this file are subject to the terms of the
20N/A# Common Development and Distribution License (the "License").
20N/A# You may not use this file except in compliance with the License.
20N/A#
20N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
20N/A# or http://www.opensolaris.org/os/licensing.
20N/A# See the License for the specific language governing permissions
20N/A# and limitations under the License.
20N/A#
20N/A# When distributing Covered Code, include this CDDL HEADER in each
20N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
20N/A# If applicable, add the following below this CDDL HEADER, with the
20N/A# fields enclosed by brackets "[]" replaced with your own identifying
20N/A# information: Portions Copyright [yyyy] [name of copyright owner]
20N/A#
20N/A# CDDL HEADER END
20N/A#
3158N/A# Copyright 2007 Sun Microsystems, Inc. All rights reserved.
20N/A# Use is subject to license terms.
20N/A#
3143N/A
3143N/A# pkg.depotd - package repository daemon
22N/A
0N/A# XXX The prototype pkg.depotd combines both the version management server that
50N/A# answers to pkgsend(1) sessions and the HTTP file server that answers to the
50N/A# various GET operations that a pkg(1) client makes. This split is expected to
50N/A# be made more explicit, by constraining the pkg(1) operations such that they
50N/A# can be served as a typical HTTP/HTTPS session. Thus, pkg.depotd will reduce
50N/A# to a special purpose HTTP/HTTPS server explicitly for the version management
50N/A# operations, and must manipulate the various state files--catalogs, in
50N/A# particular--such that the pkg(1) pull client can operately accurately with
50N/A# only a basic HTTP/HTTPS server in place.
50N/A
50N/A# XXX We should support simple "last-modified" operations via HEAD queries.
50N/A
50N/A# XXX Although we pushed the evaluation of next-version, etc. to the pull
50N/A# client, we should probably provide a query API to do same on the server, for
50N/A# dumb clients (like a notification service).
589N/A
589N/Aimport BaseHTTPServer
965N/Aimport errno
965N/Aimport getopt
965N/Aimport os
965N/Aimport re
965N/Aimport sha
2951N/Aimport shutil
1836N/Aimport sys
1836N/Aimport time
382N/Aimport urllib
812N/A
382N/Aimport pkg.catalog as catalog
382N/Aimport pkg.content as content
382N/Aimport pkg.dependency as dependency
1963N/Aimport pkg.fmri as fmri
382N/Aimport pkg.misc as misc
1963N/Aimport pkg.package as package
382N/Aimport pkg.version as version
382N/A
382N/Aimport pkg.server.config as config
382N/Aimport pkg.server.transaction as trans
382N/A
26N/Adef usage():
689N/A print """\
689N/AUsage: /usr/lib/pkg.depotd [-n]
466N/A"""
0N/A
468N/Adef catalog_get(scfg, request):
812N/A scfg.inc_catalog()
812N/A
52N/A request.send_response(200)
812N/A request.send_header('Content-type', 'text/plain')
451N/A request.end_headers()
0N/A request.wfile.write("%s" % scfg.catalog)
3194N/A
3194N/Adef manifest_get(scfg, request):
382N/A """The request is an encoded pkg FMRI. If the version is specified
382N/A incompletely, we return an error, as the client is expected to form
382N/A correct requests, based on its interpretation of the catalog and its
452N/A image policies."""
382N/A
452N/A scfg.inc_manifest()
382N/A
382N/A # Parse request into FMRI component and decode.
3143N/A m = re.match("^/manifest/(.*)", request.path)
3194N/A pfmri = urllib.unquote(m.group(1))
382N/A
22N/A f = fmri.PkgFmri(pfmri, None)
1836N/A
2507N/A # Open manifest and send.
1836N/A file = open("%s/%s" % (scfg.pkg_root, f.get_dir_path()), "r")
1836N/A data = file.read()
2962N/A
2962N/A request.send_response(200)
2962N/A request.send_header('Content-type', 'text/plain')
1431N/A request.end_headers()
1968N/A request.wfile.write(data)
873N/A
812N/Adef file_get_single(scfg, request):
1431N/A """The request is the SHA-1 hash name for the file."""
873N/A scfg.inc_file()
1431N/A
466N/A m = re.match("^/file/(.*)", request.path)
1431N/A fhash = m.group(1)
466N/A
466N/A try:
466N/A file = open(scfg.file_root + "/" + misc.hash_file_name(fhash))
23N/A except IOError, e:
466N/A if e.errno == errno.ENOENT:
466N/A request.send_response(404)
466N/A else:
466N/A request.send_response(500)
466N/A return
466N/A
466N/A data = file.read()
466N/A
1431N/A request.send_response(200)
1633N/A request.send_header("Content-type", "application/data")
1633N/A request.end_headers()
1633N/A request.wfile.write(data)
1633N/A
466N/Adef trans_open(scfg, request):
466N/A # XXX Authentication will be handled by virtue of possessing a signed
466N/A # certificate (or a more elaborate system).
1633N/A t = trans.Transaction()
1633N/A
1633N/A ret = t.open(scfg, request)
1633N/A if ret == 200:
1633N/A scfg.in_flight_trans[t.get_basename()] = t
1633N/A
3143N/A request.send_response(200)
2230N/A request.send_header('Content-type', 'text/plain')
1968N/A request.send_header('Transaction-ID', t.get_basename())
1633N/A request.end_headers()
2515N/A elif ret == 400:
2816N/A request.send_response(400)
2816N/A else:
1937N/A request.send_response(500)
382N/A
2230N/A
2230N/Adef trans_close(scfg, request):
2230N/A # Pull transaction ID from headers.
2230N/A m = re.match("^/close/(.*)", request.path)
2028N/A trans_id = m.group(1)
1968N/A
1968N/A # XXX KeyError?
2028N/A t = scfg.in_flight_trans[trans_id]
1968N/A t.close(request)
1968N/A del scfg.in_flight_trans[trans_id]
1968N/A
2028N/Adef trans_abandon(scfg, request):
2028N/A # Pull transaction ID from headers.
2028N/A m = re.match("^/abandon/(.*)", request.path)
1968N/A trans_id = m.group(1)
1968N/A
1968N/A t = scfg.in_flight_trans[trans_id]
1968N/A t.abandon(request)
1968N/A del scfg.in_flight_trans[trans_id]
1968N/A
589N/Adef trans_add(scfg, request):
589N/A m = re.match("^/add/([^/]*)/(.*)", request.path)
589N/A trans_id = m.group(1)
589N/A type = m.group(2)
1431N/A
1431N/A t = scfg.in_flight_trans[trans_id]
1431N/A t.add_content(request, type)
1431N/A
1431N/Aif "PKG_REPO" in os.environ:
858N/A scfg = config.SvrConfig(os.environ["PKG_REPO"], "pkg.sun.com")
1633N/Aelse:
2962N/A scfg = config.SvrConfig("/var/pkg/repo", "pkg.sun.com")
3053N/A
2515N/Aclass pkgHandler(BaseHTTPServer.BaseHTTPRequestHandler):
2515N/A
466N/A def do_GET(self):
466N/A # Client APIs
466N/A if re.match("^/catalog$", self.path):
466N/A catalog_get(scfg, self)
466N/A elif re.match("^/manifest/.*$", self.path):
466N/A manifest_get(scfg, self)
466N/A elif re.match("^/file/.*$", self.path):
466N/A file_get_single(scfg, self)
466N/A
589N/A # Publisher APIs
589N/A elif re.match("^/open/(.*)$", self.path):
589N/A trans_open(scfg, self)
1191N/A elif re.match("^/close/(.*)$", self.path):
1191N/A trans_close(scfg, self)
1191N/A elif re.match("^/abandon/(.*)$", self.path):
1191N/A trans_abandon(scfg, self)
1191N/A elif re.match("^/add/(.*)$", self.path):
2816N/A trans_add(scfg, self)
2816N/A
589N/A # Informational APIs
589N/A elif re.match("^/$", self.path) or re.match("^/index.html",
589N/A self.path):
589N/A self.send_response(200)
812N/A self.send_header('Content-type', 'text/html')
812N/A self.end_headers()
812N/A self.wfile.write("""\
812N/A<html>
812N/A<body>
812N/A<h1><code>pkg</code> server ok</h1>
812N/A<h2>Statistics</h2>
1968N/A<pre>
1968N/A""")
1968N/A self.wfile.write(scfg.get_status())
812N/A self.wfile.write("""\
812N/A</pre>
812N/A<h2>Catalog</h2>
812N/A<pre>
1475N/A""")
1475N/A self.wfile.write("%s" % scfg.catalog)
1475N/A self.wfile.write("""\
1475N/A</pre>
975N/A</body>
975N/A</html>""")
975N/A else:
975N/A self.send_response(404)
1633N/A self.send_header('Content-type', 'text/plain')
1633N/A self.end_headers()
1633N/A self.wfile.write('''404 GET URI %s ; headers %s''' %
1633N/A (self.path, self.headers))
2028N/A
1633N/A
3143N/A def do_PUT(self):
1633N/A self.send_response(200)
14N/A self.send_header('Content-type', 'text/plain')
382N/A self.end_headers()
429N/A self.wfile.write('''PUT URI %s ; headers %s''' %
14N/A (self.path, self.headers))
404N/A
404N/A def do_POST(self):
30N/A if re.match("^/add/(.*)$", self.path):
382N/A trans_add(scfg, self)
30N/A else:
791N/A self.send_response(404)
2728N/A
2728N/A def do_DELETE(self):
689N/A self.send_response(200)
1968N/A self.send_header('Content-type', 'text/plain')
1968N/A self.end_headers()
1968N/A self.wfile.write('''URI %s ; headers %s''' %
1968N/A (self.path, self.headers))
1191N/A
258N/Aif __name__ == "__main__":
1968N/A scfg.init_dirs()
2816N/A scfg.acquire_in_flight()
382N/A scfg.acquire_catalog()
1968N/A
30N/A try:
589N/A opts, pargs = getopt.getopt(sys.argv[1:], "n")
589N/A for opt, arg in opts:
1968N/A if opt == "-n":
589N/A sys.exit(0)
589N/A except getopt.GetoptError, e:
589N/A print "pkg.depotd: unknown option '%s'" % e.opt
589N/A usage()
1968N/A
589N/A server = BaseHTTPServer.HTTPServer(('', 10000), pkgHandler)
1968N/A server.serve_forever()
466N/A