Skip to content

Commit

Permalink
Removed folder path to fix SCMLinkValidationProbe (#351)
Browse files Browse the repository at this point in the history
Co-authored-by: Adrien Lecharpentier <[email protected]>
  • Loading branch information
Jagrutiti and alecharp authored Sep 6, 2023
1 parent a4d1c53 commit 423ce4b
Show file tree
Hide file tree
Showing 14 changed files with 295 additions and 59 deletions.
4 changes: 4 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,9 @@
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -66,15 +67,14 @@ public ProbeResult doApply(Plugin plugin, ProbeContext context) {
if (!matcher.find()) {
return ProbeResult.error(key(), "SCM link doesn't match GitHub plugin repositories");
}

final String folder = matcher.group("folder");
final Optional<String> folder = context.getScmFolderPath();
final Set<String> files = new HashSet<>();

final List<String> paths = new ArrayList<>(3);
paths.add("pom.xml");
if (folder != null) {
paths.add(folder + "/pom.xml");
paths.add(folder + "/src/main");

if (folder.isPresent()) {
paths.add(folder.get() + "/pom.xml");
paths.add(folder.get() + "/src/main");
} else {
paths.add("src/main");
}
Expand Down Expand Up @@ -123,6 +123,11 @@ public ProbeResult doApply(Plugin plugin, ProbeContext context) {
}
}

@Override
public String[] getProbeResultRequirement() {
return new String[]{SCMLinkValidationProbe.KEY, LastCommitDateProbe.KEY};
}

@Override
public String key() {
return KEY;
Expand All @@ -142,9 +147,4 @@ protected boolean isSourceCodeRelated() {
*/
return false;
}

@Override
public String[] getProbeResultRequirement() {
return new String[]{SCMLinkValidationProbe.KEY, LastCommitDateProbe.KEY};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package io.jenkins.pluginhealth.scoring.probes;

import java.time.ZonedDateTime;
import java.util.Optional;
import java.util.regex.Matcher;

import io.jenkins.pluginhealth.scoring.model.Plugin;
Expand All @@ -45,10 +46,9 @@
@Component
@Order(value = LastCommitDateProbe.ORDER)
public class LastCommitDateProbe extends Probe {
private static final Logger LOGGER = LoggerFactory.getLogger(LastCommitDateProbe.class);

public static final int ORDER = SCMLinkValidationProbe.ORDER + 100;
public static final String KEY = "last-commit-date";
private static final Logger LOGGER = LoggerFactory.getLogger(LastCommitDateProbe.class);

@Override
public ProbeResult doApply(Plugin plugin, ProbeContext context) {
Expand All @@ -57,12 +57,12 @@ public ProbeResult doApply(Plugin plugin, ProbeContext context) {
return ProbeResult.failure(key(), "The SCM link is not valid");
}
final String repo = String.format("https://%s/%s", matcher.group("server"), matcher.group("repo"));
final String folder = matcher.group("folder");
final Optional<String> folder = context.getScmFolderPath();

try (Git git = Git.cloneRepository().setURI(repo).setDirectory(context.getScmRepository().toFile()).call()) {
final LogCommand logCommand = git.log().setMaxCount(1);
if (folder != null) {
logCommand.addPath(folder);
if (folder.isPresent()) {
logCommand.addPath(folder.get().toString());
}
final RevCommit commit = logCommand.call().iterator().next();
if (commit == null) {
Expand All @@ -80,6 +80,11 @@ public ProbeResult doApply(Plugin plugin, ProbeContext context) {
}
}

@Override
public String[] getProbeResultRequirement() {
return new String[]{SCMLinkValidationProbe.KEY};
}

@Override
public String key() {
return KEY;
Expand All @@ -99,9 +104,4 @@ protected boolean isSourceCodeRelated() {
*/
return false;
}

@Override
public String[] getProbeResultRequirement() {
return new String[]{SCMLinkValidationProbe.KEY};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ private boolean shouldBeExecuted(Plugin plugin, ProbeContext context) {
/**
* List of probe key to be present in the {@link Plugin#details} map and to be {@link ResultStatus#SUCCESS} in
* order to consider executing the {@link Probe#doApply(Plugin, ProbeContext)} code.
* By default, the requirement is an empty array. I cannot be null.
* By default, the requirement is an empty array. It cannot be null.
*
* @return array of {@link Probe#key()} to be present in {@link Plugin#details}.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public class ProbeContext {
private GitHub github;
private ZonedDateTime lastCommitDate;
private Map<String, String> pluginDocumentationLinks;
private Optional<String> scmFolderPath;

public ProbeContext(String pluginName, UpdateCenter updateCenter) throws IOException {
this.updateCenter = updateCenter;
Expand Down Expand Up @@ -88,6 +89,14 @@ public Optional<String> getRepositoryName(String scm) {
return match.find() ? Optional.of(match.group("repo")) : Optional.empty();
}

public Optional<String> getScmFolderPath() {
return scmFolderPath;
}

public void setScmFolderPath(Optional<String> scmFolderPath) {
this.scmFolderPath = scmFolderPath;
}

/* default */ void cleanUp() throws IOException {
try (Stream<Path> paths = Files.walk(this.scmRepository)) {
paths.sorted(Comparator.reverseOrder())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,24 @@

package io.jenkins.pluginhealth.scoring.probes;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import io.jenkins.pluginhealth.scoring.model.Plugin;
import io.jenkins.pluginhealth.scoring.model.ProbeResult;

import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
Expand All @@ -42,20 +53,102 @@
@Component
@Order(value = SCMLinkValidationProbe.ORDER)
public class SCMLinkValidationProbe extends Probe {
private static final Logger LOGGER = LoggerFactory.getLogger(SCMLinkValidationProbe.class);

private static final String GH_REGEXP = "https://(?<server>[^/]*)/(?<repo>jenkinsci/[^/]*)(?:/(?<folder>.*))?";
public static final Pattern GH_PATTERN = Pattern.compile(GH_REGEXP);
public static final int ORDER = UpdateCenterPluginPublicationProbe.ORDER + 100;
public static final String KEY = "scm";
private static final Logger LOGGER = LoggerFactory.getLogger(SCMLinkValidationProbe.class);
private static final String GH_REGEXP = "https://(?<server>[^/]*)/(?<repo>jenkinsci/[^/]*)";
public static final Pattern GH_PATTERN = Pattern.compile(GH_REGEXP);

@Override
public ProbeResult doApply(Plugin plugin, ProbeContext context) {
if (plugin.getScm() == null || plugin.getScm().isBlank()) {
LOGGER.warn("{} has no SCM link", plugin.getName());
return ProbeResult.error(key(), "The plugin SCM link is empty");
return ProbeResult.error(key(), "The plugin SCM link is empty.");
}
return fromSCMLink(context, plugin.getScm(), plugin.getName());
}

/**
* Validates the SCM link, and sets {@link ProbeContext#setScmFolderPath(Optional)}. The value is always the path of the POM file.
*
* @param context Refer {@link ProbeContext}.
* @param scm The SCM link {@link Plugin#getScm()}.
* @param pluginName The name of the plugin {@link Plugin#getName()}.
* @return ProbeResult {@link ProbeResult}.
*/
private ProbeResult fromSCMLink(ProbeContext context, String scm, String pluginName) {
Matcher matcher = GH_PATTERN.matcher(scm);
if (!matcher.find()) {
LOGGER.atDebug().log(() -> String.format("%s is not respecting the SCM URL Template.", scm));
return ProbeResult.failure(key(), "SCM link doesn't match GitHub plugin repositories.");
}
try {
context.getGitHub().getRepository(matcher.group("repo")); // clones the repository, fetches the repo path using the regex Matcher
Optional<Path> pluginPathInRepository = findPluginPom(context.getScmRepository(), pluginName);
Optional<Path> folderPath = pluginPathInRepository.map(path -> path.getParent());
if (folderPath.isEmpty()) {
return ProbeResult.error(key(), String.format("No valid POM file found in %s plugin.", pluginName));
}
context.setScmFolderPath(folderPath.map(path -> path.getFileName().toString()));
return ProbeResult.success(key(), "The plugin SCM link is valid.");
} catch (IOException ex) {
return ProbeResult.failure(key(), "The plugin SCM link is invalid.");
}
}

/**
* Searches for Pom file in every directory available in the repository.
*
* @param directory path in the scm.
* @param pluginName the name of the plugin.
* @return an Optional path if pom file is found.
*/
private Optional<Path> findPluginPom(Path directory, String pluginName) {
if (!Files.isDirectory(directory)) {
LOGGER.error("Directory {} does not exists during {} probe.", directory, pluginName);
return Optional.empty();
}
/*
* The `maxDepth` is 3 because a lot of plugins aren't located deeper than <root>/plugins/<pom.xml>.
* If the `maxDepth` is more than 3, we will be navigating the `src/main/java/io/jenkins/plugins/artifactId/` folder.
* */
try (Stream<Path> paths = Files.find(directory, 3, (path, $) ->
"pom.xml".equals(path.getFileName().toString()))) {
return paths
.filter(pom -> pomFileMatchesPlugin(pom, pluginName))
.findFirst();
} catch (IOException e) {
LOGGER.error("Could not browse the folder during probe {}. {}", pluginName, e);
}
return Optional.empty();
}

/**
* Checks whether the plugin's pom.xml matches the `packaging` and the `artifactId` of the plugin.
* This helps in finding the correct pom that belongs to the plugin.
*
* @param pluginName The name of the plugin to match.
* @param pomFilePath The path of the pom file to be checked.
* @return a boolean value stating whether the file checked matches the criteria.
*/
private boolean pomFileMatchesPlugin(Path pomFilePath, String pluginName) {
MavenXpp3Reader mavenReader = new MavenXpp3Reader();
try (Reader reader = new InputStreamReader(new FileInputStream(pomFilePath.toFile()), StandardCharsets.UTF_8)) {
Model model = mavenReader.read(reader);
if ("hpi".equals(model.getPackaging()) && pluginName.equals(model.getArtifactId())) {
return true;
}
} catch (IOException e) {
LOGGER.error("Pom file not found for {}.", pluginName, e);
} catch (XmlPullParserException e) {
LOGGER.error("Could not parse pom file for {}.", pluginName, e);
}
return fromSCMLink(context, plugin.getScm());
return false;
}

@Override
public String[] getProbeResultRequirement() {
return new String[]{UpdateCenterPluginPublicationProbe.KEY};
}

@Override
Expand All @@ -75,26 +168,4 @@ public String getDescription() {
protected boolean requiresRelease() {
return true;
}

private ProbeResult fromSCMLink(ProbeContext context, String scm) {
Matcher matcher = GH_PATTERN.matcher(scm);
if (!matcher.find()) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("{} is not respecting the SCM URL Template", scm);
}
return ProbeResult.failure(key(), "SCM link doesn't match GitHub plugin repositories");
}

try {
context.getGitHub().getRepository(matcher.group("repo"));
return ProbeResult.success(key(), "The plugin SCM link is valid");
} catch (IOException ex) {
return ProbeResult.failure(key(), "The plugin SCM link is invalid");
}
}

@Override
public String[] getProbeResultRequirement() {
return new String[]{UpdateCenterPluginPublicationProbe.KEY};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.Map;
import java.util.Optional;

import io.jenkins.pluginhealth.scoring.model.Plugin;
import io.jenkins.pluginhealth.scoring.model.ProbeResult;
Expand Down Expand Up @@ -126,6 +127,8 @@ void shouldFailIfThereIsNotReleasedCommitsInModule() throws IOException, GitAPIE
LastCommitDateProbe.KEY, ProbeResult.success(LastCommitDateProbe.KEY, "")
));
when(ctx.getScmRepository()).thenReturn(repository);
when(ctx.getScmFolderPath()).thenReturn(Optional.of("test-folder"));

when(plugin.getScm()).thenReturn(scmLink);

final PersonIdent defaultCommitter = new PersonIdent(
Expand Down
Loading

0 comments on commit 423ce4b

Please sign in to comment.