Skip to content

Commit

Permalink
update API to rest framework (#34)
Browse files Browse the repository at this point in the history
* Add or update the Azure App Service build and deployment workflow config

* Update azure_app_testmwa2.yml

* Update azure_app_testmwa2.yml

* Update azure_app_testmwa2.yml

* update requirements

* update requirements

* update

* fix dev script

* update for munkirepo plugins

* update for MunkiRepo class

* update for munki plugins

* clean up

* fix manifest serializer

* add error handling

---------

Co-authored-by: Steve Küng <[email protected]>
  • Loading branch information
SteveKueng and Steve Küng committed Jun 23, 2024
1 parent 7838bbf commit bf5d909
Show file tree
Hide file tree
Showing 19 changed files with 295 additions and 1,126 deletions.
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

0 comments on commit bf5d909

Please sign in to comment.