From c13e4615f367246dc4126e889756068d03035ac5 Mon Sep 17 00:00:00 2001 From: Moe Abushawish Date: Wed, 20 Sep 2023 00:06:09 -0400 Subject: [PATCH] Introduce capability to retry when upload fails up to a given maxRetriesForUploadInt --- README.md | 2 ++ action.yml | 3 ++ src/input-validation.ts | 8 +++++ src/main.ts | 67 +++++++++++++++++++++++++++++------------ 4 files changed, 60 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 57543737..8ed3ae40 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ This action will help you upload an Android `.apk` or `.aab` (Android App Bundle | serviceAccountJson | The service account json private key file to authorize the upload request. Can be used instead of `serviceAccountJsonPlainText` to specify a file rather than provide a secret | A path to a valid `service-account.json` file | true (or serviceAccountJsonPlainText) | | existingEditId | The ID of an existing edit that has not been completed. If this is supplied, the action will append information to that rather than creating an edit | A valid, unpublished Edit ID | false | | ~~releaseFile~~ | Please switch to using `releaseFiles` as this will be removed in the future | | false | +| maxRetriesForUpload | How many times to reattempt to upload to Google Play if an upload fails. Defaults to `0` reattempts, aside from the initial upload, but can be 0 to 99 (inclusive-inclusive) | `[0-99]` | false | ## Outputs @@ -46,6 +47,7 @@ with: whatsNewDirectory: distribution/whatsnew mappingFile: app/build/outputs/mapping/release/mapping.txt debugSymbols: app/intermediates/merged_native_libs/release/out/lib + maxRetriesForUpload: 5 ``` ## FAQ diff --git a/action.yml b/action.yml index c1527320..61cf3894 100644 --- a/action.yml +++ b/action.yml @@ -54,6 +54,9 @@ inputs: existingEditId: description: "The ID of an existing edit that has not been completed. If this is supplied, the action will append information to that rather than creating an edit" required: false + maxRetriesForUpload: + description: "How many times to reattempt to upload to Google Play if an upload fails. Defaults to 0 reattempts, aside from the initial upload, but can be 0 to 99 (inclusive-inclusive)" + required: false outputs: internalSharingDownloadUrl: description: "The internal app sharing download url if track was 'internalsharing'" diff --git a/src/input-validation.ts b/src/input-validation.ts index 6b3aa275..1101c276 100644 --- a/src/input-validation.ts +++ b/src/input-validation.ts @@ -43,6 +43,14 @@ export async function validateInAppUpdatePriority(inAppUpdatePriority: number | } } +export async function validateMaxRetriesForUpload(maxRetriesForUpload: number | undefined): Promise { + if (maxRetriesForUpload) { + if (maxRetriesForUpload < 0 || maxRetriesForUpload > 99) { + return Promise.reject(new Error('maxRetriesForUpload must be between 0 and 99, inclusive-inclusive')) + } + } +} + export async function validateReleaseFiles(releaseFiles: string[] | undefined): Promise { if (!releaseFiles) { return Promise.reject(new Error(`You must provide 'releaseFiles' in your configuration`)) diff --git a/src/main.ts b/src/main.ts index 30362581..3455ea22 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,7 @@ import * as core from '@actions/core' import * as fs from "fs" import { runUpload } from "./edits" -import { validateInAppUpdatePriority, validateReleaseFiles, validateStatus, validateUserFraction } from "./input-validation" +import { validateInAppUpdatePriority, validateMaxRetriesForUpload, validateReleaseFiles, validateStatus, validateUserFraction } from "./input-validation" import { unlink, writeFile } from 'fs/promises' import pTimeout from 'p-timeout' @@ -24,6 +24,7 @@ export async function run() { const debugSymbols = core.getInput('debugSymbols', { required: false }); const changesNotSentForReview = core.getInput('changesNotSentForReview', { required: false }) == 'true'; const existingEditId = core.getInput('existingEditId'); + const maxRetriesForUpload = core.getInput('maxRetriesForUpload', { required: false }); await validateServiceAccountJson(serviceAccountJsonRaw, serviceAccountJson) @@ -48,6 +49,14 @@ export async function run() { } await validateInAppUpdatePriority(inAppUpdatePriorityInt) + let maxRetriesForUploadInt = 0 + if (maxRetriesForUpload) { + maxRetriesForUploadInt = parseInt(maxRetriesForUpload) + } else { + maxRetriesForUploadInt = 0 + } + await validateMaxRetriesForUpload(maxRetriesForUploadInt) + // Check release files while maintaining backward compatibility if (releaseFile) { core.warning(`WARNING!! 'releaseFile' is deprecated and will be removed in a future release. Please migrate to 'releaseFiles'`) @@ -66,25 +75,43 @@ export async function run() { core.warning(`Unable to find 'debugSymbols' @ ${debugSymbols}`); } - await pTimeout( - runUpload( - packageName, - track, - inAppUpdatePriorityInt, - userFractionFloat, - whatsNewDir, - mappingFile, - debugSymbols, - releaseName, - changesNotSentForReview, - existingEditId, - status, - validatedReleaseFiles - ), - { - milliseconds: 3.6e+6 + let retries = maxRetriesForUploadInt; + let success = false; + while (retries >= 0) { + try { + await pTimeout( + runUpload( + packageName, + track, + inAppUpdatePriorityInt, + userFractionFloat, + whatsNewDir, + mappingFile, + debugSymbols, + releaseName, + changesNotSentForReview, + existingEditId, + status, + validatedReleaseFiles + ), + { + milliseconds: 3.6e+6 + } + ); + success = true; + break; + } catch (error) { + retries--; + if (retries > 0) { + core.warning(`runUpload() failed. Will try again ${retries} more time(s)`); + } } - ) + } + + if (!success) { + throw new Error(`runUpload() failed after ${maxRetriesForUploadInt + 1} attempts`); + } + } catch (error: unknown) { if (error instanceof Error) { core.setFailed(error.message) @@ -92,7 +119,7 @@ export async function run() { core.setFailed('Unknown error occurred.') } } finally { - if (core.getInput('serviceAccountJsonPlainText', { required: false})) { + if (core.getInput('serviceAccountJsonPlainText', { required: false })) { // Cleanup our auth file that we created. core.debug('Cleaning up service account json file'); await unlink('./serviceAccountJson.json');