def post(self): """POST method. This method behaves a little strangely. BlobstoreUploadHandler only allows returns statuses of 301, 302, 303 (not even 200), so one must redirect away to return more information to the caller. Parameters: file: package file contents pkginfo: packageinfo file contents name: filename of package e.g. 'Firefox-1.0.dmg' """ # Only blobstore/upload service/scotty requests should be # invoking this handler. if not handlers.IsBlobstore(): logging.critical('POST to /uploadpkg not from Blobstore: %s', self.request.headers) self.redirect('/') gaeserver.DoMunkiAuth(require_level=gaeserver.LEVEL_UPLOADPKG) user = self.request.get('user') filename = self.request.get('name') install_types = self.request.get('install_types') catalogs = self.request.get('catalogs', None) manifests = self.request.get('manifests', None) if catalogs is None or not install_types or not user or not filename: msg = 'uploadpkg POST required parameters missing' logging.error(msg) self.redirect('/uploadpkg?mode=error&msg=%s' % msg) return if catalogs == '': catalogs = [] else: catalogs = catalogs.split(',') if manifests in ['', None]: manifests = [] else: manifests = manifests.split(',') install_types = install_types.split(',') upload_files = self.get_uploads('file') upload_pkginfo_files = self.get_uploads('pkginfo') if not len(upload_pkginfo_files) and not self.request.get('pkginfo'): self.redirect('/uploadpkg?mode=error&msg=No%20file%20received') return if len(upload_pkginfo_files): # obtain the pkginfo from a blob, and then throw it away. this is # a necessary hack because the upload handler grabbed it, but we don't # intend to keep it in blobstore. pkginfo_str = gae_util.GetBlobAndDel(upload_pkginfo_files[0].key()) else: # otherwise, grab the form parameter. pkginfo_str = self.request.get('pkginfo') blob_info = upload_files[0] blobstore_key = str(blob_info.key()) # Parse, validate, and encode the pkginfo plist. plist = plist_lib.MunkiPackageInfoPlist(pkginfo_str) try: plist.Parse() except plist_lib.PlistError: logging.exception('Invalid pkginfo plist uploaded:\n%s\n', pkginfo_str) gae_util.SafeBlobDel(blobstore_key) self.redirect( '/uploadpkg?mode=error&msg=No%20valid%20pkginfo%20received') return filename = plist['installer_item_location'] pkgdata_sha256 = plist['installer_item_hash'] # verify the blob was actually written; in case Blobstore failed to write # the blob but still POSTed to this handler (very, very rare). blob_info = blobstore.BlobInfo.get(blobstore_key) if not blob_info: logging.critical( 'Blobstore returned a key for %s that does not exist: %s', filename, blobstore_key) self.redirect('/uploadpkg?mode=error&msg=Blobstore%20failure') return # Obtain a lock on the PackageInfo entity for this package. lock = 'pkgsinfo_%s' % filename if not gae_util.ObtainLock(lock, timeout=5.0): gae_util.SafeBlobDel(blobstore_key) self.redirect( '/uploadpkg?mode=error&msg=Could%20not%20lock%20pkgsinfo') return old_blobstore_key = None pkg = models.PackageInfo.get_or_insert(filename) if not pkg.IsSafeToModify(): gae_util.ReleaseLock(lock) gae_util.SafeBlobDel(blobstore_key) self.redirect( '/uploadpkg?mode=error&msg=Package%20is%20not%20modifiable') return if pkg.blobstore_key: # a previous blob exists. delete it when the update has succeeded. old_blobstore_key = pkg.blobstore_key pkg.blobstore_key = blobstore_key pkg.name = plist.GetPackageName() pkg.filename = filename pkg.user = user pkg.catalogs = catalogs pkg.manifests = manifests pkg.install_types = install_types pkg.plist = plist pkg.pkgdata_sha256 = pkgdata_sha256 # update the PackageInfo model with the new plist string and blobstore key. try: pkg.put() success = True except db.Error: logging.exception('error on PackageInfo.put()') success = False # if it failed, delete the blob that was just uploaded -- it's # an orphan. if not success: gae_util.SafeBlobDel(blobstore_key) # if this is a new entity (get_or_insert puts), attempt to delete it. if not old_blobstore_key: gae_util.SafeEntityDel(pkg) gae_util.ReleaseLock(lock) self.redirect('/uploadpkg?mode=error') return # if an old blob was associated with this Package, delete it. # the new blob that was just uploaded has replaced it. if old_blobstore_key: gae_util.SafeBlobDel(old_blobstore_key) gae_util.ReleaseLock(lock) # Generate catalogs for newly uploaded pkginfo plist. for catalog in pkg.catalogs: models.Catalog.Generate(catalog, delay=1) # Log admin upload to Datastore. admin_log = models.AdminPackageLog(user=user, action='uploadpkg', filename=filename, catalogs=catalogs, manifests=manifests, install_types=install_types, plist=pkg.plist.GetXml()) admin_log.put() self.redirect('/uploadpkg?mode=success&key=%s' % blobstore_key)
def post(self): """POST Handler. This method behaves a little strangely. BlobstoreUploadHandler only allows returns statuses of 301, 302, 303 (not even 200), so one must redirect away to return more information to the caller. Parameters: file: package file contents pkginfo: packageinfo file contents name: filename of package e.g. 'Firefox-1.0.dmg' """ # Only blobstore/upload service/scotty requests should be # invoking this handler. if not handlers.IsBlobstore(): logging.error('POST to uploadpkg not from Blobstore: %s', self.request.headers) self.redirect('/admin/packages') return if not self.get_uploads('file'): logging.error('Upload package does not exist.') return blob_info = self.get_uploads('file')[0] blobstore_key = str(blob_info.key()) # Obtain a lock on the PackageInfo entity for this package. lock = models.GetLockForPackage(blob_info.filename) try: lock.Acquire(timeout=30, max_acquire_attempts=5) except datastore_locks.AcquireLockError: gae_util.SafeBlobDel(blobstore_key) self._RedirectWithErrorMsg('PackageInfo is locked') return p = models.PackageInfo.get_by_key_name(blob_info.filename) if not p: lock.Release() gae_util.SafeBlobDel(blobstore_key) self._RedirectWithErrorMsg('PackageInfo not found') return if not p.IsSafeToModify(): lock.Release() gae_util.SafeBlobDel(blobstore_key) self._RedirectWithErrorMsg('PackageInfo is not modifiable') return installer_item_size = p.plist['installer_item_size'] size_difference = int(blob_info.size / 1024) - installer_item_size if abs(size_difference) > 1: lock.Release() gae_util.SafeBlobDel(blobstore_key) msg = 'Blob size (%s) does not match PackageInfo plist size (%s)' % ( blob_info.size, installer_item_size) self._RedirectWithErrorMsg(msg) return old_blobstore_key = None if p.blobstore_key: # a previous blob exists. delete it when the update has succeeded. old_blobstore_key = p.blobstore_key p.blob_info = blob_info # update the PackageInfo model with the new plist string and blobstore key. try: p.put() error = None except db.Error, e: logging.exception('error on PackageInfo.put()') error = 'pkginfo.put() failed with: %s' % str(e)
def post(self): """POST This method behaves a little strangely. BlobstoreUploadHandler only allows returns statuses of 301, 302, 303 (not even 200), so one must redirect away to return more information to the caller. Parameters: file: package file contents pkginfo: packageinfo file contents name: filename of package e.g. 'Firefox-1.0.dmg' """ # Only blobstore/upload service/scotty requests should be # invoking this handler. if not handlers.IsBlobstore(): logging.critical('POST to /uploadpkg not from Blobstore: %s', self.request.headers) self.redirect('/') gaeserver.DoMunkiAuth(require_level=gaeserver.LEVEL_UPLOADPKG) user = self.request.get('user') filename = self.request.get('name') install_types = self.request.get('install_types') catalogs = self.request.get('catalogs', None) manifests = self.request.get('manifests', None) if catalogs is None or not install_types or not user or not filename: msg = 'uploadpkg POST required parameters missing' logging.error(msg) self.redirect('/uploadpkg?mode=error&msg=%s' % msg) return if catalogs == '': catalogs = [] else: catalogs = catalogs.split(',') if manifests in ['', None]: manifests = [] else: manifests = manifests.split(',') install_types = install_types.split(',') upload_files = self.get_uploads('file') upload_pkginfo_files = self.get_uploads('pkginfo') if not len(upload_pkginfo_files) and not self.request.get('pkginfo'): self.redirect('/uploadpkg?mode=error&msg=No%20file%20received') return if len(upload_pkginfo_files): # obtain the pkginfo from a blob, and then throw it away. this is # a necessary hack because the upload handler grabbed it, but we don't # intend to keep it in blobstore. pkginfo_str = gae_util.GetBlobAndDel(upload_pkginfo_files[0].key()) else: # otherwise, grab the form parameter. pkginfo_str = self.request.get('pkginfo') blob_info = upload_files[0] blobstore_key = str(blob_info.key()) # Parse, validate, and encode the pkginfo plist. plist = plist_lib.MunkiPackageInfoPlist(pkginfo_str) try: plist.Parse() except plist_lib.PlistError, e: logging.exception('Invalid pkginfo plist uploaded:\n%s\n', pkginfo_str) gae_util.SafeBlobDel(blobstore_key) self.redirect( '/uploadpkg?mode=error&msg=No%20valid%20pkginfo%20received') return