Skip to content

Commit

Permalink
Namespace selection in the dialog (#323)
Browse files Browse the repository at this point in the history
* Moved plugin version check to a background thread.

* Handle rich output in MirrordApi

* Kind of works

* Selection works

* Namespace passed to execution

* Fixed refresh loop

* More strict parse error check

* Changelog

* Format

* Docs

* RichOutput docs

* Added comment on inserting last chosen target.
  • Loading branch information
Razz4780 authored Jan 29, 2025
1 parent 9743ff2 commit c417508
Show file tree
Hide file tree
Showing 4 changed files with 451 additions and 226 deletions.
1 change: 1 addition & 0 deletions changelog.d/+namespace-selection.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The target selection dialog now allows for switching between available namespaces.
144 changes: 131 additions & 13 deletions modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package com.metalbear.mirrord

import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException
import com.google.gson.annotations.SerializedName
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.wsl.WSLCommandLineOptions
Expand Down Expand Up @@ -146,12 +147,97 @@ private const val MIRRORD_FOR_TEAMS_INVITE_AFTER = 100
*/
private const val MIRRORD_FOR_TEAMS_INVITE_EVERY = 30

/**
* Name of the environment variable used to trigger rich output of `mirrord ls`.
*/
private const val MIRRORD_LS_RICH_OUTPUT_ENV = "MIRRORD_LS_RICH_OUTPUT"

/**
* Interact with mirrord CLI using this API.
*/
class MirrordApi(private val service: MirrordProjectService, private val projectEnvVars: Map<String, String>?) {
private class MirrordLsTask(cli: String, projectEnvVars: Map<String, String>?) : MirrordCliTask<List<String>>(cli, "ls", null, projectEnvVars) {
override fun compute(project: Project, process: Process, setText: (String) -> Unit): List<String> {
/**
* New format of found target returned from `mirrord ls`.
*/
data class FoundTarget(
/**
* Path to the target, e.g `pod/my-pod`.
*/
val path: String,
/**
* Whether this target can be selected.
*/
val available: Boolean
)

/**
* New format of `mirrord ls`, enabled by setting MIRRORD_LS_RICH_OUTPUT_ENV to `true`.
*/
private data class RichOutput(
/**
* Targets found in the namespace.
*/
val targets: Array<FoundTarget>,
/**
* Namespace where the lookup was done.
*/
@SerializedName("current_namespace") val currentNamespace: String,
/**
* All namespaces available to the user.
*/
val namespaces: Array<String>
) {
/**
* Generated by IntelliJ.
*
* If it's not overrode, we get a warning, because this class has an Array field.
*/
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as RichOutput

if (!targets.contentEquals(other.targets)) return false
if (currentNamespace != other.currentNamespace) return false
if (!namespaces.contentEquals(other.namespaces)) return false

return true
}

/**
* Generated by IntelliJ.
*
* If it's not overrode, we get a warning, because this class has an Array field.
*/
override fun hashCode(): Int {
var result = targets.contentHashCode()
result = 31 * result + currentNamespace.hashCode()
result = 31 * result + namespaces.contentHashCode()
return result
}
}

/**
* Output of `mirrord ls`.
*/
class MirrordLsOutput(
/**
* List of found targets.
*/
val targets: List<FoundTarget>,
/**
* Namespace where the lookup was done.
*/
val currentNamespace: String?,
/**
* All namespaces avaiable to the user.
*/
val namespaces: List<String>?
)

private class MirrordLsTask(cli: String, projectEnvVars: Map<String, String>?) : MirrordCliTask<MirrordLsOutput>(cli, "ls", null, projectEnvVars) {
override fun compute(project: Project, process: Process, setText: (String) -> Unit): MirrordLsOutput {
setText("mirrord is listing targets...")

process.waitFor()
Expand All @@ -163,24 +249,48 @@ class MirrordApi(private val service: MirrordProjectService, private val project
val data = process.inputStream.bufferedReader().readText()
MirrordLogger.logger.debug("parsing mirrord ls output: $data")

val pods = SafeParser().parse(data, Array<String>::class.java).toMutableList()
val output = try {
val richOutput = SafeParser().parse(data, RichOutput::class.java)
MirrordLsOutput(richOutput.targets.toList(), richOutput.currentNamespace, richOutput.namespaces.toList())
} catch (error: Throwable) {
if (error.cause != null && error.cause is JsonSyntaxException) {
val simpleOutput = SafeParser().parse(data, Array<String>::class.java)
MirrordLsOutput(
simpleOutput.map { FoundTarget(it, true) },
null,
null
)
} else {
throw error
}
}

if (pods.isEmpty()) {
project.service<MirrordProjectService>().notifier.notifySimple("No mirrord target available in the configured namespace. You can run targetless, or set a different target namespace or kubeconfig in the mirrord configuration file.", NotificationType.INFORMATION)
if (output.targets.isEmpty()) {
project
.service<MirrordProjectService>()
.notifier
.notifySimple(
"No mirrord target available in the configured namespace. " +
"You can run targetless, or set a different target namespace " +
"or kubeconfig in the mirrord configuration file.",
NotificationType.INFORMATION
)
}

return pods
return output
}
}

/**
* Runs `mirrord ls` to get the list of available targets.
* Displays a modal progress dialog.
*
* @return list of pods
* @return available targets
*/
fun listPods(cli: String, configFile: String?, wslDistribution: WSLDistribution?): List<String> {
val task = MirrordLsTask(cli, projectEnvVars).apply {
fun listTargets(cli: String, configFile: String?, wslDistribution: WSLDistribution?, namespace: String?): MirrordLsOutput {
val envVars = projectEnvVars.orEmpty() + (MIRRORD_LS_RICH_OUTPUT_ENV to "true")
val task = MirrordLsTask(cli, envVars).apply {
this.namespace = namespace
this.configFile = configFile
this.wslDistribution = wslDistribution
this.output = "json"
Expand Down Expand Up @@ -354,11 +464,12 @@ class MirrordApi(private val service: MirrordProjectService, private val project
*
* @return environment for the user's application
*/
fun exec(cli: String, target: String?, configFile: String?, executable: String?, wslDistribution: WSLDistribution?): MirrordExecution {
fun exec(cli: String, target: MirrordExecDialog.UserSelection, configFile: String?, executable: String?, wslDistribution: WSLDistribution?): MirrordExecution {
bumpRunCounter()

val task = MirrordExtTask(cli, projectEnvVars).apply {
this.target = target
this.target = target.target
this.namespace = target.namespace
this.configFile = configFile
this.executable = executable
this.wslDistribution = wslDistribution
Expand All @@ -376,11 +487,12 @@ class MirrordApi(private val service: MirrordProjectService, private val project
return result
}

fun containerExec(cli: String, target: String?, configFile: String?, wslDistribution: WSLDistribution?): MirrordContainerExecution {
fun containerExec(cli: String, target: MirrordExecDialog.UserSelection, configFile: String?, wslDistribution: WSLDistribution?): MirrordContainerExecution {
bumpRunCounter()

val task = MirrordContainerExtTask(cli, projectEnvVars).apply {
this.target = target
this.target = target.target
this.namespace = target.namespace
this.configFile = configFile
this.wslDistribution = wslDistribution
}
Expand Down Expand Up @@ -431,6 +543,7 @@ class MirrordApi(private val service: MirrordProjectService, private val project
*/
private abstract class MirrordCliTask<T>(private val cli: String, private val command: String, private val args: List<String>?, private val projectEnvVars: Map<String, String>?) {
var target: String? = null
var namespace: String? = null
var configFile: String? = null
var executable: String? = null
var wslDistribution: WSLDistribution? = null
Expand All @@ -451,11 +564,16 @@ private abstract class MirrordCliTask<T>(private val cli: String, private val co
addParameter(it)
}

namespace?.let {
environment.put("MIRRORD_TARGET_NAMESPACE", it)
}

configFile?.let {
val formattedPath = wslDistribution?.getWslPath(it) ?: it
addParameter("-f")
addParameter(formattedPath)
}

executable?.let {
addParameter("-e")
addParameter(it)
Expand Down
Loading

0 comments on commit c417508

Please sign in to comment.