You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@maven.apache.org by "mthmulders (via GitHub)" <gi...@apache.org> on 2023/02/09 15:25:49 UTC

[GitHub] [maven] mthmulders opened a new pull request, #995: [MNG-6869] New flag to verify Maven installation status

mthmulders opened a new pull request, #995:
URL: https://github.com/apache/maven/pull/995

   This pull request adds a few basic checks that users can run to see if they've installed Maven correctly. This includes:
   1. A check if the local Maven repository is usable.
   2. A check if Maven can connect to remote repositories configured in **settings.xml**.
   3. A check if Maven can resolve a pre-existing artifact (to be precise, `org.apache.maven:apache-maven:3.8.6`).
   
   These checks honour **settings.xml** profile activation through `<activeProfiles>`. The artifact resolution check honours any configured mirrors.
   
   The connection check is quite rudimentary, and only functions at the HTTP connection level. Since there is no guarantee that a repository will contain a particular artifact, there's not much more we can do.
   
   The artifact resolution check is a bit more thorough: it checks if there is _any_ way in which Maven can resolve itself. It does so by temporarily using a "dummy" local repository. This is necessary to prevent a "false positive", where the artifact may have existed before. 
   
   We have not added unit tests or integration tests (yet). At some level, they may be useful, but we expect a full integration test to be a significant amount of work that would not provide a lot of value. We're curious to hear your opinions about that trade-off.
   
   ---
   
   Following this checklist to help us incorporate your contribution quickly and easily:
   
    - [X] Make sure there is a [JIRA issue](https://issues.apache.org/jira/browse/MNG) filed
          for the change (usually before you start working on it).  Trivial changes like typos do not
          require a JIRA issue. Your pull request should address just this issue, without
          pulling in other changes.
           --> [MNG-6869](https://issues.apache.org/jira/browse/MNG-6869)
    - [ ] Each commit in the pull request should have a meaningful subject line and body.
    - [X] Format the pull request title like `[MNG-XXX] SUMMARY`,
          where you replace `MNG-XXX` and `SUMMARY` with the appropriate JIRA issue.
    - [ ] Also format the first line of the commit message like `[MNG-XXX] SUMMARY`.
          Best practice is to use the JIRA issue title in both the pull request title and in the first line of the commit message.
    - [X] Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
    - [X] Run `mvn clean verify` to make sure basic checks pass. A more thorough check will
          be performed on your pull request automatically.
    - [X] You have run the [Core IT][core-its] successfully.
   
   If your pull request is about ~20 lines of code you don't need to sign an
   [Individual Contributor License Agreement](https://www.apache.org/licenses/icla.pdf) if you are unsure
   please ask on the developers list.
   
   To make clear that you license your contribution under
   the [Apache License Version 2.0, January 2004](http://www.apache.org/licenses/LICENSE-2.0)
   you have to acknowledge this by using the following check-box.
   
    - [ ] I hereby declare this contribution to be licenced under the [Apache License Version 2.0, January 2004](http://www.apache.org/licenses/LICENSE-2.0)
   
    - [X] In any other case, please file an [Apache Individual Contributor License Agreement](https://www.apache.org/licenses/icla.pdf).
   
   [core-its]: https://maven.apache.org/core-its/core-it-suite/
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] mthmulders commented on a diff in pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "mthmulders (via GitHub)" <gi...@apache.org>.
mthmulders commented on code in PR #995:
URL: https://github.com/apache/maven/pull/995#discussion_r1115692352


##########
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) {

Review Comment:
   The goal of this code is not to explain what is wrong and how the user should fix it. I agree with @rmannibucau that the Aether logs are more suitable for that. What this code wants to do is _tell_ the user they have a problem, without even building a project.
   
   Ideally, in the case of the StackOverflow questions that @rfscholte refers to, we could say "please share the output of `mvn --status`". Their output might look like this for example:
   
   ```
   [INFO] Local repository setup check completed
   [INFO] Connection check for repository 'corporate-repo' at 'https://pkgs.dev.azure.com/big-corporate/project/_packaging/whatever-repository/maven/v1' completed
   Downloading from corporate-repo: https://pkgs.dev.azure.com/big-corporate/project/_packaging/whatever-repository/maven/v1/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6.pom
   Downloading from misconfigured-mirror: https://non-existing.example.com/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6.pom
   [INFO] Artifact resolution check completed
   [INFO]
   [ERROR] The following issues where found
   [ERROR] 1.  Connection to misconfigured-mirror [https://non-existing.example.com/maven2/] not possible. Cause: non-existing.example.com: nodename nor servname provided, or not known
   [ERROR] 2.  Could not find artifact org.apache.maven:apache-maven:pom:3.8.6 in corporate-repo (https://pkgs.dev.azure.com/big-corporate/project/_packaging/whatever-repository/maven/v1)
   [ERROR] 3.  Could not transfer artifact org.apache.maven:apache-maven:pom:3.8.6 from/to misconfigured-mirror (https://non-existing.example.com/maven2/): non-existing.example.com: nodename nor servname provided, or not known
   ```
   
   Even though this doesn't tell the user the solution, it points them in a few directions where they could look. It's a lot more compact than the complete Aether logs - which the user could still inspect, in case this message doesn't point them in the right direction already.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] mthmulders commented on pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "mthmulders (via GitHub)" <gi...@apache.org>.
mthmulders commented on PR #995:
URL: https://github.com/apache/maven/pull/995#issuecomment-1424703182

   > Isnt it something a plugin would be a better location?
   
   The current approach does not need any additional download. I think that's a good thing, as we can now use it to also point the user at issues in their configuration that would prevent artifact resolution. If we moved this to a plugin, we'd need artifact resolution to actually work...
   
   > Can need some basic downloads but would enable to do more checks on the project, better configure the dummy artifact - 3.8.6 or maven cant be hardcoded in case of central "mirroring", it can be forbidden intentionally.
   
   I'm open to suggestions for another publicly available artifact. But in order to "prove" that artifact resolution works, we need something that is publicly available - I think something that at the very least is hosted by Central, and maybe by mirrors. 
   
   > It would also enable to maje checks grow, in particular with coming multilocal repo feature for ex.
   
   More thorough checks could reside in a plugin, but as pointed out before, we can't rely on resolution to work until we've checked that it works. So agreed, additional checks could live in a plugin, but I'm convinced that we'll need a few basic checks (among them artifact resolution and local repository setup) to live in Core.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] rmannibucau commented on a diff in pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "rmannibucau (via GitHub)" <gi...@apache.org>.
rmannibucau commented on code in PR #995:
URL: https://github.com/apache/maven/pull/995#discussion_r1102733350


##########
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) {

Review Comment:
   From my experience this is not testing a random artifact which works but more enabling the aether logs which help, ultimately logging the request to be able to replay it with curl to do the check. Rest is random tests which can easily be proven false positive/negative due to proxies (correct) configuration.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] rmannibucau commented on pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "rmannibucau (via GitHub)" <gi...@apache.org>.
rmannibucau commented on PR #995:
URL: https://github.com/apache/maven/pull/995#issuecomment-1425415034

   @michael-o or just enhance the CLI with a small SPI to enable to add custom command with extensions so this does not need a noop at all and is purely optional and in a dedicated module which can get a real configuration and enhancements later?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] gnodet commented on a diff in pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "gnodet (via GitHub)" <gi...@apache.org>.
gnodet commented on code in PR #995:
URL: https://github.com/apache/maven/pull/995#discussion_r1121345217


##########
maven-core/src/main/java/org/apache/maven/internal/impl/DefaultArtifactCoordinate.java:
##########
@@ -30,11 +31,11 @@
  * A wrapper class around a maven resolver artifact.
  */
 public class DefaultArtifactCoordinate implements ArtifactCoordinate {
-    private final @Nonnull AbstractSession session;

Review Comment:
   Can this change be removed from this PR ? It's completely unrelated. 
   Also, if we do change that, other classes such as `DefaultArtifact` and maybe others use the same mechanism and should be changed at the same time.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] rfscholte commented on a diff in pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "rfscholte (via GitHub)" <gi...@apache.org>.
rfscholte commented on code in PR #995:
URL: https://github.com/apache/maven/pull/995#discussion_r1101903050


##########
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) {

Review Comment:
   I think this is 1 step too much. Is this the only way to verify if a remote repository is accessible? If you need to use an explicit file instead of a directory, is it possible to use http HEAD ?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] michael-o commented on a diff in pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "michael-o (via GitHub)" <gi...@apache.org>.
michael-o commented on code in PR #995:
URL: https://github.com/apache/maven/pull/995#discussion_r1102407885


##########
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);
+    }

