Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
deploy-pages/src/internal/deployment.js
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
241 lines (205 sloc)
8.03 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const core = require('@actions/core') | |
// All variables we need from the runtime are loaded here | |
const getContext = require('./context') | |
const { | |
getSignedArtifactUrl, | |
createPagesDeployment, | |
getPagesDeploymentStatus, | |
cancelPagesDeployment | |
} = require('./api-client') | |
const temporaryErrorStatus = { | |
unknown_status: 'Unable to get deployment status.', | |
not_found: 'Deployment not found.', | |
deployment_attempt_error: 'Deployment temporarily failed, a retry will be automatically scheduled...' | |
} | |
const finalErrorStatus = { | |
deployment_failed: 'Deployment failed, try again later.', | |
deployment_content_failed: | |
'Artifact could not be deployed. Please ensure the content does not contain any hard links, symlinks and total size is less than 10GB.', | |
deployment_cancelled: 'Deployment cancelled.', | |
deployment_lost: 'Deployment failed to report final status.' | |
} | |
const MAX_TIMEOUT = 600000 | |
class Deployment { | |
constructor() { | |
const context = getContext() | |
this.runTimeUrl = context.runTimeUrl | |
this.repositoryNwo = context.repositoryNwo | |
this.runTimeToken = context.runTimeToken | |
this.buildVersion = context.buildVersion | |
this.buildActor = context.buildActor | |
this.actionsId = context.actionsId | |
this.githubToken = context.githubToken | |
this.workflowRun = context.workflowRun | |
this.deploymentInfo = null | |
this.githubApiUrl = context.githubApiUrl | |
this.githubServerUrl = context.githubServerUrl | |
this.artifactName = context.artifactName | |
this.isPreview = context.isPreview === true | |
this.timeout = MAX_TIMEOUT | |
this.startTime = null | |
} | |
// Ask the runtime for the unsigned artifact URL and deploy to GitHub Pages | |
// by creating a deployment with that artifact | |
async create(idToken) { | |
if (Number(core.getInput('timeout')) > MAX_TIMEOUT) { | |
core.warning( | |
`Warning: timeout value is greater than the allowed maximum - timeout set to the maximum of ${MAX_TIMEOUT} milliseconds.` | |
) | |
} | |
const TIMEOUT_INPUT = Number(core.getInput('timeout')) | |
this.timeout = !TIMEOUT_INPUT || TIMEOUT_INPUT <= 0 ? MAX_TIMEOUT : Math.min(TIMEOUT_INPUT, MAX_TIMEOUT) | |
try { | |
core.debug(`Actor: ${this.buildActor}`) | |
core.debug(`Action ID: ${this.actionsId}`) | |
core.debug(`Actions Workflow Run ID: ${this.workflowRun}`) | |
const artifactUrl = await getSignedArtifactUrl({ | |
runtimeToken: this.runTimeToken, | |
workflowRunId: this.workflowRun, | |
artifactName: this.artifactName | |
}) | |
const deployment = await createPagesDeployment({ | |
githubToken: this.githubToken, | |
artifactUrl, | |
buildVersion: this.buildVersion, | |
idToken, | |
isPreview: this.isPreview | |
}) | |
if (deployment) { | |
this.deploymentInfo = { | |
...deployment, | |
id: deployment.id || deployment.status_url?.split('/')?.pop() || this.buildVersion, | |
pending: true | |
} | |
this.startTime = Date.now() | |
} | |
core.info(`Created deployment for ${this.buildVersion}, ID: ${this.deploymentInfo?.id}`) | |
core.debug(JSON.stringify(deployment)) | |
return deployment | |
} catch (error) { | |
core.error(error.stack) | |
// output raw error in debug mode. | |
core.debug(JSON.stringify(error)) | |
// 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}. ` | |
if (error.status === 400) { | |
errorMessage += `Responded with: ${error.message}` | |
} else if (error.status === 403) { | |
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}` | |
} else if (error.status >= 500) { | |
errorMessage += | |
'Server error, is githubstatus.com reporting a Pages outage? Please re-run the deployment at a later time.' | |
} | |
throw new Error(errorMessage) | |
} else { | |
// istanbul ignore next | |
throw error | |
} | |
} | |
} | |
// Poll the deployment endpoint for status | |
async check() { | |
// Don't attempt to check status if no deployment was created | |
if (!this.deploymentInfo) { | |
core.setFailed(temporaryErrorStatus.not_found) | |
return | |
} | |
if (this.deploymentInfo.pending !== true) { | |
core.setFailed(temporaryErrorStatus.unknown_status) | |
return | |
} | |
const deploymentId = this.deploymentInfo.id || this.buildVersion | |
const reportingInterval = Number(core.getInput('reporting_interval')) | |
const maxErrorCount = Number(core.getInput('error_count')) | |
let errorCount = 0 | |
// Time in milliseconds between two deployment status report when status errored, default 0. | |
let errorReportingInterval = 0 | |
let deployment = null | |
let errorStatus = 0 | |
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ | |
while (true) { | |
// Handle reporting interval | |
await new Promise(resolve => setTimeout(resolve, reportingInterval + errorReportingInterval)) | |
// Check status | |
try { | |
deployment = await getPagesDeploymentStatus({ | |
githubToken: this.githubToken, | |
deploymentId | |
}) | |
if (deployment.status === 'succeed') { | |
core.info('Reported success!') | |
core.setOutput('status', 'succeed') | |
this.deploymentInfo.pending = false | |
break | |
} else if (finalErrorStatus[deployment.status]) { | |
// Fall into permanent error, it may be caused by ongoing incident, malicious deployment content, exhausted automatic retry times, invalid artifact, etc. | |
core.setFailed(finalErrorStatus[deployment.status]) | |
this.deploymentInfo.pending = false | |
break | |
} else if (temporaryErrorStatus[deployment.status]) { | |
// A temporary error happened, will query the status again | |
core.warning(temporaryErrorStatus[deployment.status]) | |
} else { | |
core.info('Current status: ' + deployment.status) | |
} | |
// reset the error reporting interval once get the proper status back. | |
errorReportingInterval = 0 | |
} catch (error) { | |
core.error(error.stack) | |
// output raw error in debug mode. | |
core.debug(JSON.stringify(error)) | |
// build customized error message based on server response | |
if (error.response) { | |
errorStatus = error.status || error.response.status | |
errorCount++ | |
// set the maximum error reporting interval greater than 15 sec but below 30 sec. | |
if (errorReportingInterval < 1000 * 15) { | |
errorReportingInterval = (errorReportingInterval << 1) | 1 | |
} | |
} | |
} | |
if (errorCount >= maxErrorCount) { | |
core.error('Too many errors, aborting!') | |
core.setFailed('Failed with status code: ' + errorStatus) | |
// Explicitly cancel the deployment | |
await this.cancel() | |
return | |
} | |
// Handle timeout | |
if (Date.now() - this.startTime >= this.timeout) { | |
core.error('Timeout reached, aborting!') | |
core.setFailed('Timeout reached, aborting!') | |
// Explicitly cancel the deployment | |
await this.cancel() | |
return | |
} | |
} | |
} | |
async cancel() { | |
// Don't attempt to cancel if no deployment was created | |
if (!this.deploymentInfo || this.deploymentInfo.pending !== true) { | |
core.debug('No deployment to cancel') | |
return | |
} | |
// Cancel the deployment | |
try { | |
const deploymentId = this.deploymentInfo.id || this.buildVersion | |
await cancelPagesDeployment({ | |
githubToken: this.githubToken, | |
deploymentId | |
}) | |
core.info(`Canceled deployment with ID ${deploymentId}`) | |
this.deploymentInfo.pending = false | |
} catch (error) { | |
core.setFailed(error) | |
if (error.response?.data) { | |
core.error(JSON.stringify(error.response.data)) | |
} | |
} | |
} | |
} | |
module.exports = { Deployment, MAX_TIMEOUT } |