Example #1
0
    def get_report(self) -> str:
        """Get verbose report.

        :return: Report why the part is dirty.
        :rtype: str
        """
        messages = []  # type: List[str]

        if self.dirty_properties:
            humanized_properties = formatting_utils.humanize_list(
                self.dirty_properties, "and")
            pluralized_connection = formatting_utils.pluralize(
                self.dirty_properties, "property appears", "properties appear")
            messages.append("The {} part {} to have changed.\n".format(
                humanized_properties, pluralized_connection))

        if self.dirty_project_options:
            humanized_options = formatting_utils.humanize_list(
                self.dirty_project_options, "and")
            pluralized_connection = formatting_utils.pluralize(
                self.dirty_project_options, "option appears", "options appear")
            messages.append("The {} project {} to have changed.\n".format(
                humanized_options, pluralized_connection))

        if self.changed_dependencies:
            dependencies = [d.part_name for d in self.changed_dependencies]
            messages.append("{} changed: {}\n".format(
                formatting_utils.pluralize(dependencies, "A dependency has",
                                           "Some dependencies have"),
                formatting_utils.humanize_list(dependencies, "and"),
            ))

        return "".join(messages)
Example #2
0
def _handle_macaroon_permission_required(response_json):
    if "permission" in response_json and "channels" in response_json:
        if response_json["permission"] == "channel":
            channels = response_json["channels"]
            return "Lacking permission to release to channel(s) {}".format(
                formatting_utils.humanize_list(channels, "and"))

    return ""
Example #3
0
 def __init__(self, step: steps.Step, other_step: steps.Step,
              keys: List[str]) -> None:
     self.keys = keys
     super().__init__(
         step=step,
         other_step=other_step,
         humanized_keys=formatting_utils.humanize_list(keys, "and"),
     )
 def test_two_items(self):
     items = ["foo", "bar"]
     output = formatting_utils.humanize_list(items, "and")
     self.assertThat(
         output,
         Equals("'bar' and 'foo'"),
         "Expected 'bar' before 'foo' due to sorting",
     )
Example #5
0
 def __init__(self, *, base: str, version: str,
              valid_versions: Sequence[str]) -> None:
     super().__init__(
         base=base,
         version=version,
         valid_versions=formatting_utils.humanize_list(valid_versions,
                                                       conjunction="or"),
     )
Example #6
0
def _monitor_build(lp: LaunchpadClient) -> None:
    target_list = humanize_list(lp.architectures, "and", "{}")
    echo.info(
        f"Building snap package for {target_list}. This may take some time to finish."
    )

    lp.monitor_build()

    echo.info("Build complete.")
    lp.cleanup()
Example #7
0
 def __init__(
     self,
     *,
     snap_name: str,
     channel: channels.Channel,
     channel_outliers: List[status.SnapStatusChannelDetails],
 ) -> None:
     arches = formatting_utils.humanize_list(
         [c.arch for c in channel_outliers], "and")
     super().__init__(snap_name=snap_name, channel=channel, arches=arches)
Example #8
0
def _determine_cause(error):
    messages = []

    # error.validator_value may contain a custom validation error message.
    # If so, use it instead of the garbage message jsonschema gives us.
    with contextlib.suppress(TypeError, KeyError):
        messages.append(
            error.validator_value["validation-failure"].format(error))

    # The schema itself may have a custom validation error message. If so,
    # use it as well.
    with contextlib.suppress(AttributeError, TypeError, KeyError):
        key = error
        if (error.schema.get("type") == "object"
                and error.validator == "additionalProperties"):
            key = list(error.instance.keys())[0]

        messages.append(error.schema["validation-failure"].format(key))

    # anyOf failures might have usable context... try to improve them a bit
    if error.validator == "anyOf":
        contextual_messages: Dict[str, str] = OrderedDict()
        for contextual_error in error.context:
            key = contextual_error.schema_path.popleft()
            if key not in contextual_messages:
                contextual_messages[key] = []
            message = contextual_error.message
            if message:
                # Sure it starts lower-case (not all messages do)
                contextual_messages[key].append(message[0].lower() +
                                                message[1:])

        oneOf_messages: List[str] = []
        for key, value in contextual_messages.items():
            oneOf_messages.append(
                formatting_utils.humanize_list(value, "and", "{}"))

        messages.append(
            formatting_utils.humanize_list(oneOf_messages, "or", "{}"))

    return " ".join(messages)
Example #9
0
    def get_summary(self) -> str:
        """Get summarized report.

        :return: Short summary of why the step is dirty.
        :rtype: str
        """
        reasons = []

        if self.previous_step_modified:
            reasons.append("{!r} step".format(self.previous_step_modified.name))

        if self.source_updated:
            reasons.append("source")

        return "{} changed".format(formatting_utils.humanize_list(reasons, "and", "{}"))
