def upload_computergroup( jamf_url, enc_creds, computergroup_name, template_contents, cli_custom_keys, verbosity, obj_id=None, ): """Upload computer group""" # if we find an object ID we put, if not, we post if obj_id: url = "{}/JSSResource/computergroups/id/{}".format(jamf_url, obj_id) else: url = "{}/JSSResource/computergroups/id/0".format(jamf_url) if verbosity > 2: print("Computer Group data:") print(template_contents) print("Uploading Computer Group...") # write the template to temp file template_xml = curl.write_temp_file(template_contents) count = 0 while True: count += 1 if verbosity > 1: print("Computer Group upload attempt {}".format(count)) method = "PUT" if obj_id else "POST" r = curl.request(method, url, enc_creds, verbosity, template_xml) # check HTTP response if curl.status_check(r, "Computer Group", computergroup_name) == "break": break if count > 5: print( "WARNING: Computer Group upload did not succeed after 5 attempts" ) print("\nHTTP POST Response Code: {}".format(r.status_code)) break sleep(30) if verbosity > 1: api_get.get_headers(r) # clean up temp files if os.path.exists(template_xml): os.remove(template_xml)
def delete(id, jamf_url, enc_creds, verbosity): """deletes a policy by obtained or set id""" url = "{}/JSSResource/policies/id/{}".format(jamf_url, id) count = 0 while True: count += 1 if verbosity > 1: print("Policy delete attempt {}".format(count)) request_type = "DELETE" r = curl.request(request_type, url, enc_creds, verbosity) # check HTTP response if curl.status_check(r, "Policy", id, request_type) == "break": break if count > 5: print("WARNING: Policy delete did not succeed after 5 attempts") print("\nHTTP POST Response Code: {}".format(r.status_code)) break sleep(30) if verbosity > 1: api_get.get_headers(r)
def upload_mobileconfig( jamf_url, enc_creds, mobileconfig_name, description, category, mobileconfig_plist, computergroup_name, template_contents, profile_uuid, verbosity, obj_id=None, ): """Update Configuration Profile metadata.""" # if we find an object ID we put, if not, we post if obj_id: url = "{}/JSSResource/osxconfigurationprofiles/id/{}".format(jamf_url, obj_id) else: url = "{}/JSSResource/osxconfigurationprofiles/id/0".format(jamf_url) # remove newlines, tabs, leading spaces, and XML-escape the payload mobileconfig_plist = mobileconfig_plist.decode("UTF-8") mobileconfig_list = mobileconfig_plist.rsplit("\n") mobileconfig_list = [x.strip("\t") for x in mobileconfig_list] mobileconfig_list = [x.strip(" ") for x in mobileconfig_list] mobileconfig = "".join(mobileconfig_list) # substitute user-assignable keys replaceable_keys = { "mobileconfig_name": mobileconfig_name, "description": description, "category": category, "payload": mobileconfig, "computergroup_name": computergroup_name, "uuid": "com.github.grahampugh.jamf-upload.{}".format(profile_uuid), } # substitute user-assignable keys (escaping for XML) template_contents = actions.substitute_assignable_keys( template_contents, replaceable_keys, verbosity, xml_escape=True ) if verbosity > 2: print("Configuration Profile to be uploaded:") print(template_contents) print("Uploading Configuration Profile..") # write the template to temp file template_xml = curl.write_temp_file(template_contents) count = 0 while True: count += 1 if verbosity > 1: print("Configuration Profile upload attempt {}".format(count)) method = "PUT" if obj_id else "POST" r = curl.request(method, url, enc_creds, verbosity, template_xml) # check HTTP response if curl.status_check(r, "Configuration Profile", mobileconfig_name) == "break": break if count > 5: print( "ERROR: Configuration Profile upload did not succeed after 5 attempts" ) print("\nHTTP POST Response Code: {}".format(r.status_code)) break sleep(10) if verbosity > 1: api_get.get_headers(r) return r
def upload_script( jamf_url, script_name, script_path, category_id, category_name, script_info, script_notes, script_priority, script_parameter4, script_parameter5, script_parameter6, script_parameter7, script_parameter8, script_parameter9, script_parameter10, script_parameter11, script_os_requirements, verbosity, token, cli_custom_keys, obj_id=None, ): """Update script metadata.""" # import script from file and replace any keys in the script # script_contents = Path(script_path).read_text() with open(script_path, "r") as file: script_contents = file.read() # substitute user-assignable keys # pylint is incorrectly stating that 'verbosity' has no value. So... # pylint: disable=no-value-for-parameter script_contents = actions.substitute_assignable_keys( script_contents, cli_custom_keys, verbosity) # priority has to be in upper case. Let's make it nice for the user if script_priority: script_priority = script_priority.upper() # build the object script_data = { "name": script_name, "info": script_info, "notes": script_notes, "priority": script_priority, "categoryId": category_id, "categoryName": category_name, "parameter4": script_parameter4, "parameter5": script_parameter5, "parameter6": script_parameter6, "parameter7": script_parameter7, "parameter8": script_parameter8, "parameter9": script_parameter9, "parameter10": script_parameter10, "parameter11": script_parameter11, "osRequirements": script_os_requirements, "scriptContents": script_contents, } # ideally we upload to the object ID but if we didn't get a good response # we fall back to the name if obj_id: url = "{}/uapi/v1/scripts/{}".format(jamf_url, obj_id) script_data["id"] = obj_id else: url = "{}/uapi/v1/scripts".format(jamf_url) if verbosity > 2: print("Script data:") print(script_data) print("Uploading script..") count = 0 script_json = curl.write_json_file(script_data) while True: count += 1 if verbosity > 1: print("Script upload attempt {}".format(count)) method = "PUT" if obj_id else "POST" r = curl.request(method, url, token, verbosity, script_json) # check HTTP response if curl.status_check(r, "Script", script_name) == "break": break if count > 5: print("ERROR: Script upload did not succeed after 5 attempts") print("\nHTTP POST Response Code: {}".format(r.status_code)) break sleep(10) if verbosity > 1: api_get.get_headers(r) # clean up temp files if os.path.exists(script_json): os.remove(script_json)
def upload_script( jamf_url, script_name, script_path, category_id, category_name, script_info, script_notes, script_priority, script_parameter4, script_parameter5, script_parameter6, script_parameter7, script_parameter8, script_parameter9, script_parameter10, script_parameter11, script_os_requirements, verbosity, token, cli_custom_keys, obj_id=None, ): """Update script metadata.""" # import script from file and replace any keys in the script # script_contents = Path(script_path).read_text() with open(script_path, "r") as file: script_contents = file.read() # user assignable keys # whenever %MY_KEY% is found in a script, it is replaced with the assigned value of MY_KEY for custom_key in cli_custom_keys: if verbosity: print( f"Replacing any instances of '{custom_key}' with", f"'{cli_custom_keys[custom_key]}'", ) script_contents = script_contents.replace( f"%{custom_key}%", cli_custom_keys[custom_key] ) # build the object script_data = { "name": script_name, "info": script_info, "notes": script_notes, "priority": script_priority, "categoryId": category_id, "categoryName": category_name, "parameter4": script_parameter4, "parameter5": script_parameter5, "parameter6": script_parameter6, "parameter7": script_parameter7, "parameter8": script_parameter8, "parameter9": script_parameter9, "parameter10": script_parameter10, "parameter11": script_parameter11, "osRequirements": script_os_requirements, "scriptContents": script_contents, } headers = { "authorization": "Bearer {}".format(token), "content-type": "application/json", "accept": "application/json", } # ideally we upload to the package ID but if we didn't get a good response # we fall back to the package name if obj_id: url = "{}/uapi/v1/scripts/{}".format(jamf_url, obj_id) script_data["id"] = obj_id else: url = "{}/uapi/v1/scripts".format(jamf_url) http = requests.Session() if verbosity > 2: http.hooks["response"] = [api_connect.logging_hook] print("Script data:") print(script_data) print("Uploading script..") count = 0 script_json = json.dumps(script_data) while True: count += 1 if verbosity > 1: print("Script upload attempt {}".format(count)) if obj_id: r = http.put(url, headers=headers, data=script_json, timeout=60) else: r = http.post(url, headers=headers, data=script_json, timeout=60) if r.status_code == 201: print("Script created successfully") break if r.status_code == 200: print("Script update successful") break if r.status_code == 409: print("ERROR: Script update failed due to a conflict") break if count > 5: print("ERROR: Script update did not succeed after 5 attempts") print("\nHTTP POST Response Code: {}".format(r.status_code)) break sleep(10) if verbosity > 1: api_get.get_headers(r) return r
def main(): """Do the main thing here""" print("\n** Jamf package upload script") print("** Uploads packages to Jamf Cloud or SMB Distribution Points.") # parse the command line arguments args = get_args() # grab values from a prefs file if supplied jamf_url, jamf_user, jamf_password, enc_creds = api_connect.get_creds_from_args( args) if args.prefs: smb_url, smb_user, smb_password = api_connect.get_smb_credentials( args.prefs) else: smb_url = "" smb_user = "" smb_password = "" # repeat for optional SMB share (but must supply a share path to invoke this) if args.share: smb_url = args.share if smb_url: if args.shareuser: smb_user = args.shareuser elif not smb_user: smb_user = input( "Enter a user with read/write permissions to {} : ".format( smb_url)) if args.sharepass: smb_password = args.sharepass elif not smb_password: smb_password = getpass.getpass( "Enter the password for '{}' : ".format(smb_user)) # get HTTP request timeout r_timeout = float(args.timeout) if not args.pkg: pkg = input("Enter the full path to the package to upload: ") args.pkg = pkg # establish a web login session which is reusable for scraping tokens if args.direct: r, login_session = login(jamf_url, jamf_user, jamf_password, args.verbose) if r.status_code != 200: print("Failed to log in to the Jamf instance at: {}".format( jamf_url)) # get the id for a category if supplied if args.category: print("Checking ID for {}".format(args.category)) category_id = api_get.get_uapi_obj_id_from_name( jamf_url, enc_creds, args.category, args.verbose, "categories") if not category_id: print("WARNING: Category not found!") category_id = "-1" # now process the list of packages for pkg_path in args.pkg: pkg_name = os.path.basename(pkg_path) # See if the package is non-flat (requires zipping prior to upload). if os.path.isdir(pkg_path): pkg_path = zip_pkg_path(pkg_path) pkg_name += ".zip" # check for existing package print("\nChecking '{}' on {}".format(pkg_name, jamf_url)) if args.verbose: print("Full path: {}".format(pkg_path)) replace_pkg = True if args.replace else False obj_id = check_pkg(pkg_name, jamf_url, enc_creds) # post the package (won't run if the pkg exists and replace_pkg is False) # process for SMB shares if defined if smb_url: # mount the share mount_smb(smb_url, smb_user, smb_password, args.verbose) # check for existing package local_pkg = check_local_pkg(args.share, pkg_name, args.verbose) if not local_pkg or replace_pkg: # copy the file copy_pkg(smb_url, pkg_path, pkg_name) # unmount the share umount_smb(smb_url) # otherwise process for cloud DP else: if obj_id == "-1" or replace_pkg: # JCDS direct upload method option if args.direct: jcds_url, jcds_token, session_token = scrape_upload_token( login_session, jamf_url, args.verbose) if jcds_url and jcds_token and session_token: if args.verbose: print("JCDS URL: {}".format(jcds_url)) print("JCDS Upload token: {}".format(jcds_token)) print("Session token: {}".format(session_token)) # post the package as chunks post_pkg_chunks( pkg_name, pkg_path, jcds_url, jcds_token, obj_id, args.chunksize, args.verbose, ) # now create the package object and get the pkg ID pkg_id = update_pkg_by_form( login_session, session_token, jamf_url, pkg_name, pkg_path, obj_id, category_id, args.verbose, ) # curl -> dbfileupload upload method option elif args.curl: r = curl_pkg( pkg_name, pkg_path, jamf_url, enc_creds, obj_id, r_timeout, args.verbose, ) try: pkg_id = ElementTree.fromstring(r).findtext("id") if pkg_id: print("\nPackage uploaded successfully, ID={}". format(pkg_id)) except ElementTree.ParseError: print("Could not parse XML. Raw output:") print(r.decode("ascii")) else: if args.verbose: if r: print("\nResponse:\n") print(r.decode("ascii")) else: print("No HTTP response") # requests -> dbfileupload upload method option else: r = post_pkg( pkg_name, pkg_path, jamf_url, enc_creds, obj_id, r_timeout, args.verbose, ) # print result of the request if r.status_code == 200 or r.status_code == 201: pkg_id = ElementTree.fromstring(r.text).findtext("id") print("\nPackage uploaded successfully, ID={}".format( pkg_id)) if args.verbose: print("HTTP POST Response Code: {}".format( r.status_code)) else: print("\nHTTP POST Response Code: {}".format( r.status_code)) if args.verbose: api_get.get_headers(r) # now process the package metadata if a category is supplied, # or if we are dealing with an SMB share if (args.category or smb_url) and not args.direct: try: pkg_id update_pkg_metadata(jamf_url, enc_creds, pkg_name, args.category, args.verbose, pkg_id) except UnboundLocalError: update_pkg_metadata(jamf_url, enc_creds, pkg_name, args.category, args.verbose) print()
def upload_extatt( jamf_url, enc_creds, extatt_name, script_path, verbosity, cli_custom_keys, obj_id=None, ): """Update extension attribute metadata.""" # import script from file and replace any keys in the script with open(script_path, "r") as file: script_contents = file.read() # substitute user-assignable keys script_contents = actions.substitute_assignable_keys( script_contents, cli_custom_keys, verbosity) # XML-escape the script script_contents_escaped = escape(script_contents) # build the object extatt_data = ( "<computer_extension_attribute>" + "<name>{}</name>".format(extatt_name) + "<enabled>true</enabled>" + "<description/>" + "<data_type>String</data_type>" + "<input_type>" + " <type>script</type>" + " <platform>Mac</platform>" + " <script>{}</script>".format(script_contents_escaped) + "</input_type>" + "<inventory_display>Extension Attributes</inventory_display>" + "<recon_display>Extension Attributes</recon_display>" + "</computer_extension_attribute>") headers = { "authorization": "Basic {}".format(enc_creds), "Accept": "application/xml", "Content-type": "application/xml", } # if we find an object ID we put, if not, we post if obj_id: url = "{}/JSSResource/computerextensionattributes/id/{}".format( jamf_url, obj_id) else: url = "{}/JSSResource/computerextensionattributes/id/0".format( jamf_url) http = requests.Session() if verbosity > 2: http.hooks["response"] = [api_connect.logging_hook] print("Extension Attribute data:") print(extatt_data) print("Uploading Extension Attribute..") count = 0 while True: count += 1 if verbosity > 1: print("Extension Attribute upload attempt {}".format(count)) if obj_id: r = http.put(url, headers=headers, data=extatt_data, timeout=60) else: r = http.post(url, headers=headers, data=extatt_data, timeout=60) if r.status_code == 200 or r.status_code == 201: print("Extension Attribute uploaded successfully") break if r.status_code == 409: print("ERROR: Extension Attribute upload failed due to a conflict") break if count > 5: print( "ERROR: Extension Attribute upload did not succeed after 5 attempts" ) print("\nHTTP POST Response Code: {}".format(r.status_code)) break sleep(10) if verbosity > 1: api_get.get_headers(r) return r
def update_pkg_metadata(jamf_url, enc_creds, pkg_name, pkg_metadata, hash_value, verbosity, pkg_id=None): """Update package metadata. Currently only serves category""" if hash_value: hash_type = "SHA_512" else: hash_type = "MD5" # build the package record XML pkg_data = ( "<package>" + f"<name>{pkg_name}</name>" + f"<filename>{pkg_name}</filename>" + f"<category>{escape(pkg_metadata['category'])}</category>" + f"<info>{escape(pkg_metadata['info'])}</info>" + f"<notes>{escape(pkg_metadata['notes'])}</notes>" + f"<priority>{pkg_metadata['priority']}</priority>" + f"<reboot_required>{pkg_metadata['reboot_required']}</reboot_required>" + f"<required_processor>{pkg_metadata['required_processor']}</required_processor>" + f"<os_requirement>{pkg_metadata['os_requirement']}</os_requirement>" + f"<hash_type>{hash_type}</hash_type>" + f"<hash_value>{hash_value}</hash_value>" + f"<send_notification>{pkg_metadata['send_notification']}</send_notification>" + "</package>") # ideally we upload to the package ID but if we didn't get a good response # we fall back to the package name if pkg_id: method = "PUT" url = "{}/JSSResource/packages/id/{}".format(jamf_url, pkg_id) else: method = "POST" url = "{}/JSSResource/packages/name/{}".format(jamf_url, pkg_name) if verbosity > 2: print("Package data:") print(pkg_data) count = 0 while True: count += 1 if verbosity > 1: print(f"Package metadata upload attempt {count}") pkg_xml = curl.write_temp_file(pkg_data) r = curl.request(method, url, enc_creds, verbosity, pkg_xml) # check HTTP response if curl.status_check(r, "Package", pkg_name) == "break": break if count > 5: print( "WARNING: Package metadata update did not succeed after 5 attempts" ) print(f"HTTP POST Response Code: {r.status_code}") print("ERROR: Package metadata upload failed ") exit(-1) sleep(30) if verbosity: api_get.get_headers(r) # clean up temp files if os.path.exists(pkg_xml): os.remove(pkg_xml)
def main(): """Do the main thing here""" print("\n** Jamf package upload script") print("** Uploads packages to Jamf Cloud or SMB Distribution Points.") # parse the command line arguments args = get_args() verbosity = args.verbose # create a dictionary of package metadata from the args pkg_metadata = { "category": args.category, "info": args.info, "notes": args.notes, "reboot_required": args.reboot_required, "priority": args.priority, "os_requirement": args.os_requirement, "required_processor": args.required_processor, "send_notification": args.send_notification, } # grab values from a prefs file if supplied ( jamf_url, jamf_user, jamf_password, slack_webhook, enc_creds, ) = api_connect.get_creds_from_args(args) if args.prefs: smb_url, smb_user, smb_pass = api_connect.get_smb_credentials( args.prefs) else: smb_url = "" smb_user = "" smb_pass = "" # repeat for optional SMB share (but must supply a share path to invoke this) if args.smb_url: smb_url = args.smb_url if args.smb_user: smb_user = args.smb_user if not smb_user: smb_user = input( "Enter a user with read/write permissions to {} : ".format( smb_url)) if args.smb_pass: smb_pass = args.smb_pass if not smb_pass: if not smb_pass: smb_pass = getpass.getpass( "Enter the password for '{}' : ".format(smb_user)) # get HTTP request timeout r_timeout = float(args.timeout) if not args.pkg: pkg = input("Enter the full path to the package to upload: ") args.pkg = pkg # establish a web login session which is reusable for scraping tokens if args.direct: r, login_session = login(jamf_url, jamf_user, jamf_password, verbosity) if r.status_code != 200: print("Failed to log in to the Jamf instance at: {}".format( jamf_url)) # get the id for a category if supplied if args.category: print("Checking ID for category '{}'".format(args.category)) # now get the session token token = api_connect.get_uapi_token(jamf_url, enc_creds, verbosity) category_id = api_get.get_uapi_obj_id_from_name( jamf_url, "categories", args.category, token, verbosity) if not category_id: print("WARNING: Category not found!") category_id = "-1" # add to the pkg_metadata dictionary pkg_metadata["category_id"] = category_id # now process the list of packages for pkg_path in args.pkg: pkg_name = os.path.basename(pkg_path) # See if the package is non-flat (requires zipping prior to upload). if os.path.isdir(pkg_path): pkg_path = zip_pkg_path(pkg_path) pkg_name += ".zip" # calculate the SHA-512 hash of the package sha512string = sha512sum(pkg_path) # check for existing package print("\nChecking '{}' on {}".format(pkg_name, jamf_url)) if verbosity: print("Full path: {}".format(pkg_path)) replace_pkg = True if args.replace else False obj_id = check_pkg(pkg_name, jamf_url, enc_creds, verbosity) if obj_id != "-1": print("Package '{}' already exists: ID {}".format( pkg_name, obj_id)) pkg_id = obj_id # assign pkg_id for smb runs - JCDS runs get it from the pkg upload else: pkg_id = "" # post the package (won't run if the pkg exists and replace_pkg is False) # process for SMB shares if defined if args.smb_url: # mount the share smb_actions.mount_smb(args.smb_url, args.smb_user, args.smb_pass, verbosity) # check for existing package local_pkg = check_local_pkg(args.smb_url, pkg_name, verbosity) if not local_pkg or replace_pkg: # copy the file smb_actions.copy_pkg(args.smb_url, pkg_path, pkg_name) # unmount the share smb_actions.umount_smb(args.smb_url) # otherwise process for cloud DP else: if obj_id == "-1" or replace_pkg: # JCDS direct upload method option if args.direct: jcds_url, jcds_token, session_token = scrape_upload_token( login_session, jamf_url, verbosity) if jcds_url and jcds_token and session_token: if verbosity: print("JCDS URL: {}".format(jcds_url)) print("JCDS Upload token: {}".format(jcds_token)) print("Session token: {}".format(session_token)) # post the package as chunks post_pkg_chunks( pkg_name, pkg_path, jcds_url, jcds_token, obj_id, args.chunksize, verbosity, ) # now create the package object and get the pkg ID pkg_id = update_pkg_by_form( login_session, session_token, jamf_url, pkg_name, pkg_path, obj_id, pkg_metadata, verbosity, ) # curl -> dbfileupload upload method option elif args.nscurl: r = nscurl_pkg( pkg_name, pkg_path, jamf_url, enc_creds, obj_id, r_timeout, verbosity, ) try: pkg_id = ElementTree.fromstring(r).findtext("id") if pkg_id: print("\nPackage uploaded successfully, ID={}". format(pkg_id)) except ElementTree.ParseError: print("Could not parse XML. Raw output:") print(r.decode("ascii")) else: if verbosity: if r: print("\nResponse:\n") print(r.decode("ascii")) else: print("No HTTP response") # requests -> dbfileupload upload method option elif args.requests: r = post_pkg( pkg_name, pkg_path, jamf_url, enc_creds, obj_id, r_timeout, verbosity, ) # print result of the request if r.status_code == 200 or r.status_code == 201: pkg_id = ElementTree.fromstring(r.text).findtext("id") print("\nPackage uploaded successfully, ID={}".format( pkg_id)) if verbosity: print("HTTP POST Response Code: {}".format( r.status_code)) else: print("\nHTTP POST Response Code: {}".format( r.status_code)) if verbosity: api_get.get_headers(r) # curl -> dbfileupload upload method option else: r = curl_pkg( pkg_name, pkg_path, jamf_url, enc_creds, obj_id, r_timeout, verbosity, ) try: pkg_id = ElementTree.fromstring(r).findtext("id") if pkg_id: print("\nPackage uploaded successfully, ID={}". format(pkg_id)) except ElementTree.ParseError: print("Could not parse XML. Raw output:") print(r.decode("ascii")) else: if verbosity: if r: print("\nResponse:\n") print(r.decode("ascii")) else: print("No HTTP response") # now process the package metadata if a category is supplied, # or if we are dealing with an SMB share if not args.direct: if pkg_id: if verbosity: print("Updating package metadata for {}".format(pkg_id)) update_pkg_metadata( jamf_url, enc_creds, pkg_name, pkg_metadata, sha512string, verbosity, pkg_id, ) else: if verbosity: print("Creating package metadata") update_pkg_metadata(jamf_url, enc_creds, pkg_name, pkg_metadata, sha512string, verbosity) print()
def upload_ea( jamf_url, enc_creds, ea_name, script_path, verbosity, cli_custom_keys, obj_id=None, ): """Update extension attribute metadata.""" # import script from file and replace any keys in the script with open(script_path, "r") as file: script_contents = file.read() # substitute user-assignable keys # pylint is incorrectly stating that 'verbosity' has no value. So... # pylint: disable=no-value-for-parameter script_contents = actions.substitute_assignable_keys( script_contents, cli_custom_keys, verbosity ) # XML-escape the script script_contents_escaped = escape(script_contents) # build the object ea_data = ( "<computer_extension_attribute>" + "<name>{}</name>".format(ea_name) + "<enabled>true</enabled>" + "<description/>" + "<data_type>String</data_type>" + "<input_type>" + " <type>script</type>" + " <platform>Mac</platform>" + " <script>{}</script>".format(script_contents_escaped) + "</input_type>" + "<inventory_display>Extension Attributes</inventory_display>" + "<recon_display>Extension Attributes</recon_display>" + "</computer_extension_attribute>" ) # if we find an object ID we put, if not, we post if obj_id: url = "{}/JSSResource/computerextensionattributes/id/{}".format( jamf_url, obj_id ) else: url = "{}/JSSResource/computerextensionattributes/id/0".format(jamf_url) if verbosity > 2: print("Extension Attribute data:") print(ea_data) print("Uploading Extension Attribute..") # write the template to temp file template_xml = curl.write_temp_file(ea_data) count = 0 while True: count += 1 if verbosity > 1: print("Extension Attribute upload attempt {}".format(count)) method = "PUT" if obj_id else "POST" r = curl.request(method, url, enc_creds, verbosity, template_xml) # check HTTP response if curl.status_check(r, "Extension Attribute", ea_name) == "break": break if count > 5: print("ERROR: Extension Attribute upload did not succeed after 5 attempts") print("\nHTTP POST Response Code: {}".format(r.status_code)) break sleep(10) if verbosity > 1: api_get.get_headers(r) # clean up temp files if os.path.exists(template_xml): os.remove(template_xml)
def upload_policy_icon( jamf_url, enc_creds, policy_name, policy_icon_path, replace_icon, verbosity, obj_id=None, ): """Upload an icon to the policy that was just created""" # check that the policy exists. # Use the obj_id if we have it, or use name if we don't have it yet # We may need a wait loop here for new policies if not obj_id: # check for existing policy print("\nChecking '{}' on {}".format(policy_name, jamf_url)) obj_id = api_get.get_api_obj_id_from_name(jamf_url, "policy", policy_name, enc_creds, verbosity) if not obj_id: print( "ERROR: could not locate ID for policy '{}' so cannot upload icon" .format(policy_name)) return # Now grab the name of the existing icon using the API existing_icon = api_get.get_api_obj_value_from_id( jamf_url, "policy", obj_id, "self_service/self_service_icon/filename", enc_creds, verbosity, ) # If the icon naame matches that we already have, don't upload again # unless --replace-icon is set policy_icon_name = os.path.basename(policy_icon_path) if existing_icon != policy_icon_name or replace_icon: url = "{}/JSSResource/fileuploads/policies/id/{}".format( jamf_url, obj_id) print("Uploading icon...") count = 0 while True: count += 1 if verbosity > 1: print("Icon upload attempt {}".format(count)) r = curl.request("POST", url, enc_creds, verbosity, policy_icon_path) # check HTTP response if curl.status_check(r, "Icon", policy_icon_name) == "break": break if count > 5: print("WARNING: Icon upload did not succeed after 5 attempts") print("\nHTTP POST Response Code: {}".format(r.status_code)) break sleep(30) if verbosity > 1: api_get.get_headers(r) else: print("Existing icon matches local resource - skipping upload.")
def upload_script( jamf_url, script_name, script_path, category_id, category_name, script_info, script_notes, script_priority, script_parameter4, script_parameter5, script_parameter6, script_parameter7, script_parameter8, script_parameter9, script_parameter10, script_parameter11, script_os_requirements, verbosity, token, cli_custom_keys, obj_id=None, ): """Update script metadata.""" # import script from file and replace any keys in the script # script_contents = Path(script_path).read_text() with open(script_path, "r") as file: script_contents = file.read() # substitute user-assignable keys script_contents = actions.substitute_assignable_keys( script_contents, cli_custom_keys, verbosity) # priority has to be in upper case. Let's make it nice for the user if script_priority: script_priority = script_priority.upper() # build the object script_data = { "name": script_name, "info": script_info, "notes": script_notes, "priority": script_priority, "categoryId": category_id, "categoryName": category_name, "parameter4": script_parameter4, "parameter5": script_parameter5, "parameter6": script_parameter6, "parameter7": script_parameter7, "parameter8": script_parameter8, "parameter9": script_parameter9, "parameter10": script_parameter10, "parameter11": script_parameter11, "osRequirements": script_os_requirements, "scriptContents": script_contents, } headers = { "authorization": "Bearer {}".format(token), "content-type": "application/json", "accept": "application/json", } # ideally we upload to the object ID but if we didn't get a good response # we fall back to the name if obj_id: url = "{}/uapi/v1/scripts/{}".format(jamf_url, obj_id) script_data["id"] = obj_id else: url = "{}/uapi/v1/scripts".format(jamf_url) http = requests.Session() if verbosity > 2: http.hooks["response"] = [api_connect.logging_hook] print("Script data:") print(script_data) print("Uploading script..") count = 0 script_json = json.dumps(script_data) while True: count += 1 if verbosity > 1: print("Script upload attempt {}".format(count)) if obj_id: r = http.put(url, headers=headers, data=script_json, timeout=60) else: r = http.post(url, headers=headers, data=script_json, timeout=60) if r.status_code == 200 or r.status_code == 201: print("Script uploaded successfully") break if r.status_code == 409: print("ERROR: Script upload failed due to a conflict") break if count > 5: print("ERROR: Script upload did not succeed after 5 attempts") print("\nHTTP POST Response Code: {}".format(r.status_code)) break sleep(10) if verbosity > 1: api_get.get_headers(r) return r
def upload_category(jamf_url, category_name, priority, verbosity, token, obj_id=0): """Update category metadata.""" # build the object category_data = {"priority": priority, "name": category_name} if obj_id: url = "{}/uapi/v1/categories/{}".format(jamf_url, obj_id) category_data["name"] = category_name else: url = "{}/uapi/v1/categories".format(jamf_url) if verbosity > 2: print("Category data:") print(category_data) print("Uploading category..") count = 0 # we cannot PUT a category of the same name due to a bug in Jamf Pro (PI-008157). # so we have to do a first pass with a temporary different name, then change it back... if obj_id: category_name_temp = category_name + "_TEMP" category_data_temp = {"priority": priority, "name": category_name_temp} category_json_temp = curl.write_json_file(category_data_temp) while True: count += 1 if verbosity > 1: print("Category upload attempt {}".format(count)) r = curl.request("PUT", url, token, verbosity, category_json_temp) # check HTTP response if curl.status_check(r, "Category", category_name_temp) == "break": break if count > 5: print( "ERROR: Temporary category update did not succeed after 5 attempts" ) print("\nHTTP POST Response Code: {}".format(r.status_code)) break sleep(10) # write the category. If updating an existing category, this reverts the name to its original. category_json = curl.write_json_file(category_data) while True: count += 1 if verbosity > 1: print("Category upload attempt {}".format(count)) method = "PUT" if obj_id else "POST" r = curl.request(method, url, token, verbosity, category_json) # check HTTP response if curl.status_check(r, "Category", category_name) == "break": break if count > 5: print("ERROR: Category creation did not succeed after 5 attempts") print("\nHTTP POST Response Code: {}".format(r.status_code)) break sleep(10) if verbosity > 1: api_get.get_headers(r) # clean up temp files for file in category_json, category_json_temp: if os.path.exists(file): os.remove(file) return r
def upload_category(jamf_url, category_name, priority, verbosity, token, obj_id=0): """Update category metadata.""" # build the object category_data = {"priority": priority, "name": category_name} headers = { "authorization": "Bearer {}".format(token), "content-type": "application/json", "accept": "application/json", } if obj_id: url = "{}/uapi/v1/categories/{}".format(jamf_url, obj_id) category_data["name"] = category_name else: url = "{}/uapi/v1/categories".format(jamf_url) http = requests.Session() if verbosity > 2: http.hooks["response"] = [api_connect.logging_hook] print("Category data:") print(category_data) print("Uploading category..") count = 0 category_json = json.dumps(category_data) # we cannot PUT a category of the same name due to a bug in Jamf Pro (PI-008157). # so we have to do a first pass with a temporary different name, then change it back... if obj_id: category_data_temp = { "priority": priority, "name": category_name + "_TEMP" } category_json_temp = json.dumps(category_data_temp) while True: count += 1 if verbosity > 1: print("Category upload attempt {}".format(count)) r = http.put(url, headers=headers, data=category_json_temp, timeout=60) if r.status_code == 200: print( "Temporary category update successful. Waiting before updating again..." ) sleep(2) break if r.status_code == 409: print( "ERROR: Temporary category update failed due to a conflict" ) break if count > 5: print( "ERROR: Temporary category update did not succeed after 5 attempts" ) print("\nHTTP POST Response Code: {}".format(r.status_code)) break sleep(10) # write the category. If updating an existing category, this reverts the name to it's original. while True: count += 1 if verbosity > 1: print("Category upload attempt {}".format(count)) if obj_id: r = http.put(url, headers=headers, data=category_json, timeout=60) else: r = http.post(url, headers=headers, data=category_json, timeout=60) if r.status_code == 201: print("Category created successfully") break if r.status_code == 200: print("Category update successful") break if r.status_code == 409: print("ERROR: Category creation failed due to a conflict") break if count > 5: print("ERROR: Category creation did not succeed after 5 attempts") print("\nHTTP POST Response Code: {}".format(r.status_code)) break sleep(10) if verbosity > 1: api_get.get_headers(r) return r