Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test preview #19597

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Reuse the same tunnel for an environment
  • Loading branch information
mustard-mh committed Jan 24, 2025
commit e2af4a7801295c34df1e5be5fab79716a8ece5ef
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ import java.net.URI
import java.util.*
import java.util.concurrent.Future

// TODO: Validate Scopes
// TODO(hw): Validate Scopes
val authScopesJetBrainsToolbox = listOf(
"function:getGitpodTokenScopes",
"function:getLoggedInUser",
Original file line number Diff line number Diff line change
@@ -6,8 +6,6 @@ package io.gitpod.toolbox.components

import com.jetbrains.toolbox.gateway.ui.UiField
import com.jetbrains.toolbox.gateway.ui.UiPage
import java.util.function.BiConsumer
import java.util.function.Function

abstract class AbstractUiPage : UiPage {
private var stateAccessor: UiPage.UiFieldStateAccessor? = null
Original file line number Diff line number Diff line change
@@ -6,12 +6,7 @@ package io.gitpod.toolbox.gateway

import com.jetbrains.toolbox.gateway.AbstractRemoteProviderEnvironment
import com.jetbrains.toolbox.gateway.EnvironmentVisibilityState
import com.jetbrains.toolbox.gateway.deploy.CommandExecution
import com.jetbrains.toolbox.gateway.deploy.HostAccessInterfaces
import com.jetbrains.toolbox.gateway.deploy.HostCommandExecutor
import com.jetbrains.toolbox.gateway.deploy.HostFileAccessor
import com.jetbrains.toolbox.gateway.environments.EnvironmentContentsView
import com.jetbrains.toolbox.gateway.environments.HostAccessCapableEnvironmentContentsView
import com.jetbrains.toolbox.gateway.states.EnvironmentStateConsumer
import com.jetbrains.toolbox.gateway.states.StandardRemoteEnvironmentState
import com.jetbrains.toolbox.gateway.ui.ActionDescription
@@ -30,19 +25,15 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import java.util.concurrent.CompletableFuture

class GitpodRemoteProviderEnvironment(
class GitpodRemoteEnvironment(
private val authManager: GitpodAuthManager,
private val connectParams: ConnectParams,
private val publicApi: GitpodPublicApiManager, observablePropertiesFactory: ObservablePropertiesFactory?,
) : AbstractRemoteProviderEnvironment(observablePropertiesFactory), DisposableHandle {
private val actionList = Utils.observablePropertiesFactory.emptyObservableList<ActionDescription>();
private val contentsViewFuture: CompletableFuture<EnvironmentContentsView> = CompletableFuture.completedFuture(
GitpodSSHEnvironmentContentsView(
authManager,
connectParams,
publicApi,
)
)
private val envContentsView = GitpodRemoteEnvironmentContentsView(connectParams, publicApi)
private val contentsViewFuture: CompletableFuture<EnvironmentContentsView> =
CompletableFuture.completedFuture(envContentsView)
private var watchWorkspaceJob: Job? = null

private val lastWSEnvState = MutableSharedFlow<WorkspaceEnvState>(1, 0, BufferOverflow.DROP_OLDEST)
@@ -65,6 +56,9 @@ class GitpodRemoteProviderEnvironment(
lastPhase = status.phase
GitpodLogger.debug("${connectParams.workspaceId} status updated: $lastPhase")
lastWSEnvState.tryEmit(WorkspaceEnvState(status.phase))
Utils.coroutineScope.launch {
envContentsView.updateEnvironmentMeta(status)
}
}
}
}
@@ -78,19 +72,17 @@ class GitpodRemoteProviderEnvironment(
}

override fun getId(): String = connectParams.uniqueID
override fun getName(): String = connectParams.resolvedWorkspaceId
override fun getName(): String = connectParams.uniqueID

override fun getContentsView(): CompletableFuture<EnvironmentContentsView> {
GitpodLogger.info("=============test.getContentView id: $id")
return contentsViewFuture
}
override fun getContentsView(): CompletableFuture<EnvironmentContentsView> = contentsViewFuture

override fun setVisible(visibilityState: EnvironmentVisibilityState) {
}

override fun getActionList(): ObservableList<ActionDescription> = actionList

override fun onDelete() {
// TODO: delete workspace?
watchWorkspaceJob?.cancel()
}

@@ -120,4 +112,5 @@ private class WorkspaceEnvState(val phase: WorkspaceInstanceStatus.Phase) {
WorkspaceInstanceStatus.Phase.PHASE_STOPPED to StandardRemoteEnvironmentState.Hibernated,
)
}
// TODO(hw): add customized state
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) 2024 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.toolbox.gateway

