Add SwitchSecureStore site program
Add site program that will switch SecureStore implementations.
Running:
java -jar gerrit.war SwitchSecureStore --new-secure-store-lib $path -d
$site
will read all stored values from old SecureStore, then store them using
new implementation, remove jar with old implementation from lib/
directory, copy new jar to lib/ and finally set new value of
gerrit.secureStoreClass configuration option.
Change-Id: I79391e7cda5901a48b83ee35ac2776e0b977944b
Signed-off-by: Dariusz Luksza <dariusz@luksza.org>
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/SiteLibraryLoaderUtil.java b/gerrit-common/src/main/java/com/google/gerrit/common/SiteLibraryLoaderUtil.java
index d5d4adc..a98e0a5 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/SiteLibraryLoaderUtil.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/SiteLibraryLoaderUtil.java
@@ -22,14 +22,7 @@
public final class SiteLibraryLoaderUtil {
public static void loadSiteLib(File libdir) {
- File[] jars = libdir.listFiles(new FileFilter() {
- @Override
- public boolean accept(File path) {
- String name = path.getName();
- return (name.endsWith(".jar") || name.endsWith(".zip"))
- && path.isFile();
- }
- });
+ File[] jars = listJars(libdir);
if (jars != null && 0 < jars.length) {
Arrays.sort(jars, new Comparator<File>() {
@Override
@@ -46,6 +39,18 @@
}
}
+ public static File[] listJars(File libdir) {
+ File[] jars = libdir.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File path) {
+ String name = path.getName();
+ return (name.endsWith(".jar") || name.endsWith(".zip"))
+ && path.isFile();
+ }
+ });
+ return jars;
+ }
+
private SiteLibraryLoaderUtil() {
}
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/SwitchSecureStore.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/SwitchSecureStore.java
new file mode 100644
index 0000000..f2feae1
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/SwitchSecureStore.java
@@ -0,0 +1,217 @@
+// Copyright (C) 2014 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.google.gerrit.pgm;
+
+import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+import com.google.gerrit.common.IoUtil;
+import com.google.gerrit.common.SiteLibraryLoaderUtil;
+import com.google.gerrit.pgm.util.SiteProgram;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.plugins.JarScanner;
+import com.google.gerrit.server.securestore.DefaultSecureStore;
+import com.google.gerrit.server.securestore.SecureStore;
+import com.google.gerrit.server.securestore.SecureStore.EntryKey;
+import com.google.inject.Injector;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+public class SwitchSecureStore extends SiteProgram {
+ private static String getSecureStoreClassFromGerritConfig(SitePaths sitePaths) {
+ FileBasedConfig cfg =
+ new FileBasedConfig(sitePaths.gerrit_config, FS.DETECTED);
+ try {
+ cfg.load();
+ } catch (IOException | ConfigInvalidException e) {
+ throw new RuntimeException("Cannot read gerrit.config file", e);
+ }
+ return cfg.getString("gerrit", null, "secureStoreClass");
+ }
+
+ private static final Logger log = LoggerFactory
+ .getLogger(SwitchSecureStore.class);
+
+ @Option(name = "--new-secure-store-lib",
+ usage = "Path to new SecureStore implementation",
+ required = true)
+ private String newSecureStoreLib;
+
+ @Override
+ public int run() throws Exception {
+ SitePaths sitePaths = new SitePaths(getSitePath());
+ File newSecureStoreFile = new File(newSecureStoreLib);
+ if (!newSecureStoreFile.exists()) {
+ log.error(String.format("File %s doesn't exists",
+ newSecureStoreFile.getAbsolutePath()));
+ return -1;
+ }
+
+ String newSecureStore = getNewSecureStoreClassName(newSecureStoreFile);
+ String currentSecureStoreName = getCurrentSecureStoreClassName(sitePaths);
+
+ if (currentSecureStoreName.equals(newSecureStore)) {
+ log.error("Old and new SecureStore implementation names "
+ + "are the same. Migration will not work");
+ return -1;
+ }
+
+ IoUtil.loadJARs(newSecureStoreFile);
+ SiteLibraryLoaderUtil.loadSiteLib(sitePaths.lib_dir);
+
+ log.info("Current secureStoreClass property ({}) will be replaced with {}",
+ currentSecureStoreName, newSecureStore);
+ Injector dbInjector = createDbInjector(SINGLE_USER);
+ SecureStore currentStore =
+ getSecureStore(currentSecureStoreName, dbInjector);
+ SecureStore newStore = getSecureStore(newSecureStore, dbInjector);
+
+ migrateProperties(currentStore, newStore);
+
+ removeOldLib(sitePaths, currentSecureStoreName);
+ copyNewLib(sitePaths, newSecureStoreFile);
+
+ updateGerritConfig(sitePaths, newSecureStore);
+
+ return 0;
+ }
+
+ private void migrateProperties(SecureStore currentStore, SecureStore newStore) {
+ log.info("Migrate entries");
+ for (EntryKey key : currentStore.list()) {
+ String[] value =
+ currentStore.getList(key.section, key.subsection, key.name);
+ if (value != null) {
+ newStore.setList(key.section, key.subsection, key.name,
+ Arrays.asList(value));
+ } else {
+ String msg =
+ String.format("Cannot migrate entry for %s", key.section);
+ if (key.subsection != null) {
+ msg = msg + String.format(".%s", key.subsection);
+ }
+ msg = msg + String.format(".%s", key.name);
+ throw new RuntimeException(msg);
+ }
+ }
+ }
+
+ private void removeOldLib(SitePaths sitePaths, String currentSecureStoreName) {
+ File oldSecureStore =
+ findJarWithSecureStore(sitePaths, currentSecureStoreName);
+ if (oldSecureStore != null) {
+ log.info("Removing old SecureStore ({}) from lib/ directory",
+ oldSecureStore.getName());
+ if (!oldSecureStore.delete()) {
+ log.error("Cannot remove {}", oldSecureStore.getAbsolutePath());
+ }
+ } else {
+ log.info("Cannot find jar with old SecureStore ({}) in lib/ directory",
+ currentSecureStoreName);
+ }
+ }
+
+ private void copyNewLib(SitePaths sitePaths, File newSecureStoreFile)
+ throws IOException {
+ log.info("Copy new SecureStore ({}) into lib/ directory",
+ newSecureStoreFile.getName());
+ Files.copy(newSecureStoreFile, new File(sitePaths.lib_dir,
+ newSecureStoreFile.getName()));
+ }
+
+ private void updateGerritConfig(SitePaths sitePaths, String newSecureStore)
+ throws IOException, ConfigInvalidException {
+ log.info("Set gerrit.secureStoreClass property of gerrit.config to {}",
+ newSecureStore);
+ FileBasedConfig config =
+ new FileBasedConfig(sitePaths.gerrit_config, FS.DETECTED);
+ config.load();
+ config.setString("gerrit", null, "secureStoreClass", newSecureStore);
+ config.save();
+ }
+
+ private String getNewSecureStoreClassName(File secureStore)
+ throws IOException {
+ JarScanner scanner = new JarScanner(secureStore);
+ List<String> newSecureStores =
+ scanner.findSubClassesOf(SecureStore.class);
+ if (newSecureStores.isEmpty()) {
+ throw new RuntimeException(String.format(
+ "Cannot find implementation of SecureStore interface in %s",
+ secureStore.getAbsolutePath()));
+ }
+ if (newSecureStores.size() > 1) {
+ throw new RuntimeException(String.format(
+ "Found too many implementations of SecureStore:\n%s\nin %s", Joiner
+ .on("\n").join(newSecureStores), secureStore.getAbsolutePath()));
+ }
+ return Iterables.getOnlyElement(newSecureStores);
+ }
+
+ private String getCurrentSecureStoreClassName(SitePaths sitePaths) {
+ String current = getSecureStoreClassFromGerritConfig(sitePaths);
+ if (!Strings.isNullOrEmpty(current)) {
+ return current;
+ }
+ return DefaultSecureStore.class.getName();
+ }
+
+ private SecureStore getSecureStore(String className, Injector injector) {
+ try {
+ @SuppressWarnings("unchecked")
+ Class<? extends SecureStore> clazz =
+ (Class<? extends SecureStore>) Class.forName(className);
+ return injector.getInstance(clazz);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(
+ String.format("Cannot load SecureStore implementation: %s", className), e);
+ }
+ }
+
+ private File findJarWithSecureStore(SitePaths sitePaths,
+ String secureStoreClass) {
+ File[] jars = SiteLibraryLoaderUtil.listJars(sitePaths.lib_dir);
+ if (jars == null || jars.length == 0) {
+ return null;
+ }
+ String secureStoreClassPath = secureStoreClass.replace('.', '/') + ".class";
+ for (File jar : jars) {
+ try (JarFile jarFile = new JarFile(jar)) {
+ ZipEntry entry = jarFile.getEntry(secureStoreClassPath);
+ if (entry != null) {
+ return jar;
+ }
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+ return null;
+ }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
index c46f8e3..c9e76c8 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -217,7 +217,8 @@
&& !currentSecureStoreClassName.equals(secureStoreInitData.className)) {
String err =
String.format(
- "Different secure store was previously configured: %s.",
+ "Different secure store was previously configured: %s. "
+ + "Use SwitchSecureStore program to switch between implementations.",
currentSecureStoreClassName);
die(err, new RuntimeException("secure store mismatch"));
}