Last active
January 25, 2019 19:19
-
-
Save homebysix/382a56f4d7c3429dd92f8da9ff590ab3 to your computer and use it in GitHub Desktop.
MunkiPkg linting
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
variables: | |
TZ: America/Los_Angeles | |
munkipkg_linting: | |
script: python munkipkg_linting.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
import logging | |
import os | |
import plistlib | |
import json | |
import sys | |
from xml.parsers.expat import ExpatError | |
from glob import glob | |
from distutils.version import StrictVersion | |
try: | |
import yaml | |
YAML_INSTALLED = True | |
except ImportError: | |
YAML_INSTALLED = False | |
BUILD_INFO_NAMES = ('build-info.plist', 'build-info.json', 'build-info.yaml', 'build-info.yml') | |
# Object for handling log output. | |
logging.basicConfig(stream=sys.stdout, | |
format='%(asctime)s [%(levelname)s] %(message)s', | |
level=logging.INFO) | |
logger = logging.getLogger() | |
logger.setLevel(logging.INFO) | |
def test_executables(status, project): | |
'''Ensures that all outset scripts included in MunkiPkg projects are executable.''' | |
for script in glob(project + '/payload/usr/local/outset/*/*'): | |
if not os.access(script, os.X_OK): | |
logger.error('%s is an outset script, but is not set to executable.', script) | |
status = False | |
return status | |
def test_gitignore(status, project): | |
'''Ensures that all MunkiPkg projects contain a .gitignore file.''' | |
if not os.path.isfile(os.path.join(project, '.gitignore')): | |
logger.error('%s does not contain a .gitignore file.', project) | |
status = False | |
return status | |
def test_build_info(status, project): | |
'''Ensures that build-info files are valid, and returns dict of contents for further testing.''' | |
info_dict = None | |
for info_path in (os.path.join(project, info_name) for info_name in BUILD_INFO_NAMES): | |
if os.path.isfile(info_path): | |
try: | |
if info_path.endswith('.plist'): | |
info_dict = plistlib.readPlist(info_path) | |
break | |
elif info_path.endswith('.json'): | |
with open(info_path, 'r') as openfile: | |
info_dict = json.load(openfile) | |
break | |
elif info_path.endswith(('.yaml', '.yml')): | |
if YAML_INSTALLED: | |
with open(info_path, 'r') as openfile: | |
info_dict = yaml.load(openfile) | |
break | |
else: | |
logger.warning('Cannot import yaml, skipping build info: %s', info_path) | |
except (ExpatError, ValueError, yaml.scanner.ScannerError) as err: | |
logger.error('%s is not a valid %s file:\n' | |
'%s', info_path, info_path.split('.')[-1], err) | |
return status, info_dict | |
def test_bundle_id(status, project, info_dict): | |
'''Ensures that all MunkiPkg projects use company-specific bundle identifiers.''' | |
required_prefix = 'com.example.' | |
if not 'identifier' in info_dict: | |
logger.error('The build-info file for %s does not have a bundle identifier.', project) | |
status = False | |
elif not info_dict['identifier'].startswith(required_prefix): | |
logger.error('The bundle identifier for %s does not start with %s.', project, required_prefix) | |
status = False | |
return status | |
def test_version(status, project, info_dict): | |
'''Ensures that all MunkiPkg projects have parseable versions.''' | |
if not 'version' in info_dict: | |
logger.error('The build-info file for %s does not have a version.', project) | |
status = False | |
else: | |
try: | |
version = StrictVersion(info_dict['version']) | |
if version < StrictVersion('1.0'): | |
logger.error('The version for %s is less than 1.0: %s', project, version) | |
status = False | |
except ValueError: | |
logger.error('The version for %s is not valid: %s', project, info_dict['version']) | |
status = False | |
except TypeError: | |
logger.error('The version for %s is stored as a %s, but should be a string: ' | |
'%s', project, type(info_dict['version']), info_dict['version']) | |
status = False | |
return status | |
def test_name(status, project, info_dict): | |
'''Ensures that all MunkiPkg projects have parseable versions.''' | |
if not 'name' in info_dict: | |
logger.error('The build-info file for %s does not have a name.', project) | |
status = False | |
elif '-${version}' not in info_dict['name']: | |
logger.error('The name for %s does not contain a version variable: ' | |
'%s', project, info_dict['name']) | |
status = False | |
elif not info_dict['name'].endswith('.pkg'): | |
logger.error('The name for %s does not end with .pkg: ' | |
'%s', project, info_dict['name']) | |
status = False | |
return status | |
def main(): | |
'''Main process.''' | |
status = True | |
# Extensions to include in linting. | |
munkipkg_project_dirs = [] | |
# Gather list of eligible files. | |
for root, dirs, files in os.walk('.'): | |
dirs[:] = [d for d in dirs if not d.startswith('.')] | |
if any(build_info in files for build_info in BUILD_INFO_NAMES): | |
munkipkg_project_dirs.append(root) | |
# Process files. | |
for project in munkipkg_project_dirs: | |
status = test_executables(status, project) | |
status = test_gitignore(status, project) | |
status, info_dict = test_build_info(status, project) | |
status = test_bundle_id(status, project, info_dict) | |
status = test_version(status, project, info_dict) | |
status = test_name(status, project, info_dict) | |
if status: | |
logger.info('Tested %d munkipkg projects. All tests passed.', len(munkipkg_project_dirs)) | |
else: | |
logger.error('Tested %d munkipkg projects. Some tests failed. See details above.', len(munkipkg_project_dirs)) | |
sys.exit(1) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment