[recipe_testing] Support allowlist of repos whose tryjobs to run

We currently run blocking builders from all repos listed in
commit-queue.cfg in recipes CQ. However, some of those repos are less
well-maintained that others and their tryjobs may be broken at any point
in time.

This change makes it possible to specify a "critical" set of
repositories whose tryjobs we should run in recipes CQ, excluding other
repositories considered non-critical in order to lower the probability
of false rejections.

Bug: 339695130
Change-Id: Ifa7f17a6963f77d98c48674e465dddd6cee1ca99
Reviewed-on: https://fuchsia-review.googlesource.com/c/infra/recipes/+/1044896
Reviewed-by: Ina Huh <ihuh@google.com>
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
Fuchsia-Auto-Submit: Oliver Newman <olivernewman@google.com>
Reviewed-by: Rob Mohr <mohrr@google.com>
diff --git a/recipe_modules/commit_queue/api.py b/recipe_modules/commit_queue/api.py
index 7a27cbf..96e5b7e 100644
--- a/recipe_modules/commit_queue/api.py
+++ b/recipe_modules/commit_queue/api.py
@@ -24,12 +24,24 @@
         include_all_modes=False,
         include_path_filtered=False,
         config_name=None,
+        included_repos=frozenset(),
     ):
         cfg = self._load(project, config_name=config_name)
 
         builders = set()
         if cfg:
             for group in cfg.config_groups:
