Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f152b798e | ||
|
|
c390199be6 | ||
|
|
a3053b5cc2 | ||
|
|
b9872153b8 | ||
|
|
ce9eae0785 | ||
|
|
d3c5f358d1 | ||
|
|
75a3586061 | ||
|
|
8ac8bf1d3d | ||
|
|
141b3509e4 | ||
|
|
790e6f7194 | ||
|
|
ef454f0991 | ||
|
|
86ce0b159a | ||
|
|
c11a7cdeac | ||
|
|
c94ca49c9c | ||
|
|
fa7657714a | ||
|
|
c1f9d37323 | ||
|
|
8f1c589e25 |
@@ -24,10 +24,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set Node.js 20.x
|
- name: Set Node.js 20.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 20.x
|
||||||
|
|
||||||
@@ -54,8 +54,8 @@ jobs:
|
|||||||
echo '${{ env.file1 }}' > artifact-path/first.txt
|
echo '${{ env.file1 }}' > artifact-path/first.txt
|
||||||
echo '${{ env.file2 }}' > artifact-path/second.txt
|
echo '${{ env.file2 }}' > artifact-path/second.txt
|
||||||
|
|
||||||
- name: Upload Artifacts using actions/github-script@v6
|
- name: Upload Artifacts using actions/github-script@v7
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const artifact = require('./packages/artifact/lib/artifact')
|
const artifact = require('./packages/artifact/lib/artifact')
|
||||||
@@ -68,25 +68,20 @@ jobs:
|
|||||||
const uploadResult = await artifact.create().uploadArtifact(artifactName, fileContents, './')
|
const uploadResult = await artifact.create().uploadArtifact(artifactName, fileContents, './')
|
||||||
console.log(uploadResult)
|
console.log(uploadResult)
|
||||||
|
|
||||||
const success = uploadResult.success
|
|
||||||
const size = uploadResult.size
|
const size = uploadResult.size
|
||||||
const id = uploadResult.id
|
const id = uploadResult.id
|
||||||
|
|
||||||
if (!success) {
|
console.log(`Successfully uploaded artifact ${id}`)
|
||||||
throw new Error('Failed to upload artifact')
|
|
||||||
} else {
|
|
||||||
console.log(`Successfully uploaded artifact ${id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
verify:
|
verify:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build]
|
needs: [build]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set Node.js 20.x
|
- name: Set Node.js 20.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 20.x
|
||||||
|
|
||||||
@@ -101,8 +96,8 @@ jobs:
|
|||||||
npm run tsc
|
npm run tsc
|
||||||
working-directory: packages/artifact
|
working-directory: packages/artifact
|
||||||
|
|
||||||
- name: List artifacts using actions/github-script@v6
|
- name: List artifacts using actions/github-script@v7
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const artifact = require('./packages/artifact/lib/artifact')
|
const artifact = require('./packages/artifact/lib/artifact')
|
||||||
|
|||||||
@@ -164,7 +164,6 @@ describe('download-artifact', () => {
|
|||||||
fixtures.blobStorageUrl
|
fixtures.blobStorageUrl
|
||||||
)
|
)
|
||||||
expectExtractedArchive(fixtures.workspaceDir)
|
expectExtractedArchive(fixtures.workspaceDir)
|
||||||
expect(response.success).toBe(true)
|
|
||||||
expect(response.downloadPath).toBe(fixtures.workspaceDir)
|
expect(response.downloadPath).toBe(fixtures.workspaceDir)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -214,7 +213,6 @@ describe('download-artifact', () => {
|
|||||||
fixtures.blobStorageUrl
|
fixtures.blobStorageUrl
|
||||||
)
|
)
|
||||||
expectExtractedArchive(customPath)
|
expectExtractedArchive(customPath)
|
||||||
expect(response.success).toBe(true)
|
|
||||||
expect(response.downloadPath).toBe(customPath)
|
expect(response.downloadPath).toBe(customPath)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -344,7 +342,6 @@ describe('download-artifact', () => {
|
|||||||
const response = await downloadArtifactInternal(fixtures.artifactID)
|
const response = await downloadArtifactInternal(fixtures.artifactID)
|
||||||
|
|
||||||
expectExtractedArchive(fixtures.workspaceDir)
|
expectExtractedArchive(fixtures.workspaceDir)
|
||||||
expect(response.success).toBe(true)
|
|
||||||
expect(response.downloadPath).toBe(fixtures.workspaceDir)
|
expect(response.downloadPath).toBe(fixtures.workspaceDir)
|
||||||
expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
|
expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
|
||||||
expect(mockListArtifacts).toHaveBeenCalledWith({
|
expect(mockListArtifacts).toHaveBeenCalledWith({
|
||||||
@@ -396,7 +393,6 @@ describe('download-artifact', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expectExtractedArchive(customPath)
|
expectExtractedArchive(customPath)
|
||||||
expect(response.success).toBe(true)
|
|
||||||
expect(response.downloadPath).toBe(customPath)
|
expect(response.downloadPath).toBe(customPath)
|
||||||
expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
|
expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
|
||||||
expect(mockListArtifacts).toHaveBeenCalledWith({
|
expect(mockListArtifacts).toHaveBeenCalledWith({
|
||||||
|
|||||||
@@ -0,0 +1,239 @@
|
|||||||
|
import * as github from '@actions/github'
|
||||||
|
import type {RequestInterface} from '@octokit/types'
|
||||||
|
import {
|
||||||
|
getArtifactInternal,
|
||||||
|
getArtifactPublic
|
||||||
|
} from '../src/internal/find/get-artifact'
|
||||||
|
import * as config from '../src/internal/shared/config'
|
||||||
|
import {ArtifactServiceClientJSON, Timestamp} from '../src/generated'
|
||||||
|
import * as util from '../src/internal/shared/util'
|
||||||
|
import {noopLogs} from './common'
|
||||||
|
import {
|
||||||
|
ArtifactNotFoundError,
|
||||||
|
InvalidResponseError
|
||||||
|
} from '../src/internal/shared/errors'
|
||||||
|
|
||||||
|
type MockedRequest = jest.MockedFunction<RequestInterface<object>>
|
||||||
|
|
||||||
|
jest.mock('@actions/github', () => ({
|
||||||
|
getOctokit: jest.fn().mockReturnValue({
|
||||||
|
request: jest.fn()
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
const fixtures = {
|
||||||
|
repo: 'toolkit',
|
||||||
|
owner: 'actions',
|
||||||
|
token: 'ghp_1234567890',
|
||||||
|
runId: 123,
|
||||||
|
backendIds: {
|
||||||
|
workflowRunBackendId: 'c4d7c21f-ba3f-4ddc-a8c8-6f2f626f8422',
|
||||||
|
workflowJobRunBackendId: '760803a1-f890-4d25-9a6e-a3fc01a0c7cf'
|
||||||
|
},
|
||||||
|
artifacts: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'my-artifact',
|
||||||
|
size: 456,
|
||||||
|
createdAt: new Date('2023-12-01')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'my-artifact',
|
||||||
|
size: 456,
|
||||||
|
createdAt: new Date('2023-12-02')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('get-artifact', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
noopLogs()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('public', () => {
|
||||||
|
it('should return the artifact if it is found', async () => {
|
||||||
|
const mockRequest = github.getOctokit(fixtures.token)
|
||||||
|
.request as MockedRequest
|
||||||
|
mockRequest.mockResolvedValueOnce({
|
||||||
|
status: 200,
|
||||||
|
headers: {},
|
||||||
|
url: '',
|
||||||
|
data: {
|
||||||
|
artifacts: [
|
||||||
|
{
|
||||||
|
name: fixtures.artifacts[0].name,
|
||||||
|
id: fixtures.artifacts[0].id,
|
||||||
|
size_in_bytes: fixtures.artifacts[0].size,
|
||||||
|
created_at: fixtures.artifacts[0].createdAt.toISOString()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await getArtifactPublic(
|
||||||
|
fixtures.artifacts[0].name,
|
||||||
|
fixtures.runId,
|
||||||
|
fixtures.owner,
|
||||||
|
fixtures.repo,
|
||||||
|
fixtures.token
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response).toEqual({
|
||||||
|
artifact: fixtures.artifacts[0]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return the latest artifact if multiple are found', async () => {
|
||||||
|
const mockRequest = github.getOctokit(fixtures.token)
|
||||||
|
.request as MockedRequest
|
||||||
|
mockRequest.mockResolvedValueOnce({
|
||||||
|
status: 200,
|
||||||
|
headers: {},
|
||||||
|
url: '',
|
||||||
|
data: {
|
||||||
|
artifacts: fixtures.artifacts.map(artifact => ({
|
||||||
|
name: artifact.name,
|
||||||
|
id: artifact.id,
|
||||||
|
size_in_bytes: artifact.size,
|
||||||
|
created_at: artifact.createdAt.toISOString()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await getArtifactPublic(
|
||||||
|
fixtures.artifacts[0].name,
|
||||||
|
fixtures.runId,
|
||||||
|
fixtures.owner,
|
||||||
|
fixtures.repo,
|
||||||
|
fixtures.token
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response).toEqual({
|
||||||
|
artifact: fixtures.artifacts[1]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail if no artifacts are found', async () => {
|
||||||
|
const mockRequest = github.getOctokit(fixtures.token)
|
||||||
|
.request as MockedRequest
|
||||||
|
mockRequest.mockResolvedValueOnce({
|
||||||
|
status: 200,
|
||||||
|
headers: {},
|
||||||
|
url: '',
|
||||||
|
data: {
|
||||||
|
artifacts: []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = getArtifactPublic(
|
||||||
|
fixtures.artifacts[0].name,
|
||||||
|
fixtures.runId,
|
||||||
|
fixtures.owner,
|
||||||
|
fixtures.repo,
|
||||||
|
fixtures.token
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response).rejects.toThrowError(ArtifactNotFoundError)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail if non-200 response', async () => {
|
||||||
|
const mockRequest = github.getOctokit(fixtures.token)
|
||||||
|
.request as MockedRequest
|
||||||
|
mockRequest.mockResolvedValueOnce({
|
||||||
|
status: 404,
|
||||||
|
headers: {},
|
||||||
|
url: '',
|
||||||
|
data: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = getArtifactPublic(
|
||||||
|
fixtures.artifacts[0].name,
|
||||||
|
fixtures.runId,
|
||||||
|
fixtures.owner,
|
||||||
|
fixtures.repo,
|
||||||
|
fixtures.token
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response).rejects.toThrowError(InvalidResponseError)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('internal', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(util, 'getBackendIdsFromToken')
|
||||||
|
.mockReturnValue(fixtures.backendIds)
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(config, 'getResultsServiceUrl')
|
||||||
|
.mockReturnValue('https://results.local')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return the artifact if it is found', async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
|
||||||
|
.mockResolvedValue({
|
||||||
|
artifacts: [
|
||||||
|
{
|
||||||
|
...fixtures.backendIds,
|
||||||
|
databaseId: fixtures.artifacts[0].id.toString(),
|
||||||
|
name: fixtures.artifacts[0].name,
|
||||||
|
size: fixtures.artifacts[0].size.toString(),
|
||||||
|
createdAt: Timestamp.fromDate(fixtures.artifacts[0].createdAt)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await getArtifactInternal(fixtures.artifacts[0].name)
|
||||||
|
|
||||||
|
expect(response).toEqual({
|
||||||
|
artifact: fixtures.artifacts[0]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return the latest artifact if multiple are found', async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
|
||||||
|
.mockResolvedValue({
|
||||||
|
artifacts: fixtures.artifacts.map(artifact => ({
|
||||||
|
...fixtures.backendIds,
|
||||||
|
databaseId: artifact.id.toString(),
|
||||||
|
name: artifact.name,
|
||||||
|
size: artifact.size.toString(),
|
||||||
|
createdAt: Timestamp.fromDate(artifact.createdAt)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await getArtifactInternal(fixtures.artifacts[0].name)
|
||||||
|
|
||||||
|
expect(response).toEqual({
|
||||||
|
artifact: fixtures.artifacts[1]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail if no artifacts are found', async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
|
||||||
|
.mockResolvedValue({
|
||||||
|
artifacts: []
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = getArtifactInternal(fixtures.artifacts[0].name)
|
||||||
|
|
||||||
|
expect(response).rejects.toThrowError(ArtifactNotFoundError)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail if non-200 response', async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
|
||||||
|
.mockRejectedValue(new Error('boom'))
|
||||||
|
|
||||||
|
const response = getArtifactInternal(fixtures.artifacts[0].name)
|
||||||
|
|
||||||
|
expect(response).rejects.toThrow()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
import * as github from '@actions/github'
|
||||||
|
import type {RestEndpointMethods} from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/method-types'
|
||||||
|
import type {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/parameters-and-response-types'
|
||||||
|
import {
|
||||||
|
listArtifactsInternal,
|
||||||
|
listArtifactsPublic
|
||||||
|
} from '../src/internal/find/list-artifacts'
|
||||||
|
import * as config from '../src/internal/shared/config'
|
||||||
|
import {ArtifactServiceClientJSON, Timestamp} from '../src/generated'
|
||||||
|
import * as util from '../src/internal/shared/util'
|
||||||
|
import {noopLogs} from './common'
|
||||||
|
import {Artifact} from '../src/internal/shared/interfaces'
|
||||||
|
|
||||||
|
type MockedListWorkflowRunArtifacts = jest.MockedFunction<
|
||||||
|
RestEndpointMethods['actions']['listWorkflowRunArtifacts']
|
||||||
|
>
|
||||||
|
|
||||||
|
jest.mock('@actions/github', () => ({
|
||||||
|
getOctokit: jest.fn().mockReturnValue({
|
||||||
|
rest: {
|
||||||
|
actions: {
|
||||||
|
listWorkflowRunArtifacts: jest.fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
const artifactsToListResponse = (
|
||||||
|
artifacts: Artifact[]
|
||||||
|
): RestEndpointMethodTypes['actions']['listWorkflowRunArtifacts']['response']['data'] => {
|
||||||
|
return {
|
||||||
|
total_count: artifacts.length,
|
||||||
|
artifacts: artifacts.map(artifact => ({
|
||||||
|
name: artifact.name,
|
||||||
|
id: artifact.id,
|
||||||
|
size_in_bytes: artifact.size,
|
||||||
|
created_at: artifact.createdAt?.toISOString() || '',
|
||||||
|
run_id: fixtures.runId,
|
||||||
|
// unused fields for tests
|
||||||
|
url: '',
|
||||||
|
archive_download_url: '',
|
||||||
|
expired: false,
|
||||||
|
expires_at: '',
|
||||||
|
node_id: '',
|
||||||
|
run_url: '',
|
||||||
|
type: '',
|
||||||
|
updated_at: ''
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixtures = {
|
||||||
|
repo: 'toolkit',
|
||||||
|
owner: 'actions',
|
||||||
|
token: 'ghp_1234567890',
|
||||||
|
runId: 123,
|
||||||
|
backendIds: {
|
||||||
|
workflowRunBackendId: 'c4d7c21f-ba3f-4ddc-a8c8-6f2f626f8422',
|
||||||
|
workflowJobRunBackendId: '760803a1-f890-4d25-9a6e-a3fc01a0c7cf'
|
||||||
|
},
|
||||||
|
artifacts: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'my-artifact',
|
||||||
|
size: 456,
|
||||||
|
createdAt: new Date('2023-12-01')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'my-artifact',
|
||||||
|
size: 456,
|
||||||
|
createdAt: new Date('2023-12-02')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('list-artifact', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
noopLogs()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('public', () => {
|
||||||
|
it('should return a list of artifacts', async () => {
|
||||||
|
const mockListArtifacts = github.getOctokit(fixtures.token).rest.actions
|
||||||
|
.listWorkflowRunArtifacts as MockedListWorkflowRunArtifacts
|
||||||
|
|
||||||
|
mockListArtifacts.mockResolvedValueOnce({
|
||||||
|
status: 200,
|
||||||
|
headers: {},
|
||||||
|
url: '',
|
||||||
|
data: artifactsToListResponse(fixtures.artifacts)
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await listArtifactsPublic(
|
||||||
|
fixtures.runId,
|
||||||
|
fixtures.owner,
|
||||||
|
fixtures.repo,
|
||||||
|
fixtures.token,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response).toEqual({
|
||||||
|
artifacts: fixtures.artifacts
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return the latest artifact when latest is specified', async () => {
|
||||||
|
const mockListArtifacts = github.getOctokit(fixtures.token).rest.actions
|
||||||
|
.listWorkflowRunArtifacts as MockedListWorkflowRunArtifacts
|
||||||
|
|
||||||
|
mockListArtifacts.mockResolvedValueOnce({
|
||||||
|
status: 200,
|
||||||
|
headers: {},
|
||||||
|
url: '',
|
||||||
|
data: artifactsToListResponse(fixtures.artifacts)
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await listArtifactsPublic(
|
||||||
|
fixtures.runId,
|
||||||
|
fixtures.owner,
|
||||||
|
fixtures.repo,
|
||||||
|
fixtures.token,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response).toEqual({
|
||||||
|
artifacts: [fixtures.artifacts[1]]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can return empty artifacts', async () => {
|
||||||
|
const mockListArtifacts = github.getOctokit(fixtures.token).rest.actions
|
||||||
|
.listWorkflowRunArtifacts as MockedListWorkflowRunArtifacts
|
||||||
|
|
||||||
|
mockListArtifacts.mockResolvedValueOnce({
|
||||||
|
status: 200,
|
||||||
|
headers: {},
|
||||||
|
url: '',
|
||||||
|
data: {
|
||||||
|
total_count: 0,
|
||||||
|
artifacts: []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await listArtifactsPublic(
|
||||||
|
fixtures.runId,
|
||||||
|
fixtures.owner,
|
||||||
|
fixtures.repo,
|
||||||
|
fixtures.token,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response).toEqual({
|
||||||
|
artifacts: []
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail if non-200 response', async () => {
|
||||||
|
const mockListArtifacts = github.getOctokit(fixtures.token).rest.actions
|
||||||
|
.listWorkflowRunArtifacts as MockedListWorkflowRunArtifacts
|
||||||
|
|
||||||
|
mockListArtifacts.mockRejectedValue(new Error('boom'))
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
listArtifactsPublic(
|
||||||
|
fixtures.runId,
|
||||||
|
fixtures.owner,
|
||||||
|
fixtures.repo,
|
||||||
|
fixtures.token,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
).rejects.toThrow('boom')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('internal', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
||||||
|
jest
|
||||||
|
.spyOn(util, 'getBackendIdsFromToken')
|
||||||
|
.mockReturnValue(fixtures.backendIds)
|
||||||
|
jest
|
||||||
|
.spyOn(config, 'getResultsServiceUrl')
|
||||||
|
.mockReturnValue('https://results.local')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return a list of artifacts', async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
|
||||||
|
.mockResolvedValue({
|
||||||
|
artifacts: fixtures.artifacts.map(artifact => ({
|
||||||
|
...fixtures.backendIds,
|
||||||
|
databaseId: artifact.id.toString(),
|
||||||
|
name: artifact.name,
|
||||||
|
size: artifact.size.toString(),
|
||||||
|
createdAt: Timestamp.fromDate(artifact.createdAt)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
const response = await listArtifactsInternal(false)
|
||||||
|
expect(response).toEqual({
|
||||||
|
artifacts: fixtures.artifacts
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return the latest artifact when latest is specified', async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
|
||||||
|
.mockResolvedValue({
|
||||||
|
artifacts: fixtures.artifacts.map(artifact => ({
|
||||||
|
...fixtures.backendIds,
|
||||||
|
databaseId: artifact.id.toString(),
|
||||||
|
name: artifact.name,
|
||||||
|
size: artifact.size.toString(),
|
||||||
|
createdAt: Timestamp.fromDate(artifact.createdAt)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
const response = await listArtifactsInternal(true)
|
||||||
|
expect(response).toEqual({
|
||||||
|
artifacts: [fixtures.artifacts[1]]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can return empty artifacts', async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
|
||||||
|
.mockResolvedValue({
|
||||||
|
artifacts: []
|
||||||
|
})
|
||||||
|
const response = await listArtifactsInternal(false)
|
||||||
|
expect(response).toEqual({
|
||||||
|
artifacts: []
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail if non-200 response', async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
|
||||||
|
.mockRejectedValue(new Error('boom'))
|
||||||
|
await expect(listArtifactsInternal(false)).rejects.toThrow('boom')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -7,6 +7,7 @@ import {Timestamp, ArtifactServiceClientJSON} from '../src/generated'
|
|||||||
import * as blobUpload from '../src/internal/upload/blob-upload'
|
import * as blobUpload from '../src/internal/upload/blob-upload'
|
||||||
import {uploadArtifact} from '../src/internal/upload/upload-artifact'
|
import {uploadArtifact} from '../src/internal/upload/upload-artifact'
|
||||||
import {noopLogs} from './common'
|
import {noopLogs} from './common'
|
||||||
|
import {FilesNotFoundError} from '../src/internal/shared/errors'
|
||||||
|
|
||||||
describe('upload-artifact', () => {
|
describe('upload-artifact', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -59,7 +60,6 @@ describe('upload-artifact', () => {
|
|||||||
)
|
)
|
||||||
jest.spyOn(blobUpload, 'uploadZipToBlobStorage').mockReturnValue(
|
jest.spyOn(blobUpload, 'uploadZipToBlobStorage').mockReturnValue(
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
isSuccess: true,
|
|
||||||
uploadSize: 1234,
|
uploadSize: 1234,
|
||||||
sha256Hash: 'test-sha256-hash'
|
sha256Hash: 'test-sha256-hash'
|
||||||
})
|
})
|
||||||
@@ -84,7 +84,7 @@ describe('upload-artifact', () => {
|
|||||||
'/home/user/files/plz-upload'
|
'/home/user/files/plz-upload'
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(uploadResp).resolves.toEqual({success: true, size: 1234, id: 1})
|
expect(uploadResp).resolves.toEqual({size: 1234, id: 1})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw an error if the root directory is invalid', () => {
|
it('should throw an error if the root directory is invalid', () => {
|
||||||
@@ -107,7 +107,7 @@ describe('upload-artifact', () => {
|
|||||||
expect(uploadResp).rejects.toThrow('Invalid root directory')
|
expect(uploadResp).rejects.toThrow('Invalid root directory')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return false if there are no files to upload', () => {
|
it('should reject if there are no files to upload', () => {
|
||||||
jest
|
jest
|
||||||
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
||||||
.mockReturnValue()
|
.mockReturnValue()
|
||||||
@@ -124,7 +124,7 @@ describe('upload-artifact', () => {
|
|||||||
],
|
],
|
||||||
'/home/user/files/plz-upload'
|
'/home/user/files/plz-upload'
|
||||||
)
|
)
|
||||||
expect(uploadResp).resolves.toEqual({success: false})
|
expect(uploadResp).rejects.toThrowError(FilesNotFoundError)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should reject if no backend IDs are found', () => {
|
it('should reject if no backend IDs are found', () => {
|
||||||
@@ -217,7 +217,7 @@ describe('upload-artifact', () => {
|
|||||||
'/home/user/files/plz-upload'
|
'/home/user/files/plz-upload'
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(uploadResp).resolves.toEqual({success: false})
|
expect(uploadResp).rejects.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return false if blob storage upload is unsuccessful', () => {
|
it('should return false if blob storage upload is unsuccessful', () => {
|
||||||
@@ -262,7 +262,7 @@ describe('upload-artifact', () => {
|
|||||||
)
|
)
|
||||||
jest
|
jest
|
||||||
.spyOn(blobUpload, 'uploadZipToBlobStorage')
|
.spyOn(blobUpload, 'uploadZipToBlobStorage')
|
||||||
.mockReturnValue(Promise.resolve({isSuccess: false}))
|
.mockReturnValue(Promise.reject(new Error('boom')))
|
||||||
|
|
||||||
// ArtifactHttpClient mocks
|
// ArtifactHttpClient mocks
|
||||||
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
||||||
@@ -280,10 +280,10 @@ describe('upload-artifact', () => {
|
|||||||
'/home/user/files/plz-upload'
|
'/home/user/files/plz-upload'
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(uploadResp).resolves.toEqual({success: false})
|
expect(uploadResp).rejects.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return false if finalize artifact fails', () => {
|
it('should reject if finalize artifact fails', () => {
|
||||||
const mockDate = new Date('2020-01-01')
|
const mockDate = new Date('2020-01-01')
|
||||||
jest
|
jest
|
||||||
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
||||||
@@ -325,7 +325,6 @@ describe('upload-artifact', () => {
|
|||||||
)
|
)
|
||||||
jest.spyOn(blobUpload, 'uploadZipToBlobStorage').mockReturnValue(
|
jest.spyOn(blobUpload, 'uploadZipToBlobStorage').mockReturnValue(
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
isSuccess: true,
|
|
||||||
uploadSize: 1234,
|
uploadSize: 1234,
|
||||||
sha256Hash: 'test-sha256-hash'
|
sha256Hash: 'test-sha256-hash'
|
||||||
})
|
})
|
||||||
@@ -350,6 +349,6 @@ describe('upload-artifact', () => {
|
|||||||
'/home/user/files/plz-upload'
|
'/home/user/files/plz-upload'
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(uploadResp).resolves.toEqual({success: false})
|
expect(uploadResp).rejects.toThrow()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {ArtifactClient, Client} from './internal/client'
|
|||||||
* Exported functionality that we want to expose for any users of @actions/artifact
|
* Exported functionality that we want to expose for any users of @actions/artifact
|
||||||
*/
|
*/
|
||||||
export * from './internal/shared/interfaces'
|
export * from './internal/shared/interfaces'
|
||||||
|
export * from './internal/shared/errors'
|
||||||
export {ArtifactClient}
|
export {ArtifactClient}
|
||||||
|
|
||||||
export function create(): ArtifactClient {
|
export function create(): ArtifactClient {
|
||||||
|
|||||||
@@ -163,6 +163,12 @@ export interface ListArtifactsResponse_MonolithArtifact {
|
|||||||
* @generated from protobuf field: int64 size = 5;
|
* @generated from protobuf field: int64 size = 5;
|
||||||
*/
|
*/
|
||||||
size: string;
|
size: string;
|
||||||
|
/**
|
||||||
|
* When the artifact was created in the monolith
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: google.protobuf.Timestamp created_at = 6;
|
||||||
|
*/
|
||||||
|
createdAt?: Timestamp;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @generated from protobuf message github.actions.results.api.v1.GetSignedArtifactURLRequest
|
* @generated from protobuf message github.actions.results.api.v1.GetSignedArtifactURLRequest
|
||||||
@@ -571,7 +577,8 @@ class ListArtifactsResponse_MonolithArtifact$Type extends MessageType<ListArtifa
|
|||||||
{ no: 2, name: "workflow_job_run_backend_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
{ no: 2, name: "workflow_job_run_backend_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||||
{ no: 3, name: "database_id", kind: "scalar", T: 3 /*ScalarType.INT64*/ },
|
{ no: 3, name: "database_id", kind: "scalar", T: 3 /*ScalarType.INT64*/ },
|
||||||
{ no: 4, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
{ no: 4, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||||
{ no: 5, name: "size", kind: "scalar", T: 3 /*ScalarType.INT64*/ }
|
{ no: 5, name: "size", kind: "scalar", T: 3 /*ScalarType.INT64*/ },
|
||||||
|
{ no: 6, name: "created_at", kind: "message", T: () => Timestamp }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
create(value?: PartialMessage<ListArtifactsResponse_MonolithArtifact>): ListArtifactsResponse_MonolithArtifact {
|
create(value?: PartialMessage<ListArtifactsResponse_MonolithArtifact>): ListArtifactsResponse_MonolithArtifact {
|
||||||
@@ -601,6 +608,9 @@ class ListArtifactsResponse_MonolithArtifact$Type extends MessageType<ListArtifa
|
|||||||
case /* int64 size */ 5:
|
case /* int64 size */ 5:
|
||||||
message.size = reader.int64().toString();
|
message.size = reader.int64().toString();
|
||||||
break;
|
break;
|
||||||
|
case /* google.protobuf.Timestamp created_at */ 6:
|
||||||
|
message.createdAt = Timestamp.internalBinaryRead(reader, reader.uint32(), options, message.createdAt);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
let u = options.readUnknownField;
|
let u = options.readUnknownField;
|
||||||
if (u === "throw")
|
if (u === "throw")
|
||||||
@@ -628,6 +638,9 @@ class ListArtifactsResponse_MonolithArtifact$Type extends MessageType<ListArtifa
|
|||||||
/* int64 size = 5; */
|
/* int64 size = 5; */
|
||||||
if (message.size !== "0")
|
if (message.size !== "0")
|
||||||
writer.tag(5, WireType.Varint).int64(message.size);
|
writer.tag(5, WireType.Varint).int64(message.size);
|
||||||
|
/* google.protobuf.Timestamp created_at = 6; */
|
||||||
|
if (message.createdAt)
|
||||||
|
Timestamp.internalBinaryWrite(message.createdAt, writer.tag(6, WireType.LengthDelimited).fork(), options).join();
|
||||||
let u = options.writeUnknownFields;
|
let u = options.writeUnknownFields;
|
||||||
if (u !== false)
|
if (u !== false)
|
||||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import {warning} from '@actions/core'
|
import {warning} from '@actions/core'
|
||||||
import {isGhes} from './shared/config'
|
import {isGhes} from './shared/config'
|
||||||
import {
|
import {
|
||||||
UploadOptions,
|
UploadArtifactOptions,
|
||||||
UploadResponse,
|
UploadArtifactResponse,
|
||||||
DownloadArtifactOptions,
|
DownloadArtifactOptions,
|
||||||
GetArtifactResponse,
|
GetArtifactResponse,
|
||||||
|
ListArtifactsOptions,
|
||||||
ListArtifactsResponse,
|
ListArtifactsResponse,
|
||||||
DownloadArtifactResponse,
|
DownloadArtifactResponse,
|
||||||
FindOptions
|
FindOptions
|
||||||
@@ -16,6 +17,7 @@ import {
|
|||||||
} from './download/download-artifact'
|
} from './download/download-artifact'
|
||||||
import {getArtifactPublic, getArtifactInternal} from './find/get-artifact'
|
import {getArtifactPublic, getArtifactInternal} from './find/get-artifact'
|
||||||
import {listArtifactsPublic, listArtifactsInternal} from './find/list-artifacts'
|
import {listArtifactsPublic, listArtifactsInternal} from './find/list-artifacts'
|
||||||
|
import {GHESNotSupportedError} from './shared/errors'
|
||||||
|
|
||||||
export interface ArtifactClient {
|
export interface ArtifactClient {
|
||||||
/**
|
/**
|
||||||
@@ -25,14 +27,14 @@ export interface ArtifactClient {
|
|||||||
* @param files A list of absolute or relative paths that denote what files should be uploaded
|
* @param files A list of absolute or relative paths that denote what files should be uploaded
|
||||||
* @param rootDirectory An absolute or relative file path that denotes the root parent directory of the files being uploaded
|
* @param rootDirectory An absolute or relative file path that denotes the root parent directory of the files being uploaded
|
||||||
* @param options Extra options for customizing the upload behavior
|
* @param options Extra options for customizing the upload behavior
|
||||||
* @returns single UploadResponse object
|
* @returns single UploadArtifactResponse object
|
||||||
*/
|
*/
|
||||||
uploadArtifact(
|
uploadArtifact(
|
||||||
name: string,
|
name: string,
|
||||||
files: string[],
|
files: string[],
|
||||||
rootDirectory: string,
|
rootDirectory: string,
|
||||||
options?: UploadOptions
|
options?: UploadArtifactOptions
|
||||||
): Promise<UploadResponse>
|
): Promise<UploadArtifactResponse>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists all artifacts that are part of the current workflow run.
|
* Lists all artifacts that are part of the current workflow run.
|
||||||
@@ -44,10 +46,14 @@ export interface ArtifactClient {
|
|||||||
* @param options Extra options that allow for the customization of the list behavior
|
* @param options Extra options that allow for the customization of the list behavior
|
||||||
* @returns ListArtifactResponse object
|
* @returns ListArtifactResponse object
|
||||||
*/
|
*/
|
||||||
listArtifacts(options?: FindOptions): Promise<ListArtifactsResponse>
|
listArtifacts(
|
||||||
|
options?: ListArtifactsOptions & FindOptions
|
||||||
|
): Promise<ListArtifactsResponse>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds an artifact by name.
|
* Finds an artifact by name.
|
||||||
|
* If there are multiple artifacts with the same name in the same workflow run, this will return the latest.
|
||||||
|
* If the artifact is not found, it will throw.
|
||||||
*
|
*
|
||||||
* If options.findBy is specified, this will use the public List Artifacts API with a name filter which can get artifacts from other runs.
|
* If options.findBy is specified, this will use the public List Artifacts API with a name filter which can get artifacts from other runs.
|
||||||
* https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#list-workflow-run-artifacts
|
* https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#list-workflow-run-artifacts
|
||||||
@@ -93,18 +99,13 @@ export class Client implements ArtifactClient {
|
|||||||
name: string,
|
name: string,
|
||||||
files: string[],
|
files: string[],
|
||||||
rootDirectory: string,
|
rootDirectory: string,
|
||||||
options?: UploadOptions
|
options?: UploadArtifactOptions
|
||||||
): Promise<UploadResponse> {
|
): Promise<UploadArtifactResponse> {
|
||||||
if (isGhes()) {
|
|
||||||
warning(
|
|
||||||
`@actions/artifact v2.0.0+ and upload-artifact@v4+ are not currently supported on GHES.`
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
success: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (isGhes()) {
|
||||||
|
throw new GHESNotSupportedError()
|
||||||
|
}
|
||||||
|
|
||||||
return uploadArtifact(name, files, rootDirectory, options)
|
return uploadArtifact(name, files, rootDirectory, options)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
warning(
|
warning(
|
||||||
@@ -114,9 +115,8 @@ Errors can be temporary, so please try again and optionally run the action with
|
|||||||
|
|
||||||
If the error persists, please check whether Actions is operating normally at [https://githubstatus.com](https://www.githubstatus.com).`
|
If the error persists, please check whether Actions is operating normally at [https://githubstatus.com](https://www.githubstatus.com).`
|
||||||
)
|
)
|
||||||
return {
|
|
||||||
success: false
|
throw error
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,16 +127,11 @@ If the error persists, please check whether Actions is operating normally at [ht
|
|||||||
artifactId: number,
|
artifactId: number,
|
||||||
options?: DownloadArtifactOptions & FindOptions
|
options?: DownloadArtifactOptions & FindOptions
|
||||||
): Promise<DownloadArtifactResponse> {
|
): Promise<DownloadArtifactResponse> {
|
||||||
if (isGhes()) {
|
|
||||||
warning(
|
|
||||||
`@actions/artifact v2.0.0+ and download-artifact@v4+ are not currently supported on GHES.`
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
success: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (isGhes()) {
|
||||||
|
throw new GHESNotSupportedError()
|
||||||
|
}
|
||||||
|
|
||||||
if (options?.findBy) {
|
if (options?.findBy) {
|
||||||
const {
|
const {
|
||||||
findBy: {repositoryOwner, repositoryName, token},
|
findBy: {repositoryOwner, repositoryName, token},
|
||||||
@@ -155,33 +150,28 @@ If the error persists, please check whether Actions is operating normally at [ht
|
|||||||
return downloadArtifactInternal(artifactId, options)
|
return downloadArtifactInternal(artifactId, options)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
warning(
|
warning(
|
||||||
`Artifact download failed with error: ${error}.
|
`Download Artifact failed with error: ${error}.
|
||||||
|
|
||||||
Errors can be temporary, so please try again and optionally run the action with debug mode enabled for more information.
|
Errors can be temporary, so please try again and optionally run the action with debug mode enabled for more information.
|
||||||
|
|
||||||
If the error persists, please check whether Actions and API requests are operating normally at [https://githubstatus.com](https://www.githubstatus.com).`
|
If the error persists, please check whether Actions and API requests are operating normally at [https://githubstatus.com](https://www.githubstatus.com).`
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
throw error
|
||||||
success: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List Artifacts
|
* List Artifacts
|
||||||
*/
|
*/
|
||||||
async listArtifacts(options?: FindOptions): Promise<ListArtifactsResponse> {
|
async listArtifacts(
|
||||||
if (isGhes()) {
|
options?: ListArtifactsOptions & FindOptions
|
||||||
warning(
|
): Promise<ListArtifactsResponse> {
|
||||||
`@actions/artifact v2.0.0+ and download-artifact@v4+ are not currently supported on GHES.`
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
artifacts: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (isGhes()) {
|
||||||
|
throw new GHESNotSupportedError()
|
||||||
|
}
|
||||||
|
|
||||||
if (options?.findBy) {
|
if (options?.findBy) {
|
||||||
const {
|
const {
|
||||||
findBy: {workflowRunId, repositoryOwner, repositoryName, token}
|
findBy: {workflowRunId, repositoryOwner, repositoryName, token}
|
||||||
@@ -191,11 +181,12 @@ If the error persists, please check whether Actions and API requests are operati
|
|||||||
workflowRunId,
|
workflowRunId,
|
||||||
repositoryOwner,
|
repositoryOwner,
|
||||||
repositoryName,
|
repositoryName,
|
||||||
token
|
token,
|
||||||
|
options?.latest
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return listArtifactsInternal()
|
return listArtifactsInternal(options?.latest)
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
warning(
|
warning(
|
||||||
`Listing Artifacts failed with error: ${error}.
|
`Listing Artifacts failed with error: ${error}.
|
||||||
@@ -205,9 +196,7 @@ Errors can be temporary, so please try again and optionally run the action with
|
|||||||
If the error persists, please check whether Actions and API requests are operating normally at [https://githubstatus.com](https://www.githubstatus.com).`
|
If the error persists, please check whether Actions and API requests are operating normally at [https://githubstatus.com](https://www.githubstatus.com).`
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
throw error
|
||||||
artifacts: []
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,16 +207,11 @@ If the error persists, please check whether Actions and API requests are operati
|
|||||||
artifactName: string,
|
artifactName: string,
|
||||||
options?: FindOptions
|
options?: FindOptions
|
||||||
): Promise<GetArtifactResponse> {
|
): Promise<GetArtifactResponse> {
|
||||||
if (isGhes()) {
|
|
||||||
warning(
|
|
||||||
`@actions/artifact v2.0.0+ and download-artifact@v4+ are not currently supported on GHES.`
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
success: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (isGhes()) {
|
||||||
|
throw new GHESNotSupportedError()
|
||||||
|
}
|
||||||
|
|
||||||
if (options?.findBy) {
|
if (options?.findBy) {
|
||||||
const {
|
const {
|
||||||
findBy: {workflowRunId, repositoryOwner, repositoryName, token}
|
findBy: {workflowRunId, repositoryOwner, repositoryName, token}
|
||||||
@@ -245,15 +229,13 @@ If the error persists, please check whether Actions and API requests are operati
|
|||||||
return getArtifactInternal(artifactName)
|
return getArtifactInternal(artifactName)
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
warning(
|
warning(
|
||||||
`Fetching Artifact failed with error: ${error}.
|
`Get Artifact failed with error: ${error}.
|
||||||
|
|
||||||
Errors can be temporary, so please try again and optionally run the action with debug mode enabled for more information.
|
Errors can be temporary, so please try again and optionally run the action with debug mode enabled for more information.
|
||||||
|
|
||||||
If the error persists, please check whether Actions and API requests are operating normally at [https://githubstatus.com](https://www.githubstatus.com).`
|
If the error persists, please check whether Actions and API requests are operating normally at [https://githubstatus.com](https://www.githubstatus.com).`
|
||||||
)
|
)
|
||||||
return {
|
throw error
|
||||||
success: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
ListArtifactsRequest
|
ListArtifactsRequest
|
||||||
} from '../../generated'
|
} from '../../generated'
|
||||||
import {getBackendIdsFromToken} from '../shared/util'
|
import {getBackendIdsFromToken} from '../shared/util'
|
||||||
|
import {ArtifactNotFoundError} from '../shared/errors'
|
||||||
|
|
||||||
const scrubQueryParameters = (url: string): string => {
|
const scrubQueryParameters = (url: string): string => {
|
||||||
const parsed = new URL(url)
|
const parsed = new URL(url)
|
||||||
@@ -95,7 +96,7 @@ export async function downloadArtifactPublic(
|
|||||||
throw new Error(`Unable to download and extract artifact: ${error.message}`)
|
throw new Error(`Unable to download and extract artifact: ${error.message}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {success: true, downloadPath}
|
return {downloadPath}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadArtifactInternal(
|
export async function downloadArtifactInternal(
|
||||||
@@ -118,10 +119,9 @@ export async function downloadArtifactInternal(
|
|||||||
const {artifacts} = await artifactClient.ListArtifacts(listReq)
|
const {artifacts} = await artifactClient.ListArtifacts(listReq)
|
||||||
|
|
||||||
if (artifacts.length === 0) {
|
if (artifacts.length === 0) {
|
||||||
core.warning(
|
throw new ArtifactNotFoundError(
|
||||||
`No artifacts found for ID: ${artifactId}\nAre you trying to download from a different run? Try specifying a github-token with \`actions:read\` scope.`
|
`No artifacts found for ID: ${artifactId}\nAre you trying to download from a different run? Try specifying a github-token with \`actions:read\` scope.`
|
||||||
)
|
)
|
||||||
return {success: false}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (artifacts.length > 1) {
|
if (artifacts.length > 1) {
|
||||||
@@ -148,7 +148,7 @@ export async function downloadArtifactInternal(
|
|||||||
throw new Error(`Unable to download and extract artifact: ${error.message}`)
|
throw new Error(`Unable to download and extract artifact: ${error.message}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {success: true, downloadPath}
|
return {downloadPath}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveOrCreateDirectory(
|
async function resolveOrCreateDirectory(
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import {GetArtifactResponse} from '../shared/interfaces'
|
|||||||
import {getBackendIdsFromToken} from '../shared/util'
|
import {getBackendIdsFromToken} from '../shared/util'
|
||||||
import {getUserAgentString} from '../shared/user-agent'
|
import {getUserAgentString} from '../shared/user-agent'
|
||||||
import {internalArtifactTwirpClient} from '../shared/artifact-twirp-client'
|
import {internalArtifactTwirpClient} from '../shared/artifact-twirp-client'
|
||||||
import {ListArtifactsRequest, StringValue} from '../../generated'
|
import {ListArtifactsRequest, StringValue, Timestamp} from '../../generated'
|
||||||
|
import {ArtifactNotFoundError, InvalidResponseError} from '../shared/errors'
|
||||||
|
|
||||||
export async function getArtifactPublic(
|
export async function getArtifactPublic(
|
||||||
artifactName: string,
|
artifactName: string,
|
||||||
@@ -41,31 +42,31 @@ export async function getArtifactPublic(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (getArtifactResp.status !== 200) {
|
if (getArtifactResp.status !== 200) {
|
||||||
core.warning(`non-200 response from GitHub API: ${getArtifactResp.status}`)
|
throw new InvalidResponseError(
|
||||||
return {
|
`Invalid response from GitHub API: ${getArtifactResp.status} (${getArtifactResp?.headers?.['x-github-request-id']})`
|
||||||
success: false
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getArtifactResp.data.artifacts.length === 0) {
|
if (getArtifactResp.data.artifacts.length === 0) {
|
||||||
core.warning('no artifacts found')
|
throw new ArtifactNotFoundError(
|
||||||
return {
|
`Artifact not found for name: ${artifactName}`
|
||||||
success: false
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let artifact = getArtifactResp.data.artifacts[0]
|
||||||
if (getArtifactResp.data.artifacts.length > 1) {
|
if (getArtifactResp.data.artifacts.length > 1) {
|
||||||
core.warning(
|
artifact = getArtifactResp.data.artifacts.sort((a, b) => b.id - a.id)[0]
|
||||||
'more than one artifact found for a single name, returning first'
|
core.debug(
|
||||||
|
`More than one artifact found for a single name, returning newest (id: ${artifact.id})`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
|
||||||
artifact: {
|
artifact: {
|
||||||
name: getArtifactResp.data.artifacts[0].name,
|
name: artifact.name,
|
||||||
id: getArtifactResp.data.artifacts[0].id,
|
id: artifact.id,
|
||||||
size: getArtifactResp.data.artifacts[0].size_in_bytes
|
size: artifact.size_in_bytes,
|
||||||
|
createdAt: artifact.created_at ? new Date(artifact.created_at) : undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,32 +88,30 @@ export async function getArtifactInternal(
|
|||||||
const res = await artifactClient.ListArtifacts(req)
|
const res = await artifactClient.ListArtifacts(req)
|
||||||
|
|
||||||
if (res.artifacts.length === 0) {
|
if (res.artifacts.length === 0) {
|
||||||
core.warning('no artifacts found')
|
throw new ArtifactNotFoundError(
|
||||||
return {
|
`Artifact not found for name: ${artifactName}`
|
||||||
success: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.artifacts.length > 1) {
|
|
||||||
core.warning(
|
|
||||||
'more than one artifact found for a single name, returning first'
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// In the case of reruns, we may have artifacts with the same name scoped under the same workflow run.
|
let artifact = res.artifacts[0]
|
||||||
// Let's prefer the artifact closest scoped to this run.
|
if (res.artifacts.length > 1) {
|
||||||
// If it doesn't exist (e.g. partial rerun) we'll use the first match.
|
artifact = res.artifacts.sort(
|
||||||
const artifact =
|
(a, b) => Number(b.databaseId) - Number(a.databaseId)
|
||||||
res.artifacts.find(
|
)[0]
|
||||||
artifact => artifact.workflowRunBackendId === workflowRunBackendId
|
|
||||||
) || res.artifacts[0]
|
core.debug(
|
||||||
|
`More than one artifact found for a single name, returning newest (id: ${artifact.databaseId})`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
|
||||||
artifact: {
|
artifact: {
|
||||||
name: artifact.name,
|
name: artifact.name,
|
||||||
id: Number(artifact.databaseId),
|
id: Number(artifact.databaseId),
|
||||||
size: Number(artifact.size)
|
size: Number(artifact.size),
|
||||||
|
createdAt: artifact.createdAt
|
||||||
|
? Timestamp.toDate(artifact.createdAt)
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {retry} from '@octokit/plugin-retry'
|
|||||||
import {OctokitOptions} from '@octokit/core/dist-types/types'
|
import {OctokitOptions} from '@octokit/core/dist-types/types'
|
||||||
import {internalArtifactTwirpClient} from '../shared/artifact-twirp-client'
|
import {internalArtifactTwirpClient} from '../shared/artifact-twirp-client'
|
||||||
import {getBackendIdsFromToken} from '../shared/util'
|
import {getBackendIdsFromToken} from '../shared/util'
|
||||||
import {ListArtifactsRequest} from 'src/generated'
|
import {ListArtifactsRequest, Timestamp} from '../../generated'
|
||||||
|
|
||||||
// Limiting to 1000 for perf reasons
|
// Limiting to 1000 for perf reasons
|
||||||
const maximumArtifactCount = 1000
|
const maximumArtifactCount = 1000
|
||||||
@@ -20,13 +20,14 @@ export async function listArtifactsPublic(
|
|||||||
workflowRunId: number,
|
workflowRunId: number,
|
||||||
repositoryOwner: string,
|
repositoryOwner: string,
|
||||||
repositoryName: string,
|
repositoryName: string,
|
||||||
token: string
|
token: string,
|
||||||
|
latest = false
|
||||||
): Promise<ListArtifactsResponse> {
|
): Promise<ListArtifactsResponse> {
|
||||||
info(
|
info(
|
||||||
`Fetching artifact list for workflow run ${workflowRunId} in repository ${repositoryOwner}/${repositoryName}`
|
`Fetching artifact list for workflow run ${workflowRunId} in repository ${repositoryOwner}/${repositoryName}`
|
||||||
)
|
)
|
||||||
|
|
||||||
const artifacts: Artifact[] = []
|
let artifacts: Artifact[] = []
|
||||||
const [retryOpts, requestOpts] = getRetryOptions(defaultGitHubOptions)
|
const [retryOpts, requestOpts] = getRetryOptions(defaultGitHubOptions)
|
||||||
|
|
||||||
const opts: OctokitOptions = {
|
const opts: OctokitOptions = {
|
||||||
@@ -65,7 +66,8 @@ export async function listArtifactsPublic(
|
|||||||
artifacts.push({
|
artifacts.push({
|
||||||
name: artifact.name,
|
name: artifact.name,
|
||||||
id: artifact.id,
|
id: artifact.id,
|
||||||
size: artifact.size_in_bytes
|
size: artifact.size_in_bytes,
|
||||||
|
createdAt: artifact.created_at ? new Date(artifact.created_at) : undefined
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,11 +93,18 @@ export async function listArtifactsPublic(
|
|||||||
artifacts.push({
|
artifacts.push({
|
||||||
name: artifact.name,
|
name: artifact.name,
|
||||||
id: artifact.id,
|
id: artifact.id,
|
||||||
size: artifact.size_in_bytes
|
size: artifact.size_in_bytes,
|
||||||
|
createdAt: artifact.created_at
|
||||||
|
? new Date(artifact.created_at)
|
||||||
|
: undefined
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (latest) {
|
||||||
|
artifacts = filterLatest(artifacts)
|
||||||
|
}
|
||||||
|
|
||||||
info(`Found ${artifacts.length} artifact(s)`)
|
info(`Found ${artifacts.length} artifact(s)`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -103,7 +112,9 @@ export async function listArtifactsPublic(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listArtifactsInternal(): Promise<ListArtifactsResponse> {
|
export async function listArtifactsInternal(
|
||||||
|
latest = false
|
||||||
|
): Promise<ListArtifactsResponse> {
|
||||||
const artifactClient = internalArtifactTwirpClient()
|
const artifactClient = internalArtifactTwirpClient()
|
||||||
|
|
||||||
const {workflowRunBackendId, workflowJobRunBackendId} =
|
const {workflowRunBackendId, workflowJobRunBackendId} =
|
||||||
@@ -115,15 +126,40 @@ export async function listArtifactsInternal(): Promise<ListArtifactsResponse> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const res = await artifactClient.ListArtifacts(req)
|
const res = await artifactClient.ListArtifacts(req)
|
||||||
const artifacts = res.artifacts.map(artifact => ({
|
let artifacts: Artifact[] = res.artifacts.map(artifact => ({
|
||||||
name: artifact.name,
|
name: artifact.name,
|
||||||
id: Number(artifact.databaseId),
|
id: Number(artifact.databaseId),
|
||||||
size: Number(artifact.size)
|
size: Number(artifact.size),
|
||||||
|
createdAt: artifact.createdAt
|
||||||
|
? Timestamp.toDate(artifact.createdAt)
|
||||||
|
: undefined
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
if (latest) {
|
||||||
|
artifacts = filterLatest(artifacts)
|
||||||
|
}
|
||||||
|
|
||||||
info(`Found ${artifacts.length} artifact(s)`)
|
info(`Found ${artifacts.length} artifact(s)`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
artifacts
|
artifacts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters a list of artifacts to only include the latest artifact for each name
|
||||||
|
* @param artifacts The artifacts to filter
|
||||||
|
* @returns The filtered list of artifacts
|
||||||
|
*/
|
||||||
|
function filterLatest(artifacts: Artifact[]): Artifact[] {
|
||||||
|
artifacts.sort((a, b) => b.id - a.id)
|
||||||
|
const latestArtifacts: Artifact[] = []
|
||||||
|
const seenArtifactNames = new Set<string>()
|
||||||
|
for (const artifact of artifacts) {
|
||||||
|
if (!seenArtifactNames.has(artifact.name)) {
|
||||||
|
latestArtifacts.push(artifact)
|
||||||
|
seenArtifactNames.add(artifact.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return latestArtifacts
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
export class FilesNotFoundError extends Error {
|
||||||
|
files: string[]
|
||||||
|
|
||||||
|
constructor(files: string[] = []) {
|
||||||
|
let message = 'No files were found to upload'
|
||||||
|
if (files.length > 0) {
|
||||||
|
message += `: ${files.join(', ')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
super(message)
|
||||||
|
this.files = files
|
||||||
|
this.name = 'FilesNotFoundError'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InvalidResponseError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message)
|
||||||
|
this.name = 'InvalidResponseError'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ArtifactNotFoundError extends Error {
|
||||||
|
constructor(message = 'Artifact not found') {
|
||||||
|
super(message)
|
||||||
|
this.name = 'ArtifactNotFoundError'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GHESNotSupportedError extends Error {
|
||||||
|
constructor(
|
||||||
|
message = '@actions/artifact v2.0.0+ and download-artifact@v4+ are not currently supported on GHES.'
|
||||||
|
) {
|
||||||
|
super(message)
|
||||||
|
this.name = 'GHESNotSupportedError'
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,12 +3,7 @@
|
|||||||
* UploadArtifact *
|
* UploadArtifact *
|
||||||
* *
|
* *
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
export interface UploadResponse {
|
export interface UploadArtifactResponse {
|
||||||
/**
|
|
||||||
* Denotes if an artifact was successfully uploaded
|
|
||||||
*/
|
|
||||||
success: boolean
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Total size of the artifact in bytes. Not provided if no artifact was uploaded
|
* Total size of the artifact in bytes. Not provided if no artifact was uploaded
|
||||||
*/
|
*/
|
||||||
@@ -21,7 +16,7 @@ export interface UploadResponse {
|
|||||||
id?: number
|
id?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UploadOptions {
|
export interface UploadArtifactOptions {
|
||||||
/**
|
/**
|
||||||
* Duration after which artifact will expire in days.
|
* Duration after which artifact will expire in days.
|
||||||
*
|
*
|
||||||
@@ -58,15 +53,10 @@ export interface UploadOptions {
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
export interface GetArtifactResponse {
|
export interface GetArtifactResponse {
|
||||||
/**
|
|
||||||
* If an artifact was found
|
|
||||||
*/
|
|
||||||
success: boolean
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata about the artifact that was found
|
* Metadata about the artifact that was found
|
||||||
*/
|
*/
|
||||||
artifact?: Artifact
|
artifact: Artifact
|
||||||
}
|
}
|
||||||
|
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
@@ -74,6 +64,15 @@ export interface GetArtifactResponse {
|
|||||||
* ListArtifact *
|
* ListArtifact *
|
||||||
* *
|
* *
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
export interface ListArtifactsOptions {
|
||||||
|
/**
|
||||||
|
* Filter the workflow run's artifacts to the latest by name
|
||||||
|
* In the case of reruns, this can be useful to avoid duplicates
|
||||||
|
*/
|
||||||
|
latest?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface ListArtifactsResponse {
|
export interface ListArtifactsResponse {
|
||||||
/**
|
/**
|
||||||
* A list of artifacts that were found
|
* A list of artifacts that were found
|
||||||
@@ -87,10 +86,6 @@ export interface ListArtifactsResponse {
|
|||||||
* *
|
* *
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
export interface DownloadArtifactResponse {
|
export interface DownloadArtifactResponse {
|
||||||
/**
|
|
||||||
* If the artifact download was successful
|
|
||||||
*/
|
|
||||||
success: boolean
|
|
||||||
/**
|
/**
|
||||||
* The path where the artifact was downloaded to
|
* The path where the artifact was downloaded to
|
||||||
*/
|
*/
|
||||||
@@ -124,6 +119,11 @@ export interface Artifact {
|
|||||||
* The size of the artifact in bytes
|
* The size of the artifact in bytes
|
||||||
*/
|
*/
|
||||||
size: number
|
size: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time when the artifact was created
|
||||||
|
*/
|
||||||
|
createdAt?: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindOptions are for fetching Artifact(s) out of the scope of the current run.
|
// FindOptions are for fetching Artifact(s) out of the scope of the current run.
|
||||||
|
|||||||
@@ -7,11 +7,6 @@ import * as crypto from 'crypto'
|
|||||||
import * as stream from 'stream'
|
import * as stream from 'stream'
|
||||||
|
|
||||||
export interface BlobUploadResponse {
|
export interface BlobUploadResponse {
|
||||||
/**
|
|
||||||
* If the upload was successful or not
|
|
||||||
*/
|
|
||||||
isSuccess: boolean
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The total reported upload size in bytes. Empty if the upload failed
|
* The total reported upload size in bytes. Empty if the upload failed
|
||||||
*/
|
*/
|
||||||
@@ -55,41 +50,28 @@ export async function uploadZipToBlobStorage(
|
|||||||
zipUploadStream.pipe(uploadStream) // This stream is used for the upload
|
zipUploadStream.pipe(uploadStream) // This stream is used for the upload
|
||||||
zipUploadStream.pipe(hashStream).setEncoding('hex') // This stream is used to compute a hash of the zip content that gets used. Integrity check
|
zipUploadStream.pipe(hashStream).setEncoding('hex') // This stream is used to compute a hash of the zip content that gets used. Integrity check
|
||||||
|
|
||||||
try {
|
core.info('Beginning upload of artifact content to blob storage')
|
||||||
core.info('Beginning upload of artifact content to blob storage')
|
|
||||||
|
|
||||||
await blockBlobClient.uploadStream(
|
await blockBlobClient.uploadStream(
|
||||||
uploadStream,
|
uploadStream,
|
||||||
bufferSize,
|
bufferSize,
|
||||||
maxConcurrency,
|
maxConcurrency,
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
|
|
||||||
core.info('Finished uploading artifact content to blob storage!')
|
core.info('Finished uploading artifact content to blob storage!')
|
||||||
|
|
||||||
hashStream.end()
|
hashStream.end()
|
||||||
sha256Hash = hashStream.read() as string
|
sha256Hash = hashStream.read() as string
|
||||||
core.info(`SHA256 hash of uploaded artifact zip is ${sha256Hash}`)
|
core.info(`SHA256 hash of uploaded artifact zip is ${sha256Hash}`)
|
||||||
} catch (error) {
|
|
||||||
core.warning(
|
|
||||||
`Failed to upload artifact zip to blob storage, error: ${error}`
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
isSuccess: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uploadByteCount === 0) {
|
if (uploadByteCount === 0) {
|
||||||
core.warning(
|
core.warning(
|
||||||
`No data was uploaded to blob storage. Reported upload byte count is 0`
|
`No data was uploaded to blob storage. Reported upload byte count is 0.`
|
||||||
)
|
)
|
||||||
return {
|
|
||||||
isSuccess: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isSuccess: true,
|
|
||||||
uploadSize: uploadByteCount,
|
uploadSize: uploadByteCount,
|
||||||
sha256Hash
|
sha256Hash
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {UploadOptions, UploadResponse} from '../shared/interfaces'
|
import {
|
||||||
|
UploadArtifactOptions,
|
||||||
|
UploadArtifactResponse
|
||||||
|
} from '../shared/interfaces'
|
||||||
import {getExpiration} from './retention'
|
import {getExpiration} from './retention'
|
||||||
import {validateArtifactName} from './path-and-artifact-name-validation'
|
import {validateArtifactName} from './path-and-artifact-name-validation'
|
||||||
import {internalArtifactTwirpClient} from '../shared/artifact-twirp-client'
|
import {internalArtifactTwirpClient} from '../shared/artifact-twirp-client'
|
||||||
@@ -16,13 +19,14 @@ import {
|
|||||||
FinalizeArtifactRequest,
|
FinalizeArtifactRequest,
|
||||||
StringValue
|
StringValue
|
||||||
} from '../../generated'
|
} from '../../generated'
|
||||||
|
import {FilesNotFoundError, InvalidResponseError} from '../shared/errors'
|
||||||
|
|
||||||
export async function uploadArtifact(
|
export async function uploadArtifact(
|
||||||
name: string,
|
name: string,
|
||||||
files: string[],
|
files: string[],
|
||||||
rootDirectory: string,
|
rootDirectory: string,
|
||||||
options?: UploadOptions | undefined
|
options?: UploadArtifactOptions | undefined
|
||||||
): Promise<UploadResponse> {
|
): Promise<UploadArtifactResponse> {
|
||||||
validateArtifactName(name)
|
validateArtifactName(name)
|
||||||
validateRootDirectory(rootDirectory)
|
validateRootDirectory(rootDirectory)
|
||||||
|
|
||||||
@@ -31,10 +35,9 @@ export async function uploadArtifact(
|
|||||||
rootDirectory
|
rootDirectory
|
||||||
)
|
)
|
||||||
if (zipSpecification.length === 0) {
|
if (zipSpecification.length === 0) {
|
||||||
core.warning(`No files were found to upload`)
|
throw new FilesNotFoundError(
|
||||||
return {
|
zipSpecification.flatMap(s => (s.sourcePath ? [s.sourcePath] : []))
|
||||||
success: false
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const zipUploadStream = await createZipUploadStream(
|
const zipUploadStream = await createZipUploadStream(
|
||||||
@@ -65,10 +68,9 @@ export async function uploadArtifact(
|
|||||||
const createArtifactResp =
|
const createArtifactResp =
|
||||||
await artifactClient.CreateArtifact(createArtifactReq)
|
await artifactClient.CreateArtifact(createArtifactReq)
|
||||||
if (!createArtifactResp.ok) {
|
if (!createArtifactResp.ok) {
|
||||||
core.warning(`Failed to create artifact`)
|
throw new InvalidResponseError(
|
||||||
return {
|
'CreateArtifact: response from backend was not ok'
|
||||||
success: false
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload zip to blob storage
|
// Upload zip to blob storage
|
||||||
@@ -76,11 +78,6 @@ export async function uploadArtifact(
|
|||||||
createArtifactResp.signedUploadUrl,
|
createArtifactResp.signedUploadUrl,
|
||||||
zipUploadStream
|
zipUploadStream
|
||||||
)
|
)
|
||||||
if (uploadResult.isSuccess === false) {
|
|
||||||
return {
|
|
||||||
success: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// finalize the artifact
|
// finalize the artifact
|
||||||
const finalizeArtifactReq: FinalizeArtifactRequest = {
|
const finalizeArtifactReq: FinalizeArtifactRequest = {
|
||||||
@@ -101,10 +98,9 @@ export async function uploadArtifact(
|
|||||||
const finalizeArtifactResp =
|
const finalizeArtifactResp =
|
||||||
await artifactClient.FinalizeArtifact(finalizeArtifactReq)
|
await artifactClient.FinalizeArtifact(finalizeArtifactReq)
|
||||||
if (!finalizeArtifactResp.ok) {
|
if (!finalizeArtifactResp.ok) {
|
||||||
core.warning(`Failed to finalize artifact`)
|
throw new InvalidResponseError(
|
||||||
return {
|
'FinalizeArtifact: response from backend was not ok'
|
||||||
success: false
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const artifactId = BigInt(finalizeArtifactResp.artifactId)
|
const artifactId = BigInt(finalizeArtifactResp.artifactId)
|
||||||
@@ -113,7 +109,6 @@ export async function uploadArtifact(
|
|||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
|
||||||
size: uploadResult.uploadSize,
|
size: uploadResult.uploadSize,
|
||||||
id: Number(artifactId)
|
id: Number(artifactId)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user