Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update API to rest framework #34

Merged
merged 16 commits into from
Jun 23, 2024
260 changes: 57 additions & 203 deletions api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,31 @@
"""
from django.conf import settings

import os
import sys
import logging
import plistlib
from xml.parsers.expat import ExpatError

from munkiwebadmin.utils import MunkiGit
from process.utils import record_status

REPO_DIR = settings.MUNKI_REPO_DIR
LOGGER = logging.getLogger('munkiwebadmin')
MUNKI_REPO_URL = settings.MUNKI_REPO_URL
MUNKI_REPO_PLUGIN = settings.MUNKI_REPO_PLUGIN
MUNKITOOLS_DIR = settings.MUNKITOOLS_DIR

# import munkitools
sys.path.append(MUNKITOOLS_DIR)

try:
GIT = settings.GIT_PATH
except AttributeError:
GIT = None
from munkilib.admin import makecatalogslib
from munkilib.wrappers import (readPlistFromString, writePlistToString)
from munkilib import munkirepo
except ImportError:
LOGGER.error('Failed to import munkilib')
raise

# connect to the munki repo
try:
repo = munkirepo.connect(MUNKI_REPO_URL, MUNKI_REPO_PLUGIN)
except munkirepo.RepoError as err:
print(u'Repo error: %s' % err, file=sys.stderr)
raise


class FileError(Exception):
Expand Down Expand Up @@ -50,225 +60,69 @@ class FileAlreadyExistsError(FileError):
pass


class Plist(object):
class MunkiRepo(object):
'''Pseudo-Django object'''
@classmethod
def list(cls, kind):
'''Returns a list of available plists'''
kind_dir = os.path.join(REPO_DIR, kind)
plists = []
for dirpath, dirnames, filenames in os.walk(kind_dir):
record_status(
'%s_list_process' % kind,
message='Scanning %s...' % dirpath[len(kind_dir)+1:])
# don't recurse into directories that start with a period.
dirnames[:] = [name for name in dirnames
if not name.startswith('.')]
subdir = dirpath[len(kind_dir):].lstrip(os.path.sep)
if os.path.sep == '\\':
plists.extend([os.path.join(subdir, name).replace('\\', '/')
for name in filenames
if not name.startswith('.')])
else:
plists.extend([os.path.join(subdir, name)
for name in filenames
if not name.startswith('.')])
plists = repo.itemlist(kind)
return plists

@classmethod
def new(cls, kind, pathname, user, plist_data=None):
'''Returns a new plist object'''
filepath = os.path.join(REPO_DIR, kind, os.path.normpath(pathname))
if os.path.exists(filepath):
raise FileAlreadyExistsError(
'%s/%s already exists!' % (kind, pathname))
plist_parent_dir = os.path.dirname(filepath)
if not os.path.exists(plist_parent_dir):
try:
# attempt to create missing intermediate dirs
os.makedirs(plist_parent_dir)
except (IOError, OSError) as err:
LOGGER.error('Create failed for %s/%s: %s', kind, pathname, err)
raise FileWriteError(err)
if plist_data:
plist = plist_data
else:
# create a useful empty plist
if kind == 'manifests':
plist = {}
for section in [
'catalogs', 'included_manifests', 'managed_installs',
'managed_uninstalls', 'managed_updates',
'optional_installs']:
plist[section] = []
elif kind == "pkgsinfo":
plist = {
'name': 'ProductName',
'display_name': 'Display Name',
'description': 'Product description',
'version': '1.0',
'catalogs': ['development']
}
def get(cls, kind, pathname):
'''Reads a file and returns the contents'''
try:
plistFile=open(filepath,'wb')
plistlib.dump(plist, plistFile)
plistFile.close()
LOGGER.info('Created %s/%s', kind, pathname)
if user and GIT:
MunkiGit().add_file_at_path(filepath, user)
except (IOError, OSError) as err:
LOGGER.error('Create failed for %s/%s: %s', kind, pathname, err)
raise FileWriteError(err)
return plist
return repo.get(kind + '/' + pathname)
except munkirepo.RepoError as err:
LOGGER.error('Read failed for %s/%s: %s', kind, pathname, err)
raise FileReadError(err)

@classmethod
def read(cls, kind, pathname):
'''Reads a plist file and returns the plist as a dictionary'''
filepath = os.path.join(REPO_DIR, kind, os.path.normpath(pathname))
if not os.path.exists(filepath):
raise FileDoesNotExistError('%s/%s not found' % (kind, pathname))
try:
with open(filepath, 'rb') as fp:
plistdata = plistlib.load(fp)
return plistdata
except (IOError, OSError) as err:
return readPlistFromString(repo.get(kind + '/' + pathname))
except munkirepo.RepoError as err:
LOGGER.error('Read failed for %s/%s: %s', kind, pathname, err)
raise FileReadError(err)
except (ExpatError, IOError):
# could not parse, return empty dict
return {}


@classmethod
def write(cls, data, kind, pathname, user):
def write(cls, data, kind, pathname):
'''Writes a text data to (plist) file'''
filepath = os.path.join(REPO_DIR, kind, os.path.normpath(pathname))
plist_parent_dir = os.path.dirname(filepath)
if not os.path.exists(plist_parent_dir):
try:
# attempt to create missing intermediate dirs
os.makedirs(plist_parent_dir)
except OSError as err:
LOGGER.error('Create failed for %s/%s: %s', kind, pathname, err)
raise FileWriteError(err)
try:
fileref=open(filepath,'wb')
plistlib.dump(data, fileref)
fileref.close()
print('Writing %s to %s/%s' % (data, kind, pathname))
repo.put(kind + '/' + pathname, writePlistToString(data))
LOGGER.info('Wrote %s/%s', kind, pathname)
if user and GIT:
MunkiGit().add_file_at_path(filepath, user)
except (IOError, OSError) as err:
except munkirepo.RepoError as err:
LOGGER.error('Write failed for %s/%s: %s', kind, pathname, err)
raise FileWriteError(err)

@classmethod
def delete(cls, kind, pathname, user):
'''Deletes a plist file'''
filepath = os.path.join(REPO_DIR, kind, os.path.normpath(pathname))
if not os.path.exists(filepath):
raise FileDoesNotExistError(
'%s/%s does not exist' % (kind, pathname))
def writedata(cls, data, kind, pathname):
'''Writes a text data to file'''
try:
os.unlink(filepath)
LOGGER.info('Deleted %s/%s', kind, pathname)
if user and GIT:
MunkiGit().delete_file_at_path(filepath, user)
except (IOError, OSError) as err:
LOGGER.error('Delete failed for %s/%s: %s', kind, pathname, err)
raise FileDeleteError(err)


class MunkiFile(object):
'''Pseudo-Django object'''
@classmethod
def get_fullpath(cls, kind, pathname):
'''Returns full filesystem path to requested resource'''
return os.path.join(REPO_DIR, kind, pathname)

@classmethod
def list(cls, kind):
'''Returns a list of available plists'''
files_dir = os.path.join(REPO_DIR, kind)
files = []
for dirpath, dirnames, filenames in os.walk(files_dir):
# don't recurse into directories that start with a period.
dirnames[:] = [name for name in dirnames if not name.startswith('.')]
subdir = dirpath[len(files_dir):].lstrip(os.path.sep)
if os.path.sep == '\\':
files.extend([os.path.join(subdir, name).replace('\\', '/')
for name in filenames
if not name.startswith('.')])
else:
files.extend([os.path.join(subdir, name)
for name in filenames
if not name.startswith('.')])
return files

@classmethod
def new(cls, kind, fileupload, pathname, user):
'''Creates a new file from a file upload; returns
FileAlreadyExistsError if the file already exists at the path'''
filepath = os.path.join(REPO_DIR, kind, os.path.normpath(pathname))
if os.path.exists(filepath):
raise FileAlreadyExistsError(
'%s/%s already exists!' % (kind, pathname))
file_parent_dir = os.path.dirname(filepath)
if not os.path.exists(file_parent_dir):
try:
# attempt to create missing intermediate dirs
os.makedirs(file_parent_dir)
except (IOError, OSError) as err:
LOGGER.error(
'Create failed for %s/%s: %s', kind, pathname, err)
raise FileWriteError(err)
cls.write(kind, fileupload, pathname, user)

@classmethod
def write(cls, kind, fileupload, pathname, user):
'''Retreives a file upload and saves it to pathname'''
filepath = os.path.join(REPO_DIR, kind, os.path.normpath(pathname))
LOGGER.debug('Writing %s to %s', fileupload, filepath)
try:
with open(filepath, 'wb') as fileref:
for chunk in fileupload.chunks():
LOGGER.debug('Writing chunk...')
fileref.write(chunk)
if user and GIT and kind == "icons":
MunkiGit().add_file_at_path(filepath, user)
print('Writing %s to %s/%s' % (data, kind, pathname))
repo.put(kind + '/' + pathname, data)
LOGGER.info('Wrote %s/%s', kind, pathname)
except (IOError, OSError) as err:
LOGGER.error('Write failed for %s/%s: %s', kind, pathname, err)
raise FileWriteError(err)
except Exception as err:
except munkirepo.RepoError as err:
LOGGER.error('Write failed for %s/%s: %s', kind, pathname, err)
raise FileWriteError(err)

@classmethod
def writedata(cls, kind, filedata, pathname, user):
'''Retreives a file upload and saves it to pathname'''
filepath = os.path.join(REPO_DIR, kind, os.path.normpath(pathname))
try:
with open(filepath, 'wb') as fileref:
fileref.write(filedata)
if user and GIT and kind == "icons":
MunkiGit().add_file_at_path(filepath, user)
LOGGER.info('Wrote %s/%s', kind, pathname)
except (IOError, OSError) as err:
LOGGER.error('Write failed for %s/%s: %s', kind, pathname, err)
raise FileWriteError(err)

@classmethod
def delete(cls, kind, pathname, user):
'''Deletes file at pathname'''
filepath = os.path.join(REPO_DIR, kind, os.path.normpath(pathname))
if not os.path.exists(filepath):
raise FileDoesNotExistError(
'%s/%s does not exist' % (kind, pathname))
def delete(cls, kind, pathname):
'''Deletes a plist file'''
try:
os.unlink(filepath)
if user and GIT and kind == "icons":
MunkiGit().delete_file_at_path(filepath, user)
repo.delete(kind + '/' + pathname)
LOGGER.info('Deleted %s/%s', kind, pathname)
except (IOError, OSError) as err:
except munkirepo.RepoError as err:
LOGGER.error('Delete failed for %s/%s: %s', kind, pathname, err)
raise FileDeleteError(err)
raise FileDeleteError(err)

@classmethod
def makecatalogs(cls, output_fn=print):
'''Calls makecatalogs'''
try:
makecatalogslib.makecatalogs(repo, {}, output_fn=output_fn)
except makecatalogslib.MakeCatalogsError as err:
LOGGER.error('makecatalogs failed: %s', err)
raise FileError(err)
17 changes: 15 additions & 2 deletions api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,30 @@ class Meta:
depth = 1

class ManifestSerializer(ModelSerializer):
catalogs = serializers.ListField(child=serializers.CharField(), required=False)
conditional_items = serializers.ListField(child=serializers.DictField(), required=False)
default_installs = serializers.ListField(child=serializers.CharField(), required=False)
featured_items = serializers.ListField(child=serializers.CharField(), required=False)
included_manifests = serializers.ListField(child=serializers.CharField(), required=False)
managed_installs = serializers.ListField(child=serializers.CharField(), required=False)
managed_uninstalls = serializers.ListField(child=serializers.CharField(), required=False)
managed_updates = serializers.ListField(child=serializers.CharField(), required=False)
optional_installs = serializers.ListField(child=serializers.CharField(), required=False)
included_manifests = serializers.ListField(child=serializers.CharField(), required=False)



display_name = serializers.CharField(required=False, allow_blank=True)
class Meta:
model = ManifestFile
fields = [
'catalogs',
'conditional_items',
'default_installs',
'featured_items',
'included_manifests',
'managed_installs',
'managed_uninstalls',
'managed_updates',
'optional_installs',
'included_manifests',
'display_name'
]
Loading
Loading