Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
enforce a max timeout
  • Loading branch information
Greta Parks authored and Greta Parks committed May 11, 2023
1 parent bacaae7 commit 054faf7
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 26 deletions.
2 changes: 1 addition & 1 deletion coverage_badge.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 16 additions & 12 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

195 changes: 195 additions & 0 deletions src/__tests__/internal/deployment.test.js
Expand Up @@ -384,6 +384,201 @@ describe('Deployment', () => {
artifactExchangeScope.done()
createDeploymentScope.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' }
]
})

const createDeploymentScope = nock('https://api.github.com')
.post(`/repos/${process.env.GITHUB_REPOSITORY}/pages/deployments`, {
artifact_url: 'https://fake-artifact.com&%24expand=SignedContent',
pages_build_version: process.env.GITHUB_SHA,
oidc_token: fakeJwt
})
.reply(200, {
status_url: `https://api.github.com/repos/${process.env.GITHUB_REPOSITORY}/pages/deployments/${process.env.GITHUB_SHA}`,
page_url: 'https://actions.github.io/is-awesome'
})

const cancelDeploymentScope = nock('https://api.github.com')
.post(`/repos/${process.env.GITHUB_REPOSITORY}/pages/deployments/${process.env.GITHUB_SHA}/cancel`)
.reply(200, {})

core.getIDToken = jest.fn().mockResolvedValue(fakeJwt)

// Set timeout to great than max
jest.spyOn(core, 'getInput').mockImplementation(param => {
switch (param) {
case 'artifact_name':
return 'github-pages'
case 'token':
return process.env.GITHUB_TOKEN
case 'timeout':
return maxTimeout + 1
default:
return process.env[`INPUT_${param.toUpperCase()}`] || ''
}
})

const now = Date.now()
const mockStartTime = now - maxTimeout
jest
.spyOn(Date, 'now')
.mockImplementationOnce(() => mockStartTime)
.mockImplementationOnce(() => now)

// Create the deployment
const deployment = new Deployment()
await deployment.create(fakeJwt)
await deployment.check()

expect(deployment.timeout).toEqual(maxTimeout)
expect(core.error).toBeCalledWith('Timeout reached, aborting!')
expect(core.setFailed).toBeCalledWith('Timeout reached, aborting!')

artifactExchangeScope.done()
createDeploymentScope.done()
cancelDeploymentScope.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' }
]
})

const createDeploymentScope = nock('https://api.github.com')
.post(`/repos/${process.env.GITHUB_REPOSITORY}/pages/deployments`, {
artifact_url: 'https://fake-artifact.com&%24expand=SignedContent',
pages_build_version: process.env.GITHUB_SHA,
oidc_token: fakeJwt
})
.reply(200, {
status_url: `https://api.github.com/repos/${process.env.GITHUB_REPOSITORY}/pages/deployments/${process.env.GITHUB_SHA}`,
page_url: 'https://actions.github.io/is-awesome'
})

const cancelDeploymentScope = nock('https://api.github.com')
.post(`/repos/${process.env.GITHUB_REPOSITORY}/pages/deployments/${process.env.GITHUB_SHA}/cancel`)
.reply(200, {})

core.getIDToken = jest.fn().mockResolvedValue(fakeJwt)

// Set timeout to great than max
jest.spyOn(core, 'getInput').mockImplementation(param => {
switch (param) {
case 'artifact_name':
return 'github-pages'
case 'token':
return process.env.GITHUB_TOKEN
case 'timeout':
return 42
default:
return process.env[`INPUT_${param.toUpperCase()}`] || ''
}
})

const now = Date.now()
const mockStartTime = now - 42
jest
.spyOn(Date, 'now')
.mockImplementationOnce(() => mockStartTime)
.mockImplementationOnce(() => now)

// Create the deployment
const deployment = new Deployment()
await deployment.create(fakeJwt)
await deployment.check()

expect(deployment.timeout).toEqual(42)
expect(core.error).toBeCalledWith('Timeout reached, aborting!')
expect(core.setFailed).toBeCalledWith('Timeout reached, aborting!')

artifactExchangeScope.done()
createDeploymentScope.done()
cancelDeploymentScope.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' }
]
})

const createDeploymentScope = nock('https://api.github.com')
.post(`/repos/${process.env.GITHUB_REPOSITORY}/pages/deployments`, {
artifact_url: 'https://fake-artifact.com&%24expand=SignedContent',
pages_build_version: process.env.GITHUB_SHA,
oidc_token: fakeJwt
})
.reply(200, {
status_url: `https://api.github.com/repos/${process.env.GITHUB_REPOSITORY}/pages/deployments/${process.env.GITHUB_SHA}`,
page_url: 'https://actions.github.io/is-awesome'
})

const deploymentStatusScope = nock('https://api.github.com')
.get(`/repos/${process.env.GITHUB_REPOSITORY}/pages/deployments/${process.env.GITHUB_SHA}`)
.reply(200, {
status: 'succeed'
})

core.getIDToken = jest.fn().mockResolvedValue(fakeJwt)

// Set timeout to great than max
jest.spyOn(core, 'getInput').mockImplementation(param => {
switch (param) {
case 'artifact_name':
return 'github-pages'
case 'token':
return process.env.GITHUB_TOKEN
case 'timeout':
return 42
default:
return process.env[`INPUT_${param.toUpperCase()}`] || ''
}
})

const now = Date.now()
const mockStartTime = now
jest
.spyOn(Date, 'now')
.mockImplementationOnce(() => mockStartTime)
.mockImplementationOnce(() => now)

// Create the deployment
const deployment = new Deployment()
await deployment.create(fakeJwt)
await deployment.check()

expect(deployment.timeout).toEqual(42)
expect(core.error).not.toBeCalled()
expect(core.setOutput).toBeCalledWith('status', 'succeed')
expect(core.info).toHaveBeenLastCalledWith('Reported success!')

artifactExchangeScope.done()
createDeploymentScope.done()
deploymentStatusScope.done()
})
})

describe('#cancel', () => {
Expand Down
28 changes: 16 additions & 12 deletions src/internal/deployment.js
Expand Up @@ -41,6 +41,8 @@ class Deployment {
this.githubServerUrl = context.githubServerUrl
this.artifactName = context.artifactName
this.isPreview = context.isPreview === true
this.timeout = maxTimeout
this.startTime = null
}

// Ask the runtime for the unsigned artifact URL and deploy to GitHub Pages
Expand All @@ -52,6 +54,9 @@ class Deployment {
)
}

let timeoutInput = Number(core.getInput('timeout'))
this.timeout = timeoutInput <= 0 ? maxTimeout : Math.min(timeoutInput, maxTimeout)

try {
core.debug(`Actor: ${this.buildActor}`)
core.debug(`Action ID: ${this.actionsId}`)
Expand All @@ -77,6 +82,7 @@ class 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}`)
Expand Down Expand Up @@ -125,11 +131,9 @@ class Deployment {
}

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.
Expand All @@ -139,6 +143,16 @@ class Deployment {

/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
while (true) {
// 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
}

// Handle reporting interval
await new Promise(resolve => setTimeout(resolve, reportingInterval + errorReportingInterval))

Expand Down Expand Up @@ -195,16 +209,6 @@ class 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
}
}
}

Expand Down

0 comments on commit 054faf7

Please sign in to comment.