diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/BuildSystemVerifier.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/BuildSystemVerifier.java deleted file mode 100644 index ade1753889..0000000000 --- a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/BuildSystemVerifier.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.jenkins.infra.repository_permissions_updater.hosting; - -import java.io.IOException; - -public interface BuildSystemVerifier extends Verifier { - boolean hasBuildFile(HostingRequest issue) throws IOException; -} diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/ConditionChecker.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/ConditionChecker.java deleted file mode 100644 index 78b09eec81..0000000000 --- a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/ConditionChecker.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.jenkins.infra.repository_permissions_updater.hosting; - -import java.io.IOException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.apache.commons.lang3.StringUtils; -import org.kohsuke.github.GHContent; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GitHub; - -public abstract class ConditionChecker { - public abstract boolean checkCondition(HostingRequest issue) throws IOException; - - protected boolean fileExistsInForkFrom(HostingRequest issue, String fileName) throws IOException { - boolean res = false; - GitHub github = GitHub.connect(); - String forkFrom = issue.getRepositoryUrl(); - - if (StringUtils.isNotBlank(forkFrom)) { - Matcher m = Pattern.compile("(?:https://github\\.com/)?(\\S+)/(\\S+)", Pattern.CASE_INSENSITIVE).matcher(forkFrom); - if (m.matches()) { - String owner = m.group(1); - String repoName = m.group(2); - - GHRepository repo = github.getRepository(owner + "/" + repoName); - try { - GHContent file = repo.getFileContent(fileName); - res = file != null && file.isFile(); - } catch (IOException ignored) { - } - } - } - return res; - } -} diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/FileExistsConditionChecker.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/FileExistsConditionChecker.java deleted file mode 100644 index 3bf540a05a..0000000000 --- a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/FileExistsConditionChecker.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.jenkins.infra.repository_permissions_updater.hosting; - -import java.io.IOException; - -public class FileExistsConditionChecker extends ConditionChecker { - private final String fileName; - - public FileExistsConditionChecker(String fileName) { - this.fileName = fileName; - } - - @Override - public boolean checkCondition(HostingRequest issue) throws IOException { - return fileExistsInForkFrom(issue, fileName); - } -} diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/GradleVerifier.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/GradleVerifier.java deleted file mode 100644 index 82e026aa70..0000000000 --- a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/GradleVerifier.java +++ /dev/null @@ -1,393 +0,0 @@ -package io.jenkins.infra.repository_permissions_updater.hosting; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.Charset; -import java.util.HashSet; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.codehaus.groovy.ast.ASTNode; -import org.codehaus.groovy.ast.CodeVisitorSupport; -import org.codehaus.groovy.ast.builder.AstBuilder; -import org.codehaus.groovy.ast.expr.ArgumentListExpression; -import org.codehaus.groovy.ast.expr.BinaryExpression; -import org.codehaus.groovy.ast.expr.ClosureExpression; -import org.codehaus.groovy.ast.expr.ConstantExpression; -import org.codehaus.groovy.ast.expr.Expression; -import org.codehaus.groovy.ast.expr.MethodCallExpression; -import org.codehaus.groovy.ast.expr.VariableExpression; -import org.codehaus.groovy.ast.stmt.BlockStatement; -import org.codehaus.groovy.ast.stmt.ExpressionStatement; -import org.codehaus.groovy.ast.stmt.Statement; -import org.codehaus.groovy.control.CompilationFailedException; -import org.codehaus.groovy.control.CompilePhase; -import org.kohsuke.github.GHContent; -import org.kohsuke.github.GHFileNotFoundException; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GitHub; - -import static io.jenkins.infra.repository_permissions_updater.hosting.HostingChecker.LOWEST_JENKINS_VERSION; -import static java.util.regex.Pattern.CASE_INSENSITIVE; - -/** - * @deprecated until the Gradle JPI plugin supports the same scope of features as the Maven HPI plugin. - */ -@Deprecated -public class GradleVerifier extends CodeVisitorSupport implements BuildSystemVerifier { - - public static final String SPECIFY_LICENSE = "Please specify a license in your build.gradle file using the `licenses` closure. See https://github.com/jenkinsci/gradle-jpi-plugin#configuration for an example."; - public static final String MISSING_JENKINS_VERSION = "Your build.gradle file is missing the `jenkinsVersion` item."; - public static final String MISSING_SHORTNAME = "Your build.gradle file is missing the `shortName` item."; - public static final String MISSING_DISPLAYNAME = "Your build.gradle file is missing the `displayName` item."; - public static final String MISSING_GROUP = "Your build.gradle file is missing the `group` item. Please add one with the value `io.jenkins.plugins`"; - public static final String MISSING_GROUP_VALUE = "Your build.gradle file has a `group` but the value is empty. Please set the value to `io.jenkins.plugins` or a company/group specific `group`"; - public static final String JPI_PLUGIN_VERSION_TOO_LOW = "The version of the gradle jpi plugin in your build.gradle (%s) is to low, please update to at least (%s)"; - public static final String CANNOT_VERIFY_GROUP = "The `group` value from your build.gradle seems to be a variable, we cannot verify the `group` value in this case, please make sure the group is `io.jenkins.plugins` or a company/group specific `group`"; - public static final String SHOULD_BE_IO_JENKINS_PLUGINS = "The `group` from the build.gradle should be `io.jenkins.plugins` instead of `org.jenkins-ci.plugins`"; - public static final String NO_BUILD_GRADLE_FOUND = "No build.gradle found in root of project, if you are using a different build system, or this is not a plugin, you can disregard this message"; - public static final String INVALID_BUILD_GRADLE = "Your build.gradle file did not parse correctly, please verify the content."; - public static final String INCORRECT_TARGET_COMPAT_VERSION = "Only targetCompatibility=1.8 is supported for Jenkins plugins."; - public static final String CANNOT_VERIFY_TARGET_COMPAT = "The `targetCompatibility` value from your build.gradle seems to be a variable, we cannot verify the `targetCompatibility` in this case, please make sure it is set to `1.8`"; - public static final String CANNOT_VERIFY_JENKINS_VERSION = "The `jenkinsVersion` value from your build.gradle seems to be a variable, we cannot verify the `jenkinsVersion` in this case, please make sure it is set to at least `%s`"; - - - public static final Version LOWEST_JPI_PLUGIN_VERSION = new Version(0,47); - public static final Version JAVA_COMPATIBILITY_VERSION = new Version(1,8,0); - - private boolean hasJenkinsVersion = false; - private boolean hasShortName = false; - private boolean hasDisplayName = false; - private boolean hasGroup = false; - private boolean hasLicense = false; - - private String forkTo; - private String forkFrom; - - @Override - public void verify(HostingRequest issue, HashSet hostingIssues) throws IOException { - GitHub github = GitHub.connect(); - forkFrom = issue.getRepositoryUrl(); - forkTo = issue.getNewRepoName(); - - if(StringUtils.isNotBlank(forkFrom)) { - Matcher m = Pattern.compile("(?:https://github\\.com/)?(\\S+)/(\\S+)", CASE_INSENSITIVE).matcher(forkFrom); - if(m.matches()) { - String owner = m.group(1); - String repoName = m.group(2); - - GHRepository repo = github.getRepository(owner+"/"+repoName); - try { - GHContent buildGradle = repo.getFileContent("build.gradle"); - if(buildGradle != null) { - InputStream input = buildGradle.read(); - - AstBuilder astBuilder = new AstBuilder(); - List nodes = astBuilder.buildFromString(CompilePhase.SEMANTIC_ANALYSIS, false, IOUtils.toString(input, Charset.defaultCharset())); - - BlockStatement node = (BlockStatement) nodes.getFirst(); - for (Statement s : node.getStatements()) { - if (s instanceof ExpressionStatement statement) { - Expression e = statement.getExpression(); - if (e instanceof MethodCallExpression mc) { - if (mc.getMethodAsString().equals("plugins")) { - // make sure we get the correct version of the gradle jpi plugin - checkPluginVersion(((ArgumentListExpression) mc.getArguments()).getExpression(0), hostingIssues); - } else if (mc.getMethodAsString().equals("repositories")) { - // verify that any references to repo.jenkins-ci.org are correct - checkRepositories(((ArgumentListExpression) mc.getArguments()).getExpression(0), hostingIssues); - } else if (mc.getMethodAsString().equals("jenkinsPlugin")) { - // verify the things that will make it into the pom.xml that is published - checkJenkinsPlugin(((ArgumentListExpression) mc.getArguments()).getExpression(0), hostingIssues); - } - } else if (e instanceof BinaryExpression be) { - VariableExpression v = (VariableExpression) be.getLeftExpression(); - if (v.getName().equals("group")) { - checkGroup(be.getRightExpression(), hostingIssues); - } else if (v.getName().equals("targetCompatibility")) { - checkTargetCompatibility(be.getRightExpression(), hostingIssues); - } - } - } - // There are other possibilities here, but we currently don't take them into account - } - - if (!hasJenkinsVersion) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, MISSING_JENKINS_VERSION)); - } - - if (!hasShortName) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, MISSING_SHORTNAME)); - } - - if (!hasDisplayName) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, MISSING_DISPLAYNAME)); - } - - if (!hasGroup) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, MISSING_GROUP)); - } - - if (!hasLicense) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, SPECIFY_LICENSE)); - } - } else { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.WARNING, NO_BUILD_GRADLE_FOUND)); - } - } catch(GHFileNotFoundException e) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.WARNING, NO_BUILD_GRADLE_FOUND)); - } catch(CompilationFailedException e) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, INVALID_BUILD_GRADLE)); - } - } else { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingChecker.INVALID_FORK_FROM, forkFrom)); - } - } - } - - @Override - public boolean hasBuildFile(HostingRequest issue) throws IOException { - return HostingChecker.fileExistsInRepo(issue, "build.gradle"); - } - - public static String getShortName(String contents) { - String res = null; - - AstBuilder astBuilder = new AstBuilder(); - List nodes = astBuilder.buildFromString(CompilePhase.SEMANTIC_ANALYSIS, false, contents); - boolean isDone = false; - - BlockStatement node = (BlockStatement) nodes.getFirst(); - for (Statement s : node.getStatements()) { - Expression e = ((ExpressionStatement) s).getExpression(); - if (e instanceof MethodCallExpression mc) { - if (mc.getMethodAsString().equals("jenkinsPlugin")) { - Expression jenkinsPlugin = ((ArgumentListExpression) mc.getArguments()).getExpression(0); - if (jenkinsPlugin instanceof ClosureExpression c) { - for (Statement st : ((BlockStatement) c.getCode()).getStatements()) { - ExpressionStatement sm = (ExpressionStatement) st; - if (sm.getExpression() instanceof BinaryExpression) { - BinaryExpression be = (BinaryExpression) sm.getExpression(); - if (be.getLeftExpression().getText().equals("shortName")) { - if (be.getRightExpression() instanceof ConstantExpression) { - res = be.getRightExpression().getText(); - isDone = true; - break; - } - } - } - } - } - } - } - if(isDone) { - break; - } - } - return res; - } - - public static String getGroupId(String contents) { - String res = null; - AstBuilder astBuilder = new AstBuilder(); - List nodes = astBuilder.buildFromString(CompilePhase.SEMANTIC_ANALYSIS, false, contents); - - BlockStatement node = (BlockStatement) nodes.getFirst(); - for (Statement s : node.getStatements()) { - Expression e = ((ExpressionStatement) s).getExpression(); - if (e instanceof BinaryExpression be) { - VariableExpression v = (VariableExpression) be.getLeftExpression(); - if (v.getName().equals("group")) { - if (be.getRightExpression() instanceof ConstantExpression) { - res = be.getRightExpression().getText(); - break; - } - } - } - } - return res; - } - - private void checkPluginVersion(Expression plugins, HashSet hostingIssues) { - if(plugins instanceof ClosureExpression c) { - for (Statement st : ((BlockStatement) c.getCode()).getStatements()) { - Expression e = ((ExpressionStatement) st).getExpression(); - if (e instanceof MethodCallExpression mc) { - // if no version if there for the jpi plugin, the latest will be used - if (mc.getMethodAsString().equals("version")) { - e = ((ArgumentListExpression) mc.getArguments()).getExpression(0); - if (e instanceof ConstantExpression expression && mc.getObjectExpression() instanceof MethodCallExpression) { - if (((MethodCallExpression) mc.getObjectExpression()).getMethodAsString().equals("id")) { - String pluginId = ((ConstantExpression) ((ArgumentListExpression) mc.getArguments()).getExpression(0)).getValue().toString(); - if (pluginId.equals("org.jenkins-ci.jpi")) { - Version jpiPluginVersion = new Version(expression.getValue().toString()); - if (jpiPluginVersion.compareTo(LOWEST_JPI_PLUGIN_VERSION) < 0) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, JPI_PLUGIN_VERSION_TOO_LOW, jpiPluginVersion, LOWEST_JPI_PLUGIN_VERSION)); - } - } - } - } - } - } - } - } - } - - private void checkRepositories(Expression repositories, HashSet hostingIssues) { - if(repositories instanceof ClosureExpression c) { - for(Statement st : ((BlockStatement)c.getCode()).getStatements()) { - Expression e = ((ExpressionStatement) st).getExpression(); - if(e instanceof MethodCallExpression expression && expression.getMethodAsString().equals("maven")) { - c = (ClosureExpression)((ArgumentListExpression)expression.getArguments()).getExpression(0); - for(Statement s : ((BlockStatement)c.getCode()).getStatements()) { - e = ((ExpressionStatement)s).getExpression(); - if(e instanceof MethodCallExpression callExpression && callExpression.getMethodAsString().equals("url")) { - e = ((ArgumentListExpression)callExpression.getArguments()).getExpression(0); - if(e instanceof ConstantExpression) { - String url = e.getText(); - if(url.contains("repo.jenkins-ci.org")) { - try { - URI uri = new URI(url); - if(!uri.getScheme().equals("https")) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "You MUST use an https:// scheme in your build.gradle for the `repositories` block for repo.jenkins-ci.org urls.")); - } - } catch(URISyntaxException ex) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "The `repositories` block in your build.gradle for 'repo.jenkins-ci.org' has an invalid URL")); - } - } - } - } - } - } - } - } - } - - private void checkJenkinsPlugin(Expression jenkinsPlugin, HashSet hostingIssues) { - if(jenkinsPlugin instanceof ClosureExpression c) { - for(Statement st : ((BlockStatement)c.getCode()).getStatements()) { - ExpressionStatement s = (ExpressionStatement)st; - if(s.getExpression() instanceof BinaryExpression) { - BinaryExpression be = (BinaryExpression)s.getExpression(); - if(be.getLeftExpression().getText().equals("coreVersion")) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "`coreVersion` is deprecated, please use `jenkinsVersion`")); - } else if(be.getLeftExpression().getText().equals("jenkinsVersion")) { - checkJenkinsVersion(be.getRightExpression(), hostingIssues); - } else if(be.getLeftExpression().getText().equals("shortName")) { - checkShortName(be.getRightExpression(), hostingIssues); - } else if(be.getLeftExpression().getText().equals("displayName")) { - checkDisplayName(be.getRightExpression(), hostingIssues); - } - } else if(s.getExpression() instanceof MethodCallExpression) { - MethodCallExpression mc = (MethodCallExpression)s.getExpression(); - if(mc.getMethodAsString().equals("licenses")) { - checkLicenses(((ArgumentListExpression) mc.getArguments()).getExpression(0), hostingIssues); - } - } - } - } - } - - private void checkGroup(Expression e, HashSet hostingIssues) { - if(e instanceof ConstantExpression) { - String groupId = e.getText(); - if(StringUtils.isBlank(groupId)) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, MISSING_GROUP_VALUE)); - } else { - hasGroup = true; - if(groupId.equals("org.jenkins-ci.plugins")) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, SHOULD_BE_IO_JENKINS_PLUGINS)); - } - } - } else { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.INFO, CANNOT_VERIFY_GROUP)); - } - } - - private void checkTargetCompatibility(Expression e, HashSet hostingIssues) { - if(e instanceof ConstantExpression) { - Version targetCompatVersion = new Version(e.getText()); - if(targetCompatVersion.compareTo(JAVA_COMPATIBILITY_VERSION) != 0) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, INCORRECT_TARGET_COMPAT_VERSION)); - } - } else { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.INFO, CANNOT_VERIFY_TARGET_COMPAT)); - } - } - - private void checkLicenses(Expression e, HashSet hostingIssues) { - if(e instanceof ClosureExpression c) { - for(Statement st : ((BlockStatement)c.getCode()).getStatements()) { - e = ((ExpressionStatement) st).getExpression(); - if(e instanceof MethodCallExpression expression && expression.getMethodAsString().equals("license")) { - hasLicense = true; -// c = (ClosureExpression)((ArgumentListExpression)((MethodCallExpression)e).getArguments()).getExpression(0); -// for(Statement s : ((BlockStatement)c.getCode()).getStatements()) { -// e = ((ExpressionStatement)s).getExpression(); -// if(e instanceof MethodCallExpression) { -// String methodName = ((MethodCallExpression)e).getMethodAsString(); -// if(methodName.equals("name")) { -// -// } else if(methodName.equals("url")) { -// // would be nice if we could check the url -// } -// } -// } -// System.out.println(e.getText()); - } - } - } - } - - private void checkJenkinsVersion(Expression e, HashSet hostingIssues) { - if(e instanceof ConstantExpression) { - Version jenkinsVersion = new Version(e.getText()); - if(jenkinsVersion.compareTo(LOWEST_JENKINS_VERSION) < 0) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "The `jenkinsVersion` value in your build.gradle does not meet the minimum Jenkins version required, please update your jenkinsVersion to at least %s. Take a look at the [baseline recommendations](https://www.jenkins.io/doc/developer/plugin-development/choosing-jenkins-baseline/#currently-recommended-versions).".formatted(jenkinsVersion, LOWEST_JENKINS_VERSION))); - } - hasJenkinsVersion = true; - } else { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.INFO, CANNOT_VERIFY_JENKINS_VERSION, LOWEST_JENKINS_VERSION)); - } - } - - private void checkShortName(Expression e, HashSet hostingIssues) { - if(e instanceof ConstantExpression) { - String shortName = e.getText(); - if(StringUtils.isNotBlank(shortName)) { - if(StringUtils.isNotBlank(forkTo) && !shortName.equals(forkTo.replace("-plugin", ""))) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "The `shortName` from the build.gradle (%s) is incorrect, it should be '%s' ('New Repository Name' field with \"-plugin\" removed)", shortName, (forkTo.replace("-plugin", "")).toLowerCase())); - } - - if(shortName.toLowerCase().contains("jenkins")) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "The `shortName` from the build.gradle (%s) should not contain \"Jenkins\"", shortName)); - } - - if(!shortName.toLowerCase().equals(shortName)) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "The `shortName`` from the build.gradle (%s) should be all lower case", shortName)); - } - hasShortName = true; - } else { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "The build.gradle file does not contain a valid `shortName` for the project")); - } - } - } - - private void checkDisplayName(Expression e, HashSet hostingIssues) { - if(e instanceof ConstantExpression) { - String name = e.getText(); - if(StringUtils.isNotBlank(name)) { - if(name.toLowerCase().contains("jenkins")) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "The `displayName` in the build.gradle should not contain \"Jenkins\"")); - } - hasDisplayName = true; - } else { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "The build.gradle file does not contain a valid `displayName` for the project")); - } - } - } -} diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/Hoster.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/Hoster.java index 98bf269f6d..fa252933d7 100644 --- a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/Hoster.java +++ b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/Hoster.java @@ -7,7 +7,8 @@ import com.atlassian.jira.rest.client.api.domain.Component; import com.atlassian.jira.rest.client.api.domain.input.ComponentInput; import io.atlassian.util.concurrent.Promise; -import io.jenkins.infra.repository_permissions_updater.hosting.HostingRequest.IssueTracker; +import io.jenkins.infra.repository_permissions_updater.hosting.model.HostingRequest; +import io.jenkins.infra.repository_permissions_updater.hosting.model.HostingRequest.IssueTracker; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -19,6 +20,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; + +import io.jenkins.infra.repository_permissions_updater.hosting.verify.MavenVerifierConsumer; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.kohsuke.github.GHContent; @@ -47,6 +50,7 @@ public class Hoster { private static final Logger LOGGER = LoggerFactory.getLogger(Hoster.class); + private static final Pattern GITHUB_HOST_PATTERN = Pattern.compile("(?:https://github\\.com/)?(\\S+)/(\\S+)", CASE_INSENSITIVE); public static void main(String[] args) { new Hoster().run(Integer.parseInt(args[0])); @@ -59,12 +63,12 @@ private void run(int issueID) { try { final HostingRequest hostingRequest = HostingRequestParser.retrieveAndParse(issueID); - String defaultAssignee = hostingRequest.getJenkinsProjectUsers().getFirst(); - String forkFrom = hostingRequest.getRepositoryUrl(); - List users = hostingRequest.getGithubUsers(); - IssueTracker issueTrackerChoice = hostingRequest.getIssueTracker(); + String defaultAssignee = hostingRequest.jenkinsProjectUsers().getFirst(); + String forkFrom = hostingRequest.repositoryUrl(); + List users = hostingRequest.githubUsers(); + IssueTracker issueTrackerChoice = hostingRequest.issueTracker(); - String forkTo = hostingRequest.getNewRepoName(); + String forkTo = hostingRequest.newRepoName(); if (StringUtils.isBlank(forkFrom) || StringUtils.isBlank(forkTo) || users.isEmpty()) { LOGGER.info("Could not retrieve information (or information does not exist) from the Hosting request"); @@ -72,7 +76,7 @@ private void run(int issueID) { } // Parse forkFrom in order to determine original repo owner and repo name - Matcher m = Pattern.compile("(?:https://github\\.com/)?(\\S+)/(\\S+)", CASE_INSENSITIVE).matcher(forkFrom); + Matcher m = GITHUB_HOST_PATTERN.matcher(forkFrom); if (m.matches()) { if (!forkGitHub(m.group(1), m.group(2), forkTo, users, issueTrackerChoice == IssueTracker.GITHUB)) { LOGGER.error("Hosting request failed to fork repository on Github"); @@ -103,7 +107,7 @@ private void run(int issueID) { componentId = ""; } - String prUrl = createUploadPermissionPR(issueID, forkTo, users, hostingRequest.getJenkinsProjectUsers(), issueTrackerChoice == IssueTracker.GITHUB, componentId); + String prUrl = createUploadPermissionPR(issueID, forkTo, users, hostingRequest.jenkinsProjectUsers(), issueTrackerChoice == IssueTracker.GITHUB, componentId); if (StringUtils.isBlank(prUrl)) { LOGGER.error("Could not create upload permission pull request"); } @@ -393,8 +397,8 @@ private static String getArtifactPath(GitHub github, String forkTo) throws IOExc if (file != null && file.isFile()) { String contents = IOUtils.toString(file.read(), StandardCharsets.UTF_8); if (file.isFile()) { - artifactId = MavenVerifier.getArtifactId(contents); - groupId = MavenVerifier.getGroupId(contents); + artifactId = MavenVerifierConsumer.getArtifactId(contents); + groupId = MavenVerifierConsumer.getGroupId(contents); } } } catch (IOException e) { diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/HostingChecker.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/HostingChecker.java index 9d52720061..f5d5d23b37 100644 --- a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/HostingChecker.java +++ b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/HostingChecker.java @@ -5,9 +5,16 @@ import java.util.Comparator; import java.util.HashSet; import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; + +import io.jenkins.infra.repository_permissions_updater.hosting.condition.PomFileExistsCondition; +import io.jenkins.infra.repository_permissions_updater.hosting.model.HostingRequest; +import io.jenkins.infra.repository_permissions_updater.hosting.model.VerificationMessage; +import io.jenkins.infra.repository_permissions_updater.hosting.model.Version; +import io.jenkins.infra.repository_permissions_updater.hosting.verify.*; import org.apache.commons.lang3.StringUtils; import org.javatuples.Triplet; import org.kohsuke.github.GHContent; @@ -25,10 +32,16 @@ public class HostingChecker { private static final Logger LOGGER = LoggerFactory.getLogger(HostingChecker.class); - public static final String INVALID_FORK_FROM = "Repository URL '%s' is not a valid GitHub repository (check that you do not have .git at the end, GitHub API doesn't support this)."; - public static final Version LOWEST_JENKINS_VERSION = new Version(2, 440, 3); + private static final boolean DEBUG_HOSTING = Boolean.getBoolean("debugHosting"); + public static final String INITIAL_HOSTING_REQUEST_FEEDBACK = """ + It appears you have some issues with your hosting request. Please see the list below and \ + correct all issues marked Required. Your hosting request will not be \ + approved until these issues are corrected. Issues marked with Warning \ + or Info are just recommendations and will not stall the hosting process. + """; + public static void main(String[] args) throws IOException { new HostingChecker().checkRequest(Integer.parseInt(args[0])); } @@ -37,26 +50,24 @@ public void checkRequest(int issueID) throws IOException { boolean hasBuildSystem = false; HashSet hostingIssues = new HashSet<>(); - boolean debug = System.getProperty("debugHosting", "false").equalsIgnoreCase("true"); - - ArrayList> verifications = new ArrayList<>(); - verifications.add(Triplet.with("Jira", new HostingFieldVerifier(), null)); - verifications.add(Triplet.with("GitHub", new GitHubVerifier(), null)); - verifications.add(Triplet.with("Maven", new MavenVerifier(), new FileExistsConditionChecker("pom.xml"))); - verifications.add(Triplet.with("JenkinsProjectUsers", new JenkinsProjectUserVerifier(), null)); + ArrayList>, Predicate>> verifications = new ArrayList<>(); + verifications.add(Triplet.with("Jira", new HostingFieldVerifierConsumer(), null)); + verifications.add(Triplet.with("GitHub", new GitHubVerifierConsumer(), null)); + verifications.add(Triplet.with("Maven", new MavenVerifierConsumer(), new PomFileExistsCondition())); + verifications.add(Triplet.with("JenkinsProjectUsers", new JenkinsProjectUserVerifierConsumer(), null)); final HostingRequest hostingRequest = HostingRequestParser.retrieveAndParse(issueID); - for (Triplet verifier : verifications) { + for (Triplet>, Predicate> verifier : verifications) { try { - boolean runIt = verifier.getValue2() == null || verifier.getValue2().checkCondition(hostingRequest); + boolean runIt = verifier.getValue2() == null || verifier.getValue2().test(hostingRequest); if (runIt) { LOGGER.info("Running verification '" + verifier.getValue0() + "'"); - verifier.getValue1().verify(hostingRequest, hostingIssues); + verifier.getValue1().accept(hostingRequest, hostingIssues); } - if (verifier.getValue1() instanceof BuildSystemVerifier) { - hasBuildSystem |= ((BuildSystemVerifier) verifier.getValue1()).hasBuildFile(hostingRequest); + if (verifier.getValue1() instanceof MavenVerifierConsumer mavenVerifierConsumer) { + hasBuildSystem |= mavenVerifierConsumer.hasBuildFile(hostingRequest); } } catch (Exception e) { LOGGER.error("Error running verification '" + verifier.getValue0(), e); @@ -72,24 +83,27 @@ public void checkRequest(int issueID) throws IOException { StringBuilder msg = new StringBuilder("Hello from your friendly Jenkins Hosting Checker\n\n"); LOGGER.info("Checking if there were errors"); if (!hostingIssues.isEmpty()) { - msg.append("It appears you have some issues with your hosting request. Please see the list below and " - + "correct all issues marked Required. Your hosting request will not be " - + "approved until these issues are corrected. Issues marked with Warning " - + "or Info are just recommendations and will not stall the hosting process.\n"); + msg.append(INITIAL_HOSTING_REQUEST_FEEDBACK); LOGGER.info("Appending issues to msg"); appendIssues(msg, hostingIssues, 1); - msg.append("\nYou can re-trigger a check by editing your hosting request or by commenting `/hosting re-check`"); + msg.append(""" + + You can re-trigger a check by editing your hosting request or by commenting `/hosting re-check`"""); } else { - msg.append("It looks like you have everything in order for your hosting request. " - + "A member of the [Jenkins hosting team](https://www.jenkins.io/project/teams/hosting/#members-of-the-hosting-team) " - + "will check over things that I am not able to check" - + "(code review, README content, etc) and process the request as quickly as possible. " - + "Thank you for your patience.\n") - .append("\nHosting team members can host this request with `/hosting host`"); + msg.append(""" + It looks like you have everything in order for your hosting request. \ + A member of the [Jenkins hosting team](https://www.jenkins.io/project/teams/hosting/#members-of-the-hosting-team) \ + will check over things that I am not able to check\ + (code review, README content, etc) and process the request as quickly as possible. \ + Thank you for your patience. + """) + .append(""" + + Hosting team members can host this request with `/hosting host`"""); } LOGGER.info(msg.toString()); - if (!debug) { + if (!DEBUG_HOSTING) { GitHub github = GitHub.connect(); GHIssue issue = github.getRepository(HOSTING_REPO_SLUG).getIssue(issueID); issue.comment(msg.toString()); @@ -108,7 +122,7 @@ public void checkRequest(int issueID) throws IOException { } private void appendIssues(StringBuilder msg, Set issues, int level) { - for (VerificationMessage issue : issues.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList())) { + for (VerificationMessage issue : issues.stream().sorted(Comparator.reverseOrder()).toList()) { if (level == 1) { msg.append("%s %s %s: %s%n".formatted(StringUtils.repeat("*", level), issue.getSeverity().getColor(), issue.getSeverity().getMessage(), issue.getMessage())); } else { @@ -124,7 +138,7 @@ private void appendIssues(StringBuilder msg, Set issues, in public static boolean fileExistsInRepo(HostingRequest issue, String fileName) throws IOException { boolean res = false; GitHub github = GitHub.connect(); - String forkFrom = issue.getRepositoryUrl(); + String forkFrom = issue.repositoryUrl(); if (StringUtils.isNotBlank(forkFrom)) { Matcher m = Pattern.compile("https://github\\.com/(\\S+)/(\\S+)", CASE_INSENSITIVE).matcher(forkFrom); if (m.matches()) { diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/HostingConfig.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/HostingConfig.java index 1ed13ae530..6f5eb355ef 100644 --- a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/HostingConfig.java +++ b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/HostingConfig.java @@ -1,5 +1,8 @@ package io.jenkins.infra.repository_permissions_updater.hosting; +import java.util.ResourceBundle; +import java.util.regex.Pattern; + public final class HostingConfig { static final String TARGET_ORG_NAME; @@ -10,8 +13,11 @@ public final class HostingConfig { static final String JIRA_USERNAME = System.getenv("JIRA_USERNAME"); static final String JIRA_PASSWORD = System.getenv("JIRA_PASSWORD"); static final String JIRA_PROJECT; + public static final Pattern GITHUB_FORK_PATTERN; + public static final ResourceBundle RESOURCE_BUNDLE; private HostingConfig() { + throw new IllegalStateException("Utility class"); } static { @@ -24,5 +30,8 @@ private HostingConfig() { String projectOverride = System.getenv("JIRA_PROJECT_NAME"); JIRA_PROJECT = projectOverride != null ? projectOverride : "JENKINS"; + + GITHUB_FORK_PATTERN = Pattern.compile("(?:https://github\\.com/)?(\\S+)/(\\S+)", Pattern.CASE_INSENSITIVE); + RESOURCE_BUNDLE = ResourceBundle.getBundle(HOSTING_REPO_NAME); } } diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/HostingFieldVerifier.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/HostingFieldVerifier.java deleted file mode 100644 index ddcff79460..0000000000 --- a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/HostingFieldVerifier.java +++ /dev/null @@ -1,89 +0,0 @@ -package io.jenkins.infra.repository_permissions_updater.hosting; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.apache.commons.lang3.StringUtils; - -public class HostingFieldVerifier implements Verifier { - - @Override - public void verify(HostingRequest issue, HashSet hostingIssues) { - String forkTo = issue.getNewRepoName(); - String forkFrom = issue.getRepositoryUrl(); - List users = issue.getGithubUsers(); - - // check list of users - if(users.isEmpty()) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "Missing list of users to authorize in 'GitHub Users to Authorize as Committers'")); - } - - if(StringUtils.isBlank(forkFrom)) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingChecker.INVALID_FORK_FROM, "")); - } else { - boolean updateIssue = false; - if(forkFrom.endsWith(".git")) { - forkFrom = forkFrom.substring(0, forkFrom.length() - 4); - updateIssue = true; - } - - if(forkFrom.startsWith("http://")) { - forkFrom = forkFrom.replace("http://", "https://"); - updateIssue = true; - } - - // check the repo they want to fork from to make sure it conforms - if(!Pattern.matches("https://github\\.com/(\\S+)/(\\S+)", forkFrom)) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingChecker.INVALID_FORK_FROM, forkFrom)); - } - - if(updateIssue) { - // TODO implement update - } - } - - if(StringUtils.isBlank(forkTo)) { - HashSet subitems = new HashSet<>(); - subitems.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "It must match the artifactId (with -plugin added) from your pom.xml.")); - subitems.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "It must end in -plugin if hosting request is for a Jenkins plugin.")); - subitems.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "It must be all lowercase.")); - subitems.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "It must NOT contain \"Jenkins\".")); - subitems.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "It must use hyphens ( - ) instead of spaces or camel case.")); - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, subitems, "You must specify the repository name to fork to in 'New Repository Name' field with the following rules:")); - } else { - String originalForkTo = forkTo; - // we don't like camel case - ThisIsCamelCase becomes this-is-camel-case - Matcher m = Pattern.compile("(\\B[A-Z]+?(?=[A-Z][^A-Z])|\\B[A-Z]+?(?=[^A-Z]))").matcher(forkTo); - String forkToLower = m.replaceAll("-$1").toLowerCase(); - if(forkToLower.contains("-jenkins") || forkToLower.contains("-hudson")) { - forkToLower = forkToLower.replace("-jenkins", "").replace("-hudson", ""); - } else if(forkToLower.contains("jenkins") || forkToLower.contains("hudson")) { - forkToLower = forkToLower.replace("jenkins", "").replace("hudson", ""); - } - - // sometimes if we remove jenkins/hudson, we're left with something like -jmh, so trim it - forkToLower = StringUtils.strip(forkToLower, "- "); - - if(!forkToLower.endsWith("-plugin")) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "'New Repository Name' must end with \"-plugin\" (disregard if you are not requesting hosting of a plugin)")); - } - - // we don't like spaces... - forkToLower = forkToLower.replace(" ", "-"); - - if(!forkToLower.equals(originalForkTo)) { - // TODO implement update -// issueUpdates.add(new IssueInputBuilder().setFieldValue(JiraHelper.FORK_TO_JIRA_FIELD, forkToLower).build()); - } - } - - // TODO implement update -// if(issueUpdates.size() > 0) { -// for(IssueInput issueUpdate : issueUpdates) { -// issueClient.updateIssue(issue.getKey(), issueUpdate).claim(); -// } -// } - } -} diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/HostingRequest.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/HostingRequest.java deleted file mode 100644 index 7f706d25d8..0000000000 --- a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/HostingRequest.java +++ /dev/null @@ -1,62 +0,0 @@ -package io.jenkins.infra.repository_permissions_updater.hosting; - -import java.util.Collections; -import java.util.List; -import java.util.Locale; - -public class HostingRequest { - - /** - * e.g https://github.com/user/repo-name - */ - private final String repositoryUrl; - /** - * e.g. your-cool-plugin - */ - private final String newRepoName; - private final List githubUsers; - private final List jenkinsProjectUsers; - private final IssueTracker issueTracker; - - public HostingRequest( - String repositoryUrl, - String newRepoName, - List githubUsers, - List jenkinsProjectUsers, - IssueTracker issueTracker - ) { - this.repositoryUrl = repositoryUrl; - this.newRepoName = newRepoName; - this.githubUsers = Collections.unmodifiableList(githubUsers); - this.jenkinsProjectUsers = Collections.unmodifiableList(jenkinsProjectUsers); - this.issueTracker = issueTracker; - } - - public String getRepositoryUrl() { - return repositoryUrl; - } - - public String getNewRepoName() { - return newRepoName; - } - - public List getGithubUsers() { - return githubUsers; - } - - public List getJenkinsProjectUsers() { - return jenkinsProjectUsers; - } - - public IssueTracker getIssueTracker() { - return issueTracker; - } - - public enum IssueTracker { - GITHUB, JIRA; - - public static IssueTracker fromString(String string) { - return string.toLowerCase(Locale.ROOT).contains("git") ? GITHUB : JIRA; - } - } -} diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/HostingRequestParser.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/HostingRequestParser.java index 626ddceddf..f04e65acfb 100644 --- a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/HostingRequestParser.java +++ b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/HostingRequestParser.java @@ -1,6 +1,7 @@ package io.jenkins.infra.repository_permissions_updater.hosting; -import io.jenkins.infra.repository_permissions_updater.hosting.HostingRequest.IssueTracker; +import io.jenkins.infra.repository_permissions_updater.hosting.model.HostingRequest; +import io.jenkins.infra.repository_permissions_updater.hosting.model.HostingRequest.IssueTracker; import java.io.IOException; import java.util.AbstractMap; import java.util.Arrays; @@ -46,7 +47,7 @@ public static HostingRequest parse(String body) { fields.get(GITHUB_USERS).asList() .stream() .map(user -> user.replace("@", "")) - .collect(Collectors.toList()), + .toList(), fields.get(JENKINS_PROJECT_USERS).asList(), IssueTracker.fromString(fields.get(ISSUE_TRACKER).asString()) ); diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/JenkinsProjectUserVerifier.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/JenkinsProjectUserVerifier.java deleted file mode 100644 index c67eda543c..0000000000 --- a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/JenkinsProjectUserVerifier.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.jenkins.infra.repository_permissions_updater.hosting; - -import io.jenkins.infra.repository_permissions_updater.KnownUsers; -import java.io.IOException; -import java.util.HashSet; -import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; - -public class JenkinsProjectUserVerifier implements Verifier{ - - @Override - public void verify(HostingRequest request, HashSet hostingIssues) throws IOException { - - String missingInArtifactory = request.getJenkinsProjectUsers() - .stream().filter(user -> !KnownUsers.existsInArtifactory(user)) - .collect(Collectors.joining(", ")); - String missingInJira = request.getJenkinsProjectUsers() - .stream().filter(user -> !KnownUsers.existsInJira(user)) - .collect(Collectors.joining(", ")); - - if (StringUtils.isNotBlank(missingInArtifactory)) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "The following usernames in 'Jenkins project users to have release permission' need to log into [Artifactory](https://repo.jenkins-ci.org/): %s (reports are re-synced hourly, wait to re-check for a bit after logging in)", missingInArtifactory)); - } - if (StringUtils.isNotBlank(missingInJira)) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "The following usernames in 'Jenkins project users to have release permission' need to log into [Jira](https://issues.jenkins.io): %s (reports are re-synced hourly, wait to re-check for a bit after logging in)", missingInJira)); - } - - } -} diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/Verifier.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/Verifier.java deleted file mode 100644 index ee2f7736b8..0000000000 --- a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/Verifier.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.jenkins.infra.repository_permissions_updater.hosting; - -import java.io.IOException; -import java.util.HashSet; - -public interface Verifier { - void verify(HostingRequest request, HashSet hostingIssues) throws IOException; -} diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/condition/PomFileExistsCondition.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/condition/PomFileExistsCondition.java new file mode 100644 index 0000000000..0ffe4bd8ca --- /dev/null +++ b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/condition/PomFileExistsCondition.java @@ -0,0 +1,57 @@ +package io.jenkins.infra.repository_permissions_updater.hosting.condition; + +import io.jenkins.infra.repository_permissions_updater.hosting.model.HostingRequest; +import org.apache.commons.lang3.StringUtils; +import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GitHub; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class PomFileExistsCondition implements Predicate { + + private static final String POM_FILE_NAME = "pom.xml"; + private static final Pattern GITHUB_HOST_PATTERN = Pattern.compile("(?:https://github\\.com/)?(\\S+)/(\\S+)", Pattern.CASE_INSENSITIVE); + private static final Logger LOGGER = LoggerFactory.getLogger(PomFileExistsCondition.class); + private final GitHub github; + + public PomFileExistsCondition() throws IOException { + this.github = GitHub.connect(); + } + + @Override + public boolean test(HostingRequest hostingRequest) { + boolean res = false; + + String forkFrom = hostingRequest.repositoryUrl(); + + if (!StringUtils.isNotBlank(forkFrom)) return res; + + Matcher m = GITHUB_HOST_PATTERN.matcher(forkFrom); + + if (!m.matches()) return res; + + String owner = m.group(1); + String repoName = m.group(2); + + GHRepository repo = null; + try { + repo = github.getRepository(owner + "/" + repoName); + } catch (IOException e) { + LOGGER.warn("Could not get repo {}", owner + "/" + repoName, e); + } + if (repo == null) return res; + + try { + GHContent file = repo.getFileContent(POM_FILE_NAME); + res = file != null && file.isFile(); + } catch (IOException ignored) { + } + return res; + } +} diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/model/HostingRequest.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/model/HostingRequest.java new file mode 100644 index 0000000000..b48296b7e4 --- /dev/null +++ b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/model/HostingRequest.java @@ -0,0 +1,37 @@ +package io.jenkins.infra.repository_permissions_updater.hosting.model; + +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +/** + * @param repositoryUrl e.g https://github.com/user/repo-name + * @param newRepoName e.g. your-cool-plugin + */ +public record HostingRequest(String repositoryUrl, String newRepoName, + List githubUsers, + List jenkinsProjectUsers, + io.jenkins.infra.repository_permissions_updater.hosting.model.HostingRequest.IssueTracker issueTracker) { + + public HostingRequest( + String repositoryUrl, + String newRepoName, + List githubUsers, + List jenkinsProjectUsers, + IssueTracker issueTracker + ) { + this.repositoryUrl = repositoryUrl; + this.newRepoName = newRepoName; + this.githubUsers = Collections.unmodifiableList(githubUsers); + this.jenkinsProjectUsers = Collections.unmodifiableList(jenkinsProjectUsers); + this.issueTracker = issueTracker; + } + + public enum IssueTracker { + GITHUB, JIRA; + + public static IssueTracker fromString(String string) { + return string.toLowerCase(Locale.ROOT).contains("git") ? GITHUB : JIRA; + } + } +} diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/VerificationMessage.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/model/VerificationMessage.java similarity index 97% rename from src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/VerificationMessage.java rename to src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/model/VerificationMessage.java index 060521d070..73277a7c7b 100644 --- a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/VerificationMessage.java +++ b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/model/VerificationMessage.java @@ -1,4 +1,4 @@ -package io.jenkins.infra.repository_permissions_updater.hosting; +package io.jenkins.infra.repository_permissions_updater.hosting.model; import java.util.Collections; import java.util.Comparator; diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/Version.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/model/Version.java similarity index 76% rename from src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/Version.java rename to src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/model/Version.java index e005f94234..8a4e54cc0a 100644 --- a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/Version.java +++ b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/model/Version.java @@ -1,21 +1,21 @@ -package io.jenkins.infra.repository_permissions_updater.hosting; +package io.jenkins.infra.repository_permissions_updater.hosting.model; import java.util.NoSuchElementException; import java.util.StringTokenizer; -public class Version implements Comparable { +public class Version implements Comparable { private final int major; private final int minor; - private final int micro; + private final int patch; private static final String SEPARATOR = "."; public static final Version EMPTY = new Version(0, 0, 0); - public Version(int major, int minor, int micro) { + public Version(int major, int minor, int patch) { this.major = major; this.minor = minor; - this.micro = micro; + this.patch = patch; validate(); } @@ -30,7 +30,7 @@ public Version(int major, int minor) { public Version(String version) { int major; int minor = -1; - int micro = -1; + int patch = -1; try { StringTokenizer st = new StringTokenizer(version, SEPARATOR, true); @@ -42,7 +42,7 @@ public Version(String version) { if (st.hasMoreTokens()) { st.nextToken(); // consume delimiter - micro = Integer.parseInt(st.nextToken()); + patch = Integer.parseInt(st.nextToken()); if (st.hasMoreTokens()) { throw new IllegalArgumentException("invalid format"); @@ -55,7 +55,7 @@ public Version(String version) { this.major = major; this.minor = minor; - this.micro = micro; + this.patch = patch; validate(); } @@ -64,7 +64,7 @@ private void validate() { throw new IllegalArgumentException("negative major"); } - if (minor < 0 && micro >= 0) { + if (minor < 0 && patch >= 0) { throw new IllegalArgumentException("negative minor with micro provided"); } } @@ -90,21 +90,21 @@ public int getMinor() { return Math.max(minor, 0); } - public int getMicro() { - return Math.max(micro, 0); + public int getPatch() { + return Math.max(patch, 0); } public String toString() { - if (major >= 0 && minor >= 0 && micro < 0) { + if (major >= 0 && minor >= 0 && patch < 0) { return major + SEPARATOR + minor; } else if (major >= 0 && minor < 0) { return "" + major; } - return major + SEPARATOR + minor + SEPARATOR + micro; + return major + SEPARATOR + minor + SEPARATOR + patch; } public int hashCode() { - return (major << 24) + (minor << 16) + (micro << 8); + return (major << 24) + (minor << 16) + (patch << 8); } public boolean equals(Object object) { @@ -118,18 +118,16 @@ public boolean equals(Object object) { Version other = (Version) object; return (major == other.major) && (minor == other.minor) - && (micro == other.micro); + && (patch == other.patch); } - public int compareTo(Object object) { - if (object == this) { + public int compareTo(Version other) { + if (other == this) { return 0; } int localMinor = Math.max(minor, 0); - int localMicro = Math.max(micro, 0); - - Version other = (Version) object; + int localMicro = Math.max(patch, 0); int result = major - other.major; if (result != 0) { @@ -141,7 +139,7 @@ public int compareTo(Object object) { return result; } - result = localMicro - other.getMicro(); + result = localMicro - other.getPatch(); return result; } } diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/GitHubVerifier.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/verify/GitHubVerifierConsumer.java similarity index 81% rename from src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/GitHubVerifier.java rename to src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/verify/GitHubVerifierConsumer.java index 3d6d1aa645..89fa5f685c 100644 --- a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/GitHubVerifier.java +++ b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/verify/GitHubVerifierConsumer.java @@ -1,26 +1,29 @@ -package io.jenkins.infra.repository_permissions_updater.hosting; +package io.jenkins.infra.repository_permissions_updater.hosting.verify; + +import io.jenkins.infra.repository_permissions_updater.hosting.HostingConfig; +import io.jenkins.infra.repository_permissions_updater.hosting.model.HostingRequest; +import io.jenkins.infra.repository_permissions_updater.hosting.model.VerificationMessage; +import org.apache.commons.lang3.StringUtils; +import org.kohsuke.github.*; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.apache.commons.lang3.StringUtils; -import org.kohsuke.github.GHContent; -import org.kohsuke.github.GHLicense; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GHUser; -import org.kohsuke.github.GitHub; -import static java.util.regex.Pattern.CASE_INSENSITIVE; +public final class GitHubVerifierConsumer implements VerifierConsumer { + + private final GitHub gitHub; + + public GitHubVerifierConsumer() throws IOException { + gitHub = GitHub.connect(); + } -public class GitHubVerifier implements Verifier { @Override - public void verify(HostingRequest request, HashSet hostingIssues) throws IOException { - GitHub github = GitHub.connect(); - String forkFrom = request.getRepositoryUrl(); - List users = request.getGithubUsers(); + public void accept(HostingRequest request, HashSet hostingIssues) { + + String forkFrom = request.repositoryUrl(); + List users = request.githubUsers(); if (users != null && !users.isEmpty()) { List invalidUsers = new ArrayList<>(); @@ -30,7 +33,7 @@ public void verify(HostingRequest request, HashSet hostingI } try { - GHUser ghUser = github.getUser(user.trim()); + GHUser ghUser = this.gitHub.getUser(user.trim()); if (ghUser == null || !ghUser.getType().equalsIgnoreCase("user")) { invalidUsers.add(user.trim()); } @@ -47,7 +50,7 @@ public void verify(HostingRequest request, HashSet hostingI if (StringUtils.isNotBlank(forkFrom)) { forkFrom = forkFrom.trim(); - Matcher m = Pattern.compile("https?://github\\.com/(\\S+)/(\\S+)", CASE_INSENSITIVE).matcher(forkFrom); + var m = HostingConfig.GITHUB_FORK_PATTERN.matcher(forkFrom); if (m.matches()) { String owner = m.group(1); String repoName = m.group(2); @@ -64,9 +67,9 @@ public void verify(HostingRequest request, HashSet hostingI GHRepository repo = null; try { - repo = github.getRepository(owner + "/" + repoName); + repo = gitHub.getRepository(owner + "/" + repoName); } catch (Exception e) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingChecker.INVALID_FORK_FROM, forkFrom)); + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("INVALID_FORK_FROM"), forkFrom)); } if (repo != null) { @@ -115,7 +118,7 @@ public void verify(HostingRequest request, HashSet hostingI } } } else { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingChecker.INVALID_FORK_FROM, forkFrom)); + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("INVALID_FORK_FROM"), forkFrom)); } } } diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/verify/HostingFieldVerifierConsumer.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/verify/HostingFieldVerifierConsumer.java new file mode 100644 index 0000000000..f8f54c5dfc --- /dev/null +++ b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/verify/HostingFieldVerifierConsumer.java @@ -0,0 +1,74 @@ +package io.jenkins.infra.repository_permissions_updater.hosting.verify; + +import io.jenkins.infra.repository_permissions_updater.hosting.HostingConfig; +import io.jenkins.infra.repository_permissions_updater.hosting.model.HostingRequest; +import io.jenkins.infra.repository_permissions_updater.hosting.model.VerificationMessage; +import org.apache.commons.lang3.StringUtils; + +import java.util.HashSet; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class HostingFieldVerifierConsumer implements VerifierConsumer { + @Override + public void accept(HostingRequest issue, HashSet hostingIssues) { + String forkTo = issue.newRepoName(); + String forkFrom = issue.repositoryUrl(); + List users = issue.githubUsers(); + + // check list of users + if (users.isEmpty()) { + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("MISSING_LIST_OF_USERS"))); + } + + if (StringUtils.isBlank(forkFrom)) { + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("INVALID_FORK_FROM"), "")); + } else { + boolean updateIssue = false; + if (forkFrom.endsWith(".git")) { + forkFrom = forkFrom.substring(0, forkFrom.length() - 4); + updateIssue = true; + } + + if (forkFrom.startsWith("http://")) { + forkFrom = forkFrom.replace("http://", "https://"); + updateIssue = true; + } + + // check the repo they want to fork from to make sure it conforms + if (!HostingConfig.GITHUB_FORK_PATTERN.matcher(forkFrom).matches()) { + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("INVALID_FORK_FROM"), forkFrom)); + } + + if (updateIssue) { + } + } + + if (StringUtils.isBlank(forkTo)) { + HashSet subitems = new HashSet<>(); + subitems.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("IT_MUST_MATCH_ARTIFACT_ID"))); + subitems.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("IT_MUST_MATCH_PLUGIN"))); + subitems.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("IT_MUST_LOWERCASE"))); + subitems.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("IT_MUST_NOT_CONTAIN_JENKINS"))); + subitems.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("IT_MUST_HYPHENS"))); + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, subitems, HostingConfig.RESOURCE_BUNDLE.getString("IT_MUST_NEW_REPO"))); + } else { + // we don't like camel case - ThisIsCamelCase becomes this-is-camel-case + Matcher m = Pattern.compile("(\\B[A-Z]+?(?=[A-Z][^A-Z])|\\B[A-Z]+?(?=[^A-Z]))").matcher(forkTo); + String forkToLower = m.replaceAll("-$1").toLowerCase(); + if (forkToLower.contains("-jenkins") || forkToLower.contains("-hudson")) { + forkToLower = forkToLower.replace("-jenkins", "").replace("-hudson", ""); + } else if (forkToLower.contains("jenkins") || forkToLower.contains("hudson")) { + forkToLower = forkToLower.replace("jenkins", "").replace("hudson", ""); + } + + // sometimes if we remove jenkins/hudson, we're left with something like -jmh, so trim it + forkToLower = StringUtils.strip(forkToLower, "- "); + + if (!forkToLower.endsWith("-plugin")) { + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("NEW_REPO_MUST_END"))); + } + } + } +} diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/verify/JenkinsProjectUserVerifierConsumer.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/verify/JenkinsProjectUserVerifierConsumer.java new file mode 100644 index 0000000000..60f7825625 --- /dev/null +++ b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/verify/JenkinsProjectUserVerifierConsumer.java @@ -0,0 +1,32 @@ +package io.jenkins.infra.repository_permissions_updater.hosting.verify; + +import io.jenkins.infra.repository_permissions_updater.KnownUsers; +import io.jenkins.infra.repository_permissions_updater.hosting.HostingConfig; +import io.jenkins.infra.repository_permissions_updater.hosting.model.HostingRequest; +import io.jenkins.infra.repository_permissions_updater.hosting.model.VerificationMessage; +import org.apache.commons.lang3.StringUtils; + +import java.util.HashSet; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public final class JenkinsProjectUserVerifierConsumer implements VerifierConsumer { + @Override + public void accept(HostingRequest request, HashSet hostingIssues) { + String missingInArtifactory = request.jenkinsProjectUsers() + .stream() + .filter(Predicate.not(KnownUsers::existsInArtifactory)) + .collect(Collectors.joining(", ")); + + String missingInJira = request.jenkinsProjectUsers() + .stream().filter(Predicate.not(KnownUsers::existsInJira)) + .collect(Collectors.joining(", ")); + + if (StringUtils.isNotBlank(missingInArtifactory)) { + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("MISSING_IN_ARTIFACTORY"), missingInArtifactory)); + } + if (StringUtils.isNotBlank(missingInJira)) { + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("MISSING_IN_JIRA"), missingInJira)); + } + } +} diff --git a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/MavenVerifier.java b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/verify/MavenVerifierConsumer.java similarity index 73% rename from src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/MavenVerifier.java rename to src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/verify/MavenVerifierConsumer.java index 59a0bb0b8f..bfb7da41f1 100644 --- a/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/MavenVerifier.java +++ b/src/main/java/io/jenkins/infra/repository_permissions_updater/hosting/verify/MavenVerifierConsumer.java @@ -1,96 +1,129 @@ -package io.jenkins.infra.repository_permissions_updater.hosting; +package io.jenkins.infra.repository_permissions_updater.hosting.verify; -import java.io.IOException; -import java.io.InputStream; -import java.io.StringReader; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.HashSet; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import io.jenkins.infra.repository_permissions_updater.hosting.HostingChecker; +import io.jenkins.infra.repository_permissions_updater.hosting.HostingConfig; +import io.jenkins.infra.repository_permissions_updater.hosting.model.HostingRequest; +import io.jenkins.infra.repository_permissions_updater.hosting.model.VerificationMessage; +import io.jenkins.infra.repository_permissions_updater.hosting.model.Version; import org.apache.commons.lang3.StringUtils; import org.apache.maven.model.License; import org.apache.maven.model.Model; import org.apache.maven.model.Parent; import org.apache.maven.model.Repository; -import org.apache.maven.model.Scm; import org.apache.maven.model.io.xpp3.MavenXpp3Reader; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; import org.kohsuke.github.GHContent; -import org.kohsuke.github.GHFileNotFoundException; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static io.jenkins.infra.repository_permissions_updater.hosting.HostingChecker.LOWEST_JENKINS_VERSION; -import static java.util.regex.Pattern.CASE_INSENSITIVE; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashSet; +import java.util.List; +import java.util.regex.Matcher; + + +public final class MavenVerifierConsumer implements VerifierConsumer { -public class MavenVerifier implements BuildSystemVerifier { private static final int MAX_LENGTH_OF_GROUP_ID_PLUS_ARTIFACT_ID = 100; private static final int MAX_LENGTH_OF_ARTIFACT_ID = 37; - private static final Logger LOGGER = LoggerFactory.getLogger(MavenVerifier.class); + private static final Logger LOGGER = LoggerFactory.getLogger(MavenVerifierConsumer.class); public static final Version LOWEST_PARENT_POM_VERSION = new Version(4, 85); public static final Version PARENT_POM_WITH_JENKINS_VERSION = new Version(2); - public static final String INVALID_POM = "The pom.xml file in the root of the origin repository is not valid"; - public static final String SPECIFY_LICENSE = "Please specify a license in your pom.xml file using the <licenses> tag. See https://maven.apache.org/pom.html#Licenses for more information."; - public static final String MISSING_POM_XML = "No pom.xml found in root of project, if you are using a different build system, or this is not a plugin, you can disregard this message"; + private final GitHub github; - public static final String SHOULD_BE_IO_JENKINS_PLUGINS = "The <groupId> from the pom.xml should be `io.jenkins.plugins` instead of `%s`"; + public MavenVerifierConsumer() throws IOException { + this.github = GitHub.connect(); + } @Override - public void verify(HostingRequest issue, HashSet hostingIssues) throws IOException { - GitHub github = GitHub.connect(); - String forkTo = issue.getNewRepoName(); - String forkFrom = issue.getRepositoryUrl(); - - if(StringUtils.isNotBlank(forkFrom)) { - Matcher m = Pattern.compile("(?:https://github\\.com/)?(\\S+)/(\\S+)", CASE_INSENSITIVE).matcher(forkFrom); - if(m.matches()) { - String owner = m.group(1); - String repoName = m.group(2); - - GHRepository repo = github.getRepository(owner+"/"+repoName); - try { - GHContent pomXml = repo.getFileContent("pom.xml"); - if(pomXml != null && pomXml.isFile()) { - InputStream contents = pomXml.read(); - MavenXpp3Reader reader = new MavenXpp3Reader(); - Model model = reader.read(contents); - - try { - checkArtifactId(model, forkTo, hostingIssues); - checkParentInfoAndJenkinsVersion(model, hostingIssues); - checkName(model, hostingIssues); - checkLicenses(model, hostingIssues); - checkGroupId(model, hostingIssues); - checkRepositories(model, hostingIssues); - checkPluginRepositories(model, hostingIssues); - checkSoftwareConfigurationManagementField(model, hostingIssues); - } catch(Exception e) { - LOGGER.error("Failed looking at pom.xml", e); - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, INVALID_POM)); - } - } - } catch(GHFileNotFoundException e) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.WARNING, MISSING_POM_XML)); - } catch(XmlPullParserException e) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, INVALID_POM)); - } - } else { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingChecker.INVALID_FORK_FROM, forkFrom)); + public void accept(HostingRequest issue, HashSet hostingIssues) { + String forkTo = issue.newRepoName(); + String forkFrom = issue.repositoryUrl(); + if(!StringUtils.isNotBlank(forkFrom)) return; + + Matcher m = HostingConfig.GITHUB_FORK_PATTERN.matcher(forkFrom); + if(!m.matches()) { + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("INVALID_FORK"), forkFrom)); + return; + } + + String owner = m.group(1); + String repoName = m.group(2); + + GHRepository repo = null; + try { + repo = github.getRepository(owner+"/"+repoName); + } catch (IOException e) { + LOGGER.error("Cannot find repository for {}", repoName, e); + } + if (repo == null) { + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("REPOSITORY_CANNOT_BE_FOUND"))); + return; + } + try { + GHContent pomXml = null; + try { + pomXml = repo.getFileContent("pom.xml"); + } catch (IOException e) { + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.WARNING, HostingConfig.RESOURCE_BUNDLE.getString("MISSING_POM_XML"))); + } + if(pomXml == null) { + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.WARNING, HostingConfig.RESOURCE_BUNDLE.getString("MISSING_POM_XML"))); + return; } + if(!pomXml.isFile()) { + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.WARNING, HostingConfig.RESOURCE_BUNDLE.getString("MISSING_POM_XML"))); + return; + } + InputStream contents = null; + try { + contents = pomXml.read(); + } catch (IOException e) { + LOGGER.error("Cannot read pom.xml file", e); + } + MavenXpp3Reader reader = new MavenXpp3Reader(); + Model model = null; + try { + model = reader.read(contents); + } catch (IOException e) { + LOGGER.error("Cannot read maven model", e); + } + if(model == null) { + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("INVALID_POM"))); + return; + } + + try { + checkArtifactId(model, forkTo, hostingIssues); + checkParentInfoAndJenkinsVersion(model, hostingIssues); + checkName(model, hostingIssues); + checkLicenses(model, hostingIssues); + checkGroupId(model, hostingIssues); + checkRepositories(model, hostingIssues); + checkPluginRepositories(model, hostingIssues); + checkSoftwareConfigurationManagementField(model, hostingIssues); + } catch(Exception e) { + LOGGER.error("Failed looking at pom.xml", e); + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("INVALID_POM"))); + } + } catch(XmlPullParserException e) { + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("INVALID_POM"))); } } - @Override public boolean hasBuildFile(HostingRequest issue) throws IOException { return HostingChecker.fileExistsInRepo(issue, "pom.xml"); } + private void checkArtifactId(Model model, String forkTo, HashSet hostingIssues) { try { if(StringUtils.isBlank(forkTo)) { @@ -107,7 +140,7 @@ private void checkArtifactId(Model model, String forkTo, HashSet= MAX_LENGTH_OF_ARTIFACT_ID) { hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "The 'artifactId' `%s` from the pom.xml is incorrect, it must have less than %d characters, currently it has %d characters", artifactId, MAX_LENGTH_OF_ARTIFACT_ID, artifactId.length())); } - + int lengthOfGroupIdAndArtifactId = groupId.length() + artifactId.length(); if (lengthOfGroupIdAndArtifactId >= MAX_LENGTH_OF_GROUP_ID_PLUS_ARTIFACT_ID) { hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, "The 'artifactId' `%s` and 'groupId' `%s` from the pom.xml is incorrect, combined they must have less than %d characters, currently they have %d characters", artifactId, groupId, MAX_LENGTH_OF_GROUP_ID_PLUS_ARTIFACT_ID, lengthOfGroupIdAndArtifactId)); @@ -125,7 +158,7 @@ private void checkArtifactId(Model model, String forkTo, HashSet hostingIssue String groupId = model.getGroupId(); if(StringUtils.isNotBlank(groupId)) { if (!groupId.equals("io.jenkins.plugins")) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, SHOULD_BE_IO_JENKINS_PLUGINS, groupId)); + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("SHOULD_BE_IO_JENKINS_PLUGINS"), groupId)); } } else { Parent parent = model.getParent(); @@ -149,7 +182,7 @@ private void checkGroupId(Model model, HashSet hostingIssue } } catch(Exception e) { LOGGER.error("Error trying to access groupId", e); - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, INVALID_POM)); + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("INVALID_POM"))); } } @@ -165,7 +198,7 @@ private void checkName(Model model, HashSet hostingIssues) } } catch(Exception e) { LOGGER.error("Error trying to access ", e); - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, INVALID_POM)); + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("INVALID_POM"))); } } @@ -215,9 +248,9 @@ private void checkParentInfoAndJenkinsVersion(Model model, HashSet%s` to at least %s in your pom.xml. Take a look at the [baseline recommendations](https://www.jenkins.io/doc/developer/plugin-development/choosing-jenkins-baseline/#currently-recommended-versions).", - jenkinsVersion, LOWEST_JENKINS_VERSION)); + jenkinsVersion, HostingChecker.LOWEST_JENKINS_VERSION)); } } } @@ -231,7 +264,7 @@ private void checkLicenses(Model model, HashSet hostingIssu // first check the pom.xml List licenses = model.getLicenses(); if(licenses.isEmpty()) { - hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, SPECIFY_LICENSE)); + hostingIssues.add(new VerificationMessage(VerificationMessage.Severity.REQUIRED, HostingConfig.RESOURCE_BUNDLE.getString("SPECIFY_LICENSE"))); } } @@ -285,4 +318,5 @@ private void checkSoftwareConfigurationManagementField(Model model, HashSet> { +} diff --git a/src/main/resources/repository-permissions-updater.properties b/src/main/resources/repository-permissions-updater.properties new file mode 100644 index 0000000000..ffac441087 --- /dev/null +++ b/src/main/resources/repository-permissions-updater.properties @@ -0,0 +1,16 @@ +MISSING_LIST_OF_USERS=Missing list of users to authorize in 'GitHub Users to Authorize as Committers' +IT_MUST_MATCH_ARTIFACT_ID=It must match the c (with -plugin added) from your pom.xml. +IT_MUST_MATCH_PLUGIN=It must end in -plugin if hosting request is for a Jenkins plugin. +IT_MUST_LOWERCASE=It must be all lowercase. +IT_MUST_NOT_CONTAIN_JENKINS=It must NOT contain "Jenkins". +IT_MUST_HYPHENS=It must use hyphens ( - ) instead of spaces or camel case. +IT_MUST_NEW_REPO=You must specify the repository name to fork to in 'New Repository Name' field with the following rules: +NEW_REPO_MUST_END='New Repository Name' must end with "-plugin" (disregard if you are not requesting hosting of a plugin) +MISSING_IN_ARTIFACTORY=The following usernames in 'Jenkins project users to have release permission' need to log into [Artifactory](https://repo.jenkins-ci.org/): %s (reports are re-synced hourly, wait to re-check for a bit after logging in) +MISSING_IN_JIRA=The following usernames in 'Jenkins project users to have release permission' need to log into [Jira](https://issues.jenkins.io): %s (reports are re-synced hourly, wait to re-check for a bit after logging in) +INVALID_FORK_FROM=Repository URL '%s' is not a valid GitHub repository (check that you do not have .git at the end, GitHub API doesn't support this). +REPOSITORY_CANNOT_BE_FOUND=The specified repository cannot be found +SHOULD_BE_IO_JENKINS_PLUGINS=The <groupId> from the pom.xml should be `io.jenkins.plugins` instead of `%s` +MISSING_POM_XML=No pom.xml found in root of project, if you are using a different build system, or this is not a plugin, you can disregard this message +SPECIFY_LICENSE=Please specify a license in your pom.xml file using the <licenses> tag. See https://maven.apache.org/pom.html#Licenses for more information. +INVALID_POM=The pom.xml file in the root of the origin repository is not valid \ No newline at end of file