Merge "Allow use server.go for testing against local site"

This commit is contained in:
Dmitrii Filippov
2020-02-12 15:24:51 +00:00
committed by Gerrit Code Review
10 changed files with 57 additions and 335 deletions

View File

@@ -338,7 +338,7 @@ public class WebAppInitializer extends GuiceServletContextListener implements Fi
new AbstractModule() { new AbstractModule() {
@Override @Override
protected void configure() { protected void configure() {
bind(GerritOptions.class).toInstance(new GerritOptions(false, false, false)); bind(GerritOptions.class).toInstance(new GerritOptions(false, false, ""));
bind(GerritRuntime.class).toInstance(GerritRuntime.DAEMON); bind(GerritRuntime.class).toInstance(GerritRuntime.DAEMON);
} }
}); });

View File

@@ -1,160 +0,0 @@
// Copyright (C) 2016 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.httpd.raw;
import static com.google.common.base.MoreObjects.firstNonNull;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Joiner;
import com.google.common.escape.Escaper;
import com.google.common.flogger.FluentLogger;
import com.google.common.html.HtmlEscapers;
import com.google.common.io.ByteStreams;
import com.google.gerrit.launcher.GerritLauncher;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.util.http.CacheHeaders;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.util.RawParseUtils;
public class BazelBuild {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final Path sourceRoot;
public BazelBuild(Path sourceRoot) {
this.sourceRoot = sourceRoot;
}
// builds the given label.
public void build(Label label) throws IOException, BuildFailureException {
ProcessBuilder proc = newBuildProcess(label);
proc.directory(sourceRoot.toFile()).redirectErrorStream(true);
logger.atInfo().log("building %s", label.fullName());
long start = TimeUtil.nowMs();
Process rebuild = proc.start();
byte[] out;
try (InputStream in = rebuild.getInputStream()) {
out = ByteStreams.toByteArray(in);
} finally {
rebuild.getOutputStream().close();
}
int status;
try {
status = rebuild.waitFor();
} catch (InterruptedException e) {
String msg = "interrupted waiting for: " + Joiner.on(' ').join(proc.command());
logger.atSevere().withCause(e).log(msg);
throw new InterruptedIOException(msg);
}
if (status != 0) {
logger.atWarning().log("build failed: %s", new String(out, UTF_8));
throw new BuildFailureException(out);
}
long time = TimeUtil.nowMs() - start;
logger.atInfo().log("UPDATED %s in %.3fs", label.fullName(), time / 1000.0);
}
// Represents a label in bazel.
static class Label {
protected final String pkg;
protected final String name;
public String fullName() {
return "//" + pkg + ":" + name;
}
@Override
public String toString() {
return fullName();
}
// Label in Bazel style.
Label(String pkg, String name) {
this.name = name;
this.pkg = pkg;
}
}
static class BuildFailureException extends Exception {
private static final long serialVersionUID = 1L;
final byte[] why;
BuildFailureException(byte[] why) {
this.why = why;
}
public void display(String rule, HttpServletResponse res) throws IOException {
res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
res.setContentType("text/html");
res.setCharacterEncoding(UTF_8.name());
CacheHeaders.setNotCacheable(res);
Escaper html = HtmlEscapers.htmlEscaper();
try (PrintWriter w = res.getWriter()) {
w.write("<html><title>BUILD FAILED</title><body>");
w.format("<h1>%s FAILED</h1>", html.escape(rule));
w.write("<pre>");
w.write(html.escape(RawParseUtils.decode(why)));
w.write("</pre>");
w.write("</body></html>");
}
}
}
private ProcessBuilder newBuildProcess(Label label) throws IOException {
Properties properties = GerritLauncher.loadBuildProperties(sourceRoot.resolve(".bazel_path"));
String bazel = firstNonNull(properties.getProperty("bazel"), "bazel");
List<String> cmd = new ArrayList<>();
cmd.add(bazel);
cmd.add("build");
if (GerritLauncher.isJdk9OrLater()) {
String v = GerritLauncher.getJdkVersionPostJdk8();
cmd.add("--host_java_toolchain=@bazel_tools//tools/jdk:toolchain_java" + v);
cmd.add("--java_toolchain=@bazel_tools//tools/jdk:toolchain_java" + v);
}
cmd.add(label.fullName());
ProcessBuilder proc = new ProcessBuilder(cmd);
if (properties.containsKey("PATH")) {
proc.environment().put("PATH", properties.getProperty("PATH"));
}
return proc;
}
/** returns the root relative path to the artifact for the given label */
public Path targetPath(Label l) {
return sourceRoot.resolve("bazel-bin").resolve(l.pkg).resolve(l.name);
}
/** Label for the polygerrit component zip. */
public Label polygerritComponents() {
return new Label("polygerrit-ui", "polygerrit_components.bower_components.zip");
}
/** Label for the fonts zip file. */
public Label fontZipLabel() {
return new Label("polygerrit-ui", "fonts.zip");
}
}

