[ https://issues.apache.org/jira/browse/MNG-6869?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17686705#comment-17686705 ]
ASF GitHub Bot commented on MNG-6869: ------------------------------------- mthmulders commented on code in PR #995: URL: https://github.com/apache/maven/pull/995#discussion_r1101895130 ########## maven-embedder/src/main/java/org/apache/maven/cli/MavenStatusCommand.java: ########## @@ -0,0 +1,217 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.cli; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.maven.api.ArtifactCoordinate; +import org.apache.maven.api.Session; +import org.apache.maven.api.services.ArtifactResolver; +import org.apache.maven.api.services.ArtifactResolverException; +import org.apache.maven.api.services.ArtifactResolverResult; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.bridge.MavenRepositorySystem; +import org.apache.maven.execution.DefaultMavenExecutionResult; +import org.apache.maven.execution.MavenExecutionRequest; +import org.apache.maven.execution.MavenExecutionRequestPopulationException; +import org.apache.maven.execution.MavenExecutionRequestPopulator; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory; +import org.apache.maven.internal.impl.DefaultArtifactCoordinate; +import org.apache.maven.internal.impl.DefaultSession; +import org.apache.maven.internal.impl.DefaultSessionFactory; +import org.apache.maven.session.scope.internal.SessionScope; +import org.codehaus.plexus.PlexusContainer; +import org.codehaus.plexus.component.repository.exception.ComponentLookupException; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.resolution.ArtifactResolutionException; +import org.eclipse.aether.resolution.ArtifactResult; +import org.eclipse.aether.transfer.ArtifactNotFoundException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MavenStatusCommand { + private static final Logger LOGGER = LoggerFactory.getLogger(MavenStatusCommand.class); + + /** + * In order to verify artifacts can be downloaded from the remote repositories we want to resolve an actual + * artifact. The Apache Maven artifact was chosen as it eventually, be it by proxy, mirror or directly, will be + * gathered from the central repository. The version is chosen arbitrarily since any listed should work. + */ + public static final Artifact APACHE_MAVEN_ARTIFACT = + new DefaultArtifact("org.apache.maven", "apache-maven", null, "pom", "3.8.6"); + + private final MavenExecutionRequestPopulator mavenExecutionRequestPopulator; + private final ArtifactResolver artifactResolver; + private final RemoteRepositoryConnectionVerifier remoteRepositoryConnectionVerifier; + private final DefaultSessionFactory defaultSessionFactory; + private final DefaultRepositorySystemSessionFactory repoSession; + private final MavenRepositorySystem repositorySystem; + private final PlexusContainer container; + private final SessionScope sessionScope; + private Path tempLocalRepository; + + public MavenStatusCommand(final PlexusContainer container) throws ComponentLookupException { + this.container = container; + this.remoteRepositoryConnectionVerifier = new RemoteRepositoryConnectionVerifier(container); + this.mavenExecutionRequestPopulator = container.lookup(MavenExecutionRequestPopulator.class); + this.artifactResolver = container.lookup(ArtifactResolver.class); + this.defaultSessionFactory = container.lookup(DefaultSessionFactory.class); + this.repoSession = container.lookup(DefaultRepositorySystemSessionFactory.class); + this.sessionScope = container.lookup(SessionScope.class); + this.repositorySystem = container.lookup(MavenRepositorySystem.class); + } + + public List<String> verify(final MavenExecutionRequest cliRequest) throws MavenExecutionRequestPopulationException { + final MavenExecutionRequest mavenExecutionRequest = mavenExecutionRequestPopulator.populateDefaults(cliRequest); + + final ArtifactRepository localRepository = cliRequest.getLocalRepository(); + + final List<String> localRepositoryIssues = + verifyLocalRepository(Paths.get(URI.create(localRepository.getUrl()))); + + // We overwrite the local repository with a temporary folder to avoid using a cached version of the artifact. + setTemporaryLocalRepositoryPathOnRequest(cliRequest); + + final List<String> remoteRepositoryIssues = + verifyRemoteRepositoryConnections(cliRequest.getRemoteRepositories(), mavenExecutionRequest); + final List<String> artifactResolutionIssues = verifyArtifactResolution(mavenExecutionRequest); + + cleanupTempFiles(); + + // Collect all issues into a single list + return Stream.of(localRepositoryIssues, remoteRepositoryIssues, artifactResolutionIssues) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + + private void cleanupTempFiles() { + if (tempLocalRepository != null) { + try (Stream<Path> files = Files.walk(tempLocalRepository)) { + files.sorted(Comparator.reverseOrder()) // Sort in reverse order so that directories are deleted last + .map(Path::toFile) + .forEach(File::delete); + } catch (IOException ioe) { + LOGGER.debug("Failed to delete temporary local repository", ioe); + } + } + } + + private void setTemporaryLocalRepositoryPathOnRequest(final MavenExecutionRequest request) { + try { + tempLocalRepository = Files.createTempDirectory("mvn-status").toAbsolutePath(); + request.setLocalRepositoryPath(tempLocalRepository.toString()); + request.setLocalRepository(repositorySystem.createLocalRepository(request, tempLocalRepository.toFile())); + } catch (Exception ex) { + LOGGER.debug("Could not create temporary local repository", ex); + LOGGER.warn("Artifact resolution test is less accurate as it may use earlier resolution results."); + } + } + + private List<String> verifyRemoteRepositoryConnections( + final List<ArtifactRepository> remoteRepositories, final MavenExecutionRequest mavenExecutionRequest) { + final List<String> issues = new ArrayList<>(); + + for (ArtifactRepository remoteRepository : remoteRepositories) { + final RepositorySystemSession repositorySession = repoSession.newRepositorySession(mavenExecutionRequest); + remoteRepositoryConnectionVerifier + .verifyConnectionToRemoteRepository(repositorySession, remoteRepository) + .ifPresent(issues::add); + } + + return issues; + } + + private List<String> verifyArtifactResolution(final MavenExecutionRequest mavenExecutionRequest) { + final Session session = this.defaultSessionFactory.getSession(new MavenSession( + container, + repoSession.newRepositorySession(mavenExecutionRequest), + mavenExecutionRequest, + new DefaultMavenExecutionResult())); + + sessionScope.enter(); + try { + sessionScope.seed(DefaultSession.class, (DefaultSession) session); + + ArtifactCoordinate artifactCoordinate = new DefaultArtifactCoordinate(session, APACHE_MAVEN_ARTIFACT); + ArtifactResolverResult resolverResult = + artifactResolver.resolve(session, Collections.singleton(artifactCoordinate)); + + resolverResult + .getArtifacts() + .forEach((key, value) -> + LOGGER.debug("Successfully resolved {} to {}", key.toString(), value.toString())); + + return Collections.emptyList(); + } catch (ArtifactResolverException are) { + return extractIssuesFromArtifactResolverException(are); + } finally { + sessionScope.exit(); + LOGGER.info("Artifact resolution check completed"); + } + } + + private List<String> extractIssuesFromArtifactResolverException(final Exception exception) { + final boolean isArtifactResolutionException = exception.getCause() instanceof ArtifactResolutionException; + if (isArtifactResolutionException) { + final ArtifactResolutionException are = (ArtifactResolutionException) exception.getCause(); + return are.getResults().stream() + .map(ArtifactResult::getExceptions) + .flatMap(List::stream) + .map(ArtifactNotFoundException.class::cast) + .map(Throwable::getMessage) + .collect(Collectors.toList()); + } else { + return Collections.singletonList(exception.getMessage()); + } + } + + private List<String> verifyLocalRepository(final Path localRepositoryPath) { + final List<String> issues = new ArrayList<>(); + + if (!Files.isDirectory(localRepositoryPath)) { + issues.add(String.format("Local repository path %s is not a directory.", localRepositoryPath)); + } + + if (!Files.isReadable(localRepositoryPath)) { + issues.add(String.format("No read permissions on local repository %s.", localRepositoryPath)); + } + + if (!Files.isWritable(localRepositoryPath)) { + issues.add(String.format("No write permissions on local repository %s.", localRepositoryPath)); + } + Review Comment: Fixed ########## maven-embedder/src/main/java/org/apache/maven/cli/RemoteRepositoryConnectionVerifier.java: ########## @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.cli; + +import java.net.URI; +import java.util.Optional; + +import org.apache.commons.lang3.StringUtils; +import org.apache.maven.RepositoryUtils; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.codehaus.plexus.PlexusContainer; +import org.codehaus.plexus.component.repository.exception.ComponentLookupException; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.spi.connector.transport.GetTask; +import org.eclipse.aether.spi.connector.transport.Transporter; +import org.eclipse.aether.spi.connector.transport.TransporterProvider; +import org.eclipse.aether.transfer.NoTransporterException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Helper class to verify connection to a remote repository. + */ +public class RemoteRepositoryConnectionVerifier { + private static final Artifact APACHE_MAVEN_ARTIFACT = MavenStatusCommand.APACHE_MAVEN_ARTIFACT; + private final Logger logger; + private final TransporterProvider transporterProvider; + + public RemoteRepositoryConnectionVerifier(final PlexusContainer container) throws ComponentLookupException { + this.logger = LoggerFactory.getILoggerFactory().getLogger(RemoteRepositoryConnectionVerifier.class.getName()); + this.transporterProvider = container.lookup(TransporterProvider.class); + } + + private boolean isCentralOrMirrorOfCentral(final RemoteRepository remoteRepository) { + return "central".equals(remoteRepository.getId()) + || remoteRepository.getMirroredRepositories().stream() + .map(RemoteRepository::getId) + .anyMatch("central"::equals); + } + + public Optional<String> verifyConnectionToRemoteRepository( + final RepositorySystemSession session, final ArtifactRepository artifactRepository) { + final RemoteRepository repository = RepositoryUtils.toRepo(artifactRepository); + + final String artifactPath; + + if (isCentralOrMirrorOfCentral(repository)) { + // We can be sure the Apache Maven artifact should be resolvable. + artifactPath = artifactRepository.getLayout().pathOf(RepositoryUtils.toArtifact(APACHE_MAVEN_ARTIFACT)); + } else { + // We cannot be sure about any artifact that lives here. + artifactPath = ""; + } + + try { + final Transporter transporter = transporterProvider.newTransporter(session, repository); + final Optional<String> maybeIssue = verifyConnectionUsingTransport(transporter, repository, artifactPath); + + if (!maybeIssue.isPresent()) { + logger.info("Connection check for {} [{}] completed", repository.getId(), repository.getUrl()); + } + + return maybeIssue; + } catch (final NoTransporterException nte) { + final String message = String.format( + "There is no compatible transport for remote repository %s with location %s", Review Comment: Fixed > New flag to verify the status > ----------------------------- > > Key: MNG-6869 > URL: https://issues.apache.org/jira/browse/MNG-6869 > Project: Maven > Issue Type: New Feature > Reporter: Robert Scholte > Assignee: Maarten Mulders > Priority: Major > > While working on INFRA-19861 we had issues with invalid changes in the > settings.xml. > This was detected too late. After installation {{mvn --version}} is called, > but it will only show the version of Maven. > It would be better to have a flag that verifies it a bit more: > - can Maven read/write to the local repository > - can Maven access all predefined repositories? (does the proxy work?) > This gives a much better feedback if Maven can do its job. > Current workaround: call something like {{mvn help:evaluate > -Dexpression=settings.localRepository -q -DforceStdout}} -- This message was sent by Atlassian Jira (v8.20.10#820010)