import com.jetbrains.toolbox.gateway.environments.CachedIdeStub
import com.jetbrains.toolbox.gateway.environments.CachedProjectStub
import com.jetbrains.toolbox.gateway.environments.ManualEnvironmentContentsView
import com.jetbrains.toolbox.gateway.environments.SshEnvironmentContentsView
import com.jetbrains.toolbox.gateway.ssh.SshConnectionInfo
import io.gitpod.publicapi.experimental.v1.Workspaces.WorkspaceInstanceStatus
import io.gitpod.toolbox.service.*
import io.gitpod.toolbox.utils.GitpodLogger
import java.util.concurrent.CompletableFuture

class GitpodRemoteEnvironmentContentsView(
private val connectParams: ConnectParams,
private val publicApi: GitpodPublicApiManager,
) : SshEnvironmentContentsView, ManualEnvironmentContentsView {
private var cancel = {}
private val stateListeners = mutableSetOf<ManualEnvironmentContentsView.Listener>()
private val provider = GitpodConnectionProvider(object : ConnectionInfoProvider {
override fun getUniqueID() = connectParams.uniqueID

override suspend fun getWebsocketTunnelUrl(): String {
val workspace = publicApi.getWorkspace(connectParams.workspaceId)
return workspace.getTunnelUrl()
}

override suspend fun getOwnerToken(): String {
return publicApi.getWorkspaceOwnerToken(connectParams.workspaceId)
}
})

private val connectionInfo = CompletableFuture.supplyAsync {
GitpodLogger.info("===============connectionInfo ${connectParams.uniqueID}")
val (connInfo, cancel) = provider.connect()
this.cancel = cancel
connInfo
}

override fun getConnectionInfo(): CompletableFuture<SshConnectionInfo> = connectionInfo

var metadata: GitpodPublicApiManager.JoinLink2Response? = null
suspend fun updateEnvironmentMeta(status: WorkspaceInstanceStatus) {
if (metadata == null && status.phase == WorkspaceInstanceStatus.Phase.PHASE_RUNNING) {
metadata = publicApi.fetchJoinLink2Info(connectParams.workspaceId, status.getIDEUrl())
}
if (metadata == null) {
// TODO(hw): restore from cache?
return
}
stateListeners.forEach {
it.onProjectListUpdated(listOf(object : CachedProjectStub {
override fun getPath() = metadata!!.projectPath
override fun getName() = metadata!!.projectPath.split("/").last()
override fun getIdeHint() = metadata!!.ideVersion
}))
it.onIdeListUpdated(listOf(object : CachedIdeStub {
override fun getProductCode() = metadata!!.ideVersion
override fun isRunning() = status.phase == WorkspaceInstanceStatus.Phase.PHASE_RUNNING
}))
}
}

override fun addEnvironmentContentsListener(p0: ManualEnvironmentContentsView.Listener) {
stateListeners += p0
}

override fun removeEnvironmentContentsListener(p0: ManualEnvironmentContentsView.Listener) {
stateListeners -= p0
}

override fun close() {
cancel()
}
}
Original file line number Diff line number Diff line change
@@ -30,7 +30,7 @@ class GitpodRemoteProvider(
private val loginPage = GitpodLoginPage(authManger)

// cache consumed environments map locally
private val environmentMap = mutableMapOf<String, Pair<Workspaces.Workspace, GitpodRemoteProviderEnvironment>>()
private val environmentMap = mutableMapOf<String, Pair<Workspaces.Workspace, GitpodRemoteEnvironment>>()

private var pendingConnectParams: Pair<String, ConnectParams>? = null
private val openInToolboxUriHandler = GitpodOpenInToolboxUriHandler { (gitpodHost, connectParams) ->
@@ -54,7 +54,7 @@ class GitpodRemoteProvider(
var (workspace) = obj ?: Pair(null, null)
if (obj == null) {
workspace = publicApi.getWorkspace(workspaceId)
val env = GitpodRemoteProviderEnvironment(
val env = GitpodRemoteEnvironment(
authManger,
connectParams,
publicApi,
@@ -63,9 +63,10 @@ class GitpodRemoteProvider(
environmentMap[connectParams.uniqueID] = Pair(workspace, env)
consumer.consumeEnvironments(environmentMap.values.map { it.second })
}
val joinLinkInfo = workspace!!.fetchJoinLink2Info(publicApi.getWorkspaceOwnerToken(workspaceId))
val joinLinkInfo = publicApi.fetchJoinLink2Info(workspaceId, workspace!!.getIDEUrl())
// TODO(hw): verify if it's working
Utils.clientHelper.prepareClient(joinLinkInfo.ideVersion)
Utils.clientHelper.setAutoConnectOnEnvironmentReady(workspaceId, joinLinkInfo.ideVersion, joinLinkInfo.projectPath)
Utils.clientHelper.setAutoConnectOnEnvironmentReady(connectParams.uniqueID, joinLinkInfo.ideVersion, joinLinkInfo.projectPath)
}

private fun showWorkspacesList() {
@@ -77,7 +78,7 @@ class GitpodRemoteProvider(
}
consumer.consumeEnvironments(workspaces.map {
val connectParams = it.getConnectParams()
val env = environmentMap[connectParams.uniqueID]?.second ?: GitpodRemoteProviderEnvironment(
val env = environmentMap[connectParams.uniqueID]?.second ?: GitpodRemoteEnvironment(
authManger,
connectParams,
publicApi,

This file was deleted.

Original file line number Diff line number Diff line change
@@ -5,11 +5,8 @@
package io.gitpod.toolbox.service

import io.gitpod.publicapi.experimental.v1.Workspaces.Workspace
import io.gitpod.toolbox.utils.await
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Request
import io.gitpod.publicapi.experimental.v1.Workspaces.WorkspaceInstanceStatus
import java.net.URI
import java.net.URL

fun Workspace.getConnectParams(): ConnectParams {
@@ -26,20 +23,14 @@ fun Workspace.getGitpodHost(): String {
return hostSegments.takeLast(2).joinToString(".")
}

@Serializable
class JoinLink2Response(val appPid: Int, val joinLink: String, val ideVersion: String, val projectPath: String)

suspend fun Workspace.fetchJoinLink2Info(ownerToken: String): JoinLink2Response {
val backendUrl = "https://24000-${URL(getIDEUrl()).host}/joinLink2"
val client = Utils.httpClient
val req = Request.Builder().url(backendUrl).header("x-gitpod-owner-token", ownerToken)
val response = client.newCall(req.build()).await()
if (!response.isSuccessful) {
throw IllegalStateException("Failed to get join link $backendUrl info: ${response.code} ${response.message}")
}
if (response.body == null) {
throw IllegalStateException("Failed to get join link $backendUrl info: no body")
}
return Json.decodeFromString<JoinLink2Response>(response.body!!.string())
fun WorkspaceInstanceStatus.usingToolbox() = editor.preferToolbox

fun Workspace.getTunnelUrl(): String {
val workspaceHost = URI.create(status.instance.status.url).host
return "wss://${workspaceHost}/_supervisor/tunnel/ssh"
}

fun WorkspaceInstanceStatus.getIDEUrl(): String {
return url
}

Loading
Oops, something went wrong.