Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ pypi: https://pypi.org/project/envoy.code_format.python_check

#### [envoy.dependency.check](envoy.dependency.check)

version: 0.0.1.dev0
version: 0.0.1

pypi: https://pypi.org/project/envoy.dependency.check

Expand Down
2 changes: 1 addition & 1 deletion envoy.dependency.check/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.1-dev
0.0.1
1 change: 1 addition & 0 deletions envoy.dependency.check/envoy/dependency/check/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pytooling_library(
"abstract/cves/cves.py",
"abstract/cves/version_matcher.py",
"abstract/dependency.py",
"abstract/issues.py",
"abstract/release.py",
"checker.py",
"cmd.py",
Expand Down
12 changes: 10 additions & 2 deletions envoy.dependency.check/envoy/dependency/check/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@
ADependencyCVE,
ADependencyCVEs,
ADependencyCVEVersionMatcher,
ADependencyGithubRelease)
ADependencyGithubRelease,
AGithubDependencyIssue,
AGithubDependencyIssues)
from .checker import (
Dependency,
DependencyChecker,
DependencyCPE,
DependencyCVE,
DependencyCVEs,
DependencyCVEVersionMatcher,
DependencyGithubRelease)
DependencyGithubRelease,
GithubDependencyIssue,
GithubDependencyIssues)
from .cmd import run, main
from . import checker

Expand All @@ -29,6 +33,8 @@
"ADependencyCVEs",
"ADependencyCVEVersionMatcher",
"ADependencyGithubRelease",
"AGithubDependencyIssue",
"AGithubDependencyIssues",
"checker",
"Dependency",
"DependencyChecker",
Expand All @@ -37,6 +43,8 @@
"DependencyCVEs",
"DependencyCVEVersionMatcher",
"DependencyGithubRelease",
"GithubDependencyIssue",
"GithubDependencyIssues",
"exceptions",
"main",
"run",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
ADependencyCVEs,
ADependencyCVEVersionMatcher)
from .dependency import ADependency
from .issues import AGithubDependencyIssue, AGithubDependencyIssues
from .release import ADependencyGithubRelease


Expand All @@ -16,4 +17,6 @@
"ADependencyCVE",
"ADependencyCVEs",
"ADependencyCVEVersionMatcher",
"AGithubDependencyIssue",
"AGithubDependencyIssues",
"ADependencyGithubRelease")
173 changes: 170 additions & 3 deletions envoy.dependency.check/envoy/dependency/check/abstract/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ADependencyChecker(
metaclass=abstracts.Abstraction):
"""Dependency checker."""

checks = ("cves", "release_dates", "releases")
checks = ("cves", "release_dates", "release_issues", "releases")

@property
@abc.abstractmethod
Expand Down Expand Up @@ -84,6 +84,7 @@ def disabled_checks(self):
disabled = {}
if not self.access_token:
disabled["release_dates"] = "No Github access token supplied"
disabled["release_issues"] = "No Github access token supplied"
disabled["releases"] = "No Github access token supplied"
return disabled

Expand Down Expand Up @@ -111,6 +112,17 @@ def github_dependencies(self) -> Tuple["abstract.ADependency", ...]:
deps.append(dep)
return tuple(deps)

@cached_property
def issues(self) -> "abstract.AGithubDependencyIssues":
"""Dependency issues."""
return self.issues_class(self.github)

@property # type:ignore
@abstracts.interfacemethod
def issues_class(self) -> Type["abstract.AGithubDependencyIssues"]:
"""Dependency issues class."""
raise NotImplementedError

@property
def repository_locations_path(self) -> pathlib.Path:
return pathlib.Path(self.args.repository_locations)
Expand All @@ -136,6 +148,14 @@ async def check_release_dates(self) -> None:
for dep in self.github_dependencies:
await self.dep_date_check(dep)

async def check_release_issues(self) -> None:
"""Check dependency issues."""
await self.release_issues_labels_check()
for dep in self.github_dependencies:
await self.dep_release_issue_check(dep)
await self.release_issues_missing_dep_check()
await self.release_issues_duplicate_check()

async def check_releases(self) -> None:
"""Check dependencies for new releases."""
for dep in self.github_dependencies:
Expand Down Expand Up @@ -175,6 +195,47 @@ async def dep_date_check(
"release_dates",
[f"Date matches ({dep.release_date}): {dep.id}"])

async def dep_release_issue_check(
self,
dep: "abstract.ADependency") -> None:
"""Check issues for dependency."""
issue = (await self.issues.dep_issues).get(dep.id)
newer_release = await dep.newer_release
if not newer_release:
if issue:
# There is an open issue, but the dep is already
# up-to-date.
self.warn(
"release_issues",
[f"Stale issue: {dep.id} #{issue.number}"])
if self.fix:
await self._dep_release_issue_close_stale(issue, dep)
else:
# No issue required
self.succeed(
"release_issues",
[f"No issue required: {dep.id}"])
return
if issue:
if issue.version == (await dep.newer_release).version:
# Required issue exists
self.succeed(
"release_issues",
[f"Issue exists (#{issue.number}): {dep.id}"])
return
# Existing issue is showing incorrect version
self.warn(
"release_issues",
[f"Out-of-date issue (#{issue.number}): {dep.id} "
f"({issue.version} -> {newer_release.version})"])
else:
# Issue is required to be added
self.warn(
"release_issues",
[f"Missing issue: {dep.id} ({newer_release.version})"])
if self.fix:
await self._dep_release_issue_create(issue, dep)

