Repository: firebase/firebase-tools (ts)

Issue:
v2 functions don't support `retry` even though cloud functions gen2 do
<!-- DO NOT DELETE
validate_template=true
template_path=.github/ISSUE_TEMPLATE/bug_report.md
-->

### [REQUIRED] Environment info

<!-- What version of the Firebase CLI (`firebase-tools`) are you using? Note that your issue may already be fixed in the latest versions. The latest version can be found at https://github.com/firebase/firebase-tools/releases -->

<!-- Output of `firebase --version` -->

**firebase-tools:** 11.21.0

<!-- e.g. macOS, Windows, Ubuntu -->

**Platform:** macOS

### [REQUIRED] Test case

<!-- Provide a minimal, complete, and verifiable example (http://stackoverflow.com/help/mcve) -->

```
exports.customeventhandler = onCustomEventPublished(
  {
    eventType: "firebase.extensions.storage-resize-images.v1.complete",
    retry: true,
  },
  (e) => {
    console.log(JSON.stringify(e));
  });
```

### [REQUIRED] Steps to reproduce

<!-- Provide the steps needed to reproduce the issue with the above test case. -->

firebase deploy --only functions

### [REQUIRED] Expected behavior

<!-- What is the expected behavior? -->

Should deploy function with retry policy

### [REQUIRED] Actual behavior

<!-- Run the command with --debug flag, and include the logs below. -->

Shows a warning `Cannot set a retry policy on Cloud Function` and does not set the retry policy.

Warning is coming from here: https://github.com/firebase/firebase-tools/blob/a2b9389928c7586d2bbdfda4f9212bd7fcc88f21/src/gcp/cloudfunctionsv2.ts#L528

`EventTrigger` has `retryPolicy` field, so it should be set when `retry` option is specified.
https://cloud.google.com/functions/docs/reference/rest/v2/projects.locations.functions#eventtrigger


Pull request description:
Fix retry for event triggered functions
Allows gen 2 functions to set the retry on event triggered functions. Fixes #5484 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6a14a3c87..398cb139b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,5 @@
 - Improve performance and reliability when deploying multiple 2nd gen functions using single builds. (#6376)
 - Fixed an issue where `emulators:export` did not check if the target folder is empty. (#6313)
+- Fixed an issue where retry could not be set for event triggered functions. (#6391)
 - Fixed "Could not find the next executable" on Next.js deployments (#6372)
 - Fixed issues caused by breaking changes in Next >=v13.5.0. (#6382)
diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts
index 8098e3d97..4f4912c04 100644
--- a/src/gcp/cloudfunctionsv2.ts
+++ b/src/gcp/cloudfunctionsv2.ts
@@ -36,6 +36,12 @@ export type FunctionState = "ACTIVE" | "FAILED" | "DEPLOYING" | "DELETING" | "UN
 // Values allowed for the operator field in EventFilter
 export type EventFilterOperator = "match-path-pattern";
 
+// Values allowed for the event trigger retry policy in case of a function's execution failure.
+export type RetryPolicy =
+  | "RETRY_POLICY_UNSPECIFIED"
+  | "RETRY_POLICY_DO_NOT_RETRY"
+  | "RETRY_POLICY_RETRY";
+
 /** Settings for building a container out of the customer source. */
 export interface BuildConfig {
   runtime: runtimes.Runtime;
@@ -140,6 +146,8 @@ export interface EventTrigger {
   // to the defualt compute service account.
   serviceAccountEmail?: string;
 
+  retryPolicy?: RetryPolicy;
+
   // The name of the channel associated with the trigger in
   // `projects/{project}/locations/{location}/channels/{channel}` format.
   channel?: string;
@@ -542,6 +550,7 @@ export function functionFromEndpoint(endpoint: backend.Endpoint): InputCloudFunc
   if (backend.isEventTriggered(endpoint)) {
     gcfFunction.eventTrigger = {
       eventType: endpoint.eventTrigger.eventType,
+      retryPolicy: "RETRY_POLICY_UNSPECIFIED",
     };
     if (gcfFunction.eventTrigger.eventType === PUBSUB_PUBLISH_EVENT) {
       if (!endpoint.eventTrigger.eventFilters?.topic) {
@@ -579,9 +588,10 @@ export function functionFromEndpoint(endpoint: backend.Endpoint): InputCloudFunc
     );
     proto.copyIfPresent(gcfFunction.eventTrigger, endpoint.eventTrigger, "channel");
 
-    if (endpoint.eventTrigger.retry) {
-      logger.warn("Cannot set a retry policy on Cloud Function", endpoint.id);
-    }
+    endpoint.eventTrigger.retry
+      ? (gcfFunction.eventTrigger.retryPolicy = "RETRY_POLICY_RETRY")
+      : (gcfFunction.eventTrigger!.retryPolicy = "RETRY_POLICY_DO_NOT_RETRY");
+
     // By default, Functions Framework in GCFv2 opts to downcast incoming cloudevent messages to legacy formats.
     // Since Firebase Functions SDK expects messages in cloudevent format, we set FUNCTION_SIGNATURE_TYPE to tell
     // Functions Framework to disable downcast before passing the cloudevent message to function handler.
@@ -665,7 +675,7 @@ export function endpointFromFunction(gcfFunction: OutputCloudFunction): backend.
     trigger = {
       eventTrigger: {
         eventType: gcfFunction.eventTrigger.eventType,
-        retry: false,
+        retry: gcfFunction.eventTrigger.retryPolicy === "RETRY_POLICY_RETRY" ? true : false,
       },
     };
     if (Object.keys(eventFilters).length) {


Test patch:
diff --git a/src/test/gcp/cloudfunctionsv2.spec.ts b/src/test/gcp/cloudfunctionsv2.spec.ts
index ca7092fed..ce9724d89 100644
--- a/src/test/gcp/cloudfunctionsv2.spec.ts
+++ b/src/test/gcp/cloudfunctionsv2.spec.ts
@@ -108,7 +108,7 @@ describe("cloudfunctionsv2", () => {
             resource: "projects/p/regions/r/instances/i",
             serviceName: "compute.googleapis.com",
           },
-          retry: false,
+          retry: true,
           channel: "projects/myproject/locations/us-wildwest11/channels/mychannel",
         },
       };
@@ -126,6 +126,7 @@ describe("cloudfunctionsv2", () => {
               value: "compute.googleapis.com",
             },
           ],
+          retryPolicy: "RETRY_POLICY_RETRY",
           channel: "projects/myproject/locations/us-wildwest11/channels/mychannel",
         },
         serviceConfig: {
@@ -165,6 +166,7 @@ describe("cloudfunctionsv2", () => {
               operator: "match-path-pattern",
             },
           ],
+          retryPolicy: "RETRY_POLICY_DO_NOT_RETRY",
         },
         serviceConfig: {
           ...CLOUD_FUNCTION_V2.serviceConfig,
@@ -302,6 +304,7 @@ describe("cloudfunctionsv2", () => {
               value: "pubsub.googleapis.com",
             },
           ],
+          retryPolicy: "RETRY_POLICY_DO_NOT_RETRY",
         },
         serviceConfig: {
           ...CLOUD_FUNCTION_V2.serviceConfig,