Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>tools.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-toml</artifactId>
</dependency>

<!-- Test dependencies -->
<dependency>
Expand Down
136 changes: 136 additions & 0 deletions src/main/java/land/oras/auth/RegistriesConf.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*-
* =LICENSE=
* ORAS Java SDK
* ===
* Copyright (C) 2024 - 2025 ORAS
* ===
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =LICENSEEND=
*/

package land.oras.auth;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import land.oras.exception.OrasException;
import land.oras.utils.JsonUtils;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Handle registries.conf configuration
*/
@NullMarked
public class RegistriesConf {

private static final Logger LOG = LoggerFactory.getLogger(RegistriesConf.class);

/**
* The internal config
*/
private final Config config;

/**
* Constructor for RegistriesConf.
*
* @param config configuration instance.
*/
RegistriesConf(Config config) {
this.config = Objects.requireNonNull(config, "Config cannot be null");
}

/**
* Create a new RegistriesConf instance by loading configuration from the specified paths.
* @param configPaths The list of paths to load configuration from.
* @return A new RegistriesConf instance.
*/
public static RegistriesConf newConf(List<Path> configPaths) {
List<ConfigFile> files = new ArrayList<>();
for (Path configPath : configPaths) {
if (Files.exists(configPath)) {
ConfigFile configFile = JsonUtils.fromJson(configPath, ConfigFile.class);
LOG.debug("Loaded registries config file: {}", configPath);
files.add(configFile);
}
}
return new RegistriesConf(Config.load(files));
}

/**
* Create a new RegistriesConf instance by loading configuration from standard paths.
* @return A new RegistriesConf instance.
*/
public static RegistriesConf newConf() {
Path globalPath = Path.of("/etc/containers/registries.conf");
List<Path> paths = List.of(
globalPath,
System.getenv("HOME") != null
? Path.of(System.getenv("HOME"), ".config", "containers", "registries.conf")
: globalPath);
return newConf(paths);
}

/**
* The model of the config file
*
*/
record ConfigFile(@Nullable List<String> unqualifiedRegistries) {}

/**
* Get the list of unclassified registries.
* @return an unmodifiable list of unclassified registries.
*/
public List<String> getUnclassifiedRegistries() {
return Collections.unmodifiableList(config.unqualifiedRegistries);
}

/**
* Nested Config class for configuration management.
*/
static class Config {

/**
* Private constructor to prevent instantiation.
*/
private Config() {}

/**
* List of unqualified registries.
*/
private final List<String> unqualifiedRegistries = new LinkedList<>();

/**
* Loads the configuration from a JSON file at the specified path and populates registries configuration
*
* @param configFiles The config files
* @return A {@code Config} object populated with config
* @throws OrasException If an error occurs while reading or parsing the TOML file.
*/
public static Config load(List<ConfigFile> configFiles) throws OrasException {
Config config = new Config();
for (ConfigFile configFile : configFiles) {
if (configFile.unqualifiedRegistries != null) {
config.unqualifiedRegistries.addAll(configFile.unqualifiedRegistries);
}
}
return config;
}
}
}
101 changes: 101 additions & 0 deletions src/main/java/land/oras/utils/TomlUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*-
* =LICENSE=
* ORAS Java SDK
* ===
* Copyright (C) 2024 - 2025 ORAS
* ===
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =LICENSEEND=
*/

package land.oras.utils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import land.oras.exception.OrasException;
import org.jspecify.annotations.NullMarked;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.dataformat.toml.TomlMapper;

/**
* Utility class for TOML operations.
* Use Jackson 3 internally for TOML operations
*/
@NullMarked
public final class TomlUtils {

/**
* TOML mapper instance
*/
private static final ObjectMapper tomlMapper;

/**
* Utils class
*/
private TomlUtils() {
// Hide constructor
}

static {
tomlMapper = TomlMapper.builder().build();
}

/**
* Convert an object to a TOML string
* @param object The object to convert
* @return The TOML string
*/
public static String toToml(Object object) {
try {
return tomlMapper.writeValueAsString(object);
} catch (JacksonException e) {
throw new OrasException("Unable to convert object to TOML string", e);
}
}

/**
* Convert a TOML string to an object
* @param toml The TOML string
* @param clazz The class of the object
* @param <T> The type of the object
* @return The object
*/
public static <T> T fromToml(String toml, Class<T> clazz) {
try {
return tomlMapper.readValue(toml, clazz);
} catch (JacksonException e) {
throw new OrasException("Unable to parse TOML string", e);
}
}

/**
* Convert a TOML string to an object
* of the TOML string is not important.
* @param path The path to the TOML file
* @param clazz The class of the object
* @param <T> The type of the object
* @return The object
*/
public static <T> T fromToml(Path path, Class<T> clazz) {
try {
return tomlMapper.readValue(Files.readString(path, StandardCharsets.UTF_8), clazz);
} catch (IOException e) {
throw new OrasException("Unable to read TOML file due to IO error", e);
} catch (JacksonException e) {
throw new OrasException("Unable to parse TOML file", e);
}
}
}
63 changes: 63 additions & 0 deletions src/test/java/land/oras/utils/TomlUtilsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*-
* =LICENSE=
* ORAS Java SDK
* ===
* Copyright (C) 2024 - 2025 ORAS
* ===
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =LICENSEEND=
*/

package land.oras.utils;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.nio.file.Path;
import java.util.Map;
import land.oras.exception.OrasException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.CleanupMode;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;

@Execution(ExecutionMode.CONCURRENT)
public class TomlUtilsTest {

/**
* Temporary dir
*/
@TempDir(cleanup = CleanupMode.ON_SUCCESS)
private static Path dir;

@Test
void failToParseTomlString() {
assertThrows(OrasException.class, () -> TomlUtils.fromToml("not a toml", Object.class));
}

@Test
@SuppressWarnings("unchecked")
void shouldParseToml() {
String tomlMap = """
key1 = "value1"
key2 = "value2"
""";
Map<String, String> map = TomlUtils.fromToml(tomlMap, Map.class);
assertNotNull(map);
assertEquals(2, map.size());
assertEquals("value1", map.get("key1"));
assertEquals("value2", map.get("key2"));
}
}