Exemple #1
0
def promote(args):
    """Replace a package in a policy with another package.

    Designed with the intention of facilitating promotion from
    development to production of a package.

    If any arg is missing, an interactive menu will be displayed.

    Args:
        args: argparser args with properties:
            new_package: ID or name of package to install.
            policy: Name or ID of policy.
            update_name: Bool, Will atttempt to update the policy name
                with the new package name (minus the extension). e.g.
                "Install NetHack-3.4.3" will result in "Install
                NetHack-3.4.4".

                The policy name must include the exact package name,
                and version number for this to do anything.
    """
    jss_connection = JSSConnection.get()
    all_packages = jss_connection.Package()

    # Handle policy arguments.
    if args.policy:
        policy = jss_connection.Policy(args.policy)
    else:
        policy_name = tools.policy_menu(jss_connection.Policy(),
                                        all_packages)
        policy = jss_connection.Policy(policy_name)

    cur_pkg = policy.findtext("package_configuration/packages/package/name")

    # Handle package arguments.
    if args.new_package:
        new_pkg_name = args.new_package
    else:
        new_pkg_name = tools.get_pkg_menu(all_packages, cur_pkg)

    # Make changes to policy.
    policy.remove_object_from_list(cur_pkg, "package_configuration/packages")
    policy.add_package(jss_connection.Package(new_pkg_name))

    # Handle policy name updating.
    if args.update_name:
        try:
            tools.update_name(policy, cur_pkg, new_pkg_name)
        except ValueError:
            print "Unable to update policy name!"

    # Save policy and remind user to flush logs if needed.
    policy.save()
    url = JSSConnection.get().base_url
    tools.log_warning(url, policy)
Exemple #2
0
def promote(args):
    """Replace a package in a policy with another package.

    Designed with the intention of facilitating promotion from
    development to production of a package.

    If any arg is missing, an interactive menu will be displayed.

    Args:
        args: argparser args with properties:
            new_package: ID or name of package to install.
            policy: Name or ID of policy.
            update_name: Bool, Will atttempt to update the policy name
                with the new package name (minus the extension). e.g.
                "Install NetHack-3.4.3" will result in "Install
                NetHack-3.4.4".

                The policy name must include the exact package name,
                and version number for this to do anything.
    """
    jss_connection = JSSConnection.get()
    all_packages = jss_connection.Package()

    # Handle policy arguments.
    if args.policy:
        policy = jss_connection.Policy(args.policy)
    else:
        policy_name = tools.policy_menu(jss_connection.Policy(), all_packages)
        policy = jss_connection.Policy(policy_name)

    cur_pkg = policy.findtext("package_configuration/packages/package/name")

    # Handle package arguments.
    if args.new_package:
        new_pkg_name = args.new_package
    else:
        new_pkg_name = tools.get_pkg_menu(all_packages, cur_pkg)

    # Make changes to policy.
    policy.remove_object_from_list(cur_pkg, "package_configuration/packages")
    policy.add_package(jss_connection.Package(new_pkg_name))

    # Handle policy name updating.
    if args.update_name:
        try:
            tools.update_name(policy, cur_pkg, new_pkg_name)
        except ValueError:
            print "Unable to update policy name!"

    # Save policy and remind user to flush logs if needed.
    policy.save()
    url = JSSConnection.get().base_url
    tools.log_warning(url, policy)
Exemple #3
0
def _get_scoped(search_group):
    """Return all policies and config profiles scoped to a group.

    Args:
        args: argparser args with properties:
            group: Name or ID of computer group.

    Returns:
        Formatted string report.
    """
    jss_connection = JSSConnection.get()
    group = jss_connection.ComputerGroup(search_group)

    # Search for policies.
    policies = jss_connection.Policy().retrieve_all()
    policy_results = tools.find_groups_in_scope([group], policies)
    policy_heading = "Policies scoped to %s" % group.name
    output = tools.build_results_string(policy_heading, policy_results) + "\n"

    policy_results = tools.get_scoped_to_all(policies)
    policy_heading = "Policies scoped to all computers"
    output += tools.build_results_string(policy_heading, policy_results) + "\n"

    # Search for configuration profiles.
    configps = jss_connection.OSXConfigurationProfile().retrieve_all()
    configp_results = tools.find_groups_in_scope([group], configps)
    configp_heading = "Configuration profiles scoped to %s" % group.name
    output += (tools.build_results_string(configp_heading, configp_results) +
               "\n")
    configp_results = tools.get_scoped_to_all(configps)
    configp_heading = "Configuration profiles scoped to all computers"
    output += tools.build_results_string(configp_heading, configp_results)

    return output
