Skip to content

Commit ae41bda

Browse files
committed
impl: reconfigure the ssh config when user modifies the wildcard settings
- right now the ssh config is triggered only when at the login and only when new workspaces are created. In the last case, the ssh config updates only the new sections. - but the user can go into settings page and enable or disable the wildcard ssh config. With this patch after the user hits Save, the ssh re-configuration is triggered, without the need to restart Toolbox.
1 parent aa930a4 commit ae41bda

File tree

3 files changed

+49
-15
lines changed

3 files changed

+49
-15
lines changed

src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,4 +204,8 @@ class CoderRemoteEnvironment(
204204
* Companion to equals, for sets.
205205
*/
206206
override fun hashCode(): Int = id.hashCode()
207+
208+
override fun toString(): String {
209+
return "CoderRemoteEnvironment(name='$name')"
210+
}
207211
}

src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,16 @@ import com.jetbrains.toolbox.api.remoteDev.RemoteProvider
2020
import com.jetbrains.toolbox.api.remoteDev.RemoteProviderEnvironment
2121
import com.jetbrains.toolbox.api.ui.actions.ActionDescription
2222
import com.jetbrains.toolbox.api.ui.components.UiPage
23+
import kotlinx.coroutines.ExperimentalCoroutinesApi
2324
import kotlinx.coroutines.Job
24-
import kotlinx.coroutines.delay
25+
import kotlinx.coroutines.channels.Channel
2526
import kotlinx.coroutines.flow.MutableStateFlow
2627
import kotlinx.coroutines.flow.StateFlow
2728
import kotlinx.coroutines.flow.update
2829
import kotlinx.coroutines.isActive
2930
import kotlinx.coroutines.launch
31+
import kotlinx.coroutines.selects.onTimeout
32+
import kotlinx.coroutines.selects.select
3033
import okhttp3.OkHttpClient
3134
import java.net.URI
3235
import java.net.URL
@@ -35,18 +38,20 @@ import kotlin.time.Duration.Companion.seconds
3538
import com.jetbrains.toolbox.api.ui.components.AccountDropdownField as DropDownMenu
3639
import com.jetbrains.toolbox.api.ui.components.AccountDropdownField as dropDownFactory
3740