Example #10
0
def _interpret_anyOf(error):
    """Interpret a validation error caused by the anyOf validator.

    Returns:
        A string containing a (hopefully) helpful validation error message. It
        may be empty.
    """

    usages = []
    try:
        for validator in error.validator_value:
            usages.append(validator["usage"])
    except (TypeError, KeyError):
        return ""

    return "must be one of {}".format(
        formatting_utils.humanize_list(usages, "or", "{}"))
Example #11
0
def _adopt_info(
    config_data: Dict[str, Any],
    extracted_metadata: _metadata.ExtractedMetadata,
    prime_dir: str,
):
    ignored_keys = _adopt_keys(config_data, extracted_metadata, prime_dir)
    if ignored_keys:
        logger.warning(
            "The {keys} {plural_property} {plural_is} specified in adopted "
            "info as well as the YAML: taking the {plural_property} from the "
            "YAML".format(
                keys=formatting_utils.humanize_list(list(ignored_keys), "and"),
                plural_property=formatting_utils.pluralize(
                    ignored_keys, "property", "properties"),
                plural_is=formatting_utils.pluralize(ignored_keys, "is",
                                                     "are"),
            ))
Example #12
0
    def warn_ld_library_paths(self) -> None:
        root_ld_library_path = self._snap_meta.environment.get(
            "LD_LIBRARY_PATH")
        # Dictionary of app names with LD_LIBRARY_PATH in their environment.
        app_environment: Dict[str, str] = dict()

        for app_name, app_props in self._config_data.get("apps",
                                                         dict()).items():
            with contextlib.suppress(KeyError):
                app_environment[app_name] = app_props["environment"][
                    "LD_LIBRARY_PATH"]

        if root_ld_library_path is None and not app_environment:
            return

        ld_library_path_empty: Set[str] = set()
        if root_ld_library_path is None and app_environment:
            ld_library_path_empty = {
                name
                for name, ld_env in app_environment.items() if
                "$LD_LIBRARY_PATH" in ld_env or "${LD_LIBRARY_PATH}" in ld_env
            }
        elif (root_ld_library_path is not None
              and "LD_LIBRARY_PATH" in root_ld_library_path):
            ld_library_path_empty = {"."}

        _EMPTY_LD_LIBRARY_PATH_ITEM_PATTERN = re.compile("^:|::|:$")

        for name, ld_env in app_environment.items():
            if _EMPTY_LD_LIBRARY_PATH_ITEM_PATTERN.findall(ld_env):
                ld_library_path_empty.add(name)

        if (root_ld_library_path is not None
                and _EMPTY_LD_LIBRARY_PATH_ITEM_PATTERN.findall(
                    root_ld_library_path)):
            ld_library_path_empty.add(".")

        if ld_library_path_empty:
            logger.warning(
                "CVE-2020-27348: A potentially empty LD_LIBRARY_PATH has been set for environment "
                "in {}. "
                "The current working directory will be added to the library path if empty. "
                "This can cause unexpected libraries to be loaded.".format(
                    formatting_utils.humanize_list(
                        sorted(ld_library_path_empty), "and")))
Example #13
0
    def get_summary(self) -> str:
        """Get summarized report.

        :return: Short summary of why the part is dirty.
        :rtype: str
        """
        reasons = []

        reasons_count = 0
        if self.dirty_properties:
            reasons_count += 1
        if self.dirty_project_options:
            reasons_count += 1
        if self.changed_dependencies:
            reasons_count += 1

        if self.dirty_properties:
            # Be specific only if this is the only reason
            if reasons_count > 1 or len(self.dirty_properties) > 1:
                reasons.append("properties")
            else:
                reasons.append("{!r} property".format(
                    next(iter(self.dirty_properties))))

        if self.dirty_project_options:
            # Be specific only if this is the only reason
            if reasons_count > 1 or len(self.dirty_project_options) > 1:
                reasons.append("options")
            else:
                reasons.append("{!r} option".format(
                    next(iter(self.dirty_project_options))))

        if self.changed_dependencies:
            # Be specific only if this is the only reason
            if reasons_count > 1 or len(self.changed_dependencies) > 1:
                reasons.append("dependencies")
            else:
                reasons.append("{!r}".format(
                    next(iter(self.changed_dependencies)).part_name))

        return "{} changed".format(
            formatting_utils.humanize_list(reasons, "and", "{}"))
Example #14
0
def _clean_parts(part_names, step, config, staged_state, primed_state):
    if not step:
        step = steps.next_step(None)

    for part_name in part_names:
        dirty_parts = _clean_part(part_name, step, config, staged_state, primed_state)
        dirty_part_names = {p.name for p in dirty_parts}

        parts_not_being_cleaned = dirty_part_names.difference(part_names)
        if parts_not_being_cleaned:
            logger.warning(
                "Cleaned {!r}, which makes the following {} out of date: "
                "{}".format(
                    part_name,
                    formatting_utils.pluralize(
                        parts_not_being_cleaned, "part", "parts"
                    ),
                    formatting_utils.humanize_list(parts_not_being_cleaned, "and"),
                )
            )
 def test_another_conjunction(self):
     items = ["foo", "bar", "baz", "qux"]
     output = formatting_utils.humanize_list(items, "or")
     self.assertThat(output, Equals("'bar', 'baz', 'foo', or 'qux'"))
 def test_four_items(self):
     items = ["foo", "bar", "baz", "qux"]
     output = formatting_utils.humanize_list(items, "and")
     self.assertThat(output, Equals("'bar', 'baz', 'foo', and 'qux'"))
