Skip to content

impl: support uri handling #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Mar 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 24 additions & 15 deletions src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ import com.coder.toolbox.services.CoderSecretsService
import com.coder.toolbox.services.CoderSettingsService
import com.coder.toolbox.settings.CoderSettings
import com.coder.toolbox.settings.Source
import com.coder.toolbox.util.CoderProtocolHandler
import com.coder.toolbox.util.DialogUi
import com.coder.toolbox.util.LinkHandler
import com.coder.toolbox.util.toQueryParameters
import com.coder.toolbox.views.Action
import com.coder.toolbox.views.CoderSettingsPage
import com.coder.toolbox.views.ConnectPage
Expand Down Expand Up @@ -53,7 +52,6 @@ class CoderRemoteProvider(
private val secrets: CoderSecretsService = CoderSecretsService(context.secretsStore)
private val settingsPage: CoderSettingsPage = CoderSettingsPage(context, settingsService)
private val dialogUi = DialogUi(context, settings)
private val linkHandler = LinkHandler(context, settings, httpClient, dialogUi)

// The REST client, if we are signed in
private var client: CoderRestClient? = null
Expand All @@ -65,7 +63,9 @@ class CoderRemoteProvider(

// On the first load, automatically log in if we can.
private var firstRun = true

private val isInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false)
private var coderHeaderPage = NewEnvironmentPage(context, context.i18n.pnotr(getDeploymentURL()?.first ?: ""))
private val linkHandler = CoderProtocolHandler(context, settings, httpClient, dialogUi, isInitialized)
override val environments: MutableStateFlow<LoadableState<List<RemoteProviderEnvironment>>> = MutableStateFlow(
LoadableState.Value(emptyList())
)
Expand Down Expand Up @@ -122,6 +122,12 @@ class CoderRemoteProvider(
environments.update {
LoadableState.Value(resolvedEnvironments.toList())
}
if (isInitialized.value == false) {
context.logger.info("Environments for ${client.url} are now initialized")
isInitialized.update {
true
}
}

lastEnvironments = resolvedEnvironments
} catch (_: CancellationException) {
Expand Down Expand Up @@ -171,14 +177,14 @@ class CoderRemoteProvider(
/**
* Cancel polling and clear the client and environments.
*
* Called as part of our own logout but it is unclear where it is called by
* Toolbox. Maybe on uninstall?
* Also called as part of our own logout.
*/
override fun close() {
pollJob?.cancel()
client = null
client?.close()
lastEnvironments = null
environments.value = LoadableState.Value(emptyList())
isInitialized.update { false }
}

override val svgIcon: SvgIcon =
Expand Down Expand Up @@ -213,8 +219,7 @@ class CoderRemoteProvider(
* Just displays the deployment URL at the moment, but we could use this as
* a form for creating new environments.
*/
override fun getNewEnvironmentUiPage(): UiPage =
NewEnvironmentPage(context, context.i18n.pnotr(getDeploymentURL()?.first ?: ""))
override fun getNewEnvironmentUiPage(): UiPage = coderHeaderPage

/**
* We always show a list of environments.
Expand All @@ -233,11 +238,13 @@ class CoderRemoteProvider(
* Handle incoming links (like from the dashboard).
*/
override suspend fun handleUri(uri: URI) {
val params = uri.toQueryParameters()
context.cs.launch {
val name = linkHandler.handle(params)
// TODO@JB: Now what? How do we actually connect this workspace?
context.logger.debug("External request for $name: $uri")
linkHandler.handle(uri, shouldDoAutoLogin()) { restClient, cli ->
// stop polling and de-initialize resources
close()
// start initialization with the new settings
[email protected] = restClient
coderHeaderPage = NewEnvironmentPage(context, context.i18n.pnotr(restClient.url.toString()))
pollJob = poll(restClient, cli)
}
}

Expand All @@ -263,7 +270,7 @@ class CoderRemoteProvider(
// Show sign in page if we have not configured the client yet.
if (client == null) {
// When coming back to the application, authenticate immediately.
val autologin = firstRun && secrets.rememberMe == "true"
val autologin = shouldDoAutoLogin()
var autologinEx: Exception? = null
secrets.lastToken.let { lastToken ->
secrets.lastDeploymentURL.let { lastDeploymentURL ->
Expand Down Expand Up @@ -302,6 +309,8 @@ class CoderRemoteProvider(
return null
}

private fun shouldDoAutoLogin(): Boolean = firstRun && secrets.rememberMe == "true"

/**
* Create a connect page that starts polling and resets the UI on success.
*/
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/com/coder/toolbox/CoderToolboxContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.jetbrains.toolbox.api.core.PluginSecretStore
import com.jetbrains.toolbox.api.core.PluginSettingsStore
import com.jetbrains.toolbox.api.core.diagnostics.Logger
import com.jetbrains.toolbox.api.localization.LocalizableStringFactory
import com.jetbrains.toolbox.api.remoteDev.connection.ClientHelper
import com.jetbrains.toolbox.api.remoteDev.states.EnvironmentStateColorPalette
import com.jetbrains.toolbox.api.remoteDev.ui.EnvironmentUiPageManager
import com.jetbrains.toolbox.api.ui.ToolboxUi
Expand All @@ -13,6 +14,7 @@ data class CoderToolboxContext(
val ui: ToolboxUi,
val envPageManager: EnvironmentUiPageManager,
val envStateColorPalette: EnvironmentStateColorPalette,
val ideOrchestrator: ClientHelper,
val cs: CoroutineScope,
val logger: Logger,
val i18n: LocalizableStringFactory,
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/com/coder/toolbox/CoderToolboxExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.jetbrains.toolbox.api.core.diagnostics.Logger
import com.jetbrains.toolbox.api.localization.LocalizableStringFactory
import com.jetbrains.toolbox.api.remoteDev.RemoteDevExtension
import com.jetbrains.toolbox.api.remoteDev.RemoteProvider
import com.jetbrains.toolbox.api.remoteDev.connection.ClientHelper
import com.jetbrains.toolbox.api.remoteDev.states.EnvironmentStateColorPalette
import com.jetbrains.toolbox.api.remoteDev.ui.EnvironmentUiPageManager
import com.jetbrains.toolbox.api.ui.ToolboxUi
Expand All @@ -24,6 +25,7 @@ class CoderToolboxExtension : RemoteDevExtension {
serviceLocator.getService(ToolboxUi::class.java),
serviceLocator.getService(EnvironmentUiPageManager::class.java),
serviceLocator.getService(EnvironmentStateColorPalette::class.java),
serviceLocator.getService(ClientHelper::class.java),
serviceLocator.getService(CoroutineScope::class.java),
serviceLocator.getService(Logger::class.java),
serviceLocator.getService(LocalizableStringFactory::class.java),
Expand Down
5 changes: 2 additions & 3 deletions src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ fun ensureCLI(
deploymentURL: URL,
buildVersion: String,
settings: CoderSettings,
indicator: ((t: String) -> Unit)? = null,
): CoderCLIManager {
val cli = CoderCLIManager(deploymentURL, context.logger, settings)

Expand All @@ -76,7 +75,7 @@ fun ensureCLI(

// If downloads are enabled download the new version.
if (settings.enableDownloads) {
indicator?.invoke("Downloading Coder CLI...")
context.logger.info("Downloading Coder CLI...")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we also log what version of CLI is being downloaded and the source URL?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is using https://dev.coder.com/bin/ to download the binary, and it doesn't contain the version. I think the version can be resolved only after running the cli, which means much later than this log.

try {
cli.download()
return cli
Expand All @@ -98,7 +97,7 @@ fun ensureCLI(
}

if (settings.enableDownloads) {
indicator?.invoke("Downloading Coder CLI...")
context.logger.info("Downloading Coder CLI...")
dataCLI.download()
return dataCLI
}
Expand Down
21 changes: 21 additions & 0 deletions src/main/kotlin/com/coder/toolbox/sdk/CoderRestClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,19 @@ open class CoderRestClient(
return workspacesResponse.body()!!.workspaces
}

/**
* Retrieves a workspace with the provided id.
* @throws [APIResponseException].
*/
fun workspace(workspaceID: UUID): Workspace {
val workspacesResponse = retroRestClient.workspace(workspaceID).execute()
if (!workspacesResponse.isSuccessful) {
throw APIResponseException("retrieve workspace", url, workspacesResponse)
}

return workspacesResponse.body()!!
}

/**
* Retrieves all the agent names for all workspaces, including those that
* are off. Meant to be used when configuring SSH.
Expand Down Expand Up @@ -272,4 +285,12 @@ open class CoderRestClient(
}
return buildResponse.body()!!
}

fun close() {
httpClient.apply {
dispatcher.executorService.shutdown()
connectionPool.evictAll()
cache?.close()
}
}
}
9 changes: 9 additions & 0 deletions src/main/kotlin/com/coder/toolbox/sdk/v2/CoderV2RestFacade.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.coder.toolbox.sdk.v2.models.BuildInfo
import com.coder.toolbox.sdk.v2.models.CreateWorkspaceBuildRequest
import com.coder.toolbox.sdk.v2.models.Template
import com.coder.toolbox.sdk.v2.models.User
import com.coder.toolbox.sdk.v2.models.Workspace
import com.coder.toolbox.sdk.v2.models.WorkspaceBuild
import com.coder.toolbox.sdk.v2.models.WorkspaceResource
import com.coder.toolbox.sdk.v2.models.WorkspacesResponse
Expand All @@ -30,6 +31,14 @@ interface CoderV2RestFacade {
@Query("q") searchParams: String,
): Call<WorkspacesResponse>

/**
* Retrieves a workspace with the provided id.
*/
@GET("api/v2/workspaces/{workspaceID}")
fun workspace(
@Path("workspaceID") workspaceID: UUID
): Call<Workspace>

@GET("api/v2/buildinfo")
fun buildInfo(): Call<BuildInfo>

Expand Down
Loading
Loading