Exemple #4
0
def _get_exclusions_by_type(group):
    """Private function for retrieving excluded groups.

    Will handle both mobile device and computer group exclusions.

    Args:
        group: A jss.ComputerGroup or jss.MobileDeviceGroup object.
    """
    jss_connection = JSSConnection.get()
    scopables = []
    header = " with %s excluded from scope." % group.name
    base_search = "scope/exclusions/%s_groups/%s_group"

    if isinstance(group, jss.ComputerGroup):
        search = base_search % ("computer", "computer")
        scopables.append({"containers": jss_connection.Policy(),
                          "heading": "Policies"})
        scopables.append(
            {"containers": jss_connection.OSXConfigurationProfile(),
             "heading": "Configuration Profiles"})
    elif isinstance(group, jss.MobileDeviceGroup):
        search = base_search % ("mobile_device", "mobile_device")
        scopables.append(
            {"containers": jss_connection.MobileDeviceConfigurationProfile(),
             "heading": "Mobile Device Configuration Profiles"})

    for item in scopables:
        results = tools.find_objects_in_containers(group, search,
                                                   item["containers"])
        output = tools.build_results_string(item["heading"] + header, results)
        print output
Exemple #5
0
def _get_scoped(search_group):
    """Return all policies and config profiles scoped to a group.

    Args:
        args: argparser args with properties:
            group: Name or ID of computer group.

    Returns:
        Formatted string report.
    """
    jss_connection = JSSConnection.get()
    group = jss_connection.ComputerGroup(search_group)

    # Search for policies.
    policies = jss_connection.Policy().retrieve_all()
    policy_results = tools.find_groups_in_scope([group], policies)
    policy_heading = "Policies scoped to %s" % group.name
    output = tools.build_results_string(policy_heading, policy_results) + "\n"

    policy_results = tools.get_scoped_to_all(policies)
    policy_heading = "Policies scoped to all computers"
    output += tools.build_results_string(policy_heading, policy_results) + "\n"

    # Search for configuration profiles.
    configps = jss_connection.OSXConfigurationProfile().retrieve_all()
    configp_results = tools.find_groups_in_scope([group], configps)
    configp_heading = "Configuration profiles scoped to %s" % group.name
    output += (tools.build_results_string(configp_heading, configp_results) +
               "\n")
    configp_results = tools.get_scoped_to_all(configps)
    configp_heading = "Configuration profiles scoped to all computers"
    output += tools.build_results_string(configp_heading, configp_results)

    return output
Exemple #6
0
def get_excluded(args):
    """Print all policies and config profiles with group excluded.

    Args:
        args: argparser args with properties:
            group: Name or ID of computer group.
    """
    group = JSSConnection.get().ComputerGroup(args.group)
    _get_exclusions_by_type(group)
Exemple #7
0
def get_md_excluded(args):
    """Print all mobile device config profiles with group excluded.

    Args:
        args: argparser args with properties:
            group: Name or ID of mobile device group.
    """
    group = JSSConnection.get().MobileDeviceGroup(args.group)
    _get_exclusions_by_type(group)
Exemple #8
0
def get_md_excluded(args):
    """Print all mobile device config profiles with group excluded.

    Args:
        args: argparser args with properties:
            group: Name or ID of mobile device group.
    """
    group = JSSConnection.get().MobileDeviceGroup(args.group)
    _get_exclusions_by_type(group)
