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() {
@Override
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);
}
});

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;
import static com.google.common.base.Preconditions.checkArgument;
import static java.nio.file.Files.exists;
import static java.nio.file.Files.isReadable;
@@ -222,7 +221,8 @@ public class StaticModule extends ServletModule {
@CanonicalWebUrl @Nullable String canonicalUrl,
@GerritServerConfig Config cfg,
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");
return new IndexServlet(canonicalUrl, cdnPath, faviconPath, gerritApi);
}
@@ -233,29 +233,8 @@ public class StaticModule extends ServletModule {
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() {
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
? p.warFs.getPath("/polygerrit_ui")
@@ -265,7 +244,6 @@ public class StaticModule extends ServletModule {
private static class Paths {
private final FileSystem warFs;
private final BazelBuild builder;
private final Path sourceRoot;
private final Path unpackedWar;
private final boolean development;
@@ -285,21 +263,19 @@ public class StaticModule extends ServletModule {
launcherLoadedFrom.getParentFile().getParentFile().getParentFile().toURI());
sourceRoot = null;
development = false;
builder = null;
return;
}
warFs = getDistributionArchive(launcherLoadedFrom);
if (warFs == null) {
unpackedWar = makeWarTempDir();
development = true;
} else if (options.forcePolyGerritDev()) {
} else if (options.useDevCdn()) {
unpackedWar = null;
development = true;
} else {
unpackedWar = null;
development = false;
sourceRoot = null;
builder = null;
return;
}
} catch (IOException e) {
@@ -307,7 +283,6 @@ public class StaticModule extends ServletModule {
}
sourceRoot = getSourceRootOrNull();
builder = new BazelBuild(sourceRoot);
}
private static Path getSourceRootOrNull() {
@@ -376,21 +351,15 @@ public class StaticModule extends ServletModule {
private final Paths paths;
private final HttpServlet polyGerritIndex;
private final PolyGerritUiServlet polygerritUI;
private final BowerComponentsDevServlet bowerComponentServlet;
private final FontsDevServlet fontServlet;
@Inject
PolyGerritFilter(
Paths paths,
@Named(POLYGERRIT_INDEX_SERVLET) HttpServlet polyGerritIndex,
PolyGerritUiServlet polygerritUI,
@Nullable BowerComponentsDevServlet bowerComponentServlet,
@Nullable FontsDevServlet fontServlet) {
PolyGerritUiServlet polygerritUI) {
this.paths = paths;
this.polyGerritIndex = polyGerritIndex;
this.polygerritUI = polygerritUI;
this.bowerComponentServlet = bowerComponentServlet;
this.fontServlet = fontServlet;
}
@Override
@@ -408,22 +377,6 @@ public class StaticModule extends ServletModule {
GuiceFilterRequestWrapper reqWrapper = new GuiceFilterRequestWrapper(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)) {
polyGerritIndex.service(reqWrapper, res);
return;

View File

@@ -168,8 +168,18 @@ public class Daemon extends SiteProgram {
@Option(name = "--headless", usage = "Don't start the UI frontend")
private boolean headless;
@Option(name = "--polygerrit-dev", usage = "Force PolyGerrit UI for development")
private boolean polyGerritDev;
private String devCdn = "";
@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(
name = "--init",
@@ -463,8 +473,7 @@ public class Daemon extends SiteProgram {
new AbstractModule() {
@Override
protected void configure() {
bind(GerritOptions.class)
.toInstance(new GerritOptions(headless, replica, polyGerritDev));
bind(GerritOptions.class).toInstance(new GerritOptions(headless, replica, devCdn));
if (inMemoryTest) {
bind(String.class)
.annotatedWith(SecureStoreClassName.class)

View File

@@ -17,12 +17,12 @@ package com.google.gerrit.server.config;
public class GerritOptions {
private final boolean headless;
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.slave = slave;
this.forcePolyGerritDev = forcePolyGerritDev;
this.devCdn = devCdn;
}
public boolean headless() {
@@ -33,7 +33,11 @@ public class GerritOptions {
return !slave;
}
public boolean forcePolyGerritDev() {
return !headless && forcePolyGerritDev;
public String devCdn() {
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.
bind(Path.class).annotatedWith(SitePath.class).toInstance(Paths.get("."));
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(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
harmless.
## Running locally against production data
## Serving files locally
#### Go server
To test the local Polymer frontend against gerrit-review.googlesource.com
simply execute:
To test the local Polymer frontend against production data or a local test site execute:
```sh
./polygerrit-ui/run-server.sh
@@ -53,10 +52,7 @@ simply execute:
npm run start
```
Then visit http://localhost:8081
This method is based on a
[simple hand-written Go webserver](https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/server.go).
These commands start the [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
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
@@ -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
```
## 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
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.
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),
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:
[this command](https://gerrit-review.googlesource.com/Documentation/dev-readme.html#run_daemon).
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
$(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 \
-d $GERRIT_SITE \
--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
This step requires the `web-component-tester` npm module.

View File

@@ -64,11 +64,12 @@ func main() {
log.Fatal(err)
}
http.Handle("/", http.FileServer(http.Dir("app")))
http.Handle("/", addDevHeadersMiddleware(http.FileServer(http.Dir("app"))))
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.FileServer(httpfs.New(zipfs.New(fontsArchive, "fonts"))))
addDevHeadersMiddleware(http.FileServer(httpfs.New(zipfs.New(fontsArchive, "fonts")))))
http.HandleFunc("/index.html", handleIndex)
http.HandleFunc("/changes/", handleProxy)
@@ -92,6 +93,14 @@ func main() {
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) {
absBinPath, err := resourceBasePath()
if err != nil {