Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce capability to retry when upload fails, up to the given maxRetriesForUpload parameter #193

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'"
Expand Down
8 changes: 8 additions & 0 deletions src/input-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ export async function validateInAppUpdatePriority(inAppUpdatePriority: number |
}
}

export async function validateMaxRetriesForUpload(maxRetriesForUpload: number | undefined): Promise<void> {
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<string[]> {
if (!releaseFiles) {
return Promise.reject(new Error(`You must provide 'releaseFiles' in your configuration`))
Expand Down
67 changes: 47 additions & 20 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -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)

Expand All @@ -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'`)
Expand All @@ -66,33 +75,51 @@ 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(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this fails part-way through, it will leave a dangling edit. We probably need to do a retry around each request, and try to discard the edit on fail

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oufff, maybe I bit off more than I could chew. Not familiar with Google Play's API and how the edits work. Will try to take another stab tomorrow after work, getting late here! Thanks for the code review!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries! Thanks for taking the time to look at this :)

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)
} else {
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');
Expand Down