Exemple #9
0
def get_excluded(args):
    """Print all policies and config profiles with group excluded.

    Args:
        args: argparser args with properties:
            group: Name or ID of computer group.
    """
    group = JSSConnection.get().ComputerGroup(args.group)
    _get_exclusions_by_type(group)
Exemple #10
0
def md_group_search_or_modify(args):
    """Perform a group search or add/remove mobile devices from group.

    Args:
        args: argparser args with properties:
            search: Name or ID of computer group.
            add: List of ID, name, or name wildcard searches to add.
            remove: List of ID, name, or name wildcard searches to
                remove.
            dry_run: Bool whether to save or just print group XML.
    """
    jss_connection = JSSConnection.get()
    group_search_method = jss_connection.MobileDeviceGroup
    member_search_method = jss_connection.MobileDevice
    _group_search_or_modify(group_search_method, member_search_method, args)
Exemple #11
0
def md_group_search_or_modify(args):
    """Perform a group search or add/remove mobile devices from group.

    Args:
        args: argparser args with properties:
            search: Name or ID of computer group.
            add: List of ID, name, or name wildcard searches to add.
            remove: List of ID, name, or name wildcard searches to
                remove.
            dry_run: Bool whether to save or just print group XML.
    """
    jss_connection = JSSConnection.get()
    group_search_method = jss_connection.MobileDeviceGroup
    member_search_method = jss_connection.MobileDevice
    _group_search_or_modify(group_search_method, member_search_method, args)
Exemple #12
0
def batch_scope(args):
    """Scope a list of policies to a computer group.

    Args:
        args: argparser args with properties:
            group: Name, wildcard search, or ID of computer group.
            policy: List of ID's or names of policies to scope.
                Wildcard searches accepted.
    """
    jss_connection = JSSConnection.get()
    groups = tools.search_for_object(jss_connection.ComputerGroup, args.group)
    print "Scoping to groups: %s" % ", ".join([group.name for group in groups])
    print 79 * "-"
    for policy_query in args.policy:
        policies = tools.search_for_object(jss_connection.Policy, policy_query)
        for policy in policies:
            for group in groups:
                policy.add_object_to_scope(group)
            policy.save()
            print "%s: Success." % policy.name
Exemple #13
0
def batch_scope(args):
    """Scope a list of policies to a computer group.

    Args:
        args: argparser args with properties:
            group: Name, wildcard search, or ID of computer group.
            policy: List of ID's or names of policies to scope.
                Wildcard searches accepted.
    """
    jss_connection = JSSConnection.get()
    groups = tools.search_for_object(jss_connection.ComputerGroup, args.group)
    print "Scoping to groups: %s" % ", ".join([group.name for group in groups])
    print 79 * "-"
    for policy_query in args.policy:
        policies = tools.search_for_object(jss_connection.Policy, policy_query)
        for policy in policies:
            for group in groups:
                policy.add_object_to_scope(group)
            policy.save()
            print "%s: Success." % policy.name
Exemple #14
0
def _get_md_scoped(search_group):
    """Return all mobile device config profiles scoped to a group.

    Args:
        args: argparser args with properties:
            group: Name or ID of group.

    Returns:
        Formatted string report.
    """
    jss_connection = JSSConnection.get()
    group = jss_connection.MobileDeviceGroup(search_group)

    configps = jss_connection.MobileDeviceConfigurationProfile().retrieve_all()
    results = tools.find_groups_in_scope([group], configps)
    output = tools.build_results_string("Profiles scoped to %s" % group.name,
                                        results) + "\n"
    results = tools.get_scoped_to_all(configps)
    output += tools.build_results_string(
        "Profiles scoped to all mobile devices", results)

    return output
Exemple #15
0
def _get_md_scoped(search_group):
    """Return all mobile device config profiles scoped to a group.

    Args:
        args: argparser args with properties:
            group: Name or ID of group.

    Returns:
        Formatted string report.
    """
    jss_connection = JSSConnection.get()
    group = jss_connection.MobileDeviceGroup(search_group)

    configps = jss_connection.MobileDeviceConfigurationProfile().retrieve_all()
    results = tools.find_groups_in_scope([group], configps)
    output = tools.build_results_string("Profiles scoped to %s" % group.name,
                                        results) + "\n"
    results = tools.get_scoped_to_all(configps)
    output += tools.build_results_string(
        "Profiles scoped to all mobile devices", results)

    return output
