@click.group(invoke_without_command=True,
             options_metavar=GENERAL_OPTIONS_METAVAR)
@click.option('-h', '--host', type=str, envvar='ZHMC_HOST',
              help="Hostname or IP address of the HMC "
                   "(Default: ZHMC_HOST environment variable).")
@click.option('-u', '--userid', type=str, envvar='ZHMC_USERID',
              help="Username for the HMC "
                   "(Default: ZHMC_USERID environment variable).")
@click.option('-p', '--password', type=str, envvar='ZHMC_PASSWORD',
              help="Password for the HMC "
                   "(Default: ZHMC_PASSWORD environment variable).")
@click.option('-n', '--no-verify', is_flag=True, default=None,
              envvar='ZHMC_NO_VERIFY',
              help="Do not verify the HMC certificate. "
                   "(Default: ZHMC_NO_VERIFY environment variable, or verify "
                   "the HMC certificate).")
@click.option('-c', '--ca-certs', type=str, envvar='ZHMC_CA_CERTS',
              help="Path name of certificate file or directory with CA "
                   "certificates to be used for verifying the HMC certificate. "
                   "(Default: Path name in ZHMC_CA_CERTS environment variable, "
                   "or path name in REQUESTS_CA_BUNDLE environment variable, "
                   "or path name in CURL_CA_BUNDLE environment variable, "
                   "or the 'certifi' Python package which provides the "
                   "Mozilla CA Certificate List).")
@click.option('-o', '--output-format',
              type=click.Choice(TABLE_FORMATS + ['json']),
              help='Output format (Default: {def_of}).'.
              format(def_of=DEFAULT_OUTPUT_FORMAT))
@click.option('-x', '--transpose', type=str, is_flag=True,
              help='Transpose the output table for metrics.')
@click.option('-e', '--error-format', type=click.Choice(ERROR_FORMATS),
              help='Error message format (Default: {def_ef}).'.
              format(def_ef=DEFAULT_ERROR_FORMAT))
@click.option('-t', '--timestats', type=str, is_flag=True,
              help='Show time statistics of HMC operations.')
@click.option('--log', type=str, metavar='COMP=LEVEL,...',
              help="Set a component to a log level (COMP: [{comps}], "
              "LEVEL: [{levels}], Default: {def_log}).".
              format(comps='|'.join(LOG_COMPONENTS),
                     levels='|'.join(LOG_LEVELS),
                     def_log=DEFAULT_LOG))
@click.option('--log-dest', type=click.Choice(LOG_DESTINATIONS),
              help="Log destination for this command (Default: {def_dest}).".
              format(def_dest=DEFAULT_LOG_DESTINATION))
@click.option('--syslog-facility', type=click.Choice(SYSLOG_FACILITIES),
              help="Syslog facility when logging to the syslog "
              "(Default: {def_slf}).".
              format(def_slf=DEFAULT_SYSLOG_FACILITY))
@click.version_option(
    message='%(prog)s, version %(version)s\n' + ZHMCCLIENT_VERSION,
    help="Show the versions of this command and of the zhmcclient package and "
    "exit.")
