Add Yaml configuration tags list to map upgrader
Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>pull/4718/head
parent
2515b05ee3
commit
3e0d14fed4
|
@ -17,6 +17,10 @@
|
||||||
<name>openHAB Core :: Tools :: Upgrade tool</name>
|
<name>openHAB Core :: Tools :: Upgrade tool</name>
|
||||||
<description>A tool for upgrading openHAB</description>
|
<description>A tool for upgrading openHAB</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<jackson.version>2.18.2</jackson.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openhab.core.bundles</groupId>
|
<groupId>org.openhab.core.bundles</groupId>
|
||||||
|
@ -73,6 +77,18 @@
|
||||||
<artifactId>org.eclipse.jdt.annotation</artifactId>
|
<artifactId>org.eclipse.jdt.annotation</artifactId>
|
||||||
<version>2.2.600</version>
|
<version>2.2.600</version>
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -50,7 +50,8 @@ public class UpgradeTool {
|
||||||
private static final List<Upgrader> UPGRADERS = List.of( //
|
private static final List<Upgrader> UPGRADERS = List.of( //
|
||||||
new ItemUnitToMetadataUpgrader(), //
|
new ItemUnitToMetadataUpgrader(), //
|
||||||
new JSProfileUpgrader(), //
|
new JSProfileUpgrader(), //
|
||||||
new ScriptProfileUpgrader() //
|
new ScriptProfileUpgrader(), //
|
||||||
|
new YamlConfigurationV1TagsUpgrader() // Added in 5.0
|
||||||
);
|
);
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(UpgradeTool.class);
|
private static final Logger logger = LoggerFactory.getLogger(UpgradeTool.class);
|
||||||
|
|
|
@ -0,0 +1,195 @@
|
||||||
|
/*
|
||||||
|
* 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.internal;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.FileVisitResult;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.SimpleFileVisitor;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.tools.Upgrader;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||||
|
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||||
|
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
|
||||||
|
import com.fasterxml.jackson.dataformat.yaml.YAMLParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link YamlConfigurationV1TagsUpgrader} upgrades YAML Tags Configuration from List to Map.
|
||||||
|
*
|
||||||
|
* Convert list to map format for tags in V1 configuration files.
|
||||||
|
*
|
||||||
|
* Input file criteria:
|
||||||
|
* - Search only in CONF/tags/, or in the given directory, and its subdirectories
|
||||||
|
* - Contains a version key with value 1
|
||||||
|
* - it must contain a tags key that is a list
|
||||||
|
* - The tags list must contain a uid key
|
||||||
|
* - If the above criteria are not met, the file will not be modified
|
||||||
|
*
|
||||||
|
* Output file will
|
||||||
|
* - Retain `version: 1`
|
||||||
|
* - convert tags list to a map with uid as key and the rest as map
|
||||||
|
* - Preserve the order of the tags
|
||||||
|
* - other keys will be unchanged
|
||||||
|
* - A backup of the original file will be created with the extension `.yaml.org`
|
||||||
|
* - If an .org file already exists, append a number to the end, e.g. `.org.1`
|
||||||
|
*
|
||||||
|
* @since 5.0.0
|
||||||
|
*
|
||||||
|
* @author Jimmy Tanagra - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class YamlConfigurationV1TagsUpgrader implements Upgrader {
|
||||||
|
private static final String VERSION = "version";
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(YamlConfigurationV1TagsUpgrader.class);
|
||||||
|
|
||||||
|
private final YAMLFactory yamlFactory;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
public YamlConfigurationV1TagsUpgrader() {
|
||||||
|
// match the options used in {@link YamlModelRepositoryImpl}
|
||||||
|
yamlFactory = YAMLFactory.builder() //
|
||||||
|
.disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER) // omit "---" at file start
|
||||||
|
.disable(YAMLGenerator.Feature.SPLIT_LINES) // do not split long lines
|
||||||
|
.enable(YAMLGenerator.Feature.INDENT_ARRAYS_WITH_INDICATOR) // indent arrays
|
||||||
|
.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES) // use quotes only where necessary
|
||||||
|
.enable(YAMLParser.Feature.PARSE_BOOLEAN_LIKE_WORDS_AS_STRINGS).build(); // do not parse ON/OFF/... as
|
||||||
|
// booleans
|
||||||
|
objectMapper = new ObjectMapper(yamlFactory);
|
||||||
|
objectMapper.findAndRegisterModules();
|
||||||
|
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
|
||||||
|
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
|
||||||
|
objectMapper.setSerializationInclusion(Include.NON_NULL);
|
||||||
|
objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "yamlTagsListToMap";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Upgrade YAML 'tags' list to map format on V1 configuration files";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean execute(String userdataDir, String confDir) {
|
||||||
|
String confEnv = System.getenv("OPENHAB_CONF");
|
||||||
|
// If confDir is set to OPENHAB_CONF, look inside /tags/ subdirectory
|
||||||
|
// otherwise use the given confDir as is
|
||||||
|
if (confEnv != null && !confEnv.isBlank() && confEnv.equals(confDir)) {
|
||||||
|
confDir = Path.of(confEnv, "tags").toString();
|
||||||
|
}
|
||||||
|
Path configPath = Path.of(confDir).toAbsolutePath();
|
||||||
|
logger.info("Upgrading YAML tags configurations in '{}'", configPath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Files.walkFileTree(configPath, new SimpleFileVisitor<>() {
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFile(@NonNullByDefault({}) Path file,
|
||||||
|
@NonNullByDefault({}) BasicFileAttributes attrs) throws IOException {
|
||||||
|
if (attrs.isRegularFile()) {
|
||||||
|
Path relativePath = configPath.relativize(file);
|
||||||
|
String modelName = relativePath.toString();
|
||||||
|
if (!relativePath.startsWith("automation") && modelName.endsWith(".yaml")) {
|
||||||
|
logger.info("Checking {}", file);
|
||||||
|
convertTagsListToMap(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFileFailed(@NonNullByDefault({}) Path file,
|
||||||
|
@NonNullByDefault({}) IOException exc) throws IOException {
|
||||||
|
logger.warn("Failed to process {}: {}", file.toAbsolutePath(), exc.getClass().getSimpleName());
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Failed to walk through the directory {}: {}", configPath, e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void convertTagsListToMap(Path filePath) {
|
||||||
|
try {
|
||||||
|
JsonNode fileContent = objectMapper.readTree(filePath.toFile());
|
||||||
|
|
||||||
|
JsonNode versionNode = fileContent.get(VERSION);
|
||||||
|
if (versionNode == null || !versionNode.canConvertToInt() || versionNode.asInt() != 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonNode tagsNode = fileContent.get("tags");
|
||||||
|
if (tagsNode == null || !tagsNode.isArray()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Found v1 yaml file with tags list {}", filePath);
|
||||||
|
fileContent.properties().forEach(entry -> {
|
||||||
|
String key = entry.getKey();
|
||||||
|
JsonNode node = entry.getValue();
|
||||||
|
if (key.equals("tags")) {
|
||||||
|
ObjectNode tagsMap = objectMapper.createObjectNode();
|
||||||
|
for (JsonNode tag : node) {
|
||||||
|
if (tag.hasNonNull("uid")) {
|
||||||
|
String uid = tag.get("uid").asText();
|
||||||
|
((ObjectNode) tag).remove("uid");
|
||||||
|
tagsMap.set(uid, tag);
|
||||||
|
} else {
|
||||||
|
logger.warn("Tag {} does not have a uid, skipping", tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
((ObjectNode) fileContent).set(key, tagsMap);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
String output = objectMapper.writeValueAsString(fileContent);
|
||||||
|
saveFile(filePath, output);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Failed to read YAML file {}: {}", filePath, e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveFile(Path filePath, String content) {
|
||||||
|
Path backupPath = filePath.resolveSibling(filePath.getFileName() + ".org");
|
||||||
|
int i = 1;
|
||||||
|
while (Files.exists(backupPath)) {
|
||||||
|
backupPath = filePath.resolveSibling(filePath.getFileName() + ".org." + i);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Files.move(filePath, backupPath);
|
||||||
|
Files.writeString(filePath, content);
|
||||||
|
logger.info("Converted {} to map format, and the original file saved as {}", filePath, backupPath);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Failed to save YAML file {}: {}", filePath, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue