Beispiel #1
0
    def test_exception_is_config_value_true(self, config_dict, key_val,
                                            FakeConfig):
        path_to_value = "features.allow_beta"
        cfg = FakeConfig()
        cfg.override_features(config_dict)
        with pytest.raises(exceptions.UserFacingError) as excinfo:
            util.is_config_value_true(config=cfg.cfg,
                                      path_to_value=path_to_value)

        expected_msg = status.ERROR_INVALID_CONFIG_VALUE.format(
            path_to_value=path_to_value,
            expected_value="boolean string: true or false",
            value=key_val,
        )
        assert expected_msg == str(excinfo.value)
def write_esm_announcement_message(cfg: config.UAConfig, series: str) -> None:
    """Write human-readable messages if ESM is offered on this LTS release.

    Do not write ESM announcements if esm-apps is enabled or beta.

    :param cfg: UAConfig instance for this environment.
    :param series: string of Ubuntu release series: 'xenial'.
    """
    apps_cls = entitlements.entitlement_factory(cfg=cfg, name="esm-apps")
    apps_inst = apps_cls(cfg)
    enabled_status = ApplicationStatus.ENABLED
    apps_not_enabled = apps_inst.application_status()[0] != enabled_status
    config_allow_beta = util.is_config_value_true(
        config=cfg.cfg, path_to_value="features.allow_beta")
    apps_not_beta = bool(config_allow_beta or not apps_cls.is_beta)

    msg_dir = os.path.join(cfg.data_dir, "messages")
    esm_news_file = os.path.join(msg_dir, ExternalMessage.ESM_ANNOUNCE.value)
    platform_info = util.get_platform_info()
    is_active_esm = util.is_active_esm(platform_info["series"])
    if is_active_esm:
        ua_esm_url = defaults.EOL_UA_URL_TMPL.format(
            hyphenatedrelease=platform_info["release"].replace(".", "-"))
    else:
        ua_esm_url = defaults.BASE_ESM_URL
    if apps_not_beta and apps_not_enabled:
        util.write_file(esm_news_file,
                        "\n" + ANNOUNCE_ESM_TMPL.format(url=ua_esm_url))
    else:
        util.remove_file(esm_news_file)
    def _allow_fips_on_cloud_instance(
        self, series: str, cloud_id: str
    ) -> bool:
        """Return False when FIPS is allowed on this cloud and series.

        On Xenial GCP there will be no cloud-optimized kernel so
        block default ubuntu-fips enable. This can be overridden in
        config with features.allow_xenial_fips_on_cloud.

        GCP doesn't yet have a cloud-optimized kernel or metapackage so
        block enable of fips if the contract does not specify ubuntu-gcp-fips.
        This also can be overridden in config with
        features.allow_default_fips_metapackage_on_gcp.

        :return: False when this cloud, series or config override allows FIPS.
        """
        if cloud_id == "gce":
            if util.is_config_value_true(
                config=self.cfg.cfg,
                path_to_value="features.allow_default_fips_metapackage_on_gcp",
            ):
                return True

            # GCE only has FIPS support for bionic and focal machines
            if series in ("bionic", "focal"):
                return True

            return bool("ubuntu-gcp-fips" in super().packages)

        return True
    def status(self, show_beta=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]", self.read_cache("status-cache"))
            if not response:
                response = self._unattached_status()
        elif not self.is_attached:
            response = self._unattached_status()
        else:
            response = self._attached_status()
        response.update(self._get_config_status())
        if os.getuid() == 0:
            self.write_cache("status-cache", response)

        config_allow_beta = util.is_config_value_true(
            config=self.cfg, path_to_value="features.allow_beta")
        show_beta |= config_allow_beta
        if not show_beta:
            response = self._remove_beta_resources(response)

        return response
