Add REST APIs to test submit_rule and submit_filter

These are available over SSH, but not on HTTP. Make them available
on HTTP with REST API views on the revision resource. Use the REST
implementations to back the SSH commands, so the implementation is
not duplicated.

To match the REST API the SSH test-submit rule command no longer
accepts a format parameter. Output is in pretty formatted JSON.

Change-Id: I6a57b4561067eaa32d407a426c95ea61a96f1948
This commit is contained in:
Shawn Pearce
2013-03-04 07:54:09 -08:00
parent 1e1af68b34
commit b1f730b894
14 changed files with 635 additions and 312 deletions

View File

@@ -0,0 +1,81 @@
// Copyright (C) 2012 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.sshd.commands;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.change.ChangesCollection;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.change.Revisions;
import com.google.gerrit.server.change.TestSubmitRule.Filters;
import com.google.gerrit.server.change.TestSubmitRule.Input;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
import java.nio.ByteBuffer;
abstract class BaseTestPrologCommand extends SshCommand {
private Input input = new Input();
@Inject
private ChangesCollection changes;
@Inject
private Revisions revisions;
@Argument(index = 0, required = true, usage = "ChangeId to load in prolog environment")
protected String changeId;
@Option(name = "-s",
usage = "Read prolog script from stdin instead of reading rules.pl from the refs/meta/config branch")
protected boolean useStdin;
@Option(name = "--no-filters", aliases = {"-n"},
usage = "Don't run the submit_filter/2 from the parent projects")
void setNoFilters(boolean no) {
input.filters = no ? Filters.SKIP : Filters.RUN;
}
protected abstract RestModifyView<RevisionResource, Input> createView();
protected final void run() throws UnloggedFailure {
try {
RevisionResource revision = revisions.parse(
changes.parse(
TopLevelResource.INSTANCE,
IdString.fromUrl(changeId)),
IdString.fromUrl("current"));
if (useStdin) {
ByteBuffer buf = IO.readWholeStream(in, 4096);
input.rule = RawParseUtils.decode(
buf.array(),
buf.arrayOffset(),
buf.limit());
}
Object result = createView().apply(revision, input);
OutputFormat.JSON.newGson().toJson(result, stdout);
stdout.print('\n');
} catch (Exception e) {
throw new UnloggedFailure("Processing of prolog script failed: " + e);
}
}
}

View File

@@ -1,108 +0,0 @@
// Copyright (C) 2012 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.sshd.commands;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.SubmitRuleEvaluator;
import com.google.gerrit.sshd.SshCommand;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.googlecode.prolog_cafe.lang.ListTerm;
import com.googlecode.prolog_cafe.lang.Term;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
import java.util.List;
abstract class BaseTestSubmit extends SshCommand {
@Inject
protected ReviewDb db;
@Inject
private ChangeControl.Factory ccFactory;
@Inject
protected AccountCache accountCache;
@Inject
@AnonymousCowardName
protected String anonymousCowardName;
@Argument(index = 0, required = true, usage = "ChangeId to load in prolog environment")
protected String changeId;
@Option(name = "-s",
usage = "Read prolog script from stdin instead of reading rules.pl from the refs/meta/config branch")
protected boolean useStdin;
@Option(name = "--format", metaVar = "FMT", usage = "Output display format")
protected OutputFormat format = OutputFormat.TEXT;
@Option(name = "--no-filters", aliases = {"-n"},
usage = "Don't run the submit_filter/2 from the parent projects")
protected boolean skipSubmitFilters;
private Change change;
private ChangeControl changeControl;
protected abstract SubmitRuleEvaluator createEvaluator(PatchSet ps)
throws Exception;
protected abstract void processResults(ListTerm results, Term submitRule)
throws Exception;
protected final void run() throws UnloggedFailure {
try {
PatchSet ps = db.patchSets().get(getChange().currentPatchSetId());
SubmitRuleEvaluator evaluator = createEvaluator(ps);
processResults(evaluator.evaluate(), evaluator.getSubmitRule());
} catch (Exception e) {
throw new UnloggedFailure("Processing of prolog script failed: " + e);
}
}
protected final Change getChange() throws OrmException, UnloggedFailure {
if (change == null) {
List<Change> changeList =
db.changes().byKey(new Change.Key(changeId)).toList();
if (changeList.size() != 1)
throw new UnloggedFailure(1, "Invalid ChangeId");
change = changeList.get(0);
}
return change;
}
protected final ChangeControl getChangeControl() throws OrmException,
NoSuchChangeException, UnloggedFailure {
if (changeControl == null) {
// Will throw exception if current user can not access this change, and
// thus will leak information that a change-id is valid even though the
// user are not allowed to see the change.
// See http://code.google.com/p/gerrit/issues/detail?id=1586
changeControl = ccFactory.controlFor(getChange());
}
return changeControl;
}
}

View File

@@ -32,7 +32,6 @@ public class MasterCommandModule extends CommandModule {
command(gerrit, RenameGroupCommand.class);
command(gerrit, CreateProjectCommand.class);
command(gerrit, AdminQueryShell.class);
command(gerrit, TestSubmitRule.class);
command(gerrit, SetReviewersCommand.class);
command(gerrit, Receive.class);
command(gerrit, AdminSetParent.class);
@@ -43,7 +42,7 @@ public class MasterCommandModule extends CommandModule {
command(gerrit, SetProjectCommand.class);
command(gerrit, "test-submit").toProvider(new DispatchCommandProvider(testSubmit));
command(testSubmit, TestSubmitRule.class);
command(testSubmit, TestSubmitType.class);
command(testSubmit, TestSubmitRuleCommand.class);
command(testSubmit, TestSubmitTypeCommand.class);
}
}

