-
-
Notifications
You must be signed in to change notification settings - Fork 441
UpgradeTool: Add Yaml configuration upgrader to convert tags list to map #4762
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
Merged
Merged
Changes from 2 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
fbf683a
Upgrade Tool: Split upgrade commands into individual classes
jimtng 60844f8
Add Yaml configuration tags list to map upgrader
jimtng d8b10b2
Log the exception message instead of class name
jimtng dd0fefb
allow unspecified userdata dir when --command is given
jimtng de9a253
print the userdata and conf directories
jimtng 4577733
inform about quoting paths, fix missing userdata dir
jimtng d05d5ef
refactor directory handling
jimtng File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -12,8 +12,10 @@ | |||||
*/ | ||||||
package org.openhab.core.tools; | ||||||
|
||||||
import static org.openhab.core.tools.internal.Upgrader.*; | ||||||
|
||||||
import java.io.PrintStream; | ||||||
import java.nio.file.Path; | ||||||
import java.time.ZonedDateTime; | ||||||
import java.util.List; | ||||||
import java.util.Set; | ||||||
|
||||||
import org.apache.commons.cli.CommandLine; | ||||||
|
@@ -23,27 +25,48 @@ | |||||
import org.apache.commons.cli.Options; | ||||||
import org.apache.commons.cli.ParseException; | ||||||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||||||
import org.openhab.core.tools.internal.Upgrader; | ||||||
import org.eclipse.jdt.annotation.Nullable; | ||||||
import org.openhab.core.storage.json.internal.JsonStorage; | ||||||
import org.openhab.core.tools.internal.*; | ||||||
import org.slf4j.Logger; | ||||||
import org.slf4j.LoggerFactory; | ||||||
|
||||||
/** | ||||||
* The {@link UpgradeTool} is a tool for upgrading openHAB to mitigate breaking changes | ||||||
* | ||||||
* @author Jan N. Klug - Initial contribution | ||||||
* @author Jimmy Tanagra - Refactor upgraders into individual classes | ||||||
*/ | ||||||
@NonNullByDefault | ||||||
public class UpgradeTool { | ||||||
private static final Set<String> LOG_LEVELS = Set.of("TRACE", "DEBUG", "INFO", "WARN", "ERROR"); | ||||||
private static final String OPT_COMMAND = "command"; | ||||||
private static final String OPT_DIR = "dir"; | ||||||
private static final String OPT_LIST_COMMANDS = "list-commands"; | ||||||
private static final String OPT_USERDATA_DIR = "userdata"; | ||||||
private static final String OPT_CONF_DIR = "conf"; | ||||||
private static final String OPT_LOG = "log"; | ||||||
private static final String OPT_FORCE = "force"; | ||||||
|
||||||
private static final List<Upgrader> UPGRADERS = List.of( // | ||||||
new ItemUnitToMetadataUpgrader(), // | ||||||
new JSProfileUpgrader(), // | ||||||
new ScriptProfileUpgrader(), // | ||||||
new YamlConfigurationV1TagsUpgrader() // Added in 5.0 | ||||||
); | ||||||
|
||||||
private static final Logger logger = LoggerFactory.getLogger(UpgradeTool.class); | ||||||
private static @Nullable JsonStorage<UpgradeRecord> upgradeRecords = null; | ||||||
|
||||||
private static Options getOptions() { | ||||||
Options options = new Options(); | ||||||
|
||||||
options.addOption(Option.builder().longOpt(OPT_DIR).desc("directory to process").numberOfArgs(1).build()); | ||||||
options.addOption(Option.builder().longOpt(OPT_USERDATA_DIR).desc("USERDATA directory to process") | ||||||
.numberOfArgs(1).build()); | ||||||
options.addOption( | ||||||
Option.builder().longOpt(OPT_CONF_DIR).desc("CONF directory to process").numberOfArgs(1).build()); | ||||||
options.addOption(Option.builder().longOpt(OPT_COMMAND).numberOfArgs(1) | ||||||
.desc("command to execute (executes all if omitted)").build()); | ||||||
options.addOption(Option.builder().longOpt(OPT_LIST_COMMANDS).desc("list available commands").build()); | ||||||
options.addOption(Option.builder().longOpt(OPT_LOG).numberOfArgs(1).desc("log verbosity").build()); | ||||||
options.addOption(Option.builder().longOpt(OPT_FORCE).desc("force execution (even if already done)").build()); | ||||||
|
||||||
|
@@ -58,42 +81,110 @@ public static void main(String[] args) { | |||||
String loglevel = commandLine.hasOption(OPT_LOG) ? commandLine.getOptionValue(OPT_LOG).toUpperCase() | ||||||
: "INFO"; | ||||||
if (!LOG_LEVELS.contains(loglevel)) { | ||||||
System.out.println("Allowed log-levels are " + LOG_LEVELS); | ||||||
println("Allowed log-levels are " + LOG_LEVELS); | ||||||
System.exit(0); | ||||||
} | ||||||
|
||||||
if (commandLine.hasOption(OPT_LIST_COMMANDS)) { | ||||||
println("Available commands:"); | ||||||
UPGRADERS.stream().forEach(upgrader -> { | ||||||
println(" - " + upgrader.getName() + ": " + upgrader.getDescription()); | ||||||
}); | ||||||
System.exit(0); | ||||||
} | ||||||
|
||||||
System.setProperty(org.slf4j.simple.SimpleLogger.DEFAULT_LOG_LEVEL_KEY, loglevel); | ||||||
|
||||||
String baseDir = commandLine.hasOption(OPT_DIR) ? commandLine.getOptionValue(OPT_DIR) | ||||||
String userdataDir = commandLine.hasOption(OPT_USERDATA_DIR) ? commandLine.getOptionValue(OPT_USERDATA_DIR) | ||||||
: System.getenv("OPENHAB_USERDATA"); | ||||||
if (baseDir == null || baseDir.isBlank()) { | ||||||
System.out.println( | ||||||
"Please either set the environment variable ${OPENHAB_USERDATA} or provide a directory through the --dir option."); | ||||||
if (userdataDir == null || userdataDir.isBlank()) { | ||||||
println("Please either set the environment variable ${OPENHAB_USERDATA} or provide a directory through the --userdata option."); | ||||||
System.exit(0); | ||||||
} else { | ||||||
boolean force = commandLine.hasOption(OPT_FORCE); | ||||||
return; | ||||||
} | ||||||
|
||||||
String confDir = commandLine.hasOption(OPT_CONF_DIR) ? commandLine.getOptionValue(OPT_CONF_DIR) | ||||||
: System.getenv("OPENHAB_CONF"); | ||||||
if (confDir == null || confDir.isBlank()) { | ||||||
println("Please either set the environment variable ${OPENHAB_CONF} or provide a directory through the --conf option."); | ||||||
System.exit(0); | ||||||
return; | ||||||
} | ||||||
|
||||||
boolean force = commandLine.hasOption(OPT_FORCE); | ||||||
String command = commandLine.hasOption(OPT_COMMAND) ? commandLine.getOptionValue(OPT_COMMAND) : null; | ||||||
|
||||||
if (command != null && UPGRADERS.stream().filter(u -> u.getName().equals(command)).findAny().isEmpty()) { | ||||||
println("Unknown command: " + command); | ||||||
System.exit(0); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When an unknown command is encountered, it’s better to exit with a non-zero status (
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||
} | ||||||
|
||||||
Path upgradeJsonDatabasePath = Path.of(userdataDir, "jsondb", "org.openhab.core.tools.UpgradeTool"); | ||||||
upgradeRecords = new JsonStorage<>(upgradeJsonDatabasePath.toFile(), null, 5, 0, 0, List.of()); | ||||||
|
||||||
Upgrader upgrader = new Upgrader(baseDir, force); | ||||||
if (!commandLine.hasOption(OPT_COMMAND) | ||||||
|| ITEM_COPY_UNIT_TO_METADATA.equals(commandLine.getOptionValue(OPT_COMMAND))) { | ||||||
upgrader.itemCopyUnitToMetadata(); | ||||||
UPGRADERS.forEach(upgrader -> { | ||||||
String upgraderName = upgrader.getName(); | ||||||
if (command != null && !upgraderName.equals(command)) { | ||||||
return; | ||||||
} | ||||||
if (!commandLine.hasOption(OPT_COMMAND) | ||||||
|| LINK_UPGRADE_JS_PROFILE.equals(commandLine.getOptionValue(OPT_COMMAND))) { | ||||||
upgrader.linkUpgradeJsProfile(); | ||||||
if (!force && lastExecuted(upgraderName) instanceof String executionDate) { | ||||||
logger.info("Already executed '{}' on {}. Use '--force' to execute it again.", upgraderName, | ||||||
executionDate); | ||||||
return; | ||||||
} | ||||||
if (!commandLine.hasOption(OPT_COMMAND) | ||||||
|| LINK_UPGRADE_SCRIPT_PROFILE.equals(commandLine.getOptionValue(OPT_COMMAND))) { | ||||||
upgrader.linkUpgradeScriptProfile(); | ||||||
try { | ||||||
logger.info("Executing {}: {}", upgraderName, upgrader.getDescription()); | ||||||
if (upgrader.execute(userdataDir, confDir)) { | ||||||
updateUpgradeRecord(upgraderName); | ||||||
} | ||||||
} catch (Exception e) { | ||||||
logger.error("Error executing upgrader {}: {}", upgraderName, e.getMessage()); | ||||||
} | ||||||
} | ||||||
}); | ||||||
} catch (ParseException e) { | ||||||
HelpFormatter formatter = new HelpFormatter(); | ||||||
String commands = Set.of(ITEM_COPY_UNIT_TO_METADATA, LINK_UPGRADE_JS_PROFILE, LINK_UPGRADE_SCRIPT_PROFILE) | ||||||
.toString(); | ||||||
formatter.printHelp("upgradetool", "", options, "Available commands: " + commands, true); | ||||||
formatter.printHelp("upgradetool", "", options, "", true); | ||||||
} | ||||||
|
||||||
System.exit(0); | ||||||
} | ||||||
|
||||||
private static @Nullable String lastExecuted(String upgrader) { | ||||||
JsonStorage<UpgradeRecord> records = upgradeRecords; | ||||||
if (records != null) { | ||||||
UpgradeRecord upgradeRecord = records.get(upgrader); | ||||||
if (upgradeRecord != null) { | ||||||
return upgradeRecord.executionDate; | ||||||
} | ||||||
} else { | ||||||
logger.error("Upgrade records storage is not initialized."); | ||||||
} | ||||||
return null; | ||||||
} | ||||||
|
||||||
private static void updateUpgradeRecord(String upgrader) { | ||||||
JsonStorage<UpgradeRecord> records = upgradeRecords; | ||||||
if (records != null) { | ||||||
records.put(upgrader, new UpgradeRecord(ZonedDateTime.now())); | ||||||
records.flush(); | ||||||
} else { | ||||||
logger.error("Upgrade records storage is not initialized."); | ||||||
} | ||||||
} | ||||||
|
||||||
// to avoid compiler's null pointer warnings | ||||||
private static void println(String message) { | ||||||
PrintStream out = System.out; | ||||||
if (out != null) { | ||||||
out.println(message); | ||||||
} | ||||||
} | ||||||
|
||||||
private static class UpgradeRecord { | ||||||
public final String executionDate; | ||||||
|
||||||
public UpgradeRecord(ZonedDateTime executionDate) { | ||||||
this.executionDate = executionDate.toString(); | ||||||
} | ||||||
} | ||||||
} |
40 changes: 40 additions & 0 deletions
40
tools/upgradetool/src/main/java/org/openhab/core/tools/Upgrader.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/* | ||
* Copyright (c) 2010-2025 Contributors to the openHAB project | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.openhab.core.tools; | ||
|
||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||
|
||
/** | ||
* The {@link Upgrader} provides an interface for upgrading openHAB configuration files. | ||
* | ||
* Implementing class MUST provide a no-argument constructor. | ||
* | ||
* @author Jimmy Tanagra - Initial contribution | ||
*/ | ||
@NonNullByDefault | ||
public interface Upgrader { | ||
String getName(); | ||
|
||
String getDescription(); | ||
|
||
/** | ||
* Executes the upgrade process. | ||
* | ||
* @param userdataDir the OPENHAB_USERDATA directory for the upgrade, | ||
* or a custom path given by the user as --userdata argument | ||
* @param confDir the OPENHAB_CONF directory for the upgrade, | ||
* or a custom path given by the user as --conf argument | ||
* @return true if the upgrade was successful, false otherwise | ||
*/ | ||
boolean execute(String userdataDir, String confDir); | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.