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}