View File

@@ -1,95 +0,0 @@
// Copyright (C) 2012 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.sshd.commands;
import com.google.gerrit.common.data.AccountInfo;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.events.AccountAttribute;
import com.google.gerrit.server.events.SubmitLabelAttribute;
import com.google.gerrit.server.events.SubmitRecordAttribute;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.SubmitRuleEvaluator;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gson.reflect.TypeToken;
import com.googlecode.prolog_cafe.lang.ListTerm;
import com.googlecode.prolog_cafe.lang.Term;
import java.util.LinkedList;
import java.util.List;
/** Command that allows testing of prolog submit-rules in a live instance. */
@CommandMetaData(name = "rule", descr = "Test prolog submit rules")
final class TestSubmitRule extends BaseTestSubmit {
protected SubmitRuleEvaluator createEvaluator(PatchSet ps) throws Exception {
ChangeControl cc = getChangeControl();
return new SubmitRuleEvaluator(
db, ps, cc.getProjectControl(), cc, getChange(), null,
false, "locate_submit_rule", "can_submit",
"locate_submit_filter", "filter_submit_results",
skipSubmitFilters, useStdin ? in : null);
}
protected void processResults(ListTerm results, Term submitRule) throws Exception {
@SuppressWarnings("unchecked")
List<SubmitRecord> res = getChangeControl().resultsToSubmitRecord(submitRule,
results.toJava());
if (res.isEmpty()) {
// Should never occur for a well written rule
Change c = getChange();
stderr.print("Submit rule " + submitRule + " for change " + c.getChangeId()
+ " of " + c.getProject().get() + " has no solution");
return;
}
for (SubmitRecord r : res) {
if (format.isJson()) {
SubmitRecordAttribute submitRecord = new SubmitRecordAttribute();
submitRecord.status = r.status.name();
List<SubmitLabelAttribute> submitLabels = new LinkedList<SubmitLabelAttribute>();
for(SubmitRecord.Label l : r.labels) {
SubmitLabelAttribute label = new SubmitLabelAttribute();
label.label = l.label;
label.status= l.status.name();
if(l.appliedBy != null) {
Account a = accountCache.get(l.appliedBy).getAccount();
label.by = new AccountAttribute();
label.by.email = a.getPreferredEmail();
label.by.name = a.getFullName();
label.by.username = a.getUserName();
}
submitLabels.add(label);
}
submitRecord.labels = submitLabels;
format.newGson().toJson(submitRecord, new TypeToken<SubmitRecordAttribute>() {}.getType(), stdout);
stdout.print('\n');
} else {
for(SubmitRecord.Label l : r.labels) {
stdout.print(l.label + ": " + l.status);
if(l.appliedBy != null) {
AccountInfo a = new AccountInfo(accountCache.get(l.appliedBy).getAccount());
stdout.print(" by " + a.getNameEmail(anonymousCowardName));
}
stdout.print('\n');
}
stdout.print("\n" + r.status.name() + "\n");
}
}
}
}

View File

@@ -0,0 +1,35 @@
// Copyright (C) 2012 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.sshd.commands;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.change.TestSubmitRule;
import com.google.gerrit.server.change.TestSubmitRule.Input;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.inject.Inject;
import com.google.inject.Provider;
/** Command that allows testing of prolog submit-rules in a live instance. */
@CommandMetaData(name = "rule", descr = "Test prolog submit rules")
final class TestSubmitRuleCommand extends BaseTestPrologCommand {
@Inject
private Provider<TestSubmitRule> view;
@Override
protected RestModifyView<RevisionResource, Input> createView() {
return view.get();
}
}

View File

@@ -1,58 +0,0 @@
// Copyright (C) 2012 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.sshd.commands;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.SubmitRuleEvaluator;
import com.google.gerrit.sshd.CommandMetaData;
import com.googlecode.prolog_cafe.lang.ListTerm;
import com.googlecode.prolog_cafe.lang.Term;
import java.util.List;
@CommandMetaData(name = "type", descr = "Test prolog submit type")
final class TestSubmitType extends BaseTestSubmit {
@Override
protected SubmitRuleEvaluator createEvaluator(PatchSet ps) throws Exception {
ChangeControl cc = getChangeControl();
return new SubmitRuleEvaluator(
db, ps, cc.getProjectControl(), cc, getChange(), null,
false, "locate_submit_type", "get_submit_type",
"locate_submit_type_filter", "filter_submit_type_results",
skipSubmitFilters, useStdin ? in : null);
}
@Override
protected void processResults(ListTerm resultsTerm, Term submitRule)
throws Exception {
@SuppressWarnings("unchecked")
List<String> results = resultsTerm.toJava();
if (results.isEmpty()) {
// Should never occur for a well written rule
Change c = getChange();
stderr.print("Submit rule " + submitRule + " for change " + c.getChangeId()
+ " of " + c.getProject().get() + " has no solution");
return;
}
String typeName = results.get(0);
stdout.print(typeName);
stdout.print('\n');
}
}

View File

@@ -0,0 +1,35 @@
// Copyright (C) 2012 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.sshd.commands;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.change.TestSubmitRule.Input;
import com.google.gerrit.server.change.TestSubmitType;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.inject.Inject;
import com.google.inject.Provider;
@CommandMetaData(name = "type", descr = "Test prolog submit type")
final class TestSubmitTypeCommand extends BaseTestPrologCommand {
@Inject
private Provider<TestSubmitType> view;
@Override
protected RestModifyView<RevisionResource, Input> createView() {
return view.get();
}
}