blob: 1f565129ccad32652b968d25a21f3db996446406 [file] [log] [blame]
// Copyright (C) 2013 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.init;
import static com.google.inject.Scopes.SINGLETON;
import static com.google.inject.Stage.PRODUCTION;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Die;
import com.google.gerrit.common.JarUtil;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.index.IndexType;
import com.google.gerrit.metrics.DisabledMetricMaker;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.init.api.InitFlags;
import com.google.gerrit.pgm.init.api.InstallAllPlugins;
import com.google.gerrit.pgm.init.api.InstallPlugins;
import com.google.gerrit.pgm.init.api.LibraryDownload;
import com.google.gerrit.pgm.init.index.IndexManagerOnInit;
import com.google.gerrit.pgm.init.index.IndexModuleOnInit;
import com.google.gerrit.pgm.init.index.lucene.LuceneIndexModuleOnInit;
import com.google.gerrit.pgm.util.SiteProgram;
import com.google.gerrit.server.config.GerritServerConfigModule;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.index.IndexModule;
import com.google.gerrit.server.plugins.JarScanner;
import com.google.gerrit.server.schema.NoteDbSchemaUpdater;
import com.google.gerrit.server.schema.UpdateUI;
import com.google.gerrit.server.securestore.SecureStore;
import com.google.gerrit.server.securestore.SecureStoreClassName;
import com.google.gerrit.server.securestore.SecureStoreProvider;
import com.google.inject.AbstractModule;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.Message;
import com.google.inject.util.Providers;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/** Initialize a new Gerrit installation. */
public class BaseInit extends SiteProgram {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final boolean standalone;
protected final PluginsDistribution pluginsDistribution;
private final List<String> pluginsToInstall;
private Injector sysInjector;
protected BaseInit(PluginsDistribution pluginsDistribution, List<String> pluginsToInstall) {
this.standalone = true;
this.pluginsDistribution = pluginsDistribution;
this.pluginsToInstall = pluginsToInstall;
}
public BaseInit(
Path sitePath,
boolean standalone,
PluginsDistribution pluginsDistribution,
List<String> pluginsToInstall) {
super(sitePath);
this.standalone = standalone;
this.pluginsDistribution = pluginsDistribution;
this.pluginsToInstall = pluginsToInstall;
}
@Override
public int run() throws Exception {
final SiteInit init = createSiteInit();
if (beforeInit(init)) {
return 0;
}
init.flags.autoStart = getAutoStart() && init.site.isNew;
init.flags.dev = isDev() && init.site.isNew;
init.flags.skipPlugins = skipPlugins();
init.flags.deleteCaches = getDeleteCaches();
init.flags.isNew = init.site.isNew;
final SiteRun run;
try {
init.initializer.run();
init.flags.deleteOnFailure = false;
Injector sysInjector = createSysInjector(init);
IndexManagerOnInit indexManager = sysInjector.getInstance(IndexManagerOnInit.class);
try {
indexManager.start();
run = createSiteRun(init);
try {
run.upgradeSchema();
} catch (StorageException e) {
String msg = "Couldn't upgrade schema. Expected if slave and read-only database";
System.err.println(msg);
logger.atSevere().withCause(e).log("%s", msg);
}
init.initializer.postRun(sysInjector);
} finally {
indexManager.stop();
}
} catch (Exception | Error failure) {
if (init.flags.deleteOnFailure) {
recursiveDelete(getSitePath());
}
throw failure;
}
System.err.println("Initialized " + getSitePath().toRealPath().normalize());
afterInit(run);
return 0;
}
protected boolean skipPlugins() {
return false;
}
protected String getSecureStoreLib() {
return null;
}
protected boolean skipAllDownloads() {
return false;
}
protected List<String> getSkippedDownloads() {
return Collections.emptyList();
}
/**
* Invoked before site init is called.
*
* @param init initializer instance.
*/
protected boolean beforeInit(SiteInit init) throws Exception {
return false;
}
/**
* Invoked after site init is called.
*
* @param run completed run instance.
*/
protected void afterInit(SiteRun run) throws Exception {}
@Nullable
protected List<String> getInstallPlugins() {
try {
if (pluginsToInstall != null && pluginsToInstall.isEmpty()) {
return Collections.emptyList();
}
List<String> names = pluginsDistribution.listPluginNames();
if (pluginsToInstall != null) {
names.removeIf(n -> !pluginsToInstall.contains(n));
}
return names;
} catch (FileNotFoundException e) {
logger.atWarning().log(
"Couldn't find distribution archive location. No plugin will be installed");
return null;
}
}
protected boolean installAllPlugins() {
return false;
}
protected boolean getAutoStart() {
return false;
}
public static class SiteInit {
public final SitePaths site;
final InitFlags flags;
final ConsoleUI ui;
final SitePathInitializer initializer;
@Inject
SiteInit(
final SitePaths site,
final InitFlags flags,
final ConsoleUI ui,
final SitePathInitializer initializer) {
this.site = site;
this.flags = flags;
this.ui = ui;
this.initializer = initializer;
}
}
private SiteInit createSiteInit() {
final ConsoleUI ui = getConsoleUI();
final Path sitePath = getSitePath();
final List<Module> m = new ArrayList<>();
final SecureStoreInitData secureStoreInitData = discoverSecureStoreClass();
final String currentSecureStoreClassName = getConfiguredSecureStoreClass();
if (secureStoreInitData != null
&& currentSecureStoreClassName != null
&& !currentSecureStoreClassName.equals(secureStoreInitData.className)) {
String err =
String.format(
"Different secure store was previously configured: %s. "
+ "Use SwitchSecureStore program to switch between implementations.",
currentSecureStoreClassName);
throw die(err);
}
m.add(new GerritServerConfigModule());
m.add(new InitModule(standalone));
m.add(
new AbstractModule() {
@Override
protected void configure() {
bind(ConsoleUI.class).toInstance(ui);
bind(Path.class).annotatedWith(SitePath.class).toInstance(sitePath);
List<String> plugins = MoreObjects.firstNonNull(getInstallPlugins(), new ArrayList<>());
bind(new TypeLiteral<List<String>>() {})
.annotatedWith(InstallPlugins.class)
.toInstance(plugins);
bind(new TypeLiteral<Boolean>() {})
.annotatedWith(InstallAllPlugins.class)
.toInstance(installAllPlugins());
bind(PluginsDistribution.class).toInstance(pluginsDistribution);
String secureStoreClassName;
if (secureStoreInitData != null) {
secureStoreClassName = secureStoreInitData.className;
} else {
secureStoreClassName = currentSecureStoreClassName;
}
if (secureStoreClassName != null) {
ui.message("Using secure store: %s\n", secureStoreClassName);
}
bind(SecureStoreInitData.class).toProvider(Providers.of(secureStoreInitData));
bind(String.class)
.annotatedWith(SecureStoreClassName.class)
.toProvider(Providers.of(secureStoreClassName));
bind(SecureStore.class).toProvider(SecureStoreProvider.class).in(SINGLETON);
bind(new TypeLiteral<List<String>>() {})
.annotatedWith(LibraryDownload.class)
.toInstance(getSkippedDownloads());
bind(Boolean.class).annotatedWith(LibraryDownload.class).toInstance(skipAllDownloads());
bind(MetricMaker.class).to(DisabledMetricMaker.class);
}
});
try {
return Guice.createInjector(PRODUCTION, m).getInstance(SiteInit.class);
} catch (CreationException ce) {
final Message first = ce.getErrorMessages().iterator().next();
Throwable why = first.getCause();
if (why instanceof Die) {
throw (Die) why;
}
final StringBuilder buf = new StringBuilder(ce.getMessage());
while (why != null) {
buf.append("\n");
buf.append(why.getMessage());
why = why.getCause();
if (why != null) {
buf.append("\n caused by ");
}
}
throw die(buf.toString(), new RuntimeException("InitInjector failed", ce));
}
}
protected ConsoleUI getConsoleUI() {
return ConsoleUI.getInstance(false);
}
@Nullable
private SecureStoreInitData discoverSecureStoreClass() {
String secureStore = getSecureStoreLib();
if (Strings.isNullOrEmpty(secureStore)) {
return null;
}
Path secureStoreLib = Path.of(secureStore);
if (!Files.exists(secureStoreLib)) {
throw new InvalidSecureStoreException(String.format("File %s doesn't exist", secureStore));
}
try (JarScanner scanner = new JarScanner(secureStoreLib)) {
List<String> secureStores = scanner.findSubClassesOf(SecureStore.class);
if (secureStores.isEmpty()) {
throw new InvalidSecureStoreException(
String.format(
"Cannot find class implementing %s interface in %s",
SecureStore.class.getName(), secureStore));
}
if (secureStores.size() > 1) {
throw new InvalidSecureStoreException(
String.format(
"%s has more that one implementation of %s interface",
secureStore, SecureStore.class.getName()));
}
JarUtil.loadJars(secureStoreLib);
return new SecureStoreInitData(secureStoreLib, secureStores.get(0));
} catch (IOException e) {
throw new InvalidSecureStoreException(String.format("%s is not a valid jar", secureStore), e);
}
}
public static class SiteRun {
public final ConsoleUI ui;
public final SitePaths site;
public final InitFlags flags;
final NoteDbSchemaUpdater noteDbSchemaUpdater;
final GitRepositoryManager repositoryManager;
@Inject
SiteRun(
ConsoleUI ui,
SitePaths site,
InitFlags flags,
NoteDbSchemaUpdater noteDbSchemaUpdater,
GitRepositoryManager repositoryManager) {
this.ui = ui;
this.site = site;
this.flags = flags;
this.noteDbSchemaUpdater = noteDbSchemaUpdater;
this.repositoryManager = repositoryManager;
}
void upgradeSchema() {
noteDbSchemaUpdater.update(new UpdateUIImpl(ui));
}
private static class UpdateUIImpl implements UpdateUI {
private final ConsoleUI consoleUi;
UpdateUIImpl(ConsoleUI consoleUi) {
this.consoleUi = consoleUi;
}
@Override
public void message(String message) {
System.err.println(message);
System.err.flush();
}
@Override
public boolean yesno(boolean defaultValue, String message) {
return consoleUi.yesno(defaultValue, message);
}
@Override
public void waitForUser() {
consoleUi.waitForUser();
}
@Override
public String readString(String defaultValue, Set<String> allowedValues, String message) {
return consoleUi.readString(defaultValue, allowedValues, message);
}
@Override
public boolean isBatch() {
return consoleUi.isBatch();
}
}
}
private SiteRun createSiteRun(SiteInit init) {
return createSysInjector(init).getInstance(SiteRun.class);
}
private Injector createSysInjector(SiteInit init) {
if (sysInjector == null) {
final List<Module> modules = new ArrayList<>();
modules.add(
new AbstractModule() {
@Override
protected void configure() {
bind(ConsoleUI.class).toInstance(init.ui);
bind(InitFlags.class).toInstance(init.flags);
}
});
Injector dbInjector = createDbInjector();
IndexType indexType = IndexModule.getIndexType(dbInjector);
if (indexType.isLucene()) {
modules.add(new LuceneIndexModuleOnInit());
} else if (indexType.isFake()) {
try {
Class<?> clazz = Class.forName("com.google.gerrit.index.testing.FakeIndexModuleOnInit");
Module indexOnInitModule = (Module) clazz.getDeclaredConstructor().newInstance();
modules.add(indexOnInitModule);
} catch (InstantiationException
| IllegalAccessException
| ClassNotFoundException
| NoSuchMethodException
| InvocationTargetException e) {
throw new IllegalStateException("unable to create fake index", e);
}
modules.add(new IndexModuleOnInit());
} else {
throw new IllegalStateException("unsupported index.type = " + indexType);
}
sysInjector = dbInjector.createChildInjector(modules);
}
return sysInjector;
}
private static void recursiveDelete(Path path) {
final String msg = "warn: Cannot remove ";
try {
Files.walkFileTree(
path,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path f, BasicFileAttributes attrs) throws IOException {
try {
Files.delete(f);
} catch (IOException e) {
System.err.println(msg + f);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException err) {
try {
// Previously warned if err was not null; if dir is not empty as a
// result, will cause an error that will be logged below.
Files.delete(dir);
} catch (IOException e) {
System.err.println(msg + dir);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path f, IOException e) {
System.err.println(msg + f);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
System.err.println(msg + path);
}
}
protected boolean isDev() {
return false;
}
protected boolean getDeleteCaches() {
return false;
}
}