@click.pass_context
def cli(ctx, host, userid, password, no_verify, ca_certs, output_format,
        transpose, error_format, timestats, log, log_dest, syslog_facility):
    """
    Command line interface for the IBM Z HMC.

    The options shown in this help text are general options that can also
    be specified on any of the (sub-)commands.
    """

    # Concept: In interactive mode, the global options specified in the command
    # line are used as defaults for the commands that are issued interactively.
    # The interactive commands may override these options.
    # This requires being able to determine for each option whether it has been
    # specified. This is the reason the options don't define defaults in the
    # decorators that define them.

    if ctx.obj is None:
        # We are in command mode or are processing the command line options in
        # interactive mode.
        # We apply the documented option defaults.
        if output_format is None:
            output_format = DEFAULT_OUTPUT_FORMAT
        if transpose is None:
            transpose = False
        if error_format is None:
            error_format = DEFAULT_ERROR_FORMAT
        if timestats is None:
            timestats = DEFAULT_TIMESTATS
        if no_verify is None:
            no_verify = DEFAULT_NO_VERIFY
    else:
        # We are processing an interactive command.
        # We apply the option defaults from the command line options.
        if host is None:
            host = ctx.obj.host
        if userid is None:
            userid = ctx.obj.userid
        if password is None:
            # pylint: disable=protected-access
            password = ctx.obj._password
        if no_verify is None:
            no_verify = ctx.obj.no_verify
        if ca_certs is None:
            ca_certs = ctx.obj.ca_certs
        if output_format is None:
            output_format = ctx.obj.output_format
        if transpose is None:
            transpose = ctx.obj.transpose
        if error_format is None:
            error_format = ctx.obj.error_format
        if timestats is None:
            timestats = ctx.obj.timestats

    if transpose and output_format == 'json':
        raise click_exception(
            "Transposing output tables (-x / --transpose) conflicts with "
            "non-table output format (-o / --output-format): {of}".
            format(of=output_format),
            error_format)

    if no_verify and ca_certs:
        raise click_exception(
            "Disabling HMC certificate verification (-n / --no-verify / "
            "ZHMC_NO_VERIFY) conflicts with specifying a CA certificate path "
            "(-c / --ca-certs / ZHMC_CA_CERTS)",
            error_format)

    # TODO: Add context support for the following options:
    if log is None:
        log = DEFAULT_LOG
    if log_dest is None:
        log_dest = DEFAULT_LOG_DESTINATION
    if syslog_facility is None:
        syslog_facility = DEFAULT_SYSLOG_FACILITY

    # Now we have the effective values for the options as they should be used
    # by the current command, regardless of the mode.

    # Set up logging

    for lc in LOG_COMPONENTS:
        reset_logger(lc)

    if log_dest == 'syslog':
        # The choices in SYSLOG_FACILITIES have been validated by click
        # so we don't need to further check them.
        facility = SysLogHandler.facility_names[syslog_facility]
        system = platform.system()
        if system.startswith('CYGWIN_NT'):
            # Value is 'CYGWIN_NT-6.1'; strip off trailing version:
            system = 'CYGWIN_NT'
        try:
            addresses = SYSLOG_ADDRESSES[system]
        except KeyError:
            raise NotImplementedError(
                "Logging to syslog is not supported on this platform: {p}".
                format(p=system))
        assert isinstance(addresses, list)
        for address in addresses:
            try:
                handler = SysLogHandler(address=address, facility=facility)
            except Exception:  # pylint: disable=broad-except
                continue
            break
        else:
            exc = sys.exc_info()[1]
            exc_name = exc.__class__.__name__ if exc else None
            raise RuntimeError(
                "Creating SysLogHandler with addresses {al!r} failed. "
                "Failure on last address {a!r} was: {exc}: {msg}".
                format(al=addresses, a=address, exc=exc_name, msg=exc))
        fs = '%(levelname)s %(name)s: %(message)s'
        handler.setFormatter(logging.Formatter(fs))
    elif log_dest == 'stderr':
        handler = StreamHandler(stream=sys.stderr)
        fs = '%(levelname)s %(name)s: %(message)s'
        handler.setFormatter(logging.Formatter(fs))
    else:
        # The choices in LOG_DESTINATIONS have been validated by click
        assert log_dest == 'none'
        handler = None

    log_specs = log.split(',')
    for log_spec in log_specs:

        # ignore extra ',' at begin, end or in between
        if log_spec == '':
            continue

        try:
            log_comp, log_level = log_spec.split('=', 1)
        except ValueError:
            raise click_exception("Missing '=' in COMP=LEVEL specification in "
                                  "--log option: {ls}".format(ls=log_spec),
                                  error_format)

        level = getattr(logging, log_level.upper(), None)
        if level is None:
            raise click_exception("Invalid log level in COMP=LEVEL "
                                  "specification in --log option: {ls}".
                                  format(ls=log_spec),
                                  error_format)

        if log_comp not in LOG_COMPONENTS:
            raise click_exception("Invalid log component in COMP=LEVEL "
                                  "specification in --log option: {ls}".
                                  format(ls=log_spec), error_format)

        if handler:
            setup_logger(log_comp, handler, level)

    session_id = os.environ.get('ZHMC_SESSION_ID', None)
    if session_id and session_id.startswith('faked_session:'):
        # This should be used by the zhmc function tests only.
        # A SyntaxError raised by an incorrect expression is considered
        # an internal error in the function tests and is therefore not
        # handled.
        expr = session_id.split(':', 1)[1]
        faked_session = eval(expr)  # pylint: disable=eval-used
        assert isinstance(faked_session, zhmcclient_mock.FakedSession)
        session_id = faked_session

    def get_password_via_prompt(host, userid):
        """
        Password retrieval function that prompts for the password.

        It follows the interface defined in
        :func:`~zhmcclient.get_password_interface` and needs access to the
        click context (ctx).
        """
        if userid is not None and host is not None:
            ctx.obj.spinner.stop()
            password = click.prompt(
                "Enter password (for user {userid} at HMC {host})".
                format(userid=userid, host=host), hide_input=True,
                confirmation_prompt=False, type=str, err=True)
            ctx.obj.spinner.start()
            return password

        raise click_exception("{cmd} command requires logon, but no "
                              "session-id or userid provided.".
                              format(cmd=ctx.invoked_subcommand),
                              error_format)

    # We create a command context for each command: An interactive command has
    # its own command context different from the command context for the
    # command line.
    ctx.obj = CmdContext(host, userid, password, no_verify, ca_certs,
                         output_format, transpose, error_format, timestats,
                         session_id, get_password_via_prompt)

    # Invoke default command
    if ctx.invoked_subcommand is None:
        ctx.invoke(repl) def print_properties(cmd_ctx, properties, output_format, show_list=None):
    """
    Print properties in the desired output format.

    The spinner is stopped just before printing.

    Parameters:

      cmd_ctx (CmdContext): Context object of the command.

      properties (dict): The properties.

      output_format (string): Output format from the command line.

      show_list (iterable of string): The property names to be shown.
        If `None`, all properties are shown.
    """
    if output_format in TABLE_FORMATS:
        if output_format == 'table':
            output_format = 'psql'
        print_properties_as_table(cmd_ctx, properties, output_format, show_list)
    elif output_format == 'json':
        print_properties_as_json(cmd_ctx, properties, show_list)
    else:
        raise InvalidOutputFormatError(output_format) def print_resources(
        cmd_ctx, resources, output_format, show_list=None, additions=None,
        all=False):
    # pylint: disable=redefined-builtin
    """
    Print the properties of a list of resources in the desired output format.

    While accessing the properties of the resources, they are fetched from
    the HMC as needed.
    The spinner is stopped just before printing.

    Parameters:

      cmd_ctx (CmdContext): Context object of the command.

      resources (iterable of BaseResource):
        The resources.

      output_format (string): Output format from command line.

      show_list (iterable of string):
        The property names to be shown. If a property is not in the resource
        object, it will be retrieved from the HMC. This iterable also defines
        the order of columns in the table, from left to right in iteration
        order.
        If `None`, all properties in the resource objects are shown, and their
        column order is ascending by property name.

      additions (dict of dict of values): Additional properties,
        as a dict keyed by the property name (which also needs to be listed in
        `show_list`),
        whose value is a dict keyed by the resource URI,
        whose value is the value to be shown.
        If `None`, no additional properties are defined.

      all (bool): Add all remaining properties in sorted order.

    Raises:
        InvalidOutputFormatError
        zhmcclient.HTTPError
        zhmcclient.ParseError
        zhmcclient.AuthError
        zhmcclient.ConnectionError
    """
    if output_format in TABLE_FORMATS:
        if output_format == 'table':
            output_format = 'psql'
        print_resources_as_table(
            cmd_ctx, resources, output_format, show_list, additions, all)
    elif output_format == 'json':
        print_resources_as_json(cmd_ctx, resources, show_list, additions, all)
    else:
        raise InvalidOutputFormatError(output_format) def abort_if_false(ctx, param, value):
    """
    Click callback function that aborts the current command if the option
    value is false.

    Because this used as a reaction to an interactive confirmation question,
    we issue the error message always in a human readable format (i.e. ignore
    the specified error format).

    Note that abortion mechanisms such as ctx.abort() or raising click.Abort
    terminate the CLI completely, and not just the current command. This makes
    a difference in the interactive mode.

    Parameters:

      ctx (:class:`click.Context`): The click context object. Created by the
        ``@click.pass_context`` decorator.

      param (class:`click.Option`): The click option that used this callback.

      value: The option value to be tested.
    """
    # pylint: disable=unused-argument
    if not value:
        # click.ClickException seems to be the only reasonable exception we
        # can raise here, but it prefixes the error text with 'Error: ', which
        # is confusing in this case, because the user simply decided to abort.
        raise click.ClickException("Aborted!") def options_to_properties(options, name_map=None):
    """
    Convert click options into HMC resource properties.

    The click option names in input parameters to this function are the
    original option names (e.g. as produced by `original_options()`.

    Options with a value of `None` are not added to the returned resource
    properties.

    If a name mapping dictionary is specified, the option names are mapped
    using that dictionary. If an option name is mapped to `None`, it is not
    going to be added to the set of returned resource properties.

    Parameters:

      options (dict): The options dictionary (key: original option name,
        value: option value).

      name_map (dict): `None` or name mapping dictionary (key: original
        option name, value: property name, or `None` to not add this option to
        the returned properties).

    Returns:

      dict: Resource properties (key: property name, value: option value)
    """
    properties = {}
    for name, value in six.iteritems(options):
        if value is None:
            continue
        if name_map:
            name = name_map.get(name, name)
        if name is not None:
            properties[name] = value
    return properties def original_options(options):
    """
    Return the input options with their original names.

    This is used to undo the name change the click package applies
    automatically before passing the options to the function that was decorated
    with 'click.option()'. The original names are needed in case there is
    special processing of the options on top of 'options_to_properties()'.

    The original names are constructed by replacing any underscores '_' with
    hyphens '-'. This approach may not be perfect in general, but it works for
    the zhmc CLI because the original option names do not have any underscores.

    Parameters:

      options (dict): The click options dictionary as passed to the decorated
        function by click (key: option name as changed by click, value: option
        value).

    Returns:

      dict: Options with their original names.
    """
    org_options = {}
    for name, value in six.iteritems(options):
        org_name = name.replace('_', '-')
        org_options[org_name] = value
    return org_options COMMAND_OPTIONS_METAVAR = '[COMMAND-OPTIONS]' def click_exception(exc, error_format):
    """
    Return a ClickException object with the message from an input exception
    in a desired error message format.

    Parameters:

      exc (exception or string):
        The exception or the message.

      error_format (string):
        The error format (see ``--error-format`` general option).

    Returns:
      click.ClickException: The new exception.
    """
    if error_format == 'def':
        if isinstance(exc, zhmcclient.Error):
            error_str = exc.str_def()
        elif isinstance(exc, Exception):
            error_str = str(exc)
        else:
            assert isinstance(exc, six.string_types)
            error_str = "classname: None, message: {msg}".format(msg=exc)
    else:
        assert error_format == 'msg'
        if isinstance(exc, Exception):
            error_str = "{exc}: {msg}".format(
                exc=exc.__class__.__name__, msg=exc)
        else:
            assert isinstance(exc, six.string_types)
            error_str = exc
    new_exc = click.ClickException(error_str)
    new_exc.__cause__ = None
    return new_exc def add_options(click_options):
    """
    Decorator that adds multiple Click options to the decorated function.

    The list is reversed because of the way Click processes options.

    Note: This function has its origins in the
    https://github.com/pywbem/pywbemtools project (Apache 2.0 license)

    Parameters:

      click_options (list): List of `click.option` objects.
    """

    def _add_options(func):
        """
        Apply the Click options to the function in reversed order.
        """
        for option in reversed(click_options):
            func = option(func)
        return func

    return _add_options LIST_OPTIONS = [
    click.option('--names-only', is_flag=True, required=False,
                 help='Restrict properties shown to only the names of the '
                 'resource and its parents'),
    click.option('--uri', is_flag=True, required=False,
                 help='Add the resource URI to the properties shown'),
    click.option('--all', is_flag=True, required=False,
                 help='Show all properties'),
]
import click
import zhmcclient
from .zhmccli import cli
from ._helper import print_properties, print_resources, abort_if_false, \
    options_to_properties, original_options, COMMAND_OPTIONS_METAVAR, \
    click_exception, add_options, LIST_OPTIONS
              '0 indicates that there is no such comparison.')
