Rename 'functions' directory to 'internal'

This commit is contained in:
James M. Greene
2023-04-18 00:32:13 -05:00
parent efce2761be
commit 6657d340c9
8 changed files with 14 additions and 14 deletions
+170
View File
@@ -0,0 +1,170 @@
const core = require('@actions/core')
const github = require('@actions/github')
const hc = require('@actions/http-client')
const { RequestError } = require('@octokit/request-error')
const HttpStatusMessages = require('http-status-messages')
// All variables we need from the runtime are loaded here
const getContext = require('./context')
async function processRuntimeResponse(res, requestOptions) {
// Parse the response body as JSON
let obj = null
try {
const contents = await res.readBody()
if (contents && contents.length > 0) {
obj = JSON.parse(contents)
}
} catch (error) {
// Invalid resource (contents not json); leaving resulting obj as null
}
// Specific response shape aligned with Octokit
const response = {
url: res.message?.url || requestOptions.url,
status: res.message?.statusCode || 0,
headers: {
...res.message?.headers
},
data: obj
}
// Forcibly throw errors for negative HTTP status codes!
// @actions/http-client doesn't do this by default.
// Mimic the errors thrown by Octokit for consistency.
if (response.status >= 400) {
// Try to get an error message from the response body
const errorMsg =
(typeof response.data === 'string' && response.data) ||
response.data?.error ||
response.data?.message ||
// Try the Node HTTP IncomingMessage's statusMessage property
res.message?.statusMessage ||
// Fallback to the HTTP status message based on the status code
HttpStatusMessages[response.status] ||
// Or if the status code is unexpected...
`Unknown error (${response.status})`
throw new RequestError(errorMsg, response.status, {
response,
request: requestOptions
})
}
return response
}
async function getSignedArtifactUrl({ runtimeToken, workflowRunId, artifactName }) {
const { runTimeUrl: RUNTIME_URL } = getContext()
const artifactExchangeUrl = `${RUNTIME_URL}_apis/pipelines/workflows/${workflowRunId}/artifacts?api-version=6.0-preview`
const httpClient = new hc.HttpClient()
let data = null
try {
const requestHeaders = {
accept: 'application/json',
authorization: `Bearer ${runtimeToken}`
}
const requestOptions = {
method: 'GET',
url: artifactExchangeUrl,
headers: {
...requestHeaders
},
body: null
}
core.info(`Artifact exchange URL: ${artifactExchangeUrl}`)
const res = await httpClient.get(artifactExchangeUrl, requestHeaders)
// May throw a RequestError (HttpError)
const response = await processRuntimeResponse(res, requestOptions)
data = response.data
core.debug(JSON.stringify(data))
} catch (error) {
core.error('Getting signed artifact URL failed', error)
throw error
}
const artifactRawUrl = data?.value?.find(artifact => artifact.name === artifactName)?.url
if (!artifactRawUrl) {
throw new Error(
'No uploaded artifact was found! Please check if there are any errors at build step, or uploaded artifact name is correct.'
)
}
const signedArtifactUrl = `${artifactRawUrl}&%24expand=SignedContent`
return signedArtifactUrl
}
async function createPagesDeployment({ githubToken, artifactUrl, buildVersion, idToken, isPreview = false }) {
const octokit = github.getOctokit(githubToken)
const payload = {
artifact_url: artifactUrl,
pages_build_version: buildVersion,
oidc_token: idToken
}
if (isPreview === true) {
payload.preview = true
}
core.info(`Creating Pages deployment with payload:\n${JSON.stringify(payload, null, '\t')}`)
try {
const response = await octokit.request('POST /repos/{owner}/{repo}/pages/deployments', {
owner: github.context.repo.owner,
repo: github.context.repo.repo,
...payload
})
return response.data
} catch (error) {
core.error('Creating Pages deployment failed', error)
throw error
}
}
async function getPagesDeploymentStatus({ githubToken, deploymentId }) {
const octokit = github.getOctokit(githubToken)
core.info('Getting Pages deployment status...')
try {
const response = await octokit.request('GET /repos/{owner}/{repo}/pages/deployments/{deploymentId}', {
owner: github.context.repo.owner,
repo: github.context.repo.repo,
deploymentId
})
return response.data
} catch (error) {
core.error('Getting Pages deployment status failed', error)
throw error
}
}
async function cancelPagesDeployment({ githubToken, deploymentId }) {
const octokit = github.getOctokit(githubToken)
core.info('Canceling Pages deployment...')
try {
const response = await octokit.request('POST /repos/{owner}/{repo}/pages/deployments/{deploymentId}/cancel', {
owner: github.context.repo.owner,
repo: github.context.repo.repo,
deploymentId
})
return response.data
} catch (error) {
core.error('Canceling Pages deployment failed', error)
throw error
}
}
module.exports = {
getSignedArtifactUrl,
createPagesDeployment,
getPagesDeploymentStatus,
cancelPagesDeployment
}
+30
View File
@@ -0,0 +1,30 @@
const core = require('@actions/core')
// Load variables from Actions runtime
function getRequiredVars() {
return {
runTimeUrl: process.env.ACTIONS_RUNTIME_URL,
workflowRun: process.env.GITHUB_RUN_ID,
runTimeToken: process.env.ACTIONS_RUNTIME_TOKEN,
repositoryNwo: process.env.GITHUB_REPOSITORY,
buildVersion: process.env.GITHUB_SHA,
buildActor: process.env.GITHUB_ACTOR,
actionsId: process.env.GITHUB_ACTION,
githubToken: core.getInput('token'),
githubApiUrl: process.env.GITHUB_API_URL ?? 'https://api.github.com',
githubServerUrl: process.env.GITHUB_SERVER_URL ?? 'https://github.com',
artifactName: core.getInput('artifact_name') || 'github-pages',
isPreview: core.getInput('preview') === 'true'
}
}
module.exports = function getContext() {
const requiredVars = getRequiredVars()
for (const variable in requiredVars) {
if (requiredVars[variable] === undefined) {
throw new Error(`${variable} is undefined. Cannot continue.`)
}
}
core.debug('all variables are set')
return requiredVars
}
+229
View File
@@ -0,0 +1,229 @@
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.'
}
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
}
// Ask the runtime for the unsigned artifact URL and deploy to GitHub Pages
// by creating a deployment with that artifact
async create(idToken) {
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
}
}
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 timeout = Number(core.getInput('timeout'))
const reportingInterval = Number(core.getInput('reporting_interval'))
const maxErrorCount = Number(core.getInput('error_count'))
let startTime = Date.now()
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() - startTime >= 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 }