Merge pull request #279 from actions/artifacts-next-ga

Use artifacts v4
This commit is contained in:
Jess Bees
2023-12-18 20:42:41 -05:00
committed by GitHub
11 changed files with 1025 additions and 5952 deletions

View File

@@ -6,11 +6,11 @@ This action is used to deploy [Actions artifacts][artifacts] to [GitHub Pages](h
## Usage
See [action.yml](action.yml) for the various `inputs` this action supports.
See [action.yml](action.yml) for the various `inputs` this action supports (or [below](#inputs-📥)).
For examples that make use of this action, check out our [starter-workflows][starter-workflows] in a variety of frameworks.
This action expects an artifact named `github-pages` to have been created prior to execution. This is done automatically when using [`actions/upload-pages-artifact`][upload-pages-artifact].
This action deploys a Pages site previously uploaded as an artifact (e.g. using [`actions/upload-pages-artifact`][upload-pages-artifact]).
We recommend this action to be used in a dedicated job:
@@ -71,7 +71,7 @@ jobs:
There are a few important considerations to be aware of:
1. The artifact being deployed must have been uploaded in a previous step, either in the same job or a separate job that doesn't execute until the upload is complete.
1. The artifact being deployed must have been uploaded in a previous step, either in the same job or a separate job that doesn't execute until the upload is complete. See [`actions/upload-pages-artifact`][upload-pages-artifact] for more information about the format of the artifact we expect.
2. The job that executes the deployment must at minimum have the following permissions:
- `pages: write`

351
dist/index.js generated vendored
View File

@@ -24859,7 +24859,7 @@ __export(dist_src_exports, {
module.exports = __toCommonJS(dist_src_exports);
// pkg/dist-src/version.js
var VERSION = "9.1.4";
var VERSION = "9.1.5";
// pkg/dist-src/normalize-paginated-list-response.js
function normalizePaginatedListResponse(response) {
@@ -27863,102 +27863,6 @@ class Deprecation extends Error {
exports.Deprecation = Deprecation;
/***/ }),
/***/ 3703:
/***/ ((module) => {
// Source: 2014-06-11: http://en.wikipedia.org/wiki/HTTP_status_codes
module.exports = {
100: "Continue",
101: "Switching Protocols",
102: "Processing",
200: "OK",
201: "Created",
202: "Accepted",
203: "Non-Authoritative Information",
204: "No Content",
205: "Reset Content",
206: "Partial Content",
207: "Multi-Status",
208: "Already Reported",
226: "IM Used",
300: "Multiple Choices",
301: "Moved Permanently",
302: "Found",
303: "See Other",
304: "Not Modified",
305: "Use Proxy",
306: "Switch Proxy",
307: "Temporary Redirect",
308: "Permanent Redirect",
400: "Bad Request",
401: "Unauthorized",
402: "Payment Required",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
406: "Not Acceptable",
407: "Proxy Authentication Required",
408: "Request Timeout",
409: "Conflict",
410: "Gone",
411: "Length Required",
412: "Precondition Failed",
413: "Request Entity Too Large",
414: "Request-URI Too Long",
415: "Unsupported Media Type",
416: "Requested Range Not Satisfiable",
417: "Expectation Failed",
418: "I'm a teapot",
419: "Authentication Timeout",
420: "Method Failure",
420: "Enhance Your Calm",
422: "Unprocessable Entity",
423: "Locked",
424: "Failed Dependency",
426: "Upgrade Required",
428: "Precondition Required",
429: "Too Many Requests",
431: "Request Header Fields Too Large",
440: "Login Timeout",
444: "No Response",
449: "Retry With",
450: "Blocked by Windows Parental Controls",
451: "Unavailable For Legal Reasons",
451: "Redirect",
494: "Request Header Too Large",
495: "Cert Error",
496: "No Cert",
497: "HTTP to HTTPS",
499: "Client Closed Request",
500: "Internal Server Error",
501: "Not Implemented",
502: "Bad Gateway",
503: "Service Unavailable",
504: "Gateway Timeout",
505: "HTTP Version Not Supported",
506: "Variant Also Negotiates",
507: "Insufficient Storage",
508: "Loop Detected",
509: "Bandwidth Limit Exceeded",
510: "Not Extended",
511: "Network Authentication Required",
520: "Origin Error",
521: "Web server is down",
522: "Connection timed out",
523: "Proxy Declined Request",
524: "A timeout occurred",
598: "Network read timeout error",
599: "Network connect timeout error"
};
/***/ }),
/***/ 1223:
@@ -29659,6 +29563,18 @@ module.exports = class BodyReadable extends Readable {
return super.destroy(err)
}
_destroy (err, callback) {
// Workaround for Node "bug". If the stream is destroyed in same
// tick as it is created, then a user who is waiting for a
// promise (i.e micro tick) for installing a 'error' listener will
// never get a chance and will always encounter an unhandled exception.
// - tick => process.nextTick(fn)
// - micro tick => queueMicrotask(fn)
queueMicrotask(() => {
callback(err)
})
}
emit (ev, ...args) {
if (ev === 'data') {
// Node < 16.7
@@ -29763,7 +29679,7 @@ module.exports = class BodyReadable extends Readable {
}
}
if (this.closed) {
if (this._readableState.closeEmitted) {
return Promise.resolve(null)
}
@@ -29807,33 +29723,44 @@ function isUnusable (self) {
}
async function consume (stream, type) {
if (isUnusable(stream)) {
throw new TypeError('unusable')
}
assert(!stream[kConsume])
return new Promise((resolve, reject) => {
stream[kConsume] = {
type,
stream,
resolve,
reject,
length: 0,
body: []
if (isUnusable(stream)) {
const rState = stream._readableState
if (rState.destroyed && rState.closeEmitted === false) {
stream
.on('error', err => {
reject(err)
})
.on('close', () => {
reject(new TypeError('unusable'))
})
} else {
reject(rState.errored ?? new TypeError('unusable'))
}
} else {
stream[kConsume] = {
type,
stream,
resolve,
reject,
length: 0,
body: []
}
stream
.on('error', function (err) {
consumeFinish(this[kConsume], err)
})
.on('close', function () {
if (this[kConsume].body !== null) {
consumeFinish(this[kConsume], new RequestAbortedError())
}
})
queueMicrotask(() => consumeStart(stream[kConsume]))
}
stream
.on('error', function (err) {
consumeFinish(this[kConsume], err)
})
.on('close', function () {
if (this[kConsume].body !== null) {
consumeFinish(this[kConsume], new RequestAbortedError())
}
})
process.nextTick(consumeStart, stream[kConsume])
})
}
@@ -33209,12 +33136,19 @@ function writeStream ({ h2stream, body, client, request, socket, contentLength,
body.resume()
}
}
const onAbort = function () {
if (finished) {
return
const onClose = function () {
// 'close' might be emitted *before* 'error' for
// broken streams. Wait a tick to avoid this case.
queueMicrotask(() => {
// It's only safe to remove 'error' listener after
// 'close'.
body.removeListener('error', onFinished)
})
if (!finished) {
const err = new RequestAbortedError()
queueMicrotask(() => onFinished(err))
}
const err = new RequestAbortedError()
queueMicrotask(() => onFinished(err))
}
const onFinished = function (err) {
if (finished) {
@@ -33232,8 +33166,7 @@ function writeStream ({ h2stream, body, client, request, socket, contentLength,
body
.removeListener('data', onData)
.removeListener('end', onFinished)
.removeListener('error', onFinished)
.removeListener('close', onAbort)
.removeListener('close', onClose)
if (!err) {
try {
@@ -33256,7 +33189,7 @@ function writeStream ({ h2stream, body, client, request, socket, contentLength,
.on('data', onData)
.on('end', onFinished)
.on('error', onFinished)
.on('close', onAbort)
.on('close', onClose)
if (body.resume) {
body.resume()
@@ -51320,120 +51253,62 @@ function wrappy (fn, cb) {
const core = __nccwpck_require__(2186)
const github = __nccwpck_require__(5438)
const hc = __nccwpck_require__(6255)
const { RequestError } = __nccwpck_require__(537)
const HttpStatusMessages = __nccwpck_require__(3703)
// All variables we need from the runtime are loaded here
const getContext = __nccwpck_require__(8454)
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 getSignedArtifactMetadata({ 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
async function getArtifactMetadata({ githubToken, runId, artifactName }) {
const octokit = github.getOctokit(githubToken)
try {
const requestHeaders = {
accept: 'application/json',
authorization: `Bearer ${runtimeToken}`
}
const requestOptions = {
method: 'GET',
url: artifactExchangeUrl,
headers: {
...requestHeaders
},
body: null
core.info(`Fetching artifact metadata for ${artifactName} in run ${runId}`)
const response = await octokit.request(
'GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts?name={artifactName}',
{
owner: github.context.repo.owner,
repo: github.context.repo.repo,
run_id: runId,
artifactName: artifactName
}
)
const artifactCount = response.data.total_count
core.debug(`List artifact count: ${artifactCount}`)
if (artifactCount === 0) {
throw new Error(
`No artifacts found for workflow run ${runId}. Ensure artifacts are uploaded with actions/artifact@v4 or later.`
)
} else if (artifactCount > 1) {
throw new Error(
`Multiple artifact unexpectedly found for workflow run ${runId}. Artifact count is ${artifactCount}.`
)
}
core.info(`Artifact exchange URL: ${artifactExchangeUrl}`)
const res = await httpClient.get(artifactExchangeUrl, requestHeaders)
const artifact = response.data.artifacts[0]
core.debug(`Artifact: ${JSON.stringify(artifact)}`)
// May throw a RequestError (HttpError)
const response = await processRuntimeResponse(res, requestOptions)
const artifactSize = artifact.size_in_bytes
if (!artifactSize) {
core.warning('Artifact size was not found. Unable to verify if artifact size exceeds the allowed size.')
}
data = response.data
core.debug(JSON.stringify(data))
return {
id: artifact.id,
size: artifactSize
}
} catch (error) {
core.error('Getting signed artifact URL failed', error)
core.error(
'Fetching artifact metadata failed. Is githubstatus.com reporting issues with API requests, Pages or Actions? Please re-run the deployment at a later time.',
error
)
throw error
}
const artifact = data?.value?.find(artifact => artifact.name === artifactName)
const artifactRawUrl = artifact?.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`
const artifactSize = artifact?.size
if (!artifactSize) {
core.warning('Artifact size was not found. Unable to verify if artifact size exceeds the allowed size.')
}
return {
url: signedArtifactUrl,
size: artifactSize
}
}
async function createPagesDeployment({ githubToken, artifactUrl, buildVersion, idToken, isPreview = false }) {
async function createPagesDeployment({ githubToken, artifactId, buildVersion, idToken, isPreview = false }) {
const octokit = github.getOctokit(githubToken)
const payload = {
artifact_url: artifactUrl,
artifact_id: artifactId,
pages_build_version: buildVersion,
oidc_token: idToken
}
@@ -51493,7 +51368,7 @@ async function cancelPagesDeployment({ githubToken, deploymentId }) {
}
module.exports = {
getSignedArtifactMetadata,
getArtifactMetadata,
createPagesDeployment,
getPagesDeploymentStatus,
cancelPagesDeployment
@@ -51510,9 +51385,7 @@ const core = __nccwpck_require__(2186)
// 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,
@@ -51547,7 +51420,7 @@ const core = __nccwpck_require__(2186)
// All variables we need from the runtime are loaded here
const getContext = __nccwpck_require__(8454)
const {
getSignedArtifactMetadata,
getArtifactMetadata,
createPagesDeployment,
getPagesDeploymentStatus,
cancelPagesDeployment
@@ -51575,9 +51448,7 @@ const SIZE_LIMIT_DESCRIPTION = '1 GB'
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
@@ -51592,8 +51463,8 @@ class Deployment {
this.startTime = null
}
// Ask the runtime for the unsigned artifact URL and deploy to GitHub Pages
// by creating a deployment with that artifact
// Call GitHub api to fetch artifacts matching the provided name and deploy to GitHub Pages
// by creating a deployment with that artifact id
async create(idToken) {
if (Number(core.getInput('timeout')) > MAX_TIMEOUT) {
core.warning(
@@ -51609,9 +51480,9 @@ class Deployment {
core.debug(`Action ID: ${this.actionsId}`)
core.debug(`Actions Workflow Run ID: ${this.workflowRun}`)
const artifactData = await getSignedArtifactMetadata({
runtimeToken: this.runTimeToken,
workflowRunId: this.workflowRun,
const artifactData = await getArtifactMetadata({
githubToken: this.githubToken,
runId: this.workflowRun,
artifactName: this.artifactName
})
@@ -51623,7 +51494,7 @@ class Deployment {
const deployment = await createPagesDeployment({
githubToken: this.githubToken,
artifactUrl: artifactData.url,
artifactId: artifactData.id,
buildVersion: this.buildVersion,
idToken,
isPreview: this.isPreview

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

2
dist/licenses.txt generated vendored
View File

@@ -465,8 +465,6 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
http-status-messages
once
ISC
The ISC License

5993
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,10 +5,7 @@
"main": "./dist/index.js",
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.0",
"@actions/http-client": "^2.2.0",
"@octokit/request-error": "^5.0.1",
"http-status-messages": "^1.1.0"
"@actions/github": "^6.0.0"
},
"devDependencies": {
"@vercel/ncc": "^0.38.1",

View File

@@ -4,9 +4,7 @@ const path = require('path')
describe('with all environment variables set', () => {
beforeEach(() => {
process.env.ACTIONS_RUNTIME_URL = 'http://my-url'
process.env.GITHUB_RUN_ID = '123'
process.env.ACTIONS_RUNTIME_TOKEN = 'a-token'
process.env.GITHUB_REPOSITORY = 'actions/is-awesome'
process.env.GITHUB_TOKEN = 'gha-token'
process.env.GITHUB_SHA = '123abc'
@@ -26,7 +24,7 @@ describe('with all environment variables set', () => {
describe('with variables missing', () => {
it('execution fails if there are missing variables', done => {
delete process.env.ACTIONS_RUNTIME_URL
delete process.env.GITHUB_RUN_ID
const ip = path.join(__dirname, '../index.js')
cp.exec(`node ${ip}`, { env: process.env }, (err, stdout) => {
expect(stdout).toBe('')

View File

@@ -1,6 +1,4 @@
const core = require('@actions/core')
// For mocking network calls with core http (http-client)
const nock = require('nock')
// For mocking network calls with native Fetch (octokit)
const { MockAgent, setGlobalDispatcher } = require('undici')
@@ -14,9 +12,7 @@ describe('Deployment', () => {
beforeEach(() => {
jest.clearAllMocks()
process.env.ACTIONS_RUNTIME_URL = 'http://my-url/'
process.env.GITHUB_RUN_ID = '123'
process.env.ACTIONS_RUNTIME_TOKEN = 'a-token'
process.env.GITHUB_REPOSITORY = 'actions/is-awesome'
process.env.GITHUB_TOKEN = 'gha-token'
process.env.GITHUB_SHA = '123abc'
@@ -67,14 +63,19 @@ describe('Deployment', () => {
it('can successfully create a deployment', async () => {
process.env.GITHUB_SHA = 'valid-build-version'
const artifactExchangeScope = nock(`http://my-url`)
.get('/_apis/pipelines/workflows/123/artifacts?api-version=6.0-preview')
.reply(200, {
value: [
{ url: 'https://another-artifact.com', name: 'another-artifact' },
{ url: 'https://fake-artifact.com', name: 'github-pages' }
]
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(
200,
{
total_count: 1,
artifacts: [{ id: 11, name: `github-pages`, size_in_bytes: 221 }]
},
{ headers: { 'content-type': 'application/json' } }
)
mockPool
.intercept({
@@ -85,10 +86,10 @@ describe('Deployment', () => {
const keys = Object.keys(body).sort()
return (
keys.length === 3 &&
keys[0] === 'artifact_url' &&
keys[0] === 'artifact_id' &&
keys[1] === 'oidc_token' &&
keys[2] === 'pages_build_version' &&
body.artifact_url === 'https://fake-artifact.com&%24expand=SignedContent' &&
body.artifact_id === 11 &&
body.pages_build_version === process.env.GITHUB_SHA &&
body.oidc_token === fakeJwt
)
@@ -113,21 +114,24 @@ describe('Deployment', () => {
expect(core.info).toHaveBeenLastCalledWith(
expect.stringMatching(new RegExp(`^Created deployment for ${process.env.GITHUB_SHA}`))
)
artifactExchangeScope.done()
})
it('can successfully create a preview deployment', async () => {
process.env.GITHUB_SHA = 'valid-build-version'
const artifactExchangeScope = nock(`http://my-url`)
.get('/_apis/pipelines/workflows/123/artifacts?api-version=6.0-preview')
.reply(200, {
value: [
{ url: 'https://another-artifact.com', name: 'another-artifact' },
{ url: 'https://fake-artifact.com', name: 'github-pages' }
]
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(
200,
{
total_count: 1,
artifacts: [{ id: 11, name: `github-pages`, size_in_bytes: 221 }]
},
{ headers: { 'content-type': 'application/json' } }
)
mockPool
.intercept({
@@ -138,11 +142,11 @@ describe('Deployment', () => {
const keys = Object.keys(body).sort()
return (
keys.length === 4 &&
keys[0] === 'artifact_url' &&
keys[0] === 'artifact_id' &&
keys[1] === 'oidc_token' &&
keys[2] === 'pages_build_version' &&
keys[3] === 'preview' &&
body.artifact_url === 'https://fake-artifact.com&%24expand=SignedContent' &&
body.artifact_id === 11 &&
body.pages_build_version === process.env.GITHUB_SHA &&
body.oidc_token === fakeJwt &&
body.preview === true
@@ -172,32 +176,42 @@ describe('Deployment', () => {
expect(core.info).toHaveBeenLastCalledWith(
expect.stringMatching(new RegExp(`^Created deployment for ${process.env.GITHUB_SHA}`))
)
artifactExchangeScope.done()
})
it('reports errors with failed artifact exchange', async () => {
it('reports errors with failed artifact metadata exchange', async () => {
process.env.GITHUB_SHA = 'invalid-build-version'
const artifactExchangeScope = nock(`http://my-url`)
.get('/_apis/pipelines/workflows/123/artifacts?api-version=6.0-preview')
.reply(400, {})
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(400, { message: 'Bad request' }, { headers: { 'content-type': 'application/json' } })
// Create the deployment
const deployment = new Deployment()
await expect(deployment.create()).rejects.toEqual(
new Error(
`Failed to create deployment (status: 400) with build version ${process.env.GITHUB_SHA}. Responded with: Bad Request`
`Failed to create deployment (status: 400) with build version ${process.env.GITHUB_SHA}. Responded with: Bad request`
)
)
artifactExchangeScope.done()
})
it('reports errors with a failed 500 in a deployment', async () => {
process.env.GITHUB_SHA = 'build-version'
const artifactExchangeScope = nock(`http://my-url`)
.get('/_apis/pipelines/workflows/123/artifacts?api-version=6.0-preview')
.reply(200, { value: [{ url: 'https://invalid-artifact.com', name: 'github-pages' }] })
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(
200,
{
total_count: 1,
artifacts: [{ id: 11, name: `github-pages`, size_in_bytes: 221 }]
},
{ headers: { 'content-type': 'application/json' } }
)
mockPool
.intercept({
@@ -208,9 +222,9 @@ describe('Deployment', () => {
const keys = Object.keys(body).sort()
return (
keys.length === 2 &&
keys[0] === 'artifact_url' &&
keys[0] === 'artifact_id' &&
keys[1] === 'pages_build_version' &&
body.artifact_url === 'https://invalid-artifact.com&%24expand=SignedContent' &&
body.artifact_id === 11 &&
body.pages_build_version === process.env.GITHUB_SHA
)
}
@@ -224,15 +238,23 @@ describe('Deployment', () => {
`Failed to create deployment (status: 500) with build version ${process.env.GITHUB_SHA}. Server error, is githubstatus.com reporting a Pages outage? Please re-run the deployment at a later time.`
)
)
artifactExchangeScope.done()
})
it('reports errors with an unexpected 403 during deployment', async () => {
process.env.GITHUB_SHA = 'build-version'
const artifactExchangeScope = nock(`http://my-url`)
.get('/_apis/pipelines/workflows/123/artifacts?api-version=6.0-preview')
.reply(200, { value: [{ url: 'https://invalid-artifact.com', name: 'github-pages' }] })
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(
200,
{
total_count: 1,
artifacts: [{ id: 11, name: `github-pages`, size_in_bytes: 221 }]
},
{ headers: { 'content-type': 'application/json' } }
)
mockPool
.intercept({
@@ -243,9 +265,9 @@ describe('Deployment', () => {
const keys = Object.keys(body).sort()
return (
keys.length === 2 &&
keys[0] === 'artifact_url' &&
keys[0] === 'artifact_id' &&
keys[1] === 'pages_build_version' &&
body.artifact_url === 'https://invalid-artifact.com&%24expand=SignedContent' &&
body.artifact_id === 11 &&
body.pages_build_version === process.env.GITHUB_SHA
)
}
@@ -259,15 +281,23 @@ describe('Deployment', () => {
`Failed to create deployment (status: 403) with build version ${process.env.GITHUB_SHA}. Ensure GITHUB_TOKEN has permission "pages: write".`
)
)
artifactExchangeScope.done()
})
it('reports errors with an unexpected 404 during deployment', async () => {
process.env.GITHUB_SHA = 'build-version'
const artifactExchangeScope = nock(`http://my-url`)
.get('/_apis/pipelines/workflows/123/artifacts?api-version=6.0-preview')
.reply(200, { value: [{ url: 'https://invalid-artifact.com', name: 'github-pages' }] })
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(
200,
{
total_count: 1,
artifacts: [{ id: 11, name: `github-pages`, size_in_bytes: 221 }]
},
{ headers: { 'content-type': 'application/json' } }
)
mockPool
.intercept({
@@ -278,9 +308,9 @@ describe('Deployment', () => {
const keys = Object.keys(body).sort()
return (
keys.length === 2 &&
keys[0] === 'artifact_url' &&
keys[0] === 'artifact_id' &&
keys[1] === 'pages_build_version' &&
body.artifact_url === 'https://invalid-artifact.com&%24expand=SignedContent' &&
body.artifact_id === 11 &&
body.pages_build_version === process.env.GITHUB_SHA
)
}
@@ -294,15 +324,23 @@ describe('Deployment', () => {
`Failed to create deployment (status: 404) with build version ${process.env.GITHUB_SHA}. Ensure GitHub Pages has been enabled: https://github.com/actions/is-awesome/settings/pages`
)
)
artifactExchangeScope.done()
})
it('reports errors with failed deployments', async () => {
process.env.GITHUB_SHA = 'invalid-build-version'
const artifactExchangeScope = nock(`http://my-url`)
.get('/_apis/pipelines/workflows/123/artifacts?api-version=6.0-preview')
.reply(200, { value: [{ url: 'https://invalid-artifact.com', name: 'github-pages' }] })
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(
200,
{
total_count: 1,
artifacts: [{ id: 11, name: `github-pages`, size_in_bytes: 221 }]
},
{ headers: { 'content-type': 'application/json' } }
)
mockPool
.intercept({
@@ -313,9 +351,9 @@ describe('Deployment', () => {
const keys = Object.keys(body).sort()
return (
keys.length === 2 &&
keys[0] === 'artifact_url' &&
keys[0] === 'artifact_id' &&
keys[1] === 'pages_build_version' &&
body.artifact_url === 'https://invalid-artifact.com&%24expand=SignedContent' &&
body.artifact_id === 11 &&
body.pages_build_version === process.env.GITHUB_SHA
)
}
@@ -329,22 +367,98 @@ describe('Deployment', () => {
`Failed to create deployment (status: 400) with build version ${process.env.GITHUB_SHA}. Responded with: Bad request`
)
)
})
artifactExchangeScope.done()
it('fails if there are multiple artifacts with the same name', async () => {
process.env.GITHUB_SHA = 'valid-build-version'
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(
200,
{
total_count: 2,
artifacts: [
{
id: 13,
name: `github-pages`,
size_in_bytes: 1400
},
{
id: 14,
name: `github-pages`,
size_in_bytes: 1620
}
]
},
{ headers: { 'content-type': 'application/json' } }
)
const deployment = new Deployment()
await expect(deployment.create(fakeJwt)).rejects.toThrow(
`Multiple artifact unexpectedly found for workflow run ${process.env.GITHUB_RUN_ID}. Artifact count is 2.`
)
})
it('fails if there are no artifacts found', async () => {
process.env.GITHUB_SHA = 'valid-build-version'
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(
200,
{
total_count: 0,
artifacts: []
},
{ headers: { 'content-type': 'application/json' } }
)
const deployment = new Deployment()
await expect(deployment.create(fakeJwt)).rejects.toThrow(
`No artifacts found for workflow run ${process.env.GITHUB_RUN_ID}. Ensure artifacts are uploaded with actions/artifact@v4 or later.`
)
})
it('fails with error message if list artifact endpoint returns 500', async () => {
process.env.GITHUB_SHA = 'valid-build-version'
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(500, { message: 'oh no' }, { headers: { 'content-type': 'application/json' } })
const deployment = new Deployment()
await expect(deployment.create(fakeJwt)).rejects.toThrow(
`Failed to create deployment (status: 500) with build version valid-build-version. Server error, is githubstatus.com reporting a Pages outage? Please re-run the deployment at a later time.`
)
})
it('warns if the artifact size is bigger than maximum', async () => {
process.env.GITHUB_SHA = 'valid-build-version'
const artifactSize = ONE_GIGABYTE + 1
const artifactExchangeScope = nock(`http://my-url`)
.get('/_apis/pipelines/workflows/123/artifacts?api-version=6.0-preview')
.reply(200, {
value: [
{ url: 'https://fake-artifact.com', name: 'github-pages', size: `${artifactSize}` },
{ url: 'https://another-artifact.com', name: 'another-artifact' }
]
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(
200,
{
total_count: 1,
artifacts: [{ id: 12, name: `github-pages`, size_in_bytes: artifactSize }]
},
{ headers: { 'content-type': 'application/json' } }
)
mockPool
.intercept({
@@ -355,10 +469,10 @@ describe('Deployment', () => {
const keys = Object.keys(body).sort()
return (
keys.length === 3 &&
keys[0] === 'artifact_url' &&
keys[0] === 'artifact_id' &&
keys[1] === 'oidc_token' &&
keys[2] === 'pages_build_version' &&
body.artifact_url === 'https://fake-artifact.com&%24expand=SignedContent' &&
body.artifact_id === 12 &&
body.oidc_token === fakeJwt &&
body.pages_build_version === process.env.GITHUB_SHA
)
@@ -383,21 +497,24 @@ describe('Deployment', () => {
expect(core.info).toHaveBeenLastCalledWith(
expect.stringMatching(new RegExp(`^Created deployment for ${process.env.GITHUB_SHA}`))
)
artifactExchangeScope.done()
})
it('warns when the timeout is greater than the maximum allowed', async () => {
process.env.GITHUB_SHA = 'valid-build-version'
const artifactExchangeScope = nock(`http://my-url`)
.get('/_apis/pipelines/workflows/123/artifacts?api-version=6.0-preview')
.reply(200, {
value: [
{ url: 'https://another-artifact.com', name: 'another-artifact' },
{ url: 'https://fake-artifact.com', name: 'github-pages' }
]
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(
200,
{
total_count: 1,
artifacts: [{ id: 11, name: `github-pages`, size_in_bytes: 221 }]
},
{ headers: { 'content-type': 'application/json' } }
)
mockPool
.intercept({
@@ -408,10 +525,10 @@ describe('Deployment', () => {
const keys = Object.keys(body).sort()
return (
keys.length === 3 &&
keys[0] === 'artifact_url' &&
keys[0] === 'artifact_id' &&
keys[1] === 'oidc_token' &&
keys[2] === 'pages_build_version' &&
body.artifact_url === 'https://fake-artifact.com&%24expand=SignedContent' &&
body.artifact_id === 11 &&
body.oidc_token === fakeJwt &&
body.pages_build_version === process.env.GITHUB_SHA
)
@@ -450,8 +567,6 @@ describe('Deployment', () => {
expect(core.warning).toBeCalledWith(
`Warning: timeout value is greater than the allowed maximum - timeout set to the maximum of ${MAX_TIMEOUT} milliseconds.`
)
artifactExchangeScope.done()
})
})
@@ -459,14 +574,19 @@ describe('Deployment', () => {
it('sets output to success when deployment is successful', async () => {
process.env.GITHUB_SHA = 'valid-build-version'
const artifactExchangeScope = nock(`http://my-url`)
.get('/_apis/pipelines/workflows/123/artifacts?api-version=6.0-preview')
.reply(200, {
value: [
{ url: 'https://another-artifact.com', name: 'another-artifact' },
{ url: 'https://fake-artifact.com', name: 'github-pages' }
]
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(
200,
{
total_count: 1,
artifacts: [{ id: 11, name: `github-pages`, size_in_bytes: 221 }]
},
{ headers: { 'content-type': 'application/json' } }
)
mockPool
.intercept({
@@ -477,10 +597,10 @@ describe('Deployment', () => {
const keys = Object.keys(body).sort()
return (
keys.length === 3 &&
keys[0] === 'artifact_url' &&
keys[0] === 'artifact_id' &&
keys[1] === 'oidc_token' &&
keys[2] === 'pages_build_version' &&
body.artifact_url === 'https://fake-artifact.com&%24expand=SignedContent' &&
body.artifact_id === 11 &&
body.oidc_token === fakeJwt &&
body.pages_build_version === process.env.GITHUB_SHA
)
@@ -511,8 +631,6 @@ describe('Deployment', () => {
expect(core.setOutput).toBeCalledWith('status', 'succeed')
expect(core.info).toHaveBeenLastCalledWith('Reported success!')
artifactExchangeScope.done()
})
it('fails check when no deployment is found', async () => {
@@ -525,14 +643,19 @@ describe('Deployment', () => {
it('exits early when deployment is not in progress', async () => {
process.env.GITHUB_SHA = 'valid-build-version'
const artifactExchangeScope = nock(`http://my-url`)
.get('/_apis/pipelines/workflows/123/artifacts?api-version=6.0-preview')
.reply(200, {
value: [
{ url: 'https://another-artifact.com', name: 'another-artifact' },
{ url: 'https://fake-artifact.com', name: 'github-pages' }
]
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(
200,
{
total_count: 1,
artifacts: [{ id: 11, name: `github-pages`, size_in_bytes: 221 }]
},
{ headers: { 'content-type': 'application/json' } }
)
mockPool
.intercept({
@@ -543,10 +666,10 @@ describe('Deployment', () => {
const keys = Object.keys(body).sort()
return (
keys.length === 3 &&
keys[0] === 'artifact_url' &&
keys[0] === 'artifact_id' &&
keys[1] === 'oidc_token' &&
keys[2] === 'pages_build_version' &&
body.artifact_url === 'https://fake-artifact.com&%24expand=SignedContent' &&
body.artifact_id === 11 &&
body.oidc_token === fakeJwt &&
body.pages_build_version === process.env.GITHUB_SHA
)
@@ -568,21 +691,24 @@ describe('Deployment', () => {
deployment.deploymentInfo.pending = false
await deployment.check()
expect(core.setFailed).toBeCalledWith('Unable to get deployment status.')
artifactExchangeScope.done()
})
it('enforces max timeout', async () => {
process.env.GITHUB_SHA = 'valid-build-version'
const artifactExchangeScope = nock(`http://my-url`)
.get('/_apis/pipelines/workflows/123/artifacts?api-version=6.0-preview')
.reply(200, {
value: [
{ url: 'https://another-artifact.com', name: 'another-artifact' },
{ url: 'https://fake-artifact.com', name: 'github-pages' }
]
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(
200,
{
total_count: 1,
artifacts: [{ id: 11, name: `github-pages`, size_in_bytes: 221 }]
},
{ headers: { 'content-type': 'application/json' } }
)
mockPool
.intercept({
@@ -593,10 +719,10 @@ describe('Deployment', () => {
const keys = Object.keys(body).sort()
return (
keys.length === 3 &&
keys[0] === 'artifact_url' &&
keys[0] === 'artifact_id' &&
keys[1] === 'oidc_token' &&
keys[2] === 'pages_build_version' &&
body.artifact_url === 'https://fake-artifact.com&%24expand=SignedContent' &&
body.artifact_id === 11 &&
body.oidc_token === fakeJwt &&
body.pages_build_version === process.env.GITHUB_SHA
)
@@ -664,21 +790,24 @@ describe('Deployment', () => {
expect(deployment.timeout).toEqual(MAX_TIMEOUT)
expect(core.error).toBeCalledWith('Timeout reached, aborting!')
expect(core.setFailed).toBeCalledWith('Timeout reached, aborting!')
artifactExchangeScope.done()
})
it('sets timeout to user timeout if user timeout is less than max timeout', async () => {
process.env.GITHUB_SHA = 'valid-build-version'
const artifactExchangeScope = nock(`http://my-url`)
.get('/_apis/pipelines/workflows/123/artifacts?api-version=6.0-preview')
.reply(200, {
value: [
{ url: 'https://another-artifact.com', name: 'another-artifact' },
{ url: 'https://fake-artifact.com', name: 'github-pages' }
]
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(
200,
{
total_count: 1,
artifacts: [{ id: 11, name: `github-pages`, size_in_bytes: 221 }]
},
{ headers: { 'content-type': 'application/json' } }
)
mockPool
.intercept({
@@ -689,10 +818,10 @@ describe('Deployment', () => {
const keys = Object.keys(body).sort()
return (
keys.length === 3 &&
keys[0] === 'artifact_url' &&
keys[0] === 'artifact_id' &&
keys[1] === 'oidc_token' &&
keys[2] === 'pages_build_version' &&
body.artifact_url === 'https://fake-artifact.com&%24expand=SignedContent' &&
body.artifact_id === 11 &&
body.oidc_token === fakeJwt &&
body.pages_build_version === process.env.GITHUB_SHA
)
@@ -749,21 +878,24 @@ describe('Deployment', () => {
expect(deployment.timeout).toEqual(42)
expect(core.error).toBeCalledWith('Timeout reached, aborting!')
expect(core.setFailed).toBeCalledWith('Timeout reached, aborting!')
artifactExchangeScope.done()
})
it('sets output to success when timeout is set but not reached', async () => {
process.env.GITHUB_SHA = 'valid-build-version'
const artifactExchangeScope = nock(`http://my-url`)
.get('/_apis/pipelines/workflows/123/artifacts?api-version=6.0-preview')
.reply(200, {
value: [
{ url: 'https://another-artifact.com', name: 'another-artifact' },
{ url: 'https://fake-artifact.com', name: 'github-pages' }
]
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(
200,
{
total_count: 1,
artifacts: [{ id: 11, name: `github-pages`, size_in_bytes: 221 }]
},
{ headers: { 'content-type': 'application/json' } }
)
mockPool
.intercept({
@@ -774,10 +906,10 @@ describe('Deployment', () => {
const keys = Object.keys(body).sort()
return (
keys.length === 3 &&
keys[0] === 'artifact_url' &&
keys[0] === 'artifact_id' &&
keys[1] === 'oidc_token' &&
keys[2] === 'pages_build_version' &&
body.artifact_url === 'https://fake-artifact.com&%24expand=SignedContent' &&
body.artifact_id === 11 &&
body.oidc_token === fakeJwt &&
body.pages_build_version === process.env.GITHUB_SHA
)
@@ -835,8 +967,6 @@ describe('Deployment', () => {
expect(core.error).not.toBeCalled()
expect(core.setOutput).toBeCalledWith('status', 'succeed')
expect(core.info).toHaveBeenLastCalledWith('Reported success!')
artifactExchangeScope.done()
})
})
@@ -844,14 +974,19 @@ describe('Deployment', () => {
it('can successfully cancel a deployment', async () => {
process.env.GITHUB_SHA = 'valid-build-version'
const artifactExchangeScope = nock(`http://my-url`)
.get('/_apis/pipelines/workflows/123/artifacts?api-version=6.0-preview')
.reply(200, {
value: [
{ url: 'https://another-artifact.com', name: 'another-artifact' },
{ url: 'https://fake-artifact.com', name: 'github-pages' }
]
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(
200,
{
total_count: 1,
artifacts: [{ id: 11, name: `github-pages`, size_in_bytes: 221 }]
},
{ headers: { 'content-type': 'application/json' } }
)
mockPool
.intercept({
@@ -862,10 +997,10 @@ describe('Deployment', () => {
const keys = Object.keys(body).sort()
return (
keys.length === 3 &&
keys[0] === 'artifact_url' &&
keys[0] === 'artifact_id' &&
keys[1] === 'oidc_token' &&
keys[2] === 'pages_build_version' &&
body.artifact_url === 'https://fake-artifact.com&%24expand=SignedContent' &&
body.artifact_id === 11 &&
body.oidc_token === fakeJwt &&
body.pages_build_version === process.env.GITHUB_SHA
)
@@ -897,8 +1032,6 @@ describe('Deployment', () => {
await deployment.cancel()
expect(core.info).toHaveBeenLastCalledWith(`Canceled deployment with ID ${process.env.GITHUB_SHA}`)
artifactExchangeScope.done()
})
it('can exit if a pages deployment was not created and none need to be cancelled', async () => {
@@ -917,14 +1050,19 @@ describe('Deployment', () => {
it('catches an error when trying to cancel a deployment', async () => {
process.env.GITHUB_SHA = 'valid-build-version'
const artifactExchangeScope = nock(`http://my-url`)
.get('/_apis/pipelines/workflows/123/artifacts?api-version=6.0-preview')
.reply(200, {
value: [
{ url: 'https://another-artifact.com', name: 'another-artifact' },
{ url: 'https://fake-artifact.com', name: 'github-pages' }
]
mockPool
.intercept({
path: `/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/artifacts?name=github-pages`,
method: 'GET'
})
.reply(
200,
{
total_count: 1,
artifacts: [{ id: 11, name: `github-pages`, size_in_bytes: 221 }]
},
{ headers: { 'content-type': 'application/json' } }
)
mockPool
.intercept({
@@ -935,10 +1073,10 @@ describe('Deployment', () => {
const keys = Object.keys(body).sort()
return (
keys.length === 3 &&
keys[0] === 'artifact_url' &&
keys[0] === 'artifact_id' &&
keys[1] === 'oidc_token' &&
keys[2] === 'pages_build_version' &&
body.artifact_url === 'https://fake-artifact.com&%24expand=SignedContent' &&
body.artifact_id === 11 &&
body.oidc_token === fakeJwt &&
body.pages_build_version === process.env.GITHUB_SHA
)
@@ -970,8 +1108,6 @@ describe('Deployment', () => {
await deployment.cancel()
expect(core.error).toHaveBeenCalledWith(`Canceling Pages deployment failed`, expect.anything())
artifactExchangeScope.done()
})
})
})

View File

@@ -1,119 +1,61 @@
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 getSignedArtifactMetadata({ 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
async function getArtifactMetadata({ githubToken, runId, artifactName }) {
const octokit = github.getOctokit(githubToken)
try {
const requestHeaders = {
accept: 'application/json',
authorization: `Bearer ${runtimeToken}`
}
const requestOptions = {
method: 'GET',
url: artifactExchangeUrl,
headers: {
...requestHeaders
},
body: null
core.info(`Fetching artifact metadata for ${artifactName} in run ${runId}`)
const response = await octokit.request(
'GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts?name={artifactName}',
{
owner: github.context.repo.owner,
repo: github.context.repo.repo,
run_id: runId,
artifactName: artifactName
}
)
const artifactCount = response.data.total_count
core.debug(`List artifact count: ${artifactCount}`)
if (artifactCount === 0) {
throw new Error(
`No artifacts found for workflow run ${runId}. Ensure artifacts are uploaded with actions/artifact@v4 or later.`
)
} else if (artifactCount > 1) {
throw new Error(
`Multiple artifact unexpectedly found for workflow run ${runId}. Artifact count is ${artifactCount}.`
)
}
core.info(`Artifact exchange URL: ${artifactExchangeUrl}`)
const res = await httpClient.get(artifactExchangeUrl, requestHeaders)
const artifact = response.data.artifacts[0]
core.debug(`Artifact: ${JSON.stringify(artifact)}`)
// May throw a RequestError (HttpError)
const response = await processRuntimeResponse(res, requestOptions)
const artifactSize = artifact.size_in_bytes
if (!artifactSize) {
core.warning('Artifact size was not found. Unable to verify if artifact size exceeds the allowed size.')
}
data = response.data
core.debug(JSON.stringify(data))
return {
id: artifact.id,
size: artifactSize
}
} catch (error) {
core.error('Getting signed artifact URL failed', error)
core.error(
'Fetching artifact metadata failed. Is githubstatus.com reporting issues with API requests, Pages or Actions? Please re-run the deployment at a later time.',
error
)
throw error
}
const artifact = data?.value?.find(artifact => artifact.name === artifactName)
const artifactRawUrl = artifact?.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`
const artifactSize = artifact?.size
if (!artifactSize) {
core.warning('Artifact size was not found. Unable to verify if artifact size exceeds the allowed size.')
}
return {
url: signedArtifactUrl,
size: artifactSize
}
}
async function createPagesDeployment({ githubToken, artifactUrl, buildVersion, idToken, isPreview = false }) {
async function createPagesDeployment({ githubToken, artifactId, buildVersion, idToken, isPreview = false }) {
const octokit = github.getOctokit(githubToken)
const payload = {
artifact_url: artifactUrl,
artifact_id: artifactId,
pages_build_version: buildVersion,
oidc_token: idToken
}
@@ -173,7 +115,7 @@ async function cancelPagesDeployment({ githubToken, deploymentId }) {
}
module.exports = {
getSignedArtifactMetadata,
getArtifactMetadata,
createPagesDeployment,
getPagesDeploymentStatus,
cancelPagesDeployment

View File

@@ -3,9 +3,7 @@ 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,

View File

@@ -3,7 +3,7 @@ const core = require('@actions/core')
// All variables we need from the runtime are loaded here
const getContext = require('./context')
const {
getSignedArtifactMetadata,
getArtifactMetadata,
createPagesDeployment,
getPagesDeploymentStatus,
cancelPagesDeployment
@@ -31,9 +31,7 @@ const SIZE_LIMIT_DESCRIPTION = '1 GB'
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
@@ -48,8 +46,8 @@ class Deployment {
this.startTime = null
}
// Ask the runtime for the unsigned artifact URL and deploy to GitHub Pages
// by creating a deployment with that artifact
// Call GitHub api to fetch artifacts matching the provided name and deploy to GitHub Pages
// by creating a deployment with that artifact id
async create(idToken) {
if (Number(core.getInput('timeout')) > MAX_TIMEOUT) {
core.warning(
@@ -65,9 +63,9 @@ class Deployment {
core.debug(`Action ID: ${this.actionsId}`)
core.debug(`Actions Workflow Run ID: ${this.workflowRun}`)
const artifactData = await getSignedArtifactMetadata({
runtimeToken: this.runTimeToken,
workflowRunId: this.workflowRun,
const artifactData = await getArtifactMetadata({
githubToken: this.githubToken,
runId: this.workflowRun,
artifactName: this.artifactName
})
@@ -79,7 +77,7 @@ class Deployment {
const deployment = await createPagesDeployment({
githubToken: this.githubToken,
artifactUrl: artifactData.url,
artifactId: artifactData.id,
buildVersion: this.buildVersion,
idToken,
isPreview: this.isPreview