-
Notifications
You must be signed in to change notification settings - Fork 43
Allow env variable substitution in CLI client properties #242
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
base: main
Are you sure you want to change the base?
Conversation
…variables in config file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request adds environment variable substitution support to the CLI client properties file by replacing the standard Java Properties class with Apache Commons Configuration2's Configuration interface. This allows users to specify property values like password=${env:OIE_PASSWORD} in the configuration file, which will be resolved from environment variables at runtime.
Changes:
- Replaced
Propertieswith Apache Commons Configuration2'sConfigurationfor loading CLI configuration - Added new imports for Configuration2 classes
- Modified configuration loading logic to use
FileBasedConfigurationBuilderinstead of direct file reading
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| String server = line.getOptionValue("a", config.getString("address")); | ||
| String user = line.getOptionValue("u", config.getString("user")); | ||
| String password = line.getOptionValue("p", config.getString("password")); | ||
| String script = line.getOptionValue("s", config.getString("script")); |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The config variable is declared but may be null when accessed. If the ConfigurationException is caught (line 166), config remains null, but it's then used unconditionally on lines 175-178 which will cause a NullPointerException. The config variable should be checked for null before use, or the error handling flow should exit the method after handling the exception.
| public CommandLineInterface(String[] args) { | ||
| out = new PrintWriter(System.out, true); | ||
| err = new PrintWriter(System.out, true); | ||
| run(args); | ||
| } |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent indentation detected. Lines 109-113 and 120-191 use inconsistent indentation compared to the rest of the file. The opening brace and method body should align with standard Java formatting (typically 4 spaces per indent level).
| FileBasedConfigurationBuilder<FileBasedConfiguration> builder = | ||
| new FileBasedConfigurationBuilder<FileBasedConfiguration>(PropertiesConfiguration.class) |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The FileBasedConfigurationBuilder is created with a generic type parameter that specifies FileBasedConfiguration, but then instantiated with PropertiesConfiguration.class. While this works due to inheritance, it's clearer and more type-safe to use PropertiesConfiguration as the type parameter to match the actual class being instantiated.
| FileBasedConfigurationBuilder<FileBasedConfiguration> builder = | |
| new FileBasedConfigurationBuilder<FileBasedConfiguration>(PropertiesConfiguration.class) | |
| FileBasedConfigurationBuilder<PropertiesConfiguration> builder = | |
| new FileBasedConfigurationBuilder<PropertiesConfiguration>(PropertiesConfiguration.class) |
| config = builder.getConfiguration(); | ||
| // config contains all properties read from the file |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing documentation. The new configuration loading mechanism using Apache Commons Configuration2 should be documented with comments explaining that it supports environment variable substitution (e.g., using ${env:VARIABLE_NAME} syntax). This is the key feature of this PR but there's no code comment explaining this capability to future developers.
| config = builder.getConfiguration(); | |
| // config contains all properties read from the file | |
| // Load properties using Apache Commons Configuration2. The resulting config | |
| // instance supports variable interpolation, including environment variables | |
| // using the ${env:VARIABLE_NAME} syntax (for example ${env:MC_SERVER_URL}). | |
| config = builder.getConfiguration(); |
| try | ||
| { | ||
| config = builder.getConfiguration(); | ||
| // config contains all properties read from the file | ||
| } | ||
| catch(ConfigurationException cex) | ||
| { | ||
| // loading of the configuration file failed | ||
| if (line.hasOption("c")) { | ||
| error("We could not find the file: " + line.getOptionValue("c"), null); | ||
| System.exit(2); | ||
| } | ||
| } |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The try-catch block formatting is inconsistent with Java conventions. The opening brace after 'try' should be on the same line (line 161), and 'catch' should be on the same line as the closing brace of the try block (line 166). Currently the formatting deviates from standard Java style.
| try | ||
| { | ||
| config = builder.getConfiguration(); | ||
| // config contains all properties read from the file |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant comment on line 164 states "config contains all properties read from the file" but provides no additional value beyond what's obvious from the code. Consider removing this comment or replacing it with documentation about the environment variable substitution feature that this change enables.
| // config contains all properties read from the file | |
| // Configuration values loaded here support property interpolation (including environment variable substitution). |
| error("We could not find the file: " + line.getOptionValue("c"), null); | ||
| System.exit(2); |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unclear error message. The comment on line 168 says "loading of the configuration file failed" but the actual implementation only shows this error if line.hasOption("c") is true. This means if the default config file doesn't exist and the user didn't specify -c, the error is silently ignored. This behavior should be clarified in the error handling or the error message should be updated to reflect when it actually triggers.
| error("We could not find the file: " + line.getOptionValue("c"), null); | |
| System.exit(2); | |
| // An explicit configuration file was specified with -c; fail fast with a clear error | |
| error("Failed to load the configuration file specified with -c: " + line.getOptionValue("c"), cex); | |
| System.exit(2); | |
| } else { | |
| // No explicit config was provided; fall back to an empty configuration | |
| config = new PropertiesConfiguration(); |
| } | ||
|
|
||
| private boolean checkAlertName(String name) throws ClientException { | ||
| if (name.equals("")) { |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inefficient comparison to empty string, check for zero length instead.
| if (name.equals("")) { | |
| if (name.length() == 0) { |
| String path = arguments[1].getText(); | ||
| File file = new File(path); | ||
|
|
||
| if (file != null && file.exists()) { |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check is useless. file cannot be null at this check, since new File(...) always is non-null.
| if (file != null && file.exists()) { | |
| if (file.exists()) { |
| if (file != null) { | ||
| try { | ||
| PropertiesConfiguration properties = PropertiesConfigurationUtil.create(file); | ||
| properties.clear(); | ||
| PropertiesConfigurationLayout layout = properties.getLayout(); | ||
|
|
||
| Map<String, ConfigurationProperty> configurationMap = client.getConfigurationMap(); | ||
| Map<String, ConfigurationProperty> sortedMap = new TreeMap<String, ConfigurationProperty>(String.CASE_INSENSITIVE_ORDER); | ||
| sortedMap.putAll(configurationMap); | ||
|
|
||
| for (Entry<String, ConfigurationProperty> entry : sortedMap.entrySet()) { | ||
| String key = entry.getKey(); | ||
| String value = entry.getValue().getValue(); | ||
| String comment = entry.getValue().getComment(); | ||
|
|
||
| if (StringUtils.isNotBlank(key)) { | ||
| properties.setProperty(key, value); | ||
| layout.setComment(key, StringUtils.isBlank(comment) ? null : comment); | ||
| } | ||
| } | ||
|
|
||
| PropertiesConfigurationUtil.saveTo(properties, file); | ||
|
|
||
| out.println("Configuration map export complete."); | ||
| } catch (ConfigurationException | IOException e) { | ||
| error("Unable to export configuration map.", e); | ||
| } |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check is useless. file cannot be null at this check, since new File(...) always is non-null.
| if (file != null) { | |
| try { | |
| PropertiesConfiguration properties = PropertiesConfigurationUtil.create(file); | |
| properties.clear(); | |
| PropertiesConfigurationLayout layout = properties.getLayout(); | |
| Map<String, ConfigurationProperty> configurationMap = client.getConfigurationMap(); | |
| Map<String, ConfigurationProperty> sortedMap = new TreeMap<String, ConfigurationProperty>(String.CASE_INSENSITIVE_ORDER); | |
| sortedMap.putAll(configurationMap); | |
| for (Entry<String, ConfigurationProperty> entry : sortedMap.entrySet()) { | |
| String key = entry.getKey(); | |
| String value = entry.getValue().getValue(); | |
| String comment = entry.getValue().getComment(); | |
| if (StringUtils.isNotBlank(key)) { | |
| properties.setProperty(key, value); | |
| layout.setComment(key, StringUtils.isBlank(comment) ? null : comment); | |
| } | |
| } | |
| PropertiesConfigurationUtil.saveTo(properties, file); | |
| out.println("Configuration map export complete."); | |
| } catch (ConfigurationException | IOException e) { | |
| error("Unable to export configuration map.", e); | |
| } | |
| try { | |
| PropertiesConfiguration properties = PropertiesConfigurationUtil.create(file); | |
| properties.clear(); | |
| PropertiesConfigurationLayout layout = properties.getLayout(); | |
| Map<String, ConfigurationProperty> configurationMap = client.getConfigurationMap(); | |
| Map<String, ConfigurationProperty> sortedMap = new TreeMap<String, ConfigurationProperty>(String.CASE_INSENSITIVE_ORDER); | |
| sortedMap.putAll(configurationMap); | |
| for (Entry<String, ConfigurationProperty> entry : sortedMap.entrySet()) { | |
| String key = entry.getKey(); | |
| String value = entry.getValue().getValue(); | |
| String comment = entry.getValue().getComment(); | |
| if (StringUtils.isNotBlank(key)) { | |
| properties.setProperty(key, value); | |
| layout.setComment(key, StringUtils.isBlank(comment) ? null : comment); | |
| } | |
| } | |
| PropertiesConfigurationUtil.saveTo(properties, file); | |
| out.println("Configuration map export complete."); | |
| } catch (ConfigurationException | IOException e) { | |
| error("Unable to export configuration map.", e); |
This is draft pull request. For some reason MirthServletTest fails when I build this. But also when I build the main branch the same test fails, so something might be wrong with my computer. The functionality still works.
Testing the functionality on Linux:
password=${env:OIE_PASSWORD}export OIE_PASSWORD=<password>~/engine/server/setup$ java -jar mirth-cli-launcher.jar -c ../../command/conf/mirth-cli-config.properties