Example #17
0
 def __init__(self, *, architecture: str, supported: List[str]) -> None:
     super().__init__(
         architecture=architecture,
         supported=formatting_utils.humanize_list(supported, "and"),
     )
 def test_one_item(self):
     items = ["foo"]
     output = formatting_utils.humanize_list(items, "and")
     self.assertThat(output, Equals("'foo'"))
 def test_no_items(self):
     items = []
     output = formatting_utils.humanize_list(items, "and")
     self.assertThat(output, Equals(""))
Example #20
0
 def __init__(self, keys: List[str]) -> None:
     super().__init__(keys=formatting_utils.humanize_list(keys, "and"))
Example #21
0
def _validate_architectures(instance):
    standalone_build_ons = collections.Counter()
    build_ons = collections.Counter()
    run_ons = collections.Counter()

    saw_strings = False
    saw_dicts = False

    for item in instance:
        # This could either be a dict or a string. In the latter case, the
        # schema will take care of it. We just need to further validate the
        # dict.
        if isinstance(item, str):
            saw_strings = True
        elif isinstance(item, dict):
            saw_dicts = True
            build_on = _get_architectures_set(item, "build-on")
            build_ons.update(build_on)

            # Add to the list of run-ons. However, if no run-on is specified,
            # we know it's implicitly the value of build-on, so use that
            # for validation instead.
            run_on = _get_architectures_set(item, "run-on")
            if run_on:
                run_ons.update(run_on)
            else:
                standalone_build_ons.update(build_on)

    # Architectures can either be a list of strings, or a list of objects.
    # Mixing the two forms is unsupported.
    if saw_strings and saw_dicts:
        raise jsonschema.exceptions.ValidationError(
            "every item must either be a string or an object",
            path=["architectures"],
            instance=instance,
        )

    # At this point, individual build-ons and run-ons have been validated,
    # we just need to validate them across each other.

    # First of all, if we have a `run-on: [all]` (or a standalone
    # `build-on: [all]`) then we should only have one item in the instance,
    # otherwise we know we'll have multiple snaps claiming they run on the same
    # architectures (i.e. all and something else).
    number_of_snaps = len(instance)
    if "all" in run_ons and number_of_snaps > 1:
        raise jsonschema.exceptions.ValidationError(
            "one of the items has 'all' in 'run-on', but there are {} "
            "items: upon release they will conflict. 'all' should only be "
            "used if there is a single item".format(number_of_snaps),
            path=["architectures"],
            instance=instance,
        )
    if "all" in build_ons and number_of_snaps > 1:
        raise jsonschema.exceptions.ValidationError(
            "one of the items has 'all' in 'build-on', but there are {} "
            "items: snapcraft doesn't know which one to use. 'all' should "
            "only be used if there is a single item".format(number_of_snaps),
            path=["architectures"],
            instance=instance,
        )

    # We want to ensure that multiple `run-on`s (or standalone `build-on`s)
    # don't include the same arch, or they'll clash with each other when
    # releasing.
    all_run_ons = run_ons + standalone_build_ons
    duplicates = {arch for (arch, count) in all_run_ons.items() if count > 1}
    if duplicates:
        raise jsonschema.exceptions.ValidationError(
            "multiple items will build snaps that claim to run on {}".format(
                formatting_utils.humanize_list(duplicates, "and")),
            path=["architectures"],
            instance=instance,
        )

    # Finally, ensure that multiple `build-on`s don't include the same arch
    # or Snapcraft has no way of knowing which one to use.
    duplicates = {arch for (arch, count) in build_ons.items() if count > 1}
    if duplicates:
        raise jsonschema.exceptions.ValidationError(
            "{} {} present in the 'build-on' of multiple items, which means "
            "snapcraft doesn't know which 'run-on' to use when building on "
            "{} {}".format(
                formatting_utils.humanize_list(duplicates, "and"),
                formatting_utils.pluralize(duplicates, "is", "are"),
                formatting_utils.pluralize(duplicates, "that", "those"),
                formatting_utils.pluralize(duplicates, "architecture",
                                           "architectures"),
            ),
            path=["architectures"],
            instance=instance,
        )

    return True
Example #22
0
 def __init__(self, source_type: str, options: List[str]) -> None:
     self.options = options
     super().__init__(
         source_type=source_type,
         humanized_options=formatting_utils.humanize_list(options, "and"),
     )
Example #23
0
 def get_brief(self) -> str:
     missing_pkgs = formatting_utils.humanize_list(self.packages, "and")
     return f"Failed to find installation candidate for packages: {missing_pkgs}"