View File

@@ -1,49 +0,0 @@
// Copyright (C) 2015 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.httpd.raw;
import com.google.common.cache.Cache;
import com.google.gerrit.launcher.GerritLauncher;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Objects;
/* Bower component servlet only used in development mode */
class BowerComponentsDevServlet extends ResourceServlet {
private static final long serialVersionUID = 1L;
private final Path bowerComponents;
private final Path zip;
BowerComponentsDevServlet(Cache<Path, Resource> cache, BazelBuild builder) throws IOException {
super(cache, true);
Objects.requireNonNull(builder);
BazelBuild.Label label = builder.polygerritComponents();
try {
builder.build(label);
} catch (BazelBuild.BuildFailureException e) {
throw new IOException(e);
}
zip = builder.targetPath(label);
bowerComponents = GerritLauncher.newZipFileSystem(zip).getPath("/");
}
@Override
protected Path getResourcePath(String pathInfo) throws IOException {
return bowerComponents.resolve(pathInfo);
}
}

View File

@@ -1,50 +0,0 @@
// Copyright (C) 2016 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.httpd.raw;
import com.google.common.cache.Cache;
import com.google.gerrit.launcher.GerritLauncher;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Objects;
/* Font servlet only used in development mode */
class FontsDevServlet extends ResourceServlet {
private static final long serialVersionUID = 1L;
private final Path fonts;
FontsDevServlet(Cache<Path, Resource> cache, BazelBuild builder) throws IOException {
super(cache, true);
Objects.requireNonNull(builder);
BazelBuild.Label zipLabel = builder.fontZipLabel();
try {
builder.build(zipLabel);
} catch (BazelBuild.BuildFailureException e) {
throw new IOException(e);
}
Path zip = builder.targetPath(zipLabel);
Objects.requireNonNull(zip);
fonts = GerritLauncher.newZipFileSystem(zip).getPath("/");
}
@Override
protected Path getResourcePath(String pathInfo) throws IOException {
return fonts.resolve(pathInfo);
}
}

View File

