Skip to content

Commit

Permalink
[PIE-2052] Besu CLI -V to print plugin versions (hyperledger#123)
Browse files Browse the repository at this point in the history
 -- Use (PicoCLI) custom factory to construct version provider which can return optional plugin versions 
 -- Use plugin's jar manifest implementation and version to build plugin version during plugin registration

Signed-off-by: Usman Saleem <[email protected]>
Signed-off-by: edwardmack <[email protected]>
  • Loading branch information
usmansaleem authored and edwardmack committed Nov 4, 2019
1 parent ada6f23 commit 7530cad
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 3 deletions.
5 changes: 4 additions & 1 deletion besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import org.hyperledger.besu.cli.subcommands.blocks.BlocksSubCommand.RlpBlockExporterFactory;
import org.hyperledger.besu.cli.subcommands.operator.OperatorSubCommand;
import org.hyperledger.besu.cli.subcommands.rlp.RLPSubCommand;
import org.hyperledger.besu.cli.util.BesuCommandCustomFactory;
import org.hyperledger.besu.cli.util.CommandLineUtils;
import org.hyperledger.besu.cli.util.ConfigOptionSearchAndRunHandler;
import org.hyperledger.besu.cli.util.VersionProvider;
Expand Down Expand Up @@ -773,7 +774,9 @@ public void parse(
final BesuExceptionHandler exceptionHandler,
final InputStream in,
final String... args) {
commandLine = new CommandLine(this).setCaseInsensitiveEnumValuesAllowed(true);
commandLine =
new CommandLine(this, new BesuCommandCustomFactory(besuPluginContext))
.setCaseInsensitiveEnumValuesAllowed(true);
handleStandaloneCommand()
.addSubCommands(resultHandler, in)
.registerConverters()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright ConsenSys AG.
*
* 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.util;

import org.hyperledger.besu.services.PluginVersionsProvider;

import java.lang.reflect.Constructor;

import picocli.CommandLine;

/**
* Custom PicoCli IFactory to handle version provider construction with plugin versions. Based on
* same logic as PicoCLI DefaultFactory.
*/
public class BesuCommandCustomFactory implements CommandLine.IFactory {
private final PluginVersionsProvider pluginVersionsProvider;

public BesuCommandCustomFactory(final PluginVersionsProvider pluginVersionsProvider) {
this.pluginVersionsProvider = pluginVersionsProvider;
}

@SuppressWarnings("unchecked")
@Override
public <T> T create(final Class<T> cls) throws Exception {
if (CommandLine.IVersionProvider.class.isAssignableFrom(cls)) {
return (T) new VersionProvider(pluginVersionsProvider);
}

final Constructor<T> constructor = cls.getDeclaredConstructor();
try {
return constructor.newInstance();
} catch (Exception e) {
constructor.setAccessible(true);
return constructor.newInstance();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,24 @@
package org.hyperledger.besu.cli.util;

import org.hyperledger.besu.BesuInfo;
import org.hyperledger.besu.services.PluginVersionsProvider;

import java.util.stream.Stream;

import picocli.CommandLine;

public class VersionProvider implements CommandLine.IVersionProvider {
private final PluginVersionsProvider pluginVersionsProvider;

public VersionProvider(final PluginVersionsProvider pluginVersionsProvider) {
this.pluginVersionsProvider = pluginVersionsProvider;
}

@Override
public String[] getVersion() {
return new String[] {BesuInfo.version()};
// the PluginVersionsProvider has registered plugins and their versions by this time.
return Stream.concat(
Stream.of(BesuInfo.version()), pluginVersionsProvider.getPluginVersions().stream())
.toArray(String[]::new);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,22 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.Predicate;
import java.util.stream.Stream;

import com.google.common.annotations.VisibleForTesting;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class BesuPluginContextImpl implements BesuContext {
public class BesuPluginContextImpl implements BesuContext, PluginVersionsProvider {

private static final Logger LOG = LogManager.getLogger();

Expand All @@ -57,6 +59,7 @@ private enum Lifecycle {
private Lifecycle state = Lifecycle.UNINITIALIZED;
private final Map<Class<?>, ? super Object> serviceRegistry = new HashMap<>();
private final List<BesuPlugin> plugins = new ArrayList<>();
private final List<String> pluginVersions = new ArrayList<>();

public <T> void addService(final Class<T> serviceType, final T service) {
checkArgument(serviceType.isInterface(), "Services must be Java interfaces.");
Expand Down Expand Up @@ -89,6 +92,7 @@ public void registerPlugins(final Path pluginsDir) {
try {
plugin.register(this);
LOG.debug("Registered plugin of type {}.", plugin.getClass().getName());
addPluginVersion(plugin);
} catch (final Exception e) {
LOG.error(
"Error registering plugin of type {}, start and stop will not be called. \n{}",
Expand All @@ -104,6 +108,20 @@ public void registerPlugins(final Path pluginsDir) {
state = Lifecycle.REGISTERED;
}

private void addPluginVersion(final BesuPlugin plugin) {
final Package pluginPackage = plugin.getClass().getPackage();
final String implTitle =
Optional.ofNullable(pluginPackage.getImplementationTitle())
.filter(Predicate.not(String::isBlank))
.orElse(plugin.getClass().getSimpleName());
final String implVersion =
Optional.ofNullable(pluginPackage.getImplementationVersion())
.filter(Predicate.not(String::isBlank))
.orElse("<Unknown Version>");
final String pluginVersion = implTitle + "/v" + implVersion;
pluginVersions.add(pluginVersion);
}

public void startPlugins() {
checkState(
state == Lifecycle.REGISTERED,
Expand Down Expand Up @@ -153,6 +171,11 @@ public void stopPlugins() {
state = Lifecycle.STOPPED;
}

@Override
public Collection<String> getPluginVersions() {
return Collections.unmodifiableList(pluginVersions);
}

private static URL pathToURIOrNull(final Path p) {
try {
return p.toUri().toURL();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright ConsenSys AG.
*
* 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.services;

import java.util.Collection;

public interface PluginVersionsProvider {
Collection<String> getPluginVersions();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright ConsenSys AG.
*
* 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.util;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

import org.hyperledger.besu.BesuInfo;
import org.hyperledger.besu.services.PluginVersionsProvider;

import java.util.Arrays;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class BesuCommandCustomFactoryTest {

@Mock private PluginVersionsProvider pluginVersionsProvider;

@Before
public void initMocks() {
when(pluginVersionsProvider.getPluginVersions()).thenReturn(Arrays.asList("v1", "v2"));
}

@Test
public void testCreateVersionProviderInstance() throws Exception {
final BesuCommandCustomFactory besuCommandCustomFactory =
new BesuCommandCustomFactory(pluginVersionsProvider);
final VersionProvider versionProvider = besuCommandCustomFactory.create(VersionProvider.class);
assertThat(versionProvider.getVersion()).containsExactly(BesuInfo.version(), "v1", "v2");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright ConsenSys AG.
*
* 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.util;

import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

import org.hyperledger.besu.BesuInfo;
import org.hyperledger.besu.services.PluginVersionsProvider;

import java.util.Collections;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class VersionProviderTest {

@Mock private PluginVersionsProvider pluginVersionsProvider;

@Test
public void validateEmptyListGenerateBesuInfoVersionOnly() {
when(pluginVersionsProvider.getPluginVersions()).thenReturn(emptyList());
final VersionProvider versionProvider = new VersionProvider(pluginVersionsProvider);
assertThat(versionProvider.getVersion()).containsOnly(BesuInfo.version());
}

@Test
public void validateVersionListGenerateValidValues() {
when(pluginVersionsProvider.getPluginVersions()).thenReturn(Collections.singletonList("test"));
final VersionProvider versionProvider = new VersionProvider(pluginVersionsProvider);
assertThat(versionProvider.getVersion()).containsExactly(BesuInfo.version(), "test");
}
}

0 comments on commit 7530cad

Please sign in to comment.