def test_read_cache_returns_none_when_data_path_absent( self, tmpdir, key, path_basename ): """Return None when the specified key data_path is not cached.""" cfg = UAConfig({"data_dir": tmpdir.strpath}) assert None is cfg.read_cache(key) assert not tmpdir.join(path_basename).check()
def _perform_enable(entitlement_name: str, cfg: config.UAConfig, *, assume_yes: bool = False, silent_if_inapplicable: bool = False, allow_beta: bool = False) -> bool: """Perform the enable action on a named entitlement. (This helper excludes any messaging, so that different enablement code paths can message themselves.) :param entitlement_name: the name of the entitlement to enable :param cfg: the UAConfig to pass to the entitlement :param assume_yes: Assume a yes response for any prompts during service enable :param silent_if_inapplicable: don't output messages when determining if an entitlement can be enabled on this system :param allow_beta: Allow enabling beta services @return: True on success, False otherwise """ ent_cls = entitlements.ENTITLEMENT_CLASS_BY_NAME[entitlement_name] if not allow_beta and ent_cls.is_beta: tmpl = ua_status.MESSAGE_INVALID_SERVICE_OP_FAILURE_TMPL raise exceptions.UserFacingError( tmpl.format(operation="enable", name=entitlement_name)) entitlement = ent_cls(cfg, assume_yes=assume_yes) ret = entitlement.enable(silent_if_inapplicable=silent_if_inapplicable) cfg.status() # Update the status cache return ret
def test_data_path_returns_file_path_with_defined_data_paths( self, key, path_basename ): """When key is defined in Config.data_paths return data_path value.""" cfg = UAConfig({"data_dir": "/my/dir"}) private_path = "/my/dir/{}/{}".format(PRIVATE_SUBDIR, path_basename) assert private_path == cfg.data_path(key=key)
def _detach(cfg: config.UAConfig, assume_yes: bool) -> int: """Detach the machine from the active Ubuntu Advantage subscription, :param cfg: a ``config.UAConfig`` instance :param assume_yes: Assume a yes answer to any prompts requested. In this case, it means automatically disable any service during detach. @return: 0 on success, 1 otherwise """ to_disable = [] for ent_cls in entitlements.ENTITLEMENT_CLASSES: ent = ent_cls(cfg) if ent.can_disable(silent=True): to_disable.append(ent) if to_disable: suffix = "s" if len(to_disable) > 1 else "" print("Detach will disable the following service{}:".format(suffix)) for ent in to_disable: print(" {}".format(ent.name)) if not util.prompt_for_confirmation(assume_yes=assume_yes): return 1 for ent in to_disable: ent.disable(silent=True) contract_client = contract.UAContractClient(cfg) machine_token = cfg.machine_token["machineToken"] contract_id = cfg.machine_token["machineTokenInfo"]["contractInfo"]["id"] contract_client.detach_machine_from_contract(machine_token, contract_id) cfg.delete_cache() print(ua_status.MESSAGE_DETACH_SUCCESS) return 0
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 status(cfg: UAConfig, show_beta: bool = False) -> Dict[str, Any]: """Return status as a dict, using a cache for non-root users When unattached, get available resources from the contract service to report detailed availability of different resources for this machine. Write the status-cache when called by root. """ if os.getuid() != 0: response = cast("Dict[str, Any]", cfg.read_cache("status-cache")) if not response: response = _unattached_status(cfg) elif not cfg.is_attached: response = _unattached_status(cfg) else: response = _attached_status(cfg) response.update(_get_config_status(cfg)) if os.getuid() == 0: cfg.write_cache("status-cache", response) # Try to remove fix reboot notices if not applicable if not util.should_reboot(): cfg.remove_notice( "", messages.ENABLE_REBOOT_REQUIRED_TMPL.format( operation="fix operation" ), ) response = _handle_beta_resources(cfg, show_beta, response) return response
def _perform_enable(entitlement_name: str, cfg: config.UAConfig, *, assume_yes: bool = False, silent_if_inapplicable: bool = False) -> bool: """Perform the enable action on a named entitlement. (This helper excludes any messaging, so that different enablement code paths can message themselves.) :param entitlement_name: the name of the entitlement to enable :param cfg: the UAConfig to pass to the entitlement :param assume_yes: Assume a yes response for any prompts during service enable :param silent_if_inapplicable: don't output messages when determining if an entitlement can be enabled on this system @return: True on success, False otherwise """ ent_cls = entitlements.ENTITLEMENT_CLASS_BY_NAME[entitlement_name] entitlement = ent_cls(cfg, assume_yes=assume_yes) ret = entitlement.enable(silent_if_inapplicable=silent_if_inapplicable) cfg.status() # Update the status cache return ret
def test_accounts_extracts_accounts_key_from_account_read_cache( self, tmpdir): """Config.accounts property extracts the accounts key from cache.""" cfg = UAConfig({'data_dir': tmpdir.strpath}) cfg.write_cache('accounts', {'accounts': ['acct1', 'acct2']}) assert ['acct1', 'acct2'] == cfg.accounts
def attach_with_token( cfg: config.UAConfig, token: str, allow_enable: bool ) -> None: """ Common functionality to take a token and attach via contract backend :raise UrlError: On unexpected connectivity issues to contract server or inability to access identity doc from metadata service. :raise ContractAPIError: On unexpected errors when talking to the contract server. """ from uaclient.jobs.update_messaging import update_apt_and_motd_messages try: contract.request_updated_contract( cfg, token, allow_enable=allow_enable ) except exceptions.UrlError as exc: # Persist updated status in the event of partial attach ua_status.status(cfg=cfg) update_apt_and_motd_messages(cfg) raise exc except exceptions.UserFacingError as exc: # Persist updated status in the event of partial attach ua_status.status(cfg=cfg) update_apt_and_motd_messages(cfg) raise exc current_iid = identity.get_instance_id() if current_iid: cfg.write_cache("instance-id", current_iid) update_apt_and_motd_messages(cfg)
def test_write_datetime(self, tmpdir): cfg = UAConfig({"data_dir": tmpdir.strpath}) key = "test_key" dt = datetime.datetime.now() cfg.write_cache(key, dt) with open(cfg.data_path(key)) as f: assert dt.isoformat() == f.read().strip('"')
def test_read_cache_returns_none_when_data_path_absent( self, key, path_basename): """Return None when the specified key data_path is not cached.""" tmp_dir = self.tmp_dir() cfg = UAConfig({'data_dir': tmp_dir}) assert None is cfg.read_cache(key) assert False is os.path.exists(os.path.join(tmp_dir, path_basename))
def test_entitlements_property_keyed_by_entitlement_name(self, tmpdir): """Return machine_token resourceEntitlements, keyed by name.""" cfg = UAConfig({"data_dir": tmpdir.strpath}) token = { "machineTokenInfo": { "contractInfo": { "resourceEntitlements": [ { "type": "entitlement1", "entitled": True }, { "type": "entitlement2", "entitled": True }, ] } } } cfg.write_cache("machine-token", token) expected = { "entitlement1": { "entitlement": { "entitled": True, "type": "entitlement1" } }, "entitlement2": { "entitlement": { "entitled": True, "type": "entitlement2" } }, } assert expected == cfg.entitlements
def test_entitlements_uses_resource_token_from_machine_token(self, tmpdir): """Include entitlement-specicific resourceTokens from machine_token""" cfg = UAConfig({"data_dir": tmpdir.strpath}) token = { "availableResources": ALL_RESOURCES_AVAILABLE, "machineTokenInfo": { "contractInfo": { "resourceEntitlements": [ {"type": "entitlement1", "entitled": True}, {"type": "entitlement2", "entitled": True}, ] } }, "resourceTokens": [ {"type": "entitlement1", "token": "ent1-token"}, {"type": "entitlement2", "token": "ent2-token"}, ], } cfg.write_cache("machine-token", token) expected = { "entitlement1": { "entitlement": {"entitled": True, "type": "entitlement1"}, "resourceToken": "ent1-token", }, "entitlement2": { "entitlement": {"entitled": True, "type": "entitlement2"}, "resourceToken": "ent2-token", }, } assert expected == cfg.entitlements
def test_data_path_returns_file_path_with_undefined_data_paths( self, key, path_basename ): """When key is not in Config.data_paths the key is used to data_dir""" cfg = UAConfig({"data_dir": "/my/d"}) assert "/my/d/{}/{}".format(PRIVATE_SUBDIR, key) == cfg.data_path( key=key )
def test_nonroot_user_uses_cache_if_available(self, m_getuid, tmpdir): m_getuid.return_value = 1000 status = {"pass": True} cfg = UAConfig({"data_dir": tmpdir.strpath}) cfg.write_cache("status-cache", status) assert status == cfg.status()
def test_cache_file_is_written_world_readable(self, _m_getuid, _m_get_available_resources, tmpdir): cfg = UAConfig({"data_dir": tmpdir.strpath}) cfg.status() assert 0o644 == stat.S_IMODE( os.lstat(cfg.data_path("status-cache")).st_mode)
def test_read_cache_returns_content_when_data_path_present( self, tmpdir, key, path_basename): cfg = UAConfig({'data_dir': tmpdir.strpath}) data_path = tmpdir.join(path_basename) with open(data_path.strpath, 'w') as f: f.write('content%s' % key) assert 'content%s' % key == cfg.read_cache(key)
def test_datetimes_are_unserialised(self, tmpdir): cfg = UAConfig({"data_dir": tmpdir.strpath}) os.makedirs(tmpdir.join(PRIVATE_SUBDIR).strpath) data_path = tmpdir.join(PRIVATE_SUBDIR, "dt_test") with open(data_path.strpath, "w") as f: f.write('{"dt": "2019-07-25T14:35:51"}') actual = cfg.read_cache("dt_test") assert {"dt": datetime.datetime(2019, 7, 25, 14, 35, 51)} == actual
def test_read_cache_returns_content_when_data_path_present( self, tmpdir, key, path_basename): cfg = UAConfig({"data_dir": tmpdir.strpath}) os.makedirs(tmpdir.join(PRIVATE_SUBDIR).strpath) data_path = tmpdir.join(PRIVATE_SUBDIR, path_basename) with open(data_path.strpath, "w") as f: f.write("content{}".format(key)) assert "content{}".format(key) == cfg.read_cache(key)
def test_write_cache_writes_non_private_dir_when_private_is_false( self, tmpdir): """When content is not a string, write a json string.""" cfg = UAConfig({'data_dir': tmpdir.strpath}) assert None is cfg.write_cache('key', 'value', private=False) with open(tmpdir.join('key').strpath, 'r') as stream: assert 'value' == stream.read() assert 'value' == cfg.read_cache('key')
def test_read_cache_returns_content_when_data_path_present( self, key, path_basename): tmp_dir = self.tmp_dir() cfg = UAConfig({'data_dir': tmp_dir}) data_path = self.tmp_path(path_basename, tmp_dir) with open(data_path, 'w') as f: f.write('content%s' % key) assert 'content%s' % key == cfg.read_cache(key)
def test_read_cache_returns_stuctured_content_when_json_data_path_present( self, tmpdir, key, path_basename): cfg = UAConfig({'data_dir': tmpdir.strpath}) data_path = tmpdir.join(path_basename) expected = {key: 'content%s' % key} with open(data_path.strpath, 'w') as f: f.write(json.dumps(expected)) assert expected == cfg.read_cache(key)
def test_delete_cache_ignores_files_not_defined_in_data_paths( self, tmpdir): """Any files in data_dir undefined in cfg.data_paths will remain.""" cfg = UAConfig({'data_dir': tmpdir.strpath}) t_file = tmpdir.join('otherfile') with open(t_file.strpath, 'w') as f: f.write('content') assert [os.path.basename(t_file.strpath)] == os.listdir(tmpdir.strpath) cfg.delete_cache() assert [os.path.basename(t_file.strpath)] == os.listdir(tmpdir.strpath)
def test_read_cache_returns_stuctured_content_when_json_data_path_present( self, tmpdir, key, path_basename): cfg = UAConfig({"data_dir": tmpdir.strpath}) os.makedirs(tmpdir.join(PRIVATE_SUBDIR).strpath) data_path = tmpdir.join(PRIVATE_SUBDIR, path_basename) expected = {key: "content{}".format(key)} with open(data_path.strpath, "w") as f: f.write(json.dumps(expected)) assert expected == cfg.read_cache(key)
def test_write_cache_writes_json_string_when_content_not_a_string( self, tmpdir, key, value): """When content is not a string, write a json string.""" cfg = UAConfig({"data_dir": tmpdir.strpath}) expected_json_content = json.dumps(value) assert None is cfg.write_cache(key, value) with open(tmpdir.join(PRIVATE_SUBDIR, key).strpath, "r") as stream: assert expected_json_content == stream.read() assert value == cfg.read_cache(key)
def test_write_cache_creates_secure_private_dir(self, tmpdir): """private_dir is created with permission 0o700.""" cfg = UAConfig({"data_dir": tmpdir.strpath}) # unknown keys are written to the private dir expected_dir = tmpdir.join(PRIVATE_SUBDIR) assert None is cfg.write_cache("somekey", "somevalue") assert True is os.path.isdir( expected_dir.strpath), "Missing expected directory {}".format( expected_dir) assert 0o700 == stat.S_IMODE(os.lstat(expected_dir.strpath).st_mode)
def test_accounts_extracts_accounts_key_from_machine_token_cache( self, tmpdir): """Use machine_token cached accountInfo when no accounts cache.""" cfg = UAConfig({'data_dir': tmpdir.strpath}) accountInfo = {'id': '1', 'name': 'accountname'} cfg.write_cache('machine-token', {'machineTokenInfo': {'accountInfo': accountInfo}}) assert [accountInfo] == cfg.accounts
def test_read_cache_returns_stuctured_content_when_json_data_path_present( self, key, path_basename): tmp_dir = self.tmp_dir() cfg = UAConfig({'data_dir': tmp_dir}) data_path = self.tmp_path(path_basename, tmp_dir) expected = {key: 'content%s' % key} with open(data_path, 'w') as f: f.write(json.dumps(expected)) assert expected == cfg.read_cache(key)
def test_delete_cache_ignores_files_not_defined_in_data_paths(self): """Any files in data_dir undefined in cfg.data_paths will remain.""" tmp_dir = self.tmp_dir() cfg = UAConfig({'data_dir': tmp_dir}) t_file = self.tmp_path('otherfile', tmp_dir) with open(t_file, 'w') as f: f.write('content') assert [os.path.basename(t_file)] == os.listdir(tmp_dir) cfg.delete_cache() assert [os.path.basename(t_file)] == os.listdir(tmp_dir)
def test_accounts_logs_warning_when_missing_accounts_key_in_cache(self): """Config.accounts warns when missing 'accounts' key in cache""" tmp_dir = self.tmp_dir() cfg = UAConfig({'data_dir': tmp_dir}) cfg.write_cache('accounts', {'non-accounts': 'somethingelse'}) assert [] == cfg.accounts expected_warning = ("WARNING: Missing 'accounts' key in cache %s" % self.tmp_path('accounts.json', tmp_dir)) assert expected_warning in self.logs