333 lines
11 KiB
Python
333 lines
11 KiB
Python
import json
|
|
import logging
|
|
import textwrap
|
|
from typing import Any, Dict, List # noqa: F401
|
|
|
|
from uaclient import (
|
|
config,
|
|
contract,
|
|
entitlements,
|
|
event_logger,
|
|
exceptions,
|
|
messages,
|
|
status,
|
|
util,
|
|
)
|
|
from uaclient.api import ProgressWrapper
|
|
from uaclient.api.u.pro.services.dependencies.v1 import (
|
|
ServiceWithDependencies,
|
|
_dependencies,
|
|
)
|
|
from uaclient.api.u.pro.status.enabled_services.v1 import _enabled_services
|
|
from uaclient.cli import cli_util
|
|
from uaclient.cli.commands import ProArgument, ProArgumentGroup, ProCommand
|
|
from uaclient.cli.parser import HelpCategory
|
|
from uaclient.entitlements.entitlement_status import CanDisableFailure
|
|
|
|
event = event_logger.get_event_logger()
|
|
LOG = logging.getLogger(util.replace_top_level_logger_name(__name__))
|
|
|
|
|
|
def prompt_for_dependency_handling(
|
|
cfg: config.UAConfig,
|
|
service: str,
|
|
all_dependencies: List[ServiceWithDependencies],
|
|
enabled_service_names: List[str],
|
|
called_name: str,
|
|
service_title: str,
|
|
):
|
|
dependent_services = []
|
|
for s in all_dependencies:
|
|
if s.name == service or s.name not in enabled_service_names:
|
|
continue
|
|
for requirement in s.depends_on:
|
|
if requirement.name == service:
|
|
dependent_services.append(s.name)
|
|
|
|
for dependent_service in dependent_services:
|
|
dependent_service_title = entitlements.get_title(
|
|
cfg, dependent_service
|
|
)
|
|
user_msg = messages.DEPENDENT_SERVICE.format(
|
|
service_being_disabled=service_title,
|
|
dependent_service=dependent_service_title,
|
|
)
|
|
if not util.prompt_for_confirmation(msg=user_msg):
|
|
raise exceptions.DependentServiceStopsDisable(
|
|
service_being_disabled=service_title,
|
|
dependent_service=dependent_service_title,
|
|
)
|
|
|
|
|
|
def perform_disable(
|
|
entitlement, cfg, *, json_output, assume_yes, update_status=True
|
|
):
|
|
"""Perform the disable action on a named entitlement.
|
|
|
|
:param entitlement_name: the name of the entitlement to enable
|
|
:param cfg: the UAConfig to pass to the entitlement
|
|
:param json_output: output should be json only
|
|
|
|
@return: True on success, False otherwise
|
|
"""
|
|
# Make sure we have the correct variant of the service
|
|
# This can affect what packages get uninstalled
|
|
variant = entitlement.enabled_variant
|
|
if variant is not None:
|
|
entitlement = variant
|
|
|
|
if json_output:
|
|
progress = ProgressWrapper()
|
|
else:
|
|
progress = ProgressWrapper(
|
|
cli_util.CLIEnableDisableProgress(assume_yes=assume_yes)
|
|
)
|
|
|
|
ret, reason = entitlement.disable(progress)
|
|
|
|
if not ret:
|
|
event.service_failed(entitlement.name)
|
|
|
|
if reason is not None and isinstance(reason, CanDisableFailure):
|
|
if reason.message is not None:
|
|
event.info(reason.message.msg)
|
|
event.error(
|
|
error_msg=reason.message.msg,
|
|
error_code=reason.message.name,
|
|
service=entitlement.name,
|
|
)
|
|
else:
|
|
event.service_processed(entitlement.name)
|
|
|
|
if update_status:
|
|
status.status(cfg=cfg) # Update the status cache
|
|
|
|
return ret
|
|
|
|
|
|
@cli_util.verify_json_format_args
|
|
@cli_util.assert_root
|
|
@cli_util.assert_attached(cli_util._raise_enable_disable_unattached_error)
|
|
@cli_util.assert_lock_file("pro disable")
|
|
def action_disable(args, *, cfg, **kwargs):
|
|
"""Perform the disable action on a list of entitlements.
|
|
|
|
@return: 0 on success, 1 otherwise
|
|
"""
|
|
processed_services = []
|
|
failed_services = []
|
|
errors = [] # type: List[Dict[str, Any]]
|
|
warnings = [] # type: List[Dict[str, str]]
|
|
|
|
json_response = {
|
|
"_schema_version": event_logger.JSON_SCHEMA_VERSION,
|
|
"result": "success",
|
|
"needs_reboot": False,
|
|
}
|
|
|
|
json_output = args.format == "json"
|
|
assume_yes = args.assume_yes
|
|
# HACK NOTICE: interactive_only_print here will be a no-op "null_print"
|
|
# function defined above if args.format == "json". We use this function
|
|
# throughout enable for things that should get printed in the normal
|
|
# interactive output so that they don't get printed for the json output.
|
|
interactive_only_print = cli_util.create_interactive_only_print_function(
|
|
json_output
|
|
)
|
|
|
|
if args.purge and assume_yes:
|
|
raise exceptions.InvalidOptionCombination(
|
|
option1="--purge", option2="--assume-yes"
|
|
)
|
|
|
|
names = getattr(args, "service", [])
|
|
(
|
|
entitlements_found,
|
|
entitlements_not_found,
|
|
) = entitlements.get_valid_entitlement_names(names, cfg)
|
|
enabled_service_names = [
|
|
s.name for s in _enabled_services(cfg).enabled_services
|
|
]
|
|
all_dependencies = _dependencies(cfg).services
|
|
|
|
ret = True
|
|
for ent_name in entitlements_found:
|
|
ent = entitlements.entitlement_factory(
|
|
cfg=cfg,
|
|
name=ent_name,
|
|
purge=args.purge,
|
|
)
|
|
|
|
variant = ent.enabled_variant
|
|
if variant is not None:
|
|
ent = variant
|
|
|
|
if not assume_yes:
|
|
# this never happens for json output because we assert earlier that
|
|
# assume_yes must be True for json output
|
|
try:
|
|
prompt_for_dependency_handling(
|
|
cfg,
|
|
ent.name,
|
|
all_dependencies,
|
|
enabled_service_names,
|
|
called_name=ent_name,
|
|
service_title=ent.title,
|
|
)
|
|
except exceptions.UbuntuProError as e:
|
|
LOG.exception(e)
|
|
interactive_only_print(e.msg)
|
|
interactive_only_print(
|
|
messages.ENABLE_FAILED.format(title=ent.title)
|
|
)
|
|
ret = False
|
|
continue
|
|
|
|
if json_output:
|
|
progress = ProgressWrapper()
|
|
else:
|
|
progress = ProgressWrapper(
|
|
cli_util.CLIEnableDisableProgress(assume_yes=assume_yes)
|
|
)
|
|
progress.total_steps = ent.calculate_total_disable_steps()
|
|
try:
|
|
disable_ret, reason = ent.disable(progress)
|
|
status.status(cfg=cfg) # Update the status cache
|
|
|
|
if not disable_ret:
|
|
ret = False
|
|
failed_services.append(ent_name)
|
|
if reason is not None and isinstance(
|
|
reason, CanDisableFailure
|
|
):
|
|
if reason.message is not None:
|
|
interactive_only_print(reason.message.msg)
|
|
errors.append(
|
|
{
|
|
"type": "service",
|
|
"service": ent.name,
|
|
"message": reason.message.msg,
|
|
"message_code": reason.message.name,
|
|
}
|
|
)
|
|
else:
|
|
processed_services.append(ent_name)
|
|
ent_reboot_required = ent._check_for_reboot()
|
|
if ent_reboot_required:
|
|
json_response["needs_reboot"] = True
|
|
interactive_only_print(
|
|
messages.ENABLE_REBOOT_REQUIRED_TMPL.format(
|
|
operation="disable operation"
|
|
)
|
|
)
|
|
except exceptions.UbuntuProError as e:
|
|
ret = False
|
|
failed_services.append(ent_name)
|
|
interactive_only_print(e.msg)
|
|
interactive_only_print(
|
|
messages.DISABLE_FAILED_TMPL.format(title=ent.title)
|
|
)
|
|
errors.append(
|
|
{
|
|
"type": "service",
|
|
"service": ent.name,
|
|
"message": e.msg,
|
|
"message_code": e.msg_code,
|
|
"additional_info": e.additional_info,
|
|
}
|
|
)
|
|
|
|
if entitlements_not_found:
|
|
ret = False
|
|
valid_names = (
|
|
"Try " + ", ".join(entitlements.valid_services(cfg=cfg)) + "."
|
|
)
|
|
service_msg = "\n".join(
|
|
textwrap.wrap(
|
|
valid_names,
|
|
width=80,
|
|
break_long_words=False,
|
|
break_on_hyphens=False,
|
|
)
|
|
)
|
|
err = exceptions.InvalidServiceOpError(
|
|
operation="disable",
|
|
invalid_service=", ".join(entitlements_not_found),
|
|
service_msg=service_msg,
|
|
)
|
|
interactive_only_print(err.msg)
|
|
errors.append(
|
|
{
|
|
"type": "system",
|
|
"service": None,
|
|
"message": err.msg,
|
|
"message_code": err.msg_code,
|
|
"additional_info": err.additional_info,
|
|
}
|
|
)
|
|
|
|
contract_client = contract.UAContractClient(cfg)
|
|
contract_client.update_activity_token()
|
|
|
|
if json_output:
|
|
processed_services.sort()
|
|
failed_services.sort()
|
|
|
|
json_response["result"] = "success" if ret else "failure"
|
|
json_response["processed_services"] = processed_services
|
|
json_response["failed_services"] = failed_services
|
|
json_response["errors"] = errors
|
|
json_response["warnings"] = warnings
|
|
|
|
print(
|
|
json.dumps(
|
|
json_response,
|
|
cls=util.DatetimeAwareJSONEncoder,
|
|
sort_keys=True,
|
|
)
|
|
)
|
|
|
|
return 0 if ret else 1
|
|
|
|
|
|
disable_command = ProCommand(
|
|
"disable",
|
|
help=messages.CLI_ROOT_DISABLE,
|
|
description=messages.CLI_DISABLE_DESC,
|
|
action=action_disable,
|
|
help_category=HelpCategory.OTHER,
|
|
argument_groups=[
|
|
ProArgumentGroup(
|
|
arguments=[
|
|
ProArgument(
|
|
"service",
|
|
help=messages.CLI_DISABLE_SERVICE.format(
|
|
options=", ".join(
|
|
entitlements.valid_services(cfg=config.UAConfig())
|
|
)
|
|
),
|
|
action="store",
|
|
nargs="+",
|
|
),
|
|
ProArgument(
|
|
"--assume-yes",
|
|
help=messages.CLI_ASSUME_YES.format(command="disable"),
|
|
action="store_true",
|
|
),
|
|
ProArgument(
|
|
"--format",
|
|
help=messages.CLI_FORMAT_DESC.format(default="cli"),
|
|
action="store",
|
|
choices=["cli", "json"],
|
|
default="cli",
|
|
),
|
|
ProArgument(
|
|
"--purge",
|
|
help=messages.CLI_PURGE,
|
|
action="store_true",
|
|
),
|
|
]
|
|
)
|
|
],
|
|
)
|