Exemple #16
0
def get_package_policies(args):
    """Print all policies which install a package.

    Args:
        args: argparser args with properties:
            package: ID, name, or wildcard-search-name of package.
    """
    search = "package_configuration/packages/package"
    jss_connection = JSSConnection.get()
    policies = jss_connection.Policy()
    packages = tools.search_for_object(jss_connection.Package, args.package)

    results = set(tools.find_objects_in_containers(packages, search, policies))
    output = tools.build_results_string(
        "Policies which install '%s'" % args.package, results) + "\n"

    search = "packages/package"
    imaging_configs = jss_connection.ComputerConfiguration()
    ic_results = set(
        tools.find_objects_in_containers(packages, search, imaging_configs))
    output += tools.build_results_string(
        "Imaging configs which install '%s'" % args.package, ic_results)
    print output
Exemple #17
0
def get_package_policies(args):
    """Print all policies which install a package.

    Args:
        args: argparser args with properties:
            package: ID, name, or wildcard-search-name of package.
    """
    search = "package_configuration/packages/package"
    jss_connection = JSSConnection.get()
    policies = jss_connection.Policy()
    packages = tools.search_for_object(jss_connection.Package, args.package)

    results = set(tools.find_objects_in_containers(packages, search, policies))
    output = tools.build_results_string("Policies which install '%s'" %
                                        args.package, results) + "\n"

    search = "packages/package"
    imaging_configs = jss_connection.ComputerConfiguration()
    ic_results = set(tools.find_objects_in_containers(packages, search,
                                                      imaging_configs))
    output += tools.build_results_string("Imaging configs which install '%s'" %
                                         args.package, ic_results)
    print output
Exemple #18
0
def _get_exclusions_by_type(group):
    """Private function for retrieving excluded groups.

    Will handle both mobile device and computer group exclusions.

    Args:
        group: A jss.ComputerGroup or jss.MobileDeviceGroup object.
    """
    jss_connection = JSSConnection.get()
    scopables = []
    header = " with %s excluded from scope." % group.name
    base_search = "scope/exclusions/%s_groups/%s_group"

    if isinstance(group, jss.ComputerGroup):
        search = base_search % ("computer", "computer")
        scopables.append({
            "containers": jss_connection.Policy(),
            "heading": "Policies"
        })
        scopables.append({
            "containers": jss_connection.OSXConfigurationProfile(),
            "heading": "Configuration Profiles"
        })
    elif isinstance(group, jss.MobileDeviceGroup):
        search = base_search % ("mobile_device", "mobile_device")
        scopables.append({
            "containers":
            jss_connection.MobileDeviceConfigurationProfile(),
            "heading":
            "Mobile Device Configuration Profiles"
        })

    for item in scopables:
        results = tools.find_objects_in_containers(group, search,
                                                   item["containers"])
        output = tools.build_results_string(item["heading"] + header, results)
        print output
