Skip to content

Commit 52fa781

Browse files
committed
Merge branch 'main' into eap
2 parents 2e134ac + 1eb164b commit 52fa781

11 files changed

+238
-129
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,30 @@
33
# coder-gateway Changelog
44

55
## Unreleased
6+
Bug fixes and enhancements included in `2.1.4` release:
67

78
### Added
89
- ability to open a template in the Dashboard
910
- ability to sort by workspace name, or by template name or by workspace status
1011
- a new token is requested when the one persisted is expired
12+
- support for re-using already installed IDE backends
13+
14+
### Changed
15+
- renamed the plugin from `Coder Gateway` to `Gateway`
16+
- workspaces and agents are now resolved and displayed progressively
17+
18+
### Fixed
19+
- icon rendering on `macOS`
20+
- `darwin` agents are now recognized as `macOS`
21+
- unsupported OS warning is displayed only for running workspaces
22+
23+
## 2.1.4 - 2022-12-23
24+
25+
### Added
26+
- ability to open a template in the Dashboard
27+
- ability to sort by workspace name, or by template name or by workspace status
28+
- a new token is requested when the one persisted is expired
29+
- support for re-using already installed IDE backends
1130

1231
### Changed
1332
- renamed the plugin from `Coder Gateway` to `Gateway`

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
pluginGroup=com.coder.gateway
44
pluginName=coder-gateway
55
# SemVer format -> https://semver.org
6-
pluginVersion=2.1.3-eap.0
6+
pluginVersion=2.1.4-eap.0
77
# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
88
# for insight into build numbers and IntelliJ Platform versions.
99
pluginSinceBuild=223.7571.70

src/main/kotlin/com/coder/gateway/CoderGatewayConnectionProvider.kt

Lines changed: 10 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,93 +2,39 @@
22

33
package com.coder.gateway
44

5-
import com.coder.gateway.models.RecentWorkspaceConnection
65
import com.coder.gateway.services.CoderRecentWorkspaceConnectionsService
76
import com.intellij.openapi.components.service
87
import com.intellij.openapi.rd.util.launchUnderBackgroundProgress
9-
import com.intellij.remote.AuthType
10-
import com.intellij.remote.RemoteCredentialsHolder
11-
import com.intellij.ssh.config.unified.SshConfig
128
import com.jetbrains.gateway.api.ConnectionRequestor
139
import com.jetbrains.gateway.api.GatewayConnectionHandle
1410
import com.jetbrains.gateway.api.GatewayConnectionProvider
1511
import com.jetbrains.gateway.api.GatewayUI
16-
import com.jetbrains.gateway.ssh.HighLevelHostAccessor
17-
import com.jetbrains.gateway.ssh.HostDeployInputs
18-
import com.jetbrains.gateway.ssh.IdeInfo
19-
import com.jetbrains.gateway.ssh.IntelliJPlatformProduct
2012
import com.jetbrains.gateway.ssh.SshDeployFlowUtil
2113
import com.jetbrains.gateway.ssh.SshMultistagePanelContext
22-
import com.jetbrains.gateway.ssh.deploy.DeployTargetInfo.DeployWithDownload
2314
import com.jetbrains.rd.util.lifetime.LifetimeDefinition
2415
import kotlinx.coroutines.launch
25-
import java.net.URI
2616
import java.time.Duration
27-
import java.time.LocalDateTime
28-
import java.time.format.DateTimeFormatter
2917

