[gitlab] try and scan remote servers for a gitlab instance

IDEA-320465 Fixed

GitOrigin-RevId: 843b3b005475ff3ef1c25c09e6d2f3557447f6bc
This commit is contained in:
Ivan Semenov
2023-05-16 13:28:19 +02:00
committed by intellij-monorepo-bot
parent 8cd1016e97
commit 1ca8b7cc81
8 changed files with 118 additions and 35 deletions

View File

@@ -1,9 +1,13 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package git4idea.remote.hosting
import com.intellij.collaboration.api.ServerPath
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.NlsSafe
import com.intellij.util.UriUtil
import com.intellij.util.io.URLUtil
import git4idea.remote.GitRemoteUrlCoordinates
import org.jetbrains.annotations.ApiStatus
import java.net.URI
import java.net.URISyntaxException
@@ -45,7 +49,7 @@ object GitHostingUrlUtil {
fun match(serverUri: URI, gitRemoteUrl: String): Boolean {
val remoteUri = getUriFromRemoteUrl(gitRemoteUrl) ?: return false
if(!serverUri.host.equals(remoteUri.host, true)) return false
if (!serverUri.host.equals(remoteUri.host, true)) return false
if (serverUri.path != null && serverUri.path != "/") {
val remoteUriPath = remoteUri.path ?: return false
@@ -53,4 +57,34 @@ object GitHostingUrlUtil {
}
return true
}
@ApiStatus.Internal
suspend fun <S : ServerPath> findServerAt(log: Logger, coordinates: GitRemoteUrlCoordinates, serverCheck: suspend (URI) -> S?): S? {
val uri = getUriFromRemoteUrl(coordinates.url)
log.debug("Extracted URI $uri from remote ${coordinates.url}")
if (uri == null) return null
val host = uri.host ?: return null
val path = uri.path ?: return null
val pathParts = path.removePrefix("/").split('/').takeIf { it.size >= 2 } ?: return null
val serverSuffix = if (pathParts.size == 2) null else pathParts.subList(0, pathParts.size - 2).joinToString("/")
for (serverUri in listOf(
URI(URLUtil.HTTPS_PROTOCOL, host, serverSuffix, null),
URI(URLUtil.HTTP_PROTOCOL, host, serverSuffix, null),
URI(URLUtil.HTTP_PROTOCOL, null, host, 8080, serverSuffix, null, null)
)) {
log.debug("Looking for server at $serverUri")
try {
val server = serverCheck(serverUri)
if (server != null) {
log.debug("Found server at $serverUri")
return server
}
}
catch (ignored: Throwable) {
}
}
return null
}
}

View File

@@ -4,8 +4,10 @@ package git4idea.remote.hosting
import com.intellij.collaboration.api.ServerPath
import com.intellij.dvcs.repo.VcsRepositoryManager
import com.intellij.dvcs.repo.VcsRepositoryMappingListener
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.util.io.URLUtil
import git4idea.remote.GitRemoteUrlCoordinates
import git4idea.repo.GitRepository
import git4idea.repo.GitRepositoryChangeListener
@@ -14,6 +16,8 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
import org.jetbrains.annotations.ApiStatus
import java.net.URI
fun gitRemotesFlow(project: Project): Flow<Set<GitRemoteUrlCoordinates>> =
callbackFlow {
@@ -69,7 +73,12 @@ fun <S : ServerPath> GitRemotesFlow.discoverServers(knownServersFlow: Flow<Set<S
remotes.chunked(parallelism).forEach { remotesChunk ->
remotesChunk.map { remote ->
async {
val server = checkForDedicatedServer(remote)
val server = try {
checkForDedicatedServer(remote)
}
catch (e: Exception) {
null
}
if (server != null) send(server)
}
}.awaitAll()

View File

@@ -26,8 +26,12 @@ class GHHostedRepositoriesManager(project: Project, cs: CoroutineScope) : Hosted
mutableSetOf(GithubServerPath.DEFAULT_SERVER) + accounts.map { it.server }
}.distinctUntilChanged()
val discoveredServersFlow = gitRemotesFlow.discoverServers(accountsServersFlow) {
checkForDedicatedServer(it)
val discoveredServersFlow = gitRemotesFlow.discoverServers(accountsServersFlow) { remote ->
GitHostingUrlUtil.findServerAt(LOG, remote) {
val server = GithubServerPath.from(it.toString())
val metadata = service<GHEnterpriseServerMetadataLoader>().loadMetadata(server).await()
if (metadata != null) server else null
}
}.runningFold(emptySet<GithubServerPath>()) { accumulator, value ->
accumulator + value
}.distinctUntilChanged()
@@ -46,33 +50,6 @@ class GHHostedRepositoriesManager(project: Project, cs: CoroutineScope) : Hosted
override val knownRepositoriesState: StateFlow<Set<GHGitRepositoryMapping>> =
knownRepositoriesFlow.stateIn(cs, getStateSharingStartConfig(), emptySet())
private suspend fun checkForDedicatedServer(remote: GitRemoteUrlCoordinates): GithubServerPath? {
val uri = GitHostingUrlUtil.getUriFromRemoteUrl(remote.url)
LOG.debug("Extracted URI $uri from remote ${remote.url}")
if (uri == null) return null
val host = uri.host ?: return null
val path = uri.path ?: return null
val pathParts = path.removePrefix("/").split('/').takeIf { it.size >= 2 } ?: return null
val serverSuffix = if (pathParts.size == 2) null else pathParts.subList(0, pathParts.size - 2).joinToString("/", "/")
for (server in listOf(
GithubServerPath(false, host, null, serverSuffix),
GithubServerPath(true, host, null, serverSuffix),
GithubServerPath(true, host, 8080, serverSuffix)
)) {
LOG.debug("Looking for GHE server at $server")
try {
service<GHEnterpriseServerMetadataLoader>().loadMetadata(server).await()
LOG.debug("Found GHE server at $server")
return server
}
catch (ignored: Throwable) {
}
}
return null
}
companion object {
private val LOG = logger<GHHostedRepositoriesManager>()

View File

@@ -22,6 +22,8 @@
<applicationService serviceImplementation="org.jetbrains.plugins.gitlab.authentication.accounts.GitLabPersistentAccounts"/>
<applicationService serviceInterface="org.jetbrains.plugins.gitlab.authentication.accounts.GitLabAccountManager"
serviceImplementation="org.jetbrains.plugins.gitlab.authentication.accounts.PersistentGitLabAccountManager"/>
<applicationService serviceInterface="org.jetbrains.plugins.gitlab.GitLabServersManager"
serviceImplementation="org.jetbrains.plugins.gitlab.CachingGitLabServersManager"/>
<projectService serviceInterface="org.jetbrains.plugins.gitlab.GitLabProjectsManager"
serviceImplementation="org.jetbrains.plugins.gitlab.GitLabProjectsManagerImpl"/>

View File

@@ -4,9 +4,7 @@ package org.jetbrains.plugins.gitlab
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import git4idea.remote.hosting.HostedGitRepositoriesManager
import git4idea.remote.hosting.gitRemotesFlow
import git4idea.remote.hosting.mapToServers
import git4idea.remote.hosting.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import org.jetbrains.plugins.gitlab.api.GitLabServerPath
@@ -24,7 +22,21 @@ internal class GitLabProjectsManagerImpl(project: Project, cs: CoroutineScope) :
mutableSetOf(GitLabServerPath.DEFAULT_SERVER) + accounts.map { it.server }
}.distinctUntilChanged()
val knownRepositoriesFlow = gitRemotesFlow.mapToServers(accountsServersFlow) { server, remote ->
val discoveredServersFlow = gitRemotesFlow.discoverServers(accountsServersFlow) { remote ->
GitHostingUrlUtil.findServerAt(LOG, remote) {
val server = GitLabServerPath(it.toString())
val isGitLabServer = service<GitLabServersManager>().checkIsGitLabServer(server)
if (isGitLabServer) server else null
}
}.runningFold(emptySet<GitLabServerPath>()) { accumulator, value ->
accumulator + value
}.distinctUntilChanged()
val serversFlow = accountsServersFlow.combine(discoveredServersFlow) { servers1, servers2 ->
servers1 + servers2
}
val knownRepositoriesFlow = gitRemotesFlow.mapToServers(serversFlow) { server, remote ->
GitLabProjectMapping.create(server, remote)
}.onEach {
LOG.debug("New list of known repos: $it")

View File

@@ -0,0 +1,24 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gitlab
import kotlinx.coroutines.*
import org.jetbrains.plugins.gitlab.api.GitLabApiImpl
import org.jetbrains.plugins.gitlab.api.GitLabServerPath
import org.jetbrains.plugins.gitlab.api.request.checkIsGitLabServer
import java.util.concurrent.ConcurrentHashMap
interface GitLabServersManager {
suspend fun checkIsGitLabServer(server: GitLabServerPath): Boolean
}
internal class CachingGitLabServersManager(private val cs: CoroutineScope) : GitLabServersManager {
private val cache = ConcurrentHashMap<GitLabServerPath, Deferred<Boolean>>()
override suspend fun checkIsGitLabServer(server: GitLabServerPath): Boolean =
cache.getOrPut(server) {
cs.async(Dispatchers.IO + CoroutineName("GitLab Server metadata loader")) {
GitLabApiImpl().checkIsGitLabServer(server)
}
}.await()
}

View File

@@ -31,6 +31,8 @@ class GitLabApiImpl private constructor(httpHelper: HttpApiHelper)
constructor(tokenSupplier: () -> String) : this(httpHelper(tokenSupplier))
constructor() : this(httpHelper())
companion object {
private fun httpHelper(tokenSupplier: () -> String): HttpApiHelper {
val authConfigurer = object : AuthorizationConfigurer() {
@@ -43,6 +45,13 @@ class GitLabApiImpl private constructor(httpHelper: HttpApiHelper)
return HttpApiHelper(logger = logger<GitLabApi>(),
requestConfigurer = requestConfigurer)
}
private fun httpHelper(): HttpApiHelper {
val requestConfigurer = CompoundRequestConfigurer(RequestTimeoutConfigurer(),
CommonHeadersConfigurer())
return HttpApiHelper(logger = logger<GitLabApi>(),
requestConfigurer = requestConfigurer)
}
}
}

View File

@@ -0,0 +1,16 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gitlab.api.request
import com.intellij.collaboration.util.resolveRelative
import org.jetbrains.plugins.gitlab.api.GitLabApi
import org.jetbrains.plugins.gitlab.api.GitLabServerPath
import java.net.http.HttpResponse
suspend fun GitLabApi.checkIsGitLabServer(server: GitLabServerPath): Boolean {
val uri = server.restApiUri.resolveRelative("metadata")
val request = request(uri).GET().build()
val response = sendAndAwaitCancellable(request, HttpResponse.BodyHandlers.discarding())
return response.headers().map().keys.any {
it.contains("gitlab", true)
}
}