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)
Example #2
0
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
Example #6
0
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()
Example #10
0
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)
Example #11
0
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.")
Example #12
0
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