@click.option('--case-sensitive', type=int, required=False,
              help='The new indicator whether the password is case sensitive.')
@click.pass_obj
def password_rule_update(cmd_ctx, password_rule, **options):
    """
    Update the properties of an HMC password rule.

    Note that HMC password rules cannot be renamed.

    Note that the characters rules of password rules can be managed via the
    'characterrule' command group.

    Only the properties will be changed for which a corresponding option is
    specified, so the default for all options is not to change properties.

    In addition to the command-specific options shown in this help text, the
    general options (see 'zhmc --help') can also be specified right after the
    'zhmc' command name.
    """
    cmd_ctx.execute_cmd(lambda: cmd_password_rule_update(
        cmd_ctx, password_rule, options))


@password_rule_group.command('delete', options_metavar=COMMAND_OPTIONS_METAVAR)
@click.argument('PASSWORD_RULE', type=str, metavar='PASSWORD_RULE')
@click.option('-y', '--yes', is_flag=True, callback=abort_if_false,
              expose_value=False,
              help='Skip prompt to confirm deletion of the password rule.',
              prompt='Are you sure you want to delete this password rule ?')
@click.pass_obj
def password_rule_delete(cmd_ctx, password_rule):
    """
    Delete a user-defined HMC password rule.

    In addition to the command-specific options shown in this help text, the
    general options (see 'zhmc --help') can also be specified right after the
    'zhmc' command name.
    """
    cmd_ctx.execute_cmd(lambda: cmd_password_rule_delete(
        cmd_ctx, password_rule))