Review Comment:
   Please see:
   * https://github.com/apache/maven/pull/419
   * https://issues.apache.org/jira/browse/MNG-4645



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] rmannibucau commented on pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "rmannibucau (via GitHub)" <gi...@apache.org>.
rmannibucau commented on PR #995:
URL: https://github.com/apache/maven/pull/995#issuecomment-1424380402

   Isnt it something a plugin would be a better location? Can need some basic downloads but would enable to do more checks on the project, better configure the dummy artifact - 3.8.6 or maven cant be hardcoded in case of central "mirroring", it can be forbidden intentionally.
   It would also enable to maje checks grow, in particular with coming multilocal repo feature for ex.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] mthmulders commented on a diff in pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "mthmulders (via GitHub)" <gi...@apache.org>.
mthmulders commented on code in PR #995:
URL: https://github.com/apache/maven/pull/995#discussion_r1101924124


##########
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);
+    }

Review Comment:
   I'm afraid I don't really understand what you're saying here. Care to elaborate a bit more?



##########
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",
+                    repository.getId(), repository.getUrl());
+            return Optional.of(message);
+        }
+    }
+
+    private Optional<String> verifyConnectionUsingTransport(
+            final Transporter transporter, final RemoteRepository remoteRepository, final String artifactPath) {
+        try {
+            final GetTask task = new GetTask(URI.create(artifactPath));
+            transporter.get(task);
+            return Optional.empty();
+        } catch (final Exception e) {
+            return classifyException(remoteRepository, e);
+        }
+    }
+
+    private Optional<String> classifyException(final RemoteRepository remoteRepository, final Exception e) {
+        final String message = e.getMessage();
+        final String repositoryId = remoteRepository.getId();
+        final String repositoryUrl = remoteRepository.getUrl();
+        final String repository = String.format("%s [%s]", repositoryId, repositoryUrl);
+
+        final boolean notFound = StringUtils.contains(message, "status code: 404");
+        final boolean unauthorized = StringUtils.contains(message, "status code: 401");
+        final boolean forbidden = StringUtils.contains(message, "status code: 403");

Review Comment:
   Agreed. This hurts a lot.
   
   Yes, for now, the check only works with HTTP. We used to have an explicit check for that in an earlier version of the code, but we no longer needed that. We'll revisit this particular piece.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] mthmulders commented on pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "mthmulders (via GitHub)" <gi...@apache.org>.
mthmulders commented on PR #995:
URL: https://github.com/apache/maven/pull/995#issuecomment-1441813122

   Personally, I don't see much value in moving the code to a new module. If I understand correctly, we'd be moving this code to a different module inside the same Git repository, only to introduce some complexity in maven-embedder to load that exact same code. Also, this PR doesn't pull in new dependencies to maven-embedder, which we could have avoided by moving the code elsewhere, so I really fail to see the benefits of such a refactoring.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] rmannibucau commented on pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "rmannibucau (via GitHub)" <gi...@apache.org>.
rmannibucau commented on PR #995:
URL: https://github.com/apache/maven/pull/995#issuecomment-1441818193

   >  so I really fail to see the benefits of such a refactoring
   
   1. not deliver it by default
   2. not add specific options by default
   3. not make it grow to actually validate the installation completely and properly (if it becomes a feature the impl must be way more complex)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] michael-o commented on a diff in pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "michael-o (via GitHub)" <gi...@apache.org>.
