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>
|
||||
<description>A tool for upgrading openHAB</description>
|
||||
|
||||
<properties>
|
||||
<jackson.version>2.18.2</jackson.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
|
@ -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>
|
||||
|
|
|
@ -50,7 +50,8 @@ public class UpgradeTool {
|
|||
private static final List<Upgrader> UPGRADERS = List.of( //
|
||||
new ItemUnitToMetadataUpgrader(), //
|
||||
new JSProfileUpgrader(), //
|
||||
new ScriptProfileUpgrader() //
|
||||
new ScriptProfileUpgrader(), //
|
||||
new YamlConfigurationV1TagsUpgrader() // Added in 5.0
|
||||
);
|
||||
|
||||
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