-
Emanuele Aina authored
Rather than using plaintext error messages, use readable codes and structured metadata for errors and updates to make them easier to process. This will be particularly useful for filtering: for instance we preserve the branch information rather than muddling it in the error message. Signed-off-by:
Emanuele Aina <emanuele.aina@collabora.com>
Emanuele Aina authoredRather than using plaintext error messages, use readable codes and structured metadata for errors and updates to make them easier to process. This will be particularly useful for filtering: for instance we preserve the branch information rather than muddling it in the error message. Signed-off-by:
Emanuele Aina <emanuele.aina@collabora.com>
classes.py 8.54 KiB
import dataclasses
import enum
import typing
import debian.debian_support
import gitlab.v4.objects
# FIXME: handle renamed projects without having to hardcode them here
RENAMED = {
"dbus-cxx": "dbus-c++",
"gnome-settings-daemon-data": "gnome-settings-daemon",
"gtk-2.0": "gtk+2.0",
"gtk-3.0": "gtk+3.0",
"libsigcxx-2.0": "libsigc++-2.0",
"libxmlxx2.6": "libxml++2.6",
}
class Report(enum.Enum):
def _generate_next_value_(name, start, count, last_values):
return name.lower().replace("_", "-")
APT_PACKAGE_BINARIES_AMBIGUOUS = enum.auto()
APT_PACKAGE_BINARIES_MISSING = enum.auto()
APT_PACKAGE_MISSING = enum.auto()
APT_PACKAGE_MISSING_BUT_ON_OBS = enum.auto()
APT_PACKAGE_SOURCE_AMBIGUOUS = enum.auto()
APT_PACKAGE_SOURCE_MISSING = enum.auto()
APT_PACKAGE_SOURCE_VERSION_MISMATCH_BINARIES = enum.auto()
APT_PACKAGE_VERSION_MISMATCH_OBS = enum.auto()
GIT_BRANCH_COMPONENT_MISSING = enum.auto()
GIT_BRANCH_FOLDED_BUT_NOT_REMOVED = enum.auto()
GIT_BRANCH_HAS_AMBIGUOUS_TAGS = enum.auto()
GIT_BRANCH_LICENSING_REPORT_MISSING = enum.auto()
GIT_BRANCH_MISSING_BUT_ON_OBS = enum.auto()
GIT_BRANCH_NOT_POINTING_TO_TAGGED_COMMIT = enum.auto()
GIT_BRANCH_PIPELINE_FAILED = enum.auto()
GIT_CHANNEL_LAGGING = enum.auto()
GIT_PROJECT_MISSING = enum.auto()
GIT_UPSTREAM_BRANCH_DROPPED = enum.auto()
GIT_UPSTREAM_BRANCH_NOT_MERGED = enum.auto()
OBS_PACKAGE_AMBIGUOUS = enum.auto()
OBS_PACKAGE_BUILD_FAILED = enum.auto()
OBS_PACKAGE_MISSING_BUT_IN_GIT = enum.auto()
OBS_PACKAGE_MISSING_BUT_ON_APT = enum.auto()
OBS_PACKAGE_MISSING_BUT_PUBLISHED = enum.auto()
OBS_PACKAGE_VERSION_MISMATCH = enum.auto()
UPDATE_AVAILABLE = enum.auto()
UPDATE_AVAILABLE_MAINLINE = enum.auto()
@dataclasses.dataclass
class UpstreamPackage:
name: str
version: debian.debian_support.Version
source: str
component: str
@classmethod
def to_yaml(cls, dumper, data):
d = {
"name": data.name,
"version": data.version,
"source": data.source,
"component": data.component,
}
return dumper.represent_dict(d)
@dataclasses.dataclass
class UpstreamBinaryPackage:
name: str
version: debian.debian_support.Version
pkg_source: str
repo_source: str
component: str
architectures: typing.List[str]
@classmethod
def to_yaml(cls, dumper, data):
d = {
data.name: {
data.version: data.architectures,
},
}
return dumper.represent_dict(d)
def append_arch(self, arch):
if arch not in self.architectures:
self.architectures.append(arch)
@dataclasses.dataclass
class UpstreamSource:
destination: str
distribution: str
release: str
suite: str
base: str
url_template: str
components: typing.List[str]
@staticmethod
def load_source_definitions(definitions):
sources = {}
keys = set(UpstreamSource.__dataclass_fields__.keys()) - {"destination"}
for destination, definition in definitions.items():
subset = {k: definition[k] for k in keys}
sources[destination] = UpstreamSource(destination=destination, **subset)
return sources
@property
def url(self):
data = dataclasses.asdict(self)
return self.url_template.format(**data)
@property
def component_urls(self):
base_url = self.url
urls = {component: f"{base_url}/{component}" for component in self.components}
return urls
@classmethod
def to_yaml(cls, dumper, data):
d = {
"destination": data.destination,
"distribution": data.distribution,
"release": data.release,
"suite": data.suite,
"base": data.base,
"url_template": data.url_template,
"components": data.components,
}
return dumper.represent_dict(d)
@dataclasses.dataclass
class GitTag:
name: str
commit_id: str
descendant_branches: typing.List[str] = None
@classmethod
def to_yaml(cls, dumper, data):
d = dataclasses.asdict(data)
return dumper.represent_dict(d)
@staticmethod
def version(name):
v = name.split("/", 1)[1]
# see https://dep-team.pages.debian.net/deps/dep14/
v = v.replace("%", ":")
v = v.replace("_", "~")
v = v.replace("#", "")
return debian.debian_support.Version(v)
@dataclasses.dataclass
class GitBranch:
name: str
commit_id: str
component: str = ""
tags: typing.List[str] = None
descendant_branches: typing.List[str] = None
pipeline: typing.Dict[str, str] = None
license_report: bool = False
@classmethod
def to_yaml(cls, dumper, data):
d = {
"name": data.name,
"version": data.version,
"commit_id": data.commit_id,
"component": data.component,
"license_report": data.license_report,
"tags": data.tags,
"descendant_branches": data.descendant_branches,
}
if data.pipeline:
d["pipeline"] = data.pipeline
return dumper.represent_dict(d)
@property
def version(self):
versions = (GitTag.version(t) for t in self.tags if self.is_version_tag(t))
version = max(versions, default=None)
return version
def is_version_tag(self, tagname):
prefix = self.name.split("/", 1)[0] + "/"
return tagname.startswith(prefix)
@dataclasses.dataclass
class GitProject:
project: gitlab.v4.objects.Project
branches: typing.Dict[str, GitBranch] = None
tags: typing.Dict[str, GitTag] = None
@classmethod
def to_yaml(cls, dumper, data):
d = {
"id": data.project.id,
"web_url": data.project.web_url,
"path_with_namespace": data.path_with_namespace,
"path": data.path,
"branches": data.branches,
"tags": data.tags,
}
return dumper.represent_dict(d)
@property
def path_with_namespace(self):
path = self.project.path_with_namespace
return path
@property
def path(self):
path = self.project.path
return path
@property
def packagename(self):
name = self.path
pkgname = RENAMED.get(name, name)
return pkgname
@dataclasses.dataclass
class OBSEntry:
api_url: str
project: str
name: str
results: typing.Dict[str, typing.Dict[str, typing.Dict[str, str]]]
files: typing.List[str] = None
@classmethod
def to_yaml(cls, dumper, data):
d = {
"project": data.project,
"web_url": data.web_url,
"version_without_epoch": data.version_without_epoch,
"files": data.files,
}
if data.results:
d["results"] = data.results
return dumper.represent_dict(d)
@property
def id(self):
return f"{self.project}/{self.name}"
@property
def version_without_epoch(self):
suffix = ".dsc"
dsc = max((f for f in self.files if f.endswith(suffix)), default=None)
if not dsc:
return None
v = dsc[: -len(suffix)].split("_")[1] if dsc and "_" in dsc else None
return debian.debian_support.Version(v)
@property
def web_url(self):
return f"{self.api_url}/package/show/{self.project}/{self.name}"
@dataclasses.dataclass
class OBSProject:
api_url: str
name: str
@classmethod
def to_yaml(cls, dumper, data):
d = dict(
name=data.name,
release=data.release,
component=data.component,
section=data.section,
web_url=data.web_url,
)
return dumper.represent_dict(d)
@property
def release(self):
s = self.name.split(":")[:2]
return "/".join(s)
@property
def component(self):
# for instance, `target` in `apertis:v2020:security:target:snapshots`
s = self.name.split(":")
if s[-1] == "snapshots":
return s[-2]
return s[-1]
@property
def section(self):
# for instance, `security` in `apertis:v2020:security:target:snapshots`
s = self.name.split(":")
parts = len(s)
if s[-1] == "snapshots":
parts -= 1
if parts == 3:
# for instance `apertis:v2020:target`
return "main"
return s[2]
@property
def web_url(self):
return f"{self.api_url}/project/show/{self.name}"
def __repr__(self):
return self.name