41+
@OptIn(ExperimentalCoroutinesApi::class)
3842
class CoderRemoteProvider(
3943
private val context: CoderToolboxContext,
4044
private val httpClient: OkHttpClient,
4145
) : RemoteProvider("Coder") {
4246
// Current polling job.
4347
private var pollJob: Job? = null
44-
private var lastEnvironments: Set<CoderRemoteEnvironment>? = null
48+
private val lastEnvironments = mutableSetOf<CoderRemoteEnvironment>()
4549

46-
private val cSettings = context.settingsStore.readOnly()
50+
private val settings = context.settingsStore.readOnly()
4751

4852
// Create our services from the Toolbox ones.
49-
private val settingsPage: CoderSettingsPage = CoderSettingsPage(context)
53+
private val triggerSshConfig = Channel<Boolean>(Channel.CONFLATED)
54+
private val settingsPage: CoderSettingsPage = CoderSettingsPage(context, triggerSshConfig)
5055
private val dialogUi = DialogUi(context)
5156

5257
// The REST client, if we are signed in
@@ -92,7 +97,7 @@ class CoderRemoteProvider(
9297
}?.map { agent ->
9398
// If we have an environment already, update that.
9499
val env = CoderRemoteEnvironment(context, client, ws, agent)
95-
lastEnvironments?.firstOrNull { it == env }?.let {
100+
lastEnvironments.firstOrNull { it == env }?.let {
96101
it.update(ws, agent)
97102
it
98103
} ?: env
@@ -107,9 +112,7 @@ class CoderRemoteProvider(
107112

108113
// Reconfigure if a new environment is found.
109114
// TODO@JB: Should we use the add/remove listeners instead?
110-
val newEnvironments = lastEnvironments
111-
?.let { resolvedEnvironments.subtract(it) }
112-
?: resolvedEnvironments
115+
val newEnvironments = resolvedEnvironments.subtract(lastEnvironments)
113116
if (newEnvironments.isNotEmpty()) {
114117
context.logger.info("Found new environment(s), reconfiguring CLI: $newEnvironments")
115118
cli.configSsh(newEnvironments.map { it.name }.toSet())
@@ -124,8 +127,10 @@ class CoderRemoteProvider(
124127
true
125128
}
126129
}
127-
128-
lastEnvironments = resolvedEnvironments
130+
lastEnvironments.apply {
131+
clear()
132+
addAll(resolvedEnvironments)
133+
}
129134
} catch (_: CancellationException) {
130135
context.logger.debug("${client.url} polling loop canceled")
131136
break
@@ -136,7 +141,17 @@ class CoderRemoteProvider(
136141
break
137142
}
138143
// TODO: Listening on a web socket might be better?
139-
delay(5.seconds)
144+
select<Unit> {
145+
onTimeout(5.seconds) {
146+
context.logger.trace("workspace poller waked up by the 5 seconds timeout")
147+
}
148+
triggerSshConfig.onReceive { shouldTrigger ->
149+
if (shouldTrigger) {
150+
context.logger.trace("workspace poller waked up because it should reconfigure the ssh configurations")
151+
cli.configSsh(lastEnvironments.map { it.name }.toSet())
152+
}
153+
}
154+
}
140155
}
141156
}
142157

@@ -178,7 +193,7 @@ class CoderRemoteProvider(
178193
override fun close() {
179194
pollJob?.cancel()
180195
client?.close()
181-
lastEnvironments = null
196+
lastEnvironments.clear()
182197
environments.value = LoadableState.Value(emptyList())
183198
isInitialized.update { false }
184199
}
@@ -270,7 +285,7 @@ class CoderRemoteProvider(
270285
var autologinEx: Exception? = null
271286
context.secrets.lastToken.let { lastToken ->
272287
context.secrets.lastDeploymentURL.let { lastDeploymentURL ->
273-
if (autologin && lastDeploymentURL.isNotBlank() && (lastToken.isNotBlank() || !cSettings.requireTokenAuth)) {
288+
if (autologin && lastDeploymentURL.isNotBlank() && (lastToken.isNotBlank() || !settings.requireTokenAuth)) {
274289
try {
275290
return createConnectPage(URL(lastDeploymentURL), lastToken)
276291
} catch (ex: Exception) {
@@ -342,7 +357,7 @@ class CoderRemoteProvider(
342357
if (it.isNotBlank() && context.secrets.lastDeploymentURL == deploymentURL.toString()) {
343358
it to SettingSource.LAST_USED
344359
} else {
345-
cSettings.token(deploymentURL)
360+
settings.token(deploymentURL)
346361
}
347362
}
348363

src/main/kotlin/com/coder/toolbox/views/CoderSettingsPage.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ import com.jetbrains.toolbox.api.ui.components.CheckboxField
66
import com.jetbrains.toolbox.api.ui.components.TextField
77
import com.jetbrains.toolbox.api.ui.components.TextType
88
import com.jetbrains.toolbox.api.ui.components.UiField
9+
import kotlinx.coroutines.channels.Channel
10+
import kotlinx.coroutines.channels.ClosedSendChannelException
911
import kotlinx.coroutines.flow.MutableStateFlow
1012
import kotlinx.coroutines.flow.StateFlow
13+
import kotlinx.coroutines.launch
1114

1215
/**
1316
* A page for modifying Coder settings.
@@ -16,7 +19,8 @@ import kotlinx.coroutines.flow.StateFlow
1619
* TODO@JB: There is no scroll, and our settings do not fit. As a consequence,
1720
* I have not been able to test this page.
1821
*/
19-
class CoderSettingsPage(context: CoderToolboxContext) : CoderPage(context, context.i18n.ptrl("Coder Settings"), false) {
22+
class CoderSettingsPage(context: CoderToolboxContext, triggerSshConfig: Channel<Boolean>) :
23+
CoderPage(context, context.i18n.ptrl("Coder Settings"), false) {
2024
private val settings = context.settingsStore.readOnly()
2125

2226
// TODO: Copy over the descriptions, holding until I can test this page.
@@ -87,7 +91,18 @@ class CoderSettingsPage(context: CoderToolboxContext) : CoderPage(context, conte
8791
context.settingsStore.updateCAPath(tlsCAPathField.textState.value)
8892
context.settingsStore.updateAltHostname(tlsAlternateHostnameField.textState.value)
8993
context.settingsStore.updateDisableAutostart(disableAutostartField.checkedState.value)
94+
val oldIsSshWildcardConfigEnabled = settings.isSshWildcardConfigEnabled
9095
context.settingsStore.updateEnableSshWildcardConfig(enableSshWildCardConfig.checkedState.value)
96+
97+
if (enableSshWildCardConfig.checkedState.value != oldIsSshWildcardConfigEnabled) {
98+
context.cs.launch {
99+
try {
100+
triggerSshConfig.send(true)
101+
context.logger.info("Wildcard settings have been modified from $oldIsSshWildcardConfigEnabled to ${!oldIsSshWildcardConfigEnabled}, ssh config is going to be regenerated...")
102+
} catch (_: ClosedSendChannelException) {
103+
}
104+
}
105+
}
91106
context.settingsStore.updateSshLogDir(sshLogDirField.textState.value)
92107
context.settingsStore.updateSshConfigOptions(sshExtraArgs.textState.value)
93108
}

0 commit comments

Comments
 (0)