@@ -14,7 +14,6 @@
package com.google.gerrit.httpd.raw; package com.google.gerrit.httpd.raw;
import static com.google.common.base.Preconditions.checkArgument;
import static java.nio.file.Files.exists; import static java.nio.file.Files.exists;
import static java.nio.file.Files.isReadable; import static java.nio.file.Files.isReadable;
@@ -222,7 +221,8 @@ public class StaticModule extends ServletModule {
@CanonicalWebUrl @Nullable String canonicalUrl, @CanonicalWebUrl @Nullable String canonicalUrl,
@GerritServerConfig Config cfg, @GerritServerConfig Config cfg,
GerritApi gerritApi) { GerritApi gerritApi) {
String cdnPath = cfg.getString("gerrit", null, "cdnPath"); String cdnPath =
options.useDevCdn() ? options.devCdn() : cfg.getString("gerrit", null, "cdnPath");
String faviconPath = cfg.getString("gerrit", null, "faviconPath"); String faviconPath = cfg.getString("gerrit", null, "faviconPath");
return new IndexServlet(canonicalUrl, cdnPath, faviconPath, gerritApi); return new IndexServlet(canonicalUrl, cdnPath, faviconPath, gerritApi);
} }
@@ -233,29 +233,8 @@ public class StaticModule extends ServletModule {
return new PolyGerritUiServlet(cache, polyGerritBasePath()); return new PolyGerritUiServlet(cache, polyGerritBasePath());
} }
@Provides
@Singleton
BowerComponentsDevServlet getBowerComponentsServlet(@Named(CACHE) Cache<Path, Resource> cache)
throws IOException {
return getPaths().isDev() ? new BowerComponentsDevServlet(cache, getPaths().builder) : null;
}
@Provides
@Singleton
FontsDevServlet getFontsServlet(@Named(CACHE) Cache<Path, Resource> cache) throws IOException {
return getPaths().isDev() ? new FontsDevServlet(cache, getPaths().builder) : null;
}
private Path polyGerritBasePath() { private Path polyGerritBasePath() {
Paths p = getPaths(); Paths p = getPaths();
if (options.forcePolyGerritDev()) {
checkArgument(
p.sourceRoot != null, "no source root directory found for PolyGerrit developer mode");
}
if (p.isDev()) {
return p.sourceRoot.resolve("polygerrit-ui").resolve("app");
}
return p.warFs != null return p.warFs != null
? p.warFs.getPath("/polygerrit_ui") ? p.warFs.getPath("/polygerrit_ui")
@@ -265,7 +244,6 @@ public class StaticModule extends ServletModule {
private static class Paths { private static class Paths {
private final FileSystem warFs; private final FileSystem warFs;
private final BazelBuild builder;
private final Path sourceRoot; private final Path sourceRoot;
private final Path unpackedWar; private final Path unpackedWar;
private final boolean development; private final boolean development;
@@ -285,21 +263,19 @@ public class StaticModule extends ServletModule {
launcherLoadedFrom.getParentFile().getParentFile().getParentFile().toURI()); launcherLoadedFrom.getParentFile().getParentFile().getParentFile().toURI());
sourceRoot = null; sourceRoot = null;
development = false; development = false;
builder = null;
return; return;
} }
warFs = getDistributionArchive(launcherLoadedFrom); warFs = getDistributionArchive(launcherLoadedFrom);
if (warFs == null) { if (warFs == null) {
unpackedWar = makeWarTempDir(); unpackedWar = makeWarTempDir();
development = true; development = true;
} else if (options.forcePolyGerritDev()) { } else if (options.useDevCdn()) {
unpackedWar = null; unpackedWar = null;
development = true; development = true;
} else { } else {
unpackedWar = null; unpackedWar = null;
development = false; development = false;
sourceRoot = null; sourceRoot = null;
builder = null;
return; return;
} }
} catch (IOException e) { } catch (IOException e) {
@@ -307,7 +283,6 @@ public class StaticModule extends ServletModule {
} }
sourceRoot = getSourceRootOrNull(); sourceRoot = getSourceRootOrNull();
builder = new BazelBuild(sourceRoot);
} }
private static Path getSourceRootOrNull() { private static Path getSourceRootOrNull() {
@@ -376,21 +351,15 @@ public class StaticModule extends ServletModule {
private final Paths paths; private final Paths paths;
private final HttpServlet polyGerritIndex; private final HttpServlet polyGerritIndex;
private final PolyGerritUiServlet polygerritUI; private final PolyGerritUiServlet polygerritUI;
private final BowerComponentsDevServlet bowerComponentServlet;
private final FontsDevServlet fontServlet;
@Inject @Inject
PolyGerritFilter( PolyGerritFilter(
Paths paths, Paths paths,
@Named(POLYGERRIT_INDEX_SERVLET) HttpServlet polyGerritIndex, @Named(POLYGERRIT_INDEX_SERVLET) HttpServlet polyGerritIndex,
PolyGerritUiServlet polygerritUI, PolyGerritUiServlet polygerritUI) {
@Nullable BowerComponentsDevServlet bowerComponentServlet,
@Nullable FontsDevServlet fontServlet) {
this.paths = paths; this.paths = paths;
this.polyGerritIndex = polyGerritIndex; this.polyGerritIndex = polyGerritIndex;
this.polygerritUI = polygerritUI; this.polygerritUI = polygerritUI;
this.bowerComponentServlet = bowerComponentServlet;
this.fontServlet = fontServlet;
} }
@Override @Override
@@ -408,22 +377,6 @@ public class StaticModule extends ServletModule {
GuiceFilterRequestWrapper reqWrapper = new GuiceFilterRequestWrapper(req); GuiceFilterRequestWrapper reqWrapper = new GuiceFilterRequestWrapper(req);
String path = pathInfo(req); String path = pathInfo(req);
// Special case assets during development that are built by Bazel and not
// served out of the source tree.
//
// In the war case, these are either inlined, or live under
// /polygerrit_ui in the war file, so we can just treat them as normal
// assets.
if (paths.isDev()) {
if (path.startsWith("/bower_components/")) {
bowerComponentServlet.service(reqWrapper, res);
return;
} else if (path.startsWith("/fonts/")) {
fontServlet.service(reqWrapper, res);
return;
}
}
if (isPolyGerritIndex(path)) { if (isPolyGerritIndex(path)) {
polyGerritIndex.service(reqWrapper, res); polyGerritIndex.service(reqWrapper, res);
return; return;

View File

@@ -168,8 +168,18 @@ public class Daemon extends SiteProgram {
@Option(name = "--headless", usage = "Don't start the UI frontend") @Option(name = "--headless", usage = "Don't start the UI frontend")
private boolean headless; private boolean headless;
@Option(name = "--polygerrit-dev", usage = "Force PolyGerrit UI for development") private String devCdn = "";
private boolean polyGerritDev;
@Option(name = "--dev-cdn", usage = "Use specified cdn for serving static content.")
private void setDevCdn(String cdn) {
if (cdn == null) {
cdn = "";
}
if (cdn.endsWith("/")) {
cdn = cdn.substring(0, cdn.length() - 1);
}
devCdn = cdn;
}
@Option( @Option(
name = "--init", name = "--init",
@@ -463,8 +473,7 @@ public class Daemon extends SiteProgram {
new AbstractModule() { new AbstractModule() {
@Override @Override
protected void configure() { protected void configure() {
bind(GerritOptions.class) bind(GerritOptions.class).toInstance(new GerritOptions(headless, replica, devCdn));
.toInstance(new GerritOptions(headless, replica, polyGerritDev));
if (inMemoryTest) { if (inMemoryTest) {
bind(String.class) bind(String.class)
.annotatedWith(SecureStoreClassName.class) .annotatedWith(SecureStoreClassName.class)

View File

@@ -17,12 +17,12 @@ package com.google.gerrit.server.config;
public class GerritOptions { public class GerritOptions {
private final boolean headless; private final boolean headless;
private final boolean slave; private final boolean slave;
private final boolean forcePolyGerritDev; private final String devCdn;
public GerritOptions(boolean headless, boolean slave, boolean forcePolyGerritDev) { public GerritOptions(boolean headless, boolean slave, String devCdn) {
this.headless = headless; this.headless = headless;
this.slave = slave; this.slave = slave;
this.forcePolyGerritDev = forcePolyGerritDev; this.devCdn = devCdn;
} }
public boolean headless() { public boolean headless() {
@@ -33,7 +33,11 @@ public class GerritOptions {
return !slave; return !slave;
} }
public boolean forcePolyGerritDev() { public String devCdn() {
return !headless && forcePolyGerritDev; return devCdn;
}
public boolean useDevCdn() {
return !headless && devCdn.length() > 0;
} }
} }

View File

@@ -181,7 +181,7 @@ public class InMemoryModule extends FactoryModule {
// support Path-based Configs, only FileBasedConfig. // support Path-based Configs, only FileBasedConfig.
bind(Path.class).annotatedWith(SitePath.class).toInstance(Paths.get(".")); bind(Path.class).annotatedWith(SitePath.class).toInstance(Paths.get("."));
bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(cfg); bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(cfg);
bind(GerritOptions.class).toInstance(new GerritOptions(false, false, false)); bind(GerritOptions.class).toInstance(new GerritOptions(false, false, ""));
bind(GitRepositoryManager.class).to(InMemoryRepositoryManager.class); bind(GitRepositoryManager.class).to(InMemoryRepositoryManager.class);
bind(InMemoryRepositoryManager.class).in(SINGLETON); bind(InMemoryRepositoryManager.class).in(SINGLETON);

View File

@@ -39,12 +39,11 @@ npm install
It may complain about a missing `typescript@2.3.4` peer dependency, which is It may complain about a missing `typescript@2.3.4` peer dependency, which is
harmless. harmless.
## Running locally against production data ## Serving files locally
#### Go server #### Go server
To test the local Polymer frontend against gerrit-review.googlesource.com To test the local Polymer frontend against production data or a local test site execute:
simply execute:
```sh ```sh
./polygerrit-ui/run-server.sh ./polygerrit-ui/run-server.sh
@@ -53,10 +52,7 @@ simply execute:
npm run start npm run start
``` ```
Then visit http://localhost:8081 These commands start the [simple hand-written Go webserver](https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/server.go).
This method is based on a
[simple hand-written Go webserver](https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/server.go).
Mostly it just switches between serving files locally and proxying the real Mostly it just switches between serving files locally and proxying the real
server based on the file name. It also does some basic response rewriting, e.g. server based on the file name. It also does some basic response rewriting, e.g.
it patches the `config/server/info` response with plugin information provided on it patches the `config/server/info` response with plugin information provided on
@@ -66,6 +62,12 @@ the command line:
./polygerrit-ui/run-server.sh --plugins=plugins/my_plugin/static/my_plugin.js,plugins/my_plugin/static/my_plugin.html ./polygerrit-ui/run-server.sh --plugins=plugins/my_plugin/static/my_plugin.js,plugins/my_plugin/static/my_plugin.html
``` ```
## Running locally against production data
### Local website
Start [Go server](#go-server) and then visit http://localhost:8081
The biggest draw back of this method is that you cannot log in, so cannot test The biggest draw back of this method is that you cannot log in, so cannot test
scenarios that require it. scenarios that require it.
@@ -89,9 +91,11 @@ Set up a local test site once:
3. Optionally [populate](https://gerrit.googlesource.com/gerrit/+/master/contrib/populate-fixture-data.py) your test site with some test data. 3. Optionally [populate](https://gerrit.googlesource.com/gerrit/+/master/contrib/populate-fixture-data.py) your test site with some test data.
For running a locally built Gerrit war against your test instance use For running a locally built Gerrit war against your test instance use
[this command](https://gerrit-review.googlesource.com/Documentation/dev-readme.html#run_daemon), [this command](https://gerrit-review.googlesource.com/Documentation/dev-readme.html#run_daemon).
and add the `--polygerrit-dev` option, if you want to serve the Polymer frontend
directly from the sources in `polygerrit_ui/app/` instead of from the war: If you want to serve the Polymer frontend directly from the sources in `polygerrit_ui/app/` instead of from the war:
1. Start [Go server](#go-server)
2. Add the `--dev-cdn` option:
```sh ```sh
$(bazel info output_base)/external/local_jdk/bin/java \ $(bazel info output_base)/external/local_jdk/bin/java \
@@ -99,9 +103,11 @@ $(bazel info output_base)/external/local_jdk/bin/java \
-jar bazel-bin/gerrit.war daemon \ -jar bazel-bin/gerrit.war daemon \
-d $GERRIT_SITE \ -d $GERRIT_SITE \
--console-log \ --console-log \
--polygerrit-dev --dev-cdn http://localhost:8081
``` ```
*NOTE* You can use any other cdn here, for example: https://cdn.googlesource.com/polygerrit_ui/678.0
## Running Tests ## Running Tests
This step requires the `web-component-tester` npm module. This step requires the `web-component-tester` npm module.

View File

@@ -64,11 +64,12 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
http.Handle("/", http.FileServer(http.Dir("app"))) http.Handle("/", addDevHeadersMiddleware(http.FileServer(http.Dir("app"))))
http.Handle("/bower_components/", http.Handle("/bower_components/",
http.FileServer(httpfs.New(zipfs.New(componentsArchive, "bower_components")))) addDevHeadersMiddleware(
http.FileServer(httpfs.New(zipfs.New(componentsArchive, "bower_components")))))
http.Handle("/fonts/", http.Handle("/fonts/",
http.FileServer(httpfs.New(zipfs.New(fontsArchive, "fonts")))) addDevHeadersMiddleware(http.FileServer(httpfs.New(zipfs.New(fontsArchive, "fonts")))))
http.HandleFunc("/index.html", handleIndex) http.HandleFunc("/index.html", handleIndex)
http.HandleFunc("/changes/", handleProxy) http.HandleFunc("/changes/", handleProxy)
@@ -92,6 +93,14 @@ func main() {
log.Fatal(http.ListenAndServe(*port, &server{})) log.Fatal(http.ListenAndServe(*port, &server{}))
} }
func addDevHeadersMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) {
writer.Header().Set("Access-Control-Allow-Origin", "*")
writer.Header().Set("Cache-Control", "public, max-age=10, must-revalidate")
h.ServeHTTP(writer, req)
})
}
func openDataArchive(path string) (*zip.ReadCloser, error) { func openDataArchive(path string) (*zip.ReadCloser, error) {
absBinPath, err := resourceBasePath() absBinPath, err := resourceBasePath()
if err != nil { if err != nil {