def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except KeyboardInterrupt: with util.disable_log_to_console(): logging.exception("KeyboardInterrupt") print("Interrupt received; exiting.", file=sys.stderr) sys.exit(1) except util.UrlError as exc: with util.disable_log_to_console(): msg_args = {"url": exc.url, "error": exc} if exc.url: msg_tmpl = ua_status.LOG_CONNECTIVITY_ERROR_WITH_URL_TMPL else: msg_tmpl = ua_status.LOG_CONNECTIVITY_ERROR_TMPL logging.exception(msg_tmpl.format(**msg_args)) print(ua_status.MESSAGE_CONNECTIVITY_ERROR, file=sys.stderr) sys.exit(1) except exceptions.UserFacingError as exc: with util.disable_log_to_console(): logging.exception(exc.msg) print("{}".format(exc.msg), file=sys.stderr) sys.exit(exc.exit_code) except Exception: with util.disable_log_to_console(): logging.exception("Unhandled exception, please file a bug") print(ua_status.MESSAGE_UNEXPECTED_ERROR, file=sys.stderr) sys.exit(1)
def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except KeyboardInterrupt: with util.disable_log_to_console(): logging.exception('KeyboardInterrupt') print('Interrupt received; exiting.', file=sys.stderr) sys.exit(1) except exceptions.UserFacingError as exc: with util.disable_log_to_console(): logging.exception(exc.msg) print('ERROR: {}'.format(exc.msg), file=sys.stderr) sys.exit(1)
def _attach_with_token(cfg: config.UAConfig, token: str, allow_enable: bool) -> int: """Common functionality to take a token and attach via contract backend""" try: contract.request_updated_contract(cfg, token, allow_enable=allow_enable) except util.UrlError as exc: with util.disable_log_to_console(): logging.exception(exc) print(ua_status.MESSAGE_ATTACH_FAILURE) cfg.status() # Persist updated status in the event of partial attach return 1 except exceptions.UserFacingError as exc: logging.warning(exc.msg) cfg.status() # Persist updated status in the event of partial attach return 1 contract_name = cfg.machine_token["machineTokenInfo"]["contractInfo"][ "name"] print( ua_status.MESSAGE_ATTACH_SUCCESS_TMPL.format( contract_name=contract_name)) action_status(args=None, cfg=cfg) return 0
def test_no_error_if_console_handler_not_found(self, caplog_text): with mock.patch("uaclient.util.logging.getLogger") as m_getlogger: m_getlogger.return_value.handlers = [] with util.disable_log_to_console(): pass assert "no console handler found" in caplog_text()
def action_attach(args, cfg): if cfg.is_attached: print("This machine is already attached to '{}'.".format( cfg.accounts[0]["name"])) return 0 if os.getuid() != 0: raise exceptions.NonRootUserError() contract_token = args.token if not contract_token: print("No valid contract token available") return 1 try: contract.request_updated_contract(cfg, contract_token, allow_enable=args.auto_enable) except util.UrlError as exc: with util.disable_log_to_console(): logging.exception(exc.msg) print( ua_status.MESSAGE_ATTACH_FAILURE_TMPL.format(url=cfg.contract_url)) return 1 except exceptions.UserFacingError as exc: logging.warning(exc.msg) action_status(args=None, cfg=cfg) return 1 contract_name = cfg.machine_token["machineTokenInfo"]["contractInfo"][ "name"] print( ua_status.MESSAGE_ATTACH_SUCCESS_TMPL.format( contract_name=contract_name)) action_status(args=None, cfg=cfg) return 0
def export_gpg_key_from_keyring( key_id: str, source_keyring_file: str, destination_keyfile: str ) -> None: """Export a specific key from source_keyring_file into destination_keyfile :param key_id: Long fingerprint of key to export. :param source_keyring_file: The keyring file from which to export. :param destination_keyfile: The filename created with the single exported key. :raise UserFacingError: Any GPG errors or if specific key does not exist in the source_keyring_file. """ export_cmd = [ "gpg", "--output", destination_keyfile, "--yes", "--no-auto-check-trustdb", "--no-default-keyring", "--keyring", source_keyring_file, "--export", key_id, ] logging.debug("Exporting GPG key %s from %s", key_id, source_keyring_file) try: out, err = util.subp(export_cmd) except util.ProcessExecutionError as exc: with util.disable_log_to_console(): logging.error(str(exc)) raise exceptions.UserFacingError( "Unable to export GPG keys from keyring {}".format( source_keyring_file ) ) if "nothing exported" in err: raise exceptions.UserFacingError( "GPG key '{}' not found in {}".format(key_id, source_keyring_file) ) if not os.path.exists(destination_keyfile): msg = "Unexpected error exporting GPG key '{}' from {}".format( key_id, source_keyring_file ) with util.disable_log_to_console(): logging.error(msg + " Error: {}".format(err)) raise exceptions.UserFacingError(msg)
def process_entitlements_delta( past_entitlements: "Dict[str, Any]", new_entitlements: "Dict[str, Any]", allow_enable: bool, series_overrides: bool = True, ) -> None: """Iterate over all entitlements in new_entitlement and apply any delta found according to past_entitlements. :param past_entitlements: dict containing the last valid information regarding service entitlements. :param new_entitlements: dict containing the current information regarding service entitlements. :param allow_enable: Boolean set True if allowed to perform the enable operation. When False, a message will be logged to inform the user about the recommended enabled service. :param series_overrides: Boolean set True if series overrides should be applied to the new_access dict. """ delta_error = False unexpected_error = False for name, new_entitlement in sorted(new_entitlements.items()): try: process_entitlement_delta( past_entitlements.get(name, {}), new_entitlement, allow_enable=allow_enable, series_overrides=series_overrides, ) except exceptions.UserFacingError: delta_error = True with util.disable_log_to_console(): logging.exception( "Failed to process contract delta for {name}:" " {delta}".format(name=name, delta=new_entitlement)) except Exception: unexpected_error = True with util.disable_log_to_console(): logging.exception( "Unexpected error processing contract delta for {name}:" " {delta}".format(name=name, delta=new_entitlement)) if unexpected_error: raise exceptions.UserFacingError(status.MESSAGE_UNEXPECTED_ERROR) elif delta_error: raise exceptions.UserFacingError( status.MESSAGE_ATTACH_FAILURE_DEFAULT_SERVICES)
def action_refresh(args, cfg): try: contract.request_updated_contract(cfg) except util.UrlError as exc: with util.disable_log_to_console(): logging.exception(exc) raise exceptions.UserFacingError(ua_status.MESSAGE_REFRESH_FAILURE) print(ua_status.MESSAGE_REFRESH_SUCCESS) return 0
def test_disable_log_to_console_does_nothing_at_debug_level( self, logging_sandbox, capsys): cli.setup_logging(logging.DEBUG, logging.DEBUG) with util.disable_log_to_console(): logging.error("test error") logging.info("test info") out, err = capsys.readouterr() combined_output = out + err assert "test error" in combined_output assert "test info" in combined_output
def request_updated_contract(cfg, contract_token: "Optional[str]" = None, allow_enable=False): """Request contract refresh from ua-contracts service. Compare original token to new token and react to entitlement deltas. :param cfg: Instance of UAConfig for this machine. :param contract_token: String contraining an optional contract token. :param allow_enable: Boolean set True if allowed to perform the enable operation. When False, a message will be logged to inform the user about the recommended enabled service. :raise UserFacingError: on failure to update contract or error processing contract deltas :raise UrlError: On failure to contact the server """ orig_token = cfg.machine_token orig_entitlements = cfg.entitlements if orig_token and contract_token: raise RuntimeError( "Got unexpected contract_token on an already attached machine") contract_client = UAContractClient(cfg) if contract_token: # We are a mid ua-attach and need to get machinetoken try: new_token = contract_client.request_contract_machine_attach( contract_token=contract_token) except util.UrlError as e: if isinstance(e, ContractAPIError): if hasattr(e, "code"): if e.code == 401: raise exceptions.UserFacingError( status.MESSAGE_ATTACH_INVALID_TOKEN) elif e.code == 403: raise exceptions.UserFacingError( status.MESSAGE_ATTACH_EXPIRED_TOKEN) raise e with util.disable_log_to_console(): logging.exception(str(e)) raise exceptions.UserFacingError(status.MESSAGE_CONNECTIVITY_ERROR) else: machine_token = orig_token["machineToken"] contract_id = orig_token["machineTokenInfo"]["contractInfo"]["id"] new_token = contract_client.request_machine_token_update( machine_token=machine_token, contract_id=contract_id) expiry = new_token["machineTokenInfo"]["contractInfo"].get("effectiveTo") if expiry: if datetime.strptime(expiry, "%Y-%m-%dT%H:%M:%SZ") < datetime.utcnow(): raise exceptions.UserFacingError( status.MESSAGE_CONTRACT_EXPIRED_ERROR) process_entitlements_delta(orig_entitlements, cfg.entitlements, allow_enable)
def action_attach(args, cfg): if cfg.is_attached: print("This machine is already attached to '%s'." % cfg.accounts[0]['name']) return 0 if os.getuid() != 0: raise exceptions.NonRootUserError() contract_client = contract.UAContractClient(cfg) if not args.token: with util.disable_log_to_console(): bound_macaroon_bytes = sso.discharge_root_macaroon(contract_client) if bound_macaroon_bytes is None: print('Could not attach machine. Unable to obtain authenticated' ' user token') return 1 bound_macaroon = bound_macaroon_bytes.decode('utf-8') cfg.write_cache('bound-macaroon', bound_macaroon) try: contract_client.request_accounts(macaroon_token=bound_macaroon) contract_token = contract.get_contract_token_for_account( contract_client, bound_macaroon, cfg.accounts[0]['id']) except (sso.SSOAuthError, util.UrlError) as e: logging.error(str(e)) print('Could not attach machine. Unable to obtain authenticated' ' contract token') return 1 else: contract_token = args.token if not contract_token: print('No valid contract token available') return 1 if not contract.request_updated_contract( cfg, contract_token, allow_enable=args.auto_enable): print( ua_status.MESSAGE_ATTACH_FAILURE_TMPL.format(url=cfg.contract_url)) return 1 contract_name = ( cfg.machine_token['machineTokenInfo']['contractInfo']['name']) print( ua_status.MESSAGE_ATTACH_SUCCESS_TMPL.format( contract_name=contract_name)) action_status(args=None, cfg=cfg) return 0
def request_updated_contract(cfg, contract_token: "Optional[str]" = None, allow_enable=False): """Request contract refresh from ua-contracts service. Compare original token to new token and react to entitlement deltas. :param cfg: Instance of UAConfig for this machine. :param contract_token: String contraining an optional contract token. :param allow_enable: Boolean set True if allowed to perform the enable operation. When False, a message will be logged to inform the user about the recommended enabled service. :raise UserFacingError: on failure to update contract or error processing contract deltas :raise UrlError: On failure to contact the server """ orig_token = cfg.machine_token orig_entitlements = cfg.entitlements if orig_token and contract_token: raise RuntimeError( "Got unexpected contract_token on an already attached machine") contract_client = UAContractClient(cfg) if contract_token: # We are a mid ua-attach and need to get machinetoken try: new_token = contract_client.request_contract_machine_attach( contract_token=contract_token) except util.UrlError as e: if isinstance(e, ContractAPIError): if API_ERROR_INVALID_TOKEN in e: raise exceptions.UserFacingError( status.MESSAGE_ATTACH_INVALID_TOKEN) raise e with util.disable_log_to_console(): logging.exception(str(e)) raise exceptions.UserFacingError(status.MESSAGE_CONNECTIVITY_ERROR) else: machine_token = orig_token["machineToken"] contract_id = orig_token["machineTokenInfo"]["contractInfo"]["id"] new_token = contract_client.request_machine_token_refresh( machine_token=machine_token, contract_id=contract_id) expiry = new_token["machineTokenInfo"]["contractInfo"].get("effectiveTo") if expiry: if datetime.strptime(expiry, "%Y-%m-%dT%H:%M:%SZ") < datetime.utcnow(): raise exceptions.UserFacingError( status.MESSAGE_CONTRACT_EXPIRED_ERROR) user_errors = [] for name, entitlement in sorted(cfg.entitlements.items()): if entitlement["entitlement"].get("entitled"): # Obtain each entitlement's accessContext for this machine new_access = contract_client.request_resource_machine_access( new_token["machineToken"], name) else: new_access = entitlement try: process_entitlement_delta( orig_entitlements.get(name, {}), new_access, allow_enable=allow_enable, ) except exceptions.UserFacingError as e: user_errors.append(e) except Exception as e: with util.disable_log_to_console(): logging.exception(str(e)) raise exceptions.UserFacingError( "Unexpected error handling Ubuntu Advantage contract changes") if user_errors: raise exceptions.UserFacingError( status.MESSAGE_ATTACH_FAILURE_DEFAULT_SERVICES)
def request_updated_contract( cfg, contract_token: "Optional[str]" = None, allow_enable=False ): """Request contract refresh from ua-contracts service. Compare original token to new token and react to entitlement deltas. :param cfg: Instance of UAConfig for this machine. :param contract_token: String contraining an optional contract token. :param allow_enable: Boolean set True if allowed to perform the enable operation. When False, a message will be logged to inform the user about the recommended enabled service. :raise UserFacingError: on failure to update contract or error processing contract deltas :raise UrlError: On failure to contact the server """ orig_token = cfg.machine_token orig_entitlements = cfg.entitlements if orig_token and contract_token: raise RuntimeError( "Got unexpected contract_token on an already attached machine" ) contract_client = UAContractClient(cfg) if contract_token: # We are a mid ua-attach and need to get machinetoken new_token = contract_client.request_contract_machine_attach( contract_token=contract_token ) else: machine_token = orig_token["machineToken"] contract_id = orig_token["machineTokenInfo"]["contractInfo"]["id"] new_token = contract_client.request_machine_token_refresh( machine_token=machine_token, contract_id=contract_id ) user_errors = [] for name, entitlement in sorted(cfg.entitlements.items()): if entitlement["entitlement"].get("entitled"): # Obtain each entitlement's accessContext for this machine new_access = contract_client.request_resource_machine_access( new_token["machineToken"], name ) else: new_access = entitlement try: process_entitlement_delta( orig_entitlements.get(name, {}), new_access, allow_enable=allow_enable, ) except exceptions.UserFacingError as e: user_errors.append(e) except Exception as e: with util.disable_log_to_console(): logging.exception(str(e)) raise exceptions.UserFacingError( "Unexpected error handling Ubuntu Advantage contract changes" ) if user_errors: error_lines = ["Failure processing Ubuntu Advantage contract changes."] error_lines.extend(["- {}".format(error) for error in user_errors]) raise exceptions.UserFacingError("\n".join(error_lines))