def cmd_password_rule_list(cmd_ctx, options):
    # pylint: disable=missing-function-docstring

    client = zhmcclient.Client(cmd_ctx.session)
    console = client.consoles.console

    show_list = [
        'name',
    ]
    if not options['names_only']:
        show_list.extend([
            'type',
            'description',
        ])
    if options['uri']:
        show_list.extend([
            'element-uri',
        ])

    additions = {}

    try:
        password_rules = console.password_rules.list(full_properties=False)
    except zhmcclient.Error as exc:
        raise click_exception(exc, cmd_ctx.error_format)

    try:
        print_resources(cmd_ctx, password_rules, cmd_ctx.output_format,
                        show_list, additions, all=options['all'])
    except zhmcclient.Error as exc:
        raise click_exception(exc, cmd_ctx.error_format)


def cmd_password_rule_show(cmd_ctx, password_rule_name):
    # pylint: disable=missing-function-docstring

    client = zhmcclient.Client(cmd_ctx.session)
    console = client.consoles.console
    password_rule = find_password_rule(cmd_ctx, console, password_rule_name)

    try:
        password_rule.pull_full_properties()
    except zhmcclient.Error as exc:
        raise click_exception(exc, cmd_ctx.error_format)

    properties = dict(password_rule.properties)

    print_properties(cmd_ctx, properties, cmd_ctx.output_format)


def cmd_password_rule_create(cmd_ctx, options):
    # pylint: disable=missing-function-docstring

    client = zhmcclient.Client(cmd_ctx.session)
    console = client.consoles.console

    org_options = original_options(options)
    properties = options_to_properties(org_options, None)

    try:
        new_password_rule = console.password_rules.create(properties)
    except zhmcclient.Error as exc:
        raise click_exception(exc, cmd_ctx.error_format)

    cmd_ctx.spinner.stop()
    click.echo("New password rule '{r}' has been created.".
               format(r=new_password_rule.properties['name']))


def cmd_password_rule_update(cmd_ctx, password_rule_name, options):
    # pylint: disable=missing-function-docstring

    client = zhmcclient.Client(cmd_ctx.session)
    console = client.consoles.console
    password_rule = find_password_rule(cmd_ctx, console, password_rule_name)

    org_options = original_options(options)

    properties = options_to_properties(org_options, None)