Skip to content

Commit 6eece7c

Browse files
committed
Auto-forward all ports served from Gitpod when using JetBrains IDEs
1 parent 05a0b75 commit 6eece7c

File tree

4 files changed

+133
-51
lines changed

4 files changed

+133
-51
lines changed

components/ide/jetbrains/backend-plugin/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ plugins {
1313
// Kotlin support - check the latest version at https://plugins.gradle.org/plugin/org.jetbrains.kotlin.jvm
1414
id("org.jetbrains.kotlin.jvm") version "1.7.0"
1515
// gradle-intellij-plugin - read more: https://github.com/JetBrains/gradle-intellij-plugin
16-
id("org.jetbrains.intellij") version "1.6.0"
16+
id("org.jetbrains.intellij") version "1.7.0"
1717
// detekt linter - read more: https://detekt.github.io/detekt/gradle.html
1818
id("io.gitlab.arturbosch.detekt") version "1.17.1"
1919
// ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package io.gitpod.jetbrains.remote.latest
6+
7+
import com.intellij.openapi.diagnostic.thisLogger
8+
import com.intellij.openapi.project.Project
9+
import com.intellij.remoteDev.util.onTerminationOrNow
10+
import com.intellij.util.application
11+
import com.jetbrains.codeWithMe.model.RdPortType
12+
import com.jetbrains.rd.platform.util.lifetime
13+
import com.jetbrains.rd.util.lifetime.LifetimeStatus
14+
import com.jetbrains.rdserver.portForwarding.ForwardedPortInfo
15+
import com.jetbrains.rdserver.portForwarding.PortForwardingManager
16+
import com.jetbrains.rdserver.portForwarding.remoteDev.PortEventsProcessor
17+
import io.gitpod.jetbrains.remote.GitpodManager
18+
import io.gitpod.supervisor.api.Status
19+
import io.gitpod.supervisor.api.StatusServiceGrpc
20+
import io.grpc.stub.ClientCallStreamObserver
21+
import io.grpc.stub.ClientResponseObserver
22+
import io.ktor.utils.io.*
23+
import java.util.concurrent.CompletableFuture
24+
import java.util.concurrent.TimeUnit
25+
26+
@Suppress("UnstableApiUsage")
27+
class GitpodPortForwardingService(private val project: Project) {
28+
companion object {
29+
const val FORWARDED_PORT_LABEL = "gitpod"
30+
}
31+
32+
init { start() }
33+
34+
private fun start() {
35+
if (application.isHeadlessEnvironment) return
36+
37+
observePortsListWhileProjectIsOpen()
38+
}
39+
40+
private fun observePortsListWhileProjectIsOpen() = application.executeOnPooledThread {
41+
while (project.lifetime.status == LifetimeStatus.Alive) {
42+
try {
43+
observePortsList().get()
44+
} catch (throwable: Throwable) {
45+
when (throwable) {
46+
is InterruptedException, is CancellationException -> break
47+
else -> thisLogger().error(
48+
"gitpod: Got an error while trying to get ports list from Supervisor. " +
49+
"Will try again in 1 second.",
50+
throwable
51+
)
52+
}
53+
}
54+
55+
TimeUnit.SECONDS.sleep(1)
56+
}
57+
}
58+
59+
private fun observePortsList(): CompletableFuture<Void> {
60+
val completableFuture = CompletableFuture<Void>()
61+
62+
val statusServiceStub = StatusServiceGrpc.newStub(GitpodManager.supervisorChannel)
63+
64+
val portsStatusRequest = Status.PortsStatusRequest.newBuilder().setObserve(true).build()
65+
66+
val portsStatusResponseObserver = object :
67+
ClientResponseObserver<Status.PortsStatusRequest, Status.PortsStatusResponse> {
68+
override fun beforeStart(request: ClientCallStreamObserver<Status.PortsStatusRequest>) {
69+
project.lifetime.onTerminationOrNow { request.cancel("gitpod: Project terminated.", null) }
70+
}
71+
override fun onNext(response: Status.PortsStatusResponse) { handlePortStatusResponse(response) }
72+
override fun onCompleted() { completableFuture.complete(null) }
73+
override fun onError(throwable: Throwable) { completableFuture.completeExceptionally(throwable) }
74+
}
75+
76+
statusServiceStub.portsStatus(portsStatusRequest, portsStatusResponseObserver)
77+
78+
return completableFuture
79+
}
80+
81+
private fun handlePortStatusResponse(response: Status.PortsStatusResponse) {
82+
val portNumberToServedStatusMap = mutableMapOf<Int, Boolean>()
83+
84+
for (port in response.portsList) {
85+
val isServed = (port.exposed != null || port.tunneled != null) && port.served
86+
87+
portNumberToServedStatusMap[port.localPort] = isServed
88+
}
89+
90+
updateForwardedPortsList(portNumberToServedStatusMap)
91+
}
92+
93+
private fun updateForwardedPortsList(portNumberToServedStatusMap: Map<Int, Boolean>) {
94+
val portForwardingManager = PortForwardingManager.getInstance(project)
95+
val forwardedPortsList = portForwardingManager.getForwardedPortsWithLabel(FORWARDED_PORT_LABEL)
96+
97+
portNumberToServedStatusMap.forEach { (hostPort, isServed) ->
98+
if (isServed && !forwardedPortsList.containsKey(hostPort)) {
99+
val portEventsProcessor = object : PortEventsProcessor {
100+
override fun onPortForwarded(hostPort: Int, clientPort: Int) {
101+
thisLogger().info("gitpod: Forwarded port $hostPort to client's port $clientPort.")
102+
}
103+
104+
override fun onPortForwardingEnded(hostPort: Int) {
105+
thisLogger().info("gitpod: Finished forwarding port $hostPort.")
106+
}
107+
108+
override fun onPortForwardingFailed(hostPort: Int, reason: String) {
109+
thisLogger().error("gitpod: Failed to forward port $hostPort: $reason")
110+
}
111+
}
112+
113+
val portInfo = ForwardedPortInfo(
114+
hostPort,
115+
RdPortType.HTTP,
116+
FORWARDED_PORT_LABEL,
117+
emptyList(),
118+
portEventsProcessor
119+
)
120+
121+
portForwardingManager.forwardPort(portInfo)
122+
}
123+
124+
if (!isServed && forwardedPortsList.containsKey(hostPort)) {
125+
portForwardingManager.removePort(hostPort)
126+
thisLogger().info("gitpod: Stopped forwarding port $hostPort.")
127+
}
128+
}
129+
}
130+
}

components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/latest/GitpodTerminalService.kt

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,7 @@ package io.gitpod.jetbrains.remote.latest
66

77
import com.intellij.openapi.client.ClientProjectSession
88
import com.intellij.openapi.diagnostic.thisLogger
9-
import com.intellij.openapi.rd.createLifetime
10-
import com.intellij.remoteDev.util.onTerminationOrNow
119
import com.intellij.util.application
12-
import com.jetbrains.rdserver.portForwarding.PortForwardingDiscovery
13-
import com.jetbrains.rdserver.portForwarding.PortForwardingManager
14-
import com.jetbrains.rdserver.portForwarding.remoteDev.PortEventsProcessor
1510
import com.jetbrains.rdserver.terminal.BackendTerminalManager
1611
import io.gitpod.jetbrains.remote.GitpodManager
1712
import io.gitpod.supervisor.api.Status
@@ -27,15 +22,13 @@ import java.util.concurrent.CompletableFuture
2722
import java.util.concurrent.TimeUnit
2823

2924
@Suppress("UnstableApiUsage")
30-
class GitpodTerminalService(private val session: ClientProjectSession) {
25+
class GitpodTerminalService(session: ClientProjectSession) {
3126
private companion object {
3227
var hasStarted = false
33-
val forwardedPortsList: MutableSet<Int> = mutableSetOf()
3428
}
3529

3630
private val terminalView = TerminalView.getInstance(session.project)
3731
private val backendTerminalManager = BackendTerminalManager.getInstance(session.project)
38-
private val portForwardingManager = PortForwardingManager.getInstance(session.project)
3932
private val terminalServiceFutureStub = TerminalServiceGrpc.newFutureStub(GitpodManager.supervisorChannel)
4033
private val statusServiceStub = StatusServiceGrpc.newStub(GitpodManager.supervisorChannel)
4134

@@ -88,7 +81,6 @@ class GitpodTerminalService(private val session: ClientProjectSession) {
8881
val terminal = aliasToTerminalMap[terminalAlias] ?: continue
8982

9083
createAttachedSharedTerminal(terminal)
91-
autoForwardAllPortsFromTerminal(terminal)
9284
}
9385
}
9486

@@ -177,45 +169,4 @@ class GitpodTerminalService(private val session: ClientProjectSession) {
177169
"gp tasks attach ${supervisorTerminal.alias}"
178170
)
179171
}
180-
181-
private fun autoForwardAllPortsFromTerminal(supervisorTerminal: TerminalOuterClass.Terminal) {
182-
val projectLifetime = session.project.createLifetime()
183-
184-
val discoveryCallback = object : PortForwardingDiscovery {
185-
/**
186-
* @return Whether port should be forwarded or not.
187-
* We shouldn't try to forward ports that are already forwarded.
188-
*/
189-
override fun onPortDiscovered(hostPort: Int): Boolean = !forwardedPortsList.contains(hostPort)
190-
191-
override fun getEventsProcessor(hostPort: Int) = object : PortEventsProcessor {
192-
override fun onPortForwarded(hostPort: Int, clientPort: Int) {
193-
forwardedPortsList.add(hostPort)
194-
thisLogger().info("gitpod: Forwarded port $hostPort from Supervisor's Terminal " +
195-
"${supervisorTerminal.pid} to client's port $clientPort.")
196-
197-
projectLifetime.onTerminationOrNow {
198-
if (forwardedPortsList.contains(hostPort)) {
199-
forwardedPortsList.remove(hostPort)
200-
portForwardingManager.removePort(hostPort)
201-
thisLogger().info("gitpod: Removing forwarded port $hostPort from Supervisor's Terminal " +
202-
"${supervisorTerminal.pid}")
203-
}
204-
}
205-
}
206-
207-
override fun onPortForwardingFailed(hostPort: Int, reason: String) {
208-
thisLogger().error("gitpod: Failed to forward port $hostPort from Supervisor's Terminal " +
209-
"${supervisorTerminal.pid}: $reason")
210-
}
211-
212-
override fun onPortForwardingEnded(hostPort: Int) {
213-
thisLogger().info("gitpod: Port $hostPort from Supervisor's Terminal " +
214-
"${supervisorTerminal.pid} is not being forwarded anymore.")
215-
}
216-
}
217-
}
218-
219-
portForwardingManager.forwardPortsOfPid(projectLifetime, supervisorTerminal.pid, discoveryCallback, true)
220-
}
221172
}

components/ide/jetbrains/backend-plugin/src/main/resources-latest/META-INF/extensions.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
<idea-plugin>
77
<extensions defaultExtensionNs="com.intellij">
88
<projectService serviceImplementation="io.gitpod.jetbrains.remote.latest.GitpodTerminalService" client="guest" preload="true"/>
9+
<projectService serviceImplementation="io.gitpod.jetbrains.remote.latest.GitpodPortForwardingService" preload="true"/>
910
</extensions>
1011
</idea-plugin>

0 commit comments

Comments
 (0)