Skip to content
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

Pass config env vars to mirrord verify-config. #181

Merged
merged 25 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
be559da
Pass config env vars to mirrord verify-config.
meowjesty Oct 16, 2023
c86eba0
Merge branch 'main' of github.com:metalbear-co/mirrord-intellij into …
meowjesty Oct 16, 2023
ccbbd5c
function to get the env vars from launch config // reformat code // docs
meowjesty Oct 16, 2023
dd189de
format // uncomment version check
meowjesty Oct 16, 2023
7ad3d24
format // more version check revert
meowjesty Oct 16, 2023
21fad45
ktlint format
meowjesty Oct 16, 2023
68afb2e
changelog
meowjesty Oct 16, 2023
4ecc01e
auto formatting things
meowjesty Oct 18, 2023
a2c364b
remove noisy logs // cat noises
meowjesty Oct 18, 2023
3b4beba
missing return null
meowjesty Oct 18, 2023
c0fa86e
return lift
meowjesty Oct 18, 2023
3ece4e0
do we want it in the individual IDE runs?
meowjesty Oct 19, 2023
65f07cd
use run settings env vars everywhere
meowjesty Oct 20, 2023
69ba14e
klint
meowjesty Oct 20, 2023
66e53ab
change env vars everywhere (2) // have different project|ide set addi…
meowjesty Oct 20, 2023
48a6c21
ktlint
meowjesty Oct 20, 2023
bf77f26
Merge branch 'main' of github.com:metalbear-co/mirrord-intellij into …
meowjesty Nov 14, 2023
8d165ae
have extraEnv be more complete?
meowjesty Nov 14, 2023
0206bbd
tomcat env vars
meowjesty Nov 14, 2023
d470ea6
comment configFromEnv
meowjesty Nov 14, 2023
37f65d5
addressing CR // remove function for getting env vars (we pass them d…
meowjesty Nov 22, 2023
aa0b2f5
conflicts resolved
meowjesty Nov 22, 2023
4042986
optimus prime imports
meowjesty Nov 22, 2023
6eb9c6a
lint
meowjesty Nov 22, 2023
25a9753
merge main
meowjesty Nov 28, 2023
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
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.
132 changes: 44 additions & 88 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,8 @@ package com.metalbear.mirrord

import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
import com.intellij.execution.CommonProgramRunConfigurationParameters
import com.intellij.execution.RunManager
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.wsl.WSLCommandLineOptions
import com.intellij.execution.wsl.WSLDistribution
Expand All @@ -15,41 +17,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 +44,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 +57,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 +71,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 +87,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 +97,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 +150,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 @@ -195,6 +161,8 @@ class MirrordApi(private val service: MirrordProjectService) {

val parser = SafeParser()
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 @@ -206,11 +174,8 @@ class MirrordApi(private val service: MirrordProjectService) {
*
* @return String containing a json with either a success + warnings, or the verified config errors.
*/
fun verifyConfig(
cli: String,
configFilePath: String
): String {
return MirrordVerifyConfigTask(cli, configFilePath).run(service.project)
fun verifyConfig(cli: String, configFilePath: String): String {
return MirrordVerifyConfigTask(cli, configFilePath, projectEnvVars).run(service.project)
}

/**
Expand All @@ -219,16 +184,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 @@ -254,26 +213,27 @@ 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()
}
}

/**
* Gets the env vars set by the user for the current run (there might be more than 1 run configuration).
*
* @param project: Contains the `selectedConfiguration`, which holds the active env vars.
* @return A `Map` with the launch env vars, that can be put into the `environment` when running mirrord.
*/
fun getEnvVarsFromActiveLaunchSettings(project: Project): Map<String, String>? {
Razz4780 marked this conversation as resolved.
Show resolved Hide resolved
Razz4780 marked this conversation as resolved.
Show resolved Hide resolved
val runConfig = RunManager.getInstance(project).selectedConfiguration?.configuration
return if (runConfig is CommonProgramRunConfigurationParameters) runConfig.envs else null
}

/**
* A mirrord CLI invocation.
*
* @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 @@ -285,6 +245,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 @@ -373,11 +338,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 @@ -448,12 +409,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 @@ -278,14 +278,15 @@ class MirrordBinaryManager {
}

val binary = MirrordBinary(output)
if (requiredVersion == null || requiredVersion == binary.version) {
return binary
return if (requiredVersion == null || requiredVersion == binary.version) {
binary
} else {
null
}
} catch (e: Exception) {
MirrordLogger.logger.debug("failed to find mirrord in path", e)
return null
}

return null
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ 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
Expand Down Expand Up @@ -86,6 +87,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 @@ -94,7 +96,7 @@ class MirrordExecManager(private val service: MirrordProjectService) {
wslDistribution: WSLDistribution?,
executable: String?,
product: String,
mirrordConfigFromEnv: String?
projectEnvVars: Map<String, String>?
): Pair<Map<String, String>, String?>? {
if (!service.enabled) {
MirrordLogger.logger.debug("disabled, returning")
Expand All @@ -108,12 +110,15 @@ class MirrordExecManager(private val service: MirrordProjectService) {
MirrordLogger.logger.debug("version check trigger")
service.versionCheck.checkVersion() // TODO makes an HTTP request, move to background

// TODO(alex) To avoid having this kind of code, we should merge system vars with launch vars
// everywhere.
val mirrordConfigFile = projectEnvVars?.get(CONFIG_ENV_NAME) ?: System.getenv()[CONFIG_ENV_NAME]
val cli = cliPath(wslDistribution, product)
val config = service.configApi.getConfigPath(mirrordConfigFromEnv)
val config = service.configApi.getConfigPath(mirrordConfigFile)

// 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(mirrordConfigFile)
var verifiedConfig: MirrordVerifiedConfig? = null
if (configPath != null) {
val verifiedConfigOutput = service.mirrordApi.verifyConfig(cli, configPath)
Expand Down Expand Up @@ -156,14 +161,34 @@ 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>?) {

/**
* These are the env vars set in the launch project config, and those that are set in
* some other ways, such as a patched command line, or as arguments to some app.
*
* Initialized only once to avoid issues when the user could change the active mirrord
* config mid-run, as we mix `MIRRORD_`-style env vars with the values in the `mirrord.json`
* file.
*/
private val projectEnvVars: Map<String, String>? = run {
Razz4780 marked this conversation as resolved.
Show resolved Hide resolved
val envRunSettings = getEnvVarsFromActiveLaunchSettings(manager.service.project)
extraEnvVars?.plus(envRunSettings ?: emptyMap()) ?: envRunSettings
}

//val extraEnv = params.env + (configuration as ExternalSystemRunConfiguration).settings.env

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, projectEnvVars)
} catch (e: MirrordError) {
e.showHelp(manager.service.project)
throw e
Expand All @@ -178,7 +203,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
Loading