diff --git a/components/ide/jetbrains/backend-plugin/gradle.properties b/components/ide/jetbrains/backend-plugin/gradle.properties index fb3e88059f4700..d355d2740a4274 100644 --- a/components/ide/jetbrains/backend-plugin/gradle.properties +++ b/components/ide/jetbrains/backend-plugin/gradle.properties @@ -12,7 +12,7 @@ pluginUntilBuild=213.* pluginVerifierIdeVersions=2021.3.1 # IntelliJ Platform Properties -> https://github.com/JetBrains/gradle-intellij-plugin#intellij-platform-properties platformType=IU -platformVersion=213-EAP-SNAPSHOT +platformVersion=213.6777.52 platformDownloadSources=true # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 diff --git a/components/ide/jetbrains/backend-plugin/launch-dev-server.sh b/components/ide/jetbrains/backend-plugin/launch-dev-server.sh index 82d62e6ad3f25a..4508b37315d45f 100755 --- a/components/ide/jetbrains/backend-plugin/launch-dev-server.sh +++ b/components/ide/jetbrains/backend-plugin/launch-dev-server.sh @@ -3,6 +3,9 @@ # Licensed under the GNU Affero General Public License (AGPL). # See License-AGPL.txt in the project root for license information. +set -e +set -o pipefail + TEST_BACKEND_DIR=/workspace/ide-backend if [ ! -d "$TEST_BACKEND_DIR" ]; then mkdir -p $TEST_BACKEND_DIR diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodBranding.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodBranding.kt new file mode 100644 index 00000000000000..0c84243d5f70a1 --- /dev/null +++ b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodBranding.kt @@ -0,0 +1,41 @@ +// Copyright (c) 2022 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +package io.gitpod.jetbrains.remote + +import com.intellij.openapi.components.service +import com.intellij.remoteDev.customization.GatewayBranding +import io.gitpod.jetbrains.remote.icons.GitpodIcons +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.future.await +import kotlinx.coroutines.launch +import javax.swing.Icon + +class GitpodBranding : GatewayBranding { + + val manager = service() + + /* + TODO(ak) GITPOD_WORSPACE_ID is a subject to change + ideally we should not rely on it, but here `getName` is sync + alternatively we could precompute another env var based on supervisor info endpoint + before starting backend + */ + private var name = System.getenv("GITPOD_WORKSPACE_ID") ?: "Gitpod" + init { + GlobalScope.launch { + val info = manager.pendingInfo.await() + name = info.workspaceId + } + } + + override fun getIcon(): Icon { + return GitpodIcons.Logo + } + + override fun getName(): String { + return name + } + +} \ No newline at end of file diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodClientProjectSessionTracker.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodClientProjectSessionTracker.kt index 0d52ae13a4f4e0..c33c3c6e8a1fcb 100644 --- a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodClientProjectSessionTracker.kt +++ b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodClientProjectSessionTracker.kt @@ -25,8 +25,8 @@ class GitpodClientProjectSessionTracker( event = "jb_session" properties = mapOf( "sessionId" to session.clientId.value, - "instanceId" to info.infoResponse.instanceId, - "workspaceId" to info.infoResponse.workspaceId, + "instanceId" to info.instanceId, + "workspaceId" to info.workspaceId, "appName" to ApplicationInfo.getInstance().versionName, "appVersion" to ApplicationInfo.getInstance().fullVersion, "timestamp" to System.currentTimeMillis() diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodManager.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodManager.kt index 143c0ceb891ad3..0751e064f8fc0d 100644 --- a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodManager.kt +++ b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodManager.kt @@ -18,14 +18,17 @@ import git4idea.config.GitVcsApplicationSettings import io.gitpod.gitpodprotocol.api.GitpodClient import io.gitpod.gitpodprotocol.api.GitpodServerLauncher import io.gitpod.jetbrains.remote.services.HeartbeatService -import io.gitpod.jetbrains.remote.services.SupervisorInfoService +import io.gitpod.supervisor.api.* +import io.gitpod.supervisor.api.Info.WorkspaceInfoResponse import io.gitpod.supervisor.api.Notification.* -import io.gitpod.supervisor.api.NotificationServiceGrpc +import io.grpc.ManagedChannel +import io.grpc.ManagedChannelBuilder import io.grpc.stub.ClientCallStreamObserver import io.grpc.stub.ClientResponseObserver import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.future.await +import kotlinx.coroutines.guava.asDeferred import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import org.jetbrains.ide.BuiltInServerManager @@ -37,10 +40,16 @@ import java.time.Duration import java.util.concurrent.CancellationException import java.util.concurrent.CompletableFuture import javax.websocket.DeploymentException +import io.gitpod.jetbrains.remote.utils.Retrier.retry @Service class GitpodManager : Disposable { + companion object { + // there should be only one channel per an application to avoid memory leak + val supervisorChannel: ManagedChannel = ManagedChannelBuilder.forTarget("localhost:22999").usePlaintext().build() + } + val devMode = System.getenv("JB_DEV").toBoolean() private val lifetime = Lifetime.Eternal.createNested() @@ -84,8 +93,8 @@ class GitpodManager : Disposable { private val notificationGroup = NotificationGroupManager.getInstance().getNotificationGroup("Gitpod Notifications") private val notificationsJob = GlobalScope.launch { - val notifications = NotificationServiceGrpc.newStub(SupervisorInfoService.channel) - val futureNotifications = NotificationServiceGrpc.newFutureStub(SupervisorInfoService.channel) + val notifications = NotificationServiceGrpc.newStub(supervisorChannel) + val futureNotifications = NotificationServiceGrpc.newFutureStub(supervisorChannel) while (isActive) { try { val f = CompletableFuture() @@ -145,11 +154,18 @@ class GitpodManager : Disposable { } } - val pendingInfo = CompletableFuture() + val pendingInfo = CompletableFuture() private val infoJob = GlobalScope.launch { try { - // TOO(ak) inline SupervisorInfoService - pendingInfo.complete(SupervisorInfoService.fetch()) + // TODO(ak) replace retry with proper handling of grpc errors + val infoResponse = retry(3) { + InfoServiceGrpc + .newFutureStub(supervisorChannel) + .workspaceInfo(Info.WorkspaceInfoRequest.newBuilder().build()) + .asDeferred() + .await() + } + pendingInfo.complete(infoResponse) } catch (t: Throwable) { pendingInfo.completeExceptionally(t) } @@ -163,6 +179,23 @@ class GitpodManager : Disposable { val client = GitpodClient() private val serverJob = GlobalScope.launch { val info = pendingInfo.await() + + // TODO(ak) replace retry with proper handling of grpc errors + val tokenResponse = retry(3) { + val request = Token.GetTokenRequest.newBuilder() + .setHost(info.gitpodApi.host) + .addScope("function:sendHeartBeat") + .addScope("function:trackEvent") + .setKind("gitpod") + .build() + + TokenServiceGrpc + .newFutureStub(supervisorChannel) + .getToken(request) + .asDeferred() + .await() + } + val launcher = GitpodServerLauncher.create(client) val plugin = PluginManagerCore.getPlugin(PluginId.getId("io.gitpod.jetbrains.remote"))!! val connect = { @@ -171,11 +204,11 @@ class GitpodManager : Disposable { // see https://intellij-support.jetbrains.com/hc/en-us/community/posts/360003146180/comments/360000376240 Thread.currentThread().contextClassLoader = HeartbeatService::class.java.classLoader launcher.listen( - info.infoResponse.gitpodApi.endpoint, - info.infoResponse.gitpodHost, + info.gitpodApi.endpoint, + info.gitpodHost, plugin.pluginId.idString, plugin.version, - info.tokenResponse.token + tokenResponse.token ) } finally { Thread.currentThread().contextClassLoader = originalClassLoader; @@ -186,7 +219,7 @@ class GitpodManager : Disposable { val maxReconnectionDelay = 30 * 1000L val reconnectionDelayGrowFactor = 1.5; var reconnectionDelay = minReconnectionDelay; - val gitpodHost = info.infoResponse.gitpodApi.host + val gitpodHost = info.gitpodApi.host var closeReason: Any = "cancelled" try { while (kotlin.coroutines.coroutineContext.isActive) { diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/icons/GitpodIcons.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/icons/GitpodIcons.kt new file mode 100644 index 00000000000000..f6a9219d4659f0 --- /dev/null +++ b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/icons/GitpodIcons.kt @@ -0,0 +1,11 @@ +// Copyright (c) 2022 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. +package io.gitpod.jetbrains.remote.icons + +import com.intellij.openapi.util.IconLoader + +object GitpodIcons { + @JvmField + val Logo = IconLoader.getIcon("/icons/logo.svg", javaClass) +} \ No newline at end of file diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/services/HeartbeatService.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/services/HeartbeatService.kt index 43532aea82b417..f28b21acf59c7b 100644 --- a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/services/HeartbeatService.kt +++ b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/services/HeartbeatService.kt @@ -40,7 +40,7 @@ class HeartbeatService : Disposable { } if (wasClosed != null) { - manager.client.server.sendHeartBeat(SendHeartBeatOptions(info.infoResponse.instanceId, wasClosed)).await() + manager.client.server.sendHeartBeat(SendHeartBeatOptions(info.instanceId, wasClosed)).await() } } catch (t: Throwable) { thisLogger().error("gitpod: failed to check activity:", t) diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/services/SupervisorInfoService.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/services/SupervisorInfoService.kt deleted file mode 100644 index 5edee438b769ae..00000000000000 --- a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/services/SupervisorInfoService.kt +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2021 Gitpod GmbH. All rights reserved. -// Licensed under the GNU Affero General Public License (AGPL). -// See License-AGPL.txt in the project root for license information. - -package io.gitpod.jetbrains.remote.services - -import io.gitpod.jetbrains.remote.utils.Retrier.retry -import io.gitpod.supervisor.api.Info.WorkspaceInfoRequest -import io.gitpod.supervisor.api.InfoServiceGrpc -import io.gitpod.supervisor.api.Token -import io.gitpod.supervisor.api.Token.GetTokenRequest -import io.gitpod.supervisor.api.TokenServiceGrpc -import io.grpc.ManagedChannelBuilder -import kotlinx.coroutines.guava.asDeferred - -object SupervisorInfoService { - private const val SUPERVISOR_ADDRESS = "localhost:22999" - - // there should be only one channel per an application to avoid memory leak - val channel = ManagedChannelBuilder.forTarget(SUPERVISOR_ADDRESS).usePlaintext().build() - - data class Result( - val infoResponse: io.gitpod.supervisor.api.Info.WorkspaceInfoResponse, - val tokenResponse: Token.GetTokenResponse - ) - - @Suppress("MagicNumber") - suspend fun fetch(): Result = - retry(3) { - // TODO(ak) retry forever only on network issues, otherwise propagate error - val infoResponse = InfoServiceGrpc - .newFutureStub(channel) - .workspaceInfo(WorkspaceInfoRequest.newBuilder().build()) - .asDeferred() - .await() - - val request = GetTokenRequest.newBuilder() - .setHost(infoResponse.gitpodApi.host) - .addScope("function:sendHeartBeat") - .addScope("function:trackEvent") - .setKind("gitpod") - .build() - - val tokenResponse = TokenServiceGrpc - .newFutureStub(channel) - .getToken(request) - .asDeferred() - .await() - - Result(infoResponse, tokenResponse) - } -} diff --git a/components/ide/jetbrains/backend-plugin/src/main/resources/META-INF/plugin.xml b/components/ide/jetbrains/backend-plugin/src/main/resources/META-INF/plugin.xml index 74c203ae7bf5e5..38964a566d21ff 100644 --- a/components/ide/jetbrains/backend-plugin/src/main/resources/META-INF/plugin.xml +++ b/components/ide/jetbrains/backend-plugin/src/main/resources/META-INF/plugin.xml @@ -22,6 +22,9 @@ + diff --git a/components/ide/jetbrains/backend-plugin/src/main/resources/icons/logo.svg b/components/ide/jetbrains/backend-plugin/src/main/resources/icons/logo.svg new file mode 100644 index 00000000000000..55d689fa09d504 --- /dev/null +++ b/components/ide/jetbrains/backend-plugin/src/main/resources/icons/logo.svg @@ -0,0 +1,9 @@ + + + + + + + + +