3018
class CoderGatewayConnectionProvider : GatewayConnectionProvider {
3119
private val recentConnectionsService = service<CoderRecentWorkspaceConnectionsService>()
3220

33-
private val localTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm")
34-
3521
override suspend fun connect(parameters: Map<String, String>, requestor: ConnectionRequestor): GatewayConnectionHandle? {
36-
val coderWorkspaceHostname = parameters["coder_workspace_hostname"]
37-
val projectPath = parameters["project_path"]
38-
val ideProductCode = parameters["ide_product_code"]!!
39-
val ideBuildNumber = parameters["ide_build_number"]!!
40-
val ideDownloadLink = parameters["ide_download_link"]!!
41-
val webTerminalLink = parameters["web_terminal_link"]!!
42-
43-
if (coderWorkspaceHostname != null && projectPath != null) {
44-
val sshConfiguration = SshConfig(true).apply {
45-
setHost(coderWorkspaceHostname)
46-
setUsername("coder")
47-
port = 22
48-
authType = AuthType.OPEN_SSH
49-
}
50-
51-
val clientLifetime = LifetimeDefinition()
52-
clientLifetime.launchUnderBackgroundProgress(CoderGatewayBundle.message("gateway.connector.coder.connection.provider.title"), canBeCancelled = true, isIndeterminate = true, project = null) {
53-
val context = SshMultistagePanelContext(
54-
HostDeployInputs.FullySpecified(
55-
remoteProjectPath = projectPath,
56-
deployTarget = DeployWithDownload(
57-
URI(ideDownloadLink),
58-
null,
59-
IdeInfo(
60-
product = IntelliJPlatformProduct.fromProductCode(ideProductCode)!!,
61-
buildNumber = ideBuildNumber
62-
)
63-
),
64-
remoteInfo = HostDeployInputs.WithDeployedWorker(
65-
HighLevelHostAccessor.create(
66-
RemoteCredentialsHolder().apply {
67-
setHost(coderWorkspaceHostname)
68-
userName = "coder"
69-
port = 22
70-
authType = AuthType.OPEN_SSH
71-
},
72-
true
73-
),
74-
HostDeployInputs.WithHostInfo(sshConfiguration)
75-
)
76-
)
22+
val clientLifetime = LifetimeDefinition()
23+
clientLifetime.launchUnderBackgroundProgress(CoderGatewayBundle.message("gateway.connector.coder.connection.provider.title"), canBeCancelled = true, isIndeterminate = true, project = null) {
24+
val context = SshMultistagePanelContext(parameters.toHostDeployInputs())
25+
launch {
26+
@Suppress("UnstableApiUsage") SshDeployFlowUtil.fullDeployCycle(
27+
clientLifetime, context, Duration.ofMinutes(10)
7728
)
78-
launch {
79-
@Suppress("UnstableApiUsage") SshDeployFlowUtil.fullDeployCycle(
80-
clientLifetime, context, Duration.ofMinutes(10)
81-
)
82-
}
8329
}
84-
85-
recentConnectionsService.addRecentConnection(RecentWorkspaceConnection(coderWorkspaceHostname, projectPath, localTimeFormatter.format(LocalDateTime.now()), ideProductCode, ideBuildNumber, ideDownloadLink, webTerminalLink))
86-
GatewayUI.getInstance().reset()
8730
}
31+
32+
recentConnectionsService.addRecentConnection(parameters.toRecentWorkspaceConnection())
33+
GatewayUI.getInstance().reset()
8834
return null
8935
}
9036

9137
override fun isApplicable(parameters: Map<String, String>): Boolean {
92-
return parameters["type"] == "coder"
38+
return parameters.areCoderType()
9339
}
9440
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package com.coder.gateway
2+
3+
import com.coder.gateway.models.RecentWorkspaceConnection
4+
import com.intellij.remote.AuthType
5+
import com.intellij.remote.RemoteCredentialsHolder
6+
import com.intellij.ssh.config.unified.SshConfig
7+
import com.jetbrains.gateway.ssh.HighLevelHostAccessor
8+
import com.jetbrains.gateway.ssh.HostDeployInputs
9+
import com.jetbrains.gateway.ssh.IdeInfo
10+
import com.jetbrains.gateway.ssh.IdeWithStatus
11+
import com.jetbrains.gateway.ssh.IntelliJPlatformProduct
12+
import com.jetbrains.gateway.ssh.deploy.DeployTargetInfo
13+
import java.net.URI
14+
import java.time.LocalDateTime
15+
import java.time.format.DateTimeFormatter
16+
17+
private const val CODER_WORKSPACE_HOSTNAME = "coder_workspace_hostname"
18+
private const val TYPE = "type"
19+
private const val VALUE_FOR_TYPE = "coder"
20+
private const val PROJECT_PATH = "project_path"
21+
private const val IDE_DOWNLOAD_LINK = "ide_download_link"
22+
private const val IDE_PRODUCT_CODE = "ide_product_code"
23+
private const val IDE_BUILD_NUMBER = "ide_build_number"
24+
private const val IDE_PATH_ON_HOST = "ide_path_on_host"
25+
private const val WEB_TERMINAL_LINK = "web_terminal_link"
26+
27+
private val localTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm")
28+
29+
fun RecentWorkspaceConnection.toWorkspaceParams(): Map<String, String> {
30+
val map = mutableMapOf(
31+
TYPE to VALUE_FOR_TYPE,
32+
CODER_WORKSPACE_HOSTNAME to "${this.coderWorkspaceHostname}",
33+
PROJECT_PATH to this.projectPath!!,
34+
IDE_PRODUCT_CODE to IntelliJPlatformProduct.fromProductCode(this.ideProductCode!!)!!.productCode,
35+
IDE_BUILD_NUMBER to "${this.ideBuildNumber}",
36+
WEB_TERMINAL_LINK to "${this.webTerminalLink}"
37+
)
38+
39+
if (!this.downloadSource.isNullOrBlank()) {
40+
map[IDE_DOWNLOAD_LINK] = this.downloadSource!!
41+
} else {
42+
map[IDE_PATH_ON_HOST] = this.idePathOnHost!!
43+
}
44+
return map
45+
}
46+
47+
fun IdeWithStatus.toWorkspaceParams(): Map<String, String> {
48+
val workspaceParams = mutableMapOf(
49+
TYPE to VALUE_FOR_TYPE,
50+
IDE_PRODUCT_CODE to this.product.productCode,
51+
IDE_BUILD_NUMBER to this.buildNumber
52+
)
53+
54+
if (this.download != null) {
55+
workspaceParams[IDE_DOWNLOAD_LINK] = this.download!!.link
56+
}
57+
58+
if (!this.pathOnHost.isNullOrBlank()) {
59+
workspaceParams[IDE_PATH_ON_HOST] = this.pathOnHost!!
60+
}
61+
62+
return workspaceParams
63+
}
64+
65+
fun Map<String, String>.withWorkspaceHostname(hostname: String): Map<String, String> {
66+
val map = this.toMutableMap()
67+
map[CODER_WORKSPACE_HOSTNAME] = hostname
68+
return map
69+
}
70+
71+
fun Map<String, String>.withProjectPath(projectPath: String): Map<String, String> {
72+
val map = this.toMutableMap()
73+
map[PROJECT_PATH] = projectPath
74+
return map
75+
}
76+
77+
fun Map<String, String>.withWebTerminalLink(webTerminalLink: String): Map<String, String> {
78+
val map = this.toMutableMap()
79+
map[WEB_TERMINAL_LINK] = webTerminalLink
80+
return map
81+
}
82+
83+
fun Map<String, String>.areCoderType(): Boolean {
84+
return this[TYPE] == VALUE_FOR_TYPE && !this[CODER_WORKSPACE_HOSTNAME].isNullOrBlank() && !this[PROJECT_PATH].isNullOrBlank()
85+
}
86+
87+
fun Map<String, String>.toSshConfig(): SshConfig {
88+
return SshConfig(true).apply {
89+
setHost(this@toSshConfig.workspaceHostname())
90+
setUsername("coder")
91+
port = 22
92+
authType = AuthType.OPEN_SSH
93+
}
94+
}
95+
96+
suspend fun Map<String, String>.toHostDeployInputs(): HostDeployInputs {
97+
return HostDeployInputs.FullySpecified(
98+
remoteProjectPath = this[PROJECT_PATH]!!,
99+
deployTarget = this.toDeployTargetInfo(),
100+
remoteInfo = HostDeployInputs.WithDeployedWorker(
101+
HighLevelHostAccessor.create(
102+
RemoteCredentialsHolder().apply {
103+
setHost(this@toHostDeployInputs.workspaceHostname())
104+
userName = "coder"
105+
port = 22
106+
authType = AuthType.OPEN_SSH
107+
},
108+
true
109+
),
110+
HostDeployInputs.WithHostInfo(this.toSshConfig())
111+
)
112+
)
113+
}
114+
115+
private fun Map<String, String>.toIdeInfo(): IdeInfo {
116+
return IdeInfo(
117+
product = IntelliJPlatformProduct.fromProductCode(this[IDE_PRODUCT_CODE]!!)!!,
118+
buildNumber = this[IDE_BUILD_NUMBER]!!
119+
)
120+
}
121+
122+
private fun Map<String, String>.toDeployTargetInfo(): DeployTargetInfo {
123+
return if (!this[IDE_DOWNLOAD_LINK].isNullOrBlank()) DeployTargetInfo.DeployWithDownload(
124+
URI(this[IDE_DOWNLOAD_LINK]),
125+
null,
126+
this.toIdeInfo()
127+
)
128+
else DeployTargetInfo.NoDeploy(this[IDE_PATH_ON_HOST]!!, this.toIdeInfo())
129+
}
130+
131+
private fun Map<String, String>.workspaceHostname() = this[CODER_WORKSPACE_HOSTNAME]!!
132+
private fun Map<String, String>.projectPath() = this[PROJECT_PATH]!!
133+
134+
fun Map<String, String>.toRecentWorkspaceConnection(): RecentWorkspaceConnection {
135+
return if (!this[IDE_DOWNLOAD_LINK].isNullOrBlank()) RecentWorkspaceConnection(
136+
this.workspaceHostname(),
137+
this.projectPath(),
138+
localTimeFormatter.format(LocalDateTime.now()),
139+
this[IDE_PRODUCT_CODE]!!,
140+
this[IDE_BUILD_NUMBER]!!,
141+
this[IDE_DOWNLOAD_LINK]!!,
142+
null,
143+
this[WEB_TERMINAL_LINK]!!
144+
) else RecentWorkspaceConnection(
145+
this.workspaceHostname(),
146+
this.projectPath(),
147+
localTimeFormatter.format(LocalDateTime.now()),
148+
this[IDE_PRODUCT_CODE]!!,
149+
this[IDE_BUILD_NUMBER]!!,
150+
null,
151+
this[IDE_PATH_ON_HOST],
152+
this[WEB_TERMINAL_LINK]!!
153+
)
154+
}

src/main/kotlin/com/coder/gateway/models/RecentWorkspaceConnection.kt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import com.intellij.openapi.components.BaseState
44
import com.intellij.util.xmlb.annotations.Attribute
55

66
class RecentWorkspaceConnection() : BaseState(), Comparable<RecentWorkspaceConnection> {
7-
constructor(hostname: String, prjPath: String, openedAt: String, productCode: String, buildNumber: String, source: String, terminalLink: String) : this() {
7+
constructor(hostname: String, prjPath: String, openedAt: String, productCode: String, buildNumber: String, source: String?, idePath: String?, terminalLink: String) : this() {
88
coderWorkspaceHostname = hostname
99
projectPath = prjPath
1010
lastOpened = openedAt
1111
ideProductCode = productCode
1212
ideBuildNumber = buildNumber
1313
downloadSource = source
14+
idePathOnHost = idePath
1415
webTerminalLink = terminalLink
1516
}
1617

@@ -32,6 +33,10 @@ class RecentWorkspaceConnection() : BaseState(), Comparable<RecentWorkspaceConne
3233
@get:Attribute
3334
var downloadSource by string()
3435

36+
37+
@get:Attribute
38+
var idePathOnHost by string()
39+
3540
@get:Attribute
3641
var webTerminalLink by string()
3742

@@ -47,6 +52,7 @@ class RecentWorkspaceConnection() : BaseState(), Comparable<RecentWorkspaceConne
4752
if (ideProductCode != other.ideProductCode) return false
4853
if (ideBuildNumber != other.ideBuildNumber) return false
4954
if (downloadSource != other.downloadSource) return false
55+
if (idePathOnHost != other.idePathOnHost) return false
5056
if (webTerminalLink != other.webTerminalLink) return false
5157

5258
return true
@@ -59,6 +65,7 @@ class RecentWorkspaceConnection() : BaseState(), Comparable<RecentWorkspaceConne
5965
result = 31 * result + (ideProductCode?.hashCode() ?: 0)
6066
result = 31 * result + (ideBuildNumber?.hashCode() ?: 0)
6167
result = 31 * result + (downloadSource?.hashCode() ?: 0)
68+
result = 31 * result + (idePathOnHost?.hashCode() ?: 0)
6269
result = 31 * result + (webTerminalLink?.hashCode() ?: 0)
6370

6471
return result
@@ -80,8 +87,11 @@ class RecentWorkspaceConnection() : BaseState(), Comparable<RecentWorkspaceConne
8087
val m = other.downloadSource?.let { downloadSource?.compareTo(it) }
8188
if (m != null && m != 0) return m
8289

83-
val n = other.webTerminalLink?.let { webTerminalLink?.compareTo(it) }
84-
if (n != null && n != 0) return n
90+
val n = other.idePathOnHost?.let { idePathOnHost?.compareTo(it) }
91+
if (n != null && m != 0) return n
92+
93+
val o = other.webTerminalLink?.let { webTerminalLink?.compareTo(it) }
94+
if (o != null && n != 0) return o
8595

8696
return 0
8797
}

src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@ package com.coder.gateway.sdk
33
import com.coder.gateway.sdk.convertors.InstantConverter
44
import com.coder.gateway.sdk.ex.AuthenticationResponseException
55
import com.coder.gateway.sdk.ex.TemplateResponseException
6-
import com.coder.gateway.sdk.ex.WorkspaceResourcesResponseException
76
import com.coder.gateway.sdk.ex.WorkspaceResponseException
87
import com.coder.gateway.sdk.v2.CoderV2RestFacade
98
import com.coder.gateway.sdk.v2.models.BuildInfo
109
import com.coder.gateway.sdk.v2.models.CreateWorkspaceBuildRequest
1110
import com.coder.gateway.sdk.v2.models.Template
1211
import com.coder.gateway.sdk.v2.models.User
1312
import com.coder.gateway.sdk.v2.models.Workspace
14-
import com.coder.gateway.sdk.v2.models.WorkspaceAgent
1513
import com.coder.gateway.sdk.v2.models.WorkspaceBuild
1614
import com.coder.gateway.sdk.v2.models.WorkspaceTransition
1715
import com.google.gson.Gson
@@ -91,21 +89,6 @@ class CoderRestClientService {
9189
return buildInfoResponse.body()!!
9290
}
9391

94-
/**
95-
* Retrieves the workspace agents a template declares.
96-
* A workspace is a collection of objects like, VMs, containers, cloud DBs, etc...Agents run on compute hosts like VMs or containers.
97-
*
98-
* @throws WorkspaceResourcesResponseException if workspace resources could not be retrieved.
99-
*/
100-
fun workspaceAgentsByTemplate(workspace: Workspace): List<WorkspaceAgent> {
101-
val workspaceResourcesResponse = retroRestClient.templateVersionResources(workspace.latestBuild.templateVersionID).execute()
102-
if (!workspaceResourcesResponse.isSuccessful) {
103-
throw WorkspaceResourcesResponseException("Could not retrieve agents for ${workspace.name} workspace :${workspaceResourcesResponse.code()}, reason: ${workspaceResourcesResponse.message()}")
104-
}
105-
106-
return workspaceResourcesResponse.body()!!.flatMap { it.agents ?: emptyList() }
107-
}
108-
10992
private fun template(templateID: UUID): Template {
11093
val templateResponse = retroRestClient.template(templateID).execute()
11194
if (!templateResponse.isSuccessful) {

0 commit comments

Comments
 (0)