Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevents bypassing probes when plugin has no SCM #530

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2023 Jenkins Infra
* Copyright (c) 2023-2024 Jenkins Infra
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand All @@ -21,13 +21,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package io.jenkins.pluginhealth.scoring.probes;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand All @@ -50,7 +48,7 @@ public class CodeCoverageProbe extends Probe {
private static final Logger LOGGER = LoggerFactory.getLogger(CodeCoverageProbe.class);

private static final String COVERAGE_TITLE_REGEXP =
"^Line(?: Coverage)?: (?<line>\\d{1,2}(?:\\.\\d{1,2})?)%(?: \\(.+\\))?. Branch(?: Coverage)?: (?<branch>\\d{1,2}(?:\\.\\d{1,2})?)%(?: \\(.+\\))?\\.?$";
"^Line(?: Coverage)?: (?<line>\\d{1,2}(?:\\.\\d{1,2})?)%(?: \\(.+\\))?. Branch(?: Coverage)?: (?<branch>\\d{1,2}(?:\\.\\d{1,2})?)%(?: \\(.+\\))?\\.?$";
private static final Pattern COVERAGE_TITLE_PATTERN = Pattern.compile(COVERAGE_TITLE_REGEXP);

public static final String KEY = "code-coverage";
Expand All @@ -59,40 +57,42 @@ public class CodeCoverageProbe extends Probe {
@Override
protected ProbeResult doApply(Plugin plugin, ProbeContext context) {
final io.jenkins.pluginhealth.scoring.model.updatecenter.Plugin ucPlugin =
context.getUpdateCenter().plugins().get(plugin.getName());
context.getUpdateCenter().plugins().get(plugin.getName());
if (ucPlugin == null) {
return error("Plugin cannot be found in Update-Center.");
}
final String defaultBranch = ucPlugin.defaultBranch();
if (defaultBranch == null || defaultBranch.isBlank()) {
return this.error("No default branch configured for the plugin.");
}
if (context.getRepositoryName().isEmpty()) {
return this.error("Cannot determine plugin repository.");
}
try {
final Optional<String> repositoryName = context.getRepositoryName();
if (repositoryName.isPresent()) {
final GHRepository ghRepository = context.getGitHub().getRepository(repositoryName.get());
final List<GHCheckRun> ghCheckRuns =
ghRepository.getCheckRuns(defaultBranch, Map.of("check_name", "Code Coverage")).toList();
if (ghCheckRuns.isEmpty()) {
return this.success("Could not determine code coverage for the plugin.");
}
final String repositoryName = context.getRepositoryName().get();
final GHRepository ghRepository = context.getGitHub().getRepository(repositoryName);
final List<GHCheckRun> ghCheckRuns = ghRepository
.getCheckRuns(defaultBranch, Map.of("check_name", "Code Coverage"))
.toList();
if (ghCheckRuns.isEmpty()) {
return this.success("Could not determine code coverage for the plugin.");
}

double overall_line_coverage = 100;
double overall_branch_coverage = 100;
for (GHCheckRun checkRun : ghCheckRuns) {
final Matcher matcher = COVERAGE_TITLE_PATTERN.matcher(checkRun.getOutput().getTitle());
if (matcher.matches()) {
final double line_coverage = Double.parseDouble(matcher.group("line"));
final double branch_coverage = Double.parseDouble(matcher.group("branch"));
overall_line_coverage = Math.min(overall_line_coverage, line_coverage);
overall_branch_coverage = Math.min(overall_branch_coverage, branch_coverage);
}
double overall_line_coverage = 100;
double overall_branch_coverage = 100;
for (GHCheckRun checkRun : ghCheckRuns) {
final Matcher matcher =
COVERAGE_TITLE_PATTERN.matcher(checkRun.getOutput().getTitle());
if (matcher.matches()) {
final double line_coverage = Double.parseDouble(matcher.group("line"));
final double branch_coverage = Double.parseDouble(matcher.group("branch"));
overall_line_coverage = Math.min(overall_line_coverage, line_coverage);
overall_branch_coverage = Math.min(overall_branch_coverage, branch_coverage);
}

return this.success("Line coverage: " + overall_line_coverage + "%. Branch coverage: " + overall_branch_coverage + "%.");
} else {
return this.error("Cannot determine plugin repository.");
}

return this.success("Line coverage: " + overall_line_coverage + "%. Branch coverage: "
+ overall_branch_coverage + "%.");
} catch (IOException e) {
LOGGER.warn("Could not get Coverage check for {}", plugin.getName(), e);
return this.error("Could not get coverage check");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2023 Jenkins Infra
* Copyright (c) 2023-2024 Jenkins Infra
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand All @@ -21,7 +21,6 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package io.jenkins.pluginhealth.scoring.probes;

import java.io.IOException;
Expand Down Expand Up @@ -50,14 +49,18 @@ public class DependabotPullRequestProbe extends Probe {

@Override
protected ProbeResult doApply(Plugin plugin, ProbeContext context) {
if (context.getRepositoryName().isEmpty()) {
return this.error("There is no repository for " + plugin.getName() + ".");
}
try {
final GitHub gh = context.getGitHub();
final GHRepository repository = gh.getRepository(context.getRepositoryName().orElseThrow());
final GHRepository repository =
gh.getRepository(context.getRepositoryName().get());
final List<GHPullRequest> pullRequests = repository.getPullRequests(GHIssueState.OPEN);

final long count = pullRequests.stream()
.filter(pr -> pr.getLabels().stream().anyMatch(label -> "dependencies".equals(label.getName())))
.count();
.filter(pr -> pr.getLabels().stream().anyMatch(label -> "dependencies".equals(label.getName())))
.count();

return this.success("%d".formatted(count));
} catch (NoSuchElementException | IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ protected ProbeResult doApply(Plugin plugin, ProbeContext context) {
final Matcher matcher = pattern.matcher(plugin.getVersion().toString());
return matcher.find();
}))
.map(warning -> warning.id() + "|" + warning.url())
.map(warning -> String.join("|", warning.id(), warning.url()))
.collect(Collectors.joining(", "));

return !issues.isBlank() ? this.success(issues) : this.success("No known security vulnerabilities.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,11 @@ public class PluginDescriptionMigrationProbe extends Probe {

@Override
protected ProbeResult doApply(Plugin plugin, ProbeContext context) {
final Optional<Path> scmRepositoryOpt = context.getScmRepository();
if (scmRepositoryOpt.isEmpty()) {
return error("Cannot access plugin repository.");
if (context.getScmRepository().isEmpty()) {
return this.error("There is no local repository for plugin " + plugin.getName() + ".");
}

final Path repository = scmRepositoryOpt.get();
final Path repository = context.getScmRepository().get();
final Path pluginFolder =
context.getScmFolderPath().map(repository::resolve).orElse(repository);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2023 Jenkins Infra
* Copyright (c) 2023-2024 Jenkins Infra
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand All @@ -21,7 +21,6 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package io.jenkins.pluginhealth.scoring.probes;

import java.nio.file.Path;
Expand Down Expand Up @@ -56,9 +55,7 @@ public final ProbeResult apply(Plugin plugin, ProbeContext context) {
return doApply(plugin, context);
}
final ProbeResult lastResult = plugin.getDetails().get(key());
return lastResult != null ?
lastResult :
this.error(key() + " was not executed on " + plugin.getName());
return lastResult != null ? lastResult : this.error(key() + " was not executed on " + plugin.getName());
}

private boolean shouldBeExecuted(Plugin plugin, ProbeContext context) {
Expand All @@ -75,26 +72,28 @@ private boolean shouldBeExecuted(Plugin plugin, ProbeContext context) {
if (!this.requiresRelease() && !this.isSourceCodeRelated()) {
return true;
}
if (this.requiresRelease() &&
(previousResult.timestamp() != null && previousResult.timestamp().isBefore(plugin.getReleaseTimestamp()))) {
if (this.requiresRelease()
&& (previousResult.timestamp() != null
&& previousResult.timestamp().isBefore(plugin.getReleaseTimestamp()))) {
return true;
}
final Optional<Path> optionalScmRepository = context.getScmRepository();
if (this.isSourceCodeRelated() && optionalScmRepository.isEmpty()) {
LOGGER.info(
"{} requires the SCM for {} but the SCM was not cloned locally",
this.key(), plugin.getName()
);
LOGGER.info("{} requires the SCM for {} but the SCM was not cloned locally", this.key(), plugin.getName());
return false;
}
final Optional<ZonedDateTime> optionalLastCommit = context.getLastCommitDate();
if (this.isSourceCodeRelated() &&
optionalLastCommit
.map(date -> previousResult.timestamp() != null && previousResult.timestamp().isBefore(date))
.orElseGet(() -> {
LOGGER.info("{} is based on code modification but last commit for {} is unknown. It will be executed.", key(), plugin.getName());
return true;
})) {
if (this.isSourceCodeRelated()
&& optionalLastCommit
.map(date -> previousResult.timestamp() != null
&& previousResult.timestamp().isBefore(date))
.orElseGet(() -> {
LOGGER.info(
"{} is based on code modification but last commit for {} is unknown. It will be executed.",
key(),
plugin.getName());
return true;
})) {
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public ScoringComponentResult getScore(Plugin $, Map<String, ProbeResult> probeR
100, getWeight(), List.of("Plugin does not seem to have on-going security advisory."));
}
final List<Resolution> resolutions = Arrays.stream(
probeResult.message().split(","))
probeResult.message().split(", "))
.map(m -> {
final String[] parts = m.trim().split("\\|");
return new Resolution(parts[0], parts[1]);
Expand Down
Loading
Loading