Reapply "Expose API to update remotes' configurations" This reverts commit aab69373db6152ea98e7627f816fa3d777d5fe46. As mentioned in the revered commit message, the plan was to only revert that change in order to make 3.10 release. Now, when the release is done we can get `ReplicatonRemotesUpdater` back. Importantly, this reapply doesn't effectively bring back the `ReplicationRemotesUpdater` API back in, as the definition of `ApiModule` is still moved to `replication-api` artefact. Change-Id: Id03d8d50cc662fca4af4c9a1a86d7ddb9f6254c0
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/FanoutConfigResource.java b/src/main/java/com/googlesource/gerrit/plugins/replication/FanoutConfigResource.java index 4220ddb..0a2afa6 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/FanoutConfigResource.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/FanoutConfigResource.java
@@ -23,9 +23,11 @@ import com.google.common.hash.Hashing; import com.google.gerrit.server.config.SitePaths; import com.google.inject.Inject; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Stream; @@ -61,6 +63,30 @@ } } + @Override + public void update(Config updates) throws IOException { + Set<String> remotes = updates.getSubsections("remote"); + for (String remote : remotes) { + File remoteFile = remoteConfigsDirPath.resolve(remote + ".config").toFile(); + FileBasedConfig remoteConfig = new FileBasedConfig(remoteFile, FS.DETECTED); + try { + remoteConfig.load(); + } catch (ConfigInvalidException e) { + throw new IOException( + String.format("Cannot parse configuration file: %s", remoteFile.getAbsoluteFile()), e); + } + Set<String> options = updates.getNames("remote", remote); + for (String option : options) { + List<String> values = List.of(updates.getStringList("remote", remote, option)); + remoteConfig.setStringList("remote", remote, option, values); + } + remoteConfig.save(); + } + + removeRemotes(updates); + super.update(updates); + } + private static void removeRemotes(Config config) { Set<String> remoteNames = config.getSubsections("remote"); if (remoteNames.size() > 0) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/FileConfigResource.java b/src/main/java/com/googlesource/gerrit/plugins/replication/FileConfigResource.java index baccb83..7a14cd1 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/FileConfigResource.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/FileConfigResource.java
@@ -17,6 +17,7 @@ import static com.googlesource.gerrit.plugins.replication.ReplicationQueue.repLog; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; import com.google.gerrit.common.UsedAt; import com.google.gerrit.common.UsedAt.Project; import com.google.gerrit.server.config.SitePaths; @@ -24,6 +25,7 @@ import com.googlesource.gerrit.plugins.replication.api.ConfigResource; import java.io.IOException; import java.nio.file.Path; +import java.util.List; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.storage.file.FileBasedConfig; @@ -54,6 +56,25 @@ } @Override + public void update(Config updates) throws IOException { + for (String section : updates.getSections()) { + for (String subsection : updates.getSubsections(section)) { + for (String name : updates.getNames(section, subsection, true)) { + List<String> values = + Lists.newArrayList(updates.getStringList(section, subsection, name)); + config.setStringList(section, subsection, name, values); + } + } + + for (String name : updates.getNames(section, true)) { + List<String> values = Lists.newArrayList(updates.getStringList(section, null, name)); + config.setStringList(section, null, name, values); + } + } + config.save(); + } + + @Override public String getVersion() { return Long.toString(config.getFile().lastModified()); }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfigModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfigModule.java index b0343c9..6a97432 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfigModule.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfigModule.java
@@ -23,9 +23,11 @@ import com.google.inject.Inject; import com.google.inject.ProvisionException; import com.google.inject.Scopes; +import com.google.inject.assistedinject.FactoryModuleBuilder; import com.google.inject.internal.UniqueAnnotations; import com.googlesource.gerrit.plugins.replication.api.ConfigResource; import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig; +import com.googlesource.gerrit.plugins.replication.events.ProjectDeletionState; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -57,6 +59,14 @@ } else { bind(ReplicationConfig.class).to(ReplicationConfigImpl.class).in(Scopes.SINGLETON); } + + bind(ReplicationQueue.class).in(Scopes.SINGLETON); + bind(ObservableQueue.class).to(ReplicationQueue.class); + bind(ReplicationDestinations.class).to(DestinationsCollection.class); + bind(ConfigParser.class).to(DestinationConfigParser.class).in(Scopes.SINGLETON); + + install(new FactoryModuleBuilder().build(Destination.Factory.class)); + install(new FactoryModuleBuilder().build(ProjectDeletionState.Factory.class)); } public FileBasedConfig getReplicationConfig() {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationModule.java index ac27280..5f59bf8 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationModule.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationModule.java
@@ -34,7 +34,6 @@ import com.googlesource.gerrit.plugins.replication.events.ProjectDeletionReplicationFailedEvent; import com.googlesource.gerrit.plugins.replication.events.ProjectDeletionReplicationScheduledEvent; import com.googlesource.gerrit.plugins.replication.events.ProjectDeletionReplicationSucceededEvent; -import com.googlesource.gerrit.plugins.replication.events.ProjectDeletionState; import com.googlesource.gerrit.plugins.replication.events.RefReplicatedEvent; import com.googlesource.gerrit.plugins.replication.events.RefReplicationDoneEvent; import com.googlesource.gerrit.plugins.replication.events.ReplicationScheduledEvent; @@ -52,10 +51,7 @@ @Override protected void configure() { - install(new FactoryModuleBuilder().build(Destination.Factory.class)); install(configModule); - bind(ReplicationQueue.class).in(Scopes.SINGLETON); - bind(ObservableQueue.class).to(ReplicationQueue.class); bind(LifecycleListener.class) .annotatedWith(UniqueAnnotations.create()) .to(ReplicationQueue.class); @@ -77,10 +73,8 @@ .to(StartReplicationCapability.class); install(new FactoryModuleBuilder().build(PushAll.Factory.class)); - install(new FactoryModuleBuilder().build(ProjectDeletionState.Factory.class)); bind(EventBus.class).in(Scopes.SINGLETON); - bind(ReplicationDestinations.class).to(DestinationsCollection.class); bind(ConfigParser.class).to(DestinationConfigParser.class).in(Scopes.SINGLETON); DynamicSet.setOf(binder(), ReplicationStateListener.class);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationRemotesUpdater.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationRemotesUpdater.java new file mode 100644 index 0000000..a5e84d9 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationRemotesUpdater.java
@@ -0,0 +1,110 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// 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 com.googlesource.gerrit.plugins.replication; + +import static com.google.gerrit.common.UsedAt.Project.PLUGIN_GITHUB; + +import com.google.gerrit.common.Nullable; +import com.google.gerrit.common.UsedAt; +import com.google.gerrit.extensions.registration.DynamicItem; +import com.google.gerrit.server.securestore.SecureStore; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; +import com.googlesource.gerrit.plugins.replication.api.ConfigResource; +import com.googlesource.gerrit.plugins.replication.api.ReplicationConfigOverrides; +import java.io.IOException; +import java.util.List; +import org.eclipse.jgit.lib.Config; + +/** Public API to update replication plugin remotes configurations programmatically. */ +@Singleton +@UsedAt(PLUGIN_GITHUB) +public class ReplicationRemotesUpdater { + + private final SecureStore secureStore; + private final Provider<ConfigResource> baseConfigProvider; + private final DynamicItem<ReplicationConfigOverrides> configOverridesItem; + + @Inject + ReplicationRemotesUpdater( + SecureStore secureStore, + Provider<ConfigResource> baseConfigProvider, + @Nullable DynamicItem<ReplicationConfigOverrides> configOverridesItem) { + this.secureStore = secureStore; + this.baseConfigProvider = baseConfigProvider; + this.configOverridesItem = configOverridesItem; + } + + /** + * Adds or updates the remote configuration for the replication plugin. + * + * <p>Provided JGit {@link Config} object should contain at least one named <em>remote</em> + * section. All other configurations will be ignored. + * + * <p>NOTE: The {@code remote.$name.password} will be stored using {@link SecureStore}. + * + * @param remoteConfig remotes to add or update + * @throws IOException when persisting fails + */ + public void update(Config remoteConfig) throws IOException { + if (remoteConfig.getSubsections("remote").isEmpty()) { + throw new IllegalArgumentException( + "configuration update must have at least one 'remote' section"); + } + + SeparatedRemoteConfigs configs = onlyRemoteSectionsWithSeparatedPasswords(remoteConfig); + persistRemotesPasswords(configs); + + if (hasConfigOverrides()) { + configOverridesItem.get().update(configs.remotes); + } else { + baseConfigProvider.get().update(configs.remotes); + } + } + + private SeparatedRemoteConfigs onlyRemoteSectionsWithSeparatedPasswords(Config configUpdates) { + SeparatedRemoteConfigs configs = new SeparatedRemoteConfigs(); + for (String subSection : configUpdates.getSubsections("remote")) { + for (String name : configUpdates.getNames("remote", subSection)) { + List<String> values = List.of(configUpdates.getStringList("remote", subSection, name)); + if ("password".equals(name)) { + configs.passwords.setStringList("remote", subSection, "password", values); + } else { + configs.remotes.setStringList("remote", subSection, name, values); + } + } + } + + return configs; + } + + private void persistRemotesPasswords(SeparatedRemoteConfigs configs) { + for (String subSection : configs.passwords.getSubsections("remote")) { + List<String> values = + List.of(configs.passwords.getStringList("remote", subSection, "password")); + secureStore.setList("remote", subSection, "password", values); + } + } + + private boolean hasConfigOverrides() { + return configOverridesItem != null && configOverridesItem.get() != null; + } + + private static class SeparatedRemoteConfigs { + private final Config remotes = new Config(); + private final Config passwords = new Config(); + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/api/ConfigResource.java b/src/main/java/com/googlesource/gerrit/plugins/replication/api/ConfigResource.java index 43733bc..08f35d0 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/api/ConfigResource.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/api/ConfigResource.java
@@ -14,6 +14,7 @@ package com.googlesource.gerrit.plugins.replication.api; +import java.io.IOException; import org.eclipse.jgit.lib.Config; /** @@ -36,7 +37,17 @@ */ Config getConfig(); - /** + /** + * Update the configuration resource. + * + * <p>Allows to persist changes to the configuration resource. + * + * @param config updated configuration + * @throws IOException when configuration cannot be persisted + */ + void update(Config config) throws IOException; + + /** * Current logical version string of the current configuration on the persistent storage. * * @return latest logical version number on the persistent storage
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/FanoutConfigResourceTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/FanoutConfigResourceTest.java index 9147ea1..a622170 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/FanoutConfigResourceTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/FanoutConfigResourceTest.java
@@ -24,6 +24,7 @@ import java.io.IOException; import java.util.List; import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS; import org.junit.Before; @@ -285,9 +286,69 @@ assertThat(objectUnderTest.getVersion()).isEqualTo(replicationConfigVersion); } + @Test + public void shouldAddConfigOptionToMainConfig() throws Exception { + FanoutConfigResource objectUnderTest = new FanoutConfigResource(sitePaths); + Config update = new Config(); + update.setString("new", null, "value", "set"); + + objectUnderTest.update(update); + Config updatedConfig = objectUnderTest.getConfig(); + + assertThat(updatedConfig.getString("new", null, "value")).isEqualTo("set"); + } + + @Test + public void shouldUpdateConfigOptionInMainConfig() throws Exception { + FileBasedConfig config = newReplicationConfig(); + config.setString("updatable", null, "value", "orig"); + config.save(); + FanoutConfigResource objectUnderTest = new FanoutConfigResource(sitePaths); + Config update = new Config(); + update.setString("updatable", null, "value", "updated"); + + objectUnderTest.update(update); + Config updatedConfig = objectUnderTest.getConfig(); + + assertThat(updatedConfig.getString("updatable", null, "value")).isEqualTo("updated"); + } + + @Test + public void shouldAddNewRemoteFile() throws Exception { + FanoutConfigResource objectUnderTest = new FanoutConfigResource(sitePaths); + Config update = new Config(); + update.setString("remote", remoteName1, "url", remoteUrl1); + + objectUnderTest.update(update); + + Config actual = loadRemoteConfig(remoteName1); + assertThat(actual.getString("remote", remoteName1, "url")).isEqualTo(remoteUrl1); + } + + @Test + public void shouldUpdateExistingRemote() throws Exception { + FileBasedConfig rawRemoteConfig = newRemoteConfig(remoteName1); + rawRemoteConfig.setString("remote", remoteName1, "url", remoteUrl1); + rawRemoteConfig.save(); + FanoutConfigResource objectUnderTest = new FanoutConfigResource(sitePaths); + Config update = new Config(); + update.setString("remote", remoteName1, "url", remoteUrl2); + + objectUnderTest.update(update); + + Config actual = loadRemoteConfig(remoteName1); + assertThat(actual.getString("remote", remoteName1, "url")).isEqualTo(remoteUrl2); + } + protected FileBasedConfig newRemoteConfig(String configFileName) { return new FileBasedConfig( sitePaths.etc_dir.resolve("replication/" + configFileName + ".config").toFile(), FS.DETECTED); } + + private Config loadRemoteConfig(String siteName) throws Exception { + FileBasedConfig config = newRemoteConfig(siteName); + config.load(); + return config; + } }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/FileConfigResourceTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/FileConfigResourceTest.java new file mode 100644 index 0000000..92bf58d --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/FileConfigResourceTest.java
@@ -0,0 +1,121 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// 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 com.googlesource.gerrit.plugins.replication; + +import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.io.MoreFiles; +import com.google.gerrit.server.config.SitePaths; +import com.googlesource.gerrit.plugins.replication.api.ConfigResource; +import java.nio.file.Files; +import java.nio.file.Path; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.util.FS; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class FileConfigResourceTest { + private static final String VALUE_KEY = "value"; + private static final String INITIAL_KEY = "initial"; + private static final String UPDATABLE_KEY = "updatable"; + + private Path testDir; + private SitePaths sitePaths; + private ConfigResource configResource; + + @Before + public void setUp() throws Exception { + testDir = Files.createTempDirectory("fileConfigResourceTest"); + sitePaths = new SitePaths(testDir); + configResource = newFileConfigResource(); + } + + @After + public void tearDown() throws Exception { + MoreFiles.deleteRecursively(testDir, ALLOW_INSECURE); + } + + @Test + public void updateEmptyFile() throws Exception { + Config configUpdate = newConfigUpdate(); + + Config beforeUpdate = configResource.getConfig(); + assertThat(beforeUpdate.getSections()).isEmpty(); + + configResource.update(configUpdate); + Config updatedConfig = newFileConfigResource().getConfig(); + + assertConfigUpdate(updatedConfig); + } + + @Test + public void appendOptionToConfig() throws Exception { + FileBasedConfig rawConfig = getRawReplicationConfig(); + rawConfig.setInt(INITIAL_KEY, null, VALUE_KEY, 10); + rawConfig.save(); + configResource = newFileConfigResource(); + Config configUpdate = newConfigUpdate(); + + configResource.update(configUpdate); + Config updatedConfig = configResource.getConfig(); + + assertConfigUpdate(updatedConfig); + assertThat(updatedConfig.getInt(INITIAL_KEY, null, VALUE_KEY, -1)).isEqualTo(10); + } + + @Test + public void updateExistingOption() throws Exception { + int expectedValue = 20; + Config configUpdate = new Config(); + configUpdate.setInt(UPDATABLE_KEY, null, VALUE_KEY, expectedValue); + FileBasedConfig rawConfig = getRawReplicationConfig(newConfigUpdate()); + rawConfig.save(); + + configResource.update(configUpdate); + Config updatedConfig = configResource.getConfig(); + + assertConfigUpdate(updatedConfig, expectedValue); + } + + private FileConfigResource newFileConfigResource() { + return new FileConfigResource(sitePaths); + } + + private Config newConfigUpdate() { + Config configUpdate = new Config(); + configUpdate.setInt(UPDATABLE_KEY, null, VALUE_KEY, 1); + return configUpdate; + } + + private void assertConfigUpdate(Config config) { + assertConfigUpdate(config, 1); + } + + private void assertConfigUpdate(Config config, int expectedValue) { + assertThat(config.getInt(UPDATABLE_KEY, null, VALUE_KEY, -1)).isEqualTo(expectedValue); + } + + private FileBasedConfig getRawReplicationConfig() { + return getRawReplicationConfig(new Config()); + } + + private FileBasedConfig getRawReplicationConfig(Config base) { + Path configPath = sitePaths.etc_dir.resolve(FileConfigResource.CONFIG_NAME); + return new FileBasedConfig(base, configPath.toFile(), FS.DETECTED); + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/MergedConfigResourceTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/MergedConfigResourceTest.java index 18bb603..cae78e3 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/MergedConfigResourceTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/MergedConfigResourceTest.java
@@ -22,6 +22,7 @@ import com.googlesource.gerrit.plugins.replication.api.ApiModule; import com.googlesource.gerrit.plugins.replication.api.ConfigResource; import com.googlesource.gerrit.plugins.replication.api.ReplicationConfigOverrides; +import org.apache.commons.lang3.NotImplementedException; import org.eclipse.jgit.lib.Config; import org.junit.Test; @@ -78,6 +79,11 @@ } @Override + public void update(Config config) { + throw new NotImplementedException("not implemented"); + } + + @Override public String getVersion() { return "base"; } @@ -93,6 +99,11 @@ } @Override + public void update(Config config) { + throw new NotImplementedException("not implemented"); + } + + @Override public String getVersion() { return "override"; }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationRemotesUpdaterTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationRemotesUpdaterTest.java new file mode 100644 index 0000000..bf566f2 --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationRemotesUpdaterTest.java
@@ -0,0 +1,150 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// 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 com.googlesource.gerrit.plugins.replication; + +import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; +import static com.google.common.truth.Truth.assertThat; +import static com.google.gerrit.testing.GerritJUnit.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.common.io.MoreFiles; +import com.google.common.truth.StringSubject; +import com.google.gerrit.extensions.registration.DynamicItem; +import com.google.gerrit.server.config.SitePaths; +import com.google.gerrit.server.securestore.SecureStore; +import com.google.inject.util.Providers; +import com.googlesource.gerrit.plugins.replication.api.ReplicationConfigOverrides; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import org.eclipse.jgit.lib.Config; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ReplicationRemotesUpdaterTest { + + private Path testSite; + private SecureStore secureStoreMock; + private FileConfigResource baseConfig; + + @Before + public void setUp() throws Exception { + testSite = Files.createTempDirectory("replicationRemotesUpdateTest"); + secureStoreMock = mock(SecureStore.class); + baseConfig = new FileConfigResource(new SitePaths(testSite)); + } + + @After + public void tearDown() throws Exception { + MoreFiles.deleteRecursively(testSite, ALLOW_INSECURE); + } + + @Test + public void shouldThrowWhenNoRemotesInTheUpdate() { + Config update = new Config(); + ReplicationRemotesUpdater objectUnderTest = newReplicationConfigUpdater(); + + assertThrows(IllegalArgumentException.class, () -> objectUnderTest.update(update)); + + update.setString("non-remote", null, "value", "one"); + + assertThrows(IllegalArgumentException.class, () -> objectUnderTest.update(update)); + } + + @Test + public void addRemoteSectionToBaseConfigWhenNoOverrides() throws Exception { + String url = "fake_url"; + Config update = new Config(); + setRemoteSite(update, "url", url); + ReplicationRemotesUpdater objectUnderTest = newReplicationConfigUpdater(); + + objectUnderTest.update(update); + + assertRemoteSite(baseConfig.getConfig(), "url").isEqualTo(url); + } + + @Test + public void addRemoteSectionToBaseOverridesConfig() throws Exception { + TestReplicationConfigOverrides testOverrides = new TestReplicationConfigOverrides(); + String url = "fake_url"; + Config update = new Config(); + setRemoteSite(update, "url", url); + ReplicationRemotesUpdater objectUnderTest = newReplicationConfigUpdater(testOverrides); + + objectUnderTest.update(update); + + assertRemoteSite(testOverrides.getConfig(), "url").isEqualTo(url); + assertRemoteSite(baseConfig.getConfig(), "url").isNull(); + } + + @Test + public void encryptPassword() throws Exception { + TestReplicationConfigOverrides testOverrides = new TestReplicationConfigOverrides(); + Config update = new Config(); + String password = "my_secret_password"; + setRemoteSite(update, "password", password); + ReplicationRemotesUpdater objectUnderTest = newReplicationConfigUpdater(testOverrides); + + objectUnderTest.update(update); + + verify(secureStoreMock).setList("remote", "site", "password", List.of(password)); + assertRemoteSite(baseConfig.getConfig(), "password").isNull(); + assertRemoteSite(testOverrides.getConfig(), "password").isNull(); + } + + private ReplicationRemotesUpdater newReplicationConfigUpdater() { + return newReplicationConfigUpdater(null); + } + + private void setRemoteSite(Config config, String name, String value) { + config.setString("remote", "site", name, value); + } + + private StringSubject assertRemoteSite(Config config, String name) { + return assertThat(config.getString("remote", "site", name)); + } + + private ReplicationRemotesUpdater newReplicationConfigUpdater( + ReplicationConfigOverrides overrides) { + DynamicItem<ReplicationConfigOverrides> dynamicItemMock = mock(DynamicItem.class); + when(dynamicItemMock.get()).thenReturn(overrides); + + return new ReplicationRemotesUpdater( + secureStoreMock, Providers.of(baseConfig), dynamicItemMock); + } + + static class TestReplicationConfigOverrides implements ReplicationConfigOverrides { + private Config config = new Config(); + + @Override + public Config getConfig() { + return config; + } + + @Override + public void update(Config update) throws IOException { + config = update; + } + + @Override + public String getVersion() { + return "none"; + } + } +}