def valid_services(cfg: UAConfig,
                   allow_beta: bool = False,
                   all_names: bool = False) -> List[str]:
    """Return a list of valid (non-beta) services.

    :param cfg: UAConfig instance
    :param allow_beta: if we should allow beta services to be marked as valid
    :param all_names: if we should return all the names for a service instead
        of just the presentation_name
    """
    allow_beta_cfg = is_config_value_true(cfg.cfg, "features.allow_beta")
    allow_beta |= allow_beta_cfg

    entitlements = ENTITLEMENT_CLASSES
    if not allow_beta:
        entitlements = [
            entitlement for entitlement in entitlements
            if not entitlement.is_beta
        ]

    if all_names:
        names = []
        for entitlement in entitlements:
            names.extend(entitlement(cfg=cfg).valid_names)

        return sorted(names)

    return sorted([
        entitlement(cfg=cfg).presentation_name for entitlement in entitlements
    ])
def _handle_beta_resources(cfg, show_beta, response) -> Dict[str, Any]:
    """Remove beta services from response dict if needed"""
    config_allow_beta = util.is_config_value_true(
        config=cfg.cfg, path_to_value="features.allow_beta"
    )
    show_beta |= config_allow_beta
    if show_beta:
        return response

    new_response = copy.deepcopy(response)

    released_resources = []
    for resource in new_response.get("services", {}):
        resource_name = resource["name"]
        try:
            ent_cls = entitlement_factory(cfg=cfg, name=resource_name)
        except exceptions.EntitlementNotFoundError:
            """
            Here we cannot know the status of a service,
            since it is not listed as a valid entitlement.
            Therefore, we keep this service in the list, since
            we cannot validate if it is a beta service or not.
            """
            released_resources.append(resource)
            continue

        enabled_status = UserFacingStatus.ACTIVE.value
        if not ent_cls.is_beta or resource.get("status", "") == enabled_status:
            released_resources.append(resource)

    if released_resources:
        new_response["services"] = released_resources

    return new_response
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]
    config_allow_beta = util.is_config_value_true(
        config=cfg.cfg, path_to_value="features.allow_beta")
    allow_beta |= config_allow_beta
    if not allow_beta and ent_cls.is_beta:
        tmpl = ua_status.MESSAGE_INVALID_SERVICE_OP_FAILURE_TMPL
        raise exceptions.BetaServiceError(
            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
Beispiel #8
0
    def valid_service(self):
        """Check if the service is marked as valid (non-beta)"""
        if self._valid_service is None:
            self._valid_service = (
                not self.is_beta
                or self.allow_beta
                or is_config_value_true(self.cfg.cfg, "features.allow_beta")
            )

        return self._valid_service
def action_auto_attach(args, cfg):
    disable_auto_attach = util.is_config_value_true(
        config=cfg.cfg, path_to_value="features.disable_auto_attach")
    if disable_auto_attach:
        msg = "Skipping auto-attach. Config disable_auto_attach is set."
        logging.debug(msg)
        print(msg)
        return 0
    token = _get_contract_token_from_cloud_identity(cfg)
    return _attach_with_token(cfg, token=token, allow_enable=True)
Beispiel #10
0
    def handle_incompatible_services(
        self,
    ) -> Tuple[bool, Optional[messages.NamedMessage]]:
        """
        Prompt user when incompatible services are found during enable.

        When enabling a service, we may find that there is an incompatible
        service already enable. In that situation, we can ask the user
        if the incompatible service should be disabled before proceeding.
        There are also different ways to configure that behavior:

        We can disable removing incompatible service during enable by
        adding the following lines into uaclient.conf:

        features:
          block_disable_on_enable: true
        """
        cfg_block_disable_on_enable = util.is_config_value_true(
            config=self.cfg.cfg,
            path_to_value="features.block_disable_on_enable",
        )
        for service in self.blocking_incompatible_services():
            ent = service.entitlement(self.cfg, assume_yes=True)

            user_msg = messages.INCOMPATIBLE_SERVICE.format(
                service_being_enabled=self.title,
                incompatible_service=ent.title,
            )

            e_msg = messages.INCOMPATIBLE_SERVICE_STOPS_ENABLE.format(
                service_being_enabled=self.title,
                incompatible_service=ent.title,
            )

            if cfg_block_disable_on_enable:
                return False, e_msg

            if not util.prompt_for_confirmation(
                msg=user_msg, assume_yes=self.assume_yes
            ):
                return False, e_msg

            disable_msg = "Disabling incompatible service: {}".format(
                ent.title
            )
            event.info(disable_msg)

            ret = ent.disable(silent=True)
            if not ret:
                return ret, None

        return True, None
    def _replace_metapackage_on_cloud_instance(
        self, packages: List[str]
    ) -> List[str]:
        """
        Identify correct metapackage to be used if in a cloud instance.

        Currently, the contract backend is not delivering the right
        metapackage on a Bionic Azure or AWS cloud instance. For those
        clouds, we have cloud specific fips metapackages and we should
        use them. We are now performing that correction here, but this
        is a temporary fix.
        """
        cfg_disable_fips_metapackage_override = util.is_config_value_true(
            config=self.cfg.cfg,
            path_to_value="features.disable_fips_metapackage_override",
        )

        if cfg_disable_fips_metapackage_override:
            return packages

        series = util.get_platform_info().get("series")
        if series not in ("bionic", "focal"):
            return packages

        cloud_id, _ = get_cloud_type()
        if cloud_id is None:
            cloud_id = ""

        cloud_match = re.match(r"^(?P<cloud>(azure|aws|gce)).*", cloud_id)
        cloud_id = cloud_match.group("cloud") if cloud_match else ""

        if cloud_id not in ("azure", "aws", "gce"):
            return packages

        cloud_id = "gcp" if cloud_id == "gce" else cloud_id
        cloud_metapkg = "ubuntu-{}-fips".format(cloud_id)
        # Replace only the ubuntu-fips meta package if exists
        return [
            cloud_metapkg if pkg == "ubuntu-fips" else pkg for pkg in packages
        ]
Beispiel #12
0
def poll_for_pro_license(cfg: UAConfig):
    if util.is_config_value_true(config=cfg.cfg,
                                 path_to_value="features.disable_auto_attach"):
        LOG.debug("Configured to not auto attach, shutting down")
        return
    if cfg.is_attached:
        LOG.debug("Already attached, shutting down")
        return
    if not util.is_current_series_lts():
        LOG.debug("Not on LTS, shutting down")
        return

    try:
        cloud = cloud_instance_factory()
    except exceptions.CloudFactoryError:
        LOG.debug("Not on cloud, shutting down")
        return

    if not isinstance(cloud, UAAutoAttachGCPInstance):
        LOG.debug("Not on gcp, shutting down")
        return

    if not cloud.should_poll_for_pro_license():
        LOG.debug("Not on supported instance, shutting down")
        return

    try:
        pro_license_present = cloud.is_pro_license_present(
            wait_for_change=False)
    except exceptions.CancelProLicensePolling:
        LOG.debug("Cancelling polling")
        return
    except exceptions.DelayProLicensePolling:
        # Continue to polling loop anyway and handle error there if it occurs
        # again
        pass
    else:
        if pro_license_present:
            attempt_auto_attach(cfg, cloud)
            return

    if not cfg.poll_for_pro_license:
        LOG.debug("Configured to not poll for pro license, shutting down")
        return

    while True:
        try:
            start = time.time()
            pro_license_present = cloud.is_pro_license_present(
                wait_for_change=True)
            end = time.time()
        except exceptions.CancelProLicensePolling:
            LOG.debug("Cancelling polling")
            return
        except exceptions.DelayProLicensePolling:
            time.sleep(cfg.polling_error_retry_delay)
            continue
        else:
            if cfg.is_attached:
                # This could have changed during the long poll or sleep
                LOG.debug("Already attached, shutting down")
                return
            if pro_license_present:
                attempt_auto_attach(cfg, cloud)
                return
            if end - start < 10:
                LOG.debug(
                    "wait_for_change returned quickly and no pro license"
                    " present. Waiting {} seconds before polling again".format(
                        cfg.polling_error_retry_delay))
                time.sleep(cfg.polling_error_retry_delay)
                continue
def write_apt_and_motd_templates(cfg: config.UAConfig, series: str) -> None:
    """Write messaging templates about available esm packages.

    :param cfg: UAConfig instance for this environment.
    :param series: string of Ubuntu release series: 'xenial'.
    """
    apps_no_pkg_file = ExternalMessage.APT_PRE_INVOKE_APPS_NO_PKGS.value
    apps_pkg_file = ExternalMessage.APT_PRE_INVOKE_APPS_PKGS.value
    infra_no_pkg_file = ExternalMessage.APT_PRE_INVOKE_INFRA_NO_PKGS.value
    infra_pkg_file = ExternalMessage.APT_PRE_INVOKE_INFRA_PKGS.value
    motd_apps_no_pkg_file = ExternalMessage.MOTD_APPS_NO_PKGS.value
    motd_apps_pkg_file = ExternalMessage.MOTD_APPS_PKGS.value
    motd_infra_no_pkg_file = ExternalMessage.MOTD_INFRA_NO_PKGS.value
    motd_infra_pkg_file = ExternalMessage.MOTD_INFRA_PKGS.value
    no_warranty_file = ExternalMessage.UBUNTU_NO_WARRANTY.value
    msg_dir = os.path.join(cfg.data_dir, "messages")

    apps_cls = entitlements.entitlement_factory(cfg=cfg, name="esm-apps")
    apps_inst = apps_cls(cfg)
    config_allow_beta = util.is_config_value_true(
        config=cfg.cfg, path_to_value="features.allow_beta")
    apps_valid = bool(config_allow_beta or not apps_cls.is_beta)
    infra_cls = entitlements.entitlement_factory(cfg=cfg, name="esm-infra")
    infra_inst = infra_cls(cfg)

    expiry_status, remaining_days = get_contract_expiry_status(cfg)

    enabled_status = ApplicationStatus.ENABLED
    msg_esm_apps = False
    msg_esm_infra = False
    if util.is_active_esm(series):
        no_warranty_msg = ""
        if expiry_status in (
                ContractExpiryStatus.EXPIRED,
                ContractExpiryStatus.NONE,
        ):
            no_warranty_msg = UBUNTU_NO_WARRANTY
        if infra_inst.application_status()[0] != enabled_status:
            msg_esm_infra = True
            no_warranty_msg = UBUNTU_NO_WARRANTY
        elif remaining_days <= defaults.CONTRACT_EXPIRY_PENDING_DAYS:
            msg_esm_infra = True
        _write_template_or_remove(no_warranty_msg,
                                  os.path.join(msg_dir, no_warranty_file))
    if not msg_esm_infra:
        # write_apt_and_motd_templates is only called if util.is_lts(series)
        msg_esm_apps = apps_valid

    if msg_esm_infra:
        _write_esm_service_msg_templates(
            cfg,
            infra_inst,
            expiry_status,
            remaining_days,
            infra_pkg_file,
            infra_no_pkg_file,
            motd_infra_pkg_file,
            motd_infra_no_pkg_file,
        )
    else:
        _remove_msg_templates(
            msg_dir=os.path.join(cfg.data_dir, "messages"),
            msg_template_names=[
                infra_pkg_file,
                infra_no_pkg_file,
                motd_infra_pkg_file,
                motd_infra_no_pkg_file,
            ],
        )

    if msg_esm_apps:
        _write_esm_service_msg_templates(
            cfg,
            apps_inst,
            expiry_status,
            remaining_days,
            apps_pkg_file,
            apps_no_pkg_file,
            motd_apps_pkg_file,
            motd_apps_no_pkg_file,
        )
    else:
        _remove_msg_templates(
            msg_dir=os.path.join(cfg.data_dir, "messages"),
            msg_template_names=[
                apps_pkg_file,
                apps_no_pkg_file,
                motd_apps_pkg_file,
                motd_apps_no_pkg_file,
            ],
        )
Beispiel #14
0
 def test_is_config_value_true(self, config_dict, return_val, FakeConfig):
     cfg = FakeConfig()
     cfg.override_features(config_dict)
     actual_val = util.is_config_value_true(
         config=cfg.cfg, path_to_value="features.allow_beta")
     assert return_val == actual_val