init: Refactor init to be small parts created through Guice

I was nuts when I wrote init.  Making it all one giant program
in a single class was simply insane.  Instead we break it down
into many smaller classes and use Guice to manage the creation,
dependency injection, and control flow.

Since init knew about most of the Files under the $site_path we put
them all into a single immutable class called SitePaths and replace
all references to these throughout the code base to use SitePaths
and these well defined constants.

init can now also import GerritServer.properties into the newer
style gerrit.config and secure.config.  This ensure the database
settings are setup with the current defaults

Change-Id: I4f5d8256497c1a97df35754dbe6193c78edde9e1
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce 2009-12-17 12:16:45 -08:00
parent 2fdaabd50e
commit bbf1aaccde
42 changed files with 2506 additions and 1404 deletions

View File

@ -15,7 +15,7 @@
package com.google.gerrit.httpd;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
@ -33,7 +33,7 @@ public class GitWebConfig {
private final File git_logo_png;
@Inject
GitWebConfig(@SitePath final File sitePath,
GitWebConfig(final SitePaths sitePaths,
@GerritServerConfig final Config cfg) {
final String cfgUrl = cfg.getString("gitweb", null, "url");
final String cfgCgi = cfg.getString("gitweb", null, "cgi");
@ -65,10 +65,7 @@ public class GitWebConfig {
// Use the CGI script configured by the administrator, failing if it
// cannot be used as specified.
//
cgi = new File(cfgCgi);
if (!cgi.isAbsolute()) {
cgi = new File(sitePath, cfgCgi).getAbsoluteFile();
}
cgi = sitePaths.resolve(cfgCgi);
if (!cgi.isFile()) {
throw new IllegalStateException("Cannot find gitweb.cgi: " + cgi);
}

View File

@ -31,7 +31,7 @@ package com.google.gerrit.httpd.gitweb;
import com.google.gerrit.httpd.GitWebConfig;
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.gwt.user.server.rpc.RPCServletUtils;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@ -50,13 +50,8 @@ abstract class GitWebCssServlet extends HttpServlet {
@Singleton
static class Site extends GitWebCssServlet {
@Inject
Site(@SitePath File sp, GitWebConfig gwc) throws IOException {
super(sp, gwc);
}
@Override
protected File path(File etc, GitWebConfig gitWebConfig) {
return new File(etc, "GerritSite.css");
Site(SitePaths paths, GitWebConfig gwc) throws IOException {
super(paths.site_css, gwc);
}
}
@ -64,13 +59,8 @@ abstract class GitWebCssServlet extends HttpServlet {
@Singleton
static class Default extends GitWebCssServlet {
@Inject
Default(@SitePath File sp, GitWebConfig gwc) throws IOException {
super(sp, gwc);
}
@Override
protected File path(File etc, GitWebConfig gitWebConfig) {
return gitWebConfig.getGitwebCSS();
Default(GitWebConfig gwc) throws IOException {
super(gwc.getGitwebCSS(), gwc);
}
}
@ -78,9 +68,8 @@ abstract class GitWebCssServlet extends HttpServlet {
private final byte[] raw_css;
private final byte[] gz_css;
GitWebCssServlet(@SitePath final File sitePath,
final GitWebConfig gitWebConfig) throws IOException {
final File src = path(new File(sitePath, "etc"), gitWebConfig);
GitWebCssServlet(final File src, final GitWebConfig gitWebConfig)
throws IOException {
final File dir = src.getParentFile();
final String name = src.getName();
final String raw = HtmlDomUtil.readFile(dir, name);
@ -93,8 +82,6 @@ abstract class GitWebCssServlet extends HttpServlet {
}
}
protected abstract File path(File sitePath, GitWebConfig gitWebConfig);
@Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {

View File

@ -33,7 +33,7 @@ import com.google.gerrit.common.data.GerritConfig;
import com.google.gerrit.httpd.GitWebConfig;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.IdentifiedUser;
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.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
@ -84,7 +84,7 @@ class GitWebServlet extends HttpServlet {
@Inject
GitWebServlet(final GitRepositoryManager repoManager,
final ProjectControl.Factory projectControl,
@SitePath final File sitePath, final GerritConfig gerritConfig,
final SitePaths site, final GerritConfig gerritConfig,
final GitWebConfig gitWebConfig) throws IOException {
this.repoManager = repoManager;
this.projectControl = projectControl;
@ -97,7 +97,7 @@ class GitWebServlet extends HttpServlet {
deniedActions.add("project_index");
_env = new EnvList();
makeSiteConfig(sitePath, gerritConfig);
makeSiteConfig(site, gerritConfig);
if (!_env.envMap.containsKey("SystemRoot")) {
String os = System.getProperty("os.name");
@ -115,7 +115,7 @@ class GitWebServlet extends HttpServlet {
}
}
private void makeSiteConfig(final File sitePath,
private void makeSiteConfig(final SitePaths site,
final GerritConfig gerritConfig) throws IOException {
final File myconf = File.createTempFile("gitweb_config_", ".perl");
@ -133,7 +133,6 @@ class GitWebServlet extends HttpServlet {
_env.set("GIT_DIR", ".");
_env.set("GITWEB_CONFIG", myconf.getAbsolutePath());
final File etc = new File(sitePath, "etc");
final PrintWriter p = new PrintWriter(new FileWriter(myconf));
try {
p.print("# Autogenerated by Gerrit Code Review \n");
@ -143,11 +142,11 @@ class GitWebServlet extends HttpServlet {
// We are mounted at the same level in the context as the main
// UI, so we can include the same header and footer scheme.
//
final File hdr = new File(etc, "GerritSiteHeader.html");
final File hdr = site.site_header;
if (hdr.isFile()) {
p.print("$site_header = " + quoteForPerl(hdr) + ";\n");
}
final File ftr = new File(etc, "GerritSiteFooter.html");
final File ftr = site.site_footer;
if (ftr.isFile()) {
p.print("$site_footer = " + quoteForPerl(ftr) + ";\n");
}
@ -160,7 +159,7 @@ class GitWebServlet extends HttpServlet {
p.print("$favicon = 'favicon.ico';\n");
p.print("$logo = 'gitweb-logo.png';\n");
p.print("@stylesheets = ('gitweb-default.css');\n");
final File css = new File(etc, "GerritSite.css");
final File css = site.site_css;
if (css.isFile()) {
p.print("push @stylesheets, 'gitweb-site.css';\n");
}
@ -210,7 +209,7 @@ class GitWebServlet extends HttpServlet {
// If the administrator has created a site-specific gitweb_config,
// load that before we perform any final overrides.
//
final File sitecfg = new File(etc, "gitweb_config.perl");
final File sitecfg = site.site_gitweb;
if (sitecfg.isFile()) {
p.print("$GITWEB_CONFIG = " + quoteForPerl(sitecfg) + ";\n");
p.print("if (-e $GITWEB_CONFIG) {\n");

View File

@ -20,7 +20,7 @@ import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.gwt.user.server.rpc.RPCServletUtils;
import com.google.gwtjsonrpc.server.JsonServlet;
import com.google.inject.Inject;
@ -55,10 +55,9 @@ public class HostPageServlet extends HttpServlet {
private final Document hostDoc;
@Inject
HostPageServlet(final Provider<CurrentUser> cu,
@SitePath final File sitePath, final GerritConfig gc,
@GerritServerConfig final Config cfg, final ServletContext servletContext)
throws IOException {
HostPageServlet(final Provider<CurrentUser> cu, final SitePaths site,
final GerritConfig gc, @GerritServerConfig final Config cfg,
final ServletContext servletContext) throws IOException {
currentUser = cu;
config = gc;
@ -68,15 +67,14 @@ public class HostPageServlet extends HttpServlet {
throw new FileNotFoundException("No " + pageName + " in webapp");
}
final File etc = new File(sitePath, "etc");
fixModuleReference(hostDoc, servletContext);
injectCssFile(hostDoc, "gerrit_sitecss", etc, "GerritSite.css");
injectXmlFile(hostDoc, "gerrit_header", etc, "GerritSiteHeader.html");
injectXmlFile(hostDoc, "gerrit_footer", etc, "GerritSiteFooter.html");
injectCssFile(hostDoc, "gerrit_sitecss", site.site_css);
injectXmlFile(hostDoc, "gerrit_header", site.site_header);
injectXmlFile(hostDoc, "gerrit_footer", site.site_footer);
}
private void injectXmlFile(final Document hostDoc, final String id,
final File etc, final String fileName) throws IOException {
final File src) throws IOException {
final Element banner = HtmlDomUtil.find(hostDoc, id);
if (banner == null) {
return;
@ -86,7 +84,7 @@ public class HostPageServlet extends HttpServlet {
banner.removeChild(banner.getFirstChild());
}
final Document html = HtmlDomUtil.parseFile(etc, fileName);
Document html = HtmlDomUtil.parseFile(src.getParentFile(), src.getName());
if (html == null) {
banner.getParentNode().removeChild(banner);
return;
@ -97,7 +95,7 @@ public class HostPageServlet extends HttpServlet {
}
private void injectCssFile(final Document hostDoc, final String id,
final File etc, final String fileName) throws IOException {
final File src) throws IOException {
final Element banner = HtmlDomUtil.find(hostDoc, id);
if (banner == null) {
return;
@ -107,7 +105,7 @@ public class HostPageServlet extends HttpServlet {
banner.removeChild(banner.getFirstChild());
}
final String css = HtmlDomUtil.readFile(etc, fileName);
final String css = HtmlDomUtil.readFile(src.getParentFile(), src.getName());
if (css == null) {
banner.getParentNode().removeChild(banner);
return;

View File

@ -14,7 +14,7 @@
package com.google.gerrit.httpd.raw;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.gwt.user.server.rpc.RPCServletUtils;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@ -91,8 +91,8 @@ public class StaticServlet extends HttpServlet {
private final File staticBase;
@Inject
StaticServlet(@SitePath final File sitePath) {
staticBase = new File(sitePath, "static");
StaticServlet(final SitePaths site) {
staticBase = site.static_dir;
}
private File local(final HttpServletRequest req) {

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,7 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.gerrit.lifecycle.LifecycleListener;
import com.google.gerrit.main.GerritLauncher;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
@ -92,17 +92,16 @@ public class JettyServer {
}
}
private final File sitePath;
private final SitePaths site;
private final Server httpd;
/** Location on disk where our WAR file was unpacked to. */
private Resource baseResource;
@Inject
JettyServer(@GerritServerConfig final Config cfg,
@SitePath final File sitePath, final JettyEnv env)
throws MalformedURLException, IOException {
this.sitePath = sitePath;
JettyServer(@GerritServerConfig final Config cfg, final SitePaths site,
final JettyEnv env) throws MalformedURLException, IOException {
this.site = site;
Handler app = makeContext(env, cfg);
@ -231,12 +230,7 @@ public class JettyServer {
if (path == null || path.length() == 0) {
path = def;
}
File loc = new File(path);
if (!loc.isAbsolute()) {
loc = new File(sitePath, path);
}
return loc;
return site.resolve(path);
}
private ThreadPool threadPool(Config cfg) {

View File

@ -0,0 +1,92 @@
// Copyright (C) 2009 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 com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
import org.h2.util.StartBrowser;
import java.io.IOException;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
/** Opens the user's web browser to the web UI. */
public class Browser {
private final Config cfg;
@Inject
Browser(final @GerritServerConfig Config cfg) {
this.cfg = cfg;
}
public void open() throws IOException {
open(null /* root page */);
}
public void open(final String link) throws IOException {
String url = cfg.getString("httpd", null, "listenUrl");
if (url == null) {
return;
}
if (url.startsWith("proxy-")) {
url = url.substring("proxy-".length());
}
final URI uri;
try {
uri = InitUtil.toURI(url);
} catch (URISyntaxException e) {
System.err.println("error: invalid httpd.listenUrl: " + url);
return;
}
final String hostname = uri.getHost();
final int port = InitUtil.portOf(uri);
System.err.print("Waiting for server to start ... ");
System.err.flush();
for (;;) {
final Socket s;
try {
s = new Socket(hostname, port);
} catch (IOException e) {
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
}
continue;
}
s.close();
break;
}
System.err.println("OK");
url = cfg.getString("gerrit", null, "canonicalWebUrl");
if (url == null || url.isEmpty()) {
url = uri.toString();
}
if (!url.endsWith("/")) {
url += "/";
}
if (link != null && !link.isEmpty()) {
url += "#" + link;
}
System.err.println("Opening browser ...");
StartBrowser.openURL(url);
}
}

View File

@ -0,0 +1,84 @@
// Copyright (C) 2009 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.gerrit.pgm.init.InitUtil.dnOf;
import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.reviewdb.AuthType;
import com.google.inject.Inject;
import com.google.inject.Singleton;
/** Initialize the {@code auth} configuration section. */
@Singleton
class InitAuth implements InitStep {
private final ConsoleUI ui;
private final Section auth;
private final Section ldap;
@Inject
InitAuth(final ConsoleUI ui, final Section.Factory sections) {
this.ui = ui;
this.auth = sections.get("auth");
this.ldap = sections.get("ldap");
}
public void run() {
ui.header("User Authentication");
final AuthType auth_type =
auth.select("Authentication method", "type", AuthType.OPENID);
switch (auth_type) {
case HTTP:
case HTTP_LDAP: {
String hdr = auth.get("httpHeader");
if (ui.yesno(hdr != null, "Get username from custom HTTP header")) {
auth.string("Username HTTP header", "httpHeader", "SM_USER");
} else if (hdr != null) {
auth.unset("httpHeader");
}
auth.string("SSO logout URL", "logoutUrl", null);
break;
}
}
switch (auth_type) {
case LDAP:
case HTTP_LDAP: {
String server =
ldap.string("LDAP server", "server", "ldap://localhost");
if (server != null //
&& !server.startsWith("ldap://") //
&& !server.startsWith("ldaps://")) {
if (ui.yesno(false, "Use SSL")) {
server = "ldaps://" + server;
} else {
server = "ldap://" + server;
}
ldap.set("server", server);
}
ldap.string("LDAP username", "username", null);
ldap.password("username", "password");
final String def_dn = dnOf(server);
String aBase = ldap.string("Account BaseDN", "accountBase", def_dn);
String gBase = ldap.string("Group BaseDN", "groupBase", aBase);
break;
}
}
}
}

View File

@ -0,0 +1,57 @@
// Copyright (C) 2009 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.gerrit.pgm.init.InitUtil.die;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.File;
/** Initialize the {@code cache} configuration section. */
@Singleton
class InitCache implements InitStep {
private final SitePaths site;
private final Section cache;
@Inject
InitCache(final SitePaths site, final Section.Factory sections) {
this.site = site;
this.cache = sections.get("cache");
}
public void run() {
String path = cache.get("directory");
if (path != null && path.isEmpty()) {
// Explicitly set to empty implies the administrator has
// disabled the on disk cache and doesn't want it enabled.
//
return;
}
if (path == null) {
path = "cache";
cache.set("directory", path);
}
final File loc = site.resolve(path);
if (!loc.exists() && !loc.mkdirs()) {
throw die("cannot create cache.directory " + loc.getAbsolutePath());
}
}
}

View File

@ -0,0 +1,95 @@
// Copyright (C) 2009 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.gerrit.pgm.init.InitUtil.copy;
import static com.google.gerrit.pgm.init.InitUtil.die;
import static com.google.gerrit.pgm.init.InitUtil.username;
import com.google.gerrit.main.GerritLauncher;
import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/** Initialize the {@code container} configuration section. */
@Singleton
class InitContainer implements InitStep {
private final ConsoleUI ui;
private final SitePaths site;
private final Section container;
@Inject
InitContainer(final ConsoleUI ui, final SitePaths site,
final Section.Factory sections) {
this.ui = ui;
this.site = site;
this.container = sections.get("container");
}
public void run() throws FileNotFoundException, IOException {
ui.header("Container Process");
container.string("Run as", "user", username());
container.string("Java runtime", "javaHome", javaHome());
File myWar;
try {
myWar = GerritLauncher.getDistributionArchive();
} catch (FileNotFoundException e) {
System.err.println("warn: Cannot find gerrit.war");
myWar = null;
}
String path = container.get("war");
if (path != null) {
path = container.string("Gerrit runtime", "war", //
myWar != null ? myWar.getAbsolutePath() : null);
if (path == null || path.isEmpty()) {
throw die("container.war is required");
}
} else if (myWar != null) {
final boolean copy;
final File siteWar = site.gerrit_war;
if (siteWar.exists()) {
copy = ui.yesno(true, "Upgrade %s", siteWar.getPath());
} else {
copy = ui.yesno(true, "Copy gerrit.war to %s", siteWar.getPath());
if (copy) {
container.unset("war");
} else {
container.set("war", myWar.getAbsolutePath());
}
}
if (copy) {
if (!ui.isBatch()) {
System.err.format("Copying gerrit.war to %s", siteWar.getPath());
System.err.println();
}
copy(siteWar, new FileInputStream(myWar));
}
}
}
private static String javaHome() {
return System.getProperty("java.home");
}
}

View File

@ -0,0 +1,105 @@
// Copyright (C) 2009 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.gerrit.pgm.init.InitUtil.die;
import static com.google.gerrit.pgm.init.InitUtil.username;
import static com.google.gerrit.pgm.util.DataSourceProvider.Type.H2;
import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.pgm.util.DataSourceProvider;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.File;
/** Initialize the {@code database} configuration section. */
@Singleton
class InitDatabase implements InitStep {
private final ConsoleUI ui;
private final SitePaths site;
private final Libraries libraries;
private final Section database;
@Inject
InitDatabase(final ConsoleUI ui, final SitePaths site, final Libraries libraries,
final Section.Factory sections) {
this.ui = ui;
this.site = site;
this.libraries = libraries;
this.database = sections.get("database");
}
public void run() {
ui.header("SQL Database");
final DataSourceProvider.Type db_type =
database.select("Database server type", "type", H2);
switch (db_type) {
case MYSQL:
libraries.mysqlDriver.downloadRequired();
break;
}
final boolean userPassAuth;
switch (db_type) {
case H2: {
userPassAuth = false;
String path = database.get("database");
if (path == null) {
path = "db/ReviewDB";
database.set("database", path);
}
File db = site.resolve(path);
if (db == null) {
throw die("database.database must be supplied for H2");
}
db = db.getParentFile();
if (!db.exists() && !db.mkdirs()) {
throw die("cannot create database.database " + db.getAbsolutePath());
}
break;
}
case JDBC: {
userPassAuth = true;
database.string("Driver class name", "driver", null);
database.string("URL", "url", null);
break;
}
case POSTGRES:
case POSTGRESQL:
case MYSQL: {
userPassAuth = true;
final String defPort = "(" + db_type.toString() + " default)";
database.string("Server hostname", "hostname", "localhost");
database.string("Server port", "port", defPort, true);
database.string("Database name", "database", "reviewdb");
break;
}
default:
throw die("internal bug, database " + db_type + " not supported");
}
if (userPassAuth) {
database.string("Database username", "username", username());
database.password("username", "password");
}
}
}

View File

@ -0,0 +1,49 @@
// Copyright (C) 2009 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 com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.FileBasedConfig;
import java.io.IOException;
/** Global variables used by the 'init' command. */
@Singleton
public class InitFlags {
/** Recursively delete the site path if initialization fails. */
public boolean deleteOnFailure;
/** Run the Git project importer after initialization. */
public boolean importProjects;
/** Run the daemon (and open the web UI in a browser) after initialization. */
public boolean autoStart;
public final FileBasedConfig cfg;
public final FileBasedConfig sec;
@Inject
InitFlags(final SitePaths site) throws IOException, ConfigInvalidException {
cfg = new FileBasedConfig(site.gerrit_config);
sec = new FileBasedConfig(site.secure_config);
cfg.load();
sec.load();
}
}

View File

@ -0,0 +1,55 @@
// Copyright (C) 2009 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.gerrit.pgm.init.InitUtil.die;
import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.File;
/** Initialize the GitRepositoryManager configuration section. */
@Singleton
class InitGitManager implements InitStep {
private final InitFlags flags;
private final ConsoleUI ui;
private final Section gerrit;
@Inject
InitGitManager(final InitFlags flags, final ConsoleUI ui,
final Section.Factory sections) {
this.flags = flags;
this.ui = ui;
this.gerrit = sections.get("gerrit");
}
public void run() {
ui.header("Git Repositories");
File d = gerrit.path("Location of Git repositories", "basePath", "git");
if (d == null) {
throw die("gerrit.basePath is required");
}
if (d.exists()) {
if (!flags.importProjects && d.list() != null && d.list().length > 0) {
flags.importProjects = ui.yesno(true, "Import existing repositories");
}
} else if (!d.mkdirs()) {
throw die("Cannot create " + d);
}
}
}

View File

@ -0,0 +1,203 @@
// Copyright (C) 2009 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.gerrit.pgm.init.InitUtil.chmod;
import static com.google.gerrit.pgm.init.InitUtil.die;
import static com.google.gerrit.pgm.init.InitUtil.domainOf;
import static com.google.gerrit.pgm.init.InitUtil.isAnyAddress;
import static com.google.gerrit.pgm.init.InitUtil.toURI;
import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.reviewdb.AuthType;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.SitePaths;
import com.google.gwtjsonrpc.server.SignedToken;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
/** Initialize the {@code httpd} configuration section. */
@Singleton
class InitHttpd implements InitStep {
private final ConsoleUI ui;
private final SitePaths site;
private final InitFlags flags;
private final Section httpd;
private final Section gerrit;
@Inject
InitHttpd(final ConsoleUI ui, final SitePaths site, final InitFlags flags,
final Section.Factory sections) {
this.ui = ui;
this.site = site;
this.flags = flags;
this.httpd = sections.get("httpd");
this.gerrit = sections.get("gerrit");
}
public void run() throws IOException, InterruptedException {
ui.header("HTTP Daemon");
boolean proxy = false, ssl = false;
String address = "*";
int port = -1;
String context = "/";
String listenUrl = httpd.get("listenUrl");
if (listenUrl != null && !listenUrl.isEmpty()) {
try {
final URI uri = toURI(listenUrl);
proxy = uri.getScheme().startsWith("proxy-");
ssl = uri.getScheme().endsWith("https");
address = isAnyAddress(new URI(listenUrl)) ? "*" : uri.getHost();
port = uri.getPort();
context = uri.getPath();
} catch (URISyntaxException e) {
System.err.println("warning: invalid httpd.listenUrl " + listenUrl);
}
}
proxy = ui.yesno(proxy, "Behind reverse proxy");
if (proxy) {
ssl = ui.yesno(ssl, "Proxy uses SSL (https://)");
context = ui.readString(context, "Subdirectory on proxy server");
} else {
ssl = ui.yesno(ssl, "Use SSL (https://)");
context = "/";
}
address = ui.readString(address, "Listen on address");
if (port < 0) {
if (proxy) {
port = 8081;
} else if (ssl) {
port = 8443;
} else {
port = 8080;
}
}
port = ui.readInt(port, "Listen on port");
final StringBuilder urlbuf = new StringBuilder();
urlbuf.append(proxy ? "proxy-" : "");
urlbuf.append(ssl ? "https" : "http");
urlbuf.append("://");
urlbuf.append(address);
if (0 <= port) {
urlbuf.append(":");
urlbuf.append(port);
}
urlbuf.append(context);
httpd.set("listenUrl", urlbuf.toString());
URI uri;
try {
uri = toURI(httpd.get("listenUrl"));
if (uri.getScheme().startsWith("proxy-")) {
// If its a proxy URL, assume the reverse proxy is on our system
// at the protocol standard ports (so omit the ports from the URL).
//
String s = uri.getScheme().substring("proxy-".length());
uri = new URI(s + "://" + uri.getHost() + uri.getPath());
}
} catch (URISyntaxException e) {
throw die("invalid httpd.listenUrl");
}
if (gerrit.get("canonicalWebUrl") != null //
|| (!proxy && ssl) //
|| getAuthType() == AuthType.OPENID) {
gerrit.string("Canonical URL", "canonicalWebUrl", uri.toString());
}
generateSslCertificate();
}
private void generateSslCertificate() throws IOException,
InterruptedException {
final String listenUrl = httpd.get("listenUrl");
if (!listenUrl.startsWith("https://")) {
// We aren't responsible for SSL processing.
//
return;
}
String hostname;
try {
String url = gerrit.get("canonicalWebUrl");
if (url == null || url.isEmpty()) {
url = listenUrl;
}
hostname = toURI(url).getHost();
} catch (URISyntaxException e) {
System.err.println("Invalid httpd.listenUrl, not checking certificate");
return;
}
final File store = site.ssl_keystore;
if (!ui.yesno(!store.exists(), "Create new self-signed SSL certificate")) {
return;
}
String ssl_pass = flags.sec.getString("http", null, "sslKeyPassword");
if (ssl_pass == null || ssl_pass.isEmpty()) {
ssl_pass = SignedToken.generateRandomKey();
flags.sec.setString("httpd", null, "sslKeyPassword", ssl_pass);
}
hostname = ui.readString(hostname, "Certificate server name");
final String validity =
ui.readString("365", "Certificate expires in (days)");
final String dname =
"CN=" + hostname + ",OU=Gerrit Code Review,O=" + domainOf(hostname);
final File tmpdir = new File(site.etc_dir, "tmp.sslcertgen");
if (!tmpdir.mkdir()) {
throw die("Cannot create directory " + tmpdir);
}
chmod(0600, tmpdir);
final File tmpstore = new File(tmpdir, "keystore");
Runtime.getRuntime().exec(new String[] {"keytool", //
"-keystore", tmpstore.getAbsolutePath(), //
"-storepass", ssl_pass, //
"-genkeypair", //
"-alias", hostname, //
"-keyalg", "RSA", //
"-validity", validity, //
"-dname", dname, //
"-keypass", ssl_pass, //
}).waitFor();
chmod(0600, tmpstore);
if (!tmpstore.renameTo(store)) {
throw die("Cannot rename " + tmpstore + " to " + store);
}
if (!tmpdir.delete()) {
throw die("Cannot delete " + tmpdir);
}
}
private AuthType getAuthType() {
return ConfigUtil.getEnum(flags.cfg, "auth", null, "type", AuthType.OPENID);
}
}

View File

@ -0,0 +1,52 @@
// Copyright (C) 2009 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 com.google.gerrit.server.config.FactoryModule;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.internal.UniqueAnnotations;
import java.lang.annotation.Annotation;
/** Injection configuration for the site initialization process. */
public class InitModule extends FactoryModule {
@Override
protected void configure() {
bind(SitePaths.class);
bind(InitFlags.class);
bind(Libraries.class);
bind(LibraryDownloader.class);
factory(Section.Factory.class);
// Steps are executed in the order listed here.
//
step().to(UpgradeFrom2_0_x.class);
step().to(InitGitManager.class);
step().to(InitDatabase.class);
step().to(InitAuth.class);
step().to(InitSendEmail.class);
step().to(InitContainer.class);
step().to(InitSshd.class);
step().to(InitHttpd.class);
step().to(InitCache.class);
}
protected LinkedBindingBuilder<InitStep> step() {
final Annotation id = UniqueAnnotations.create();
return bind(InitStep.class).annotatedWith(id);
}
}

View File

@ -0,0 +1,58 @@
// Copyright (C) 2009 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.gerrit.pgm.init.InitUtil.isLocal;
import static com.google.gerrit.pgm.init.InitUtil.username;
import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.mail.SmtpEmailSender.Encryption;
import com.google.inject.Inject;
import com.google.inject.Singleton;
/** Initialize the {@code sendemail} configuration section. */
@Singleton
class InitSendEmail implements InitStep {
private final ConsoleUI ui;
private final Section sendemail;
@Inject
InitSendEmail(final ConsoleUI ui, final SitePaths site,
final Section.Factory sections) {
this.ui = ui;
this.sendemail = sections.get("sendemail");
}
public void run() {
ui.header("Email Delivery");
final String hostname =
sendemail.string("SMTP server hostname", "smtpServer", "localhost");
sendemail.string("SMTP server port", "smtpServerPort", "(default)", true);
final Encryption enc =
sendemail.select("SMTP encryption", "smtpEncryption", Encryption.NONE,
true);
String username = null;
if ((enc != null && enc != Encryption.NONE) || !isLocal(hostname)) {
username = username();
}
sendemail.string("SMTP username", "smtpUser", username);
sendemail.password("smtpUser", "smtpPass");
}
}

View File

@ -0,0 +1,142 @@
// Copyright (C) 2009 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.gerrit.pgm.init.InitUtil.chmod;
import static com.google.gerrit.pgm.init.InitUtil.die;
import static com.google.gerrit.pgm.init.InitUtil.hostname;
import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.util.SocketUtil;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.apache.sshd.common.util.SecurityUtils;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
/** Initialize the {@code sshd} configuration section. */
@Singleton
class InitSshd implements InitStep {
private final ConsoleUI ui;
private final SitePaths site;
private final Libraries libraries;
private final Section sshd;
@Inject
InitSshd(final ConsoleUI ui, final SitePaths site, final Libraries libraries,
final Section.Factory sections) {
this.ui = ui;
this.site = site;
this.libraries = libraries;
this.sshd = sections.get("sshd");
}
public void run() throws Exception {
ui.header("SSH Daemon");
String hostname = "*";
int port = 29418;
String listenAddress = sshd.get("listenAddress");
if (listenAddress != null && !listenAddress.isEmpty()) {
final InetSocketAddress addr = SocketUtil.parse(listenAddress, port);
hostname = SocketUtil.hostname(addr);
port = addr.getPort();
}
hostname = ui.readString(hostname, "Listen on address");
port = ui.readInt(port, "Listen on port");
sshd.set("listenAddress", SocketUtil.format(hostname, port));
if (site.ssh_rsa.exists() || site.ssh_dsa.exists()) {
libraries.bouncyCastle.downloadRequired();
} else {
libraries.bouncyCastle.downloadOptional();
}
generateSshHostKeys();
}
private void generateSshHostKeys() throws InterruptedException, IOException {
if (!site.ssh_key.exists() //
&& !site.ssh_rsa.exists() //
&& !site.ssh_dsa.exists()) {
System.err.print("Generating SSH host key ...");
System.err.flush();
if (SecurityUtils.isBouncyCastleRegistered()) {
// Generate the SSH daemon host key using ssh-keygen.
//
final String comment = "gerrit-code-review@" + hostname();
System.err.print(" rsa...");
System.err.flush();
Runtime.getRuntime().exec(new String[] {"ssh-keygen", //
"-q" /* quiet */, //
"-t", "rsa", //
"-P", "", //
"-C", comment, //
"-f", site.ssh_rsa.getAbsolutePath() //
}).waitFor();
System.err.print(" dsa...");
System.err.flush();
Runtime.getRuntime().exec(new String[] {"ssh-keygen", //
"-q" /* quiet */, //
"-t", "dsa", //
"-P", "", //
"-C", comment, //
"-f", site.ssh_dsa.getAbsolutePath() //
}).waitFor();
} else {
// Generate the SSH daemon host key ourselves. This is complex
// because SimpleGeneratorHostKeyProvider doesn't mark the data
// file as only readable by us, exposing the private key for a
// short period of time. We try to reduce that risk by creating
// the key within a temporary directory.
//
final File tmpdir = new File(site.etc_dir, "tmp.sshkeygen");
if (!tmpdir.mkdir()) {
throw die("Cannot create directory " + tmpdir);
}
chmod(0600, tmpdir);
final File tmpkey = new File(tmpdir, site.ssh_key.getName());
final SimpleGeneratorHostKeyProvider p;
System.err.print(" rsa(simple)...");
System.err.flush();
p = new SimpleGeneratorHostKeyProvider();
p.setPath(tmpkey.getAbsolutePath());
p.setAlgorithm("RSA");
p.loadKeys(); // forces the key to generate.
chmod(0600, tmpkey);
if (!tmpkey.renameTo(site.ssh_key)) {
throw die("Cannot rename " + tmpkey + " to " + site.ssh_key);
}
if (!tmpdir.delete()) {
throw die("Cannot delete " + tmpdir);
}
}
System.err.println(" done");
}
}
}

View File

@ -0,0 +1,20 @@
// Copyright (C) 2009 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;
/** A single step in the site initialization process. */
public interface InitStep {
public void run() throws Exception;
}

View File

@ -0,0 +1,214 @@
// Copyright (C) 2009 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 com.google.gerrit.pgm.util.Die;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileBasedConfig;
import org.eclipse.jgit.lib.LockFile;
import org.eclipse.jgit.util.SystemReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
/** Utility functions to help initialize a site. */
class InitUtil {
static Die die(String why) {
return new Die(why);
}
static Die die(String why, Throwable cause) {
return new Die(why, cause);
}
static void savePublic(final FileBasedConfig sec) throws IOException {
sec.save();
}
static void saveSecure(final FileBasedConfig sec) throws IOException {
final byte[] out = Constants.encode(sec.toText());
final File path = sec.getFile();
final LockFile lf = new LockFile(path);
if (!lf.lock()) {
throw new IOException("Cannot lock " + path);
}
try {
chmod(0600, new File(path.getParentFile(), path.getName() + ".lock"));
lf.write(out);
if (!lf.commit()) {
throw new IOException("Cannot commit write to " + path);
}
} finally {
lf.unlock();
}
}
static void mkdir(final File path) {
if (!path.isDirectory() && !path.mkdir()) {
throw die("Cannot make directory " + path);
}
}
static void chmod(final int mode, final File path) {
path.setReadable(false, false /* all */);
path.setWritable(false, false /* all */);
path.setExecutable(false, false /* all */);
path.setReadable((mode & 0400) == 0400, true /* owner only */);
path.setWritable((mode & 0200) == 0200, true /* owner only */);
if (path.isDirectory() || (mode & 0100) == 0100) {
path.setExecutable(true, true /* owner only */);
}
if ((mode & 0044) == 0044) {
path.setReadable(true, false /* all */);
}
if ((mode & 0011) == 0011) {
path.setExecutable(true, false /* all */);
}
}
static String version() {
return com.google.gerrit.common.Version.getVersion();
}
static String username() {
return System.getProperty("user.name");
}
static String hostname() {
return SystemReader.getInstance().getHostname();
}
static boolean isLocal(final String hostname) {
try {
return InetAddress.getByName(hostname).isLoopbackAddress();
} catch (UnknownHostException e) {
return false;
}
}
static String dnOf(String name) {
if (name != null) {
int p = name.indexOf("://");
if (0 < p) {
name = name.substring(p + 3);
}
p = name.indexOf(".");
if (0 < p) {
name = name.substring(p + 1);
name = "DC=" + name.replaceAll("\\.", ",DC=");
} else {
name = null;
}
}
return name;
}
static String domainOf(String name) {
if (name != null) {
int p = name.indexOf("://");
if (0 < p) {
name = name.substring(p + 3);
}
p = name.indexOf(".");
if (0 < p) {
name = name.substring(p + 1);
}
}
return name;
}
static void extract(final File dst, final Class<?> sibling,
final String name) throws IOException {
final InputStream in = open(sibling, name);
if (in != null) {
copy(dst, in);
}
}
private static InputStream open(final Class<?> sibling, final String name) {
final InputStream in = sibling.getResourceAsStream(name);
if (in == null) {
String pkg = sibling.getName();
int end = pkg.lastIndexOf('.');
if (0 < end) {
pkg = pkg.substring(0, end + 1);
pkg = pkg.replace('.', '/');
} else {
pkg = "";
}
System.err.println("warn: Cannot read " + pkg + name);
return null;
}
return in;
}
static void copy(final File dst, final InputStream in)
throws FileNotFoundException, IOException {
try {
dst.getParentFile().mkdirs();
final FileOutputStream out = new FileOutputStream(dst);
try {
final byte[] buf = new byte[4096];
int n;
while (0 < (n = in.read(buf))) {
out.write(buf, 0, n);
}
} finally {
out.close();
}
} finally {
in.close();
}
}
static URI toURI(String url) throws URISyntaxException {
final URI u = new URI(url);
if (isAnyAddress(u)) {
// If the URL uses * it means all addresses on this system, use the
// current hostname instead in the returned URI.
//
final int s = url.indexOf('*');
url = url.substring(0, s) + hostname() + url.substring(s + 1);
}
return new URI(url);
}
static boolean isAnyAddress(final URI u) {
return u.getHost() == null
&& (u.getAuthority().equals("*") || u.getAuthority().startsWith("*:"));
}
static int portOf(final URI uri) {
int port = uri.getPort();
if (port < 0) {
port = "https".equals(uri.getScheme()) ? 443 : 80;
}
return port;
}
private InitUtil() {
}
}

View File

@ -12,12 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.pgm.util;
package com.google.gerrit.pgm.init;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@ -27,19 +30,20 @@ import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
/** Standard {@link LibraryDownloader} instances derived from configuration. */
public class Libraries {
@Singleton
class Libraries {
private static final String RESOURCE_FILE =
"com/google/gerrit/pgm/libraries.config";
private final ConsoleUI ui;
private final File sitePath;
private final Provider<LibraryDownloader> downloadProvider;
public LibraryDownloader bouncyCastle;
public LibraryDownloader mysqlDriver;
/* final */LibraryDownloader bouncyCastle;
/* final */LibraryDownloader mysqlDriver;
@Inject
Libraries(final Provider<LibraryDownloader> downloadProvider) {
this.downloadProvider = downloadProvider;
public Libraries(final ConsoleUI ui, final File sitePath) {
this.ui = ui;
this.sitePath = sitePath;
init();
}
@ -70,7 +74,7 @@ public class Libraries {
private void init(final Field field, final Config cfg)
throws IllegalArgumentException, IllegalAccessException {
final String n = field.getName();
final LibraryDownloader dl = new LibraryDownloader(ui, sitePath);
final LibraryDownloader dl = downloadProvider.get();
dl.setName(get(cfg, n, "name"));
dl.setJarUrl(get(cfg, n, "url"));
dl.setSHA1(get(cfg, n, "sha1"));

View File

@ -12,7 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.pgm.util;
package com.google.gerrit.pgm.init;
import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.pgm.util.Die;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.util.HttpSupport;
@ -32,38 +37,43 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/** Get optional or required 3rd party library files into $site_path/lib. */
public class LibraryDownloader {
private final ConsoleUI console;
private final File libDirectory;
class LibraryDownloader {
private final ConsoleUI ui;
private final File lib_dir;
private final ReloadSiteLibrary reload;
private boolean required;
private String name;
private String jarUrl;
private String sha1;
private File dst;
public LibraryDownloader(final ConsoleUI console, final File sitePath) {
this.console = console;
this.libDirectory = new File(sitePath, "lib");
@Inject
LibraryDownloader(final ReloadSiteLibrary reload, final ConsoleUI ui,
final SitePaths site) {
this.ui = ui;
this.lib_dir = site.lib_dir;
this.reload = reload;
}
public void setName(final String name) {
void setName(final String name) {
this.name = name;
}
public void setJarUrl(final String url) {
void setJarUrl(final String url) {
this.jarUrl = url;
}
public void setSHA1(final String sha1) {
void setSHA1(final String sha1) {
this.sha1 = sha1;
}
public void downloadRequired() {
void downloadRequired() {
this.required = true;
download();
}
public void downloadOptional() {
void downloadOptional() {
this.required = false;
download();
}
@ -82,14 +92,14 @@ public class LibraryDownloader {
name = jarName;
}
dst = new File(libDirectory, jarName);
dst = new File(lib_dir, jarName);
if (!dst.exists() && shouldGet()) {
doGet();
}
}
private boolean shouldGet() {
if (console.isBatch()) {
if (ui.isBatch()) {
return required;
} else {
@ -103,13 +113,13 @@ public class LibraryDownloader {
msg.append(" in the library, but will also function without it.\n");
}
msg.append("Download and install it now");
return console.yesno(true, msg.toString(), name);
return ui.yesno(true, msg.toString(), name);
}
}
private void doGet() {
if (!libDirectory.exists() && !libDirectory.mkdirs()) {
throw new Die("Cannot create " + libDirectory);
if (!lib_dir.exists() && !lib_dir.mkdirs()) {
throw new Die("Cannot create " + lib_dir);
}
try {
@ -118,7 +128,7 @@ public class LibraryDownloader {
} catch (IOException err) {
dst.delete();
if (console.isBatch()) {
if (ui.isBatch()) {
throw new Die("error: Cannot get " + jarUrl, err);
}
@ -135,15 +145,17 @@ public class LibraryDownloader {
System.err.println();
System.err.flush();
console.waitForUser();
ui.waitForUser();
if (dst.exists()) {
verifyFileChecksum();
} else if (!console.yesno(!required, "Continue without this library")) {
} else if (!ui.yesno(!required, "Continue without this library")) {
throw new Die("aborted by user");
}
}
reload.reload();
}
private void doGetByHttp() throws IOException {
@ -212,11 +224,11 @@ public class LibraryDownloader {
System.err.println("Checksum " + dst.getName() + " OK");
System.err.flush();
} else if (console.isBatch()) {
} else if (ui.isBatch()) {
dst.delete();
throw new Die(dst + " SHA-1 checksum does not match");
} else if (!console.yesno(null /* force an answer */,
} else if (!ui.yesno(null /* force an answer */,
"error: SHA-1 checksum does not match\n" + "Use %s anyway",//
dst.getName())) {
dst.delete();

View File

@ -0,0 +1,20 @@
// Copyright (C) 2009 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;
/** Requests the site's {@code lib/} directory be scanned again. */
public interface ReloadSiteLibrary {
public void reload();
}

View File

@ -0,0 +1,167 @@
// Copyright (C) 2009 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 com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
/** Helper to edit a section of the configuration files. */
class Section {
interface Factory {
Section get(String name);
}
private final InitFlags flags;
private final SitePaths site;
private final ConsoleUI ui;
private final String section;
@Inject
Section(final InitFlags flags, final SitePaths site, final ConsoleUI ui,
@Assisted final String section) {
this.flags = flags;
this.site = site;
this.ui = ui;
this.section = section;
}
String get(String name) {
return flags.cfg.getString(section, null, name);
}
void set(final String name, final String value) {
final ArrayList<String> all = new ArrayList<String>();
all.addAll(Arrays.asList(flags.cfg.getStringList(section, null, name)));
if (value != null) {
if (all.size() == 0 || all.size() == 1) {
flags.cfg.setString(section, null, name, value);
} else {
all.set(0, value);
flags.cfg.setStringList(section, null, name, all);
}
} else if (all.size() == 0) {
} else if (all.size() == 1) {
flags.cfg.unset(section, null, name);
} else {
all.remove(0);
flags.cfg.setStringList(section, null, name, all);
}
}
<T extends Enum<?>> void set(final String name, final T value) {
if (value != null) {
set(name, value.name());
} else {
unset(name);
}
}
void unset(String name) {
set(name, (String) null);
}
String string(final String title, final String name, final String dv) {
return string(title, name, dv, false);
}
String string(final String title, final String name, final String dv,
final boolean nullIfDefault) {
final String ov = get(name);
String nv = ui.readString(ov != null ? ov : dv, "%s", title);
if (nullIfDefault && nv == dv) {
nv = null;
}
if (!eq(ov, nv)) {
set(name, nv);
}
return nv;
}
File path(final String title, final String name, final String defValue) {
return site.resolve(string(title, name, defValue));
}
<T extends Enum<?>> T select(final String title, final String name,
final T defValue) {
return select(title, name, defValue, false);
}
<T extends Enum<?>> T select(final String title, final String name,
final T defValue, final boolean nullIfDefault) {
final boolean set = get(name) != null;
T oldValue = ConfigUtil.getEnum(flags.cfg, section, null, name, defValue);
T newValue = ui.readEnum(oldValue, "%s", title);
if (nullIfDefault && newValue == defValue) {
newValue = null;
}
if (!set || oldValue != newValue) {
if (newValue != null) {
set(name, newValue);
} else {
unset(name);
}
}
return newValue;
}
String password(final String username, final String password) {
final String ov = flags.sec.getString(section, null, password);
String user = flags.sec.getString(section, null, username);
if (user == null) {
user = get(username);
}
if (user == null) {
flags.sec.unset(section, null, password);
return null;
}
if (ov != null) {
// If the user already has a password stored, try to reuse it
// rather than prompting for a whole new one.
//
if (ui.isBatch() || !ui.yesno(false, "Change %s's password", user)) {
return ov;
}
}
final String nv = ui.password("%s's password", user);
if (!eq(ov, nv)) {
if (nv != null) {
flags.sec.setString(section, null, password, nv);
} else {
flags.sec.unset(section, null, password);
}
}
return nv;
}
private static boolean eq(final String a, final String b) {
if (a == null && b == null) {
return true;
}
return a != null ? a.equals(b) : false;
}
}

View File

@ -0,0 +1,101 @@
// Copyright (C) 2009 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.gerrit.pgm.init.InitUtil.chmod;
import static com.google.gerrit.pgm.init.InitUtil.die;
import static com.google.gerrit.pgm.init.InitUtil.extract;
import static com.google.gerrit.pgm.init.InitUtil.mkdir;
import static com.google.gerrit.pgm.init.InitUtil.savePublic;
import static com.google.gerrit.pgm.init.InitUtil.saveSecure;
import static com.google.gerrit.pgm.init.InitUtil.version;
import com.google.gerrit.pgm.Init;
import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Binding;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.TypeLiteral;
import java.util.ArrayList;
import java.util.List;
/** Initialize (or upgrade) an existing site. */
public class SitePathInitializer {
private final ConsoleUI ui;
private final InitFlags flags;
private final SitePaths site;
private final List<InitStep> steps;
@Inject
public SitePathInitializer(final Injector injector, final ConsoleUI ui,
final InitFlags flags, final SitePaths site) {
this.ui = ui;
this.flags = flags;
this.site = site;
this.steps = stepsOf(injector);
}
public void run() throws Exception {
ui.header("Gerrit Code Review %s", version());
if (site.isNew) {
if (!ui.yesno(true, "Create '%s'", site.site_path.getCanonicalPath())) {
throw die("aborted by user");
}
if (!site.site_path.isDirectory() && !site.site_path.mkdirs()) {
throw die("Cannot make directory " + site.site_path);
}
flags.deleteOnFailure = true;
}
mkdir(site.bin_dir);
mkdir(site.etc_dir);
mkdir(site.lib_dir);
mkdir(site.logs_dir);
mkdir(site.static_dir);
for (InitStep step : steps) {
step.run();
}
savePublic(flags.cfg);
saveSecure(flags.sec);
if (!site.replication_config.exists()) {
site.replication_config.createNewFile();
}
extract(site.gerrit_sh, Init.class, "gerrit.sh");
chmod(0755, site.gerrit_sh);
if (!ui.isBatch()) {
System.err.println();
}
}
private static List<InitStep> stepsOf(final Injector injector) {
final ArrayList<InitStep> r = new ArrayList<InitStep>();
for (Binding<InitStep> b : all(injector)) {
r.add(b.getProvider().get());
}
return r;
}
private static List<Binding<InitStep>> all(final Injector injector) {
return injector.findBindingsByType(new TypeLiteral<InitStep>() {});
}
}

View File

@ -0,0 +1,290 @@
// Copyright (C) 2009 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.gerrit.pgm.init.InitUtil.die;
import static com.google.gerrit.pgm.init.InitUtil.savePublic;
import static com.google.gerrit.pgm.init.InitUtil.saveSecure;
import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.pgm.util.DataSourceProvider;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.util.SocketUtil;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.FileBasedConfig;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
/** Upgrade from a 2.0.x site to a 2.1 site. */
@Singleton
class UpgradeFrom2_0_x implements InitStep {
private static final String[] etcFiles = {"gerrit.config", //
"secure.config", //
"replication.config", //
"ssh_host_rsa_key", //
"ssh_host_rsa_key.pub", //
"ssh_host_dsa_key", //
"ssh_host_dsa_key.pub", //
"ssh_host_key", //
"contact_information.pub", //
"gitweb_config.perl", //
"keystore", //
"GerritSite.css", //
"GerritSiteFooter.html", //
"GerritSiteHeader.html", //
};
private final ConsoleUI ui;
private final FileBasedConfig cfg;
private final FileBasedConfig sec;
private final File site_path;
private final File etc_dir;
private final Section.Factory sections;
@Inject
UpgradeFrom2_0_x(final SitePaths site, final InitFlags flags,
final ConsoleUI ui, final Section.Factory sections) {
this.ui = ui;
this.sections = sections;
this.cfg = flags.cfg;
this.sec = flags.sec;
this.site_path = site.site_path;
this.etc_dir = site.etc_dir;
}
public boolean isNeedUpgrade() {
for (String name : etcFiles) {
if (new File(site_path, name).exists()) {
return true;
}
}
return false;
}
public void run() throws IOException, ConfigInvalidException {
if (!isNeedUpgrade()) {
return;
}
if (!ui.yesno(true, "Upgrade '%s'", site_path.getCanonicalPath())) {
throw die("aborted by user");
}
for (String name : etcFiles) {
final File src = new File(site_path, name);
final File dst = new File(etc_dir, name);
if (src.exists()) {
if (dst.exists()) {
throw die("File " + src + " would overwrite " + dst);
}
if (!src.renameTo(dst)) {
throw die("Cannot rename " + src + " to " + dst);
}
}
}
// We have to reload the configuration after the rename as
// the initial load pulled up an non-existent (and thus
// believed to be empty) file.
//
cfg.load();
sec.load();
final Properties oldprop = readGerritServerProperties();
if (oldprop != null) {
final Section database = sections.get("database");
String url = oldprop.getProperty("url");
if (url != null && !convertUrl(database, url)) {
database.set("type", DataSourceProvider.Type.JDBC);
database.set("driver", oldprop.getProperty("driver"));
database.set("url", url);
}
String username = oldprop.getProperty("user");
if (username == null || username.isEmpty()) {
username = oldprop.getProperty("username");
}
if (username != null && !username.isEmpty()) {
cfg.setString("database", null, "username", username);
}
String password = oldprop.getProperty("password");
if (password != null && !password.isEmpty()) {
sec.setString("database", null, "password", password);
}
}
String[] values;
values = cfg.getStringList("ldap", null, "password");
cfg.unset("ldap", null, "password");
sec.setStringList("ldap", null, "password", Arrays.asList(values));
values = cfg.getStringList("sendemail", null, "smtpPass");
cfg.unset("sendemail", null, "smtpPass");
sec.setStringList("sendemail", null, "smtpPass", Arrays.asList(values));
saveSecure(sec);
savePublic(cfg);
}
private boolean convertUrl(final Section database, String url)
throws UnsupportedEncodingException {
String username = null;
String password = null;
if (url.contains("?")) {
final int q = url.indexOf('?');
for (String pair : url.substring(q + 1).split("&")) {
final int eq = pair.indexOf('=');
if (0 < eq) {
return false;
}
String n = URLDecoder.decode(pair.substring(0, eq), "UTF-8");
String v = URLDecoder.decode(pair.substring(eq + 1), "UTF-8");
if ("user".equals(n) || "username".equals(n)) {
username = v;
} else if ("password".equals(n)) {
password = v;
} else {
// There is a parameter setting we don't recognize, use the
// JDBC URL format instead to preserve the configuration.
//
return false;
}
}
url = url.substring(0, q);
}
if (url.startsWith("jdbc:h2:file:")) {
url = url.substring("jdbc:h2:file:".length());
database.set("type", DataSourceProvider.Type.H2);
database.set("database", url);
return true;
}
if (url.startsWith("jdbc:postgresql://")) {
url = url.substring("jdbc:postgresql://".length());
final int sl = url.indexOf('/');
if (sl < 0) {
return false;
}
final InetSocketAddress addr = SocketUtil.parse(url.substring(0, sl), 0);
database.set("type", DataSourceProvider.Type.POSTGRESQL);
sethost(database, addr);
database.set("database", url.substring(sl + 1));
setuser(database, username, password);
return true;
}
if (url.startsWith("jdbc:postgresql:")) {
url = url.substring("jdbc:postgresql:".length());
database.set("type", DataSourceProvider.Type.POSTGRESQL);
database.set("hostname", "localhost");
database.set("database", url);
setuser(database, username, password);
return true;
}
if (url.startsWith("jdbc:mysql://")) {
url = url.substring("jdbc:mysql://".length());
final int sl = url.indexOf('/');
if (sl < 0) {
return false;
}
final InetSocketAddress addr = SocketUtil.parse(url.substring(0, sl), 0);
database.set("type", DataSourceProvider.Type.MYSQL);
sethost(database, addr);
database.set("database", url.substring(sl + 1));
setuser(database, username, password);
return true;
}
return false;
}
private void sethost(final Section database, final InetSocketAddress addr) {
database.set("hostname", SocketUtil.hostname(addr));
if (0 < addr.getPort()) {
database.set("port", String.valueOf(addr.getPort()));
}
}
private void setuser(final Section database, String username, String password) {
if (username != null && !username.isEmpty()) {
database.set("username", username);
}
if (password != null && !password.isEmpty()) {
sec.setString("database", null, "password", password);
}
}
private Properties readGerritServerProperties() throws IOException {
final Properties srvprop = new Properties();
final String name = System.getProperty("GerritServer");
File path;
if (name != null) {
path = new File(name);
} else {
path = new File(site_path, "GerritServer.properties");
if (!path.exists()) {
path = new File("GerritServer.properties");
}
}
if (path.exists()) {
try {
final InputStream in = new FileInputStream(path);
try {
srvprop.load(in);
} finally {
in.close();
}
} catch (IOException e) {
throw new IOException("Cannot read " + name, e);
}
final Properties dbprop = new Properties();
for (final Map.Entry<Object, Object> e : srvprop.entrySet()) {
final String key = (String) e.getKey();
if (key.startsWith("database.")) {
dbprop.put(key.substring("database.".length()), e.getValue());
}
}
return dbprop;
} else {
return null;
}
}
}

View File

@ -70,6 +70,18 @@ public abstract class ConsoleUI {
/** Prompt the user for a string, suggesting a default, and returning choice. */
public abstract String readString(String def, String fmt, Object... args);
/** Prompt the user for an integer value, suggesting a default. */
public int readInt(int def, String fmt, Object... args) {
for (;;) {
String p = readString(String.valueOf(def), fmt, args);
try {
return Integer.parseInt(p.trim(), 10);
} catch (NumberFormatException e) {
System.err.println("error: Invalid integer format: " + p.trim());
}
}
}
/** Prompt the user for a password, returning the string; null if blank. */
public abstract String password(String fmt, Object... args);

View File

@ -17,7 +17,7 @@ package com.google.gerrit.pgm.util;
import com.google.gerrit.lifecycle.LifecycleListener;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.gwtorm.jdbc.SimpleDataSource;
import com.google.inject.Inject;
import com.google.inject.Provider;
@ -41,9 +41,9 @@ public final class DataSourceProvider implements Provider<DataSource>,
private final DataSource ds;
@Inject
DataSourceProvider(@SitePath final File sitePath,
DataSourceProvider(final SitePaths site,
@GerritServerConfig final Config cfg, Context ctx) {
ds = open(sitePath, cfg, ctx);
ds = open(site, cfg, ctx);
}
@Override
@ -74,7 +74,7 @@ public final class DataSourceProvider implements Provider<DataSource>,
DEFAULT, JDBC, POSTGRES, POSTGRESQL, H2, MYSQL;
}
private DataSource open(final File sitePath, final Config cfg,
private DataSource open(final SitePaths site, final Config cfg,
final Context context) {
Type type = ConfigUtil.getEnum(cfg, "database", null, "type", Type.DEFAULT);
String driver = optional(cfg, "driver");
@ -151,14 +151,11 @@ public final class DataSourceProvider implements Provider<DataSource>,
database = "db/ReviewDB";
}
File db = new File(database);
if (!db.isAbsolute()) {
db = new File(sitePath, database);
try {
db = db.getCanonicalFile();
} catch (IOException e) {
db = db.getAbsoluteFile();
}
File db = site.resolve(database);
try {
db = db.getCanonicalFile();
} catch (IOException e) {
db = db.getAbsoluteFile();
}
url = pfx + db.toURI().toString();
}

View File

@ -185,8 +185,12 @@ public abstract class SiteProgram extends AbstractProgram {
}
final StringBuilder buf = new StringBuilder();
buf.append(why.getMessage());
why = why.getCause();
if (why != null) {
buf.append(why.getMessage());
why = why.getCause();
} else {
buf.append(first.getMessage());
}
while (why != null) {
buf.append("\n caused by ");
buf.append(why.toString());

View File

@ -20,7 +20,7 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.gerrit.lifecycle.LifecycleListener;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
@ -31,9 +31,9 @@ import net.sf.ehcache.config.Configuration;
import net.sf.ehcache.config.DiskStoreConfiguration;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.jgit.lib.Config;
import java.io.File;
import java.util.HashMap;
@ -64,16 +64,16 @@ public class CachePool {
}
private final Config config;
private final File sitePath;
private final SitePaths site;
private final Object lock = new Object();
private final Map<String, CacheProvider<?, ?>> caches;
private CacheManager manager;
@Inject
CachePool(@GerritServerConfig final Config cfg, @SitePath final File sitePath) {
CachePool(@GerritServerConfig final Config cfg, final SitePaths site) {
this.config = cfg;
this.sitePath = sitePath;
this.site = site;
this.caches = new HashMap<String, CacheProvider<?, ?>>();
}
@ -198,16 +198,9 @@ public class CachePool {
return;
}
String path = config.getString("cache", null, "directory");
if (path == null || path.length() == 0) {
return;
}
File loc = new File(path);
if (!loc.isAbsolute()) {
loc = new File(sitePath, path);
}
if (loc.exists() || loc.mkdirs()) {
File loc = site.resolve(config.getString("cache", null, "directory"));
if (loc == null) {
} else if (loc.exists() || loc.mkdirs()) {
if (loc.canWrite()) {
final DiskStoreConfiguration c = new DiskStoreConfiguration();
c.setPath(loc.getAbsolutePath());

View File

@ -24,6 +24,7 @@ import org.eclipse.jgit.lib.Config;
public class GerritServerConfigModule extends AbstractModule {
@Override
protected void configure() {
bind(SitePaths.class);
bind(Config.class).annotatedWith(GerritServerConfig.class).toProvider(
GerritServerConfigProvider.class).in(SINGLETON);
}

View File

@ -24,7 +24,6 @@ import org.eclipse.jgit.lib.FileBasedConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
/** Provides {@link Config} annotated with {@link GerritServerConfig}. */
@ -32,23 +31,20 @@ class GerritServerConfigProvider implements Provider<Config> {
private static final Logger log =
LoggerFactory.getLogger(GerritServerConfigProvider.class);
private final File sitePath;
private final SitePaths site;
@Inject
GerritServerConfigProvider(@SitePath final File path) {
sitePath = path;
GerritServerConfigProvider(final SitePaths site) {
this.site = site;
}
@Override
public Config get() {
final File etc = new File(sitePath, "etc");
final File gerrit_config = new File(etc, "gerrit.config");
final File secure_config = new File(etc, "secure.config");
FileBasedConfig cfg = new FileBasedConfig(gerrit_config);
FileBasedConfig cfg = new FileBasedConfig(site.gerrit_config);
if (!cfg.getFile().exists()) {
log.info("No " + gerrit_config.getAbsolutePath() + "; assuming defaults");
log.info("No " + site.gerrit_config.getAbsolutePath()
+ "; assuming defaults");
return cfg;
}
@ -60,8 +56,8 @@ class GerritServerConfigProvider implements Provider<Config> {
throw new ProvisionException(e.getMessage(), e);
}
if (secure_config.exists()) {
cfg = new FileBasedConfig(cfg, secure_config);
if (site.secure_config.exists()) {
cfg = new FileBasedConfig(cfg, site.secure_config);
try {
cfg.load();
} catch (IOException e) {

View File

@ -0,0 +1,113 @@
// Copyright (C) 2009 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.server.config;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.File;
import java.io.FileNotFoundException;
/** Important paths within a {@link SitePath}. */
@Singleton
public final class SitePaths {
public final File site_path;
public final File bin_dir;
public final File etc_dir;
public final File lib_dir;
public final File logs_dir;
public final File static_dir;
public final File gerrit_sh;
public final File gerrit_war;
public final File gerrit_config;
public final File secure_config;
public final File replication_config;
public final File contact_information_pub;
public final File ssl_keystore;
public final File ssh_key;
public final File ssh_rsa;
public final File ssh_dsa;
public final File site_css;
public final File site_header;
public final File site_footer;
public final File site_gitweb;
/** {@code true} if {@link #site_path} has not been initialized. */
public final boolean isNew;
@Inject
SitePaths(final @SitePath File sitePath) throws FileNotFoundException {
site_path = sitePath;
bin_dir = new File(site_path, "bin");
etc_dir = new File(site_path, "etc");
lib_dir = new File(site_path, "lib");
logs_dir = new File(site_path, "logs");
static_dir = new File(site_path, "static");
gerrit_sh = new File(bin_dir, "gerrit.sh");
gerrit_war = new File(bin_dir, "gerrit.war");
gerrit_config = new File(etc_dir, "gerrit.config");
secure_config = new File(etc_dir, "secure.config");
replication_config = new File(etc_dir, "replication.config");
contact_information_pub = new File(etc_dir, "contact_information.pub");
ssl_keystore = new File(etc_dir, "keystore");
ssh_key = new File(etc_dir, "ssh_host_key");
ssh_rsa = new File(etc_dir, "ssh_host_rsa_key");
ssh_dsa = new File(etc_dir, "ssh_host_dsa_key");
site_css = new File(etc_dir, "GerritSite.css");
site_header = new File(etc_dir, "GerritSiteHeader.html");
site_footer = new File(etc_dir, "GerritSiteFooter.html");
site_gitweb = new File(etc_dir, "gitweb_config.perl");
if (site_path.exists()) {
final String[] contents = site_path.list();
if (contents != null)
isNew = contents.length == 0;
else if (site_path.isDirectory())
throw new FileNotFoundException("Cannot access " + site_path);
else
throw new FileNotFoundException("Not a directory: " + site_path);
} else {
isNew = true;
}
}
/**
* Resolve an absolute or relative path.
* <p>
* Relative paths are resolved relative to the {@link #site_path}.
*
* @param path the path string to resolve. May be null.
* @return the resolved path; null if {@code path} was null or empty.
*/
public File resolve(final String path) {
if (path != null && !path.isEmpty()) {
File loc = new File(path);
if (!loc.isAbsolute()) {
loc = new File(site_path, path);
}
return loc;
}
return null;
}
}

View File

@ -16,7 +16,7 @@ package com.google.gerrit.server.contact;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
@ -32,14 +32,14 @@ import java.net.URL;
/** Creates the {@link ContactStore} based on the configuration. */
public class ContactStoreProvider implements Provider<ContactStore> {
private final Config config;
private final File sitePath;
private final SitePaths site;
private final SchemaFactory<ReviewDb> schema;
@Inject
ContactStoreProvider(@GerritServerConfig final Config config,
@SitePath final File sitePath, final SchemaFactory<ReviewDb> schema) {
final SitePaths site, final SchemaFactory<ReviewDb> schema) {
this.config = config;
this.sitePath = sitePath;
this.site = site;
this.schema = schema;
}
@ -63,7 +63,7 @@ public class ContactStoreProvider implements Provider<ContactStore> {
}
final String storeAPPSEC = config.getString("contactstore", null, "appsec");
final File pubkey = new File(sitePath, "etc/contact_information.pub");
final File pubkey = site.contact_information_pub;
if (!pubkey.exists()) {
throw new ProvisionException("PGP public key file \""
+ pubkey.getAbsolutePath() + "\" not found");

View File

@ -0,0 +1,109 @@
// Copyright (C) 2009 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.server.git;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.Project.SubmitType;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.RepositoryCache.FileKey;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/** Imports all projects found within the repository manager. */
public class GitProjectImporter {
public interface Messages {
void warning(String msg);
}
private final GitRepositoryManager repositoryManager;
private final SchemaFactory<ReviewDb> schema;
private Messages messages;
@Inject
GitProjectImporter(final GitRepositoryManager repositoryManager,
final SchemaFactory<ReviewDb> schema) {
this.repositoryManager = repositoryManager;
this.schema = schema;
}
public void run(final Messages msg) throws OrmException, IOException {
messages = msg;
final ReviewDb db = schema.open();
try {
final HashSet<String> have = new HashSet<String>();
for (Project p : db.projects().all()) {
have.add(p.getName());
}
importProjects(repositoryManager.getBasePath(), "", db, have);
} finally {
db.close();
}
}
private void importProjects(final File dir, final String prefix,
final ReviewDb db, final Set<String> have) throws OrmException,
IOException {
final File[] ls = dir.listFiles();
if (ls == null) {
return;
}
for (File f : ls) {
String name = f.getName();
if (".".equals(name) || "..".equals(name)) {
continue;
}
if (FileKey.isGitRepository(f)) {
if (name.equals(".git")) {
name = prefix.substring(0, prefix.length() - 1);
} else if (name.endsWith(".git")) {
name = prefix + name.substring(0, name.length() - 4);
} else {
name = prefix + name;
messages.warning("Ignoring non-standard name '" + name + "'");
continue;
}
if (have.contains(name)) {
continue;
}
final Project.NameKey nameKey = new Project.NameKey(name);
final Project.Id idKey = new Project.Id(db.nextProjectId());
final Project p = new Project(nameKey, idKey);
p.setDescription(repositoryManager.getProjectDescription(name));
p.setSubmitType(SubmitType.MERGE_IF_NECESSARY);
p.setUseContributorAgreements(false);
p.setUseSignedOffBy(false);
db.projects().insert(Collections.singleton(p));
} else if (f.isDirectory()) {
importProjects(f, prefix + f.getName() + "/", db, have);
}
}
}
}

View File

@ -16,7 +16,7 @@ package com.google.gerrit.server.git;
import com.google.gerrit.lifecycle.LifecycleListener;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@ -41,7 +41,8 @@ import java.io.IOException;
/** Class managing Git repositories. */
@Singleton
public class GitRepositoryManager {
private static final Logger log = LoggerFactory.getLogger(GitRepositoryManager.class);
private static final Logger log =
LoggerFactory.getLogger(GitRepositoryManager.class);
private static final String UNNAMED =
"Unnamed repository; edit this file to name it for gitweb.";
@ -66,28 +67,20 @@ public class GitRepositoryManager {
}
}
private final File sitePath;
private final File basepath;
private final File basePath;
@Inject
GitRepositoryManager(@SitePath final File path, @GerritServerConfig final Config cfg) {
sitePath = path;
final String basePath = cfg.getString("gerrit", null, "basepath");
if (basePath != null) {
File root = new File(basePath);
if (!root.isAbsolute()) {
root = new File(sitePath, basePath);
}
basepath = root;
} else {
basepath = null;
GitRepositoryManager(final SitePaths site,
@GerritServerConfig final Config cfg) {
basePath = site.resolve(cfg.getString("gerrit", null, "basePath"));
if (basePath == null) {
throw new IllegalStateException("gerrit.basePath must be configured");
}
}
/** @return base directory under which all projects are stored. */
public File getBasePath() {
return basepath;
return basePath;
}
/**
@ -101,16 +94,12 @@ public class GitRepositoryManager {
*/
public Repository openRepository(String name)
throws RepositoryNotFoundException {
if (basepath == null) {
throw new RepositoryNotFoundException("No gerrit.basepath configured");
}
if (isUnreasonableName(name)) {
throw new RepositoryNotFoundException("Invalid name: " + name);
}
try {
final FileKey loc = FileKey.lenient(new File(basepath, name));
final FileKey loc = FileKey.lenient(new File(basePath, name));
return RepositoryCache.open(loc);
} catch (IOException e1) {
final RepositoryNotFoundException e2;
@ -131,10 +120,6 @@ public class GitRepositoryManager {
*/
public Repository createRepository(String name)
throws RepositoryNotFoundException {
if (basepath == null) {
throw new RepositoryNotFoundException("No gerrit.basepath configured");
}
if (isUnreasonableName(name)) {
throw new RepositoryNotFoundException("Invalid name: " + name);
}
@ -143,7 +128,7 @@ public class GitRepositoryManager {
if (!name.endsWith(".git")) {
name = name + ".git";
}
final FileKey loc = FileKey.exact(new File(basepath, name));
final FileKey loc = FileKey.exact(new File(basePath, name));
return RepositoryCache.open(loc, false);
} catch (IOException e1) {
final RepositoryNotFoundException e2;
@ -242,5 +227,4 @@ public class GitRepositoryManager {
return false; // is a reasonable name
}
}

View File

@ -19,7 +19,7 @@ import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.ReplicationUser;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gwtorm.client.OrmException;
@ -48,7 +48,6 @@ import org.eclipse.jgit.util.QuotedString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URISyntaxException;
@ -86,15 +85,14 @@ public class PushReplication implements ReplicationQueue {
private final ReplicationUser.Factory replicationUserFactory;
@Inject
PushReplication(final Injector i, final WorkQueue wq,
@SitePath final File sitePath, final ReplicationUser.Factory ruf,
final SchemaFactory<ReviewDb> db) throws ConfigInvalidException,
IOException {
PushReplication(final Injector i, final WorkQueue wq, final SitePaths site,
final ReplicationUser.Factory ruf, final SchemaFactory<ReviewDb> db)
throws ConfigInvalidException, IOException {
injector = i;
workQueue = wq;
database = db;
replicationUserFactory = ruf;
configs = allConfigs(sitePath);
configs = allConfigs(site);
}
@Override
@ -129,11 +127,9 @@ public class PushReplication implements ReplicationQueue {
return pat.substring(0, n) + val + pat.substring(n + 3 + key.length());
}
private List<ReplicationConfig> allConfigs(final File path)
private List<ReplicationConfig> allConfigs(final SitePaths site)
throws ConfigInvalidException, IOException {
final File etc = new File(path, "etc");
final File cfgFile = new File(etc, "replication.config");
final FileBasedConfig cfg = new FileBasedConfig(cfgFile);
final FileBasedConfig cfg = new FileBasedConfig(site.replication_config);
if (!cfg.getFile().exists()) {
log.warn("No " + cfg.getFile() + "; not replicating");
@ -150,7 +146,8 @@ public class PushReplication implements ReplicationQueue {
throw new ConfigInvalidException("Config file " + cfg.getFile()
+ " is invalid: " + e.getMessage(), e);
} catch (IOException e) {
throw new IOException("Cannot read " + cfgFile + ": " + e.getMessage(), e);
throw new IOException("Cannot read " + cfg.getFile() + ": "
+ e.getMessage(), e);
}
final List<ReplicationConfig> r = new ArrayList<ReplicationConfig>();

View File

@ -23,6 +23,7 @@ import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.SchemaVersion;
import com.google.gerrit.reviewdb.SystemConfig;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.config.WildProjectNameProvider;
import com.google.gerrit.server.workflow.NoOpFunction;
import com.google.gerrit.server.workflow.SubmitFunction;
@ -46,16 +47,20 @@ public class SchemaCreator {
private static final Project.NameKey DEFAULT_WILD_NAME =
new Project.NameKey("-- All Projects --");
private final File sitePath;
private final @SitePath
File site_path;
private final ScriptRunner index_generic;
private final ScriptRunner index_postgres;
private final ScriptRunner mysql_nextval;
@Inject
public SchemaCreator(final @SitePath File sitePath) {
this.sitePath = sitePath;
public SchemaCreator(final SitePaths site) {
this(site.site_path);
}
public SchemaCreator(final @SitePath File site) {
site_path = site;
index_generic = new ScriptRunner("index_generic.sql");
index_postgres = new ScriptRunner("index_postgres.sql");
mysql_nextval = new ScriptRunner("mysql_nextval.sql");
@ -98,23 +103,23 @@ public class SchemaCreator {
private SystemConfig initSystemConfig(final ReviewDb c) throws OrmException {
final AccountGroup admin =
new AccountGroup(new AccountGroup.NameKey("Administrators"), new AccountGroup.Id(c
.nextAccountGroupId()));
new AccountGroup(new AccountGroup.NameKey("Administrators"),
new AccountGroup.Id(c.nextAccountGroupId()));
admin.setDescription("Gerrit Site Administrators");
admin.setType(AccountGroup.Type.INTERNAL);
c.accountGroups().insert(Collections.singleton(admin));
final AccountGroup anonymous =
new AccountGroup(new AccountGroup.NameKey("Anonymous Users"), new AccountGroup.Id(c
.nextAccountGroupId()));
new AccountGroup(new AccountGroup.NameKey("Anonymous Users"),
new AccountGroup.Id(c.nextAccountGroupId()));
anonymous.setDescription("Any user, signed-in or not");
anonymous.setOwnerGroupId(admin.getId());
anonymous.setType(AccountGroup.Type.SYSTEM);
c.accountGroups().insert(Collections.singleton(anonymous));
final AccountGroup registered =
new AccountGroup(new AccountGroup.NameKey("Registered Users"), new AccountGroup.Id(c
.nextAccountGroupId()));
new AccountGroup(new AccountGroup.NameKey("Registered Users"),
new AccountGroup.Id(c.nextAccountGroupId()));
registered.setDescription("Any signed-in user");
registered.setOwnerGroupId(admin.getId());
registered.setType(AccountGroup.Type.SYSTEM);
@ -126,9 +131,9 @@ public class SchemaCreator {
s.anonymousGroupId = anonymous.getId();
s.registeredGroupId = registered.getId();
try {
s.sitePath = sitePath.getCanonicalPath();
s.sitePath = site_path.getCanonicalPath();
} catch (IOException e) {
s.sitePath = sitePath.getAbsolutePath();
s.sitePath = site_path.getAbsolutePath();
}
c.systemConfig().insert(Collections.singleton(s));
return s;
@ -160,8 +165,8 @@ public class SchemaCreator {
txn.commit();
}
private void initCodeReviewCategory(final ReviewDb c, final SystemConfig sConfig)
throws OrmException {
private void initCodeReviewCategory(final ReviewDb c,
final SystemConfig sConfig) throws OrmException {
final Transaction txn = c.beginTransaction();
final ApprovalCategory cat;
final ArrayList<ApprovalCategoryValue> vals;
@ -203,7 +208,8 @@ public class SchemaCreator {
txn.commit();
}
private void initReadCategory(final ReviewDb c, final SystemConfig sConfig) throws OrmException {
private void initReadCategory(final ReviewDb c, final SystemConfig sConfig)
throws OrmException {
final Transaction txn = c.beginTransaction();
final ApprovalCategory cat;
final ArrayList<ApprovalCategoryValue> vals;
@ -269,14 +275,16 @@ public class SchemaCreator {
cat.setFunctionName(NoOpFunction.NAME);
vals = new ArrayList<ApprovalCategoryValue>();
vals.add(value(cat, ApprovalCategory.PUSH_TAG_SIGNED, "Create Signed Tag"));
vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANNOTATED, "Create Annotated Tag"));
vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANNOTATED,
"Create Annotated Tag"));
vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANY, "Create Any Tag"));
c.approvalCategories().insert(Collections.singleton(cat), txn);
c.approvalCategoryValues().insert(vals, txn);
txn.commit();
}
private void initPushUpdateBranchCategory(final ReviewDb c) throws OrmException {
private void initPushUpdateBranchCategory(final ReviewDb c)
throws OrmException {
final Transaction txn = c.beginTransaction();
final ApprovalCategory cat;
final ArrayList<ApprovalCategoryValue> vals;
@ -287,14 +295,16 @@ public class SchemaCreator {
vals = new ArrayList<ApprovalCategoryValue>();
vals.add(value(cat, ApprovalCategory.PUSH_HEAD_UPDATE, "Update Branch"));
vals.add(value(cat, ApprovalCategory.PUSH_HEAD_CREATE, "Create Branch"));
vals.add(value(cat, ApprovalCategory.PUSH_HEAD_REPLACE, "Force Push Branch; Delete Branch"));
vals.add(value(cat, ApprovalCategory.PUSH_HEAD_REPLACE,
"Force Push Branch; Delete Branch"));
c.approvalCategories().insert(Collections.singleton(cat), txn);
c.approvalCategoryValues().insert(vals, txn);
txn.commit();
}
private static ApprovalCategoryValue value(final ApprovalCategory cat, final int value,
final String name) {
return new ApprovalCategoryValue(new ApprovalCategoryValue.Id(cat.getId(), (short) value), name);
private static ApprovalCategoryValue value(final ApprovalCategory cat,
final int value, final String name) {
return new ApprovalCategoryValue(new ApprovalCategoryValue.Id(cat.getId(),
(short) value), name);
}
}

View File

@ -17,27 +17,25 @@ package com.google.gerrit.server.schema;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.SchemaVersion;
import com.google.gerrit.reviewdb.SystemConfig;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
/** Creates or updates the current database schema. */
public class SchemaUpdater {
private final SchemaFactory<ReviewDb> schema;
private final File sitePath;
private final SitePaths site;
private final SchemaCreator creator;
@Inject
SchemaUpdater(final SchemaFactory<ReviewDb> schema,
final @SitePath File sitePath,
SchemaUpdater(final SchemaFactory<ReviewDb> schema, final SitePaths site,
final SchemaCreator creator) {
this.schema = schema;
this.sitePath = sitePath;
this.site = site;
this.creator = creator;
}
@ -70,9 +68,9 @@ public class SchemaUpdater {
throw new OrmException("No record in system_config table");
}
try {
sc.sitePath = sitePath.getCanonicalPath();
sc.sitePath = site.site_path.getCanonicalPath();
} catch (IOException e) {
sc.sitePath = sitePath.getAbsolutePath();
sc.sitePath = site.site_path.getAbsolutePath();
}
db.systemConfig().update(Collections.singleton(sc));
}

View File

@ -0,0 +1,123 @@
// Copyright (C) 2009 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.server.util;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
public final class SocketUtil {
/** True if this InetAddress is a raw IPv6 in dotted quad notation. */
public static boolean isIPv6(final InetAddress ip) {
return ip instanceof Inet6Address && ip.getHostName().equals(ip.getHostAddress());
}
/** Get the name or IP address, or {@code *} if this address is a wildcard IP. */
public static String hostname(final InetSocketAddress addr) {
if (addr.getAddress() != null) {
if (addr.getAddress().isAnyLocalAddress()) {
return "*";
}
return addr.getAddress().getHostName();
}
return addr.getHostName();
}
/** Format an address string into {@code host:port} or {@code *:port} syntax. */
public static String format(final SocketAddress s, final int defaultPort) {
if (s instanceof InetSocketAddress) {
final InetSocketAddress addr = (InetSocketAddress) s;
return format(hostname(addr), addr.getPort());
}
return s.toString();
}
/** Format an address string into {@code host:port} or {@code *:port} syntax. */
public static String format(String hostname, int port) {
if (0 <= hostname.indexOf(':')) {
hostname = "[" + hostname + "]";
}
return hostname + ":" + port;
}
/** Parse an address string such as {@code host:port} or {@code *:port}. */
public static InetSocketAddress parse(final String desc, final int defaultPort) {
String hostStr;
String portStr;
if (desc.startsWith("[")) {
// IPv6, as a raw IP address.
//
final int hostEnd = desc.indexOf(']');
if (hostEnd < 0) {
throw new IllegalArgumentException("invalid IPv6 representation");
}
hostStr = desc.substring(1, hostEnd);
portStr = desc.substring(hostEnd + 1);
} else {
// IPv4, or a host name.
//
final int hostEnd = desc.indexOf(':');
hostStr = 0 <= hostEnd ? desc.substring(0, hostEnd) : desc;
portStr = 0 <= hostEnd ? desc.substring(hostEnd) : "";
}
if ("".equals(hostStr)) {
hostStr = "*";
}
if (portStr.startsWith(":")) {
portStr = portStr.substring(1);
}
final int port;
if (portStr.length() > 0) {
try {
port = Integer.parseInt(portStr);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("invalid port");
}
} else {
port = defaultPort;
}
if ("*".equals(hostStr)) {
return new InetSocketAddress(port);
} else {
return InetSocketAddress.createUnresolved(hostStr, port);
}
}
/** Parse and resolve an address string, looking up the IP address. */
public static InetSocketAddress resolve(final String desc, final int defaultPort) {
final InetSocketAddress addr = parse(desc, defaultPort);
if (addr.getAddress() != null && addr.getAddress().isAnyLocalAddress()) {
return addr;
} else {
try {
final InetAddress host = InetAddress.getByName(addr.getHostName());
return new InetSocketAddress(host, addr.getPort());
} catch (UnknownHostException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
}
}
private SocketUtil() {
}
}

View File

@ -14,7 +14,7 @@
package com.google.gerrit.sshd;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
@ -22,26 +22,24 @@ import com.google.inject.ProvisionException;
import org.apache.sshd.common.KeyPairProvider;
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
import org.apache.sshd.common.util.SecurityUtils;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
class HostKeyProvider implements Provider<KeyPairProvider> {
private final File sitePath;
private final SitePaths site;
@Inject
HostKeyProvider(@SitePath final File sitePath) {
this.sitePath = sitePath;
HostKeyProvider(final SitePaths site) {
this.site = site;
}
@Override
public KeyPairProvider get() {
final File etc = new File(sitePath, "etc");
final File anyKey = new File(etc, "ssh_host_key");
final File rsaKey = new File(etc, "ssh_host_rsa_key");
final File dsaKey = new File(etc, "ssh_host_dsa_key");
final File anyKey = site.ssh_key;
final File rsaKey = site.ssh_rsa;
final File dsaKey = site.ssh_dsa;
final List<String> keys = new ArrayList<String>(2);
if (rsaKey.exists()) {
@ -60,18 +58,8 @@ class HostKeyProvider implements Provider<KeyPairProvider> {
}
if (keys.isEmpty()) {
// No administrator created host key? Generate and save our own.
//
final SimpleGeneratorHostKeyProvider keyp;
if (!etc.exists() && !etc.mkdirs()) {
throw new ProvisionException("Cannot create directory " + etc);
}
keyp = new SimpleGeneratorHostKeyProvider();
keyp.setPath(anyKey.getAbsolutePath());
return keyp;
throw new ProvisionException("No SSH keys under " + site.etc_dir);
}
if (!SecurityUtils.isBouncyCastleRegistered()) {
throw new ProvisionException("Bouncy Castle Crypto not installed;"
+ " needed to read server host keys: " + keys + "");

View File

@ -18,6 +18,7 @@ import com.google.gerrit.lifecycle.LifecycleListener;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gerrit.server.util.IdGenerator;
import com.google.gerrit.server.util.SocketUtil;
import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.Singleton;
@ -71,11 +72,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.PublicKey;
@ -112,28 +110,6 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
private static final Logger log = LoggerFactory.getLogger(SshDaemon.class);
private static String format(final SocketAddress addr) {
if (addr instanceof InetSocketAddress) {
final InetSocketAddress inetAddr = (InetSocketAddress) addr;
final InetAddress hostAddr = inetAddr.getAddress();
String host;
if (hostAddr.isAnyLocalAddress()) {
host = "*";
} else if (inetAddr.getPort() == IANA_SSH_PORT && !isIPv6(hostAddr)) {
return inetAddr.getHostName();
} else {
host = "[" + hostAddr.getHostName() + "]";
}
return host + ":" + inetAddr.getPort();
}
return addr.toString();
}
private static boolean isIPv6(final InetAddress ip) {
return ip instanceof Inet6Address
&& ip.getHostName().equals(ip.getHostAddress());
}
private final List<SocketAddress> listen;
private final boolean keepAlive;
private final List<HostKey> hostKeys;
@ -256,7 +232,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
}
try {
r.add(new HostKey(format(inetAddr), keyBin));
r.add(new HostKey(SocketUtil.format(inetAddr, IANA_SSH_PORT), keyBin));
} catch (JSchException e) {
log.warn("Cannot format SSHD host key", e);
}
@ -284,7 +260,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
private String addressList() {
final StringBuilder r = new StringBuilder();
for (Iterator<SocketAddress> i = listen.iterator(); i.hasNext();) {
r.append(format(i.next()));
r.append(SocketUtil.format(i.next(), IANA_SSH_PORT));
if (i.hasNext()) {
r.append(", ");
}
@ -302,7 +278,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
for (final String desc : want) {
try {
bind.add(toSocketAddress(desc));
bind.add(SocketUtil.resolve(desc, DEFAULT_PORT));
} catch (IllegalArgumentException e) {
log.error("Bad sshd.listenaddress: " + desc + ": " + e.getMessage());
}
@ -310,57 +286,6 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
return bind;
}
private SocketAddress toSocketAddress(final String desc) {
String hostStr;
String portStr;
if (desc.startsWith("[")) {
// IPv6, as a raw IP address.
//
final int hostEnd = desc.indexOf(']');
if (hostEnd < 0) {
throw new IllegalArgumentException("invalid IPv6 representation");
}
hostStr = desc.substring(1, hostEnd);
portStr = desc.substring(hostEnd + 1);
} else {
// IPv4, or a host name.
//
final int hostEnd = desc.indexOf(':');
hostStr = 0 <= hostEnd ? desc.substring(0, hostEnd) : desc;
portStr = 0 <= hostEnd ? desc.substring(hostEnd) : "";
}
if ("*".equals(hostStr)) {
hostStr = "";
}
if (portStr.startsWith(":")) {
portStr = portStr.substring(1);
}
final int port;
if (portStr.length() > 0) {
try {
port = Integer.parseInt(portStr);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("invalid port");
}
} else {
port = DEFAULT_PORT;
}
if (hostStr.length() > 0) {
try {
return new InetSocketAddress(InetAddress.getByName(hostStr), port);
} catch (UnknownHostException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
} else {
return new InetSocketAddress(port);
}
}
@SuppressWarnings("unchecked")
private void initProviderBouncyCastle() {
setKeyExchangeFactories(Arrays.<NamedFactory<KeyExchange>> asList(