1
0
Fork 0

Merge pull request #1603 from actions/robherley/network-errors

Add specific messages for network-specific node error codes
pull/1604/head
Rob Herley 2023-12-11 17:34:24 -05:00 committed by GitHub
commit 18ce228b82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 86 additions and 26 deletions

View File

@ -7,6 +7,12 @@ import {noopLogs} from './common'
jest.mock('@actions/http-client') jest.mock('@actions/http-client')
const clientOptions = {
maxAttempts: 5,
retryIntervalMs: 1,
retryMultiplier: 1.5
}
describe('artifact-http-client', () => { describe('artifact-http-client', () => {
beforeAll(() => { beforeAll(() => {
noopLogs() noopLogs()
@ -94,11 +100,7 @@ describe('artifact-http-client', () => {
} }
}) })
const client = internalArtifactTwirpClient({ const client = internalArtifactTwirpClient(clientOptions)
maxAttempts: 5,
retryIntervalMs: 1,
retryMultiplier: 1.5
})
const artifact = await client.CreateArtifact({ const artifact = await client.CreateArtifact({
workflowRunBackendId: '1234', workflowRunBackendId: '1234',
workflowJobRunBackendId: '5678', workflowJobRunBackendId: '5678',
@ -133,11 +135,7 @@ describe('artifact-http-client', () => {
post: mockPost post: mockPost
} }
}) })
const client = internalArtifactTwirpClient({ const client = internalArtifactTwirpClient(clientOptions)
maxAttempts: 5,
retryIntervalMs: 1,
retryMultiplier: 1.5
})
await expect(async () => { await expect(async () => {
await client.CreateArtifact({ await client.CreateArtifact({
workflowRunBackendId: '1234', workflowRunBackendId: '1234',
@ -172,11 +170,7 @@ describe('artifact-http-client', () => {
post: mockPost post: mockPost
} }
}) })
const client = internalArtifactTwirpClient({ const client = internalArtifactTwirpClient(clientOptions)
maxAttempts: 5,
retryIntervalMs: 1,
retryMultiplier: 1.5
})
await expect(async () => { await expect(async () => {
await client.CreateArtifact({ await client.CreateArtifact({
workflowRunBackendId: '1234', workflowRunBackendId: '1234',
@ -214,11 +208,7 @@ describe('artifact-http-client', () => {
post: mockPost post: mockPost
} }
}) })
const client = internalArtifactTwirpClient({ const client = internalArtifactTwirpClient(clientOptions)
maxAttempts: 5,
retryIntervalMs: 1,
retryMultiplier: 1.5
})
await expect(async () => { await expect(async () => {
await client.CreateArtifact({ await client.CreateArtifact({
workflowRunBackendId: '1234', workflowRunBackendId: '1234',
@ -238,4 +228,39 @@ describe('artifact-http-client', () => {
expect(mockHttpClient).toHaveBeenCalledTimes(1) expect(mockHttpClient).toHaveBeenCalledTimes(1)
expect(mockPost).toHaveBeenCalledTimes(1) expect(mockPost).toHaveBeenCalledTimes(1)
}) })
it('should properly describe a network failure', async () => {
class FakeNodeError extends Error {
code: string
constructor(code: string) {
super()
this.code = code
}
}
const mockPost = jest.fn(() => {
throw new FakeNodeError('ENOTFOUND')
})
const mockHttpClient = (
HttpClient as unknown as jest.Mock
).mockImplementation(() => {
return {
post: mockPost
}
})
const client = internalArtifactTwirpClient()
await expect(async () => {
await client.CreateArtifact({
workflowRunBackendId: '1234',
workflowJobRunBackendId: '5678',
name: 'artifact',
version: 4
})
}).rejects.toThrowError(
'Failed to CreateArtifact: Unable to make request: ENOTFOUND\nIf you are using self-hosted runners, please make sure your runner has access to all GitHub endpoints: https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#communication-between-self-hosted-runners-and-github'
)
expect(mockHttpClient).toHaveBeenCalledTimes(1)
expect(mockPost).toHaveBeenCalledTimes(1)
})
}) })

View File

@ -4,6 +4,7 @@ import {info, debug} from '@actions/core'
import {ArtifactServiceClientJSON} from '../../generated' import {ArtifactServiceClientJSON} from '../../generated'
import {getResultsServiceUrl, getRuntimeToken} from './config' import {getResultsServiceUrl, getRuntimeToken} from './config'
import {getUserAgentString} from './user-agent' import {getUserAgentString} from './user-agent'
import {NetworkError} from './errors'
// The twirp http client must implement this interface // The twirp http client must implement this interface
interface Rpc { interface Rpc {
@ -96,6 +97,9 @@ class ArtifactHttpClient implements Rpc {
} catch (error) { } catch (error) {
isRetryable = true isRetryable = true
errorMessage = error.message errorMessage = error.message
if (NetworkError.isNetworkErrorCode(error?.code)) {
throw new NetworkError(error?.code)
}
} }
if (!isRetryable) { if (!isRetryable) {

View File

@ -35,3 +35,25 @@ export class GHESNotSupportedError extends Error {
this.name = 'GHESNotSupportedError' this.name = 'GHESNotSupportedError'
} }
} }
export class NetworkError extends Error {
code: string
constructor(code: string) {
const message = `Unable to make request: ${code}\nIf you are using self-hosted runners, please make sure your runner has access to all GitHub endpoints: https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#communication-between-self-hosted-runners-and-github`
super(message)
this.code = code
this.name = 'NetworkError'
}
static isNetworkErrorCode = (code?: string): boolean => {
if (!code) return false
return [
'ECONNRESET',
'ENOTFOUND',
'ETIMEDOUT',
'ECONNREFUSED',
'EHOSTUNREACH'
].includes(code)
}
}

View File

@ -5,6 +5,7 @@ import {getUploadChunkSize, getConcurrency} from '../shared/config'
import * as core from '@actions/core' import * as core from '@actions/core'
import * as crypto from 'crypto' import * as crypto from 'crypto'
import * as stream from 'stream' import * as stream from 'stream'
import {NetworkError} from '../shared/errors'
export interface BlobUploadResponse { export interface BlobUploadResponse {
/** /**
@ -52,12 +53,20 @@ export async function uploadZipToBlobStorage(
core.info('Beginning upload of artifact content to blob storage') core.info('Beginning upload of artifact content to blob storage')
try {
await blockBlobClient.uploadStream( await blockBlobClient.uploadStream(
uploadStream, uploadStream,
bufferSize, bufferSize,
maxConcurrency, maxConcurrency,
options options
) )
} catch (error) {
if (NetworkError.isNetworkErrorCode(error?.code)) {
throw new NetworkError(error?.code)
}
throw error
}
core.info('Finished uploading artifact content to blob storage!') core.info('Finished uploading artifact content to blob storage!')