From 645f228241a1bed7506d74e37bb8874fb552910e Mon Sep 17 00:00:00 2001 From: Dmitry Dodzin Date: Sun, 15 Dec 2024 16:00:27 +0200 Subject: [PATCH 1/9] ?? --- .../com/metalbear/mirrord/MirrordApi.kt | 97 +++++++++++++++- .../metalbear/mirrord/MirrordExecManager.kt | 105 ++++++++++++++++++ .../pycharm/PythonCommandLineProvider.kt | 37 ++++-- 3 files changed, 226 insertions(+), 13 deletions(-) diff --git a/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordApi.kt b/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordApi.kt index c032963c..5bc6051a 100644 --- a/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordApi.kt +++ b/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordApi.kt @@ -101,6 +101,12 @@ data class MirrordExecution( @SerializedName("uses_operator") val usesOperator: Boolean? ) +data class MirrordContainerExecution( + val runtime: String, + @SerializedName("extra_args") val extraArgs: MutableList, + @SerializedName("uses_operator") val usesOperator: Boolean? +) + /** * Wrapper around Gson for parsing messages from the mirrord binary. */ @@ -144,7 +150,7 @@ private const val MIRRORD_FOR_TEAMS_INVITE_EVERY = 30 * Interact with mirrord CLI using this API. */ class MirrordApi(private val service: MirrordProjectService, private val projectEnvVars: Map?) { - private class MirrordLsTask(cli: String, projectEnvVars: Map?) : MirrordCliTask>(cli, "ls", null, projectEnvVars) { + private class MirrordLsTask(cli: String, projectEnvVars: Map?) : MirrordCliTask>(cli, listOf("ls"), null, projectEnvVars) { override fun compute(project: Project, process: Process, setText: (String) -> Unit): List { setText("mirrord is listing targets...") @@ -183,7 +189,7 @@ class MirrordApi(private val service: MirrordProjectService, private val project return task.run(service.project) } - private class MirrordExtTask(cli: String, projectEnvVars: Map?) : MirrordCliTask(cli, "ext", null, projectEnvVars) { + private class MirrordExtTask(cli: String, projectEnvVars: Map?) : MirrordCliTask(cli, listOf("ext"), null, projectEnvVars) { override fun compute(project: Project, process: Process, setText: (String) -> Unit): MirrordExecution { val parser = SafeParser() val bufferedReader = process.inputStream.reader().buffered() @@ -243,13 +249,73 @@ class MirrordApi(private val service: MirrordProjectService, private val project } } + private class MirrordContainerExtTask(cli: String, projectEnvVars: Map?) : MirrordCliTask(cli, listOf("container", "ext"), null, projectEnvVars) { + override fun compute(project: Project, process: Process, setText: (String) -> Unit): MirrordContainerExecution { + val parser = SafeParser() + val bufferedReader = process.inputStream.reader().buffered() + + val warningHandler = MirrordWarningHandler(project.service()) + + setText("mirrord is starting...") + for (line in bufferedReader.lines()) { + val message = parser.parse(line, Message::class.java) + when { + message.name == "mirrord preparing to launch" && message.type == MessageType.FinishedTask -> { + val success = message.success + ?: throw MirrordError("invalid message received from the mirrord binary") + if (success) { + val innerMessage = message.message + ?: throw MirrordError("invalid message received from the mirrord binary") + val executionInfo = parser.parse(innerMessage as String, MirrordContainerExecution::class.java) + setText("mirrord is running") + return executionInfo + } + } + + message.type == MessageType.Info -> { + val service = project.service() + message.message?.let { service.notifier.notifySimple(it as String, NotificationType.INFORMATION) } + } + + message.type == MessageType.Warning -> { + message.message?.let { warningHandler.handle(it as String) } + } + + message.type == MessageType.IdeMessage -> { + message.message?.run { + val ideMessage = Gson().fromJson(Gson().toJsonTree(this), IdeMessage::class.java) + val service = project.service() + ideMessage?.handleIdeMessage(service) + } + } + + else -> { + var displayMessage = message.name + message.message?.let { + displayMessage += ": $it" + } + setText(displayMessage) + } + } + } + + process.waitFor() + if (process.exitValue() != 0) { + val processStdError = process.errorStream.bufferedReader().readText() + throw MirrordError.fromStdErr(processStdError) + } else { + throw MirrordError("invalid output of the mirrord binary") + } + } + } + /** * Interacts with the `mirrord verify-config [path]` cli command. * * 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, projectEnvVars: Map?) : MirrordCliTask(cli, "verify-config", listOf("--ide", path), projectEnvVars) { + private class MirrordVerifyConfigTask(cli: String, path: String, projectEnvVars: Map?) : MirrordCliTask(cli, listOf("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() @@ -310,6 +376,27 @@ class MirrordApi(private val service: MirrordProjectService, private val project return result } + fun containerExec(cli: String, target: String?, configFile: String?, wslDistribution: WSLDistribution?): MirrordContainerExecution { + bumpRunCounter() + + val task = MirrordContainerExtTask(cli, projectEnvVars).apply { + this.target = target + this.configFile = configFile + this.wslDistribution = wslDistribution + } + + val result = task.run(service.project) + service.notifier.notifySimple("mirrord starting...", NotificationType.INFORMATION) + + result.usesOperator?.let { usesOperator -> + if (usesOperator) { + MirrordSettingsState.instance.mirrordState.operatorUsed = true + } + } + + return result + } + /** * Increments the mirrord run counter. * Can display some notifications (asking for feedback, discord invite, mirrord for Teams invite). @@ -342,7 +429,7 @@ class MirrordApi(private val service: MirrordProjectService, private val project * * @param args: An extra list of arguments (used by `verify-config`). */ -private abstract class MirrordCliTask(private val cli: String, private val command: String, private val args: List?, private val projectEnvVars: Map?) { +private abstract class MirrordCliTask(private val cli: String, private val command: List, private val args: List?, private val projectEnvVars: Map?) { var target: String? = null var configFile: String? = null var executable: String? = null @@ -353,7 +440,7 @@ private abstract class MirrordCliTask(private val cli: String, private val co * Returns command line for execution. */ private fun prepareCommandLine(project: Project): GeneralCommandLine { - return GeneralCommandLine(cli, command).apply { + return GeneralCommandLine(cli, *command.toTypedArray()).apply { // Merge our `environment` vars with what's set in the current launch run configuration. if (projectEnvVars != null) { environment.putAll(projectEnvVars) diff --git a/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt b/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt index 40719110..69a4f5c2 100644 --- a/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt +++ b/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt @@ -198,6 +198,95 @@ class MirrordExecManager(private val service: MirrordProjectService) { return executionInfo } + private fun containerStart( + wslDistribution: WSLDistribution?, + product: String, + projectEnvVars: Map? + ): MirrordContainerExecution? { + MirrordLogger.logger.debug("MirrordExecManager.startContainer") + val explicitlyEnabled = projectEnvVars?.any { (key, value) -> key == "MIRRORD_ACTIVE" && value == "1" } ?: false + if (!service.enabled && !explicitlyEnabled) { + MirrordLogger.logger.debug("disabled, returning") + return null + } + + val mirrordApi = service.mirrordApi(projectEnvVars) + + val mirrordConfigPath = projectEnvVars?.get(CONFIG_ENV_NAME)?.let { + if (it.contains("\$ProjectPath\$")) { + val projectFile = service.configApi.getProjectDir() + projectFile.canonicalPath?.let { path -> + it.replace("\$ProjectPath\$", path) + } ?: run { + service.notifier.notifySimple( + "Failed to evaluate `ProjectPath` macro used in `$CONFIG_ENV_NAME` environment variable", + NotificationType.WARNING + ) + it + } + } else { + it + } + } + 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(mirrordConfigPath) + MirrordLogger.logger.debug("MirrordExecManager.start: config path is $configPath") + + val verifiedConfig = configPath?.let { + val verifiedConfigOutput = + 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") + if (isError()) { + MirrordLogger.logger.debug("MirrordExecManager.start: invalid config error") + throw InvalidConfigException(it, "Validation failed for config") + } + } + } + + MirrordLogger.logger.debug("Verified Config: $verifiedConfig, Target selection.") + + val targetSet = verifiedConfig?.let { isTargetSet(it.config) } ?: false + 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, mirrordApi) + .takeUnless { it == MirrordExecDialog.targetlessTargetName } ?: run { + MirrordLogger.logger.info("No target specified - running targetless") + service.notifier.notification( + "No target specified, mirrord running targetless.", + NotificationType.INFORMATION + ) + .withDontShowAgain(MirrordSettingsState.NotificationId.RUNNING_TARGETLESS) + .fire() + + null + } + } else { + null + } + + val executionInfo = mirrordApi.containerExec( + cli, + target, + configPath, + wslDistribution + ) + MirrordLogger.logger.debug("MirrordExecManager.start: executionInfo: $executionInfo") + + executionInfo.extraArgs.add("-e") + executionInfo.extraArgs.add("MIRRORD_IGNORE_DEBUGGER_PORTS=\"35000-65535\"") + + return executionInfo + } + /** * Wrapper around `MirrordExecManager` that is called by each IDE, or language variant. * @@ -222,6 +311,22 @@ class MirrordExecManager(private val service: MirrordProjectService) { throw e } } + + fun containerStart(): MirrordContainerExecution? { + return try { + manager.containerStart(wsl, product, extraEnvVars) + } catch (e: MirrordError) { + e.showHelp(manager.service.project) + throw e + } catch (e: ProcessCanceledException) { + manager.service.notifier.notifySimple("mirrord was cancelled", NotificationType.WARNING) + throw e + } catch (e: Throwable) { + val mirrordError = MirrordError(e.toString(), e) + mirrordError.showHelp(manager.service.project) + throw e + } + } } /** diff --git a/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt b/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt index fd96b7a3..8024e1c2 100644 --- a/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt +++ b/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt @@ -3,11 +3,13 @@ package com.metalbear.mirrord.products.pycharm import com.intellij.execution.wsl.target.WslTargetEnvironmentRequest import com.intellij.openapi.components.service import com.intellij.openapi.project.Project +import com.intellij.openapi.util.SystemInfo import com.jetbrains.python.run.AbstractPythonRunConfiguration import com.jetbrains.python.run.PythonExecution import com.jetbrains.python.run.PythonRunParams import com.jetbrains.python.run.target.HelpersAwareTargetEnvironmentRequest import com.jetbrains.python.run.target.PythonCommandLineTargetEnvironmentProvider +import com.metalbear.mirrord.MirrordBinaryManager import com.metalbear.mirrord.MirrordProjectService class PythonCommandLineProvider : PythonCommandLineTargetEnvironmentProvider { @@ -28,18 +30,37 @@ class PythonCommandLineProvider : PythonCommandLineTargetEnvironmentProvider { } } - service.execManager.wrapper("pycharm", runParams.getEnvs()).apply { - this.wsl = wsl - }.start()?.let { executionInfo -> - for (entry in executionInfo.environment.entries.iterator()) { - pythonExecution.addEnvironmentVariable(entry.key, entry.value) + val docker = helpersAwareTargetRequest.targetEnvironmentRequest.let { + if (it.javaClass.name.startsWith("com.intellij.docker")) { + it + } else { + null } + } - for (key in executionInfo.envToUnset.orEmpty()) { - pythonExecution.envs.remove(key) + if (docker != null) { + service.execManager.wrapper("pycharm", runParams.getEnvs()).apply { + this.wsl = wsl + }.containerStart()?.let { executionInfo -> + val runCliOptions = "--entrypoint= --rm" + + executionInfo.extraArgs.add( runCliOptions); + docker.javaClass.getMethod("setRunCliOptions", Class.forName("java.lang.String")).invoke(docker, executionInfo.extraArgs.joinToString(" ")) } + } else { + service.execManager.wrapper("pycharm", runParams.getEnvs()).apply { + this.wsl = wsl + }.start()?.let { executionInfo -> + for (entry in executionInfo.environment.entries.iterator()) { + pythonExecution.addEnvironmentVariable(entry.key, entry.value) + } + + for (key in executionInfo.envToUnset.orEmpty()) { + pythonExecution.envs.remove(key) + } - pythonExecution.addEnvironmentVariable("MIRRORD_DETECT_DEBUGGER_PORT", "pydevd") + pythonExecution.addEnvironmentVariable("MIRRORD_DETECT_DEBUGGER_PORT", "pydevd") + } } } } From b1a1e7f12c25dcd3b88dc031a7b43fffe58df93c Mon Sep 17 00:00:00 2001 From: Dmitry Dodzin Date: Sun, 15 Dec 2024 18:36:03 +0200 Subject: [PATCH 2/9] Tiny --- .../mirrord/products/pycharm/PythonCommandLineProvider.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt b/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt index 8024e1c2..48f9c16f 100644 --- a/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt +++ b/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt @@ -42,9 +42,12 @@ class PythonCommandLineProvider : PythonCommandLineTargetEnvironmentProvider { service.execManager.wrapper("pycharm", runParams.getEnvs()).apply { this.wsl = wsl }.containerStart()?.let { executionInfo -> - val runCliOptions = "--entrypoint= --rm" + val runCliOptionsField = docker.javaClass.getDeclaredField("myRunCliOptions"); + if (runCliOptionsField.trySetAccessible()) { + val runCliOptions = runCliOptionsField.get(docker) as String + executionInfo.extraArgs.add(runCliOptions); + } - executionInfo.extraArgs.add( runCliOptions); docker.javaClass.getMethod("setRunCliOptions", Class.forName("java.lang.String")).invoke(docker, executionInfo.extraArgs.joinToString(" ")) } } else { From 34c74d02e12d401eb5de27a8a7e4e4e76a8e9edb Mon Sep 17 00:00:00 2001 From: Dmitry Dodzin Date: Mon, 16 Dec 2024 15:26:41 +0200 Subject: [PATCH 3/9] Update --- .../pycharm/PythonCommandLineProvider.kt | 64 +++++++++++++------ 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt b/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt index 48f9c16f..d0364daa 100644 --- a/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt +++ b/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt @@ -1,18 +1,50 @@ package com.metalbear.mirrord.products.pycharm +import com.intellij.execution.target.TargetEnvironmentRequest import com.intellij.execution.wsl.target.WslTargetEnvironmentRequest import com.intellij.openapi.components.service import com.intellij.openapi.project.Project -import com.intellij.openapi.util.SystemInfo import com.jetbrains.python.run.AbstractPythonRunConfiguration import com.jetbrains.python.run.PythonExecution import com.jetbrains.python.run.PythonRunParams import com.jetbrains.python.run.target.HelpersAwareTargetEnvironmentRequest import com.jetbrains.python.run.target.PythonCommandLineTargetEnvironmentProvider -import com.metalbear.mirrord.MirrordBinaryManager import com.metalbear.mirrord.MirrordProjectService class PythonCommandLineProvider : PythonCommandLineTargetEnvironmentProvider { + private class DockerRuntimeConfig(val inner: TargetEnvironmentRequest) { + + var runCliOptions: String? + get() { + val runCliOptionsField = inner.javaClass.getDeclaredField("myRunCliOptions"); + return if (runCliOptionsField.trySetAccessible()) { + runCliOptionsField.get(inner) as String + } else { + null + } + } + set(value) { + inner + .javaClass + .getMethod("setRunCliOptions", Class.forName("java.lang.String")) + .invoke(inner, value) + } + } + + private fun extendContainerTargetEnvironment(project: Project, runParams: PythonRunParams, docker: DockerRuntimeConfig) { + val service = project.service() + + service.execManager.wrapper("pycharm", runParams.getEnvs()).apply { + this.wsl = null + }.containerStart()?.let { executionInfo -> + docker.runCliOptions?.let { + executionInfo.extraArgs.add(it) + } + + docker.runCliOptions = executionInfo.extraArgs.joinToString(" ") + } + } + override fun extendTargetEnvironment( project: Project, helpersAwareTargetRequest: HelpersAwareTargetEnvironmentRequest, @@ -22,35 +54,25 @@ class PythonCommandLineProvider : PythonCommandLineTargetEnvironmentProvider { val service = project.service() if (runParams is AbstractPythonRunConfiguration<*>) { - val wsl = helpersAwareTargetRequest.targetEnvironmentRequest.let { - if (it is WslTargetEnvironmentRequest) { - it.configuration.distribution - } else { - null - } - } - val docker = helpersAwareTargetRequest.targetEnvironmentRequest.let { if (it.javaClass.name.startsWith("com.intellij.docker")) { - it + DockerRuntimeConfig(it) } else { null } } if (docker != null) { - service.execManager.wrapper("pycharm", runParams.getEnvs()).apply { - this.wsl = wsl - }.containerStart()?.let { executionInfo -> - val runCliOptionsField = docker.javaClass.getDeclaredField("myRunCliOptions"); - if (runCliOptionsField.trySetAccessible()) { - val runCliOptions = runCliOptionsField.get(docker) as String - executionInfo.extraArgs.add(runCliOptions); + extendContainerTargetEnvironment(project, runParams, docker) + } else { + val wsl = helpersAwareTargetRequest.targetEnvironmentRequest.let { + if (it is WslTargetEnvironmentRequest) { + it.configuration.distribution + } else { + null } - - docker.javaClass.getMethod("setRunCliOptions", Class.forName("java.lang.String")).invoke(docker, executionInfo.extraArgs.joinToString(" ")) } - } else { + service.execManager.wrapper("pycharm", runParams.getEnvs()).apply { this.wsl = wsl }.start()?.let { executionInfo -> From 0d89bc35bddd51ef1a15b27c1781cb8b71490f59 Mon Sep 17 00:00:00 2001 From: Dmitry Dodzin Date: Mon, 16 Dec 2024 16:01:38 +0200 Subject: [PATCH 4/9] Damn klint --- .../mirrord/products/pycharm/PythonCommandLineProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt b/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt index d0364daa..195920d0 100644 --- a/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt +++ b/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt @@ -16,7 +16,7 @@ class PythonCommandLineProvider : PythonCommandLineTargetEnvironmentProvider { var runCliOptions: String? get() { - val runCliOptionsField = inner.javaClass.getDeclaredField("myRunCliOptions"); + val runCliOptionsField = inner.javaClass.getDeclaredField("myRunCliOptions") return if (runCliOptionsField.trySetAccessible()) { runCliOptionsField.get(inner) as String } else { From 8425d6f687f75f4ac5821dc2b38b652701a7f69e Mon Sep 17 00:00:00 2001 From: Dmitry Dodzin Date: Mon, 16 Dec 2024 16:04:07 +0200 Subject: [PATCH 5/9] Changelog --- changelog.d/294.fixed.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/294.fixed.md diff --git a/changelog.d/294.fixed.md b/changelog.d/294.fixed.md new file mode 100644 index 00000000..2a4f4430 --- /dev/null +++ b/changelog.d/294.fixed.md @@ -0,0 +1 @@ +Add support for using python docker interpreter. From 778c3dabffb8cec5f70976e3a180d6e374bb4e70 Mon Sep 17 00:00:00 2001 From: Dmitry Dodzin Date: Tue, 17 Dec 2024 18:33:21 +0200 Subject: [PATCH 6/9] Update --- .../metalbear/mirrord/MirrordExecManager.kt | 112 +++++------------- .../pycharm/PythonCommandLineProvider.kt | 7 +- 2 files changed, 31 insertions(+), 88 deletions(-) diff --git a/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt b/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt index 69a4f5c2..e2c189ab 100644 --- a/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt +++ b/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt @@ -84,20 +84,12 @@ class MirrordExecManager(private val service: MirrordProjectService) { return wslDistribution?.getWslPath(path) ?: path } - /** - * 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 - */ - private fun start( + private fun prepareStart( wslDistribution: WSLDistribution?, - executable: String?, - product: String, - projectEnvVars: Map? - ): MirrordExecution? { + projectEnvVars: Map?, + mirrordApi: MirrordApi, + cli: String, + ): Pair? { MirrordLogger.logger.debug("MirrordExecManager.start") val mirrordActiveValue = projectEnvVars?.get("MIRRORD_ACTIVE") val explicitlyEnabled = mirrordActiveValue == "1" @@ -122,8 +114,6 @@ class MirrordExecManager(private val service: MirrordProjectService) { ) } - val mirrordApi = service.mirrordApi(projectEnvVars) - val mirrordConfigPath = projectEnvVars?.get(CONFIG_ENV_NAME)?.let { if (it.contains("\$ProjectPath\$")) { val projectFile = service.configApi.getProjectDir() @@ -140,7 +130,6 @@ class MirrordExecManager(private val service: MirrordProjectService) { it } } - 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 @@ -185,6 +174,27 @@ class MirrordExecManager(private val service: MirrordProjectService) { null } + return Pair(configPath, target) + } + + /** + * 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 + */ + private fun start( + wslDistribution: WSLDistribution?, + executable: String?, + product: String, + projectEnvVars: Map? + ): MirrordExecution? { + val cli = cliPath(wslDistribution, product) + val mirrordApi = service.mirrordApi(projectEnvVars) + val (configPath, target) = this.prepareStart(wslDistribution, projectEnvVars, mirrordApi, cli) ?: return null + val executionInfo = mirrordApi.exec( cli, target, @@ -203,75 +213,9 @@ class MirrordExecManager(private val service: MirrordProjectService) { product: String, projectEnvVars: Map? ): MirrordContainerExecution? { - MirrordLogger.logger.debug("MirrordExecManager.startContainer") - val explicitlyEnabled = projectEnvVars?.any { (key, value) -> key == "MIRRORD_ACTIVE" && value == "1" } ?: false - if (!service.enabled && !explicitlyEnabled) { - MirrordLogger.logger.debug("disabled, returning") - return null - } - - val mirrordApi = service.mirrordApi(projectEnvVars) - - val mirrordConfigPath = projectEnvVars?.get(CONFIG_ENV_NAME)?.let { - if (it.contains("\$ProjectPath\$")) { - val projectFile = service.configApi.getProjectDir() - projectFile.canonicalPath?.let { path -> - it.replace("\$ProjectPath\$", path) - } ?: run { - service.notifier.notifySimple( - "Failed to evaluate `ProjectPath` macro used in `$CONFIG_ENV_NAME` environment variable", - NotificationType.WARNING - ) - it - } - } else { - it - } - } 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(mirrordConfigPath) - MirrordLogger.logger.debug("MirrordExecManager.start: config path is $configPath") - - val verifiedConfig = configPath?.let { - val verifiedConfigOutput = - 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") - if (isError()) { - MirrordLogger.logger.debug("MirrordExecManager.start: invalid config error") - throw InvalidConfigException(it, "Validation failed for config") - } - } - } - - MirrordLogger.logger.debug("Verified Config: $verifiedConfig, Target selection.") - - val targetSet = verifiedConfig?.let { isTargetSet(it.config) } ?: false - 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, mirrordApi) - .takeUnless { it == MirrordExecDialog.targetlessTargetName } ?: run { - MirrordLogger.logger.info("No target specified - running targetless") - service.notifier.notification( - "No target specified, mirrord running targetless.", - NotificationType.INFORMATION - ) - .withDontShowAgain(MirrordSettingsState.NotificationId.RUNNING_TARGETLESS) - .fire() - - null - } - } else { - null - } + val mirrordApi = service.mirrordApi(projectEnvVars) + val (configPath, target) = this.prepareStart(wslDistribution, projectEnvVars, mirrordApi, cli) ?: return null val executionInfo = mirrordApi.containerExec( cli, diff --git a/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt b/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt index 195920d0..6bacf7e7 100644 --- a/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt +++ b/modules/products/pycharm/src/main/kotlin/com/metalbear/mirrord/products/pycharm/PythonCommandLineProvider.kt @@ -12,8 +12,9 @@ import com.jetbrains.python.run.target.PythonCommandLineTargetEnvironmentProvide import com.metalbear.mirrord.MirrordProjectService class PythonCommandLineProvider : PythonCommandLineTargetEnvironmentProvider { + // Wrapper for docker variant of TargetEnvironmentRequest because the variant is dynamically loaded from another + // plugin so we need to perform our operations via reflection api private class DockerRuntimeConfig(val inner: TargetEnvironmentRequest) { - var runCliOptions: String? get() { val runCliOptionsField = inner.javaClass.getDeclaredField("myRunCliOptions") @@ -34,9 +35,7 @@ class PythonCommandLineProvider : PythonCommandLineTargetEnvironmentProvider { private fun extendContainerTargetEnvironment(project: Project, runParams: PythonRunParams, docker: DockerRuntimeConfig) { val service = project.service() - service.execManager.wrapper("pycharm", runParams.getEnvs()).apply { - this.wsl = null - }.containerStart()?.let { executionInfo -> + service.execManager.wrapper("pycharm", runParams.getEnvs()).containerStart()?.let { executionInfo -> docker.runCliOptions?.let { executionInfo.extraArgs.add(it) } From 00df9741572a4653738de5b95699dd8946f8f9ce Mon Sep 17 00:00:00 2001 From: Dmitry Dodzin Date: Tue, 17 Dec 2024 18:41:27 +0200 Subject: [PATCH 7/9] klint --- .../src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt b/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt index e2c189ab..eb20f783 100644 --- a/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt +++ b/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt @@ -88,7 +88,7 @@ class MirrordExecManager(private val service: MirrordProjectService) { wslDistribution: WSLDistribution?, projectEnvVars: Map?, mirrordApi: MirrordApi, - cli: String, + cli: String ): Pair? { MirrordLogger.logger.debug("MirrordExecManager.start") val mirrordActiveValue = projectEnvVars?.get("MIRRORD_ACTIVE") From bd4fda19536c6fdda7b46cfec69b7bbc77bbf13e Mon Sep 17 00:00:00 2001 From: Dmitry Dodzin Date: Tue, 17 Dec 2024 19:07:32 +0200 Subject: [PATCH 8/9] Updated CLI + revert list change --- .../main/kotlin/com/metalbear/mirrord/MirrordApi.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordApi.kt b/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordApi.kt index 5bc6051a..3e441f4b 100644 --- a/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordApi.kt +++ b/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordApi.kt @@ -150,7 +150,7 @@ private const val MIRRORD_FOR_TEAMS_INVITE_EVERY = 30 * Interact with mirrord CLI using this API. */ class MirrordApi(private val service: MirrordProjectService, private val projectEnvVars: Map?) { - private class MirrordLsTask(cli: String, projectEnvVars: Map?) : MirrordCliTask>(cli, listOf("ls"), null, projectEnvVars) { + private class MirrordLsTask(cli: String, projectEnvVars: Map?) : MirrordCliTask>(cli, "ls", null, projectEnvVars) { override fun compute(project: Project, process: Process, setText: (String) -> Unit): List { setText("mirrord is listing targets...") @@ -189,7 +189,7 @@ class MirrordApi(private val service: MirrordProjectService, private val project return task.run(service.project) } - private class MirrordExtTask(cli: String, projectEnvVars: Map?) : MirrordCliTask(cli, listOf("ext"), null, projectEnvVars) { + private class MirrordExtTask(cli: String, projectEnvVars: Map?) : MirrordCliTask(cli, "ext", null, projectEnvVars) { override fun compute(project: Project, process: Process, setText: (String) -> Unit): MirrordExecution { val parser = SafeParser() val bufferedReader = process.inputStream.reader().buffered() @@ -249,7 +249,7 @@ class MirrordApi(private val service: MirrordProjectService, private val project } } - private class MirrordContainerExtTask(cli: String, projectEnvVars: Map?) : MirrordCliTask(cli, listOf("container", "ext"), null, projectEnvVars) { + private class MirrordContainerExtTask(cli: String, projectEnvVars: Map?) : MirrordCliTask(cli, "container-ext", null, projectEnvVars) { override fun compute(project: Project, process: Process, setText: (String) -> Unit): MirrordContainerExecution { val parser = SafeParser() val bufferedReader = process.inputStream.reader().buffered() @@ -315,7 +315,7 @@ class MirrordApi(private val service: MirrordProjectService, private val project * 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, projectEnvVars: Map?) : MirrordCliTask(cli, listOf("verify-config"), listOf("--ide", path), projectEnvVars) { + private class MirrordVerifyConfigTask(cli: String, path: String, projectEnvVars: Map?) : MirrordCliTask(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() @@ -429,7 +429,7 @@ class MirrordApi(private val service: MirrordProjectService, private val project * * @param args: An extra list of arguments (used by `verify-config`). */ -private abstract class MirrordCliTask(private val cli: String, private val command: List, private val args: List?, private val projectEnvVars: Map?) { +private abstract class MirrordCliTask(private val cli: String, private val command: String, private val args: List?, private val projectEnvVars: Map?) { var target: String? = null var configFile: String? = null var executable: String? = null @@ -440,7 +440,7 @@ private abstract class MirrordCliTask(private val cli: String, private val co * Returns command line for execution. */ private fun prepareCommandLine(project: Project): GeneralCommandLine { - return GeneralCommandLine(cli, *command.toTypedArray()).apply { + 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) From 0f04c5e32ddf35091a6dd7aa63987207f5e841e7 Mon Sep 17 00:00:00 2001 From: Dmitry Dodzin Date: Tue, 17 Dec 2024 19:41:21 +0200 Subject: [PATCH 9/9] Update when cli is fetched --- .../com/metalbear/mirrord/MirrordExecManager.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt b/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt index eb20f783..7ea1343f 100644 --- a/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt +++ b/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt @@ -86,9 +86,9 @@ class MirrordExecManager(private val service: MirrordProjectService) { private fun prepareStart( wslDistribution: WSLDistribution?, + product: String, projectEnvVars: Map?, - mirrordApi: MirrordApi, - cli: String + mirrordApi: MirrordApi ): Pair? { MirrordLogger.logger.debug("MirrordExecManager.start") val mirrordActiveValue = projectEnvVars?.get("MIRRORD_ACTIVE") @@ -130,6 +130,7 @@ class MirrordExecManager(private val service: MirrordProjectService) { it } } + 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 @@ -191,9 +192,9 @@ class MirrordExecManager(private val service: MirrordProjectService) { product: String, projectEnvVars: Map? ): MirrordExecution? { - val cli = cliPath(wslDistribution, product) val mirrordApi = service.mirrordApi(projectEnvVars) - val (configPath, target) = this.prepareStart(wslDistribution, projectEnvVars, mirrordApi, cli) ?: return null + val (configPath, target) = this.prepareStart(wslDistribution, product, projectEnvVars, mirrordApi) ?: return null + val cli = cliPath(wslDistribution, product) val executionInfo = mirrordApi.exec( cli, @@ -213,9 +214,9 @@ class MirrordExecManager(private val service: MirrordProjectService) { product: String, projectEnvVars: Map? ): MirrordContainerExecution? { - val cli = cliPath(wslDistribution, product) val mirrordApi = service.mirrordApi(projectEnvVars) - val (configPath, target) = this.prepareStart(wslDistribution, projectEnvVars, mirrordApi, cli) ?: return null + val (configPath, target) = this.prepareStart(wslDistribution, product, projectEnvVars, mirrordApi) ?: return null + val cli = cliPath(wslDistribution, product) val executionInfo = mirrordApi.containerExec( cli,