mirror of https://github.com/hyperledger/besu
Enhanced control over plugins registration (#6700)
Signed-off-by: Gabriel-Trintinalia <gabriel.trintinalia@consensys.net>pull/6973/head
parent
61432831d5
commit
a1f73d925e
@ -0,0 +1,53 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.cli.converter; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.plugins.PluginInfo; |
||||
|
||||
import java.util.List; |
||||
import java.util.stream.Stream; |
||||
|
||||
import picocli.CommandLine; |
||||
|
||||
/** |
||||
* Converts a comma-separated string into a list of {@link PluginInfo} objects. This converter is |
||||
* intended for use with PicoCLI to process command line arguments that specify plugin information. |
||||
*/ |
||||
public class PluginInfoConverter implements CommandLine.ITypeConverter<List<PluginInfo>> { |
||||
|
||||
/** |
||||
* Converts a comma-separated string into a list of {@link PluginInfo}. |
||||
* |
||||
* @param value The comma-separated string representing plugin names. |
||||
* @return A list of {@link PluginInfo} objects created from the provided string. |
||||
*/ |
||||
@Override |
||||
public List<PluginInfo> convert(final String value) { |
||||
if (value == null || value.isBlank()) { |
||||
return List.of(); |
||||
} |
||||
return Stream.of(value.split(",")).map(String::trim).map(this::toPluginInfo).toList(); |
||||
} |
||||
|
||||
/** |
||||
* Creates a {@link PluginInfo} object from a plugin name. |
||||
* |
||||
* @param pluginName The name of the plugin. |
||||
* @return A {@link PluginInfo} object representing the plugin. |
||||
*/ |
||||
private PluginInfo toPluginInfo(final String pluginName) { |
||||
return new PluginInfo(pluginName); |
||||
} |
||||
} |
@ -0,0 +1,63 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.cli.options.stable; |
||||
|
||||
import static org.hyperledger.besu.cli.DefaultCommandValues.DEFAULT_PLUGINS_OPTION_NAME; |
||||
|
||||
import org.hyperledger.besu.cli.converter.PluginInfoConverter; |
||||
import org.hyperledger.besu.cli.options.CLIOptions; |
||||
import org.hyperledger.besu.cli.util.CommandLineUtils; |
||||
import org.hyperledger.besu.ethereum.core.plugins.PluginConfiguration; |
||||
import org.hyperledger.besu.ethereum.core.plugins.PluginInfo; |
||||
|
||||
import java.util.List; |
||||
|
||||
import picocli.CommandLine; |
||||
|
||||
/** The Plugins Options options. */ |
||||
public class PluginsConfigurationOptions implements CLIOptions<PluginConfiguration> { |
||||
@CommandLine.Option( |
||||
names = {DEFAULT_PLUGINS_OPTION_NAME}, |
||||
description = "Comma-separated list of plugin names", |
||||
split = ",", |
||||
hidden = true, |
||||
converter = PluginInfoConverter.class, |
||||
arity = "1..*") |
||||
private List<PluginInfo> plugins; |
||||
|
||||
@Override |
||||
public PluginConfiguration toDomainObject() { |
||||
return new PluginConfiguration(plugins); |
||||
} |
||||
|
||||
@Override |
||||
public List<String> getCLIOptions() { |
||||
return CommandLineUtils.getCLIOptions(this, new PluginsConfigurationOptions()); |
||||
} |
||||
|
||||
/** |
||||
* Constructs a {@link PluginConfiguration} instance based on the command line options. |
||||
* |
||||
* @param commandLine The command line instance containing parsed options. |
||||
* @return A new {@link PluginConfiguration} instance. |
||||
*/ |
||||
public static PluginConfiguration fromCommandLine(final CommandLine commandLine) { |
||||
List<PluginInfo> plugins = |
||||
CommandLineUtils.getOptionValueOrDefault( |
||||
commandLine, DEFAULT_PLUGINS_OPTION_NAME, new PluginInfoConverter()); |
||||
|
||||
return new PluginConfiguration(plugins); |
||||
} |
||||
} |
@ -0,0 +1,110 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.cli; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.hyperledger.besu.cli.util.CommandLineUtils.getOptionValueOrDefault; |
||||
import static org.junit.jupiter.api.Assertions.assertThrows; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.ArgumentMatchers.anyString; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import org.hyperledger.besu.cli.util.CommandLineUtils; |
||||
|
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
import picocli.CommandLine; |
||||
|
||||
/** |
||||
* Unit tests for {@link CommandLineUtils} focusing on the retrieval of option values |
||||
* (getOptionValueOrDefault). |
||||
*/ |
||||
public class CommandLineUtilsDefaultsTest { |
||||
private static final String OPTION_NAME = "option"; |
||||
private static final String OPTION_VALUE = "optionValue"; |
||||
private static final String DEFAULT_VALUE = "defaultValue"; |
||||
public final CommandLine.ITypeConverter<String> converter = String::valueOf; |
||||
private CommandLine commandLine; |
||||
private CommandLine.Model.OptionSpec optionSpec; |
||||
private CommandLine.IDefaultValueProvider defaultValueProvider; |
||||
private CommandLine.ParseResult parseResult; |
||||
|
||||
@BeforeEach |
||||
public void setUp() { |
||||
commandLine = mock(CommandLine.class); |
||||
parseResult = mock(CommandLine.ParseResult.class); |
||||
CommandLine.Model.CommandSpec commandSpec = mock(CommandLine.Model.CommandSpec.class); |
||||
optionSpec = mock(CommandLine.Model.OptionSpec.class); |
||||
defaultValueProvider = mock(CommandLine.IDefaultValueProvider.class); |
||||
when(commandLine.getParseResult()).thenReturn(parseResult); |
||||
when(commandLine.getCommandSpec()).thenReturn(commandSpec); |
||||
when(commandLine.getDefaultValueProvider()).thenReturn(defaultValueProvider); |
||||
when(parseResult.matchedOptionValue(anyString(), any())).thenCallRealMethod(); |
||||
when(commandSpec.findOption(OPTION_NAME)).thenReturn(optionSpec); |
||||
} |
||||
|
||||
@Test |
||||
public void testGetOptionValueOrDefault_UserProvidedValue() { |
||||
when(parseResult.matchedOption(OPTION_NAME)).thenReturn(optionSpec); |
||||
when(optionSpec.getValue()).thenReturn(OPTION_VALUE); |
||||
|
||||
String result = getOptionValueOrDefault(commandLine, OPTION_NAME, converter); |
||||
assertThat(result).isEqualTo(OPTION_VALUE); |
||||
} |
||||
|
||||
@Test |
||||
public void testGetOptionValueOrDefault_DefaultValue() throws Exception { |
||||
when(defaultValueProvider.defaultValue(optionSpec)).thenReturn(DEFAULT_VALUE); |
||||
String result = getOptionValueOrDefault(commandLine, OPTION_NAME, converter); |
||||
assertThat(result).isEqualTo(DEFAULT_VALUE); |
||||
} |
||||
|
||||
@Test |
||||
public void userOptionOverridesDefaultValue() throws Exception { |
||||
when(parseResult.matchedOption(OPTION_NAME)).thenReturn(optionSpec); |
||||
when(optionSpec.getValue()).thenReturn(OPTION_VALUE); |
||||
|
||||
when(defaultValueProvider.defaultValue(optionSpec)).thenReturn(DEFAULT_VALUE); |
||||
String result = getOptionValueOrDefault(commandLine, OPTION_NAME, converter); |
||||
assertThat(result).isEqualTo(OPTION_VALUE); |
||||
} |
||||
|
||||
@Test |
||||
public void testGetOptionValueOrDefault_NoValueOrDefault() { |
||||
String result = getOptionValueOrDefault(commandLine, OPTION_NAME, converter); |
||||
assertThat(result).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
public void testGetOptionValueOrDefault_ConversionFailure() throws Exception { |
||||
when(defaultValueProvider.defaultValue(optionSpec)).thenReturn(DEFAULT_VALUE); |
||||
|
||||
CommandLine.ITypeConverter<Integer> failingConverter = |
||||
value -> { |
||||
throw new Exception("Conversion failed"); |
||||
}; |
||||
|
||||
String actualMessage = |
||||
assertThrows( |
||||
RuntimeException.class, |
||||
() -> getOptionValueOrDefault(commandLine, OPTION_NAME, failingConverter)) |
||||
.getMessage(); |
||||
final String expectedMessage = |
||||
"Failed to convert default value for option option: Conversion failed"; |
||||
assertThat(actualMessage).isEqualTo(expectedMessage); |
||||
} |
||||
} |
@ -0,0 +1,92 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.core.plugins; |
||||
|
||||
import static java.util.Objects.requireNonNull; |
||||
|
||||
import java.nio.file.Path; |
||||
import java.nio.file.Paths; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* Configuration for managing plugins, including their information, detection type, and directory. |
||||
*/ |
||||
public class PluginConfiguration { |
||||
private final List<PluginInfo> requestedPlugins; |
||||
private final Path pluginsDir; |
||||
|
||||
/** |
||||
* Constructs a new PluginConfiguration with the specified plugin information and requestedPlugins |
||||
* directory. |
||||
* |
||||
* @param requestedPlugins List of {@link PluginInfo} objects representing the requestedPlugins. |
||||
* @param pluginsDir The directory where requestedPlugins are located. |
||||
*/ |
||||
public PluginConfiguration(final List<PluginInfo> requestedPlugins, final Path pluginsDir) { |
||||
this.requestedPlugins = requestedPlugins; |
||||
this.pluginsDir = pluginsDir; |
||||
} |
||||
|
||||
/** |
||||
* Constructs a PluginConfiguration with specified plugins using the default directory. |
||||
* |
||||
* @param requestedPlugins List of plugins for consideration or registration. discoverable plugins |
||||
* are. |
||||
*/ |
||||
public PluginConfiguration(final List<PluginInfo> requestedPlugins) { |
||||
this.requestedPlugins = requestedPlugins; |
||||
this.pluginsDir = PluginConfiguration.defaultPluginsDir(); |
||||
} |
||||
|
||||
/** |
||||
* Constructs a PluginConfiguration with the specified plugins directory |
||||
* |
||||
* @param pluginsDir The directory where plugins are located. Cannot be null. |
||||
*/ |
||||
public PluginConfiguration(final Path pluginsDir) { |
||||
this.requestedPlugins = null; |
||||
this.pluginsDir = requireNonNull(pluginsDir); |
||||
} |
||||
|
||||
/** |
||||
* Returns the names of requested plugins, or an empty list if none. |
||||
* |
||||
* @return List of requested plugin names, never {@code null}. |
||||
*/ |
||||
public List<String> getRequestedPlugins() { |
||||
return requestedPlugins == null |
||||
? Collections.emptyList() |
||||
: requestedPlugins.stream().map(PluginInfo::name).toList(); |
||||
} |
||||
|
||||
public Path getPluginsDir() { |
||||
return pluginsDir; |
||||
} |
||||
|
||||
/** |
||||
* Returns the default plugins directory based on system properties. |
||||
* |
||||
* @return The default {@link Path} to the plugin's directory. |
||||
*/ |
||||
public static Path defaultPluginsDir() { |
||||
final String pluginsDirProperty = System.getProperty("besu.plugins.dir"); |
||||
if (pluginsDirProperty == null) { |
||||
return Paths.get(System.getProperty("besu.home", "."), "plugins"); |
||||
} else { |
||||
return Paths.get(pluginsDirProperty); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,37 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.core.plugins; |
||||
|
||||
/** Represents information about a plugin, including its name. */ |
||||
public final class PluginInfo { |
||||
private final String name; |
||||
|
||||
/** |
||||
* Constructs a new PluginInfo instance with the specified name. |
||||
* |
||||
* @param name The name of the plugin. Cannot be null or empty. |
||||
* @throws IllegalArgumentException if the name is null or empty. |
||||
*/ |
||||
public PluginInfo(final String name) { |
||||
if (name == null || name.isBlank()) { |
||||
throw new IllegalArgumentException("Plugin name cannot be null or empty."); |
||||
} |
||||
this.name = name; |
||||
} |
||||
|
||||
public String name() { |
||||
return name; |
||||
} |
||||
} |
Loading…
Reference in new issue