diff options
author | Johan Björk <jbjoerk@gmail.com> | 2012-08-02 15:57:13 +0200 |
---|---|---|
committer | Edwin Kempin <edwin.kempin@sap.com> | 2012-10-10 15:34:34 +0200 |
commit | 6fb89cfea4e092f01a27a6f9e39adedcb82f25ca (patch) | |
tree | 28469552af8690dd719e88e5c9ecad26917c8244 | |
parent | cd7b1e4814d262cd4690013fb9c2b8bd24cd118c (diff) |
Add ssh command "test-submit-rule"
The command creates a fresh prolog environment and loads a prolog script
either from stdin or rules.pl in the project refs/meta/config branch
depending on the options used.
can_submit/1 is then queried and the results are returned in human
readable text or json.
Change-Id: I246a89f9f35718320b69ec43ddf11fbe002ed566
5 files changed, 338 insertions, 1 deletions
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt index 3895b905c0..ccd9ffc191 100644 --- a/Documentation/cmd-index.txt +++ b/Documentation/cmd-index.txt @@ -147,6 +147,9 @@ link:cmd-plugin-remove.html[gerrit plugin remove]:: link:cmd-plugin-remove.html[gerrit plugin rm]:: Alias for 'gerrit plugin remove'. +link:cmd-test-submit-rule.html[gerrit test-submit-rule]:: + Test prolog submit rules. + link:cmd-kill.html[kill]:: Kills a scheduled or running task. diff --git a/Documentation/cmd-test-submit-rule.txt b/Documentation/cmd-test-submit-rule.txt new file mode 100644 index 0000000000..5b70bd1e51 --- /dev/null +++ b/Documentation/cmd-test-submit-rule.txt @@ -0,0 +1,93 @@ +gerrit test-submit-rule +======================= + +NAME +---- +gerrit test-submit-rule - Test prolog submit rules with a chosen changeset. + +SYNOPSIS +-------- +[verse] +'ssh' -p <port> <host> 'gerrit test-submit-rule' + [-s] + [--no-filters] + [--format {TEXT | JSON}] + CHANGE + +DESCRIPTION +----------- +Provides a way to test prolog link:prolog-cookbook.html[submit rules]. + +OPTIONS +------- +-s:: + Reads a rules.pl file from stdin instead of rules.pl in refs/meta/config. + +--no-filters:: + Don't run the submit_filter/2 from the parent projects of the specified change. + +--format:: + What output format to display the results in. ++ +-- +`text`:: Simple text based format. +`json`:: A JSON object described in link:json.html#submitRecord[submit record]. +`json_compact`:: Minimized JSON output. +-- + +ACCESS +------ +Can be used by anyone that has permission to read the specified changeset. + +EXAMPLES +-------- + + +Test submit_rule from stdin. +==== + $ cat non-author-codereview.pl | ssh -p 29418 review.example.com gerrit test-submit-rule -s I78f2c6673db24e4e92ed32f604c960dc952437d9 + Non-Author-Code-Review: NOT_READY + Verified: NOT_READY + Code-Review: NOT_READY by Anonymous Coward <test@email.com> + + NOT_READY +==== + +Test submit_rule from stdin and return the results as JSON. +==== + cat non-author-codereview.pl | ssh -p 29418 review.example.com gerrit test-submit-rule --format=JSON -s I78f2c6673db24e4e92ed32f604c960dc952437d9 + { + "approvals": [ + { + "type": "Verified", + "value": "NEED" + }, + { + "type": "Code-Review", + "value": "OK", + "by": { + "email": "test@email.com", + "username": "test" + } + } + ], + "value": "NOT_READY" + } +==== + +Test the active submit_rule from the refs/meta/config branch, ignoring filters in the project parents. +==== + $ ssh -p 29418 review.example.com gerrit test-submit-rule I78f2c6673db24e4e92ed32f604c960dc952437d9 --no-filters + Verified: NOT_READY + Code-Review: NOT_READY by Anonymous Coward <test@email.com> + + NOT_READY +==== + +SCRIPTING +--------- +Can be used either interactively for testing new prolog submit rules, or from a script to check the submit status of a change. + +GERRIT +------ +Part of link:index.html[Gerrit Code Review] diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java index 0863623d5d..db4b0217b3 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java @@ -570,7 +570,7 @@ public class ChangeControl { && who.arg(0).isInteger(); } - private static Term toListTerm(List<Term> terms) { + public static Term toListTerm(List<Term> terms) { Term list = Prolog.Nil; for (int i = terms.size() - 1; i >= 0; i--) { list = new ListTerm(terms.get(i), list); diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java index db4e985d01..90bc07e831 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java @@ -31,6 +31,7 @@ public class MasterCommandModule extends CommandModule { command(gerrit, "rename-group").to(RenameGroupCommand.class); command(gerrit, "create-project").to(CreateProjectCommand.class); command(gerrit, "gsql").to(AdminQueryShell.class); + command(gerrit, "test-submit-rule").to(TestSubmitRule.class); command(gerrit, "set-reviewers").to(SetReviewersCommand.class); command(gerrit, "receive-pack").to(Receive.class); command(gerrit, "set-project-parent").to(AdminSetParent.class); diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRule.java new file mode 100644 index 0000000000..9a81140360 --- /dev/null +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRule.java @@ -0,0 +1,240 @@ +// 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.reviewdb.client.Project; +import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.rules.PrologEnvironment; +import com.google.gerrit.rules.StoredValues; +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.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.ProjectState; +import com.google.gerrit.sshd.SshCommand; +import com.google.gson.reflect.TypeToken; +import com.google.inject.Inject; + +import com.googlecode.prolog_cafe.compiler.CompileException; +import com.googlecode.prolog_cafe.lang.BufferingPrologControl; +import com.googlecode.prolog_cafe.lang.JavaObjectTerm; +import com.googlecode.prolog_cafe.lang.ListTerm; +import com.googlecode.prolog_cafe.lang.Prolog; +import com.googlecode.prolog_cafe.lang.PrologClassLoader; +import com.googlecode.prolog_cafe.lang.PrologException; +import com.googlecode.prolog_cafe.lang.PrologMachineCopy; +import com.googlecode.prolog_cafe.lang.SymbolTerm; +import com.googlecode.prolog_cafe.lang.Term; +import com.googlecode.prolog_cafe.lang.VariableTerm; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** Command that allows testing of prolog submit-rules in a live instance. */ +final class TestSubmitRule extends SshCommand { + @Inject + private ReviewDb db; + + @Inject + private PrologEnvironment.Factory envFactory; + + @Inject + private ChangeControl.Factory ccFactory; + + @Inject + private AccountCache accountCache; + + final @AnonymousCowardName String anonymousCowardName; + + @Argument(index = 0, required = true, usage = "ChangeId to load in prolog environment") + private String changeId; + + @Option(name = "-s", + usage = "Read prolog script from stdin instead of reading rules.pl from the refs/meta/config branch") + private boolean useStdin; + + @Option(name = "--format", metaVar = "FMT", usage = "Output display format") + private OutputFormat format = OutputFormat.TEXT; + + @Option(name = "--no-filters", aliases = {"-n"}, + usage = "Don't run the submit_filter/2 from the parent projects") + private boolean skipSubmitFilters; + + private static final String[] PACKAGE_LIST = {Prolog.BUILTIN, "gerrit"}; + + @Inject + public TestSubmitRule(@AnonymousCowardName String anonymous) { + anonymousCowardName = anonymous; + } + private PrologMachineCopy newMachine() { + BufferingPrologControl ctl = new BufferingPrologControl(); + ctl.setMaxDatabaseSize(16 * 1024); + ctl.setPrologClassLoader(new PrologClassLoader(getClass().getClassLoader())); + return PrologMachineCopy.save(ctl); + } + + @Override + protected void run() throws UnloggedFailure { + InputStreamReader inReader = new InputStreamReader(in); + + try { + PrologEnvironment pcl; + + List<Change> changeList = + db.changes().byKey(new Change.Key(changeId)).toList(); + if (changeList.size() != 1) + throw new UnloggedFailure(1, "Invalid ChangeId"); + + Change c = changeList.get(0); + PatchSet ps = db.patchSets().get(c.currentPatchSetId()); + // 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 cc = ccFactory.controlFor(c); + ProjectState projectState = cc.getProjectControl().getProjectState(); + + if (useStdin) { + pcl = envFactory.create(newMachine()); + } else { + pcl = projectState.newPrologEnvironment(); + } + + pcl.set(StoredValues.REVIEW_DB, db); + pcl.set(StoredValues.CHANGE, c); + pcl.set(StoredValues.PATCH_SET, ps); + pcl.set(StoredValues.CHANGE_CONTROL, cc); + if (useStdin) { + pcl.initialize(PACKAGE_LIST); + pcl.execute(Prolog.BUILTIN, "consult_stream", + SymbolTerm.intern("stdin"), new JavaObjectTerm(inReader)); + } + + List<Term> results = new ArrayList<Term>(); + Term submitRule = + pcl.once("gerrit", "locate_submit_rule", new VariableTerm()); + + for (Term[] template : pcl.all("gerrit", "can_submit", submitRule, + new VariableTerm())) { + results.add(template[1]); + } + + if (!skipSubmitFilters) { + runSubmitFilters(projectState, results, pcl); + } + + List<SubmitRecord> res = cc.resultsToSubmitRecord(submitRule, results); + 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"); + } + } + } catch (Exception e) { + throw new UnloggedFailure("Processing of prolog script failed: " + e); + } + } + + private void runSubmitFilters(ProjectState projectState, List<Term> results, + PrologEnvironment pcl) throws UnloggedFailure { + ProjectState parentState = projectState.getParentState(); + PrologEnvironment childEnv = pcl; + Set<Project.NameKey> projectsSeen = new HashSet<Project.NameKey>(); + projectsSeen.add(projectState.getProject().getNameKey()); + + while (parentState != null) { + if (!projectsSeen.add(parentState.getProject().getNameKey())) { + // parent has been seen before, stop walk up inheritance tree + break; + } + PrologEnvironment parentEnv; + try { + parentEnv = parentState.newPrologEnvironment(); + } catch (CompileException err) { + throw new UnloggedFailure("Cannot consult rules.pl for " + + parentState.getProject().getName() + err); + } + + parentEnv.copyStoredValues(childEnv); + Term filterRule = + parentEnv.once("gerrit", "locate_submit_filter", new VariableTerm()); + if (filterRule != null) { + try { + Term resultsTerm = ChangeControl.toListTerm(results); + results.clear(); + Term[] template = + parentEnv.once("gerrit", "filter_submit_results", filterRule, + resultsTerm, new VariableTerm()); + @SuppressWarnings("unchecked") + final List<? extends Term> termList = + ((ListTerm) template[2]).toJava(); + results.addAll(termList); + } catch (PrologException err) { + throw new UnloggedFailure("Exception calling " + filterRule + " of " + + parentState.getProject().getName() + err); + } catch (RuntimeException err) { + throw new UnloggedFailure("Exception calling " + filterRule + " of " + + parentState.getProject().getName() + err); + } + } + + parentState = parentState.getParentState(); + childEnv = parentEnv; + } + } +} |