Exemple #19
0
def build_argument_parser():
    """Build the argument parser for jss_helper.

    Returns: A configured argparse parser.
    """
    # Create our argument parser
    parser = argparse.ArgumentParser(description="Query the JSS.")
    parser.add_argument("-v",
                        "--verbose",
                        action="store_true",
                        help="Verbose output.")
    parser.add_argument("--ssl",
                        default=False,
                        action="store_true",
                        help="Use SSL verification")
    subparser = parser.add_subparsers(dest="subparser_name",
                                      title="Actions",
                                      metavar="")

    subparsers = {}

    jss_connection = JSSConnection.get()

    # computer
    subparsers["computer"] = {
        "help": "List all computers, or search for an individual computer.",
        "func": tools.create_search_func(jss_connection.Computer),
        "args": {
            "search": {
                "help": "ID or name (wildcards allowed) of "
                "computer.",
                "default": None,
                "nargs": "?"
            }
        }
    }
    subparsers["configp"] = {
        "help": "List all configuration profiles, or search for an individual "
        "configuration profile.",
        "func":
        tools.create_search_func(jss_connection.OSXConfigurationProfile),
        "args": {
            "search": {
                "help": "ID or name (wildcards allowed) of "
                "profile.",
                "default": None,
                "nargs": "?"
            }
        }
    }
    subparsers["excluded"] = {
        "help": "List all policies and configuration profiles from which a "
        "computer group is excluded.",
        "func": get_excluded,
        "args": {
            "group": {
                "help": "ID or name of group."
            }
        }
    }
    subparsers["group"] = {
        "help": "List all computer groups, or search for an individual group.",
        "func": computer_group_search_or_modify,
        "args": {
            "search": {
                "help": "ID or name (wildcards allowed) of "
                "computer group.",
                "default": None,
                "nargs": "?"
            },
            "--add": {
                "help": "Computer ID's or names to add to group. "
                "Wildcards may be used.",
                "nargs": "*"
            },
            "--remove": {
                "help": "Computer ID's or names to remove from "
                "group. Wildcards may be used.",
                "nargs": "*"
            },
            "--dry_run": {
                "help":
                "Construct the updated XML for the "
                "group, but don't save. Prints "
                "results.",
                "action":
                "store_true"
            }
        }
    }
    subparsers["imaging_config"] = {
        "help": "List all Casper Imaging computer configurations, or search "
        "for an individual computer configuration.",
        "func": tools.create_search_func(jss_connection.ComputerConfiguration),
        "args": {
            "search": {
                "help": "ID or name (wildcards allowed) of "
                "computer configuration.",
                "default": None,
                "nargs": "?"
            }
        }
    }
    subparsers["package"] = {
        "help": "List of all packages, or search for an individual package.",
        "func": tools.create_search_func(jss_connection.Package),
        "args": {
            "search": {
                "help": "ID or name (wildcards allowed) of "
                "package.",
                "default": None,
                "nargs": "?"
            }
        }
    }
    subparsers["policy"] = {
        "help": "List all policies, or search for an individual policy.",
        "func": tools.create_search_func(jss_connection.Policy),
        "args": {
            "search": {
                "help": "ID or name (wildcards allowed) of "
                "policy.",
                "default": None,
                "nargs": "?"
            }
        }
    }
    subparsers["scoped"] = {
        "help": "List all policies and configuration profiles scoped to a "
        "computer group.",
        "func": get_scoped,
        "args": {
            "group": {
                "help": "ID or name of a computer group."
            }
        }
    }
    subparsers["installs"] = {
        "help": "Lists all policies and imaging configurations which install "
        "a package.",
        "func": get_package_policies,
        "args": {
            "package": {
                "help": "ID, name, or wildcard name of "
                "package(s)."
            }
        }
    }
    subparsers["scope_diff"] = {
        "help": "Show the difference between two groups' scoped policies and "
        "configuration profiles.",
        "func": get_group_scope_diff,
        "args": {
            "group1": {
                "help": "ID or name of first group."
            },
            "group2": {
                "help": "ID or name of second group."
            }
        }
    }
    subparsers["category"] = {
        "help": "List all categories, or search for an individual category.",
        "func": tools.create_search_func(jss_connection.Category),
        "args": {
            "search": {
                "help": "ID or name (wildcards allowed) of "
                "category.",
                "default": None,
                "nargs": "?"
            }
        }
    }
    subparsers["md"] = {
        "help": "List all mobile devices, or search for an indvidual mobile "
        "device.",
        "func": tools.create_search_func(jss_connection.MobileDevice),
        "args": {
            "search": {
                "help": "ID or name (wildcards allowed) of mobile "
                "device.",
                "default": None,
                "nargs": "?"
            }
        }
    }
    subparsers["md_group"] = {
        "help": "List all mobile device groups, or search for an individual "
        "mobile device group.",
        "func": md_group_search_or_modify,
        "args": {
            "search": {
                "help": "ID or name (wildcards allowed) of mobile "
                "device group.",
                "default": None,
                "nargs": "?"
            },
            "--add": {
                "help": "Mobile device ID's or names to add to "
                "group. Wildcards may be used.",
                "nargs": "*"
            },
            "--remove": {
                "help": "Mobile Device ID's or names to remove "
                "from group. Wildcards may be used.",
                "nargs": "*"
            },
            "--dry_run": {
                "help":
                "Construct the updated XML for the "
                "group, but don't save. Prints "
                "results.",
                "action":
                "store_true"
            }
        }
    }
    subparsers["md_configp"] = {
        "help":
        "List all mobile device configuration profiles, or search for "
        "an individual mobile device configuration profile.",
        "func":
        tools.create_search_func(
            jss_connection.MobileDeviceConfigurationProfile),
        "args": {
            "search": {
                "help": "ID or name (wildcards allowed) of mobile "
                "device configuration profile.",
                "default": None,
                "nargs": "?"
            }
        }
    }
    subparsers["md_scoped"] = {
        "help": "List all mobile device configuration profiles scoped to a "
        "mobile device group.",
        "func": get_md_scoped,
        "args": {
            "group": {
                "help": "ID or name of a mobile device group."
            }
        }
    }
    subparsers["md_scope_diff"] = {
        "help": "Show the differences between two mobile device groups' "
        "scoped mobile device configuration profiles.",
        "func": get_md_scope_diff,
        "args": {
            "group1": {
                "help": "ID or name of first group."
            },
            "group2": {
                "help": "ID or name of second group."
            }
        }
    }
    subparsers["md_excluded"] = {
        "help": "List all configuration profiles from which a mobile device "
        "group is excluded.",
        "func": get_md_excluded,
        "args": {
            "group": {
                "help": "ID or name of group."
            }
        }
    }

    sorted_subparsers = sorted(subparsers)
    for command in sorted_subparsers:
        sub = subparser.add_parser(command,
                                   help=subparsers[command]["help"],
                                   description=subparsers[command]["help"])
        for arg in subparsers[command]["args"]:
            sub.add_argument(arg, **subparsers[command]["args"][arg])
        sub.set_defaults(func=subparsers[command]["func"])

    # More complicated parsers.

    # Batch Scope
    arg_help = "Scope a list of policies to a group."
    batch_scope_subparser = subparser.add_parser("batch_scope",
                                                 help=arg_help,
                                                 description=arg_help)
    batch_scope_subparser.add_argument(
        "group",
        help="Name, ID, or wildcard search of group to scope "
        "policies.")
    arg_help = ("A space delimited list of policy IDs or names. Wildcards "
                "allowed.")
    batch_scope_subparser.add_argument("policy", help=arg_help, nargs="*")
    batch_scope_subparser.set_defaults(func=batch_scope)

    # Promote
    arg_help = ("Promote a package from development to production by updating "
                "an existing production policy with a newer package.")
    promote_subparser = subparser.add_parser("promote",
                                             help=arg_help,
                                             description=arg_help)
    promote_subparser.add_argument("policy",
                                   help="Policy name or ID.",
                                   nargs="?",
                                   default=None)
    promote_subparser.add_argument("new_package",
                                   help="Package name or ID.",
                                   nargs="?",
                                   default=None)
    arg_help = ("Update the package version number in the policy's name. "
                "The Policy name must include the exact product name and the "
                "version. The package extension is optional, and will not be "
                "modified. Text replacement also ignores '-', '_', and ' ' "
                "(space) between the name and version. e.g.: 'jss_helper "
                "promote \"Install Nethack-3.4.3\" Nethack-3.4.4' will result "
                "in the policy name: 'Install Nethack-3.4.4'. See the README "
                "for further examples and more details.")
    promote_subparser.add_argument("-u",
                                   "--update_name",
                                   help=arg_help,
                                   action="store_true")
    promote_subparser.set_defaults(func=promote)

    return parser