+                triggering_repos = set()
+                for g in group.gerrit:
+                    gitiles_host = g.url.replace("-review", "")
+                    for p in g.projects:
+                        triggering_repos.add(f"{gitiles_host}/{p.name}")
+
+                # URLs in commit-queue.cfg and `included_repos` should all use
+                # "https://" prefixes, so they should be comparable.
+                if included_repos and not triggering_repos.intersection(included_repos):
+                    continue
+
                 for builder in group.verifiers.tryjob.builders:
                     if (
                         not include_unrestricted
diff --git a/recipe_modules/commit_queue/tests/full.expected/filtered_repos.json b/recipe_modules/commit_queue/tests/full.expected/filtered_repos.json
new file mode 100644
index 0000000..6c92e21
--- /dev/null
+++ b/recipe_modules/commit_queue/tests/full.expected/filtered_repos.json
@@ -0,0 +1,58 @@
+[
+  {
+    "cmd": [],
+    "name": "fetch project commit-queue.cfg"
+  },
+  {
+    "cmd": [
+      "prpc",
+      "call",
+      "-format=json",
+      "config.luci.app",
+      "config.service.v2.Configs.GetConfig"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "fetch project commit-queue.cfg.get",
+    "stdin": "{\n  \"config_set\": \"projects/project\",\n  \"path\": \"commit-queue.cfg\"\n}",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@proto.output@{@@@",
+      "@@@STEP_LOG_LINE@proto.output@  \"raw_content\": \"CiAgICBzdWJtaXRfb3B0aW9uczogewogICAgICBtYXhfYnVyc3Q6IDQKICAgICAgYnVyc3RfZGVsYXk6IHsKICAgICAgICBzZWNvbmRzOiA0ODAKICAgICAgfQogICAgfQoKICAgIGNvbmZpZ19ncm91cHM6IHsKICAgICAgZ2Vycml0OiB7CiAgICAgICAgdXJsOiAiaHR0cHM6Ly9mdWNoc2lhLXJldmlldy5nb29nbGVzb3VyY2UuY29tIgogICAgICAgIHByb2plY3RzOiB7CiAgICAgICAgICBuYW1lOiAiY29iYWx0IgogICAgICAgICAgcmVmX3JlZ2V4cDogInJlZnMvaGVhZHMvLisiCiAgICAgICAgfQogICAgICB9CgogICAgICB2ZXJpZmllcnM6IHsKICAgICAgICBnZXJyaXRfY3FfYWJpbGl0eTogewogICAgICAgICAgY29tbWl0dGVyX2xpc3Q6ICJwcm9qZWN0LWZ1Y2hzaWEtY29tbWl0dGVycyIKICAgICAgICAgIGRyeV9ydW5fYWNjZXNzX2xpc3Q6ICJwcm9qZWN0LWZ1Y2hzaWEtdHJ5am9iLWFjY2VzcyIKICAgICAgICB9CiAgICAgICAgdHJ5am9iOiB7CiAgICAgICAgICBidWlsZGVyczogewogICAgICAgICAgICBuYW1lOiAiZnVjaHNpYS90cnkvY29iYWx0LXg2NC1saW51eCIKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIH0KICAgIH0KCiAgICBjb25maWdfZ3JvdXBzOiB7CiAgICAgIGdlcnJpdDogewogICAgICAgIHVybDogImh0dHBzOi8vZnVjaHNpYS1yZXZpZXcuZ29vZ2xlc291cmNlLmNvbSIKICAgICAgICBwcm9qZWN0czogewogICAgICAgICAgbmFtZTogImRvY3MiCiAgICAgICAgICByZWZfcmVnZXhwOiAicmVmcy9oZWFkcy8uKyIKICAgICAgICB9CiAgICAgIH0KCiAgICAgIHZlcmlmaWVyczogewogICAgICAgIGdlcnJpdF9jcV9hYmlsaXR5OiB7CiAgICAgICAgICBjb21taXR0ZXJfbGlzdDogInByb2plY3QtZnVjaHNpYS1jb21taXR0ZXJzIgogICAgICAgICAgZHJ5X3J1bl9hY2Nlc3NfbGlzdDogInByb2plY3QtZnVjaHNpYS10cnlqb2ItYWNjZXNzIgogICAgICAgIH0KICAgICAgICB0cnlqb2I6IHsKICAgICAgICAgIGJ1aWxkZXJzOiB7CiAgICAgICAgICAgIG5hbWU6ICJmdWNoc2lhL3RyeS9kb2MtY2hlY2tlciIKICAgICAgICAgICAgZXhwZXJpbWVudF9wZXJjZW50YWdlOiAxMDAKICAgICAgICAgIH0KICAgICAgICAgIGJ1aWxkZXJzOiB7CiAgICAgICAgICAgIG5hbWU6ICJmdWNoc2lhL3RyeS9zZWNyZXQtdHJ5am9iIgogICAgICAgICAgICByZXN1bHRfdmlzaWJpbGl0eTogQ09NTUVOVF9MRVZFTF9SRVNUUklDVEVECiAgICAgICAgICB9CiAgICAgICAgfQogICAgICB9CiAgICB9CgogICAgY29uZmlnX2dyb3VwczogewogICAgICBnZXJyaXQ6IHsKICAgICAgICB1cmw6ICJodHRwczovL2Z1Y2hzaWEtcmV2aWV3Lmdvb2dsZXNvdXJjZS5jb20iCiAgICAgICAgcHJvamVjdHM6IHsKICAgICAgICAgIG5hbWU6ICJmdWNoc2lhIgogICAgICAgICAgcmVmX3JlZ2V4cDogInJlZnMvaGVhZHMvLisiCiAgICAgICAgfQogICAgICB9CiAgICAgIHZlcmlmaWVyczogewogICAgICAgIGdlcnJpdF9jcV9hYmlsaXR5OiB7CiAgICAgICAgICBjb21taXR0ZXJfbGlzdDogInByb2plY3QtZnVjaHNpYS1jb21taXR0ZXJzIgogICAgICAgICAgZHJ5X3J1bl9hY2Nlc3NfbGlzdDogInByb2plY3QtZnVjaHNpYS10cnlqb2ItYWNjZXNzIgogICAgICAgIH0KICAgICAgICB0cmVlX3N0YXR1czogewogICAgICAgICAgdXJsOiAiaHR0cHM6Ly9mdWNoc2lhLXN0ZW0tc3RhdHVzLmFwcHNwb3QuY29tIgogICAgICAgIH0KCiAgICAgICAgdHJ5am9iOiB7CiAgICAgICAgICBidWlsZGVyczogewogICAgICAgICAgICBuYW1lOiAiZnVjaHNpYS90cnkvY29yZS5hcm02NC1kZWJ1ZyIKICAgICAgICAgIH0KICAgICAgICAgIGJ1aWxkZXJzOiB7CiAgICAgICAgICAgIG5hbWU6ICJmdWNoc2lhL3RyeS9jb3JlLng2NC1kZWJ1ZyIKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIH0KICAgIH0KICAgIA==\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@}@@@",
+      "@@@STEP_LOG_END@proto.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "all tryjobs",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@tryjobs@fuchsia/try/core.arm64-debug@@@",
+      "@@@STEP_LOG_LINE@tryjobs@fuchsia/try/core.x64-debug@@@",
+      "@@@STEP_LOG_END@tryjobs@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "all tryjobs (2)",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@tryjobs@fuchsia/try/core.arm64-debug@@@",
+      "@@@STEP_LOG_LINE@tryjobs@fuchsia/try/core.x64-debug@@@",
+      "@@@STEP_LOG_END@tryjobs@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/commit_queue/tests/full.py b/recipe_modules/commit_queue/tests/full.py
index f85eac3..1632e47 100644
--- a/recipe_modules/commit_queue/tests/full.py
+++ b/recipe_modules/commit_queue/tests/full.py
@@ -13,17 +13,22 @@
 PROPERTIES = {
     "include_unrestricted": Property(kind=bool, default=True),
     "include_restricted": Property(kind=bool, default=False),
+    "included_repos": Property(default=None),
 }
 
 
-def RunSteps(api, include_unrestricted, include_restricted):
+def RunSteps(api, include_unrestricted, include_restricted, included_repos):
     api.commit_queue.all_tryjobs(
-        include_unrestricted=include_unrestricted, include_restricted=include_restricted
+        include_unrestricted=include_unrestricted,
+        include_restricted=include_restricted,
+        included_repos=included_repos,
     )
 
     # Repeated call just to check caching works.
     api.commit_queue.all_tryjobs(
-        include_unrestricted=include_unrestricted, include_restricted=include_restricted
+        include_unrestricted=include_unrestricted,
+        include_restricted=include_restricted,
+        included_repos=included_repos,
     )
 
 
@@ -54,3 +59,9 @@
         api.buildbucket_util.test("location_filters", tryjob=True, project=project)
         + api.commit_queue.test_data(project, "location_filters")
     )
+
+    yield (
+        api.buildbucket_util.test("filtered_repos", tryjob=True, project=project)
+        + api.properties(included_repos={"https://fuchsia.googlesource.com/fuchsia"})
+        + api.commit_queue.test_data(project)
+    )
diff --git a/recipe_modules/recipe_testing/api.py b/recipe_modules/recipe_testing/api.py
index 5c15f1f..0da8b64 100644
--- a/recipe_modules/recipe_testing/api.py
+++ b/recipe_modules/recipe_testing/api.py
@@ -333,8 +333,6 @@
           selftest_cl (str): The CL to use to test a recursive recipe testing
             invocation.
           config (RecipeTesting proto): Many options for led/bb tests.
-            TODO(fxbug.dev/88439): Make this a formal proto under
-            recipe_modules/recipe_testing.
           selftest_builder (str|None): Builder to use to guarantee that we
             exercise the scheduling codepath when `use_buildbucket` is True.
         """
@@ -360,6 +358,7 @@
                     include_restricted=project_config.include_restricted,
                     config_name=(project_config.cq_config_name or "commit-queue.cfg"),
                     include_path_filtered=project_config.include_path_filtered,
+                    included_repos=project_config.included_repos,
                 )
             )
 
diff --git a/recipe_modules/recipe_testing/options.proto b/recipe_modules/recipe_testing/options.proto
index 7fd8e4f..b457691 100644
--- a/recipe_modules/recipe_testing/options.proto
+++ b/recipe_modules/recipe_testing/options.proto
@@ -27,6 +27,16 @@
 
   // Include tryjobs where the first location filter is not an exclude rule.
   bool include_path_filtered = 7;
+
+  // Repositories whose presubmit builders should be included. If empty, all
+  // repositories with entries in commit-queue.cfg will be considered.
+  //
+  // This is useful for including only repositories considered to be critical
+  // and excluding tryjobs for other repositories that might be less
+  // well-maintained and more likely to cause false rejections.
+  //
+  // Example value: "https://fuchsia.googlesource.com/foo".
+  repeated string included_repos = 8;
 }
 
 message Options {