From a861d5e9bc04dacfa66f1e042ea319a829d1bc63 Mon Sep 17 00:00:00 2001 From: James Felton Date: Wed, 17 Jun 2020 10:17:59 -0500 Subject: [PATCH 1/5] Prevents duplicate call in get Script template --- sync.py | 43 +++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/sync.py b/sync.py index 2319139..812126c 100755 --- a/sync.py +++ b/sync.py @@ -201,13 +201,7 @@ async def get_ea_template(session, url, user, passwd, ext_attr): auth=auth, headers=headers) as resp: if resp.status == 200: - async with session.get( - url + - '/JSSResource/computerextensionattributes/name/' + - ext_attr, - auth=auth, - headers=headers) as response: - template = ET.fromstring(await response.text()) + template = ET.fromstring(await resp.text()) else: template = ET.parse(join(mypath, 'templates/ea.xml')).getroot() @@ -235,12 +229,13 @@ async def get_ea_template(session, url, user, passwd, ext_attr): async def upload_scripts(session, url, user, passwd, semaphore): mypath = dirname(realpath(__file__)) + scripts = [] if not changed_scripts and not args.update_all: print('No Changes in Scripts') - scripts = [ - f.name for f in os.scandir(join(mypath, 'scripts')) if f.is_dir() - and f.name in changed_scripts - ] + scripts = [ + f.name for f in os.scandir(join(mypath, 'scripts')) if f.is_dir() + and f.name in changed_scripts + ] if args.update_all: print('Copying all scripts...') scripts = [ @@ -253,6 +248,7 @@ async def upload_scripts(session, url, user, passwd, semaphore): task = asyncio.ensure_future( upload_script(session, url, user, passwd, script, semaphore)) tasks.append(task) + await asyncio.gather(*tasks) @@ -323,11 +319,7 @@ async def get_script_template(session, url, user, passwd, script): auth=auth, headers=headers) as resp: if resp.status == 200: - async with session.get( - url + '/JSSResource/scripts/name/' + script, - auth=auth, - headers=headers) as response: - template = ET.fromstring(await response.text()) + template = ET.fromstring(await resp.text()) else: template = ET.parse(join( mypath, 'templates/script.xml')).getroot() @@ -377,16 +369,15 @@ async def main(): # pylint: disable=global-statement global CATEGORIES semaphore = asyncio.BoundedSemaphore(args.limit) - async with aiohttp.ClientSession() as session: - async with aiohttp.ClientSession( - connector=aiohttp.TCPConnector( - ssl=args.do_not_verify_ssl)) as session: - CATEGORIES = await get_existing_categories( - session, args.url, args.username, args.password, semaphore) - await upload_scripts(session, args.url, args.username, - args.password, semaphore) - await upload_extension_attributes(session, args.url, args.username, - args.password, semaphore) + async with aiohttp.ClientSession( + connector=aiohttp.TCPConnector( + ssl=args.do_not_verify_ssl)) as session: + CATEGORIES = await get_existing_categories( + session, args.url, args.username, args.password, semaphore) + await upload_scripts(session, args.url, args.username, + args.password, semaphore) + await upload_extension_attributes(session, args.url, args.username, + args.password, semaphore) if __name__ == '__main__': From c96abaa608ce585f461929be215166b4f835deac Mon Sep 17 00:00:00 2001 From: James Felton Date: Wed, 17 Jun 2020 10:49:25 -0500 Subject: [PATCH 2/5] updated requirements, added ID to template, reformatted --- requirements.txt | 7 ++++--- tools/download.py | 44 ++++++++++++++++++++++---------------------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1871bbf..34dce32 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ -aiohttp +aiohttp~=3.6.2 cchardet aiodns -uvloop -requests +uvloop~=0.14.0 +requests~=2.23.0 +urllib3~=1.25.9 \ No newline at end of file diff --git a/tools/download.py b/tools/download.py index 553bf78..a69ce0b 100755 --- a/tools/download.py +++ b/tools/download.py @@ -1,8 +1,9 @@ #!/usr/bin/env python import getpass +from xml.dom import minidom + import requests from xml.etree import ElementTree as ET -from xml.dom import minidom import os import argparse import urllib3 @@ -12,7 +13,8 @@ mypath = os.path.dirname(os.path.realpath(__file__)) -def download_scripts(mode, overwrite=None,): + +def download_scripts(mode, overwrite=None, ): """ Downloads Scripts to ./scripts and Extension Attributes to ./extension_attributes Folder Structure: @@ -46,12 +48,11 @@ def download_scripts(mode, overwrite=None,): download_path = 'scripts' script_xml = 'script_contents' - # Get all IDs of resource type - r = requests.get(args.url + '/JSSResource/%s' %resource, - auth = (args.username, password), - headers= {'Accept': 'application/xml','Content-Type': 'application/xml'}, - verify=args.do_not_verify_ssl) + r = requests.get(args.url + '/JSSResource/%s' % resource, + auth=(args.username, password), + headers={'Accept': 'application/xml', 'Content-Type': 'application/xml'}, + verify=args.do_not_verify_ssl) # Basic error handling if r.status_code != 200: @@ -60,13 +61,14 @@ def download_scripts(mode, overwrite=None,): Here is the HTTP Status code: %s" % r.status_code) exit(1) tree = ET.fromstring(r.content) - resource_ids = [ e.text for e in tree.findall('.//id') ] + resource_ids = [e.text for e in tree.findall('.//id')] # Download each resource and save to disk for resource_id in resource_ids: - r = requests.get(args.url + '/JSSResource/%s/id/%s' % (resource,resource_id), - auth = (args.username, password), - headers= {'Accept': 'application/xml','Content-Type': 'application/xml'}, verify=args.do_not_verify_ssl) + r = requests.get(args.url + '/JSSResource/%s/id/%s' % (resource, resource_id), + auth=(args.username, password), + headers={'Accept': 'application/xml', 'Content-Type': 'application/xml'}, + verify=args.do_not_verify_ssl) tree = ET.fromstring(r.content) if mode == 'ea': @@ -75,7 +77,7 @@ def download_scripts(mode, overwrite=None,): continue # Determine resource path (folder name) - resource_path = os.path.join(mypath, '..', download_path ,tree.find('name').text) + resource_path = os.path.join(mypath, '..', download_path, tree.find('name').text) # Check to see if it exists if os.path.exists(resource_path): @@ -85,14 +87,13 @@ def download_scripts(mode, overwrite=None,): print("\tSkipping: ", tree.find('name').text) continue - - else: # Make the folder + else: # Make the folder os.makedirs(resource_path) print('Saving: ', tree.find('name').text) # Create script string, and determine the file extension - xmlstr = ET.tostring(tree.find(script_xml), encoding='unicode', method='text').replace('\r','') + xmlstr = ET.tostring(tree.find(script_xml), encoding='unicode', method='text').replace('\r', '') if xmlstr.startswith('#!/bin/sh'): ext = '.sh' elif xmlstr.startswith('#!/usr/bin/env sh'): @@ -113,15 +114,15 @@ def download_scripts(mode, overwrite=None,): ext = '.rb' else: print('No interpreter directive found for: ', tree.find('name').text) - ext = '.sh' # Call it sh for now so the uploader detects it + ext = '.sh' # Call it sh for now so the uploader detects it - with open(os.path.join(resource_path, '%s%s' % (mode,ext)), 'w') as f: + with open(os.path.join(resource_path, '%s%s' % (mode, ext)), 'w') as f: f.write(xmlstr) - # Need to remove ID and script contents and write out xml + # Need to remove script contents and write out xml try: tree.find(script_xml).clear() - tree.remove(tree.find('id')) + # tree.remove(tree.find('id')) tree.remove(tree.find('script_contents_encoded')) tree.remove(tree.find('filename')) except: @@ -132,14 +133,13 @@ def download_scripts(mode, overwrite=None,): f.write(xmlstr) - if __name__ == '__main__': parser = argparse.ArgumentParser(description='Download Scripts from Jamf') parser.add_argument('--url') parser.add_argument('--username') parser.add_argument('--password') - parser.add_argument('--overwrite', action='store_true') # Overwrites existing files - parser.add_argument('--do_not_verify_ssl', action='store_false') # Skips SSL verification + parser.add_argument('--overwrite', action='store_true') # Overwrites existing files + parser.add_argument('--do_not_verify_ssl', action='store_false') # Skips SSL verification args = parser.parse_args() # Ask for password if not supplied via command line args From 5efd83495af9a1e8a20e73f9ae5a95f6a9a53130 Mon Sep 17 00:00:00 2001 From: James Felton Date: Thu, 18 Jun 2020 11:49:51 -0500 Subject: [PATCH 3/5] finished revisions --- sync.py | 377 ++++++++++++++++++++++++++------------------------------ 1 file changed, 178 insertions(+), 199 deletions(-) diff --git a/sync.py b/sync.py index 812126c..d9ffef0 100755 --- a/sync.py +++ b/sync.py @@ -12,6 +12,17 @@ import async_timeout import aiohttp import uvloop +import requests +import json + + +TEMPLATE_ID = 'id' + +EA_FOLDER_NAME = 'extension_attributes' + +SCRIPTS_FOLDER_NAME = 'scripts' +TEMPLATE_NAME = 'name' +TEMPLATE_CATEGORY = 'category' logging.basicConfig( level=logging.DEBUG, @@ -25,9 +36,14 @@ # Use this variable to add a Slack emoji in front of each item if # you use a post-build action for a Slack custom message SLACK_EMOJI = ":white_check_mark: " -SUPPORTED_SCRIPT_EXTENSIONS = ('sh', 'py', 'pl', 'swift', 'rb') -SUPPORTED_EA_EXTENSIONS = ('sh', 'py', 'pl', 'swift', 'rb') +SUPPORTED_SCRIPT_EXTENSIONS = ['sh', 'py', 'pl', 'swift', 'rb'] +SUPPORTED_EA_EXTENSIONS = ['sh', 'py', 'pl', 'swift', 'rb'] CATEGORIES = [] +AUTH = None +CLASSIC_HEADERS = { + 'Accept': 'application/xml', + 'Content-Type': 'application/xml' +} def check_for_changes(): @@ -42,16 +58,16 @@ def check_for_changes(): if args.jenkins: git_changes = os.popen( "git diff --name-only $GIT_PREVIOUS_COMMIT $GIT_COMMIT").read( - ).split('\n') + ).split('\n') # Compare the last two commits to determine the list of files that # were changed else: git_commits = os.popen( 'git log -2 --pretty=oneline --pretty=format:"%h"').read().split( - '\n') + '\n') command = "git diff --name-only" + " " + \ - git_commits[1] + " " + git_commits[0] + git_commits[1] + " " + git_commits[0] git_changes = os.popen(command).read().split('\n') for i in git_changes: @@ -94,300 +110,257 @@ def write_jenkins_file(): f.write(contents) -async def upload_extension_attributes(session, url, user, passwd, semaphore): +async def upload_extension_attributes(session, url, semaphore): mypath = dirname(realpath(__file__)) if not changed_ext_attrs and not args.update_all: print('No Changes in Extension Attributes') return ext_attrs = [ - f.name for f in os.scandir(join(mypath, 'extension_attributes')) + f.name for f in os.scandir(join(mypath, EA_FOLDER_NAME)) if f.is_dir() and f.name in changed_ext_attrs ] if args.update_all: print("Copying all extension attributes...") ext_attrs = [ - f.name for f in os.scandir(join(mypath, 'extension_attributes')) + f.name for f in os.scandir(join(mypath, EA_FOLDER_NAME)) if f.is_dir() ] tasks = [] for ea in ext_attrs: task = asyncio.ensure_future( - upload_extension_attribute(session, url, user, passwd, ea, - semaphore)) + upload_extension_attribute(session, url, ea, semaphore)) tasks.append(task) await asyncio.gather(*tasks) -async def upload_extension_attribute(session, url, user, passwd, ext_attr, - semaphore): - mypath = dirname(realpath(__file__)) - auth = aiohttp.BasicAuth(user, passwd) - headers = {'Accept': 'application/xml', 'Content-Type': 'application/xml'} - # Get the script files within the folder, we'll only use - # script_file[0] in case there are multiple files - script_file = [ - f.name for f in os.scandir(join('extension_attributes', ext_attr)) - if f.is_file() and f.name.split('.')[-1] in SUPPORTED_EA_EXTENSIONS - ] - if script_file == []: - print('Warning: No script file found in extension_attributes/%s' % - ext_attr) - return # Need to skip if no script. - with open( - join(mypath, 'extension_attributes', ext_attr, script_file[0]), - 'r') as f: - data = f.read() +async def upload_extension_attribute(session, url, ext_attr, semaphore): + print('uploading ', ext_attr) + + script_file = (EA_FOLDER_NAME, ext_attr) + if not script_file: + return + async with semaphore: with async_timeout.timeout(args.timeout): - template = await get_ea_template(session, url, user, passwd, - ext_attr) + template = await get_template(session, url, EA_FOLDER_NAME, ext_attr, + '/JSSResource/computerextensionattributes/name/') + ea_name = template.find(TEMPLATE_NAME).text async with session.get( - url + '/JSSResource/computerextensionattributes/name/' + - template.find('name').text, - auth=auth, - headers=headers) as resp: - template.find('input_type/script').text = data + url + '/JSSResource/computerextensionattributes/name/' + ea_name, + auth=AUTH, + headers=CLASSIC_HEADERS) as resp: + template.find('input_type/script').text = script_file if args.verbose: print(ET.tostring(template)) print('response status initial get: ', resp.status) if resp.status == 200: - put_url = url + '/JSSResource/computerextensionattributes/name/' + \ - template.find('name').text + put_url = url + '/JSSResource/computerextensionattributes/name/' + ea_name resp = await session.put( put_url, - auth=auth, + auth=AUTH, data=ET.tostring(template), - headers=headers) + headers=CLASSIC_HEADERS) else: post_url = url + '/JSSResource/computerextensionattributes/id/0' resp = await session.post( post_url, - auth=auth, + auth=AUTH, data=ET.tostring(template), - headers=headers) + headers=CLASSIC_HEADERS) if args.verbose: print('response status: ', resp.status) print('EA: ', ext_attr) - print('EA Name: ', template.find('name').text) + print('EA Name: ', template.find(TEMPLATE_NAME).text) if resp.status in (201, 200): - print('Uploaded Extension Attribute: %s' % template.find('name').text) + print('Uploaded Extension Attribute: %s' % template.find(TEMPLATE_NAME).text) else: - print('Error uploading script: %s' % template.find('name').text) + print('Error uploading script: %s' % template.find(TEMPLATE_NAME).text) print('Error: %s' % resp.status) return resp.status -async def get_ea_template(session, url, user, passwd, ext_attr): - auth = aiohttp.BasicAuth(user, passwd) - mypath = dirname(realpath(__file__)) - xml_file = [ - f.name for f in os.scandir(join('extension_attributes', ext_attr)) - if f.is_file() and f.name.split('.')[-1] in 'xml' - ] - try: - with open( - join(mypath, 'extension_attributes', ext_attr, xml_file[0]), - 'r') as file: - template = ET.fromstring(file.read()) - except IndexError: - with async_timeout.timeout(args.timeout): - headers = { - 'Accept': 'application/xml', - 'Content-Type': 'application/xml' - } - async with session.get( - url + '/JSSResource/computerextensionattributes/name/' + - ext_attr, - auth=auth, - headers=headers) as resp: - if resp.status == 200: - template = ET.fromstring(await resp.text()) - else: - template = ET.parse(join(mypath, - 'templates/ea.xml')).getroot() - # name is mandatory, so we use the foldername if nothing is set in - # a template - if args.verbose: - print(ET.tostring(template)) - if template.find('category') and template.find( - 'category').text not in CATEGORIES: - ET.SubElement(template, 'category').text = 'None' - if args.verbose: - c = template.find('category').text - print( - f'''WARNING: Unable to find category {c} in the JSS, - setting to None''' - ) - if template.find('name') is None: - ET.SubElement(template, 'name').text = ext_attr - elif not template.find('name').text or template.find( - 'name').text is None: - template.find('name').text = ext_attr - return template - - -async def upload_scripts(session, url, user, passwd, semaphore): - mypath = dirname(realpath(__file__)) +async def upload_scripts(session, url, semaphore): + my_path = dirname(realpath(__file__)) scripts = [] - if not changed_scripts and not args.update_all: + if changed_scripts and not args.update_all: print('No Changes in Scripts') scripts = [ - f.name for f in os.scandir(join(mypath, 'scripts')) if f.is_dir() - and f.name in changed_scripts + f.name for f in os.scandir(join(my_path, SCRIPTS_FOLDER_NAME)) if f.is_dir() and f.name in changed_scripts ] if args.update_all: print('Copying all scripts...') scripts = [ - f.name for f in os.scandir(join(mypath, 'scripts')) + f.name for f in os.scandir(join(my_path, SCRIPTS_FOLDER_NAME)) if f.is_dir() ] tasks = [] for script in scripts: task = asyncio.ensure_future( - upload_script(session, url, user, passwd, script, semaphore)) + upload_script(session, url, script, semaphore)) tasks.append(task) await asyncio.gather(*tasks) -async def upload_script(session, url, user, passwd, script, semaphore): - mypath = dirname(realpath(__file__)) - auth = aiohttp.BasicAuth(user, passwd) - headers = {'Accept': 'application/xml', 'Content-Type': 'application/xml'} - script_file = [ - f.name for f in os.scandir(join('scripts', script)) - if f.is_file() and f.name.split('.')[-1] in SUPPORTED_SCRIPT_EXTENSIONS - ] - if script_file == []: - print('Warning: No script file found in scripts/%s' % script) - return # Need to skip if no script. - with open(join(mypath, 'scripts', script, script_file[0]), 'r') as f: - data = f.read() +async def upload_script(session, url: str, script: str, semaphore): + new_script_contents = file_contents(SCRIPTS_FOLDER_NAME, script, SUPPORTED_SCRIPT_EXTENSIONS) + if not new_script_contents: + return + async with semaphore: with async_timeout.timeout(args.timeout): - template = await get_script_template(session, url, user, passwd, - script) - async with session.get( - url + '/JSSResource/scripts/name/' + - template.find('name').text, - auth=auth, - headers=headers) as resp: - template.find('script_contents').text = data - if resp.status == 200: - put_url = url + '/JSSResource/scripts/name/' + \ - template.find('name').text - resp = await session.put( - put_url, - auth=auth, - data=ET.tostring(template), - headers=headers) - else: - post_url = url + '/JSSResource/scripts/id/0' - resp = await session.post( - post_url, - auth=auth, - data=ET.tostring(template), - headers=headers) - if resp.status in (201, 200): - print('Uploaded script: %s' % template.find('name').text) - else: - print('Error uploading script: %s' % template.find('name').text) - print('Error: %s' % resp.status) + template = await get_template(session, url, SCRIPTS_FOLDER_NAME, script, '/JSSResource/scripts/name') + + script_id = template.find(TEMPLATE_ID).text + script_name = template.find(TEMPLATE_NAME).text + + resp = await session.get( + url + '/uapi/v1/scripts/' + script_id, + headers=HEADERS) + + if resp.status != 200: + print("Error obtaining existing script: " + script_name) + return resp.status + + script_to_put = await resp.json() + script_to_put['scriptContents'] = new_script_contents + + resp = await session.put( + url + '/uapi/v1/scripts/' + script_id, + data=json.dumps(script_to_put), + headers=HEADERS) + + if resp.status in (400, 404, 415): + resp = await session.post( + url + '/uapi/v1/scripts', + data=script_to_put, + headers=HEADERS) + + if resp.status in (201, 200): + print('Uploaded script: %s' % script_name) + else: + print('Error uploading script: %s' % script_name) + print('Error: %s' % resp.status) + return resp.status -async def get_script_template(session, url, user, passwd, script): - auth = aiohttp.BasicAuth(user, passwd) - mypath = dirname(realpath(__file__)) - xml_file = [ - f.name for f in os.scandir(join('scripts', script)) - if f.is_file() and f.name.split('.')[-1] in 'xml' - ] +async def get_template(session, url: str, dir_name: str, file_name: str, endpoint: str): try: - with open(join(mypath, 'scripts', script, xml_file[0]), 'r') as file: - template = ET.fromstring(file.read()) + script_content = file_contents(dir_name, file_name, ['xml']) + template = ET.fromstring(script_content) except IndexError: with async_timeout.timeout(args.timeout): - headers = { - 'Accept': 'application/xml', - 'Content-Type': 'application/xml' - } + async with session.get( - url + '/JSSResource/scripts/name/' + script, - auth=auth, - headers=headers) as resp: + url + endpoint + file_name, + auth=AUTH, + headers=CLASSIC_HEADERS) as resp: if resp.status == 200: template = ET.fromstring(await resp.text()) else: - template = ET.parse(join( - mypath, 'templates/script.xml')).getroot() + template = ET.parse(join(dirname(realpath(__file__)), 'templates/script.xml')).getroot() + # name is mandatory, so we use the filename if nothing is set in a template if args.verbose: print(ET.tostring(template)) - if template.find('category') is not None and template.find( - 'category').text not in CATEGORIES: - c = template.find('category').text - template.remove(template.find('category')) + + if template.find(TEMPLATE_CATEGORY) is not None and template.find(TEMPLATE_CATEGORY).text not in CATEGORIES: + c = template.find(TEMPLATE_CATEGORY).text + template.remove(template.find(TEMPLATE_CATEGORY)) if args.verbose: print( f'''WARNING: Unable to find category "{c}" in the JSS, - setting to None''' - ) - if template.find('name') is None: - ET.SubElement(template, 'name').text = script - elif not template.find('name').text or template.find( - 'name').text is None: - template.find('name').text = script + setting to None''') + if template.find(TEMPLATE_NAME) is None: + ET.SubElement(template, TEMPLATE_NAME).text = file_name + elif not template.find(TEMPLATE_NAME).text or template.find( + TEMPLATE_NAME).text is None: + template.find(TEMPLATE_NAME).text = file_name return template -async def get_existing_categories(session, url, user, passwd, semaphore): - auth = aiohttp.BasicAuth(user, passwd) - headers = { - 'Accept': 'application/xml', - 'Content-Type': 'application/xml' - } +async def get_existing_categories(session, url: str, semaphore): + all_categories = list() + page = 0 + more_to_find = True async with semaphore: with async_timeout.timeout(args.timeout): - async with session.get( - url + '/JSSResource/categories', - auth=auth, - headers=headers) as resp: - if resp.status in (201, 200): - return [ - c.find('name').text for c in [ - e for e in ET.fromstring(await resp.text()). - findall('category') - ] - ] - return [] + + while more_to_find: + async with session.get( + url + '/uapi/v1/categories?page=' + str(page), + headers=HEADERS) as resp: + + if resp.status in (200, 201): + categories_json = await resp.json() + + if categories_json['totalCount'] < 100: + more_to_find = False + + all_categories.extend(e[TEMPLATE_NAME] for e in categories_json['results']) + page = page + 1 + else: + print('Error retrieving all categories') + return all_categories + + return all_categories async def main(): - # pylint: disable=global-statement - global CATEGORIES semaphore = asyncio.BoundedSemaphore(args.limit) async with aiohttp.ClientSession( connector=aiohttp.TCPConnector( ssl=args.do_not_verify_ssl)) as session: - CATEGORIES = await get_existing_categories( - session, args.url, args.username, args.password, semaphore) - await upload_scripts(session, args.url, args.username, - args.password, semaphore) - await upload_extension_attributes(session, args.url, args.username, - args.password, semaphore) + CATEGORIES.extend(await get_existing_categories( + session, args.url, semaphore)) + await upload_scripts(session, args.url, semaphore) + await upload_extension_attributes(session, args.url, semaphore) + + +def file_contents(folder_name: str, file_name: str, extensions: list): + file = [ + f.name for f in os.scandir(join(folder_name, file_name)) + if f.is_file() and f.name.split('.')[-1] in extensions + ] + + if not file: + print('Warning: No contents found for file/%s in dir /%s' % file_name, folder_name) + return None # Need to skip if no script. + + my_path = dirname(realpath(__file__)) + with open(join(my_path, folder_name, file_name, file[0]), 'r') as f: + return f.read() + + +def configure_auth(arguments) -> (str, dict, str): + auth = aiohttp.BasicAuth(arguments.username, arguments.password) + + r = requests.post(args.url + "/uapi/auth/tokens", auth=(arguments.username, arguments.password)) + token = '' + if r.status_code == 200: + token = r.json()['token'] + else: + print('Could not authenticate to Jamf Pro with supplied username and password.') + exit(1) + + headers = {"Authorization": "Bearer " + token, + "Accept": "application/json", + "Content-Type": "application/json"} + + return auth, headers, token if __name__ == '__main__': asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) - parser = argparse.ArgumentParser(description='Sync repo with JamfPro') + parser = argparse.ArgumentParser(description='Sync repo with Jamf Pro') parser.add_argument('--url') parser.add_argument('--username') parser.add_argument('--password') - parser.add_argument('--limit', type=int, default=25) + parser.add_argument('--limit', type=int, default=5) parser.add_argument('--timeout', type=int, default=60) parser.add_argument('--verbose', action='store_true') parser.add_argument('--do_not_verify_ssl', action='store_false') @@ -395,6 +368,10 @@ async def main(): parser.add_argument('--jenkins', action='store_true') args = parser.parse_args() + if args.limit > 25: + print('limit argument exceeded 25, setting it to 25') + args.limit = 25 + changed_ext_attrs = [] changed_scripts = [] check_for_changes() @@ -408,6 +385,8 @@ async def main(): if not args.password: args.password = getpass.getpass() + AUTH, HEADERS, TOKEN = configure_auth(args) + loop = asyncio.get_event_loop() if args.verbose: From 3447446f42e2c4ca2195565462dd54a91512d647 Mon Sep 17 00:00:00 2001 From: James Felton Date: Thu, 18 Jun 2020 12:16:08 -0500 Subject: [PATCH 4/5] python style conformance --- sync.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sync.py b/sync.py index d9ffef0..823c003 100755 --- a/sync.py +++ b/sync.py @@ -111,18 +111,18 @@ def write_jenkins_file(): async def upload_extension_attributes(session, url, semaphore): - mypath = dirname(realpath(__file__)) + my_path = dirname(realpath(__file__)) if not changed_ext_attrs and not args.update_all: print('No Changes in Extension Attributes') return ext_attrs = [ - f.name for f in os.scandir(join(mypath, EA_FOLDER_NAME)) + f.name for f in os.scandir(join(my_path, EA_FOLDER_NAME)) if f.is_dir() and f.name in changed_ext_attrs ] if args.update_all: print("Copying all extension attributes...") ext_attrs = [ - f.name for f in os.scandir(join(mypath, EA_FOLDER_NAME)) + f.name for f in os.scandir(join(my_path, EA_FOLDER_NAME)) if f.is_dir() ] tasks = [] From 46d1688a9310c796f94320a59675fc06feaea8cc Mon Sep 17 00:00:00 2001 From: James Felton Date: Thu, 18 Jun 2020 16:41:42 -0500 Subject: [PATCH 5/5] fixed missing ID on script template files --- sync.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sync.py b/sync.py index 823c003..03f4ba8 100755 --- a/sync.py +++ b/sync.py @@ -136,7 +136,7 @@ async def upload_extension_attributes(session, url, semaphore): async def upload_extension_attribute(session, url, ext_attr, semaphore): print('uploading ', ext_attr) - script_file = (EA_FOLDER_NAME, ext_attr) + script_file = file_contents(EA_FOLDER_NAME, ext_attr, SUPPORTED_SCRIPT_EXTENSIONS) if not script_file: return @@ -211,10 +211,10 @@ async def upload_script(session, url: str, script: str, semaphore): async with semaphore: with async_timeout.timeout(args.timeout): - template = await get_template(session, url, SCRIPTS_FOLDER_NAME, script, '/JSSResource/scripts/name') + template = await get_template(session, url, SCRIPTS_FOLDER_NAME, script, '/JSSResource/scripts/name/') - script_id = template.find(TEMPLATE_ID).text script_name = template.find(TEMPLATE_NAME).text + script_id = template.find(TEMPLATE_ID).text resp = await session.get( url + '/uapi/v1/scripts/' + script_id, @@ -251,6 +251,9 @@ async def get_template(session, url: str, dir_name: str, file_name: str, endpoin try: script_content = file_contents(dir_name, file_name, ['xml']) template = ET.fromstring(script_content) + if template.find(TEMPLATE_ID) is None: + print('No ID found in XML file for', dir_name + '/' + file_name, 'retrieving it by name from Jamf Pro.') + raise IndexError except IndexError: with async_timeout.timeout(args.timeout):