From e3bd944077036ce18f430c7bbd6c1b9e3e613255 Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Fri, 12 Aug 2022 00:50:45 +0300 Subject: [PATCH 01/23] Impl: update the workspace panel every 5 seconds - poll the workspaces and update the view - re-select the previous workspace and enable the next button if necessary - added logic to cancel the polling when back or next buttons are triggered --- CHANGELOG.md | 52 +++++++++--------- .../views/CoderGatewayConnectorWizardView.kt | 2 + .../views/steps/CoderWorkspacesStepView.kt | 55 ++++++++++++++----- .../views/steps/CoderWorkspacesWizardStep.kt | 5 ++ 4 files changed, 76 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0019348e..f2f98de0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,51 +1,53 @@ - - + + # coder-gateway Changelog ## [Unreleased] +### Changed +- workspace panel is now updated every 5 seconds ## [2.0.2] -### Added -- support for displaying working and non-working workspaces -- better support for Light and Dark themes in the "Status" column +### Added +- support for displaying working and non-working workspaces +- better support for Light and Dark themes in the "Status" column -### Fixed -- left panel is no longer visible when a new connection is triggered from Coder's "Recent Workspaces" panel. - This provides consistency with other plugins compatible with Gateway -- the "Select IDE and Project" button in the "Coder Workspaces" view is now disabled when no workspace is selected +### Fixed +- left panel is no longer visible when a new connection is triggered from Coder's "Recent Workspaces" panel. + This provides consistency with other plugins compatible with Gateway +- the "Select IDE and Project" button in the "Coder Workspaces" view is now disabled when no workspace is selected -### Changed +### Changed - the authentication view is now merged with the "Coder Workspaces" view allowing users to quickly change the host ## [2.0.1] ### Fixed -- `Recent Coder Workspaces` label overlaps with the search bar in the `Connections` view -- working workspaces are now listed when there are issues with resolving agents -- list only workspaces owned by the logged user - +- `Recent Coder Workspaces` label overlaps with the search bar in the `Connections` view +- working workspaces are now listed when there are issues with resolving agents +- list only workspaces owned by the logged user + ### Changed -- links to documentation now point to the latest Coder OSS -- simplified main action link text from `Connect to Coder Workspaces` to `Connect to Coder` +- links to documentation now point to the latest Coder OSS +- simplified main action link text from `Connect to Coder Workspaces` to `Connect to Coder` - minimum supported Gateway build is now 222.3739.24 ## [2.0.0] ### Added -- support for Gateway 2022.2 - +- support for Gateway 2022.2 + ### Changed -- Java 17 is now required to run the plugin +- Java 17 is now required to run the plugin - adapted the code to the new SSH API provided by Gateway ## [1.0.0] ### Added -- initial scaffold for Gateway plugin -- browser based authentication on Coder environments -- REST client for Coder V2 public API -- coder-cli orchestration for setting up the SSH configurations for Coder Workspaces -- basic panel to display live Coder Workspaces -- support for multi-agent Workspaces +- initial scaffold for Gateway plugin +- browser based authentication on Coder environments +- REST client for Coder V2 public API +- coder-cli orchestration for setting up the SSH configurations for Coder Workspaces +- basic panel to display live Coder Workspaces +- support for multi-agent Workspaces - Gateway SSH connection to a Coder Workspace \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/views/CoderGatewayConnectorWizardView.kt b/src/main/kotlin/com/coder/gateway/views/CoderGatewayConnectorWizardView.kt index 734a220f..f136d6fb 100644 --- a/src/main/kotlin/com/coder/gateway/views/CoderGatewayConnectorWizardView.kt +++ b/src/main/kotlin/com/coder/gateway/views/CoderGatewayConnectorWizardView.kt @@ -51,6 +51,8 @@ class CoderGatewayConnectorWizardView : BorderLayoutPanel(), Disposable { } private fun previous() { + steps[currentStep].onPrevious() + if (currentStep == 0) { GatewayUI.getInstance().reset() } else { diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index 6888b690..07d27d69 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -16,9 +16,7 @@ import com.coder.gateway.sdk.OS import com.coder.gateway.sdk.ex.AuthenticationResponseException import com.coder.gateway.sdk.getOS import com.coder.gateway.sdk.toURL -import com.coder.gateway.sdk.v2.models.ProvisionerJobStatus import com.coder.gateway.sdk.v2.models.Workspace -import com.coder.gateway.sdk.v2.models.WorkspaceBuildTransition import com.coder.gateway.sdk.withPath import com.intellij.ide.BrowserUtil import com.intellij.ide.IdeBundle @@ -51,11 +49,13 @@ import com.intellij.util.ui.ListTableModel import com.intellij.util.ui.table.IconTableCellRenderer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.zeroturnaround.exec.ProcessExecutor -import java.awt.Color import java.awt.Component import java.awt.Dimension import javax.swing.Icon @@ -88,6 +88,8 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : } } + private var poller: Job? = null + override val component = panel { indent { row { @@ -105,10 +107,12 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : row(CoderGatewayBundle.message("gateway.connector.view.login.url.label")) { textField().resizableColumn().horizontalAlign(HorizontalAlign.FILL).gap(RightGap.SMALL).bindText(wizardModel::coderURL).applyToComponent { addActionListener { + poller?.cancel() loginAndLoadWorkspace() } } button(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text")) { + poller?.cancel() loginAndLoadWorkspace() }.applyToComponent { background = WelcomeScreenUIManager.getMainAssociatedComponentBackground() @@ -211,18 +215,30 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : } private fun loadWorkspaces() { - cs.launch { - val workspaceList = withContext(Dispatchers.IO) { - try { - return@withContext coderClient.workspaces().collectAgents() - } catch (e: Exception) { - logger.error("Could not retrieve workspaces for ${coderClient.me.username} on ${coderClient.coderURL}. Reason: $e") - emptyList() + poller = cs.launch { + while (isActive) { + val workspaceList = withContext(Dispatchers.IO) { + try { + return@withContext coderClient.workspaces().collectAgents() + } catch (e: Exception) { + logger.error("Could not retrieve workspaces for ${coderClient.me.username} on ${coderClient.coderURL}. Reason: $e") + emptyList() + } } - } - // if we just run the update on the main dispatcher, the code will block because it cant get some AWT locks - ApplicationManager.getApplication().invokeLater { listTableModelOfWorkspaces.updateItems(workspaceList) } + val selectedWorkspace = withContext(Dispatchers.Main) { + tableOfWorkspaces.selectedObject + } + + // if we just run the update on the main dispatcher, the code will block because it cant get some AWT locks + ApplicationManager.getApplication().invokeLater { + listTableModelOfWorkspaces.updateItems(workspaceList) + if (selectedWorkspace != null) { + tableOfWorkspaces.selectItem(selectedWorkspace) + } + } + } + delay(5000) } } @@ -279,10 +295,16 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : } } + override fun onPrevious() { + super.onPrevious() + poller?.cancel() + } + override fun onNext(wizardModel: CoderWorkspacesWizardModel): Boolean { val workspace = tableOfWorkspaces.selectedObject if (workspace != null) { wizardModel.selectedWorkspace = workspace + poller?.cancel() return true } return false @@ -387,6 +409,13 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : this.addRows(workspaces) } + private fun TableView.selectItem(workspace: WorkspaceAgentModel) { + this.items.forEachIndexed { index, workspaceAgentModel -> + if (workspaceAgentModel.name == workspace.name) + this.setRowSelectionInterval(index, index) + } + } + companion object { val logger = Logger.getInstance(CoderWorkspacesStepView::class.java.simpleName) } diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesWizardStep.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesWizardStep.kt index bf14fba2..6a24b240 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesWizardStep.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesWizardStep.kt @@ -10,5 +10,10 @@ sealed interface CoderWorkspacesWizardStep { val previousActionText: String fun onInit(wizardModel: CoderWorkspacesWizardModel) + + fun onPrevious() { + + } + fun onNext(wizardModel: CoderWorkspacesWizardModel): Boolean } \ No newline at end of file From 9ed6d9e486fa4d87fb622c528c422a37dd98d32f Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Fri, 12 Aug 2022 00:57:57 +0300 Subject: [PATCH 02/23] Fix: show the workspaces for which the agent can't be retrieved - can't happen while starting/stoping/updating agents - once the workspace is live the poller will retrieve the agents at the next call --- .../gateway/views/steps/CoderWorkspacesStepView.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index 07d27d69..1b8741c1 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -290,8 +290,17 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : .toList() } } catch (e: Exception) { - logger.error("Skipping workspace ${this.name} because we could not retrieve the agent(s). Reason: $e") - emptyList() + logger.warn("Agent(s) for ${this.name} could not be retrieved. Reason: $e") + listOf( + WorkspaceAgentModel( + this.name, + this.templateName, + WorkspaceAgentStatus.from(this), + null, + null, + null + ) + ) } } From 5a877819dfc52fb7261b212fa743756397f2b648 Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Fri, 12 Aug 2022 01:16:22 +0300 Subject: [PATCH 03/23] Impl: workspace version status - new column in the table showing the workspace version --- CHANGELOG.md | 3 ++ .../gateway/models/WorkspaceAgentModel.kt | 3 +- .../gateway/models/WorkspaceVersionStatus.kt | 14 ++++++++ .../views/steps/CoderWorkspacesStepView.kt | 32 ++++++++++++++++++- 4 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/com/coder/gateway/models/WorkspaceVersionStatus.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index f2f98de0..540aad4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ # coder-gateway Changelog ## [Unreleased] +### Added +- support for displaying workspace version + ### Changed - workspace panel is now updated every 5 seconds diff --git a/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentModel.kt b/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentModel.kt index 8979f73d..a255e246 100644 --- a/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentModel.kt +++ b/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentModel.kt @@ -2,12 +2,11 @@ package com.coder.gateway.models import com.coder.gateway.sdk.Arch import com.coder.gateway.sdk.OS -import com.coder.gateway.sdk.v2.models.ProvisionerJobStatus -import com.coder.gateway.sdk.v2.models.WorkspaceBuildTransition data class WorkspaceAgentModel( val name: String, val templateName: String, + val status: WorkspaceVersionStatus, val agentStatus: WorkspaceAgentStatus, val agentOS: OS?, val agentArch: Arch?, diff --git a/src/main/kotlin/com/coder/gateway/models/WorkspaceVersionStatus.kt b/src/main/kotlin/com/coder/gateway/models/WorkspaceVersionStatus.kt new file mode 100644 index 00000000..73480670 --- /dev/null +++ b/src/main/kotlin/com/coder/gateway/models/WorkspaceVersionStatus.kt @@ -0,0 +1,14 @@ +package com.coder.gateway.models + +import com.coder.gateway.sdk.v2.models.Workspace + +enum class WorkspaceVersionStatus(val label: String) { + UPDATED("Up to date"), OUTDATED("Outdated"); + + companion object { + fun from(workspace: Workspace) = when (workspace.outdated) { + true -> OUTDATED + false -> UPDATED + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index 1b8741c1..77f77a81 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -9,6 +9,7 @@ import com.coder.gateway.models.WorkspaceAgentStatus.DELETING import com.coder.gateway.models.WorkspaceAgentStatus.RUNNING import com.coder.gateway.models.WorkspaceAgentStatus.STARTING import com.coder.gateway.models.WorkspaceAgentStatus.STOPPING +import com.coder.gateway.models.WorkspaceVersionStatus import com.coder.gateway.sdk.Arch import com.coder.gateway.sdk.CoderCLIManager import com.coder.gateway.sdk.CoderRestClientService @@ -70,7 +71,13 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : private var wizardModel = CoderWorkspacesWizardModel() private val coderClient: CoderRestClientService = ApplicationManager.getApplication().getService(CoderRestClientService::class.java) - private var listTableModelOfWorkspaces = ListTableModel(WorkspaceIconColumnInfo(""), WorkspaceNameColumnInfo("Name"), WorkspaceTemplateNameColumnInfo("Template"), WorkspaceStatusColumnInfo("Status")) + private var listTableModelOfWorkspaces = ListTableModel( + WorkspaceIconColumnInfo(""), + WorkspaceNameColumnInfo("Name"), + WorkspaceTemplateNameColumnInfo("Template"), + WorkspaceVersionColumnInfo("Version"), + WorkspaceStatusColumnInfo("Status") + ) private var tableOfWorkspaces = TableView(listTableModelOfWorkspaces).apply { rowSelectionAllowed = true columnSelectionAllowed = false @@ -255,6 +262,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : WorkspaceAgentModel( this.name, this.templateName, + WorkspaceVersionStatus.from(this), WorkspaceAgentStatus.from(this), null, null, @@ -268,6 +276,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : WorkspaceAgentModel( this.name, this.templateName, + WorkspaceVersionStatus.from(this), WorkspaceAgentStatus.from(this), OS.from(agents[0].operatingSystem), Arch.from(agents[0].architecture), @@ -281,6 +290,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : WorkspaceAgentModel( workspaceName, this.templateName, + WorkspaceVersionStatus.from(this), WorkspaceAgentStatus.from(this), OS.from(agent.operatingSystem), Arch.from(agent.architecture), @@ -295,6 +305,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : WorkspaceAgentModel( this.name, this.templateName, + WorkspaceVersionStatus.from(this), WorkspaceAgentStatus.from(this), null, null, @@ -386,6 +397,25 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : } } + private class WorkspaceVersionColumnInfo(columnName: String) : ColumnInfo(columnName) { + override fun valueOf(workspace: WorkspaceAgentModel?): String? { + return workspace?.status?.label + } + + override fun getRenderer(item: WorkspaceAgentModel?): TableCellRenderer { + return object : DefaultTableCellRenderer() { + override fun getTableCellRendererComponent(table: JTable, value: Any, isSelected: Boolean, hasFocus: Boolean, row: Int, column: Int): Component { + super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column) + if (value is String) { + text = value + } + font = JBFont.h3() + return this + } + } + } + } + private class WorkspaceStatusColumnInfo(columnName: String) : ColumnInfo(columnName) { override fun valueOf(workspace: WorkspaceAgentModel?): String? { return workspace?.agentStatus?.label From fd3a4beae93a24b24b733c320157c3af21e812e4 Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Fri, 12 Aug 2022 01:20:02 +0300 Subject: [PATCH 04/23] Fix: render workspace name as bold --- .../com/coder/gateway/views/steps/CoderWorkspacesStepView.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index 77f77a81..993946bb 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -371,7 +371,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : if (value is String) { text = value } - font = JBFont.h3() + font = JBFont.h3().asBold() return this } } @@ -442,7 +442,6 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : } } - private fun ListTableModel.updateItems(workspaces: Collection) { while (this.rowCount > 0) this.removeRow(0) this.addRows(workspaces) From 45c17f4451232d4d1f96c8b51b20b2fe882dc346 Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Fri, 12 Aug 2022 23:19:19 +0300 Subject: [PATCH 05/23] Impl: add table action toolbar - with two buttons for starting/stopping a workspace - icons for start&stop --- .../com/coder/gateway/icons/CoderIcons.kt | 3 ++ .../views/steps/CoderWorkspacesStepView.kt | 31 +++++++++++++++++-- src/main/resources/run.svg | 4 +++ src/main/resources/run_dark.svg | 4 +++ src/main/resources/stop.svg | 4 +++ src/main/resources/stop_dark.svg | 4 +++ 6 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 src/main/resources/run.svg create mode 100644 src/main/resources/run_dark.svg create mode 100644 src/main/resources/stop.svg create mode 100644 src/main/resources/stop_dark.svg diff --git a/src/main/kotlin/com/coder/gateway/icons/CoderIcons.kt b/src/main/kotlin/com/coder/gateway/icons/CoderIcons.kt index 2d084eb0..7f59fcee 100644 --- a/src/main/kotlin/com/coder/gateway/icons/CoderIcons.kt +++ b/src/main/kotlin/com/coder/gateway/icons/CoderIcons.kt @@ -9,6 +9,9 @@ object CoderIcons { val OPEN_TERMINAL = IconLoader.getIcon("open_terminal.svg", javaClass) + val RUN = IconLoader.getIcon("run.svg", javaClass) + val STOP = IconLoader.getIcon("stop.svg", javaClass) + val WINDOWS = IconLoader.getIcon("windows.svg", javaClass) val MACOS = IconLoader.getIcon("macOS.svg", javaClass) val LINUX = IconLoader.getIcon("linux.svg", javaClass) diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index 993946bb..d1753572 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -19,9 +19,12 @@ import com.coder.gateway.sdk.getOS import com.coder.gateway.sdk.toURL import com.coder.gateway.sdk.v2.models.Workspace import com.coder.gateway.sdk.withPath +import com.intellij.CommonBundle +import com.intellij.icons.AllIcons import com.intellij.ide.BrowserUtil import com.intellij.ide.IdeBundle import com.intellij.openapi.Disposable +import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ModalityState import com.intellij.openapi.application.invokeAndWaitIfNeeded @@ -31,8 +34,10 @@ import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.Task import com.intellij.openapi.ui.panel.ComponentPanelBuilder import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreenUIManager +import com.intellij.ui.AnActionButton import com.intellij.ui.AppIcon import com.intellij.ui.JBColor +import com.intellij.ui.ToolbarDecorator import com.intellij.ui.components.JBTextField import com.intellij.ui.components.dialog import com.intellij.ui.dsl.builder.BottomGap @@ -76,8 +81,8 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : WorkspaceNameColumnInfo("Name"), WorkspaceTemplateNameColumnInfo("Template"), WorkspaceVersionColumnInfo("Version"), - WorkspaceStatusColumnInfo("Status") - ) + WorkspaceStatusColumnInfo("Status")) + private var tableOfWorkspaces = TableView(listTableModelOfWorkspaces).apply { rowSelectionAllowed = true columnSelectionAllowed = false @@ -93,7 +98,15 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : selectionModel.addListSelectionListener { enableNextButtonCallback(selectedObject != null && selectedObject?.agentStatus == RUNNING) } + } + + private val toolbar = ToolbarDecorator.createDecorator(tableOfWorkspaces) + .disableAddAction() + .disableRemoveAction() + .disableUpDownActions() + .addExtraAction(StartWorkspaceAction()) + .addExtraAction(StopWorkspaceAction()) private var poller: Job? = null @@ -127,7 +140,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : cell() } row { - scrollCell(tableOfWorkspaces).resizableColumn().horizontalAlign(HorizontalAlign.FILL).verticalAlign(VerticalAlign.FILL) + scrollCell(toolbar.createPanel()).resizableColumn().horizontalAlign(HorizontalAlign.FILL).verticalAlign(VerticalAlign.FILL) cell() }.topGap(TopGap.NONE).resizableRow() @@ -137,6 +150,18 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : override val previousActionText = IdeBundle.message("button.back") override val nextActionText = CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.next.text") + private inner class StartWorkspaceAction : AnActionButton("Start Workspace", "Start Workspace", CoderIcons.RUN) { + override fun actionPerformed(p0: AnActionEvent) { + TODO("Not yet implemented") + } + } + + private inner class StopWorkspaceAction : AnActionButton("Stop Workspace", "Stop Workspace", CoderIcons.STOP) { + override fun actionPerformed(p0: AnActionEvent) { + TODO("Not yet implemented") + } + } + override fun onInit(wizardModel: CoderWorkspacesWizardModel) { enableNextButtonCallback(false) } diff --git a/src/main/resources/run.svg b/src/main/resources/run.svg new file mode 100644 index 00000000..dc1a7561 --- /dev/null +++ b/src/main/resources/run.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/run_dark.svg b/src/main/resources/run_dark.svg new file mode 100644 index 00000000..d9eee730 --- /dev/null +++ b/src/main/resources/run_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/stop.svg b/src/main/resources/stop.svg new file mode 100644 index 00000000..96147a71 --- /dev/null +++ b/src/main/resources/stop.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/stop_dark.svg b/src/main/resources/stop_dark.svg new file mode 100644 index 00000000..2826ade1 --- /dev/null +++ b/src/main/resources/stop_dark.svg @@ -0,0 +1,4 @@ + + + + From 55e14ab684b9d92140446524da13e3bf7e992512 Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Fri, 12 Aug 2022 23:26:40 +0300 Subject: [PATCH 06/23] Fix: enable table antialiasing --- .../com/coder/gateway/views/steps/CoderWorkspacesStepView.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index d1753572..478ea96f 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -84,6 +84,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : WorkspaceStatusColumnInfo("Status")) private var tableOfWorkspaces = TableView(listTableModelOfWorkspaces).apply { + setEnableAntialiasing(true) rowSelectionAllowed = true columnSelectionAllowed = false tableHeader.reorderingAllowed = false From cce4f39c6bbc00b8dfbf91ba049355c3b179c06c Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Fri, 12 Aug 2022 23:29:37 +0300 Subject: [PATCH 07/23] Use strings for workspace actions from localization bundle --- .../com/coder/gateway/views/steps/CoderWorkspacesStepView.kt | 4 ++-- src/main/resources/messages/CoderGatewayBundle.properties | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index 478ea96f..b6401d59 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -151,13 +151,13 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : override val previousActionText = IdeBundle.message("button.back") override val nextActionText = CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.next.text") - private inner class StartWorkspaceAction : AnActionButton("Start Workspace", "Start Workspace", CoderIcons.RUN) { + private inner class StartWorkspaceAction : AnActionButton(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.start.text"), CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.start.text"), CoderIcons.RUN) { override fun actionPerformed(p0: AnActionEvent) { TODO("Not yet implemented") } } - private inner class StopWorkspaceAction : AnActionButton("Stop Workspace", "Stop Workspace", CoderIcons.STOP) { + private inner class StopWorkspaceAction : AnActionButton(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.stop.text"), CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.stop.text"), CoderIcons.STOP) { override fun actionPerformed(p0: AnActionEvent) { TODO("Not yet implemented") } diff --git a/src/main/resources/messages/CoderGatewayBundle.properties b/src/main/resources/messages/CoderGatewayBundle.properties index 4fccebb1..3b824916 100644 --- a/src/main/resources/messages/CoderGatewayBundle.properties +++ b/src/main/resources/messages/CoderGatewayBundle.properties @@ -10,6 +10,8 @@ gateway.connector.view.coder.workspaces.comment=Self-hosted developer workspaces gateway.connector.view.coder.workspaces.connect.text=Connect gateway.connector.view.coder.workspaces.cli.downloader.dialog.title=Authenticate and setup coder gateway.connector.view.coder.workspaces.next.text=Select IDE and Project +gateway.connector.view.coder.workspaces.start.text=Start Workspace +gateway.connector.view.coder.workspaces.stop.text=Stop Workspace gateway.connector.view.coder.remoteproject.loading.text=Retrieving products... gateway.connector.view.coder.remoteproject.ide.error.text=Could not retrieve any IDE for workspace {0} because an error was encountered gateway.connector.view.coder.remoteproject.next.text=Start IDE and Connect From eb89d2c44fd6adbeac103b8e9a7e5e3404a5dcdd Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Fri, 12 Aug 2022 23:44:23 +0300 Subject: [PATCH 08/23] Fix: enable or disable the workspace actions depending on the workspace state --- .../views/steps/CoderWorkspacesStepView.kt | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index b6401d59..c506a163 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -6,8 +6,10 @@ import com.coder.gateway.models.CoderWorkspacesWizardModel import com.coder.gateway.models.WorkspaceAgentModel import com.coder.gateway.models.WorkspaceAgentStatus import com.coder.gateway.models.WorkspaceAgentStatus.DELETING +import com.coder.gateway.models.WorkspaceAgentStatus.FAILED import com.coder.gateway.models.WorkspaceAgentStatus.RUNNING import com.coder.gateway.models.WorkspaceAgentStatus.STARTING +import com.coder.gateway.models.WorkspaceAgentStatus.STOPPED import com.coder.gateway.models.WorkspaceAgentStatus.STOPPING import com.coder.gateway.models.WorkspaceVersionStatus import com.coder.gateway.sdk.Arch @@ -19,8 +21,6 @@ import com.coder.gateway.sdk.getOS import com.coder.gateway.sdk.toURL import com.coder.gateway.sdk.v2.models.Workspace import com.coder.gateway.sdk.withPath -import com.intellij.CommonBundle -import com.intellij.icons.AllIcons import com.intellij.ide.BrowserUtil import com.intellij.ide.IdeBundle import com.intellij.openapi.Disposable @@ -81,7 +81,11 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : WorkspaceNameColumnInfo("Name"), WorkspaceTemplateNameColumnInfo("Template"), WorkspaceVersionColumnInfo("Version"), - WorkspaceStatusColumnInfo("Status")) + WorkspaceStatusColumnInfo("Status") + ) + + private val startWorkspaceAction = StartWorkspaceAction() + private val stopWorkspaceAction = StopWorkspaceAction() private var tableOfWorkspaces = TableView(listTableModelOfWorkspaces).apply { setEnableAntialiasing(true) @@ -98,16 +102,30 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : setSelectionMode(ListSelectionModel.SINGLE_SELECTION) selectionModel.addListSelectionListener { enableNextButtonCallback(selectedObject != null && selectedObject?.agentStatus == RUNNING) - } + when (selectedObject?.agentStatus) { + RUNNING -> { + startWorkspaceAction.isEnabled = false + stopWorkspaceAction.isEnabled = true + } + STOPPED, FAILED -> { + startWorkspaceAction.isEnabled = true + stopWorkspaceAction.isEnabled = false + } + + else -> { + disableAllWorkspaceActions() + } + } + } } - + private val toolbar = ToolbarDecorator.createDecorator(tableOfWorkspaces) .disableAddAction() .disableRemoveAction() .disableUpDownActions() - .addExtraAction(StartWorkspaceAction()) - .addExtraAction(StopWorkspaceAction()) + .addExtraAction(startWorkspaceAction) + .addExtraAction(stopWorkspaceAction) private var poller: Job? = null @@ -163,8 +181,14 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : } } + private fun disableAllWorkspaceActions() { + startWorkspaceAction.isEnabled = false + stopWorkspaceAction.isEnabled = false + } + override fun onInit(wizardModel: CoderWorkspacesWizardModel) { enableNextButtonCallback(false) + disableAllWorkspaceActions() } private fun loginAndLoadWorkspace() { From 86654c3e4d5ed848cf2321cdc928f8b58f0e106a Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Sat, 13 Aug 2022 00:40:02 +0300 Subject: [PATCH 09/23] Impl: add REST calls for starting/stopping workspaces --- .../gateway/sdk/CoderRestClientService.kt | 24 ++++++++++ .../coder/gateway/sdk/v2/CoderV2RestFacade.kt | 10 +++++ .../sdk/v2/models/CreateParameterRequest.kt | 12 +++++ .../v2/models/CreateWorkspaceBuildRequest.kt | 44 +++++++++++++++++++ 4 files changed, 90 insertions(+) create mode 100644 src/main/kotlin/com/coder/gateway/sdk/v2/models/CreateParameterRequest.kt create mode 100644 src/main/kotlin/com/coder/gateway/sdk/v2/models/CreateWorkspaceBuildRequest.kt diff --git a/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt b/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt index 0e37889c..b8961f58 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt @@ -6,9 +6,11 @@ import com.coder.gateway.sdk.ex.WorkspaceResourcesResponseException import com.coder.gateway.sdk.ex.WorkspaceResponseException import com.coder.gateway.sdk.v2.CoderV2RestFacade import com.coder.gateway.sdk.v2.models.BuildInfo +import com.coder.gateway.sdk.v2.models.CreateWorkspaceBuildRequest import com.coder.gateway.sdk.v2.models.User import com.coder.gateway.sdk.v2.models.Workspace import com.coder.gateway.sdk.v2.models.WorkspaceAgent +import com.coder.gateway.sdk.v2.models.WorkspaceBuild import com.google.gson.Gson import com.google.gson.GsonBuilder import com.intellij.openapi.components.Service @@ -20,8 +22,10 @@ import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import java.net.CookieManager +import java.net.HttpURLConnection.HTTP_CREATED import java.net.URL import java.time.Instant +import java.util.UUID @Service(Service.Level.APP) class CoderRestClientService { @@ -110,4 +114,24 @@ class CoderRestClientService { return workspaceResourcesResponse.body()!!.flatMap { it.agents ?: emptyList() } } + + fun startWorkspace(workspaceID: UUID, workspaceName: String): WorkspaceBuild { + val buildRequest = CreateWorkspaceBuildRequest(null, "start", null, null, null) + val buildResponse = retroRestClient.createWorkspaceBuild(workspaceID, buildRequest).execute() + if (buildResponse.code() != HTTP_CREATED) { + throw WorkspaceResponseException("Failed to build workspace ${workspaceName}: ${buildResponse.code()}, reason: ${buildResponse.message()}") + } + + return buildResponse.body()!! + } + + fun stopWorkspace(workspaceID: UUID, workspaceName: String): WorkspaceBuild { + val buildRequest = CreateWorkspaceBuildRequest(null, "stop", null, null, null) + val buildResponse = retroRestClient.createWorkspaceBuild(workspaceID, buildRequest).execute() + if (buildResponse.code() != HTTP_CREATED) { + throw WorkspaceResponseException("Failed to stop workspace ${workspaceName}: ${buildResponse.code()}, reason: ${buildResponse.message()}") + } + + return buildResponse.body()!! + } } \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/CoderV2RestFacade.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/CoderV2RestFacade.kt index 0fcaa20e..b08ffd26 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/v2/CoderV2RestFacade.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/CoderV2RestFacade.kt @@ -1,11 +1,15 @@ package com.coder.gateway.sdk.v2 import com.coder.gateway.sdk.v2.models.BuildInfo +import com.coder.gateway.sdk.v2.models.CreateWorkspaceBuildRequest import com.coder.gateway.sdk.v2.models.User import com.coder.gateway.sdk.v2.models.Workspace +import com.coder.gateway.sdk.v2.models.WorkspaceBuild import com.coder.gateway.sdk.v2.models.WorkspaceResource import retrofit2.Call +import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.POST import retrofit2.http.Path import retrofit2.http.Query import java.util.UUID @@ -29,4 +33,10 @@ interface CoderV2RestFacade { @GET("api/v2/workspacebuilds/{buildID}/resources") fun workspaceResourceByBuild(@Path("buildID") build: UUID): Call> + + /** + * Queues a new build to occur for a workspace. + */ + @POST("api/v2/workspaces/{workspaceID}/builds") + fun createWorkspaceBuild(@Path("workspaceID") workspaceID: UUID, @Body createWorkspaceBuildRequest: CreateWorkspaceBuildRequest): Call } \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/CreateParameterRequest.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/CreateParameterRequest.kt new file mode 100644 index 00000000..782c036c --- /dev/null +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/CreateParameterRequest.kt @@ -0,0 +1,12 @@ +package com.coder.gateway.sdk.v2.models + +import com.google.gson.annotations.SerializedName +import java.util.UUID + +data class CreateParameterRequest( + @SerializedName("copy_from_parameter") val cloneID: UUID?, + @SerializedName("name") val name: String, + @SerializedName("source_value") val sourceValue: String, + @SerializedName("source_scheme") val sourceScheme: String, + @SerializedName("destination_scheme") val destinationScheme: String +) \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/CreateWorkspaceBuildRequest.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/CreateWorkspaceBuildRequest.kt new file mode 100644 index 00000000..690ab029 --- /dev/null +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/CreateWorkspaceBuildRequest.kt @@ -0,0 +1,44 @@ +package com.coder.gateway.sdk.v2.models + +import com.google.gson.annotations.SerializedName +import java.util.UUID + +data class CreateWorkspaceBuildRequest( + @SerializedName("template_version_id") val templateVersionID: UUID?, + @SerializedName("transition") val transition: String, + @SerializedName("dry_run") val dryRun: Boolean?, + @SerializedName("state") val state: Array?, + @SerializedName("parameter_values") val parameterValues: Array? +) { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as CreateWorkspaceBuildRequest + + if (templateVersionID != other.templateVersionID) return false + if (transition != other.transition) return false + if (dryRun != other.dryRun) return false + if (state != null) { + if (other.state == null) return false + if (!state.contentEquals(other.state)) return false + } else if (other.state != null) return false + if (parameterValues != null) { + if (other.parameterValues == null) return false + if (!parameterValues.contentEquals(other.parameterValues)) return false + } else if (other.parameterValues != null) return false + + return true + } + + override fun hashCode(): Int { + var result = templateVersionID?.hashCode() ?: 0 + result = 31 * result + transition.hashCode() + result = 31 * result + (dryRun?.hashCode() ?: 0) + result = 31 * result + (state?.contentHashCode() ?: 0) + result = 31 * result + (parameterValues?.contentHashCode() ?: 0) + return result + } +} + From f9db23e7b36776280c55951b559f2c6f1e6fe6a6 Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Sat, 13 Aug 2022 00:42:13 +0300 Subject: [PATCH 10/23] Impl: add support for starting&stopping a workspace --- CHANGELOG.md | 1 + .../gateway/models/WorkspaceAgentModel.kt | 3 ++ .../views/steps/CoderWorkspacesStepView.kt | 37 ++++++++++++++++++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 540aad4a..b8a06322 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ## [Unreleased] ### Added - support for displaying workspace version +- support for managing the lifecycle of a workspace, i.e. start and stop ### Changed - workspace panel is now updated every 5 seconds diff --git a/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentModel.kt b/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentModel.kt index a255e246..aa603c31 100644 --- a/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentModel.kt +++ b/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentModel.kt @@ -2,8 +2,11 @@ package com.coder.gateway.models import com.coder.gateway.sdk.Arch import com.coder.gateway.sdk.OS +import java.util.UUID data class WorkspaceAgentModel( + val workspaceID: UUID, + val workspaceName: String, val name: String, val templateName: String, val status: WorkspaceVersionStatus, diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index c506a163..da265c54 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -17,6 +17,7 @@ import com.coder.gateway.sdk.CoderCLIManager import com.coder.gateway.sdk.CoderRestClientService import com.coder.gateway.sdk.OS import com.coder.gateway.sdk.ex.AuthenticationResponseException +import com.coder.gateway.sdk.ex.WorkspaceResponseException import com.coder.gateway.sdk.getOS import com.coder.gateway.sdk.toURL import com.coder.gateway.sdk.v2.models.Workspace @@ -171,13 +172,37 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : private inner class StartWorkspaceAction : AnActionButton(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.start.text"), CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.start.text"), CoderIcons.RUN) { override fun actionPerformed(p0: AnActionEvent) { - TODO("Not yet implemented") + if (tableOfWorkspaces.selectedObject != null) { + val workspace = tableOfWorkspaces.selectedObject as WorkspaceAgentModel + cs.launch { + withContext(Dispatchers.IO) { + try { + coderClient.startWorkspace(workspace.workspaceID, workspace.workspaceName) + startWorkspaceAction.isEnabled = false + } catch (e: WorkspaceResponseException) { + logger.warn("Could not build workspace ${workspace.name}, reason: $e") + } + } + } + } } } private inner class StopWorkspaceAction : AnActionButton(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.stop.text"), CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.stop.text"), CoderIcons.STOP) { override fun actionPerformed(p0: AnActionEvent) { - TODO("Not yet implemented") + if (tableOfWorkspaces.selectedObject != null) { + val workspace = tableOfWorkspaces.selectedObject as WorkspaceAgentModel + cs.launch { + withContext(Dispatchers.IO) { + try { + coderClient.stopWorkspace(workspace.workspaceID, workspace.workspaceName) + stopWorkspaceAction.isEnabled = false + } catch (e: WorkspaceResponseException) { + logger.warn("Could not stop workspace ${workspace.name}, reason: $e") + } + } + } + } } } @@ -310,6 +335,8 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : 0 -> { listOf( WorkspaceAgentModel( + this.id, + this.name, this.name, this.templateName, WorkspaceVersionStatus.from(this), @@ -324,6 +351,8 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : 1 -> { listOf( WorkspaceAgentModel( + this.id, + this.name, this.name, this.templateName, WorkspaceVersionStatus.from(this), @@ -338,6 +367,8 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : else -> agents.map { agent -> val workspaceName = "${this.name}.${agent.name}" WorkspaceAgentModel( + this.id, + this.name, workspaceName, this.templateName, WorkspaceVersionStatus.from(this), @@ -353,6 +384,8 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : logger.warn("Agent(s) for ${this.name} could not be retrieved. Reason: $e") listOf( WorkspaceAgentModel( + this.id, + this.name, this.name, this.templateName, WorkspaceVersionStatus.from(this), From 7d7c58c9f6c65ccce8c50dc0db737e344b8c579d Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Mon, 15 Aug 2022 22:10:25 +0300 Subject: [PATCH 11/23] Fix: consistent icon theming for start/stop workspace actions --- src/main/resources/run.svg | 10 ++++++---- src/main/resources/run_dark.svg | 10 ++++++---- src/main/resources/stop.svg | 10 ++++++---- src/main/resources/stop_dark.svg | 8 +++++--- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/main/resources/run.svg b/src/main/resources/run.svg index dc1a7561..d0f970ed 100644 --- a/src/main/resources/run.svg +++ b/src/main/resources/run.svg @@ -1,4 +1,6 @@ - - - - + + + + + + \ No newline at end of file diff --git a/src/main/resources/run_dark.svg b/src/main/resources/run_dark.svg index d9eee730..25c1892f 100644 --- a/src/main/resources/run_dark.svg +++ b/src/main/resources/run_dark.svg @@ -1,4 +1,6 @@ - - - - + + + + + + \ No newline at end of file diff --git a/src/main/resources/stop.svg b/src/main/resources/stop.svg index 96147a71..8347961b 100644 --- a/src/main/resources/stop.svg +++ b/src/main/resources/stop.svg @@ -1,4 +1,6 @@ - - - - + + + + + + \ No newline at end of file diff --git a/src/main/resources/stop_dark.svg b/src/main/resources/stop_dark.svg index 2826ade1..6392389f 100644 --- a/src/main/resources/stop_dark.svg +++ b/src/main/resources/stop_dark.svg @@ -1,4 +1,6 @@ - - - + + + + + From d1b1ba0d267f4702bd1d8bcdf752f84ec8b2e040 Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Mon, 15 Aug 2022 23:27:00 +0300 Subject: [PATCH 12/23] Log only request and response lines - not the whole body --- src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt b/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt index b8961f58..b37464a5 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt @@ -53,7 +53,7 @@ class CoderRestClientService { .create() val interceptor = HttpLoggingInterceptor() - interceptor.setLevel(HttpLoggingInterceptor.Level.BODY) + interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC) retroRestClient = Retrofit.Builder() .baseUrl(url.toString()) .client( From 53577a77405f8103289064abca59625857a70c70 Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Mon, 15 Aug 2022 23:47:01 +0300 Subject: [PATCH 13/23] Fix: terminal link for workspaces with a single agent - resolves #65 --- CHANGELOG.md | 12 ++++++++-- .../steps/CoderLocateRemoteProjectStepView.kt | 2 +- .../views/steps/CoderWorkspacesStepView.kt | 23 +++---------------- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8a06322..5cda7243 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,18 +3,26 @@ # coder-gateway Changelog ## [Unreleased] + ### Added + - support for displaying workspace version - support for managing the lifecycle of a workspace, i.e. start and stop +### Fixed + +- terminal link for workspaces with a single agent + ### Changed + - workspace panel is now updated every 5 seconds ## [2.0.2] + ### Added -- support for displaying working and non-working workspaces -- better support for Light and Dark themes in the "Status" column +- support for displaying working and non-working workspaces +- better support for Light and Dark themes in the "Status" column ### Fixed - left panel is no longer visible when a new connection is triggered from Coder's "Recent Workspaces" panel. diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderLocateRemoteProjectStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderLocateRemoteProjectStepView.kt index 7d494748..60857104 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderLocateRemoteProjectStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderLocateRemoteProjectStepView.kt @@ -111,7 +111,7 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit tfProject.text = if (selectedWorkspace.homeDirectory.isNullOrBlank()) "/home" else selectedWorkspace.homeDirectory titleLabel.text = CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.choose.text", selectedWorkspace.name) - terminalLink.url = "${coderClient.coderURL}/@${coderClient.me.username}/${selectedWorkspace.name}.coder/terminal" + terminalLink.url = "${coderClient.coderURL}/@${coderClient.me.username}/${selectedWorkspace.name}/terminal" cs.launch { logger.info("Retrieving available IDE's for ${selectedWorkspace.name} workspace...") diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index da265c54..97c9fc53 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -348,28 +348,12 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : ) } - 1 -> { - listOf( - WorkspaceAgentModel( - this.id, - this.name, - this.name, - this.templateName, - WorkspaceVersionStatus.from(this), - WorkspaceAgentStatus.from(this), - OS.from(agents[0].operatingSystem), - Arch.from(agents[0].architecture), - agents[0].directory - ) - ) - } - else -> agents.map { agent -> - val workspaceName = "${this.name}.${agent.name}" + val workspaceWithAgentName = "${this.name}.${agent.name}" WorkspaceAgentModel( this.id, this.name, - workspaceName, + workspaceWithAgentName, this.templateName, WorkspaceVersionStatus.from(this), WorkspaceAgentStatus.from(this), @@ -377,8 +361,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : Arch.from(agent.architecture), agent.directory ) - } - .toList() + }.toList() } } catch (e: Exception) { logger.warn("Agent(s) for ${this.name} could not be retrieved. Reason: $e") From 43a1f6f5327c799873dad2ae763e90297417fb1f Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Tue, 16 Aug 2022 00:00:42 +0300 Subject: [PATCH 14/23] Upgrade to latest Gateway build - minimum supported build is now 222.3739.40 --- CHANGELOG.md | 12 +++++------- gradle.properties | 6 +++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cda7243..d81edaa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,19 +3,16 @@ # coder-gateway Changelog ## [Unreleased] - ### Added - - support for displaying workspace version - support for managing the lifecycle of a workspace, i.e. start and stop -### Fixed - -- terminal link for workspaces with a single agent - ### Changed - - workspace panel is now updated every 5 seconds +- minimum supported Gateway build is now 222.3739.40 + +### Fixed +- terminal link for workspaces with a single agent ## [2.0.2] @@ -25,6 +22,7 @@ - better support for Light and Dark themes in the "Status" column ### Fixed + - left panel is no longer visible when a new connection is triggered from Coder's "Recent Workspaces" panel. This provides consistency with other plugins compatible with Gateway - the "Select IDE and Project" button in the "Coder Workspaces" view is now disabled when no workspace is selected diff --git a/gradle.properties b/gradle.properties index 90e0c3ec..eaeb837f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,13 +6,13 @@ pluginName=coder-gateway pluginVersion=2.0.2 # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html # for insight into build numbers and IntelliJ Platform versions. -pluginSinceBuild=222.3739.24 +pluginSinceBuild=222.3739.40 pluginUntilBuild=222.* # IntelliJ Platform Properties -> https://github.com/JetBrains/gradle-intellij-plugin#intellij-platform-properties # Gateway available build versions https://www.jetbrains.com/intellij-repository/snapshots and https://www.jetbrains.com/intellij-repository/releases platformType=GW -platformVersion=222.3739.24-CUSTOM-SNAPSHOT -instrumentationCompiler=222.3739.24-CUSTOM-SNAPSHOT +platformVersion=222.3739.40-CUSTOM-SNAPSHOT +instrumentationCompiler=222.3739.40-CUSTOM-SNAPSHOT platformDownloadSources=true # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 From 869e99c8053d4471252da98d0c378358a5e9157e Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Tue, 16 Aug 2022 00:07:01 +0300 Subject: [PATCH 15/23] Fix: poll the workspaces every 5 seconds --- .../com/coder/gateway/views/steps/CoderWorkspacesStepView.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index 97c9fc53..b52b319e 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -297,6 +297,8 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : } private fun loadWorkspaces() { + poller?.cancel() + poller = cs.launch { while (isActive) { val workspaceList = withContext(Dispatchers.IO) { @@ -319,8 +321,8 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : tableOfWorkspaces.selectItem(selectedWorkspace) } } + delay(5000) } - delay(5000) } } From e2f8fe647561053b5ab8fdd42c87f282f0896723 Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Wed, 17 Aug 2022 00:32:24 +0300 Subject: [PATCH 16/23] Fix: retrieve agents from the template - template is more reliable than a workspace build job (which can be one that stops) - resolves #67 --- CHANGELOG.md | 1 + .../com/coder/gateway/sdk/CoderRestClientService.kt | 8 ++++---- .../kotlin/com/coder/gateway/sdk/v2/CoderV2RestFacade.kt | 4 ++-- .../coder/gateway/views/steps/CoderWorkspacesStepView.kt | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d81edaa8..53da6fc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Changed - workspace panel is now updated every 5 seconds +- combinations of workspace names and agent names are now listed even when a workspace is down - minimum supported Gateway build is now 222.3739.40 ### Fixed diff --git a/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt b/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt index b37464a5..fc975209 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt @@ -101,13 +101,13 @@ class CoderRestClientService { } /** - * Retrieves the workspace agents. A workspace is a collection of objects like, VMs, containers, cloud DBs, etc... - * Agents run on compute hosts like VMs or containers. + * Retrieves the workspace agents a template declares. + * A workspace is a collection of objects like, VMs, containers, cloud DBs, etc...Agents run on compute hosts like VMs or containers. * * @throws WorkspaceResourcesResponseException if workspace resources could not be retrieved. */ - fun workspaceAgents(workspace: Workspace): List { - val workspaceResourcesResponse = retroRestClient.workspaceResourceByBuild(workspace.latestBuild.id).execute() + fun workspaceAgentsByTemplate(workspace: Workspace): List { + val workspaceResourcesResponse = retroRestClient.templateVersionResources(workspace.latestBuild.templateVersionID).execute() if (!workspaceResourcesResponse.isSuccessful) { throw WorkspaceResourcesResponseException("Could not retrieve agents for ${workspace.name} workspace :${workspaceResourcesResponse.code()}, reason: ${workspaceResourcesResponse.message()}") } diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/CoderV2RestFacade.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/CoderV2RestFacade.kt index b08ffd26..4d07b043 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/v2/CoderV2RestFacade.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/CoderV2RestFacade.kt @@ -31,8 +31,8 @@ interface CoderV2RestFacade { @GET("api/v2/buildinfo") fun buildInfo(): Call - @GET("api/v2/workspacebuilds/{buildID}/resources") - fun workspaceResourceByBuild(@Path("buildID") build: UUID): Call> + @GET("api/v2/templateversions/{templateID}/resources") + fun templateVersionResources(@Path("templateID") templateID: UUID): Call> /** * Queues a new build to occur for a workspace. diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index b52b319e..0600ef21 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -332,7 +332,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : private fun Workspace.agentModels(): List { return try { - val agents = coderClient.workspaceAgents(this) + val agents = coderClient.workspaceAgentsByTemplate(this) when (agents.size) { 0 -> { listOf( From f66debcaf9f0944def4d648c901e33c9e5388ce6 Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Wed, 17 Aug 2022 22:18:16 +0300 Subject: [PATCH 17/23] Fix: enable/disable start/stop actions after they were triggered - also includes some refactorings --- .../views/steps/CoderWorkspacesStepView.kt | 114 +++++++++--------- 1 file changed, 58 insertions(+), 56 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index 0600ef21..5464ad94 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -22,6 +22,7 @@ import com.coder.gateway.sdk.getOS import com.coder.gateway.sdk.toURL import com.coder.gateway.sdk.v2.models.Workspace import com.coder.gateway.sdk.withPath +import com.intellij.ide.ActivityTracker import com.intellij.ide.BrowserUtil import com.intellij.ide.IdeBundle import com.intellij.openapi.Disposable @@ -85,9 +86,6 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : WorkspaceStatusColumnInfo("Status") ) - private val startWorkspaceAction = StartWorkspaceAction() - private val stopWorkspaceAction = StopWorkspaceAction() - private var tableOfWorkspaces = TableView(listTableModelOfWorkspaces).apply { setEnableAntialiasing(true) rowSelectionAllowed = true @@ -103,24 +101,13 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : setSelectionMode(ListSelectionModel.SINGLE_SELECTION) selectionModel.addListSelectionListener { enableNextButtonCallback(selectedObject != null && selectedObject?.agentStatus == RUNNING) - when (selectedObject?.agentStatus) { - RUNNING -> { - startWorkspaceAction.isEnabled = false - stopWorkspaceAction.isEnabled = true - } - - STOPPED, FAILED -> { - startWorkspaceAction.isEnabled = true - stopWorkspaceAction.isEnabled = false - } - - else -> { - disableAllWorkspaceActions() - } - } + updateWorkspaceActions() } } + private val startWorkspaceAction = StartWorkspaceAction() + private val stopWorkspaceAction = StopWorkspaceAction() + private val toolbar = ToolbarDecorator.createDecorator(tableOfWorkspaces) .disableAddAction() .disableRemoveAction() @@ -178,7 +165,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : withContext(Dispatchers.IO) { try { coderClient.startWorkspace(workspace.workspaceID, workspace.workspaceName) - startWorkspaceAction.isEnabled = false + loadWorkspaces() } catch (e: WorkspaceResponseException) { logger.warn("Could not build workspace ${workspace.name}, reason: $e") } @@ -196,7 +183,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : withContext(Dispatchers.IO) { try { coderClient.stopWorkspace(workspace.workspaceID, workspace.workspaceName) - stopWorkspaceAction.isEnabled = false + loadWorkspaces() } catch (e: WorkspaceResponseException) { logger.warn("Could not stop workspace ${workspace.name}, reason: $e") } @@ -206,14 +193,29 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : } } - private fun disableAllWorkspaceActions() { - startWorkspaceAction.isEnabled = false - stopWorkspaceAction.isEnabled = false - } - override fun onInit(wizardModel: CoderWorkspacesWizardModel) { enableNextButtonCallback(false) - disableAllWorkspaceActions() + updateWorkspaceActions() + } + + private fun updateWorkspaceActions() { + when (tableOfWorkspaces.selectedObject?.agentStatus) { + RUNNING -> { + startWorkspaceAction.isEnabled = false + stopWorkspaceAction.isEnabled = true + } + + STOPPED, FAILED -> { + startWorkspaceAction.isEnabled = true + stopWorkspaceAction.isEnabled = false + } + + else -> { + startWorkspaceAction.isEnabled = false + stopWorkspaceAction.isEnabled = false + } + } + ActivityTracker.getInstance().inc() } private fun loginAndLoadWorkspace() { @@ -272,7 +274,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : token = wizardModel.token } ProgressManager.getInstance().run(authTask) - loadWorkspaces() + triggerWorkspacePolling() } private fun askToken(): String? { @@ -296,32 +298,32 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : } } - private fun loadWorkspaces() { + private fun triggerWorkspacePolling() { poller?.cancel() poller = cs.launch { while (isActive) { - val workspaceList = withContext(Dispatchers.IO) { - try { - return@withContext coderClient.workspaces().collectAgents() - } catch (e: Exception) { - logger.error("Could not retrieve workspaces for ${coderClient.me.username} on ${coderClient.coderURL}. Reason: $e") - emptyList() - } - } + loadWorkspaces() + delay(5000) + } + } + } - val selectedWorkspace = withContext(Dispatchers.Main) { - tableOfWorkspaces.selectedObject - } + private suspend fun loadWorkspaces() { + val workspaceList = withContext(Dispatchers.IO) { + try { + return@withContext coderClient.workspaces().collectAgents() + } catch (e: Exception) { + logger.error("Could not retrieve workspaces for ${coderClient.me.username} on ${coderClient.coderURL}. Reason: $e") + emptyList() + } + } - // if we just run the update on the main dispatcher, the code will block because it cant get some AWT locks - ApplicationManager.getApplication().invokeLater { - listTableModelOfWorkspaces.updateItems(workspaceList) - if (selectedWorkspace != null) { - tableOfWorkspaces.selectItem(selectedWorkspace) - } - } - delay(5000) + withContext(Dispatchers.Main) { + val selectedWorkspace = tableOfWorkspaces.selectedObject?.name + listTableModelOfWorkspaces.items = workspaceList + if (selectedWorkspace != null) { + tableOfWorkspaces.selectItem(selectedWorkspace) } } } @@ -510,15 +512,15 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : } } - private fun ListTableModel.updateItems(workspaces: Collection) { - while (this.rowCount > 0) this.removeRow(0) - this.addRows(workspaces) - } - - private fun TableView.selectItem(workspace: WorkspaceAgentModel) { - this.items.forEachIndexed { index, workspaceAgentModel -> - if (workspaceAgentModel.name == workspace.name) - this.setRowSelectionInterval(index, index) + private fun TableView.selectItem(workspaceName: String?) { + if (workspaceName != null) { + this.items.forEachIndexed { index, workspaceAgentModel -> + if (workspaceAgentModel.name == workspaceName) { + selectionModel.addSelectionInterval(convertRowIndexToView(index), convertRowIndexToView(index)) + // fix cell selection case + columnModel.selectionModel.addSelectionInterval(0, columnCount - 1) + } + } } } From 2a9a5bac556e9ceaa43ff75b73fb68b29a22a9f2 Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Wed, 17 Aug 2022 22:19:33 +0300 Subject: [PATCH 18/23] Impl: REST calls to update a workspace to latest template - also includes a new REST call for retrieving templates --- .../gateway/sdk/CoderRestClientService.kt | 22 +++++++++++++++++++ .../com/coder/gateway/sdk/ex/exceptions.kt | 4 +++- .../coder/gateway/sdk/v2/CoderV2RestFacade.kt | 4 ++++ .../coder/gateway/sdk/v2/models/Template.kt | 21 ++++++++++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/com/coder/gateway/sdk/v2/models/Template.kt diff --git a/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt b/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt index fc975209..be81a45a 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt @@ -2,11 +2,13 @@ package com.coder.gateway.sdk import com.coder.gateway.sdk.convertors.InstantConverter import com.coder.gateway.sdk.ex.AuthenticationResponseException +import com.coder.gateway.sdk.ex.TemplateResponseException import com.coder.gateway.sdk.ex.WorkspaceResourcesResponseException import com.coder.gateway.sdk.ex.WorkspaceResponseException import com.coder.gateway.sdk.v2.CoderV2RestFacade import com.coder.gateway.sdk.v2.models.BuildInfo import com.coder.gateway.sdk.v2.models.CreateWorkspaceBuildRequest +import com.coder.gateway.sdk.v2.models.Template import com.coder.gateway.sdk.v2.models.User import com.coder.gateway.sdk.v2.models.Workspace import com.coder.gateway.sdk.v2.models.WorkspaceAgent @@ -115,6 +117,14 @@ class CoderRestClientService { return workspaceResourcesResponse.body()!!.flatMap { it.agents ?: emptyList() } } + private fun template(templateID: UUID): Template { + val templateResponse = retroRestClient.template(templateID).execute() + if (!templateResponse.isSuccessful) { + throw TemplateResponseException("Failed to retrieve template with id: $templateID, reason: ${templateResponse.message()}") + } + return templateResponse.body()!! + } + fun startWorkspace(workspaceID: UUID, workspaceName: String): WorkspaceBuild { val buildRequest = CreateWorkspaceBuildRequest(null, "start", null, null, null) val buildResponse = retroRestClient.createWorkspaceBuild(workspaceID, buildRequest).execute() @@ -134,4 +144,16 @@ class CoderRestClientService { return buildResponse.body()!! } + + fun updateWorkspace(workspaceID: UUID, workspaceName: String, lastWorkspaceTransition: String, templateID: UUID): WorkspaceBuild { + val template = template(templateID) + + val buildRequest = CreateWorkspaceBuildRequest(template.activeVersionID, lastWorkspaceTransition, null, null, null) + val buildResponse = retroRestClient.createWorkspaceBuild(workspaceID, buildRequest).execute() + if (buildResponse.code() != HTTP_CREATED) { + throw WorkspaceResponseException("Failed to update workspace ${workspaceName}: ${buildResponse.code()}, reason: ${buildResponse.message()}") + } + + return buildResponse.body()!! + } } \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/sdk/ex/exceptions.kt b/src/main/kotlin/com/coder/gateway/sdk/ex/exceptions.kt index 983611b0..e225c2b9 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/ex/exceptions.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/ex/exceptions.kt @@ -6,4 +6,6 @@ class AuthenticationResponseException(reason: String) : IOException(reason) class WorkspaceResponseException(reason: String) : IOException(reason) -class WorkspaceResourcesResponseException(reason: String) : IOException(reason) \ No newline at end of file +class WorkspaceResourcesResponseException(reason: String) : IOException(reason) + +class TemplateResponseException(reason: String) : IOException(reason) \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/CoderV2RestFacade.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/CoderV2RestFacade.kt index 4d07b043..37618b51 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/v2/CoderV2RestFacade.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/CoderV2RestFacade.kt @@ -2,6 +2,7 @@ package com.coder.gateway.sdk.v2 import com.coder.gateway.sdk.v2.models.BuildInfo import com.coder.gateway.sdk.v2.models.CreateWorkspaceBuildRequest +import com.coder.gateway.sdk.v2.models.Template import com.coder.gateway.sdk.v2.models.User import com.coder.gateway.sdk.v2.models.Workspace import com.coder.gateway.sdk.v2.models.WorkspaceBuild @@ -39,4 +40,7 @@ interface CoderV2RestFacade { */ @POST("api/v2/workspaces/{workspaceID}/builds") fun createWorkspaceBuild(@Path("workspaceID") workspaceID: UUID, @Body createWorkspaceBuildRequest: CreateWorkspaceBuildRequest): Call + + @GET("api/v2/templates/{templateID}") + fun template(@Path("templateID") templateID: UUID): Call