Skip to content

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 7 commits into from
May 18, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
16 changes: 16 additions & 0 deletions tools/upgradetool/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
<name>openHAB Core :: Tools :: Upgrade tool</name>
<description>A tool for upgrading openHAB</description>

<properties>
<jackson.version>2.18.2</jackson.version>
</properties>

<dependencies>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
Expand Down Expand Up @@ -73,6 +77,18 @@
<artifactId>org.eclipse.jdt.annotation</artifactId>
<version>2.2.600</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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());

Expand All @@ -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);
Copy link
Preview

Copilot AI May 18, 2025

Choose a reason for hiding this comment

The 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 (System.exit(1)) to signal failure rather than 0.

Suggested change
System.exit(0);
System.exit(1);

Copilot uses AI. Check for mistakes.

}

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();
}
}
}
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);
}
Loading