michael-o commented on code in PR #995:
URL: https://github.com/apache/maven/pull/995#discussion_r1101671449


##########
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()));

Review Comment:
   No need for toString() here



##########
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.

Review Comment:
   directory



##########
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);
+    }

Review Comment:
   This can be deceving when blocked OR when Central is moved out of Core and removed from settings.



##########
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",
+                    repository.getId(), repository.getUrl());
+            return Optional.of(message);
+        }
+    }
+
+    private Optional<String> verifyConnectionUsingTransport(
+            final Transporter transporter, final RemoteRepository remoteRepository, final String artifactPath) {
+        try {
+            final GetTask task = new GetTask(URI.create(artifactPath));
+            transporter.get(task);
+            return Optional.empty();
+        } catch (final Exception e) {
+            return classifyException(remoteRepository, e);
+        }
+    }
+
+    private Optional<String> classifyException(final RemoteRepository remoteRepository, final Exception e) {
+        final String message = e.getMessage();
+        final String repositoryId = remoteRepository.getId();
+        final String repositoryUrl = remoteRepository.getUrl();
+        final String repository = String.format("%s [%s]", repositoryId, repositoryUrl);
+
+        final boolean notFound = StringUtils.contains(message, "status code: 404");
+        final boolean unauthorized = StringUtils.contains(message, "status code: 401");
+        final boolean forbidden = StringUtils.contains(message, "status code: 403");

Review Comment:
   Uh, that really hurts. No better way? You assume that transport is always HTTP? Either in Wagon or Resolver there is a classify method, maybe that can help...



##########
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:
   single quotes



##########
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:
   Put the placeholders in single quotes



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] mthmulders commented on a diff in pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "mthmulders (via GitHub)" <gi...@apache.org>.
mthmulders commented on code in PR #995:
URL: https://github.com/apache/maven/pull/995#discussion_r1101894882


