| // 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; |
| } |
| } |