Skip to content
This repository has been archived by the owner on Sep 26, 2019. It is now read-only.

Commit

Permalink
Support specifying config options via environment variables. (#1597)
Browse files Browse the repository at this point in the history
In descending order, the priority is CLI arg, env var, config file, default value.
  • Loading branch information
ajsutton authored Jun 24, 2019
1 parent 9cecb30 commit a1922be
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@

import tech.pegasys.pantheon.util.bytes.BytesValue;

import java.util.Objects;
import java.util.Optional;

import com.google.common.base.MoreObjects;

public class MiningParameters {

private final Optional<Address> coinbase;
Expand Down Expand Up @@ -49,4 +52,34 @@ public BytesValue getExtraData() {
public Boolean isMiningEnabled() {
return enabled;
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final MiningParameters that = (MiningParameters) o;
return Objects.equals(coinbase, that.coinbase)
&& Objects.equals(minTransactionGasPrice, that.minTransactionGasPrice)
&& Objects.equals(extraData, that.extraData)
&& Objects.equals(enabled, that.enabled);
}

@Override
public int hashCode() {
return Objects.hash(coinbase, minTransactionGasPrice, extraData, enabled);
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("coinbase", coinbase)
.add("minTransactionGasPrice", minTransactionGasPrice)
.add("extraData", extraData)
.add("enabled", enabled)
.toString();
}
}
3 changes: 2 additions & 1 deletion pantheon/src/main/java/tech/pegasys/pantheon/Pantheon.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ public static void main(final String... args) {
new SynchronizerConfiguration.Builder(),
EthereumWireProtocolConfiguration.builder(),
new RocksDbConfiguration.Builder(),
new PantheonPluginContextImpl());
new PantheonPluginContextImpl(),
System.getenv());

pantheonCommand.parse(
new RunLast().andExit(SUCCESS_EXIT_CODE),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2019 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.
*/
package tech.pegasys.pantheon.cli;

import static java.util.Arrays.asList;

import java.util.List;

import picocli.CommandLine.IDefaultValueProvider;
import picocli.CommandLine.Model.ArgSpec;

public class CascadingDefaultProvider implements IDefaultValueProvider {

private final List<IDefaultValueProvider> defaultValueProviders;

public CascadingDefaultProvider(final IDefaultValueProvider... defaultValueProviders) {
this.defaultValueProviders = asList(defaultValueProviders);
}

@Override
public String defaultValue(final ArgSpec argSpec) throws Exception {
for (final IDefaultValueProvider provider : defaultValueProviders) {
final String defaultValue = provider.defaultValue(argSpec);
if (defaultValue != null) {
return defaultValue;
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import picocli.CommandLine;
import picocli.CommandLine.AbstractParseResultHandler;
import picocli.CommandLine.ExecutionException;
import picocli.CommandLine.IDefaultValueProvider;
import picocli.CommandLine.Model.OptionSpec;
import picocli.CommandLine.ParseResult;

Expand All @@ -28,16 +30,19 @@ class ConfigOptionSearchAndRunHandler extends AbstractParseResultHandler<List<Ob
private final AbstractParseResultHandler<List<Object>> resultHandler;
private final CommandLine.IExceptionHandler2<List<Object>> exceptionHandler;
private final String configFileOptionName;
private final Map<String, String> environment;
private final boolean isDocker;

ConfigOptionSearchAndRunHandler(
final AbstractParseResultHandler<List<Object>> resultHandler,
final CommandLine.IExceptionHandler2<List<Object>> exceptionHandler,
final String configFileOptionName,
final Map<String, String> environment,
final boolean isDocker) {
this.resultHandler = resultHandler;
this.exceptionHandler = exceptionHandler;
this.configFileOptionName = configFileOptionName;
this.environment = environment;
this.isDocker = isDocker;
// use the same output as the regular options handler to ensure that outputs are all going
// in the same place. No need to do this for the exception handler as we reuse it directly.
Expand All @@ -55,9 +60,11 @@ protected List<Object> handle(final ParseResult parseResult) throws ExecutionExc
} catch (final Exception e) {
throw new ExecutionException(commandLine, e.getMessage(), e);
}
final TomlConfigFileDefaultProvider tomlConfigFileDefaultProvider =
new TomlConfigFileDefaultProvider(commandLine, configFile);
commandLine.setDefaultValueProvider(tomlConfigFileDefaultProvider);
final IDefaultValueProvider defaultValueProvider =
new CascadingDefaultProvider(
new EnvironmentVariableDefaultProvider(environment),
new TomlConfigFileDefaultProvider(commandLine, configFile));
commandLine.setDefaultValueProvider(defaultValueProvider);
} else if (isDocker) {
final File configFile = new File(DOCKER_CONFIG_LOCATION);
if (configFile.exists()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2019 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.
*/
package tech.pegasys.pantheon.cli;

import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;

import picocli.CommandLine.IDefaultValueProvider;
import picocli.CommandLine.Model.ArgSpec;
import picocli.CommandLine.Model.OptionSpec;

public class EnvironmentVariableDefaultProvider implements IDefaultValueProvider {
private static final String ENV_VAR_PREFIX = "PANTHEON_";

private final Map<String, String> environment;

public EnvironmentVariableDefaultProvider(final Map<String, String> environment) {
this.environment = environment;
}

@Override
public String defaultValue(final ArgSpec argSpec) {
return envVarNames((OptionSpec) argSpec)
.map(environment::get)
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}

private Stream<String> envVarNames(final OptionSpec spec) {
return Arrays.stream(spec.names())
.filter(name -> name.startsWith("--")) // Only long options are allowed
.map(
name ->
ENV_VAR_PREFIX
+ name.substring("--".length()).replace('-', '_').toUpperCase(Locale.US));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
Expand Down Expand Up @@ -145,6 +146,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
private final RunnerBuilder runnerBuilder;
private final PantheonController.Builder controllerBuilderFactory;
private final PantheonPluginContextImpl pantheonPluginContext;
private final Map<String, String> environment;

protected KeyLoader getKeyLoader() {
return KeyPairUtil::loadKeyPair;
Expand Down Expand Up @@ -607,7 +609,8 @@ public PantheonCommand(
final SynchronizerConfiguration.Builder synchronizerConfigurationBuilder,
final EthereumWireProtocolConfiguration.Builder ethereumWireConfigurationBuilder,
final RocksDbConfiguration.Builder rocksDbConfigurationBuilder,
final PantheonPluginContextImpl pantheonPluginContext) {
final PantheonPluginContextImpl pantheonPluginContext,
final Map<String, String> environment) {
this.logger = logger;
this.blockImporter = blockImporter;
this.runnerBuilder = runnerBuilder;
Expand All @@ -616,6 +619,7 @@ public PantheonCommand(
this.ethereumWireConfigurationBuilder = ethereumWireConfigurationBuilder;
this.rocksDbConfigurationBuilder = rocksDbConfigurationBuilder;
this.pantheonPluginContext = pantheonPluginContext;
this.environment = environment;
}

private StandaloneCommand standaloneCommands;
Expand Down Expand Up @@ -680,7 +684,7 @@ public void parse(
// and eventually it will run regular parsing of the remaining options.
final ConfigOptionSearchAndRunHandler configParsingHandler =
new ConfigOptionSearchAndRunHandler(
resultHandler, exceptionHandler, CONFIG_FILE_OPTION_NAME, isDocker);
resultHandler, exceptionHandler, CONFIG_FILE_OPTION_NAME, environment, isDocker);
commandLine.parseWithHandlers(configParsingHandler, exceptionHandler, args);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
import java.io.PrintStream;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand All @@ -75,6 +77,7 @@ public abstract class CommandTestAbstract {

protected final ByteArrayOutputStream commandErrorOutput = new ByteArrayOutputStream();
private final PrintStream errPrintStream = new PrintStream(commandErrorOutput);
private final HashMap<String, String> environment = new HashMap<>();

@Mock RunnerBuilder mockRunnerBuilder;
@Mock Runner mockRunner;
Expand Down Expand Up @@ -187,6 +190,10 @@ public void displayOutput() throws IOException {
commandErrorOutput.close();
}

protected void setEnvironemntVariable(final String name, final String value) {
environment.put(name, value);
}

protected CommandLine.Model.CommandSpec parseCommand(final String... args) {
return parseCommand(System.in, args);
}
Expand Down Expand Up @@ -215,7 +222,8 @@ private CommandLine.Model.CommandSpec parseCommand(
mockEthereumWireProtocolConfigurationBuilder,
mockRocksDbConfBuilder,
keyLoader,
mockPantheonPluginContext);
mockPantheonPluginContext,
environment);

// parse using Ansi.OFF to be able to assert on non formatted output results
pantheonCommand.parse(
Expand Down Expand Up @@ -245,7 +253,8 @@ protected KeyLoader getKeyLoader() {
final EthereumWireProtocolConfiguration.Builder mockEthereumConfigurationMockBuilder,
final RocksDbConfiguration.Builder mockRocksDbConfBuilder,
final KeyLoader keyLoader,
final PantheonPluginContextImpl pantheonPluginContext) {
final PantheonPluginContextImpl pantheonPluginContext,
final Map<String, String> environment) {
super(
mockLogger,
mockBlockImporter,
Expand All @@ -254,7 +263,8 @@ protected KeyLoader getKeyLoader() {
mockSyncConfBuilder,
mockEthereumConfigurationMockBuilder,
mockRocksDbConfBuilder,
pantheonPluginContext);
pantheonPluginContext,
environment);
this.keyLoader = keyLoader;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package tech.pegasys.pantheon.cli;

import static java.util.Collections.emptyMap;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
Expand Down Expand Up @@ -63,7 +64,7 @@ public class ConfigOptionSearchAndRunHandlerTest {
new DefaultExceptionHandler<List<Object>>().useErr(errPrintStream).useAnsi(Ansi.OFF);
private final ConfigOptionSearchAndRunHandler configParsingHandler =
new ConfigOptionSearchAndRunHandler(
resultHandler, exceptionHandler, CONFIG_FILE_OPTION_NAME, false);
resultHandler, exceptionHandler, CONFIG_FILE_OPTION_NAME, emptyMap(), false);

@Mock ParseResult mockParseResult;
@Mock CommandLine mockCommandLine;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2019 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.
*/
package tech.pegasys.pantheon.cli;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.HashMap;
import java.util.Map;

import org.junit.Test;
import picocli.CommandLine.Model.OptionSpec;

public class EnvironmentVariableDefaultProviderTest {

private final Map<String, String> environment = new HashMap<>();

private final EnvironmentVariableDefaultProvider provider =
new EnvironmentVariableDefaultProvider(environment);

@Test
public void shouldReturnNullWhenEnvironmentVariableIsNotSet() {
assertThat(provider.defaultValue(OptionSpec.builder("--no-env-var-set").build())).isNull();
}

@Test
public void shouldReturnValueWhenEnvironmentVariableIsSet() {
environment.put("PANTHEON_ENV_VAR_SET", "abc");
assertThat(provider.defaultValue(OptionSpec.builder("--env-var-set").build())).isEqualTo("abc");
}

@Test
public void shouldReturnValueWhenEnvironmentVariableIsSetForAlternateName() {
environment.put("PANTHEON_ENV_VAR_SET", "abc");
assertThat(provider.defaultValue(OptionSpec.builder("--env-var", "--env-var-set").build()))
.isEqualTo("abc");
}

@Test
public void shouldNotReturnValueForShortOptions() {
environment.put("PANTHEON_H", "abc");
assertThat(provider.defaultValue(OptionSpec.builder("-h").build())).isNull();
}
}
Loading

0 comments on commit a1922be

Please sign in to comment.