Exemple #20
0
def build_argument_parser():
    """Build the argument parser for jss_helper.

    Returns: A configured argparse parser.
    """
    # Create our argument parser
    parser = argparse.ArgumentParser(description="Query the JSS.")
    parser.add_argument("-v", "--verbose", action="store_true",
                        help="Verbose output.")
    parser.add_argument("--ssl", default=False, action="store_true",
                        help="Use SSL verification")
    subparser = parser.add_subparsers(dest="subparser_name", title="Actions",
                                      metavar="")

    subparsers = {}

    jss_connection = JSSConnection.get()

    # computer
    subparsers["computer"] = {
        "help": "List all computers, or search for an individual computer.",
        "func": tools.create_search_func(jss_connection.Computer),
        "args": {"search": {"help": "ID or name (wildcards allowed) of "
                                    "computer.",
                            "default": None,
                            "nargs": "?"}}}
    subparsers["configp"] = {
        "help": "List all configuration profiles, or search for an individual "
                "configuration profile.",
        "func": tools.create_search_func(jss_connection.OSXConfigurationProfile),
        "args": {"search": {"help": "ID or name (wildcards allowed) of "
                                    "profile.",
                            "default": None,
                            "nargs": "?"}}}
    subparsers["excluded"] = {
        "help": "List all policies and configuration profiles from which a "
                "computer group is excluded.",
        "func": get_excluded,
        "args": {"group": {"help": "ID or name of group."}}}
    subparsers["group"] = {
        "help": "List all computer groups, or search for an individual group.",
        "func": computer_group_search_or_modify,
        "args": {"search": {"help": "ID or name (wildcards allowed) of "
                                    "computer group.",
                            "default": None,
                            "nargs": "?"},
                 "--add": {"help": "Computer ID's or names to add to group. "
                                   "Wildcards may be used.",
                           "nargs": "*"},
                 "--remove": {"help": "Computer ID's or names to remove from "
                                      "group. Wildcards may be used.",
                              "nargs": "*"},
                 "--dry_run": {"help": "Construct the updated XML for the "
                                       "group, but don't save. Prints "
                                       "results.",
                               "action": "store_true"}}}
    subparsers["imaging_config"] = {
        "help": "List all Casper Imaging computer configurations, or search "
                "for an individual computer configuration.",
        "func": tools.create_search_func(jss_connection.ComputerConfiguration),
        "args": {"search": {"help": "ID or name (wildcards allowed) of "
                                    "computer configuration.",
                            "default": None,
                            "nargs": "?"}}}
    subparsers["package"] = {
        "help": "List of all packages, or search for an individual package.",
        "func": tools.create_search_func(jss_connection.Package),
        "args": {"search": {"help": "ID or name (wildcards allowed) of "
                                    "package.",
                            "default": None,
                            "nargs": "?"}}}
    subparsers["policy"] = {
        "help": "List all policies, or search for an individual policy.",
        "func": tools.create_search_func(jss_connection.Policy),
        "args": {"search": {"help": "ID or name (wildcards allowed) of "
                                    "policy.",
                            "default": None,
                            "nargs": "?"}}}
    subparsers["scoped"] = {
        "help": "List all policies and configuration profiles scoped to a "
                "computer group.",
        "func": get_scoped,
        "args": {"group": {"help": "ID or name of a computer group."}}}
    subparsers["installs"] = {
        "help": "Lists all policies and imaging configurations which install "
                "a package.",
        "func": get_package_policies,
        "args": {"package": {"help": "ID, name, or wildcard name of "
                                     "package(s)."}}}
    subparsers["scope_diff"] = {
        "help": "Show the difference between two groups' scoped policies and "
                "configuration profiles.",
        "func": get_group_scope_diff,
        "args": {"group1": {"help": "ID or name of first group."},
                 "group2": {"help": "ID or name of second group."}}}
    subparsers["category"] = {
        "help": "List all categories, or search for an individual category.",
        "func": tools.create_search_func(jss_connection.Category),
        "args": {"search": {"help": "ID or name (wildcards allowed) of "
                                    "category.",
                            "default": None,
                            "nargs": "?"}}}
    subparsers["md"] = {
        "help": "List all mobile devices, or search for an indvidual mobile "
                "device.",
        "func": tools.create_search_func(jss_connection.MobileDevice),
        "args": {"search": {"help": "ID or name (wildcards allowed) of mobile "
                                    "device.",
                            "default": None,
                            "nargs": "?"}}}
    subparsers["md_group"] = {
        "help": "List all mobile device groups, or search for an individual "
                "mobile device group.",
        "func": md_group_search_or_modify,
        "args": {"search": {"help": "ID or name (wildcards allowed) of mobile "
                                    "device group.",
                            "default": None,
                            "nargs": "?"},
                 "--add": {"help": "Mobile device ID's or names to add to "
                                   "group. Wildcards may be used.",
                           "nargs": "*"},
                 "--remove": {"help": "Mobile Device ID's or names to remove "
                                      "from group. Wildcards may be used.",
                              "nargs": "*"},
                 "--dry_run": {"help": "Construct the updated XML for the "
                                       "group, but don't save. Prints "
                                       "results.",
                               "action": "store_true"}}}
    subparsers["md_configp"] = {
        "help": "List all mobile device configuration profiles, or search for "
                "an individual mobile device configuration profile.",
        "func": tools.create_search_func(
            jss_connection.MobileDeviceConfigurationProfile),
        "args": {"search": {"help": "ID or name (wildcards allowed) of mobile "
                                    "device configuration profile.",
                            "default": None,
                            "nargs": "?"}}}
    subparsers["md_scoped"] = {
        "help": "List all mobile device configuration profiles scoped to a "
                "mobile device group.",
        "func": get_md_scoped,
        "args": {"group": {"help": "ID or name of a mobile device group."}}}
    subparsers["md_scope_diff"] = {
        "help": "Show the differences between two mobile device groups' "
                "scoped mobile device configuration profiles.",
        "func": get_md_scope_diff,
        "args": {"group1": {"help": "ID or name of first group."},
                 "group2": {"help": "ID or name of second group."}}}
    subparsers["md_excluded"] = {
        "help": "List all configuration profiles from which a mobile device "
                "group is excluded.",
        "func": get_md_excluded,
        "args": {"group": {"help": "ID or name of group."}}}

    sorted_subparsers = sorted(subparsers)
    for command in sorted_subparsers:
        sub = subparser.add_parser(command, help=subparsers[command]["help"],
                                   description=subparsers[command]["help"])
        for arg in subparsers[command]["args"]:
            sub.add_argument(arg, **subparsers[command]["args"][arg])
        sub.set_defaults(func=subparsers[command]["func"])

    # More complicated parsers.

    # Batch Scope
    arg_help = "Scope a list of policies to a group."
    batch_scope_subparser = subparser.add_parser(
        "batch_scope", help=arg_help, description=arg_help)
    batch_scope_subparser.add_argument(
        "group", help="Name, ID, or wildcard search of group to scope "
                      "policies.")
    arg_help = ("A space delimited list of policy IDs or names. Wildcards "
                "allowed.")
    batch_scope_subparser.add_argument("policy", help=arg_help, nargs="*")
    batch_scope_subparser.set_defaults(func=batch_scope)

    # Promote
    arg_help = ("Promote a package from development to production by updating "
                "an existing production policy with a newer package.")
    promote_subparser = subparser.add_parser(
        "promote", help=arg_help, description=arg_help)
    promote_subparser.add_argument("policy", help="Policy name or ID.",
                                   nargs="?", default=None)
    promote_subparser.add_argument("new_package", help="Package name or ID.",
                                   nargs="?", default=None)
    arg_help = ("Update the package version number in the policy's name. "
                "The Policy name must include the exact product name and the "
                "version. The package extension is optional, and will not be "
                "modified. Text replacement also ignores '-', '_', and ' ' "
                "(space) between the name and version. e.g.: 'jss_helper "
                "promote \"Install Nethack-3.4.3\" Nethack-3.4.4' will result "
                "in the policy name: 'Install Nethack-3.4.4'. See the README "
                "for further examples and more details.")
    promote_subparser.add_argument("-u", "--update_name", help=arg_help,
                                   action="store_true")
    promote_subparser.set_defaults(func=promote)

    return parser