Use RestApiServlet and SSH commands for plugin actions
This change is adding one endpoint with three methods:
* GET /config/server/readonly~readonly
* PUT /config/server/readonly~readonly
* DELETE /config/server/readonly~readonly
and based on this endpoint, three SSH commands:
* ssh host readonly get
* ssh host readonly put
* ssh host readonly delete
Change-Id: I1797906aebb30674c7c37ecfe03a8b944b631299
diff --git a/BUILD b/BUILD
index 73863f9..da50b99 100644
--- a/BUILD
+++ b/BUILD
@@ -15,7 +15,7 @@
"Gerrit-SshModule: com.googlesource.gerrit.plugins.readonly.SshModule",
"Gerrit-HttpModule: com.googlesource.gerrit.plugins.readonly.HttpModule",
],
- resources = glob(["src/main/**/*"]),
+ resources = glob(["src/main/resources/**/*"]),
)
junit_tests(
diff --git a/src/main/java/com/googlesource/gerrit/plugins/readonly/DeleteReadOnlyCommand.java b/src/main/java/com/googlesource/gerrit/plugins/readonly/DeleteReadOnlyCommand.java
new file mode 100644
index 0000000..392df17
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/readonly/DeleteReadOnlyCommand.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2018 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.googlesource.gerrit.plugins.readonly;
+
+import static com.google.gerrit.common.data.GlobalCapability.ADMINISTRATE_SERVER;
+import static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
+
+import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+import java.io.IOException;
+
+@RequiresAnyCapability({ADMINISTRATE_SERVER, MAINTAIN_SERVER})
+@CommandMetaData(name = "delete", description = "Disable read only mode")
+class DeleteReadOnlyCommand extends SshCommand {
+ @Inject ReadOnlyEndpoint.Delete delete;
+
+ @Override
+ protected void run() throws IOException {
+ delete.apply(null, null);
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/readonly/DisableCommandInterceptor.java b/src/main/java/com/googlesource/gerrit/plugins/readonly/DisableCommandInterceptor.java
index d287483..19b2592 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/readonly/DisableCommandInterceptor.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/readonly/DisableCommandInterceptor.java
@@ -40,6 +40,8 @@
this.state = state;
this.disableCommand = pluginName + " disable";
allowPatterns.add(Pattern.compile(String.format(PATTERN, pluginName)));
+ // Allow all SSH commands from this plugin
+ allowPrefixes.add(pluginName);
for (String allow : config.allowSshCommands()) {
if (allow.startsWith("^")) {
allowPatterns.add(Pattern.compile(allow));
diff --git a/src/main/java/com/googlesource/gerrit/plugins/readonly/GetReadOnlyCommand.java b/src/main/java/com/googlesource/gerrit/plugins/readonly/GetReadOnlyCommand.java
new file mode 100644
index 0000000..3a5c9db
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/readonly/GetReadOnlyCommand.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2018 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.googlesource.gerrit.plugins.readonly;
+
+import static com.google.gerrit.common.data.GlobalCapability.ADMINISTRATE_SERVER;
+import static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
+
+import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+
+@RequiresAnyCapability({ADMINISTRATE_SERVER, MAINTAIN_SERVER})
+@CommandMetaData(name = "get", description = "Show read only mode state")
+class GetReadOnlyCommand extends SshCommand {
+ @Inject ReadOnlyEndpoint.Get get;
+
+ @Override
+ protected void run() {
+ stdout.println(get.apply(null));
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/readonly/Module.java b/src/main/java/com/googlesource/gerrit/plugins/readonly/Module.java
index 88f681a..69fa804 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/readonly/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/readonly/Module.java
@@ -14,7 +14,10 @@
package com.googlesource.gerrit.plugins.readonly;
+import static com.google.gerrit.server.config.ConfigResource.CONFIG_KIND;
+
import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.restapi.RestApiModule;
import com.google.gerrit.server.git.validators.CommitValidationListener;
import com.google.inject.AbstractModule;
@@ -22,5 +25,14 @@
@Override
protected void configure() {
DynamicSet.bind(binder(), CommitValidationListener.class).to(ReadOnly.class);
+ install(
+ new RestApiModule() {
+ @Override
+ protected void configure() {
+ put(CONFIG_KIND, "readonly").to(ReadOnlyEndpoint.Put.class);
+ delete(CONFIG_KIND, "readonly").to(ReadOnlyEndpoint.Delete.class);
+ get(CONFIG_KIND, "readonly").to(ReadOnlyEndpoint.Get.class);
+ }
+ });
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/readonly/PutReadOnlyCommand.java b/src/main/java/com/googlesource/gerrit/plugins/readonly/PutReadOnlyCommand.java
new file mode 100644
index 0000000..bf5ffef
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/readonly/PutReadOnlyCommand.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2018 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.googlesource.gerrit.plugins.readonly;
+
+import static com.google.gerrit.common.data.GlobalCapability.ADMINISTRATE_SERVER;
+import static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
+
+import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+import java.io.IOException;
+
+@RequiresAnyCapability({ADMINISTRATE_SERVER, MAINTAIN_SERVER})
+@CommandMetaData(name = "put", description = "Enable read only mode")
+class PutReadOnlyCommand extends SshCommand {
+ @Inject ReadOnlyEndpoint.Put put;
+
+ @Override
+ protected void run() throws IOException {
+ put.apply(null, null);
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/readonly/ReadOnly.java b/src/main/java/com/googlesource/gerrit/plugins/readonly/ReadOnly.java
index 54b35e7..824ddc1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/readonly/ReadOnly.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/readonly/ReadOnly.java
@@ -41,12 +41,14 @@
private final ReadOnlyState state;
private final ReadOnlyConfig config;
private final String endpoint;
+ private final String endpoint2;
@Inject
ReadOnly(ReadOnlyState state, ReadOnlyConfig config, @PluginName String pluginName) {
this.state = state;
this.config = config;
this.endpoint = pluginName + ENDPOINT;
+ this.endpoint2 = String.format("/config/server/%s~readonly", pluginName);
}
@Override
@@ -73,9 +75,10 @@
private boolean shouldBlock(HttpServletRequest request) {
String method = request.getMethod();
- String uri = request.getRequestURI();
- return !uri.endsWith(endpoint)
- && (("POST".equals(method) && !uri.endsWith(GIT_UPLOAD_PACK_PROTOCOL))
+ String servletPath = request.getServletPath();
+ return !servletPath.endsWith(endpoint)
+ && !servletPath.endsWith(endpoint2)
+ && (("POST".equals(method) && !servletPath.endsWith(GIT_UPLOAD_PACK_PROTOCOL))
|| "PUT".equals(method)
|| "DELETE".equals(method));
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/readonly/ReadOnlyEndpoint.java b/src/main/java/com/googlesource/gerrit/plugins/readonly/ReadOnlyEndpoint.java
new file mode 100644
index 0000000..caabb57
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/readonly/ReadOnlyEndpoint.java
@@ -0,0 +1,81 @@
+// Copyright (C) 2018 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.googlesource.gerrit.plugins.readonly;
+
+import static com.google.gerrit.common.data.GlobalCapability.ADMINISTRATE_SERVER;
+import static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
+
+import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.config.ConfigResource;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.io.IOException;
+
+public class ReadOnlyEndpoint {
+ static class Input {}
+
+ @RequiresAnyCapability({ADMINISTRATE_SERVER, MAINTAIN_SERVER})
+ @Singleton
+ public static class Get implements RestReadView<ConfigResource> {
+ private final ReadOnlyState state;
+
+ @Inject
+ Get(ReadOnlyState state) {
+ this.state = state;
+ }
+
+ @Override
+ public String apply(ConfigResource resource) {
+ return state.isReadOnly() ? "on" : "off";
+ }
+ }
+
+ @RequiresAnyCapability({ADMINISTRATE_SERVER, MAINTAIN_SERVER})
+ @Singleton
+ public static class Put implements RestModifyView<ConfigResource, Input> {
+ private final ReadOnlyState state;
+
+ @Inject
+ Put(ReadOnlyState state) {
+ this.state = state;
+ }
+
+ @Override
+ public Response<String> apply(ConfigResource resource, Input input) throws IOException {
+ state.setReadOnly(true);
+ return Response.ok("");
+ }
+ }
+
+ @RequiresAnyCapability({ADMINISTRATE_SERVER, MAINTAIN_SERVER})
+ @Singleton
+ public static class Delete implements RestModifyView<ConfigResource, Input> {
+ private final ReadOnlyState state;
+
+ @Inject
+ Delete(ReadOnlyState state) {
+ this.state = state;
+ }
+
+ @Override
+ public Response<String> apply(ConfigResource resource, Input input) throws IOException {
+ state.setReadOnly(false);
+ return Response.ok("");
+ }
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/readonly/SshModule.java b/src/main/java/com/googlesource/gerrit/plugins/readonly/SshModule.java
index 8067516..c89816e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/readonly/SshModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/readonly/SshModule.java
@@ -24,5 +24,14 @@
DynamicItem.bind(binder(), SshCreateCommandInterceptor.class)
.to(DisableCommandInterceptor.class);
command(DisableCommand.class);
+
+ command(PutReadOnlyCommand.class);
+ alias("on", PutReadOnlyCommand.class);
+
+ command(DeleteReadOnlyCommand.class);
+ alias("off", DeleteReadOnlyCommand.class);
+
+ command(GetReadOnlyCommand.class);
+ alias("status", GetReadOnlyCommand.class);
}
}