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()
예제 #2
0
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)
예제 #4
0
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
예제 #5
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
예제 #7
0
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
예제 #9
0
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)
예제 #10
0
 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('"')
예제 #11
0
 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))
예제 #12
0
 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
     )
예제 #15
0
    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()
예제 #16
0
    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)
예제 #18
0
    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
예제 #19
0
    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')
예제 #21
0
    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)
예제 #24
0
    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)
예제 #25
0
    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)
예제 #26
0
 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
예제 #28
0
    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)
예제 #29
0
 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)
예제 #30
0
    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