2021-12-13 23:03:09 -05:00
const core = require ( '@actions/core' )
// All variables we need from the runtime are loaded here
const getContext = require ( './context' )
2023-02-22 08:05:32 -06:00
const {
2023-02-22 08:26:42 -06:00
getSignedArtifactUrl ,
createPagesDeployment ,
getPagesDeploymentStatus ,
cancelPagesDeployment
2023-02-22 08:05:32 -06:00
} = require ( './api-client' )
2021-12-13 23:03:09 -05:00
2023-03-03 15:42:55 -06:00
const temporaryErrorStatus = {
2022-08-23 17:19:21 -07:00
unknown_status : 'Unable to get deployment status.' ,
not_found : 'Deployment not found.' ,
deployment_attempt_error : 'Deployment temporarily failed, a retry will be automatically scheduled...'
2022-01-26 16:49:02 -08:00
}
2023-03-03 15:42:55 -06:00
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.'
}
2021-12-13 23:03:09 -05:00
class Deployment {
2022-08-23 17:19:21 -07:00
constructor () {
const context = getContext ()
this . runTimeUrl = context . runTimeUrl
this . repositoryNwo = context . repositoryNwo
this . runTimeToken = context . runTimeToken
this . buildVersion = context . buildVersion
this . buildActor = context . buildActor
2023-02-22 08:01:01 -06:00
this . actionsId = context . actionsId
2022-08-23 17:19:21 -07:00
this . githubToken = context . githubToken
this . workflowRun = context . workflowRun
this . deploymentInfo = null
this . githubApiUrl = context . githubApiUrl
2022-11-17 12:35:08 -06:00
this . githubServerUrl = context . githubServerUrl
2022-08-23 17:19:21 -07:00
this . artifactName = context . artifactName
2022-09-09 18:44:03 -05:00
this . isPreview = context . isPreview === true
2022-08-23 17:19:21 -07:00
}
2021-12-13 23:03:09 -05:00
2022-08-23 17:19:21 -07:00
// Ask the runtime for the unsigned artifact URL and deploy to GitHub Pages
// by creating a deployment with that artifact
async create ( idToken ) {
try {
2023-03-06 22:03:56 -06:00
core . debug ( `Actor: ${ this . buildActor } ` )
core . debug ( `Action ID: ${ this . actionsId } ` )
core . debug ( `Actions Workflow Run ID: ${ this . workflowRun } ` )
2021-12-13 23:03:09 -05:00
2023-02-22 08:05:32 -06:00
const artifactUrl = await getSignedArtifactUrl ({
runtimeToken : this . runTimeToken ,
workflowRunId : this . workflowRun ,
artifactName : this . artifactName
})
2023-02-22 08:15:23 -06:00
const deployment = await createPagesDeployment ({
githubToken : this . githubToken ,
artifactUrl ,
buildVersion : this . buildVersion ,
idToken ,
isPreview : this . isPreview
2022-08-23 17:19:21 -07:00
})
2023-03-09 08:19:19 -06:00
if ( deployment ) {
this . deploymentInfo = {
... deployment ,
2023-03-09 08:20:32 -06:00
id : deployment . id || deployment . status_url ? . split ( '/' ) ? . pop () || this . buildVersion ,
2023-03-09 08:19:19 -06:00
pending : true
}
2022-08-23 17:19:21 -07:00
}
2023-02-22 08:15:23 -06:00
2023-03-09 08:19:19 -06:00
core . info ( `Created deployment for ${ this . buildVersion } , ID: ${ this . deploymentInfo ? . id } ` )
2023-03-06 22:03:56 -06:00
core . debug ( JSON . stringify ( deployment ))
2023-02-22 08:15:23 -06:00
return deployment
2022-08-23 17:19:21 -07:00
} catch ( error ) {
2023-02-22 08:15:23 -06:00
core . error ( error . stack )
2022-08-23 17:19:21 -07:00
// output raw error in debug mode.
core . debug ( JSON . stringify ( error ))
// build customized error message based on server response
if ( error . response ) {
2023-03-08 19:43:11 -06:00
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 ) {
2023-02-22 08:15:23 -06:00
errorMessage += 'Ensure GITHUB_TOKEN has permission "pages: write".'
2023-03-08 19:43:11 -06:00
} else if ( error . status === 404 ) {
2022-11-17 12:35:08 -06:00
const pagesSettingsUrl = ` ${ this . githubServerUrl } / ${ this . repositoryNwo } /settings/pages`
errorMessage += `Ensure GitHub Pages has been enabled: ${ pagesSettingsUrl } `
2023-03-08 19:43:11 -06:00
} else if ( error . status >= 500 ) {
2023-02-22 08:26:42 -06:00
errorMessage +=
'Server error, is githubstatus.com reporting a Pages outage? Please re-run the deployment at a later time.'
2021-12-20 10:13:46 -08:00
}
2023-02-22 08:15:23 -06:00
throw new Error ( errorMessage )
2022-08-23 17:19:21 -07:00
} else {
throw error
2021-12-13 23:03:09 -05:00
}
}
}
2022-08-23 17:19:21 -07:00
// Poll the deployment endpoint for status
async check () {
2023-02-22 14:05:35 -06:00
// Don't attempt to check status if no deployment was created
if ( ! this . deploymentInfo ) {
2023-03-03 15:42:55 -06:00
core . setFailed ( temporaryErrorStatus . not_found )
2023-02-22 14:05:35 -06:00
return
}
if ( this . deploymentInfo . pending !== true ) {
2023-03-03 15:42:55 -06:00
core . setFailed ( temporaryErrorStatus . unknown_status )
2023-02-22 14:05:35 -06:00
return
}
const deploymentId = this . deploymentInfo . id || this . buildVersion
2023-02-22 08:22:23 -06:00
const timeout = Number ( core . getInput ( 'timeout' ))
const reportingInterval = Number ( core . getInput ( 'reporting_interval' ))
const maxErrorCount = Number ( core . getInput ( 'error_count' ))
2022-08-23 17:19:21 -07:00
2023-02-22 08:22:23 -06:00
let startTime = Date . now ()
let errorCount = 0
// Time in milliseconds between two deployment status report when status errored, default 0.
let errorReportingInterval = 0
2023-03-03 15:42:55 -06:00
let deployment = null
let errorStatus = 0
2023-02-22 08:22:23 -06:00
2023-03-03 15:42:55 -06:00
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
while ( true ) {
// Handle reporting interval
await new Promise ( resolve => setTimeout ( resolve , reportingInterval + errorReportingInterval ))
2022-08-23 17:19:21 -07:00
2023-03-03 15:42:55 -06:00
// Check status
try {
deployment = await getPagesDeploymentStatus ({
2023-02-22 08:22:23 -06:00
githubToken : this . githubToken ,
deploymentId
2022-08-23 17:19:21 -07:00
})
2023-03-03 15:42:55 -06:00
if ( deployment . status === 'succeed' ) {
2022-08-23 17:19:21 -07:00
core . info ( 'Reported success!' )
core . setOutput ( 'status' , 'succeed' )
2023-02-22 14:05:35 -06:00
this . deploymentInfo . pending = false
2022-08-23 17:19:21 -07:00
break
2023-03-03 15:42:55 -06:00
} 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 ])
2023-02-22 14:05:35 -06:00
this . deploymentInfo . pending = false
2022-08-23 17:19:21 -07:00
break
2023-03-03 15:42:55 -06:00
} else if ( temporaryErrorStatus [ deployment . status ]) {
2022-08-23 17:19:21 -07:00
// A temporary error happened, will query the status again
2023-03-03 15:42:55 -06:00
core . warning ( temporaryErrorStatus [ deployment . status ])
2022-08-23 17:19:21 -07:00
} else {
2023-03-03 15:42:55 -06:00
core . info ( 'Current status: ' + deployment . status )
2022-08-23 17:19:21 -07:00
}
2023-03-03 15:42:55 -06:00
// 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 ) {
2023-03-09 08:23:21 -06:00
errorStatus = error . status || error . response . status
2023-03-03 15:42:55 -06:00
2022-08-23 17:19:21 -07:00
errorCount ++
2023-02-22 08:23:39 -06:00
// set the maximum error reporting interval greater than 15 sec but below 30 sec.
2022-08-23 17:19:21 -07:00
if ( errorReportingInterval < 1000 * 15 ) {
errorReportingInterval = ( errorReportingInterval << 1 ) | 1
}
}
}
2023-03-03 15:42:55 -06:00
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 () - startTime >= timeout ) {
core . error ( 'Timeout reached, aborting!' )
core . setFailed ( 'Timeout reached, aborting!' )
// Explicitly cancel the deployment
await this . cancel ()
return
2022-08-23 17:19:21 -07:00
}
}
}
2022-08-23 21:32:40 -07:00
async cancel () {
2023-02-22 08:15:23 -06:00
// Don't attempt to cancel if no deployment was created
2023-02-22 14:05:35 -06:00
if ( ! this . deploymentInfo || this . deploymentInfo . pending !== true ) {
2022-08-23 21:32:40 -07:00
return
}
// Cancel the deployment
try {
2023-02-22 14:05:35 -06:00
const deploymentId = this . deploymentInfo . id || this . buildVersion
2023-02-22 08:23:03 -06:00
await cancelPagesDeployment ({
githubToken : this . githubToken ,
deploymentId
})
core . info ( `Canceled deployment with ID ${ deploymentId } ` )
2023-02-22 14:05:35 -06:00
this . deploymentInfo . pending = false
2022-08-23 21:32:40 -07:00
} catch ( error ) {
core . setFailed ( error )
2023-02-22 08:23:39 -06:00
if ( error . response ? . data ) {
core . error ( JSON . stringify ( error . response . data ))
2022-08-23 21:32:40 -07:00
}
}
}
2022-08-23 17:19:21 -07:00
}
2023-03-03 15:42:55 -06:00
2022-08-23 17:19:21 -07:00
module . exports = { Deployment }