Support to import tasks from group refs This change adds support to import a single task from any group ref (All-Users.git:/refs/groups/..) by specifying the group name or group uuid in task config file. See documentation for usage details. Release-Notes: tasks can now be imported from group refs Release-Notes: a task reference cannot start with '%' unless referring to a group Change-Id: Ie0b346acff17de14768676ca0c64b61f8d345610
diff --git a/src/main/antlr4/com/googlesource/gerrit/plugins/task/TaskReference.g4 b/src/main/antlr4/com/googlesource/gerrit/plugins/task/TaskReference.g4 index fc461d7..3869643 100644 --- a/src/main/antlr4/com/googlesource/gerrit/plugins/task/TaskReference.g4 +++ b/src/main/antlr4/com/googlesource/gerrit/plugins/task/TaskReference.g4
@@ -19,6 +19,8 @@ * TASK_REFERENCE = [ * [ // TASK_FILE_PATH ] | * [ @USERNAME [ TASK_FILE_PATH ] ] | + * [ %GROUP_NAME [ TASK_FILE_PATH ] ] | + * [ %%GROUP_UUID [ TASK_FILE_PATH ] ] | * [ TASK_FILE_PATH ] * ] '^' TASK_NAME * @@ -63,6 +65,26 @@ * reference: //^simple * Implied task: * file: All-Projects:refs/meta/config:task.config task: sample + * + * Suppose a8341ade45d83e867c24a2d37f47b410cfdbea6d is the UUID of 'CI System Owners' group. + * file: Any projects, ref, file + * reference: %CI System Owners^sample + * Implied task: + * file: All-Users:refs/groups/a8/a8341ade45d83e867c24a2d37f47b410cfdbea6d:task.config + * task: sample + * + * file: Any projects, ref, file + * reference: %CI System Owners/foo^simple + * Implied task: + * file: All-Users:refs/groups/a8/a8341ade45d83e867c24a2d37f47b410cfdbea6d:task/foo^simple + * task: sample + * + * file: Any projects, ref, file + * reference: %%a8341ade45d83e867c24a2d37f47b410cfdbea6d^sample + * Implied task: + * file: All-Users:refs/groups/a8/a8341ade45d83e867c24a2d37f47b410cfdbea6d:task.config + * task: sample + * */ grammar TaskReference; @@ -79,6 +101,8 @@ : ALL_PROJECTS_ROOT | FWD_SLASH absolute TASK_DELIMETER | user absolute? TASK_DELIMETER + | group_name absolute? TASK_DELIMETER + | group_uuid absolute? TASK_DELIMETER | (absolute| relative)? TASK_DELIMETER ; @@ -86,6 +110,14 @@ : '@' NAME ; +group_name + : '%' (NAME | NAME_WITH_SPACES) + ; + +group_uuid + : '%%' INTERNAL_GROUP_UUID + ; + absolute : FWD_SLASH relative ; @@ -102,23 +134,43 @@ : (~'^')+ EOF ; +INTERNAL_GROUP_UUID + : HEX_10 HEX_10 HEX_10 HEX_10 + ; + +fragment HEX_10 + : HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX + ; + +fragment HEX + : [0-9a-f] + ; + NAME - : URL_ALLOWED_CHARS_EXCEPT_FWD_SLASH_AND_AT URL_ALLOWED_CHARS_EXCEPT_FWD_SLASH* + : URL_ALLOWED_CHARS_EXCEPT_FWD_SLASH_AND_AT_AND_PERCENTILE URL_ALLOWED_CHARS_EXCEPT_FWD_SLASH* + ; + +NAME_WITH_SPACES + : URL_ALLOWED_CHARS_EXCEPT_FWD_SLASH_AND_AT_AND_PERCENTILE (URL_ALLOWED_CHARS_EXCEPT_FWD_SLASH | SPACE)* ; fragment URL_ALLOWED_CHARS_EXCEPT_FWD_SLASH - : URL_ALLOWED_CHARS_EXCEPT_FWD_SLASH_AND_AT - | '@' + : URL_ALLOWED_CHARS_EXCEPT_FWD_SLASH_AND_AT_AND_PERCENTILE + | '@' | '%' ; -fragment URL_ALLOWED_CHARS_EXCEPT_FWD_SLASH_AND_AT +fragment URL_ALLOWED_CHARS_EXCEPT_FWD_SLASH_AND_AT_AND_PERCENTILE : ':' | '?' | '#' | '[' | ']' |'!' | '$' | '&' | '\'' | '(' | ')' - | '*' | '+' | ',' | ';' | '=' | '%' + | '*' | '+' | ',' | ';' | '=' | 'A'..'Z' | 'a'..'z' | '0'..'9' | '_' | '.' | '\\' | '-' | '~' ; +fragment SPACE + : ' ' + ; + TASK_DELIMETER : '^' ;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/TaskKey.java b/src/main/java/com/googlesource/gerrit/plugins/task/TaskKey.java index a6bc7e1..bd0b683 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/task/TaskKey.java +++ b/src/main/java/com/googlesource/gerrit/plugins/task/TaskKey.java
@@ -16,9 +16,11 @@ import com.google.auto.value.AutoValue; import com.google.common.base.Preconditions; +import com.google.gerrit.entities.AccountGroup; import com.google.gerrit.entities.BranchNameKey; import com.google.gerrit.entities.RefNames; import com.google.gerrit.server.account.AccountCache; +import com.google.gerrit.server.account.GroupCache; import com.google.gerrit.server.config.AllProjectsName; import com.google.gerrit.server.config.AllUsersName; import java.nio.file.Path; @@ -66,16 +68,19 @@ protected BranchNameKey branch; protected String file; protected String task; + protected GroupCache groupCache; Builder( FileKey relativeTo, AllProjectsName allProjectsName, AllUsersName allUsersName, - AccountCache accountCache) { + AccountCache accountCache, + GroupCache groupCache) { this.relativeTo = relativeTo; this.allProjectsName = allProjectsName; this.allUsersName = allUsersName; this.accountCache = accountCache; + this.groupCache = groupCache; } public TaskKey buildTaskKey() { @@ -134,6 +139,34 @@ .id())); } + public void setGroupName(String groupName) throws ConfigInvalidException { + branch = + BranchNameKey.create( + allUsersName, + RefNames.refsGroups( + groupCache + .get(AccountGroup.nameKey(groupName)) + .orElseThrow( + () -> + new ConfigInvalidException( + String.format("Cannot resolve group name: %s", groupName))) + .getGroupUUID())); + } + + public void setGroupUUID(String uuid) throws ConfigInvalidException { + branch = + BranchNameKey.create( + allUsersName, + RefNames.refsGroups( + groupCache + .get(AccountGroup.uuid(uuid)) + .orElseThrow( + () -> + new ConfigInvalidException( + String.format("Cannot resolve group uuid: %s", uuid))) + .getGroupUUID())); + } + public void setReferringAllProjectsTask() { branch = BranchNameKey.create(allProjectsName, RefNames.REFS_CONFIG); }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/TaskReference.java b/src/main/java/com/googlesource/gerrit/plugins/task/TaskReference.java index 91c6f10..a7824ac 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/task/TaskReference.java +++ b/src/main/java/com/googlesource/gerrit/plugins/task/TaskReference.java
@@ -16,6 +16,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.gerrit.server.account.AccountCache; +import com.google.gerrit.server.account.GroupCache; import com.google.gerrit.server.config.AllProjectsNameProvider; import com.google.gerrit.server.config.AllUsersNameProvider; import com.google.inject.Inject; @@ -46,11 +47,16 @@ AllProjectsNameProvider allProjectsNameProvider, AllUsersNameProvider allUsersNameProvider, AccountCache accountCache, + GroupCache groupCache, @Assisted FileKey relativeTo, @Assisted String reference) { this( new TaskKey.Builder( - relativeTo, allProjectsNameProvider.get(), allUsersNameProvider.get(), accountCache), + relativeTo, + allProjectsNameProvider.get(), + allUsersNameProvider.get(), + accountCache, + groupCache), reference); } @@ -148,5 +154,27 @@ throw new RuntimeConfigInvalidException(e); } } + + @Override + public void enterGroup_name(TaskReferenceParser.Group_nameContext ctx) { + try { + String groupName = + ctx.NAME() == null + ? (ctx.NAME_WITH_SPACES() == null ? "" : ctx.NAME_WITH_SPACES().getText()) + : ctx.NAME().getText(); + builder.setGroupName(groupName); + } catch (ConfigInvalidException e) { + throw new RuntimeConfigInvalidException(e); + } + } + + @Override + public void enterGroup_uuid(TaskReferenceParser.Group_uuidContext ctx) { + try { + builder.setGroupUUID(ctx.INTERNAL_GROUP_UUID().getText()); + } catch (ConfigInvalidException e) { + throw new RuntimeConfigInvalidException(e); + } + } } }
diff --git a/src/main/resources/Documentation/task_expression.md b/src/main/resources/Documentation/task_expression.md index 66afaf8..b631cb3 100644 --- a/src/main/resources/Documentation/task_expression.md +++ b/src/main/resources/Documentation/task_expression.md
@@ -40,6 +40,8 @@ TASK_REFERENCE = [ [ // TASK_FILE_PATH ] [ @USERNAME [ TASK_FILE_PATH ] ] | + [ %GROUP_NAME [ TASK_FILE_PATH ] ] | + [ %%GROUP_UUID [ TASK_FILE_PATH ] ] | [ TASK_FILE_PATH ] ] '^' TASK_NAME ``` @@ -181,3 +183,38 @@ preload-task = //^root task ... ``` + +To reference a task from a specific group ref (All-Users.git:refs/groups/<sharded-group-uuid>), +specify the group name with `%` or group uuid with `%%`. + +When referencing from group refs, to get task from top level task.config on a group ref use +`%<group_name>^<task_name>` or `%%<group_uuid>^<task_name>` and to get any task under the +task directory use the relative path, +like: `%<group_name>/<relative path from task dir>^<task_name>` or +`%%<group_uuid>/<relative path from task dir>^<task_name>`. +It doesn't matter which project, ref and file one is referencing from while using this syntax. + +Example: +Assumption: Group uuid of group_a is 720269095421a08a24889e29d092df1839a7a706 + +All-Users:refs/groups/72/720269095421a08a24889e29d092df1839a7a706:task.config +``` + ... + [task "top level task"] + ... +``` + +All-Users:refs/groups/72/720269095421a08a24889e29d092df1839a7a706:/task/dir/common.config +``` + ... + [task "common task"] + ... +``` + +All-Projects:refs/meta/config:/task.config +``` + ... + preload-task = %group_a^top level task + preload-task = %%720269095421a08a24889e29d092df1839a7a706/dir/common.config^common task + ... +```
diff --git a/src/main/resources/Documentation/test/task-preview/subtask_using_group_syntax/root_with_subtask_non-secret_ref_with_subtask_secret_ref.md b/src/main/resources/Documentation/test/task-preview/subtask_using_group_syntax/root_with_subtask_non-secret_ref_with_subtask_secret_ref.md new file mode 100644 index 0000000..504d5a8 --- /dev/null +++ b/src/main/resources/Documentation/test/task-preview/subtask_using_group_syntax/root_with_subtask_non-secret_ref_with_subtask_secret_ref.md
@@ -0,0 +1,52 @@ +# --task-preview root file with subtask pointing to a non-secret group ref with subtask pointing to a secret group ref. + +file: `All-Projects.git:refs/meta/config:task.config` +``` + [root "Root Preview NON-SECRET group subtask with SECRET group subtask"] + applicable = "is:open" + pass = True ++ subtask = %{non_secret_group_name}/secret_external.config^NON-SECRET with SECRET subtask +``` + +file: `All-Users.git:refs/groups/{sharded_non_secret_group_uuid}:task/secret_external.config` +``` +[task "NON-SECRET with SECRET subtask"] + applicable = is:open + pass = True + subtask = %{secret_group_name}/secret.config^SECRET task +``` + +file: `All-Users:refs/groups/{sharded_secret_group_uuid}:task/secret.config` +``` +[task "SECRET task"] + applicable = is:open + pass = Fail +``` + +json: +``` +{ + "applicable" : true, + "hasPass" : true, + "name" : "Root Preview NON-SECRET group subtask with SECRET group subtask", + "status" : "WAITING", + "subTasks" : [ + { + "applicable" : true, + "hasPass" : true, + "name" : "NON-SECRET with SECRET subtask", + "status" : "WAITING", + "subTasks" : [ + { + "name" : "UNKNOWN", # Only Test Suite: non-secret + "status" : "UNKNOWN" # Only Test Suite: non-secret + "applicable" : true, # Only Test Suite: secret + "hasPass" : true, # Only Test Suite: secret + "name" : "SECRET task", # Only Test Suite: secret + "status" : "READY" # Only Test Suite: secret + } + ] + } + ] +} +```
diff --git a/src/main/resources/Documentation/test/task-preview/subtask_using_group_syntax/root_with_subtask_secret_ref.md b/src/main/resources/Documentation/test/task-preview/subtask_using_group_syntax/root_with_subtask_secret_ref.md new file mode 100644 index 0000000..9a1932c --- /dev/null +++ b/src/main/resources/Documentation/test/task-preview/subtask_using_group_syntax/root_with_subtask_secret_ref.md
@@ -0,0 +1,36 @@ +# --task-preview root file with subtask pointing to secret group ref + +file: `All-Projects.git:refs/meta/config:task.config` +``` + [root "Root Preview SECRET external group"] + applicable = is:open + pass = True ++ subtask = %{secret_group_name}/secret.config^SECRET Task +``` + +file: `All-Users.git:refs/groups/{sharded_secret_group_uuid}:task/secret.config` +``` +[task "SECRET Task"] + applicable = is:open + pass = Fail +``` + +json: +``` +{ + "applicable" : true, + "hasPass" : true, + "name" : "Root Preview SECRET external group", + "status" : "WAITING", + "subTasks" : [ + { + "name" : "UNKNOWN", # Only Test Suite: non-secret + "status" : "UNKNOWN" # Only Test Suite: non-secret + "applicable" : true, # Only Test Suite: secret + "hasPass" : true, # Only Test Suite: secret + "name" : "SECRET Task", # Only Test Suite: secret + "status" : "READY" # Only Test Suite: secret + } + ] +} +```
diff --git a/src/main/resources/Documentation/test/task_states.md b/src/main/resources/Documentation/test/task_states.md index f4f8842..2df1263 100644 --- a/src/main/resources/Documentation/test/task_states.md +++ b/src/main/resources/Documentation/test/task_states.md
@@ -2318,11 +2318,67 @@ ] } +[root "Root Import group tasks"] + applicable = is:open + subtask = %{non_secret_group_name_without_space}/foo/bar.config^Absolute Task 1 + subtask = %{non_secret_group_name_without_space}^task in group root config file 1 + subtask = %{non_secret_group_name_with_space}/foo/bar.config^Absolute Task 3 + subtask = %{non_secret_group_name_with_space}^task in group root config file 3 + subtask = %%{non_secret_group_uuid}/foo/bar.config^Absolute Task 2 + subtask = %%{non_secret_group_uuid}^task in group root config file 2 + +{ + "applicable" : true, + "hasPass" : false, + "name" : "Root Import group tasks", + "status" : "PASS", + "subTasks" : [ + { + "applicable" : true, + "hasPass" : true, + "name" : "Absolute Task 1", + "status" : "PASS" + }, + { + "applicable" : true, + "hasPass" : true, + "name" : "task in group root config file 1", + "status" : "PASS" + }, + { + "applicable" : true, + "hasPass" : true, + "name" : "Absolute Task 3", + "status" : "PASS" + }, + { + "applicable" : true, + "hasPass" : true, + "name" : "task in group root config file 3", + "status" : "PASS" + }, + { + "applicable" : true, + "hasPass" : true, + "name" : "Absolute Task 2", + "status" : "PASS" + }, + { + "applicable" : true, + "hasPass" : true, + "name" : "task in group root config file 2", + "status" : "PASS" + } + ] +} + [root "Root Reference tasks from All-Projects"] applicable = is:open subtask = //^Subtask PASS subtask = @testuser/dir/relative.config^Import All-Projects root task subtask = @testuser/dir/relative.config^Import All-Projects non-root task + subtask = %{non_secret_group_name_without_space}/dir/relative.config^Import All-Projects root task - groups + subtask = %{non_secret_group_name_without_space}/dir/relative.config^Import All-Projects non-root task - groups { "applicable" : true, @@ -2363,6 +2419,34 @@ "status" : "PASS" } ] + }, + { + "applicable" : true, + "hasPass" : false, + "name" : "Import All-Projects root task - groups", + "status" : "PASS", + "subTasks" : [ + { + "applicable" : true, + "hasPass" : true, + "name" : "Subtask PASS", + "status" : "PASS" + } + ] + }, + { + "applicable" : true, + "hasPass" : false, + "name" : "Import All-Projects non-root task - groups", + "status" : "PASS", + "subTasks" : [ + { + "applicable" : true, + "hasPass" : true, + "name" : "Sample relative task in sub dir", + "status" : "PASS" + } + ] } ] } @@ -3206,3 +3290,50 @@ applicable = is:open pass = is:open ``` + +file: `All-Users:refs/groups/{sharded_non_secret_group_uuid_without_space}:task/dir/relative.config` +``` +[task "Import All-Projects root task - groups"] + applicable = is:open + subtask = //^Subtask PASS + +[task "Import All-Projects non-root task - groups"] + applicable = is:open + subtask = //dir/common.config^Sample relative task in sub dir +``` + +file: `All-Users:refs/groups/{sharded_non_secret_group_uuid_without_space}:task/foo/bar.config` +``` +[task "Absolute Task 1"] + applicable = is:open + pass = is:open + +[task "Absolute Task 2"] + applicable = is:open + pass = is:open +``` + +file: `All-Users:refs/groups/{sharded_non_secret_group_uuid_without_space}:task.config` +``` +[task "task in group root config file 1"] + applicable = is:open + pass = is:open + +[task "task in group root config file 2"] + applicable = is:open + pass = is:open +``` + +file: `All-Users:refs/groups/{sharded_non_secret_group_uuid_with_space}:task/foo/bar.config` +``` +[task "Absolute Task 3"] + applicable = is:open + pass = is:open +``` + +file: `All-Users:refs/groups/{sharded_non_secret_group_uuid_with_space}:task.config` +``` +[task "task in group root config file 3"] + applicable = is:open + pass = is:open +```
diff --git a/src/test/java/com/googlesource/gerrit/plugins/task/TaskExpressionTest.java b/src/test/java/com/googlesource/gerrit/plugins/task/TaskExpressionTest.java index 87042a9..8ea0009 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/task/TaskExpressionTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/task/TaskExpressionTest.java
@@ -17,6 +17,7 @@ import com.google.gerrit.entities.BranchNameKey; import com.google.gerrit.entities.Project; import com.google.gerrit.server.account.AccountCache; +import com.google.gerrit.server.account.GroupCache; import com.google.gerrit.server.config.AllProjectsName; import com.google.gerrit.server.config.AllUsersName; import java.util.Iterator; @@ -233,6 +234,7 @@ protected TaskExpression getTaskExpression(FileKey file, String expression) { AccountCache accountCache = Mockito.mock(AccountCache.class); + GroupCache groupCache = Mockito.mock(GroupCache.class); TaskReference.Factory factory = Mockito.mock(TaskReference.Factory.class); Mockito.when(factory.create(Mockito.any(), Mockito.any())) .thenAnswer( @@ -242,7 +244,8 @@ (FileKey) invocation.getArguments()[0], new AllProjectsName("All-Projects"), new AllUsersName("All-Users"), - accountCache), + accountCache, + groupCache), (String) invocation.getArguments()[1])); return new TaskExpression(factory, file, expression); }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/task/TaskReferenceTest.java b/src/test/java/com/googlesource/gerrit/plugins/task/TaskReferenceTest.java index 9da1f44..e08240d 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/task/TaskReferenceTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/task/TaskReferenceTest.java
@@ -14,12 +14,16 @@ package com.googlesource.gerrit.plugins.task; +import com.google.common.collect.ImmutableSet; import com.google.gerrit.entities.Account; +import com.google.gerrit.entities.AccountGroup; import com.google.gerrit.entities.BranchNameKey; +import com.google.gerrit.entities.InternalGroup; import com.google.gerrit.entities.Project; import com.google.gerrit.entities.RefNames; import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.account.AccountState; +import com.google.gerrit.server.account.GroupCache; import com.google.gerrit.server.config.AllProjectsName; import com.google.gerrit.server.config.AllUsersName; import java.sql.Timestamp; @@ -54,6 +58,41 @@ public static final FileKey TEST_USER_COMMON_CFG = createFileKey(ALL_USERS, TEST_USER_REF, COMMON); + public static final AccountGroup.NameKey TEST_GROUP1_NAME = AccountGroup.nameKey("testgroup"); + public static final AccountGroup.NameKey TEST_GROUP2_NAME = AccountGroup.nameKey("test group"); + public static final String TEST_GROUP1_UUID = "526d2bf882635380fbd3b72320464e342fc14533"; + public static final String TEST_GROUP2_UUID = "62aa5663241f31b9483bad66132bd5d416b2bef9"; + public static final InternalGroup TEST_GROUP1 = + buildTestGroup(AccountGroup.id(1), TEST_GROUP1_NAME, AccountGroup.uuid(TEST_GROUP1_UUID)); + public static final InternalGroup TEST_GROUP2 = + buildTestGroup(AccountGroup.id(2), TEST_GROUP2_NAME, AccountGroup.uuid(TEST_GROUP2_UUID)); + public static final String TEST_GROUP1_REF = + "refs/groups/" + TEST_GROUP1_UUID.substring(0, 2) + "/" + TEST_GROUP1_UUID; + public static final String TEST_GROUP2_REF = + "refs/groups/" + TEST_GROUP2_UUID.substring(0, 2) + "/" + TEST_GROUP2_UUID; + public static final FileKey TEST_GROUP1_ROOT_CFG = + createFileKey(ALL_USERS, TEST_GROUP1_REF, ROOT); + public static final FileKey TEST_GROUP1_COMMON_CFG = + createFileKey(ALL_USERS, TEST_GROUP1_REF, COMMON); + public static final FileKey TEST_GROUP2_ROOT_CFG = + createFileKey(ALL_USERS, TEST_GROUP2_REF, ROOT); + public static final FileKey TEST_GROUP2_COMMON_CFG = + createFileKey(ALL_USERS, TEST_GROUP2_REF, COMMON); + + static InternalGroup buildTestGroup( + AccountGroup.Id id, AccountGroup.NameKey nameKey, AccountGroup.UUID uuid) { + return InternalGroup.builder() + .setGroupUUID(uuid) + .setNameKey(nameKey) + .setOwnerGroupUUID(uuid) + .setId(id) + .setVisibleToAll(true) + .setCreatedOn(new Timestamp(0L)) + .setMembers(ImmutableSet.of()) + .setSubgroups(ImmutableSet.of()) + .build(); + } + @Test public void testReferencingTaskFromSameFile() throws Exception { assertEquals(createTaskKey(ROOT_CFG, SIMPLE), getTaskFromReference(ROOT_CFG, SIMPLE)); @@ -137,17 +176,98 @@ assertNoSuchElementException(() -> getTaskFromReference(SUB_COMMON_CFG, empty)); } + @Test + public void testReferencingRootGroupNameWithoutSpaceTask() throws Exception { + String reference = "%" + TEST_GROUP1_NAME.get() + "^" + SIMPLE; + assertEquals( + createTaskKey(TEST_GROUP1_ROOT_CFG, SIMPLE), + getTaskFromReference(SUB_COMMON_CFG, reference)); + } + + @Test + public void testReferencingRootGroupNameWithSpaceTask() throws Exception { + String reference = "%" + TEST_GROUP2_NAME.get() + "^" + SIMPLE; + assertEquals( + createTaskKey(TEST_GROUP2_ROOT_CFG, SIMPLE), + getTaskFromReference(SUB_COMMON_CFG, reference)); + } + + @Test + public void testReferencingGroupNameWithoutSpaceTaskDir() throws Exception { + String reference = "%" + TEST_GROUP1_NAME.get() + "/common.config^" + SIMPLE; + assertEquals( + createTaskKey(TEST_GROUP1_COMMON_CFG, SIMPLE), + getTaskFromReference(SUB_COMMON_CFG, reference)); + } + + @Test + public void testReferencingUnknownGroupName() throws Exception { + String reference = "%unknown^" + SIMPLE; + assertNoSuchElementException(() -> getTaskFromReference(SUB_COMMON_CFG, reference)); + } + + @Test + public void testReferencingEmptyGroupName() throws Exception { + String reference = "%^" + SIMPLE; + assertNoSuchElementException(() -> getTaskFromReference(SUB_COMMON_CFG, reference)); + } + + @Test + public void testReferencingGroupNameWithSpaceTaskDir() throws Exception { + String reference = "%" + TEST_GROUP2_NAME.get() + "/common.config^" + SIMPLE; + assertEquals( + createTaskKey(TEST_GROUP2_COMMON_CFG, SIMPLE), + getTaskFromReference(SUB_COMMON_CFG, reference)); + } + + @Test + public void testReferencingRootGroupUUIDTask() throws Exception { + String reference = "%%" + TEST_GROUP1_UUID + "^" + SIMPLE; + assertEquals( + createTaskKey(TEST_GROUP1_ROOT_CFG, SIMPLE), + getTaskFromReference(SUB_COMMON_CFG, reference)); + } + + @Test + public void testReferencingGroupUUIDTaskDir() throws Exception { + String reference = "%%" + TEST_GROUP1_UUID + "/common.config^" + SIMPLE; + assertEquals( + createTaskKey(TEST_GROUP1_COMMON_CFG, SIMPLE), + getTaskFromReference(SUB_COMMON_CFG, reference)); + } + + @Test + public void testReferencingUnknownGroupUUID() throws Exception { + String reference = "%%a8341ade45d83e867c24a2d37f47b410cfdbea6d^" + SIMPLE; + assertNoSuchElementException(() -> getTaskFromReference(SUB_COMMON_CFG, reference)); + } + + @Test + public void testReferencingEmptyGroupUUID() throws Exception { + String reference = "%%^" + SIMPLE; + assertNoSuchElementException(() -> getTaskFromReference(SUB_COMMON_CFG, reference)); + } + protected static TaskKey getTaskFromReference(FileKey file, String expression) { AccountCache accountCache = Mockito.mock(AccountCache.class); + GroupCache groupCache = Mockito.mock(GroupCache.class); Mockito.when(accountCache.getByUsername(TEST_USER)) .thenReturn(Optional.of(AccountState.forAccount(TEST_USER_ACCOUNT))); + Mockito.when(groupCache.get(TEST_GROUP1_NAME)).thenReturn(Optional.of(TEST_GROUP1)); + Mockito.when(groupCache.get(TEST_GROUP2_NAME)).thenReturn(Optional.of(TEST_GROUP2)); + Mockito.when(groupCache.get(AccountGroup.uuid(TEST_GROUP1_UUID))) + .thenReturn(Optional.of(TEST_GROUP1)); + Mockito.when(groupCache.get(AccountGroup.uuid(TEST_GROUP2_UUID))) + .thenReturn(Optional.of(TEST_GROUP2)); + try { return new TaskReference( new TaskKey.Builder( file, new AllProjectsName(ALL_PROJECTS), new AllUsersName(ALL_USERS), - accountCache), + accountCache, + groupCache), expression) .getTaskKey(); } catch (ConfigInvalidException e) {
diff --git a/test/check_task_statuses.sh b/test/check_task_statuses.sh index fc7e4c6..1e262b3 100755 --- a/test/check_task_statuses.sh +++ b/test/check_task_statuses.sh
@@ -23,14 +23,31 @@ # 6. All-Users.git - must have 'read' rights on refs/users/${shardeduserid} for Registered Users # 7. All-Users.git - must have 'create' rights on refs/users/${shardeduserid} for Registered Users # 8. All-Users.git - must deny 'read' rights on refs/* for Anonymous Users +# 9. GERRIT_GIT_DIR environment variable must have the path to gerrit +# site's git directory (as group ref updates are done directly to git). create_configs_from_task_states() { for marker in $(md_file_markers "$DOC_STATES") ; do - local project="$OUT/$(md_file_marker_project "$marker")" + local project_name="$(md_file_marker_project "$marker")" + local project_dir="$OUT/$project_name" local file="$(md_file_marker_file "$marker")" + local ref="$(md_file_marker_ref "$marker")" - mkdir -p "$(dirname "$project/$file")" - md_marker_content "$DOC_STATES" "$marker" | replace_user | testdoc_2_cfg > "$project/$file" + if [[ "$ref" == refs/groups/* ]] ; then + project_dir="$project_dir-${ref:(-7)}}" + q_setup setup_repo "$project_dir" "$REMOTE_USERS" "$ref" + fi + + mkdir -p "$(dirname "$project_dir/$file")" + md_marker_content "$DOC_STATES" "$marker" | replace_user \ + | testdoc_2_cfg > "$project_dir/$file" + + if [[ "$ref" == refs/groups/* ]] ; then + # As support for pushing a change to group refs [1] is not yet in any release, + # push the update behind gerrit's back, directly into git. + # [1] https://gerrit-review.googlesource.com/c/gerrit/+/390614 + q_setup update_repo "$project_dir" "$GERRIT_GIT_DIR/All-Users.git" "$ref" + fi done } @@ -53,12 +70,14 @@ "$MYPROG" --server <gerrit_host> --non-secret-user <non-secret user> --untrusted-user <untrusted user> - --help|-h help text - --server|-s gerrit host - --non-secret-user user who don't have permission - to view other user refs. - --untrusted-user user who doesn't have permission - to view refs/meta/config ref on All-Projects repo + --help|-h help text + --server|-s gerrit host + --non-secret-user user who don't have permission + to view other user refs. + --untrusted-user user who doesn't have permission + to view refs/meta/config ref on All-Projects repo + --non-secret-group-without-space non-secret group name without spaces + --non-secret-group-with-space non-secret group name with spaces EOF [ -n "$1" ] && { echo "Error: $1" ; exit 1 ; } @@ -90,11 +109,13 @@ while (( "$#" )) ; do case "$1" in - --help|-h) usage ;; - --server|-s) shift ; SERVER=$1 ;; - --non-secret-user) shift ; NON_SECRET_USER=$1 ;; - --untrusted-user) shift ; UNTRUSTED_USER=$1 ;; - *) usage "invalid argument $1" ;; + --help|-h) usage ;; + --server|-s) shift ; SERVER=$1 ;; + --non-secret-user) shift ; NON_SECRET_USER=$1 ;; + --untrusted-user) shift ; UNTRUSTED_USER=$1 ;; + --non-secret-group-without-space) shift ; GROUP_NAME_WITHOUT_SPACE=$1 ;; + --non-secret-group-with-space) shift ; GROUP_NAME_WITH_SPACE=$1 ;; + *) usage "invalid argument $1" ;; esac shift done @@ -102,6 +123,9 @@ [ -z "$SERVER" ] && usage "You must specify --server" [ -z "$NON_SECRET_USER" ] && usage "You must specify --non-secret-user" [ -z "$UNTRUSTED_USER" ] && usage "You must specify --untrusted-user" +[ -z "$GROUP_NAME_WITHOUT_SPACE" ] && usage "You must specify --non-secret-group-without-space" +[ -z "$GROUP_NAME_WITH_SPACE" ] && usage "You must specify --non-secret-group-with-space" +[ -z "$GERRIT_GIT_DIR" ] && usage "GERRIT_GIT_DIR environment variable not set" PORT=29418 @@ -120,6 +144,13 @@ declare -A USER_REFS USER_REFS["{testuser_user_ref}"]="$(get_user_ref "$USER")" +declare -A GROUP_EXPANDED_BY_PLACEHOLDER +GROUP_EXPANDED_BY_PLACEHOLDER["{non_secret_group_name_without_space}"]="$GROUP_NAME_WITHOUT_SPACE" +GROUP_EXPANDED_BY_PLACEHOLDER["{non_secret_group_name_with_space}"]="$GROUP_NAME_WITH_SPACE" +GROUP_EXPANDED_BY_PLACEHOLDER["{non_secret_group_uuid}"]="$(get_group_uuid "$GROUP_NAME_WITHOUT_SPACE")" +GROUP_EXPANDED_BY_PLACEHOLDER["{sharded_non_secret_group_uuid_without_space}"]="$(get_sharded_group_uuid "$GROUP_NAME_WITHOUT_SPACE")" +GROUP_EXPANDED_BY_PLACEHOLDER["{sharded_non_secret_group_uuid_with_space}"]="$(get_sharded_group_uuid "$GROUP_NAME_WITH_SPACE")" + mkdir -p "$OUT" "$ALL_TASKS" "$USER_TASKS" q_setup setup_repo "$ALL" "$REMOTE_ALL" "$REF_ALL"
diff --git a/test/check_task_visibility.sh b/test/check_task_visibility.sh index b736337..1dabec4 100755 --- a/test/check_task_visibility.sh +++ b/test/check_task_visibility.sh
@@ -23,6 +23,8 @@ # 6. All-Users.git - must have 'read' rights on refs/users/${shardeduserid} for Registered Users # 7. All-Users.git - must have 'create' rights on refs/users/${shardeduserid} for Registered Users # 8. All-Users.git - must deny 'read' rights on refs/* for Anonymous Users +# 9. GERRIT_GIT_DIR environment variable must have the path to gerrit +# site's git directory (as group ref updates are done directly to git). readlink -f / &> /dev/null || readlink() { greadlink "$@" ; } # for MacOS MYDIR=$(dirname -- "$(readlink -f -- "$0")") @@ -139,7 +141,14 @@ echo "$tip_content" > "$project/$file" config_ensure "$project/$file" - q_setup update_repo "$project" "$(get_remote "$project")" "$ref" + if [[ "$ref" == refs/groups/* ]] ; then + # As support for pushing a change to group refs [1] is not yet in any release, + # push the update behind gerrit's back, directly into git. + # [1] https://gerrit-review.googlesource.com/c/gerrit/+/390614 + q_setup update_repo "$project" "$GERRIT_GIT_DIR/All-Users.git" "$ref" + else + q_setup update_repo "$project" "$(get_remote "$project")" "$ref" + fi done } @@ -166,6 +175,8 @@ --server|-s gerrit host --non-secret-user user who doesn't have permission to view other user refs. + --non-secret-group non-secret group name + --secret-group secret group name EOF [ -n "$1" ] && { echo "Error: $1" ; exit 1 ; } @@ -177,6 +188,8 @@ --help|-h) usage ;; --server|-s) shift ; SERVER=$1 ;; --non-secret-user) shift ; NON_SECRET_USER=$1 ;; + --non-secret-group) shift ; NON_SECRET_GROUP_NAME=$1 ;; + --secret-group) shift ; SECRET_GROUP_NAME=$1 ;; *) usage "invalid argument $1" ;; esac shift @@ -184,6 +197,9 @@ [ -z "$SERVER" ] && usage "You must specify --server" [ -z "$NON_SECRET_USER" ] && usage "You must specify --non-secret-user" +[ -z "$NON_SECRET_GROUP_NAME" ] && usage "You must specify --non-secret-group" +[ -z "$SECRET_GROUP_NAME" ] && usage "You must specify --secret-group" +[ -z "$GERRIT_GIT_DIR" ] && usage "GERRIT_GIT_DIR environment variable not set" RESULT=0 PORT=29418 @@ -202,6 +218,12 @@ USERS["{non_secret_user}"]="$NON_SECRET_USER" USER_REFS["{non_secret_user_ref}"]="$(get_user_ref "$NON_SECRET_USER")" +declare -A GROUP_EXPANDED_BY_PLACEHOLDER +GROUP_EXPANDED_BY_PLACEHOLDER["{secret_group_name}"]="$SECRET_GROUP_NAME" +GROUP_EXPANDED_BY_PLACEHOLDER["{sharded_secret_group_uuid}"]="$(get_sharded_group_uuid "$SECRET_GROUP_NAME")" +GROUP_EXPANDED_BY_PLACEHOLDER["{non_secret_group_name}"]="$NON_SECRET_GROUP_NAME" +GROUP_EXPANDED_BY_PLACEHOLDER["{sharded_non_secret_group_uuid}"]="$(get_sharded_group_uuid "$NON_SECRET_GROUP_NAME")" + mkdir -p "$OUT" trap 'rm -rf "$OUT"' EXIT @@ -213,10 +235,12 @@ "non_root_with_subtask_from_root_task.md" "subtask_using_user_syntax/root_with_subtask_secret_ref.md" "subtask_using_user_syntax/root_with_subtask_non-secret_ref_with_subtask_secret_ref.md" +"subtask_using_group_syntax/root_with_subtask_secret_ref.md" +"subtask_using_group_syntax/root_with_subtask_non-secret_ref_with_subtask_secret_ref.md" ) for test in "${TESTS[@]}" ; do - TEST_DOC="$(replace_user_refs < "$TEST_DOC_DIR/$test" | replace_users)" + TEST_DOC="$(replace_user_refs < "$TEST_DOC_DIR/$test" | replace_users | replace_groups)" init_configs test_change done
diff --git a/test/docker/docker-compose.yaml b/test/docker/docker-compose.yaml index 634cde4..a228122 100755 --- a/test/docker/docker-compose.yaml +++ b/test/docker/docker-compose.yaml
@@ -11,6 +11,7 @@ - gerrit-net volumes: - "gerrit-site-etc:/var/gerrit/etc" + - "gerrit-site-git:/var/gerrit/git" run_tests: build: run_tests @@ -19,10 +20,12 @@ volumes: - "../../:/task:ro" - "gerrit-site-etc:/server-ssh-key:ro" + - "gerrit-site-git:/gerrit-site-git" depends_on: - gerrit-01 environment: - GERRIT_HOST=gerrit-01 + - GERRIT_GIT_DIR=/gerrit-site-git networks: gerrit-net: @@ -30,3 +33,4 @@ volumes: gerrit-site-etc: + gerrit-site-git:
diff --git a/test/docker/gerrit/Dockerfile b/test/docker/gerrit/Dockerfile index d638517..72132d7 100755 --- a/test/docker/gerrit/Dockerfile +++ b/test/docker/gerrit/Dockerfile
@@ -8,3 +8,4 @@ COPY artifacts /tmp/ RUN cp /tmp/task.jar "$GERRIT_SITE/plugins/task.jar" RUN { [ -e /tmp/gerrit.war ] && cp /tmp/gerrit.war "$GERRIT_SITE/bin/gerrit.war" ; } || true +RUN chmod 777 "$GERRIT_SITE/git"
diff --git a/test/docker/run_tests/create-one-time-test-data.sh b/test/docker/run_tests/create-one-time-test-data.sh index 6f5562b..d949bb2 100755 --- a/test/docker/run_tests/create-one-time-test-data.sh +++ b/test/docker/run_tests/create-one-time-test-data.sh
@@ -15,6 +15,13 @@ --email "$UNTRUSTED_USER"@example.com --ssh-key - < ~/.ssh/id_rsa.pub gssh create-group "Visible-All-Projects-Config" --member "$NON_SECRET_USER" + + local secret_user=$USER + gssh create-group "$NON_SECRET_GROUP_NAME_WITHOUT_SPACE" \ + --member "$NON_SECRET_USER" --member "$secret_user" + gssh create-group "\"$NON_SECRET_GROUP_NAME_WITH_SPACE\"" \ + --member "$NON_SECRET_USER" --member "$secret_user" + gssh create-group "$SECRET_GROUP_NAME" --member "$secret_user" } setup_all_projects_repo() { @@ -44,17 +51,23 @@ USER_RUN_TESTS_DIR="$USER_HOME"/"$RUN_TESTS_DIR" while (( "$#" )) ; do case "$1" in - --non-secret-user) shift ; NON_SECRET_USER="$1" ;; - --untrusted-user) shift ; UNTRUSTED_USER="$1" ;; - *) die "invalid argument '$1'" ;; + --non-secret-user) shift ; NON_SECRET_USER="$1" ;; + --untrusted-user) shift ; UNTRUSTED_USER="$1" ;; + --non-secret-group-without-space) shift ; NON_SECRET_GROUP_NAME_WITHOUT_SPACE="$1" ;; + --non-secret-group-with-space) shift ; NON_SECRET_GROUP_NAME_WITH_SPACE="$1" ;; + --secret-group) shift ; SECRET_GROUP_NAME="$1" ;; + *) die "invalid argument '$1'" ;; esac shift done [ -z "$NON_SECRET_USER" ] && die "non-secret-user not set" [ -z "$UNTRUSTED_USER" ] && die "untrusted-user not set" +[ -z "$NON_SECRET_GROUP_NAME_WITHOUT_SPACE" ] && die "non-secret-group-without-space not set" +[ -z "$NON_SECRET_GROUP_NAME_WITH_SPACE" ] && die "non-secret-group-with-space not set" +[ -z "$SECRET_GROUP_NAME" ] && die "secret-group not set" "$USER_RUN_TESTS_DIR"/create-test-project-and-changes.sh "$USER_RUN_TESTS_DIR"/update-all-users-project.sh create_test_users_and_group -setup_all_projects_repo \ No newline at end of file +setup_all_projects_repo
diff --git a/test/docker/run_tests/start.sh b/test/docker/run_tests/start.sh index 1bd5970..4a73f24 100755 --- a/test/docker/run_tests/start.sh +++ b/test/docker/run_tests/start.sh
@@ -32,8 +32,12 @@ NON_SECRET_USER="non_secret_user" UNTRUSTED_USER="untrusted_user" +GROUP_NAME_WITHOUT_SPACE="test.group" +GROUP_NAME_WITH_SPACE="test group" +SECRET_GROUP="private_group" "$USER_RUN_TESTS_DIR"/create-one-time-test-data.sh --non-secret-user "$NON_SECRET_USER" \ - --untrusted-user "$UNTRUSTED_USER" + --untrusted-user "$UNTRUSTED_USER" --non-secret-group-without-space "$GROUP_NAME_WITHOUT_SPACE" \ + --non-secret-group-with-space "$GROUP_NAME_WITH_SPACE" --secret-group "$SECRET_GROUP" echo "Running Task plugin tests ..." @@ -41,9 +45,11 @@ "$USER_RUN_TESTS_DIR"/../../check_task_statuses.sh \ --server "$GERRIT_HOST" --non-secret-user "$NON_SECRET_USER" \ - --untrusted-user "$UNTRUSTED_USER" || RESULT=1 + --untrusted-user "$UNTRUSTED_USER" --non-secret-group-without-space "$GROUP_NAME_WITHOUT_SPACE" \ + --non-secret-group-with-space "$GROUP_NAME_WITH_SPACE" || RESULT=1 "$USER_RUN_TESTS_DIR"/../../check_task_visibility.sh --server "$GERRIT_HOST" \ - --non-secret-user "$NON_SECRET_USER" || RESULT=1 + --non-secret-user "$NON_SECRET_USER" --non-secret-group "$GROUP_NAME_WITHOUT_SPACE" \ + --secret-group "$SECRET_GROUP" || RESULT=1 exit $RESULT
diff --git a/test/docker/run_tests/update-all-users-project.sh b/test/docker/run_tests/update-all-users-project.sh index e8912c4..cfe2def 100755 --- a/test/docker/run_tests/update-all-users-project.sh +++ b/test/docker/run_tests/update-all-users-project.sh
@@ -13,4 +13,4 @@ access."refs/*".read "deny group Anonymous Users" echo -e "global:Registered-Users\tRegistered Users" >> groups echo -e "global:Anonymous-Users\tAnonymous Users" >> groups -git add . && git commit -m "project config update" && git push origin HEAD:refs/meta/config \ No newline at end of file +git add . && git commit -m "project config update" && git push origin HEAD:refs/meta/config
diff --git a/test/lib/lib_helper.sh b/test/lib/lib_helper.sh index 6ee1b89..efb6de9 100644 --- a/test/lib/lib_helper.sh +++ b/test/lib/lib_helper.sh
@@ -184,6 +184,23 @@ replace_change_properties "1" "${CHANGE1[@]}" | replace_change_properties "2" "${CHANGE2[@]}" } +replace_groups() { # < text_with_groups > test_with_expanded_groups + local text="$(< /dev/stdin)" + for placeholder in "${!GROUP_EXPANDED_BY_PLACEHOLDER[@]}" ; do + text="${text//"$placeholder"/${GROUP_EXPANDED_BY_PLACEHOLDER["$placeholder"]}}" + done + echo "$text" +} + +get_group_uuid() { # group_name > group_uuid + gssh ls-groups -v | awk '-F\t' '$1 == "'"$1"'" {print $2}' +} + +get_sharded_group_uuid() { # group_name > sharded_group_uuid + local group_id=$(get_group_uuid "$1") + echo "${group_id:0:2}/$group_id" +} + replace_users() { # < text_with_users > test_with_expanded_users local text="$(< /dev/stdin)" for user in "${!USERS[@]}" ; do @@ -211,7 +228,7 @@ } replace_tokens() { # < text > text with replacing all tokens(changes, user) - replace_default_changes | replace_user_refs | replace_user + replace_default_changes | replace_user_refs | replace_user | replace_groups } strip_non_applicable() { ensure "$MYDIR"/strip_non_applicable.py ; } # < json > json