##########
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.

Review Comment:
   Fixed



##########
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()));

Review Comment:
   Fixed



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] mthmulders commented on a diff in pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "mthmulders (via GitHub)" <gi...@apache.org>.
mthmulders commented on code in PR #995:
URL: https://github.com/apache/maven/pull/995#discussion_r1101938159


##########
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) {

Review Comment:
   > [...] 1 step too much [...]
   
   Just to verify: are you referring to the fact that this code actually _downloads_ a file (e.g., it could've been an HTTP HEAD request), or to the fact that the `--status` verifies if artifact resolution works?
   
   In the case of the former: is a remote repository required to support an HTTP HEAD request? Could we rely on the fact that if HTTP HEAD is OK, HTTP GET will be OK, too?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] mthmulders commented on a diff in pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "mthmulders (via GitHub)" <gi...@apache.org>.
mthmulders commented on code in PR #995:
URL: https://github.com/apache/maven/pull/995#discussion_r1115676030


##########
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",
+                    repository.getId(), repository.getUrl());
+            return Optional.of(message);
+        }
+    }
+
+    private Optional<String> verifyConnectionUsingTransport(
+            final Transporter transporter, final RemoteRepository remoteRepository, final String artifactPath) {
+        try {
+            final GetTask task = new GetTask(URI.create(artifactPath));
+            transporter.get(task);
+            return Optional.empty();
+        } catch (final Exception e) {
+            return classifyException(remoteRepository, e);
+        }
+    }
+
+    private Optional<String> classifyException(final RemoteRepository remoteRepository, final Exception e) {
+        final String message = e.getMessage();
+        final String repositoryId = remoteRepository.getId();
+        final String repositoryUrl = remoteRepository.getUrl();
+        final String repository = String.format("%s [%s]", repositoryId, repositoryUrl);
+
+        final boolean notFound = StringUtils.contains(message, "status code: 404");
+        final boolean unauthorized = StringUtils.contains(message, "status code: 401");
+        final boolean forbidden = StringUtils.contains(message, "status code: 403");

Review Comment:
   We now use [`transport#classify()`](https://maven.apache.org/resolver/apidocs/org/eclipse/aether/spi/connector/transport/Transporter.html#classify(java.lang.Throwable)). It does not provide a lot of detail, but it's transport-agnostic AFAICS, which is definitely a plus.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] mthmulders commented on a diff in pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "mthmulders (via GitHub)" <gi...@apache.org>.
mthmulders commented on code in PR #995:
URL: https://github.com/apache/maven/pull/995#discussion_r1115670514


##########
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);
+    }

