diff --git a/components/ide/jetbrains/backend-plugin/hot-deploy.sh b/components/ide/jetbrains/backend-plugin/hot-deploy.sh index 50d6d9b84665db..3d5ecb47b0b183 100755 --- a/components/ide/jetbrains/backend-plugin/hot-deploy.sh +++ b/components/ide/jetbrains/backend-plugin/hot-deploy.sh @@ -21,24 +21,26 @@ leeway build -DnoVerifyJBPlugin=true -Dversion="$version" -DimageRepoBase=eu.gcr dev_image="$(tar xfO "$bldfn" ./imgnames.txt | head -n1)" echo "Dev Image: $dev_image" +ide_list=("intellij" "goland" "pycharm" "phpstorm" "rubymine" "webstorm" "rider" "clion") + if [ "$qualifier" == "stable" ]; then - prop="pluginImage" + prop_list=("pluginImage" "imageLayers[0]") else - prop="pluginLatestImage" + prop_list=("pluginLatestImage" "latestImageLayers[0]") fi cf_patch=$(kubectl get cm ide-config -o=json | jq '.data."config.json"' |jq -r) -cf_patch=$(echo "$cf_patch" |jq ".ideOptions.options.intellij.$prop = \"$dev_image\"") -cf_patch=$(echo "$cf_patch" |jq ".ideOptions.options.goland.$prop = \"$dev_image\"") -cf_patch=$(echo "$cf_patch" |jq ".ideOptions.options.pycharm.$prop = \"$dev_image\"") -cf_patch=$(echo "$cf_patch" |jq ".ideOptions.options.phpstorm.$prop = \"$dev_image\"") -cf_patch=$(echo "$cf_patch" |jq ".ideOptions.options.rubymine.$prop = \"$dev_image\"") -cf_patch=$(echo "$cf_patch" |jq ".ideOptions.options.webstorm.$prop = \"$dev_image\"") -cf_patch=$(echo "$cf_patch" |jq ".ideOptions.options.rider.$prop = \"$dev_image\"") -cf_patch=$(echo "$cf_patch" |jq ".ideOptions.options.clion.$prop = \"$dev_image\"") + +for ide in "${ide_list[@]}"; do + for prop in "${prop_list[@]}"; do + cf_patch=$(echo "$cf_patch" | jq ".ideOptions.options.$ide.$prop = \"$dev_image\"") + done +done + cf_patch=$(echo "$cf_patch" |jq tostring) cf_patch="{\"data\": {\"config.json\": $cf_patch}}" kubectl patch cm ide-config --type=merge -p "$cf_patch" kubectl rollout restart deployment ide-service +kubectl rollout restart deployment server diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/AbstractGitpodTerminalService.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/AbstractGitpodTerminalService.kt index 77182e1413f141..1d3f8102fad712 100644 --- a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/AbstractGitpodTerminalService.kt +++ b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/AbstractGitpodTerminalService.kt @@ -20,14 +20,13 @@ import io.gitpod.supervisor.api.TerminalServiceGrpc import io.grpc.StatusRuntimeException import io.grpc.stub.ClientCallStreamObserver import io.grpc.stub.ClientResponseObserver -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay +import kotlinx.coroutines.* import kotlinx.coroutines.future.await import kotlinx.coroutines.guava.await import org.jetbrains.plugins.terminal.ShellTerminalWidget import java.util.concurrent.CompletableFuture import java.util.concurrent.ExecutionException +import kotlin.coroutines.coroutineContext abstract class AbstractGitpodTerminalService(project: Project) : Disposable { private val lifetime = defineNestedLifetime() @@ -39,27 +38,38 @@ abstract class AbstractGitpodTerminalService(project: Project) : Disposable { start() } - protected abstract fun runJob(lifetime: Lifetime, block: suspend CoroutineScope.() -> Unit): Job; + protected abstract fun runJob(lifetime: Lifetime, block: suspend CoroutineScope.() -> Unit): Job override fun dispose() = Unit protected fun start() { if (application.isHeadlessEnvironment) return runJob(lifetime) { - val terminals = getSupervisorTerminalsList() - val tasks = getSupervisorTasksList() - - application.invokeLater { - createTerminalsAttachedToTasks(terminals, tasks) + try { + val terminals = withTimeout(20000L) { getSupervisorTerminalsList() } + val tasks = withTimeout(20000L) { getSupervisorTasksList() } + thisLogger().info("gitpod: attaching tasks ${tasks.size}, terminals ${terminals.size}") + if (tasks.isEmpty() && terminals.isEmpty()) { + return@runJob + } + // see internal chat https://gitpod.slack.com/archives/C02BRJLGPGF/p1716540080028119 + delay(5000L) + application.invokeLater { + createTerminalsAttachedToTasks(terminals, tasks) + } + } catch (e: TimeoutCancellationException) { + thisLogger().error("gitpod: timeout while fetching tasks or terminals", e) + } catch (e: Exception) { + thisLogger().error("gitpod: error while attaching tasks", e) } } } - protected abstract fun createSharedTerminal(title: String): ShellTerminalWidget + protected abstract fun createSharedTerminal(id: String, title: String): ShellTerminalWidget private fun createTerminalsAttachedToTasks( - terminals: List, - tasks: List + terminals: List, + tasks: List ) { if (tasks.isEmpty()) return @@ -70,24 +80,31 @@ abstract class AbstractGitpodTerminalService(project: Project) : Disposable { aliasToTerminalMap[terminalAlias] = terminal } - for (task in tasks) { + tasks.forEachIndexed { index, task -> val terminalAlias = task.terminal - val terminal = aliasToTerminalMap[terminalAlias] ?: continue + val terminal = aliasToTerminalMap[terminalAlias] - createAttachedSharedTerminal(terminal) + if (terminal == null) { + thisLogger().warn("gitpod: found no terminal for task ${task.id}, expecting ${task.terminal}") + return + } + val title = terminal.title.takeIf { !it.isNullOrBlank() } ?: "Gitpod Task ${index + 1}" + thisLogger().info("gitpod: attaching task ${terminal.title} (${task.terminal}) with title $title") + createAttachedSharedTerminal(title, terminal) + thisLogger().info("gitpod: attached task ${terminal.title} (${task.terminal})") } } private tailrec suspend fun getSupervisorTasksList(): List { var tasksList: List? = null - + coroutineContext.ensureActive() try { val completableFuture = CompletableFuture>() val taskStatusRequest = Status.TasksStatusRequest.newBuilder().setObserve(true).build() val taskStatusResponseObserver = object : - ClientResponseObserver { + ClientResponseObserver { override fun beforeStart(request: ClientCallStreamObserver) = Unit override fun onNext(response: Status.TasksStatusResponse) { @@ -114,15 +131,12 @@ abstract class AbstractGitpodTerminalService(project: Project) : Disposable { } thisLogger().error( - "gitpod: Got an error while trying to get tasks list from Supervisor. " + - "Trying again in one second.", - throwable + "gitpod: Got an error while trying to get tasks list from Supervisor. Trying again in one second.", + throwable ) } - return if (tasksList != null) { - tasksList - } else { + return tasksList ?: run { delay(1000) getSupervisorTasksList() } @@ -130,7 +144,7 @@ abstract class AbstractGitpodTerminalService(project: Project) : Disposable { private tailrec suspend fun getSupervisorTerminalsList(): List { var terminalsList: List? = null - + coroutineContext.ensureActive() try { val listTerminalsRequest = TerminalOuterClass.ListTerminalsRequest.newBuilder().build() @@ -145,31 +159,28 @@ abstract class AbstractGitpodTerminalService(project: Project) : Disposable { } thisLogger().error( - "gitpod: Got an error while trying to get terminals list from Supervisor. " + - "Trying again in one second.", - throwable + "gitpod: Got an error while trying to get terminals list from Supervisor. Trying again in one second.", + throwable ) } - return if (terminalsList != null) { - terminalsList - } else { + return terminalsList ?: run { delay(1000) getSupervisorTerminalsList() } } - private fun createAttachedSharedTerminal(supervisorTerminal: TerminalOuterClass.Terminal) { - val shellTerminalWidget = createSharedTerminal(supervisorTerminal.title) + private fun createAttachedSharedTerminal(title: String, supervisorTerminal: TerminalOuterClass.Terminal) { + val shellTerminalWidget = createSharedTerminal(supervisorTerminal.alias, title) shellTerminalWidget.executeCommand("gp tasks attach ${supervisorTerminal.alias}") - closeTerminalWidgetWhenClientGetsClosed(shellTerminalWidget) + closeTerminalWidgetWhenClientGetsClosed(supervisorTerminal, shellTerminalWidget) exitTaskWhenTerminalWidgetGetsClosed(supervisorTerminal, shellTerminalWidget) listenForTaskTerminationAndTitleChanges(supervisorTerminal, shellTerminalWidget) } private fun listenForTaskTerminationAndTitleChanges( - supervisorTerminal: TerminalOuterClass.Terminal, - shellTerminalWidget: ShellTerminalWidget + supervisorTerminal: TerminalOuterClass.Terminal, + shellTerminalWidget: ShellTerminalWidget ) = runJob(lifetime) { var hasOpenSessions = true @@ -177,43 +188,42 @@ abstract class AbstractGitpodTerminalService(project: Project) : Disposable { val completableFuture = CompletableFuture() val listenTerminalRequest = TerminalOuterClass.ListenTerminalRequest.newBuilder() - .setAlias(supervisorTerminal.alias) - .build() + .setAlias(supervisorTerminal.alias) + .build() val listenTerminalResponseObserver = - object : ClientResponseObserver { - override fun beforeStart( - request: ClientCallStreamObserver - ) { - @Suppress("ObjectLiteralToLambda") - shellTerminalWidget.addListener(object : TerminalWidgetListener { - override fun allSessionsClosed(widget: TerminalWidget) { - hasOpenSessions = false - request.cancel("gitpod: Terminal closed on the client.", null) - } - }) - } + object : + ClientResponseObserver { + override fun beforeStart(request: ClientCallStreamObserver) { + @Suppress("ObjectLiteralToLambda") + shellTerminalWidget.addListener(object : TerminalWidgetListener { + override fun allSessionsClosed(widget: TerminalWidget) { + hasOpenSessions = false + request.cancel("gitpod: Terminal closed on the client.", null) + } + }) + } - override fun onNext(response: TerminalOuterClass.ListenTerminalResponse) { - when { - response.hasTitle() -> application.invokeLater { - shellTerminalWidget.terminalTitle.change { - applicationTitle = response.title - } + override fun onNext(response: TerminalOuterClass.ListenTerminalResponse) { + when { + response.hasTitle() -> application.invokeLater { + shellTerminalWidget.terminalTitle.change { + applicationTitle = response.title } + } - response.hasExitCode() -> application.invokeLater { - shellTerminalWidget.close() - } + response.hasExitCode() -> application.invokeLater { + shellTerminalWidget.close() } } + } - override fun onCompleted() = Unit + override fun onCompleted() = Unit - override fun onError(throwable: Throwable) { - completableFuture.completeExceptionally(throwable) - } + override fun onError(throwable: Throwable) { + completableFuture.completeExceptionally(throwable) } + } terminalServiceStub.listen(listenTerminalRequest, listenTerminalResponseObserver) @@ -221,19 +231,18 @@ abstract class AbstractGitpodTerminalService(project: Project) : Disposable { completableFuture.await() } catch (throwable: Throwable) { if ( - throwable is StatusRuntimeException || - throwable is ExecutionException || - throwable is InterruptedException + throwable is StatusRuntimeException || + throwable is ExecutionException || + throwable is InterruptedException ) { shellTerminalWidget.close() - thisLogger().info("gitpod: Stopped listening to " + - "'${supervisorTerminal.title}' terminal due to an expected exception.") break } - thisLogger() - .error("gitpod: Got an error while listening to " + - "'${supervisorTerminal.title}' terminal. Trying again in one second.", throwable) + thisLogger().error( + "gitpod: got an error while listening to '${supervisorTerminal.title}' terminal. Trying again in one second.", + throwable + ) } delay(1000) @@ -241,23 +250,25 @@ abstract class AbstractGitpodTerminalService(project: Project) : Disposable { } private fun exitTaskWhenTerminalWidgetGetsClosed( - supervisorTerminal: TerminalOuterClass.Terminal, - shellTerminalWidget: ShellTerminalWidget + supervisorTerminal: TerminalOuterClass.Terminal, + shellTerminalWidget: ShellTerminalWidget ) { - @Suppress("ObjectLiteralToLambda") shellTerminalWidget.addListener(object : TerminalWidgetListener { override fun allSessionsClosed(widget: TerminalWidget) { runJob(lifetime) { delay(5000) try { + thisLogger().info("gitpod: shutdown task ${supervisorTerminal.title} (${supervisorTerminal.alias})") terminalServiceFutureStub.shutdown( TerminalOuterClass.ShutdownTerminalRequest.newBuilder() .setAlias(supervisorTerminal.alias) .build() - ) + ).await() } catch (throwable: Throwable) { - thisLogger().error("gitpod: Got an error while shutting down " + - "'${supervisorTerminal.title}' terminal.", throwable) + thisLogger().error( + "gitpod: got an error while shutting down '${supervisorTerminal.title}' terminal.", + throwable + ) } } } @@ -265,9 +276,12 @@ abstract class AbstractGitpodTerminalService(project: Project) : Disposable { } private fun closeTerminalWidgetWhenClientGetsClosed( - shellTerminalWidget: ShellTerminalWidget + supervisorTerminal: TerminalOuterClass.Terminal, + shellTerminalWidget: ShellTerminalWidget ) { + @Suppress("UnstableApiUsage") lifetime.onTerminationOrNow { + thisLogger().debug("gitpod: closing task terminal service ${supervisorTerminal.title} (${supervisorTerminal.alias})") shellTerminalWidget.close() } } diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/latest/GitpodTerminalService.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/internal/GitpodTerminalServiceImpl.kt similarity index 71% rename from components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/latest/GitpodTerminalService.kt rename to components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/internal/GitpodTerminalServiceImpl.kt index 3bafaba941e8fd..2cd052c9a92450 100644 --- a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/latest/GitpodTerminalService.kt +++ b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/internal/GitpodTerminalServiceImpl.kt @@ -2,7 +2,7 @@ // 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.latest +package io.gitpod.jetbrains.remote.internal import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.project.Project @@ -11,21 +11,28 @@ import com.jetbrains.rd.util.threading.coroutines.launch import com.jetbrains.rdserver.terminal.BackendTerminalManager import io.gitpod.jetbrains.remote.AbstractGitpodTerminalService import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import org.jetbrains.plugins.terminal.ShellTerminalWidget import org.jetbrains.plugins.terminal.TerminalToolWindowManager import java.util.* -@Suppress("UnstableApiUsage") -class GitpodTerminalService(val project: Project): AbstractGitpodTerminalService(project) { +class GitpodTerminalServiceImpl(val project: Project) : AbstractGitpodTerminalService(project) { private val terminalToolWindowManager = TerminalToolWindowManager.getInstance(project) private val backendTerminalManager = BackendTerminalManager.getInstance(project) override fun runJob(lifetime: Lifetime, block: suspend CoroutineScope.() -> Unit) = lifetime.launch { block() } - override fun createSharedTerminal(title: String): ShellTerminalWidget { - val shellTerminalWidget = ShellTerminalWidget.toShellJediTermWidgetOrThrow(terminalToolWindowManager.createShellWidget(null, title, true, false)) - backendTerminalManager.shareTerminal(shellTerminalWidget.asNewWidget(), UUID.randomUUID().toString()) + override fun createSharedTerminal(id: String, title: String): ShellTerminalWidget { + val shellTerminalWidget = ShellTerminalWidget.toShellJediTermWidgetOrThrow( + terminalToolWindowManager.createShellWidget( + null, + title, + true, + false + ) + ) + backendTerminalManager.shareTerminal(shellTerminalWidget.asNewWidget(), id) return shellTerminalWidget } } diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/stable/GitpodTerminalService.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/stable/GitpodTerminalService.kt deleted file mode 100644 index 721cbbcd44724f..00000000000000 --- a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/stable/GitpodTerminalService.kt +++ /dev/null @@ -1,30 +0,0 @@ -// 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.jetbrains.remote.stable - -import com.intellij.openapi.project.Project -import com.jetbrains.rd.framework.util.launch -import com.jetbrains.rd.util.lifetime.Lifetime -import com.jetbrains.rdserver.terminal.BackendTerminalManager -import io.gitpod.jetbrains.remote.AbstractGitpodTerminalService -import kotlinx.coroutines.CoroutineScope -import org.jetbrains.plugins.terminal.ShellTerminalWidget -import org.jetbrains.plugins.terminal.TerminalToolWindowManager -import java.util.* - -@Suppress("UnstableApiUsage") -class GitpodTerminalService(val project: Project): AbstractGitpodTerminalService(project) { - - private val terminalToolWindowManager = TerminalToolWindowManager.getInstance(project) - private val backendTerminalManager = BackendTerminalManager.getInstance(project) - - override fun runJob(lifetime: Lifetime, block: suspend CoroutineScope.() -> Unit) = lifetime.launch { block() } - - override fun createSharedTerminal(title: String): ShellTerminalWidget { - val shellTerminalWidget = terminalToolWindowManager.createLocalShellWidget(null, title, true, false) - backendTerminalManager.shareTerminal(shellTerminalWidget.asNewWidget(), UUID.randomUUID().toString()) - return shellTerminalWidget - } -} diff --git a/components/ide/jetbrains/backend-plugin/src/main/resources-latest/META-INF/extensions.xml b/components/ide/jetbrains/backend-plugin/src/main/resources-latest/META-INF/extensions.xml index 6ff3d5f1593691..7a9aa263f90845 100644 --- a/components/ide/jetbrains/backend-plugin/src/main/resources-latest/META-INF/extensions.xml +++ b/components/ide/jetbrains/backend-plugin/src/main/resources-latest/META-INF/extensions.xml @@ -6,8 +6,6 @@ - diff --git a/components/ide/jetbrains/backend-plugin/src/main/resources-stable/META-INF/extensions.xml b/components/ide/jetbrains/backend-plugin/src/main/resources-stable/META-INF/extensions.xml index d1401df3f7a78c..ccaa7e4de55d58 100644 --- a/components/ide/jetbrains/backend-plugin/src/main/resources-stable/META-INF/extensions.xml +++ b/components/ide/jetbrains/backend-plugin/src/main/resources-stable/META-INF/extensions.xml @@ -6,8 +6,6 @@ - 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 490982eea0eaf7..12cf1d962e9fab 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 @@ -27,6 +27,9 @@ preload="true"/> + +