001package com.quorum.tessera.config.migration; 002 003import com.quorum.tessera.cli.CliAdapter; 004import com.quorum.tessera.cli.CliResult; 005import com.quorum.tessera.cli.CliType; 006import com.quorum.tessera.config.Config; 007import com.quorum.tessera.config.KeyConfiguration; 008import com.quorum.tessera.config.builder.ConfigBuilder; 009import com.quorum.tessera.config.builder.JdbcConfigFactory; 010import com.quorum.tessera.config.builder.KeyDataBuilder; 011import com.quorum.tessera.config.util.JaxbUtil; 012import com.quorum.tessera.io.FilesDelegate; 013import com.quorum.tessera.io.SystemAdapter; 014import picocli.CommandLine.Command; 015import picocli.CommandLine.Option; 016import picocli.CommandLine.Mixin; 017 018import javax.validation.ConstraintViolationException; 019import java.io.IOException; 020import java.io.OutputStream; 021import java.net.MalformedURLException; 022import java.net.URL; 023import java.nio.file.Files; 024import java.nio.file.Path; 025import java.nio.file.Paths; 026import java.util.Optional; 027import java.util.concurrent.Callable; 028 029@Command( 030 headerHeading = "Usage:%n%n", 031 synopsisHeading = "%n", 032 descriptionHeading = "%nDescription:%n%n", 033 parameterListHeading = "%nParameters:%n", 034 optionListHeading = "%nOptions:%n", 035 header = "Generate Tessera JSON config file from a Constellation TOML config file") 036public class LegacyCliAdapter implements CliAdapter, Callable<CliResult> { 037 038 private final FilesDelegate fileDelegate; 039 040 private final TomlConfigFactory configFactory; 041 042 @Option(names = "help", usageHelp = true, description = "display this help message") 043 private boolean isHelpRequested; 044 045 @Option(names = "--outputfile", arity = "1", description = "the path to write the configuration to") 046 private Path outputPath = Paths.get("tessera-config.json"); 047 048 @Option(names = "--tomlfile", arity = "1", description = "the path to the existing TOML configuration") 049 private Path tomlfile; 050 051 @Mixin private LegacyOverridesMixin overrides = new LegacyOverridesMixin(); 052 053 public LegacyCliAdapter() { 054 this.configFactory = new TomlConfigFactory(); 055 this.fileDelegate = FilesDelegate.create(); 056 } 057 058 @Override 059 public CliType getType() { 060 return CliType.CONFIG_MIGRATION; 061 } 062 063 @Override 064 public CliResult call() throws Exception { 065 return this.execute(); 066 } 067 068 @Override 069 public CliResult execute(String... args) throws Exception { 070 071 final ConfigBuilder configBuilder = 072 Optional.ofNullable(tomlfile) 073 .map(fileDelegate::newInputStream) 074 .map(stream -> this.configFactory.create(stream, null)) 075 .orElse(ConfigBuilder.create()); 076 077 final KeyDataBuilder keyDataBuilder = 078 Optional.ofNullable(tomlfile) 079 .map(fileDelegate::newInputStream) 080 .map(configFactory::createKeyDataBuilder) 081 .orElse(KeyDataBuilder.create()); 082 083 ConfigBuilder adjustedConfig = applyOverrides(configBuilder, keyDataBuilder); 084 085 Config config = adjustedConfig.build(); 086 087 return writeToOutputFile(config, outputPath); 088 } 089 090 static CliResult writeToOutputFile(Config config, Path outputPath) throws IOException { 091 SystemAdapter systemAdapter = SystemAdapter.INSTANCE; 092 systemAdapter.out().printf("Saving config to %s", outputPath); 093 systemAdapter.out().println(); 094 JaxbUtil.marshalWithNoValidation(config, systemAdapter.out()); 095 systemAdapter.out().println(); 096 097 try (OutputStream outputStream = Files.newOutputStream(outputPath)) { 098 JaxbUtil.marshal(config, outputStream); 099 systemAdapter.out().printf("Saved config to %s", outputPath); 100 systemAdapter.out().println(); 101 return new CliResult(0, false, config); 102 } catch (ConstraintViolationException validationException) { 103 validationException.getConstraintViolations().stream() 104 .map(cv -> "Warning: " + cv.getMessage() + " on property " + cv.getPropertyPath()) 105 .forEach(systemAdapter.err()::println); 106 107 Files.write(outputPath, JaxbUtil.marshalToStringNoValidation(config).getBytes()); 108 systemAdapter.out().printf("Saved config to %s", outputPath); 109 systemAdapter.out().println(); 110 return new CliResult(2, false, config); 111 } 112 } 113 114 ConfigBuilder applyOverrides(ConfigBuilder configBuilder, KeyDataBuilder keyDataBuilder) { 115 116 Optional.ofNullable(overrides.workdir).ifPresent(configBuilder::workdir); 117 Optional.ofNullable(overrides.workdir).ifPresent(keyDataBuilder::withWorkingDirectory); 118 119 Optional.ofNullable(overrides.url) 120 .map( 121 url -> { 122 try { 123 return new URL(url); 124 } catch (MalformedURLException e) { 125 throw new RuntimeException("Bad server url given: " + e.getMessage()); 126 } 127 }) 128 .map(uri -> uri.getProtocol() + "://" + uri.getHost()) 129 .ifPresent(configBuilder::serverHostname); 130 131 Optional.ofNullable(overrides.port).ifPresent(configBuilder::serverPort); 132 Optional.ofNullable(overrides.socket).ifPresent(configBuilder::unixSocketFile); 133 Optional.ofNullable(overrides.othernodes).ifPresent(configBuilder::peers); 134 Optional.ofNullable(overrides.publickeys).ifPresent(keyDataBuilder::withPublicKeys); 135 Optional.ofNullable(overrides.privatekeys).ifPresent(keyDataBuilder::withPrivateKeys); 136 Optional.ofNullable(overrides.alwayssendto).ifPresent(configBuilder::alwaysSendTo); 137 Optional.ofNullable(overrides.passwords).ifPresent(keyDataBuilder::withPrivateKeyPasswordFile); 138 139 Optional.ofNullable(overrides.storage) 140 .map(JdbcConfigFactory::fromLegacyStorageString) 141 .ifPresent(configBuilder::jdbcConfig); 142 143 if (overrides.whitelist) { 144 configBuilder.useWhiteList(true); 145 } 146 147 Optional.ofNullable(overrides.tls).ifPresent(configBuilder::sslAuthenticationMode); 148 Optional.ofNullable(overrides.tlsservertrust).ifPresent(configBuilder::sslServerTrustMode); 149 Optional.ofNullable(overrides.tlsclienttrust).ifPresent(configBuilder::sslClientTrustMode); 150 Optional.ofNullable(overrides.tlsservercert).ifPresent(configBuilder::sslServerTlsCertificatePath); 151 Optional.ofNullable(overrides.tlsclientcert).ifPresent(configBuilder::sslClientTlsCertificatePath); 152 Optional.ofNullable(overrides.tlsserverchain).ifPresent(configBuilder::sslServerTrustCertificates); 153 Optional.ofNullable(overrides.tlsclientchain).ifPresent(configBuilder::sslClientTrustCertificates); 154 Optional.ofNullable(overrides.tlsserverkey).ifPresent(configBuilder::sslServerTlsKeyPath); 155 Optional.ofNullable(overrides.tlsclientkey).ifPresent(configBuilder::sslClientTlsKeyPath); 156 Optional.ofNullable(overrides.tlsknownservers).ifPresent(configBuilder::sslKnownServersFile); 157 Optional.ofNullable(overrides.tlsknownclients).ifPresent(configBuilder::sslKnownClientsFile); 158 159 final KeyConfiguration keyConfiguration = keyDataBuilder.build(); 160 161 if (!keyConfiguration.getKeyData().isEmpty()) { 162 configBuilder.keyData(keyConfiguration); 163 } else if (overrides.passwords != null) { 164 SystemAdapter.INSTANCE 165 .err() 166 .println( 167 "Info: Public/Private key data not provided in overrides. Overriden password file has not been added to config."); 168 } 169 170 return configBuilder; 171 } 172 173 // TODO: remove. Here for testing 174 public void setOverrides(final LegacyOverridesMixin overrides) { 175 this.overrides = overrides; 176 } 177}