%PDF- %PDF-
Direktori : /lib/python3/dist-packages/uaclient/cli/ |
Current File : //lib/python3/dist-packages/uaclient/cli/enable.py |
import json import logging from typing import Any, Dict, List from uaclient import ( api, config, contract, entitlements, event_logger, exceptions, messages, status, util, ) from uaclient.api.u.pro.services.dependencies.v1 import ( ServiceWithDependencies, _dependencies, ) from uaclient.api.u.pro.status.enabled_services.v1 import ( EnabledService, _enabled_services, ) from uaclient.api.u.pro.status.is_attached.v1 import _is_attached from uaclient.cli import cli_util, constants from uaclient.entitlements.entitlement_status import ( CanEnableFailure, CanEnableFailureReason, ) 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_services: List[EnabledService], called_name: str, variant: str, service_title: str, ): incompatible_services = [] required_services = [] enabled_service_names = [s.name for s in enabled_services] dependencies = next( (s for s in all_dependencies if s.name == service), None ) if dependencies is not None: incompatible_services = [ s.name for s in dependencies.incompatible_with if s.name in enabled_service_names ] required_services = [ s.name for s in dependencies.depends_on if s.name not in enabled_service_names ] for incompatible_service in incompatible_services: cfg_block_disable_on_enable = util.is_config_value_true( config=cfg.cfg, path_to_value="features.block_disable_on_enable", ) incompatible_service_title = entitlements.get_title( cfg, incompatible_service ) user_msg = messages.INCOMPATIBLE_SERVICE.format( service_being_enabled=service_title, incompatible_service=incompatible_service_title, ) if cfg_block_disable_on_enable or not util.prompt_for_confirmation( msg=user_msg ): raise exceptions.IncompatibleServiceStopsEnable( service_being_enabled=service_title, incompatible_service=incompatible_service_title, ) for required_service in required_services: required_service_title = entitlements.get_title(cfg, required_service) user_msg = messages.REQUIRED_SERVICE.format( service_being_enabled=service_title, required_service=required_service_title, ) if not util.prompt_for_confirmation(msg=user_msg): raise exceptions.RequiredServiceStopsEnable( service_being_enabled=service_title, required_service=required_service_title, ) variant_enabled = next( ( s for s in enabled_services if s.name == service and s.variant_enabled and s.variant_name != variant ), None, ) if variant_enabled is not None and variant is not None: to_be_enabled_title = entitlements.get_title(cfg, service, variant) enabled_variant_title = entitlements.get_title( cfg, service, variant_enabled.variant_name ) cfg_block_disable_on_enable = util.is_config_value_true( config=cfg.cfg, path_to_value="features.block_disable_on_enable", ) user_msg = messages.INCOMPATIBLE_SERVICE.format( service_being_enabled=to_be_enabled_title, incompatible_service=enabled_variant_title, ) if cfg_block_disable_on_enable or not util.prompt_for_confirmation( msg=user_msg ): raise exceptions.IncompatibleServiceStopsEnable( service_being_enabled=to_be_enabled_title, incompatible_service=enabled_variant_title, ) def print_json_output( json_output: bool, json_response: Dict[str, Any], processed_services: List[str], failed_services: List[str], errors: List[Dict[str, Any]], warnings: List[Dict[str, Any]], success: bool, ): if json_output: processed_services.sort() failed_services.sort() json_response["result"] = "success" if success 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, ) ) @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 enable") def action_enable(args, *, cfg, **kwargs) -> int: """Perform the enable action on a named entitlement. @return: 0 on success, 1 otherwise """ processed_services = [] # type: List[str] failed_services = [] # type: List[str] errors = [] warnings = [] json_response = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "success", "needs_reboot": False, } json_output = args.format == "json" # 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 ) variant = getattr(args, "variant", "") access_only = args.access_only if variant and access_only: raise exceptions.InvalidOptionCombination( option1="--access-only", option2="--variant" ) interactive_only_print(messages.REFRESH_CONTRACT_ENABLE) try: contract.refresh(cfg) except (exceptions.ConnectivityError, exceptions.UbuntuProError): # Inability to refresh is not a critical issue during enable LOG.warning("Failed to refresh contract", exc_info=True) warnings.append( { "type": "system", "message": messages.E_REFRESH_CONTRACT_FAILURE.msg, "message_code": messages.E_REFRESH_CONTRACT_FAILURE.name, } ) if not _is_attached(cfg).is_attached_and_contract_valid: expired_err = exceptions.ContractExpiredError() interactive_only_print(expired_err.msg) errors.append( { "type": "system", "message": expired_err.msg, "message_code": expired_err.msg_code, } ) print_json_output( json_output, json_response, processed_services, failed_services, errors, warnings, success=False, ) return 1 names = getattr(args, "service", []) ( entitlements_found, entitlements_not_found, ) = entitlements.get_valid_entitlement_names(names, cfg) enabled_services = _enabled_services(cfg).enabled_services all_dependencies = _dependencies(cfg).services ret = True for ent_name in entitlements.order_entitlements_for_enabling( cfg, entitlements_found ): ent = entitlements.entitlement_factory(cfg, ent_name, variant=variant)( cfg, assume_yes=args.assume_yes, allow_beta=args.beta, called_name=ent_name, access_only=access_only, extra_args=kwargs.get("extra_args"), ) LOG.debug("Enabling entitlement %s", ent_name) LOG.debug("Variant: %s", variant) real_name = ent.name ent_title = ent.title if not args.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, real_name, all_dependencies, enabled_services, called_name=real_name, variant=variant, 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 try: if json_output: progress = api.ProgressWrapper() else: progress = api.ProgressWrapper( cli_util.CLIEnableDisableProgress() ) progress.total_steps = ent.calculate_total_enable_steps() ent_ret, reason = ent.enable(progress) status.status(cfg=cfg) # Update the status cache if ( not ent_ret and reason is not None and isinstance(reason, CanEnableFailure) ): if reason.message is not None: interactive_only_print(reason.message.msg) failed_services.append(ent_name) errors.append( { "type": "service", "service": ent_name, "message": reason.message.msg, "message_code": reason.message.name, } ) if reason.reason == CanEnableFailureReason.IS_BETA: # if we failed because ent is in beta and there was no # allow_beta flag/config, pretend it doesn't exist entitlements_not_found.append(ent_name) interactive_only_print( messages.ENABLE_FAILED.format(title=ent.title) ) elif ent_ret: processed_services.append(ent_name) if args.access_only: interactive_only_print( messages.ACCESS_ENABLED_TMPL.format(title=ent.title) ) else: interactive_only_print( messages.ENABLED_TMPL.format(title=ent.title) ) 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="install" ) ) progress.emit( "message_operation", ent.messaging.get("post_enable") ) elif not ent_ret and reason is None: failed_services.append(ent_name) interactive_only_print( messages.ENABLE_FAILED.format(title=ent.title) ) ret &= ent_ret except exceptions.UbuntuProError as e: failed_services.append(ent_name) interactive_only_print(e.msg) interactive_only_print( messages.ENABLE_FAILED.format(title=ent_title) ) errors.append( { "type": "service", "service": ent_name, "message": e.msg, "message_code": e.msg_code, "additional_info": e.additional_info, } ) ret = False if entitlements_not_found: ret = False failed_services += entitlements_not_found err = entitlements.create_enable_entitlements_not_found_error( entitlements_not_found, cfg=cfg, allow_beta=args.beta ) 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() print_json_output( json_output, json_response, processed_services, failed_services, errors, warnings, success=ret, ) return 0 if ret else 1 def add_parser(subparsers, cfg: config.UAConfig): parser = subparsers.add_parser("enable", help=messages.CLI_ROOT_ENABLE) parser.set_defaults(action=action_enable) parser.description = messages.CLI_ENABLE_DESC parser.usage = constants.USAGE_TMPL.format( name=constants.NAME, command="enable <service> [<service>]" ) parser.prog = "enable" parser._positionals.title = messages.CLI_ARGS parser._optionals.title = messages.CLI_FLAGS parser.add_argument( "service", action="store", nargs="+", help=( messages.CLI_ENABLE_SERVICE.format( options=", ".join(entitlements.valid_services(cfg=cfg)) ) ), ) parser.add_argument( "--assume-yes", action="store_true", help=messages.CLI_ASSUME_YES.format(command="enable"), ) parser.add_argument( "--access-only", action="store_true", help=messages.CLI_ENABLE_ACCESS_ONLY, ) parser.add_argument( "--beta", action="store_true", help=messages.CLI_ENABLE_BETA ) parser.add_argument( "--format", action="store", choices=["cli", "json"], default="cli", help=messages.CLI_FORMAT_DESC.format(default="cli"), ) parser.add_argument( "--variant", action="store", help=messages.CLI_ENABLE_VARIANT ) return parser