Skip to content

Commit 209b0b8

Browse files
manodnyabrlisaurabhajmera
authored
Connect to dev environment using the JetBrains SSH connection handler (#3629)
* Put alternative connect strategy under feature flag * Update CawsSshConnectionConfigModifier.kt * connects to dev env * removed registry entry * new changes * feedback changes * detekt * Fix integ * oops * fix failure --------- Co-authored-by: Richard Li <[email protected]> Co-authored-by: Richard Li <[email protected]> Co-authored-by: Saurabh Ajmera <[email protected]>
1 parent d94012e commit 209b0b8

File tree

12 files changed

+159
-31
lines changed

12 files changed

+159
-31
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ toolkitVersion=1.69-SNAPSHOT
88
publishToken=
99
publishChannel=
1010

11-
ideProfileName=2022.2
11+
ideProfileName=2023.1
1212

1313
remoteRobotPort=8080
1414

jetbrains-core/resources/META-INF/services/caws.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
<applicationService serviceImplementation="software.aws.toolkits.jetbrains.core.credentials.sono.SonoCredentialManager"/>
1010
<applicationService serviceImplementation="software.aws.toolkits.jetbrains.services.caws.envclient.CawsEnvironmentClient"/>
1111
<applicationService serviceImplementation="software.aws.toolkits.jetbrains.settings.CawsSpaceTracker"/>
12-
1312
<projectService serviceImplementation="software.aws.toolkits.jetbrains.services.caws.projectstate.CawsProjectSettings"/>
1413
</extensions>
1514

jetbrains-core/src/software/aws/toolkits/jetbrains/services/caws/CawsConstants.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package software.aws.toolkits.jetbrains.services.caws
55

66
object CawsConstants {
77
const val CAWS_ENV_PROJECT_DIR = "/projects"
8+
const val CAWS_ENV_IDE_BACKEND_DIR = "/aws/mde/ide-runtimes/jetbrains/runtime/"
89
const val CAWS_ENV_AUTH_TOKEN_VAR = "__MDE_ENV_API_AUTHORIZATION_TOKEN"
910
const val CAWS_ENV_ORG_NAME_VAR = "__DEV_ENVIRONMENT_ORGANIZATION_NAME"
1011
const val CAWS_ENV_PROJECT_NAME_VAR = "__DEV_ENVIRONMENT_PROJECT_NAME"

jetbrains-gateway/it/software/aws/toolkits/jetbrains/gateway/DevEnvConnectTest.kt

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ package software.aws.toolkits.jetbrains.gateway
55

66
import com.intellij.execution.configurations.GeneralCommandLine
77
import com.intellij.execution.util.ExecUtil
8-
import com.intellij.openapi.application.ApplicationInfo
98
import com.intellij.openapi.application.ApplicationManager
109
import com.intellij.openapi.components.service
1110
import com.intellij.openapi.components.serviceOrNull
11+
import com.intellij.openapi.util.Disposer
1212
import com.intellij.remoteDev.downloader.JetBrainsClientDownloaderConfigurationProvider
1313
import com.intellij.remoteDev.downloader.TestJetBrainsClientDownloaderConfigurationProvider
1414
import com.intellij.remoteDev.hostStatus.UnattendedHostStatus
@@ -20,7 +20,6 @@ import com.jetbrains.gateway.api.ConnectionRequestor
2020
import com.jetbrains.gateway.api.GatewayConnectionHandle
2121
import com.jetbrains.rd.util.lifetime.isNotAlive
2222
import kotlinx.coroutines.runBlocking
23-
import org.junit.jupiter.api.Assumptions.assumeTrue
2423
import org.junit.jupiter.api.BeforeEach
2524
import org.junit.jupiter.api.DynamicTest
2625
import org.junit.jupiter.api.TestFactory
@@ -47,6 +46,7 @@ import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_URL
4746
import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProvider
4847
import software.aws.toolkits.jetbrains.core.tools.MockToolManagerRule
4948
import software.aws.toolkits.jetbrains.core.tools.ToolManager
49+
import software.aws.toolkits.jetbrains.gateway.connection.IDE_BACKEND_DIR
5050
import software.aws.toolkits.jetbrains.gateway.connection.StdOutResult
5151
import software.aws.toolkits.jetbrains.gateway.connection.ThinClientTrackerService
5252
import software.aws.toolkits.jetbrains.gateway.connection.caws.CawsCommandExecutor
@@ -168,6 +168,7 @@ class DevEnvConnectTest : AfterAllCallback {
168168
// can probably abstract this out as an extension
169169
// force auth to complete now
170170
connection = ManagedBearerSsoConnection(SONO_URL, SONO_REGION, listOf("codecatalyst:read_write"))
171+
Disposer.register(disposable, connection)
171172
// pin connection to avoid dialog prompt
172173
ConnectionPinningManager.getInstance().setPinnedConnection(CodeCatalystConnection.getInstance(), connection)
173174
(connection.getConnectionSettings().tokenProvider.delegate as BearerTokenProvider).reauthenticate()
@@ -184,9 +185,6 @@ class DevEnvConnectTest : AfterAllCallback {
184185
@TestFactory
185186
@Timeout(value = 5, unit = TimeUnit.MINUTES)
186187
fun `test connect to devenv`(): Iterator<DynamicTest> = sequence<DynamicTest> {
187-
// having issues running on 222, technically works on 223, but cleanup fails
188-
assumeTrue(ApplicationInfo.getInstance().build.baselineVersion >= 231)
189-
190188
connectionHandle = runBlocking {
191189
CawsConnectionProvider().connect(
192190
mapOf(
@@ -198,9 +196,21 @@ class DevEnvConnectTest : AfterAllCallback {
198196
)
199197
} ?: error("null connection handle")
200198

199+
yield(test(::`wait for environment ready`))
200+
201+
// inject token to backend launcher script to enable the host status endpoint
202+
println(
203+
ssmFactory.executeSshCommand {
204+
it.addToRemoteCommand(
205+
"""
206+
grep -q "CWM_HOST_STATUS_OVER_HTTP_TOKEN" $IDE_BACKEND_DIR/bin/remote-dev-server.sh || sed -i.bak '2iexport CWM_HOST_STATUS_OVER_HTTP_TOKEN=$hostToken' $IDE_BACKEND_DIR/bin/remote-dev-server.sh
207+
""".trimIndent()
208+
)
209+
}
210+
)
211+
201212
yieldAll(
202213
listOf(
203-
test(::`wait for environment ready`),
204214
test(::`poll for bootstrap script availability`),
205215
test(::`wait for backend start`),
206216
test(::`wait for backend connect`)
@@ -266,12 +276,16 @@ class DevEnvConnectTest : AfterAllCallback {
266276
fun `wait for backend connect`() = runBlocking {
267277
waitUntil(
268278
succeedOn = { status ->
269-
status.projects?.any { it.users.size > 1 } == true
279+
status?.projects?.any { it.users.size > 1 } == true
270280
},
271281
failOn = { connectionHandle.lifetime.isNotAlive },
272282
maxDuration = Duration.ofMinutes(5),
273283
call = {
274-
UnattendedHostStatus.fromJson(HttpRequests.request(endpoint).readString())
284+
// can potentially have a socket reset which will lead to a very confusing error that's hard to debug
285+
// due to the Gateway connection executor continuing to run
286+
tryOrNull {
287+
UnattendedHostStatus.fromJson(HttpRequests.request(endpoint).readString())
288+
}
275289
}
276290
)
277291

jetbrains-gateway/resources/META-INF/ext-gateway.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<applicationService serviceImplementation="software.aws.toolkits.jetbrains.gateway.connection.SshAgentService" />
1010
<applicationService serviceImplementation="software.aws.toolkits.jetbrains.gateway.connection.ThinClientTrackerService" />
1111

12+
<ssh.config.sshConnectionConfigService.modifier implementation="software.aws.toolkits.jetbrains.gateway.connection.caws.CawsSshConnectionConfigModifier"/>
1213
</extensions>
1314
<applicationListeners>
1415
<listener class="software.aws.toolkits.jetbrains.gateway.GatewayDeprecationNotice" topic="com.intellij.ide.AppLifecycleListener"/>

jetbrains-gateway/src/software/aws/toolkits/jetbrains/gateway/CawsConnectionProvider.kt

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ import org.jetbrains.concurrency.AsyncPromise
3939
import org.jetbrains.concurrency.await
4040
import software.amazon.awssdk.services.codecatalyst.CodeCatalystClient
4141
import software.amazon.awssdk.services.codecatalyst.model.DevEnvironmentStatus
42-
import software.amazon.awssdk.services.codecatalyst.model.InstanceType
4342
import software.aws.toolkits.core.utils.AttributeBagKey
4443
import software.aws.toolkits.core.utils.error
4544
import software.aws.toolkits.core.utils.getLogger
@@ -60,14 +59,13 @@ import software.aws.toolkits.jetbrains.gateway.connection.workflow.CopyScripts
6059
import software.aws.toolkits.jetbrains.gateway.connection.workflow.InstallPluginBackend.InstallLocalPluginBackend
6160
import software.aws.toolkits.jetbrains.gateway.connection.workflow.InstallPluginBackend.InstallMarketplacePluginBackend
6261
import software.aws.toolkits.jetbrains.gateway.connection.workflow.PrimeSshAgent
63-
import software.aws.toolkits.jetbrains.gateway.connection.workflow.StartBackend
6462
import software.aws.toolkits.jetbrains.gateway.connection.workflow.TabbedWorkflowEmitter
6563
import software.aws.toolkits.jetbrains.gateway.connection.workflow.installBundledPluginBackend
64+
import software.aws.toolkits.jetbrains.gateway.connection.workflow.v2.StartBackendV2
6665
import software.aws.toolkits.jetbrains.gateway.welcomescreen.WorkspaceListStateChangeContext
6766
import software.aws.toolkits.jetbrains.gateway.welcomescreen.WorkspaceNotifications
6867
import software.aws.toolkits.jetbrains.services.caws.CawsProject
6968
import software.aws.toolkits.jetbrains.utils.execution.steps.Context
70-
import software.aws.toolkits.jetbrains.utils.execution.steps.Step
7169
import software.aws.toolkits.jetbrains.utils.execution.steps.StepEmitter
7270
import software.aws.toolkits.jetbrains.utils.execution.steps.StepExecutor
7371
import software.aws.toolkits.jetbrains.utils.execution.steps.StepWorkflow
@@ -111,6 +109,7 @@ class CawsConnectionProvider : GatewayConnectionProvider {
111109
val spaceName = connectionParams.space
112110
val projectName = connectionParams.project
113111
val envId = connectionParams.envId
112+
val id = WorkspaceIdentifier(CawsProject(spaceName, projectName), envId)
114113

115114
val lifetime = Lifetime.Eternal.createNested()
116115
val workflowDisposable = Lifetime.Eternal.createNestedDisposable()
@@ -166,12 +165,6 @@ class CawsConnectionProvider : GatewayConnectionProvider {
166165
)
167166
}
168167

169-
val isSmallInstance = cawsClient.getDevEnvironment {
170-
it.id(envId)
171-
it.projectName(projectName)
172-
it.spaceName(spaceName)
173-
}.instanceType().equals(InstanceType.DEV_STANDARD1_SMALL)
174-
175168
lifetime.launchIOBackground {
176169
ApplicationManager.getApplication().messageBus.syncPublisher(WorkspaceNotifications.TOPIC)
177170
.environmentStarted(
@@ -278,11 +271,9 @@ class CawsConnectionProvider : GatewayConnectionProvider {
278271
lifetime.createNested(),
279272
parameters,
280273
executor,
281-
gatewayHandle,
282-
envId,
274+
id,
283275
connectionParams.gitSettings,
284-
toolkitInstallSettings,
285-
isSmallInstance
276+
toolkitInstallSettings
286277
).await()
287278
}.invokeOnCompletion { e ->
288279
if (e == null) {
@@ -391,16 +382,14 @@ class CawsConnectionProvider : GatewayConnectionProvider {
391382
lifetime: LifetimeDefinition,
392383
parameters: Map<String, String>,
393384
executor: CawsCommandExecutor,
394-
gatewayHandle: GatewayConnectionHandle,
395-
envId: String,
385+
envId: WorkspaceIdentifier,
396386
gitSettings: GitSettings,
397387
toolkitInstallSettings: ToolkitInstallSettings,
398-
isSmallInstance: Boolean
399388
): AsyncPromise<Unit> {
400389
val remoteScriptPath = "/tmp/${UUID.randomUUID()}"
401390
val remoteProjectName = (gitSettings as? GitSettings.GitRepoSettings)?.repoName
402391

403-
val steps = buildList<Step> {
392+
val steps = buildList {
404393
add(CopyScripts(remoteScriptPath, executor))
405394

406395
when (gitSettings) {
@@ -430,7 +419,7 @@ class CawsConnectionProvider : GatewayConnectionProvider {
430419
}
431420
}
432421

433-
add(StartBackend(gatewayHandle, remoteScriptPath, remoteProjectName, executor, lifetime, envId, isSmallInstance))
422+
add(StartBackendV2(lifetime, indicator, envId, remoteProjectName))
434423
}
435424

436425
val promise = AsyncPromise<Unit>()

jetbrains-gateway/src/software/aws/toolkits/jetbrains/gateway/connection/AbstractSsmCommandExecutor.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ abstract class AbstractSsmCommandExecutor(private val region: AwsRegion, protect
161161

162162
private fun newSshCommand() = SsmCommandLineFactory(ssmTarget, startSsh(), region).sshCommand()
163163

164+
fun proxyCommand() = SsmCommandLineFactory(ssmTarget, startSsh(), region).generateProxyCommand()
165+
164166
private companion object {
165167
private val LOG = getLogger<AbstractSsmCommandExecutor>()
166168
}

jetbrains-gateway/src/software/aws/toolkits/jetbrains/gateway/connection/SsmCommandLineFactory.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class SsmCommandLineFactory(
5555
}
5656
}
5757

58-
private inner class ProxyCommand(
58+
inner class ProxyCommand(
5959
val exePath: String,
6060
val ssmPayload: String? = null,
6161
val environment: Map<String, String> = emptyMap()
@@ -72,7 +72,7 @@ class SsmCommandLineFactory(
7272
val args = ssmPayload?.let { listOf(it, region.id, "StartSession") }
7373
}
7474

75-
private fun generateProxyCommand(): ProxyCommand {
75+
fun generateProxyCommand(): ProxyCommand {
7676
val ssmPluginJson = """
7777
{
7878
"streamUrl":"${sessionParameters.streamUrl}",
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.gateway.connection.caws
5+
6+
import com.intellij.ssh.PromiscuousSshHostKeyVerifier
7+
import com.intellij.ssh.config.SshConnectionConfig
8+
import com.intellij.ssh.config.SshConnectionConfigService
9+
import com.intellij.ssh.config.SshProxyConfig
10+
import software.aws.toolkits.jetbrains.core.awsClient
11+
import software.aws.toolkits.jetbrains.core.credentials.sono.SonoCredentialManager
12+
13+
class CawsSshConnectionConfigModifier : SshConnectionConfigService.Modifier {
14+
override fun modify(initialHost: String, connectionConfig: SshConnectionConfig): SshConnectionConfig {
15+
if (!initialHost.startsWith(HOST_PREFIX)) {
16+
return connectionConfig
17+
}
18+
19+
val (space, project, envId) = initialHost.substringAfter(HOST_PREFIX).split('/')
20+
val executor = CawsCommandExecutor(
21+
SonoCredentialManager.getInstance(null).getSettingsAndPromptAuth().awsClient(),
22+
ssmTarget = envId,
23+
spaceName = space,
24+
projectName = project
25+
)
26+
27+
return connectionConfig.copy(
28+
proxyConfig = SshProxyConfig.Command(executor.proxyCommand().commandString),
29+
hostKeyVerifier = PromiscuousSshHostKeyVerifier
30+
)
31+
}
32+
33+
companion object {
34+
const val HOST_PREFIX = "aws.codecatalyst:"
35+
}
36+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.gateway.connection.workflow.v2
5+
6+
import com.jetbrains.gateway.ssh.HighLevelHostAccessor
7+
import com.jetbrains.gateway.ssh.HostDeployContext
8+
import com.jetbrains.gateway.ssh.HostDeployInputs
9+
10+
class HostContext(
11+
override var config: Unit?,
12+
override var deployData: HostDeployInputs?,
13+
override val hostAccessor: HighLevelHostAccessor?,
14+
override val remoteProjectPath: String?
15+
) : HostDeployContext<Unit>

0 commit comments

Comments
 (0)