Skip to content

Commit

Permalink
Pass config env vars to mirrord verify-config. (#181)
Browse files Browse the repository at this point in the history
* Pass config env vars to mirrord verify-config.

* function to get the env vars from launch config // reformat code // docs

* format // uncomment version check

* format // more version check revert

* ktlint format

* changelog

* auto formatting things

Co-authored-by: Michał Smolarek <[email protected]>

* remove noisy logs // cat noises

* missing return null

* return lift

* do we want it in the individual IDE runs?

* use run settings env vars everywhere

* klint

* change env vars everywhere (2) // have different project|ide set additional env vars

* ktlint

* have extraEnv be more complete?

* tomcat env vars

* comment configFromEnv

* addressing CR // remove function for getting env vars (we pass them down) // actually use the env vars in MirrordApi

* optimus prime imports

* lint

---------

Co-authored-by: Michał Smolarek <[email protected]>
  • Loading branch information
meowjesty and Razz4780 authored Nov 28, 2023
1 parent c591745 commit 5b84234
Show file tree
Hide file tree
Showing 14 changed files with 98 additions and 133 deletions.
1 change: 1 addition & 0 deletions changelog.d/167.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Passes the launch env vars section to mirrord verify-config, and mirrord, resolving config options that were set as env vars.
116 changes: 32 additions & 84 deletions modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,41 +15,18 @@ import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.openapi.project.Project
import java.util.concurrent.CancellationException
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import java.util.concurrent.*

enum class MessageType {
NewTask,
FinishedTask,
Warning
NewTask, FinishedTask, Warning
}

// I don't know how to do tags like Rust so this format is for parsing both kind of messages ;_;
data class Message(
val type: MessageType,
val name: String,
val parent: String?,
val success: Boolean?,
val message: String?
)

data class Error(
val message: String,
val severity: String,
val causes: List<String>,
val help: String,
val labels: List<String>,
val related: List<String>
)

data class MirrordExecution(
val environment: MutableMap<String, String>,
@SerializedName("patched_path")
val patchedPath: String?
)
data class Message(val type: MessageType, val name: String, val parent: String?, val success: Boolean?, val message: String?)

data class Error(val message: String, val severity: String, val causes: List<String>, val help: String, val labels: List<String>, val related: List<String>)

data class MirrordExecution(val environment: MutableMap<String, String>, @SerializedName("patched_path") val patchedPath: String?)

/**
* Wrapper around Gson for parsing messages from the mirrord binary.
Expand All @@ -65,10 +42,7 @@ private class SafeParser {
gson.fromJson(value, classOfT)
} catch (e: Throwable) {
MirrordLogger.logger.debug("failed to parse mirrord binary message", e)
throw MirrordError(
"failed to parse a message from the mirrord binary, try updating to the latest version",
e
)
throw MirrordError("failed to parse a message from the mirrord binary, try updating to the latest version", e)
}
}
}
Expand All @@ -81,8 +55,8 @@ private const val FEEDBACK_COUNTER_REVIEW_AFTER = 100
/**
* Interact with mirrord CLI using this API.
*/
class MirrordApi(private val service: MirrordProjectService) {
private class MirrordLsTask(cli: String) : MirrordCliTask<List<String>>(cli, "ls", null) {
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> {
setText("mirrord is listing targets...")

Expand All @@ -95,16 +69,10 @@ class MirrordApi(private val service: MirrordProjectService) {
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 pods = SafeParser().parse(data, Array<String>::class.java).toMutableList()

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
)
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
Expand All @@ -117,12 +85,8 @@ class MirrordApi(private val service: MirrordProjectService) {
*
* @return list of pods
*/
fun listPods(
cli: String,
configFile: String?,
wslDistribution: WSLDistribution?
): List<String> {
val task = MirrordLsTask(cli).apply {
fun listPods(cli: String, configFile: String?, wslDistribution: WSLDistribution?): List<String> {
val task = MirrordLsTask(cli, projectEnvVars).apply {
this.configFile = configFile
this.wslDistribution = wslDistribution
this.output = "json"
Expand All @@ -131,7 +95,7 @@ class MirrordApi(private val service: MirrordProjectService) {
return task.run(service.project)
}

private class MirrordExtTask(cli: String) : MirrordCliTask<MirrordExecution>(cli, "ext", null) {
private class MirrordExtTask(cli: String, projectEnvVars: Map<String, String>?) : MirrordCliTask<MirrordExecution>(cli, "ext", null, projectEnvVars) {
override fun compute(project: Project, process: Process, setText: (String) -> Unit): MirrordExecution {
val parser = SafeParser()
val bufferedReader = process.inputStream.reader().buffered()
Expand Down Expand Up @@ -184,7 +148,7 @@ class MirrordApi(private val service: MirrordProjectService) {
* Reads the output (json) from stdout which contain either a success + warnings, or the errors from the verify
* command.
*/
private class MirrordVerifyConfigTask(cli: String, path: String) : MirrordCliTask<String>(cli, "verify-config", listOf("--ide", path)) {
private class MirrordVerifyConfigTask(cli: String, path: String, projectEnvVars: Map<String, String>?) : MirrordCliTask<String>(cli, "verify-config", listOf("--ide", path), projectEnvVars) {
override fun compute(project: Project, process: Process, setText: (String) -> Unit): String {
setText("mirrord is verifying the config options...")
process.waitFor()
Expand All @@ -194,6 +158,10 @@ class MirrordApi(private val service: MirrordProjectService) {
}

val bufferedReader = process.inputStream.reader().buffered()
val stderr = process.errorStream.reader().buffered()
MirrordLogger.logger.debug(stderr.readText())

val warningHandler = MirrordWarningHandler(project.service<MirrordProjectService>())
return bufferedReader.readText()
}
}
Expand All @@ -208,7 +176,7 @@ class MirrordApi(private val service: MirrordProjectService) {
configFilePath: String,
wslDistribution: WSLDistribution?
): String {
val verifyConfigTask = MirrordVerifyConfigTask(cli, configFilePath).apply {
val verifyConfigTask = MirrordVerifyConfigTask(cli, configFilePath, projectEnvVars).apply {
this.wslDistribution = wslDistribution
}
return verifyConfigTask.run(service.project)
Expand All @@ -220,16 +188,10 @@ class MirrordApi(private val service: MirrordProjectService) {
*
* @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: String?, configFile: String?, executable: String?, wslDistribution: WSLDistribution?): MirrordExecution {
bumpFeedbackCounter()

val task = MirrordExtTask(cli).apply {
val task = MirrordExtTask(cli, projectEnvVars).apply {
this.target = target
this.configFile = configFile
this.executable = executable
Expand All @@ -255,17 +217,7 @@ class MirrordApi(private val service: MirrordProjectService) {
return
}

service.notifier.notification(
"Enjoying mirrord? Don't forget to leave a review! Also consider giving us some feedback, we'd highly appreciate it!",
NotificationType.INFORMATION
)
.withLink(
"Review",
"https://plugins.jetbrains.com/plugin/19772-mirrord/reviews"
)
.withLink("Feedback", FEEDBACK_URL)
.withDontShowAgain(MirrordSettingsState.NotificationId.PLUGIN_REVIEW)
.fire()
service.notifier.notification("Enjoying mirrord? Don't forget to leave a review! Also consider giving us some feedback, we'd highly appreciate it!", NotificationType.INFORMATION).withLink("Review", "https://plugins.jetbrains.com/plugin/19772-mirrord/reviews").withLink("Feedback", FEEDBACK_URL).withDontShowAgain(MirrordSettingsState.NotificationId.PLUGIN_REVIEW).fire()
}
}

Expand All @@ -274,7 +226,7 @@ class MirrordApi(private val service: MirrordProjectService) {
*
* @param args: An extra list of arguments (used by `verify-config`).
*/
private abstract class MirrordCliTask<T>(private val cli: String, private val command: String, private val args: List<String>?) {
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 configFile: String? = null
var executable: String? = null
Expand All @@ -286,6 +238,11 @@ private abstract class MirrordCliTask<T>(private val cli: String, private val co
*/
private fun prepareCommandLine(project: Project): GeneralCommandLine {
return GeneralCommandLine(cli, command).apply {
// Merge our `environment` vars with what's set in the current launch run configuration.
if (projectEnvVars != null) {
environment.putAll(projectEnvVars)
}

target?.let {
addParameter("-t")
addParameter(it)
Expand Down Expand Up @@ -375,11 +332,7 @@ private abstract class MirrordCliTask<T>(private val cli: String, private val co
val commandLine = prepareCommandLine(project)
MirrordLogger.logger.info("running mirrord task with following command line: ${commandLine.commandLineString}")

val process = commandLine
.toProcessBuilder()
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
val process = commandLine.toProcessBuilder().redirectOutput(ProcessBuilder.Redirect.PIPE).redirectError(ProcessBuilder.Redirect.PIPE).start()

return if (ApplicationManager.getApplication().isDispatchThread) {
// Modal dialog with progress is very visible and can be canceled by the user,
Expand Down Expand Up @@ -450,12 +403,7 @@ private class MirrordWarningHandler(private val service: MirrordProjectService)
}
}

private val filters: List<WarningFilter> = listOf(
WarningFilter(
{ message -> message.contains("Agent version") && message.contains("does not match the local mirrord version") },
MirrordSettingsState.NotificationId.AGENT_VERSION_MISMATCH
)
)
private val filters: List<WarningFilter> = listOf(WarningFilter({ message -> message.contains("Agent version") && message.contains("does not match the local mirrord version") }, MirrordSettingsState.NotificationId.AGENT_VERSION_MISMATCH))

/**
* Shows the warning notification, optionally providing the "Don't show again" option.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,6 @@ class MirrordBinaryManager {
}

val binary = MirrordBinary(output, wslDistribution)

val isRequiredVersion = try {
// for release CI, the tag can be greater than the latest release
if (System.getenv("CI_BUILD_PLUGIN") == "true") {
Expand All @@ -297,7 +296,6 @@ class MirrordBinaryManager {
} catch (e: Exception) {
MirrordLogger.logger.debug("failed to find mirrord in path", e)
}

return null
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,21 @@ import java.nio.file.Path
* if it did, it will do nothing
*/
class MirrordExecManager(private val service: MirrordProjectService) {
/** Attempts to show the target selection dialog and allow user to select the mirrord target.
/**
* Attempts to show the target selection dialog and allow user to select the mirrord target.
*
* @return target chosen by the user (or special constant for targetless mode)
* @throws ProcessCanceledException if the dialog cannot be displayed
*/
private fun chooseTarget(
cli: String,
wslDistribution: WSLDistribution?,
config: String?
config: String?,
mirrordApi: MirrordApi
): String {
MirrordLogger.logger.debug("choose target called")

val pods = service.mirrordApi.listPods(
val pods = mirrordApi.listPods(
cli,
config,
wslDistribution
Expand Down Expand Up @@ -87,6 +89,7 @@ class MirrordExecManager(private val service: MirrordProjectService) {
/**
* Starts mirrord, shows dialog for selecting pod if target is not set and returns env to set.
*
* @param envVars Contains both system env vars, and (active) launch settings, see `Wrapper`.
* @return extra environment variables to set for the executed process and path to the patched executable.
* null if mirrord service is disabled
* @throws ProcessCanceledException if the user cancelled
Expand All @@ -95,7 +98,7 @@ class MirrordExecManager(private val service: MirrordProjectService) {
wslDistribution: WSLDistribution?,
executable: String?,
product: String,
mirrordConfigFromEnv: String?
projectEnvVars: Map<String, String>?
): Pair<Map<String, String>, String?>? {
MirrordLogger.logger.debug("MirrordExecManager.start")
if (!service.enabled) {
Expand All @@ -110,16 +113,21 @@ class MirrordExecManager(private val service: MirrordProjectService) {
MirrordLogger.logger.debug("version check trigger")
service.versionCheck.checkVersion() // TODO makes an HTTP request, move to background

val mirrordApi = service.mirrordApi(projectEnvVars)

val mirrordConfigPath = projectEnvVars?.get(CONFIG_ENV_NAME)
val cli = cliPath(wslDistribution, product)

MirrordLogger.logger.debug("MirrordExecManager.start: mirrord cli path is $cli")
// Find the mirrord config path, then call `mirrord verify-config {path}` so we can display warnings/errors
// from the config without relying on mirrord-layer.
val configPath = service.configApi.getConfigPath(mirrordConfigFromEnv)

val configPath = service.configApi.getConfigPath(mirrordConfigPath)
MirrordLogger.logger.debug("MirrordExecManager.start: config path is $configPath")

val verifiedConfig = configPath?.let {
val verifiedConfigOutput =
service.mirrordApi.verifyConfig(cli, wslDistribution?.getWslPath(it) ?: it, wslDistribution)
mirrordApi.verifyConfig(cli, wslDistribution?.getWslPath(it) ?: it, wslDistribution)
MirrordLogger.logger.debug("MirrordExecManager.start: verifiedConfigOutput: $verifiedConfigOutput")
MirrordVerifiedConfig(verifiedConfigOutput, service.notifier).apply {
MirrordLogger.logger.debug("MirrordExecManager.start: MirrordVerifiedConfig: $it")
Expand All @@ -136,7 +144,8 @@ class MirrordExecManager(private val service: MirrordProjectService) {
val target = if (!targetSet) {
// There is no config file or the config does not specify a target, so show dialog.
MirrordLogger.logger.debug("target not selected, showing dialog")
chooseTarget(cli, wslDistribution, configPath)

chooseTarget(cli, wslDistribution, configPath, mirrordApi)
.takeUnless { it == MirrordExecDialog.targetlessTargetName }
.alsoIfNull {
MirrordLogger.logger.info("No target specified - running targetless")
Expand All @@ -151,7 +160,7 @@ class MirrordExecManager(private val service: MirrordProjectService) {
null
}

val executionInfo = service.mirrordApi.exec(
val executionInfo = mirrordApi.exec(
cli,
target,
configPath,
Expand All @@ -164,14 +173,18 @@ class MirrordExecManager(private val service: MirrordProjectService) {
return Pair(executionInfo.environment, executionInfo.patchedPath)
}

class Wrapper(private val manager: MirrordExecManager, private val product: String) {
/**
* Wrapper around `MirrordExecManager` that is called by each IDE, or language variant.
*
* Helps to handle special cases and differences between the IDEs or language runners (like npm).
*/
class Wrapper(private val manager: MirrordExecManager, private val product: String, private val extraEnvVars: Map<String, String>?) {
var wsl: WSLDistribution? = null
var executable: String? = null
var configFromEnv: String? = null

fun start(): Pair<Map<String, String>, String?>? {
return try {
manager.start(wsl, executable, product, configFromEnv)
manager.start(wsl, executable, product, extraEnvVars)
} catch (e: MirrordError) {
e.showHelp(manager.service.project)
throw e
Expand All @@ -186,7 +199,17 @@ class MirrordExecManager(private val service: MirrordProjectService) {
}
}

fun wrapper(product: String): Wrapper {
return Wrapper(this, product)
/**
* Gives the caller a handle to call `MirrordExecManager::start`, based on the `product`.
*
* @param product The IDE/language that we're wrapping mirrord execution around, some valid
* values are: "rider", "JS", "nodejs" (there are many more).
*
* @param extraEnvVars Environment variables that come from project/IDE special environment.
*
* @return A `Wrapper` where you may call `start` to start running mirrord.
*/
fun wrapper(product: String, extraEnvVars: Map<String, String>?): Wrapper {
return Wrapper(this, product, extraEnvVars)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,9 @@ class MirrordNpmExecutionListener : ExecutionListener {

executionGuard.originEnv = LinkedHashMap(runSettings.envs)

service.execManager.wrapper("JS").apply {
service.execManager.wrapper("JS", executionGuard.originEnv).apply {
wsl = wslDistribution
executable = executablePath
configFromEnv = runSettings.envs[CONFIG_ENV_NAME]
}.start()?.let { (newEnv, patchedPath) ->
runSettings.envs = executionGuard.originEnv + newEnv

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ class MirrordProjectService(val project: Project) : Disposable {

val versionCheck: MirrordVersionCheck = MirrordVersionCheck(this)

val mirrordApi: MirrordApi = MirrordApi(this)
fun mirrordApi(environment: Map<String, String>?): MirrordApi {
return MirrordApi(this, environment)
}

val notifier: MirrordNotifier = MirrordNotifier(this)

Expand Down
Loading

0 comments on commit 5b84234

Please sign in to comment.