Review Comment:
   The check for remote repository connectivity no longer makes a difference between "central" or another remote repository.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] mthmulders commented on a diff in pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "mthmulders (via GitHub)" <gi...@apache.org>.
mthmulders commented on code in PR #995:
URL: https://github.com/apache/maven/pull/995#discussion_r1189921909


##########
maven-core/src/main/java/org/apache/maven/internal/impl/DefaultArtifactCoordinate.java:
##########
@@ -30,11 +31,11 @@
  * A wrapper class around a maven resolver artifact.
  */
 public class DefaultArtifactCoordinate implements ArtifactCoordinate {
-    private final @Nonnull AbstractSession session;

Review Comment:
   Sure! I've just pushed a commit to undo this part.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] rmannibucau commented on pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "rmannibucau (via GitHub)" <gi...@apache.org>.
rmannibucau commented on PR #995:
URL: https://github.com/apache/maven/pull/995#issuecomment-1424752367

   > I'm open to suggestions for another publicly available artifact. But in order to "prove" that artifact resolution works, we need something that is publicly available - I think something that at the very least is hosted by Central, and maybe by mirrors.
   
   
   Point is none can be hardcoded, cause we dont know companies mirrors.
   This is why it fits a plugin better IMO (wouldnt be great to have that in maven imo).
   
   If you dont like the plugin, what about doing a `mvn-install-checker` project or extension which can grow and be downloaded at need.
   Thing is this code requires config and ultimately shouldnt be a command but more explicit error messages when it fails IMHO.
   Current flavor looks like not scaling well in time/hard to maintain in a relevant manner and is already biased due to profiles so a checker or plugin looks iso without the pitfall to impact core directly when you ll need to check proxy certs, proxy protocol, proxy passwords, sftp cert for deployment, properties typos in settings etc....(this is where this command/plugin/helper will lead if adopted).


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] mthmulders commented on a diff in pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "mthmulders (via GitHub)" <gi...@apache.org>.
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



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] michael-o commented on pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "michael-o (via GitHub)" <gi...@apache.org>.
michael-o commented on PR #995:
URL: https://github.com/apache/maven/pull/995#issuecomment-1425375596

   Why not move it into a module within Maven Core which could be no-op of this default code. It could be easily exchanged?!


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] mthmulders commented on pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "mthmulders (via GitHub)" <gi...@apache.org>.
mthmulders commented on PR #995:
URL: https://github.com/apache/maven/pull/995#issuecomment-1441842039

   > 1. not deliver it by default
   
   That would completely defy the utility and value of this feature. I do want a clean Maven installation to be able to do a few basic checks. That is exactly the idea as laid out in [MNG-6869](https://issues.apache.org/jira/browse/MNG-6869), if you ask me.
   
   Anything that requires a separate download (a plugin, or a helper script, or extension, or ....) and we do not ship with Apache Maven itself is completely opposite to the original idea, and would therefore be a `-1` from me.
   
   > 2. not add specific options by default
   
   I'm sorry, I don't understand what you mean here.
   
   > 3. not make it grow to actually validate the installation completely and properly (if it becomes a feature the impl must be way more complex)
   
   If it were to become much bigger, we could always move it out later. As long as we can ship it with Apache Maven by default (see point 1).


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [maven] rfscholte commented on a diff in pull request #995: [MNG-6869] New flag to verify Maven installation status

Posted by "rfscholte (via GitHub)" <gi...@apache.org>.
rfscholte commented on code in PR #995:
URL: https://github.com/apache/maven/pull/995#discussion_r1102729377


##########
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) {

Review Comment:
   It depends on what you define as "artifact resolution works". I'm not interested if the implementation is correct. I wish there's a way to only confirm that the connection works. It is about returning questions on StackOverflow that say Maven can't download things, but it is just that it cannot reach a repository. Possible rootcauses are missing proxies (or misconfigured) and misconfigured mirrors. Maybe @michael-o or @cstamas can think of a low-level reliable way to conform this.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@maven.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org