async def dep_release_check(
self,
dep: "abstract.ADependency") -> None:
Expand All @@ -200,6 +261,54 @@ async def dep_release_check(
"releases",
[f"Up-to-date ({dep.github_version_name}): {dep.id}"])

async def release_issues_duplicate_check(self) -> None:
"""Check for duplicate issues for dependencies."""
duplicates = False
async for issue in self.issues.duplicate_issues:
duplicates = True
self.warn(
"release_issues",
[f"Duplicate issue for dependency (#{issue.number}): "
f"{issue.dep}"])
if self.fix:
await self._release_issue_close_duplicate(issue)
if not duplicates:
self.succeed(
"release_issues",
["No duplicate issues found."])

async def release_issues_labels_check(self) -> None:
"""Check expected labels are present."""
missing = False
for label in await self.issues.missing_labels:
missing = True
# TODO: make this a warning if `fix` and fix it
self.error(
"release_issues",
[f"Missing label: {label}"])
if not missing:
self.succeed(
"release_issues",
[f"All ({len(self.issues.labels)}) "
"required labels are available."])

async def release_issues_missing_dep_check(self) -> None:
"""Check for missing dependencies for issues."""
closed = False
issues = await self.issues.open_issues
for issue in issues:
if issue.dep not in self.dep_ids:
closed = True
self.warn(
"release_issues",
[f"Missing dependency (#{issue.number}): {issue.dep}"])
if self.fix:
await self._release_issue_close_missing_dep(issue)
if not closed:
self.succeed(
"release_issues",
[f"All ({len(issues)}) issues have current dependencies."])

async def on_checks_complete(self) -> int:
await self.session.close()
return await super().on_checks_complete()
Expand All @@ -213,7 +322,7 @@ async def preload_cves(self) -> None:

@checker.preload(
when=["release_dates"],
unless=["releases"],
unless=["releases", "release_issues"],
catches=[ConcurrentError, gidgethub.GitHubException])
async def preload_release_dates(self) -> None:
preloader = inflate(
Expand All @@ -224,7 +333,15 @@ async def preload_release_dates(self) -> None:
self.log.debug(f"Preloaded release date: {dep.id}")

@checker.preload(
when=["releases"],
when=["release_issues"],
blocks=["release_dates"],
catches=[gidgethub.GitHubException])
async def preload_release_issues(self) -> None:
await self.issues.missing_labels
await self.issues.dep_issues

@checker.preload(
when=["releases", "release_issues"],
blocks=["release_dates"],
catches=[ConcurrentError, gidgethub.GitHubException])
async def preload_releases(self) -> None:
Expand All @@ -235,3 +352,53 @@ async def preload_releases(self) -> None:
d.recent_commits))
async for dep in preloader:
self.log.debug(f"Preloaded release data: {dep.id}")

async def _dep_release_issue_close_stale(
self,
issue: "abstract.AGithubDependencyIssue",
dep: "abstract.ADependency") -> None:
await issue.close()
self.log.notice(
f"Closed stale issue (#{issue.number}): {dep.id}\n"
f"{issue.title}\n{issue.body}")

async def _dep_release_issue_create(
self,
issue: "abstract.AGithubDependencyIssue",
dep: "abstract.ADependency") -> None:
if await self.issues.missing_labels:
self.error(
"release_issues",
[f"Unable to create issue for {dep.id}: missing labels"])
return
new_issue = await self.issues.create(dep)
self.log.notice(
f"Created issue (#{new_issue.number}): "
f"{dep.id} {new_issue.version}\n"
f"{new_issue.title}\n{new_issue.body}")
if not issue:
return
await new_issue.close_old(issue, dep)
self.log.notice(
f"Closed old issue (#{issue.number}): "
f"{dep.id} {issue.version}\n"
f"{issue.title}\n{issue.body}")

async def _release_issue_close_duplicate(
self,
issue: "abstract.AGithubDependencyIssue") -> None:
current_issue = (await self.issues.dep_issues)[issue.dep]
await current_issue.close_duplicate(issue)
self.log.notice(
f"Closed duplicate issue (#{issue.number}): {issue.dep}\n"
f" {issue.title}\n"
f"current issue #({current_issue.number}):\n"
f" {current_issue.title}")

async def _release_issue_close_missing_dep(
self,
issue: "abstract.AGithubDependencyIssue") -> None:
"""Close an issue that has no current dependency."""
await issue.close()
self.log.notice(
f"Closed issue with no current dependency (#{issue.number})")
Loading