From 5f7dd2fe03499bdb87b825a0f9e108402ba5d5cc Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Thu, 9 Mar 2017 16:05:31 -0800 Subject: [PATCH] MWA2APIRepo: properly handle pkgs and icons uploads --- code/client/munkilib/munkirepo/MWA2APIRepo.py | 97 +++++++++++++------ 1 file changed, 67 insertions(+), 30 deletions(-) diff --git a/code/client/munkilib/munkirepo/MWA2APIRepo.py b/code/client/munkilib/munkirepo/MWA2APIRepo.py index 2eea20b0..8477dcb3 100644 --- a/code/client/munkilib/munkirepo/MWA2APIRepo.py +++ b/code/client/munkilib/munkirepo/MWA2APIRepo.py @@ -8,9 +8,10 @@ import plistlib import subprocess import tempfile import urllib2 +from xml.parsers.expat import ExpatError -from munkilib.munkirepo import Repo -from munkilib import display +from munkilib.munkirepo import Repo, RepoError +#from munkilib import display CURL_CMD = '/usr/bin/curl' @@ -31,14 +32,15 @@ class MWA2APIRepo(Repo): credentials if needed. For the API repo, well look for a stored authtoken; if we don't find one, we'll prompt for credentials and make an authtoken.''' - print 'Please provide credentials for %s:' % self.baseurl - username = raw_input('Username: ') - password = getpass.getpass() - user_and_pass = '%s:%s' % (username, password) - self.authtoken = 'Basic %s' % base64.b64encode(user_and_pass) + if not self.authtoken: + print 'Please provide credentials for %s:' % self.baseurl + username = raw_input('Username: ') + password = getpass.getpass() + user_and_pass = '%s:%s' % (username, password) + self.authtoken = 'Basic %s' % base64.b64encode(user_and_pass) def _curl(self, relative_url, headers=None, method='GET', - filename=None, content=None): + filename=None, content=None, formdata=None): '''Use curl to talk to MWA2 API''' # we use a config/directive file to avoid having the auth header show # up in a process listing @@ -53,10 +55,10 @@ class MWA2APIRepo(Repo): for key in headers: print >> fileobj, 'header = "%s: %s"' % (key, headers[key]) print >> fileobj, 'header = "Authorization: %s"' % self.authtoken - if method == 'GET': - print >> fileobj, 'header = "Accept: application/xml"' - else: - print >> fileobj, 'header = "Content-type: application/xml"' + + if formdata: + for line in formdata: + print >> fileobj, 'form = "%s"' % line url = os.path.join(self.baseurl, relative_url) @@ -66,9 +68,9 @@ class MWA2APIRepo(Repo): cmd = [CURL_CMD, '-q', '--config', directivepath] if filename and method == 'GET': cmd.extend(['-o', filename]) - if filename and method == 'PUT': + if filename and method in ('PUT', 'POST'): cmd.extend(['-d', '@%s' % filename]) - elif content and method == 'PUT': + elif content and method in ('PUT', 'POST'): cmd.extend(['-d', content]) #display.display_debug1('Curl command is %s', cmd) @@ -76,12 +78,15 @@ class MWA2APIRepo(Repo): stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, err = proc.communicate() + fileref = open(directivepath) + curl_directives = fileref.read() + fileref.close() try: os.unlink(directivepath) except OSError: pass if proc.returncode: - raise CurlError((proc.returncode, err)) + raise CurlError((proc.returncode, err, curl_directives, cmd)) return output def itemlist(self, kind): @@ -89,11 +94,15 @@ class MWA2APIRepo(Repo): Kind might be 'catalogs', 'manifests', 'pkgsinfo', 'pkgs', or 'icons'. For a file-backed repo this would be a list of pathnames.''' url = urllib2.quote(kind.encode('UTF-8')) + '?api_fields=filename' + headers = {'Accept': 'application/xml'} try: - data = self._curl(url) + data = self._curl(url, headers=headers) except CurlError, err: - raise - plist = plistlib.readPlistFromString(data) + raise RepoError(err) + try: + plist = plistlib.readPlistFromString(data) + except ExpatError, err: + raise RepoError(err) if kind in ['catalogs', 'manifests', 'pkgsinfo']: # it's a list of dicts containing 'filename' key/values return [item['filename'] for item in plist] @@ -109,10 +118,15 @@ class MWA2APIRepo(Repo): Avoid using this method with the 'pkgs' kind as it might return a really large blob of data.''' url = urllib2.quote(resource_identifier.encode('UTF-8')) + if resource_identifier.startswith( + ('catalogs/', 'manifests/', 'pkgsinfo/')): + headers = {'Accept': 'application/xml'} + else: + headers = {} try: - return self._curl(url) + return self._curl(url, headers=headers) except CurlError, err: - raise + raise RepoError(err) def get_to_local_file(self, resource_identifier, local_file_path): '''Gets the contents of item with given resource_identifier and saves @@ -122,10 +136,15 @@ class MWA2APIRepo(Repo): /pkgsinfo/apps/Firefox-52.0.plist to a local file given by local_file_path.''' url = urllib2.quote(resource_identifier.encode('UTF-8')) + if resource_identifier.startswith( + ('catalogs/', 'manifests/', 'pkgsinfo/')): + headers = {'Accept': 'application/xml'} + else: + headers = {} try: - result = self._curl(url, filename=local_file_path) + self._curl(url, headers=headers, filename=local_file_path) except CurlError, err: - raise + raise RepoError(err) def put(self, resource_identifier, content): '''Stores content on the repo based on resource_identifier. @@ -133,10 +152,16 @@ class MWA2APIRepo(Repo): 'pkgsinfo/apps/Firefox-52.0.plist' would result in the content being saved to /pkgsinfo/apps/Firefox-52.0.plist.''' url = urllib2.quote(resource_identifier.encode('UTF-8')) + method = 'PUT' + if resource_identifier.startswith( + ('catalogs/', 'manifests/', 'pkgsinfo/')): + headers = {'Content-type': 'application/xml'} + else: + headers = {} try: - result = self._curl(url, method='PUT', content=content) + self._curl(url, headers=headers, method=method, content=content) except CurlError, err: - raise + raise RepoError(err) def put_from_local_file(self, resource_identifier, local_file_path): '''Copies the content of local_file_path to the repo based on @@ -144,10 +169,22 @@ class MWA2APIRepo(Repo): of 'pkgsinfo/apps/Firefox-52.0.plist' would result in the content being saved to /pkgsinfo/apps/Firefox-52.0.plist.''' url = urllib2.quote(resource_identifier.encode('UTF-8')) - try: - result = self._curl(url, method='PUT', filename=local_file_path) - except CurlError, err: - raise + + if resource_identifier.startswith(('pkgs/', 'icons/')): + # MWA2API only supports POST for pkgs and icons + # and file uploads need to be form encoded + formdata = ['filedata=@%s' % local_file_path] + try: + self._curl(url, method='POST', formdata=formdata) + except CurlError, err: + raise RepoError(err) + else: + headers = {'Content-type': 'application/xml'} + try: + self._curl(url, headers=headers, method='PUT', + filename=local_file_path) + except CurlError, err: + raise RepoError(err) def delete(self, resource_identifier): '''Deletes a repo object located by resource_identifier. @@ -156,7 +193,7 @@ class MWA2APIRepo(Repo): /pkgsinfo/apps/Firefox-52.0.plist.''' url = urllib2.quote(resource_identifier.encode('UTF-8')) try: - result = self._curl(url, method='DELETE') + self._curl(url, method='DELETE') except CurlError, err: - raise + raise RepoError(err) \ No newline at end of file