From 72ab98158a932a5f4cce7eb89c1cb961884c8ecc Mon Sep 17 00:00:00 2001 From: "James M. Greene" Date: Fri, 22 Dec 2023 11:40:57 -0600 Subject: [PATCH] Wrap Twirp responses like Octokit responses for consistency --- package-lock.json | 9 ++++- package.json | 4 ++- src/internal/api-client.js | 68 ++++++++++++++++++++++++++++++++++++-- src/internal/deployment.js | 27 ++++++--------- 4 files changed, 88 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index ef2c5c4..c4b9c90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,9 @@ "dependencies": { "@actions/artifact": "^2.0.0", "@actions/core": "^1.10.1", - "@actions/github": "^6.0.0" + "@actions/github": "^6.0.0", + "@octokit/request-error": "^5.0.1", + "http-status-messages": "^1.1.0" }, "devDependencies": { "@vercel/ncc": "^0.38.1", @@ -4670,6 +4672,11 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/http-status-messages": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/http-status-messages/-/http-status-messages-1.1.0.tgz", + "integrity": "sha512-zq9mKkNX6w6qYtNE0aAiH+urvEenUUIyoq8eAWQh2prhA5o03NETCOm/D2GIVt/qCFItt+23Sm1E7HyelPvi6w==" + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", diff --git a/package.json b/package.json index 7d0fff5..532d4a5 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "dependencies": { "@actions/artifact": "^2.0.0", "@actions/core": "^1.10.1", - "@actions/github": "^6.0.0" + "@actions/github": "^6.0.0", + "@octokit/request-error": "^5.0.1", + "http-status-messages": "^1.1.0" }, "devDependencies": { "@vercel/ncc": "^0.38.1", diff --git a/src/internal/api-client.js b/src/internal/api-client.js index 6ac86b9..b60fe18 100644 --- a/src/internal/api-client.js +++ b/src/internal/api-client.js @@ -1,16 +1,80 @@ const core = require('@actions/core') const github = require('@actions/github') const { DefaultArtifactClient } = require('@actions/artifact') +const { RequestError } = require('@octokit/request-error') +const HttpStatusMessages = require('http-status-messages') + +function wrapTwirpResponseLikeOctokit(twirpResponse, requestOptions) { + // Specific response shape aligned with Octokit + const response = { + url: requestOptions.url, + status: 200, + headers: { + ...requestOptions.headers + }, + data: twirpResponse + } + return response +} + +// Mimic the errors thrown by Octokit for consistency. +function wrapTwirpErrorLikeOctokit(twirpError, requestOptions) { + const rawErrorMsg = twirpError?.message || twirpError?.toString() || '' + const statusCodeMatch = rawErrorMsg.match(/Failed request: \((?\d+)\)/) + const statusCode = statusCodeMatch?.groups?.statusCode ?? 500 + + // Try to provide the best error message + const errorMsg = + rawErrorMsg || + // Fallback to the HTTP status message based on the status code + HttpStatusMessages[statusCode] || + // Or if the status code is unexpected... + `Unknown error (${statusCode})` + + // RequestError is an Octokit-specific class + return new RequestError(errorMsg, statusCode, { + response: { + url: requestOptions.url, + status: statusCode, + headers: { + ...requestOptions.headers + }, + data: rawErrorMsg ? { message: rawErrorMsg } : '' + }, + request: requestOptions + }) +} + +function getArtifactsServiceOrigin() { + const resultsUrl = process.env.ACTIONS_RESULTS_URL + return resultsUrl ? new URL(resultsUrl).origin : '' +} async function getArtifactMetadata({ artifactName }) { const artifactClient = new DefaultArtifactClient() + // Primarily for debugging purposes, accuracy is not critical + const requestOptions = { + method: 'POST', + url: `${getArtifactsServiceOrigin()}/twirp/github.actions.results.api.v1.ArtifactService/ListArtifacts`, + headers: { + 'content-type': 'application/json' + } + } + try { core.info(`Fetching artifact metadata for ${artifactName} in this workflow run`) - const response = await artifactClient.listArtifacts() + let response + try { + const twirpResponse = await artifactClient.listArtifacts() + response = wrapTwirpResponseLikeOctokit(twirpResponse, requestOptions) + } catch (twirpError) { + const octokitError = wrapTwirpErrorLikeOctokit(twirpError, requestOptions) + throw octokitError + } - const filteredArtifacts = response.artifacts.filter(artifact => artifact.name === artifactName) + const filteredArtifacts = response.data.artifacts.filter(artifact => artifact.name === artifactName) const artifactCount = filteredArtifacts.length core.debug(`List artifact count: ${artifactCount}`) diff --git a/src/internal/deployment.js b/src/internal/deployment.js index eae23bb..81220ec 100644 --- a/src/internal/deployment.js +++ b/src/internal/deployment.js @@ -62,20 +62,15 @@ class Deployment { core.debug(`Action ID: ${this.actionsId}`) core.debug(`Actions Workflow Run ID: ${this.workflowRun}`) - let artifactData try { - artifactData = await getArtifactMetadata({ artifactName: this.artifactName }) - } catch (error) { - throw new Error(`Failed to create deployment: ${error.message}`) - } + const artifactData = await getArtifactMetadata({ artifactName: this.artifactName }) - if (artifactData?.size > ONE_GIGABYTE) { - core.warning( - `Uploaded artifact size of ${artifactData?.size} bytes exceeds the allowed size of ${SIZE_LIMIT_DESCRIPTION}. Deployment might fail.` - ) - } + if (artifactData?.size > ONE_GIGABYTE) { + core.warning( + `Uploaded artifact size of ${artifactData?.size} bytes exceeds the allowed size of ${SIZE_LIMIT_DESCRIPTION}. Deployment might fail.` + ) + } - try { const deployment = await createPagesDeployment({ githubToken: this.githubToken, artifactId: artifactData.id, @@ -103,14 +98,14 @@ class Deployment { // build customized error message based on server response if (error.response) { - let errorMessage = `Failed to create deployment (status: ${error.status}) with build version ${this.buildVersion}. ` + let errorMessage = `Failed to create deployment (status: ${error.status}) with build version ${this.buildVersion}.` if (error.status === 400) { - errorMessage += `Responded with: ${error.message}` + errorMessage += ` Responded with: ${error.message}` } else if (error.status === 403) { - errorMessage += 'Ensure GITHUB_TOKEN has permission "pages: write".' + errorMessage += ' Ensure GITHUB_TOKEN has permission "pages: write".' } else if (error.status === 404) { const pagesSettingsUrl = `${this.githubServerUrl}/${this.repositoryNwo}/settings/pages` - errorMessage += `Ensure GitHub Pages has been enabled: ${pagesSettingsUrl}` + errorMessage += ` Ensure GitHub Pages has been enabled: ${pagesSettingsUrl}` // If using GHES, add a special note about compatibility if (new URL(this.githubServerUrl).hostname.toLowerCase() !== 'github.com') { errorMessage += @@ -118,7 +113,7 @@ class Deployment { } } else if (error.status >= 500) { errorMessage += - 'Server error, is githubstatus.com reporting a Pages outage? Please re-run the deployment at a later time.' + ' Server error, is githubstatus.com reporting a Pages outage? Please re-run the deployment at a later time.' } throw new Error(errorMessage) } else {