Compare commits
286
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2d2270685 | ||
|
|
0d1d5c7687 | ||
|
|
d7ddca4309 | ||
|
|
8780507298 | ||
|
|
790c56665a | ||
|
|
9d8017eadb | ||
|
|
20fee3ea63 | ||
|
|
7501423b6f | ||
|
|
d0cc3418ea | ||
|
|
b85d4e6b38 | ||
|
|
dc22dc7cad | ||
|
|
8c05dc87d8 | ||
|
|
d70fb49aaa | ||
|
|
3726c11433 | ||
|
|
71b40f7024 | ||
|
|
83e5e2517b | ||
|
|
d5c8a0fa27 | ||
|
|
780e24be34 | ||
|
|
ec9716b3cc | ||
|
|
0bc338adab | ||
|
|
5378ea8eca | ||
|
|
b95b593ca5 | ||
|
|
4fedf471b1 | ||
|
|
1b9063ee0e | ||
|
|
d096588f08 | ||
|
|
662b9d91f5 | ||
|
|
a62f530b6f | ||
|
|
2995cdf0a1 | ||
|
|
f10f9c8217 | ||
|
|
c26e6f3aba | ||
|
|
2b08dc18f2 | ||
|
|
412108cd55 | ||
|
|
8fcec1fb58 | ||
|
|
95e747361e | ||
|
|
aad39a371f | ||
|
|
7fe619c58c | ||
|
|
e6fb8f1c5d | ||
|
|
6a942b304d | ||
|
|
340a6b15b5 | ||
|
|
e0c069db55 | ||
|
|
1f7c2c79e0 | ||
|
|
5e8c25d1f5 | ||
|
|
3095d112ef | ||
|
|
16ef1448d7 | ||
|
|
e55409315f | ||
|
|
d4385a64a7 | ||
|
|
ede05b95d7 | ||
|
|
f3c12d5561 | ||
|
|
adb9c4a7f4 | ||
|
|
01f21badd5 | ||
|
|
26f8f84a96 | ||
|
|
433f76091b | ||
|
|
4426b4ea91 | ||
|
|
f522fdf89d | ||
|
|
1e0c16f0dc | ||
|
|
b7a00a3203 | ||
|
|
0827eef58f | ||
|
|
cd9197e9bd | ||
|
|
72447df44c | ||
|
|
59845ec372 | ||
|
|
cb001af8a3 | ||
|
|
4498687c5e | ||
|
|
a10e209c8d | ||
|
|
c02c929c56 | ||
|
|
c649df4b94 | ||
|
|
fb40492b6f | ||
|
|
502e8ce651 | ||
|
|
3f7df8ec5a | ||
|
|
b24632bd80 | ||
|
|
792ec716de | ||
|
|
7ad18fd6bd | ||
|
|
87171e29ca | ||
|
|
a762876d6d | ||
|
|
d89855bb90 | ||
|
|
db1d01308c | ||
|
|
4a272e9053 | ||
|
|
ee1c07d0aa | ||
|
|
c6f1224d30 | ||
|
|
1d403c2fd8 | ||
|
|
65892d5ffe | ||
|
|
8c5f6f2dc5 | ||
|
|
62f5f1885b | ||
|
|
eaf0083ee2 | ||
|
|
c1fb081674 | ||
|
|
df166709a3 | ||
|
|
c5a5de05f6 | ||
|
|
3a128c88c3 | ||
|
|
9cc30cb0d3 | ||
|
|
35d87ab129 | ||
|
|
af3981c955 | ||
|
|
27e5cf2514 | ||
|
|
b050504b2d | ||
|
|
5d0a4af70a | ||
|
|
94f18eb26e | ||
|
|
208dbe2131 | ||
|
|
46174ed573 | ||
|
|
1f087496ca | ||
|
|
8f606682c2 | ||
|
|
928d3e806d | ||
|
|
35ede8fcf0 | ||
|
|
4d31e1048a | ||
|
|
0e321b26f4 | ||
|
|
2d2513915c | ||
|
|
de236da416 | ||
|
|
4dadd612d6 | ||
|
|
54ac2dd012 | ||
|
|
4de30f744e | ||
|
|
27dfd2c41c | ||
|
|
20ed2908f1 | ||
|
|
39d19810a8 | ||
|
|
e2028d43a2 | ||
|
|
267841d7bd | ||
|
|
ab58a59f33 | ||
|
|
a1e6ef3759 | ||
|
|
8616c313a2 | ||
|
|
3ca85474b8 | ||
|
|
6c11d441a5 | ||
|
|
68ab87caa2 | ||
|
|
555b03f6fd | ||
|
|
ab8110fa2f | ||
|
|
5e9ef8532f | ||
|
|
ea4bf4810a | ||
|
|
c3e354da23 | ||
|
|
2ee77e654f | ||
|
|
83baffc3f6 | ||
|
|
19cdd5f210 | ||
|
|
b2557ac90c | ||
|
|
69409b3acd | ||
|
|
9dff82c727 | ||
|
|
d109d9c03e | ||
|
|
4e1912a3c3 | ||
|
|
9da70ffbd7 | ||
|
|
75cdb2c08f | ||
|
|
bb2278e5cf | ||
|
|
77f247b2f3 | ||
|
|
d13839fcf4 | ||
|
|
7e54468896 | ||
|
|
339447c5d3 | ||
|
|
43ce96d373 | ||
|
|
265a5be8bc | ||
|
|
65ee4d33af | ||
|
|
717ba9d9a4 | ||
|
|
01bf918aa5 | ||
|
|
28dbd8ff93 | ||
|
|
7f5921cddd | ||
|
|
89354f6540 | ||
|
|
d399e33060 | ||
|
|
29d342f176 | ||
|
|
72113fe791 | ||
|
|
7b4d9763cc | ||
|
|
26c752f562 | ||
|
|
ac1332a8e2 | ||
|
|
c6c5ef6b8e | ||
|
|
4d1dedf2c7 | ||
|
|
13abc95165 | ||
|
|
ee93b05ee9 | ||
|
|
799f8f5f3d | ||
|
|
201b082ce1 | ||
|
|
49cbbbcd99 | ||
|
|
545e0e6b95 | ||
|
|
c18a7d2f73 | ||
|
|
d14afd7973 | ||
|
|
22a72ac3d7 | ||
|
|
6ca0d9b637 | ||
|
|
650f7c6aa3 | ||
|
|
78af634e7e | ||
|
|
2a8f1c5ddd | ||
|
|
e62c6428e7 | ||
|
|
07e51a445e | ||
|
|
70e5684b1f | ||
|
|
5a62022195 | ||
|
|
8551843690 | ||
|
|
d6694e491d | ||
|
|
7f19a7886a | ||
|
|
6dd369c0e6 | ||
|
|
2a07de1333 | ||
|
|
2e1998fc42 | ||
|
|
b7a914b73b | ||
|
|
6c4e082c18 | ||
|
|
1e69bffbba | ||
|
|
d1aa255c7f | ||
|
|
7298ff3219 | ||
|
|
571d782946 | ||
|
|
ada9e00cda | ||
|
|
faf9cb2ea2 | ||
|
|
ac3a063583 | ||
|
|
7cc96bb976 | ||
|
|
fa6cc53297 | ||
|
|
f299e8ba1e | ||
|
|
1b9927d1c7 | ||
|
|
279e891118 | ||
|
|
340a1033a5 | ||
|
|
50f2977cce | ||
|
|
48a65377c0 | ||
|
|
f003268b32 | ||
|
|
3a33cca851 | ||
|
|
bb6c500939 | ||
|
|
76b6e24aee | ||
|
|
58d14c4ef5 | ||
|
|
7463cf3da6 | ||
|
|
90d9783552 | ||
|
|
7c61054649 | ||
|
|
b28406bd1f | ||
|
|
9517cdf52d | ||
|
|
49927e464a | ||
|
|
3e34f6d19c | ||
|
|
182702d2df | ||
|
|
1db73622df | ||
|
|
56832696fc | ||
|
|
176b40a888 | ||
|
|
4902d3a118 | ||
|
|
04d1a7ec3c | ||
|
|
e1b7e78d60 | ||
|
|
7640cf17c1 | ||
|
|
8d7ed4fb57 | ||
|
|
5afc042a74 | ||
|
|
5e5faf73fc | ||
|
|
361a115e53 | ||
|
|
dddc440d56 | ||
|
|
08d6f14ea8 | ||
|
|
9e63a77e7a | ||
|
|
146143a9b4 | ||
|
|
6635d12ce0 | ||
|
|
dccc3f7f1c | ||
|
|
66d5434f23 | ||
|
|
73100a7f85 | ||
|
|
c6b487124a | ||
|
|
c8466d1fac | ||
|
|
264230c2c5 | ||
|
|
32dbccb77b | ||
|
|
8735a7e2da | ||
|
|
d1df13e178 | ||
|
|
d3d7736bae | ||
|
|
7d18e7aa0d | ||
|
|
e60694077d | ||
|
|
ae38557bb0 | ||
|
|
abb586d71e | ||
|
|
81a73aba8b | ||
|
|
0e8fe8af62 | ||
|
|
29885a805e | ||
|
|
9eb3d3a673 | ||
|
|
6e642f628f | ||
|
|
0159bbe7f2 | ||
|
|
476276bf98 | ||
|
|
d82fd09f99 | ||
|
|
2961d73391 | ||
|
|
eb1cb3649c | ||
|
|
b384fe17ba | ||
|
|
ccb1df45d1 | ||
|
|
5a736647a1 | ||
|
|
918b468a41 | ||
|
|
234761dc05 | ||
|
|
fa1cb5d153 | ||
|
|
e998cf1216 | ||
|
|
2bbbf928ae | ||
|
|
fa06a1eadf | ||
|
|
5eea9e34e7 | ||
|
|
75b5e5376d | ||
|
|
be507421b1 | ||
|
|
5d943d4b7f | ||
|
|
67951b1f2b | ||
|
|
c104cf5dc0 | ||
|
|
4fb4c6ed94 | ||
|
|
df5a794b3d | ||
|
|
c01bc907ed | ||
|
|
222733049e | ||
|
|
fa9db3c8fa | ||
|
|
18a8a22c65 | ||
|
|
425f05e29d | ||
|
|
90fca23920 | ||
|
|
0d3d3bbb40 | ||
|
|
98ce947a6c | ||
|
|
2ed9516172 | ||
|
|
4fc93ec115 | ||
|
|
61d6acdeb1 | ||
|
|
f98ccd1e39 | ||
|
|
7f0a981b2e | ||
|
|
2e7a11c409 | ||
|
|
9ddf153e00 | ||
|
|
f8d95a85df | ||
|
|
59e9d284e9 | ||
|
|
4ce4c767e2 | ||
|
|
a0e6af1e53 | ||
|
|
ef77c9d60b | ||
|
|
8fee77b04b | ||
|
|
b807fc9c54 |
@@ -43,7 +43,7 @@ Note that before a PR will be accepted, you must ensure:
|
|||||||
1. In a new branch, create a new Lerna package:
|
1. In a new branch, create a new Lerna package:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ npm run create-package new-package
|
$ npm run new-package [name]
|
||||||
```
|
```
|
||||||
|
|
||||||
This will ask you some questions about the new package. Start with `0.0.0` as the first version (look generally at some of the other packages for how the package.json is structured).
|
This will ask you some questions about the new package. Start with `0.0.0` as the first version (look generally at some of the other packages for how the package.json is structured).
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
run: npm run bootstrap
|
run: npm run bootstrap
|
||||||
|
|
||||||
- name: audit tools (without allow-list)
|
- name: audit tools (without allow-list)
|
||||||
run: npm audit --audit-level=moderate
|
run: npm audit --audit-level=moderate --omit dev
|
||||||
|
|
||||||
- name: audit packages
|
- name: audit packages
|
||||||
run: npm run audit-all
|
run: npm run audit-all
|
||||||
|
|||||||
@@ -1,15 +1,30 @@
|
|||||||
name: Publish NPM
|
name: Publish NPM
|
||||||
|
|
||||||
|
run-name: Publish NPM - ${{ github.event.inputs.package }}
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
package:
|
package:
|
||||||
|
type: choice
|
||||||
required: true
|
required: true
|
||||||
description: 'core, artifact, cache, exec, github, glob, http-client, io, tool-cache, attest'
|
description: 'Which package to release'
|
||||||
|
options:
|
||||||
|
- artifact
|
||||||
|
- attest
|
||||||
|
- cache
|
||||||
|
- core
|
||||||
|
- exec
|
||||||
|
- github
|
||||||
|
- glob
|
||||||
|
- http-client
|
||||||
|
- io
|
||||||
|
- tool-cache
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest-large
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: setup repo
|
- name: setup repo
|
||||||
@@ -46,7 +61,7 @@ jobs:
|
|||||||
path: packages/${{ github.event.inputs.package }}/*.tgz
|
path: packages/${{ github.event.inputs.package }}/*.tgz
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest-large
|
||||||
needs: test
|
needs: test
|
||||||
environment: npm-publish
|
environment: npm-publish
|
||||||
permissions:
|
permissions:
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
runs-on: [ubuntu-latest, macos-latest, windows-latest]
|
runs-on: [ubuntu-latest, macos-latest-large, windows-latest]
|
||||||
|
|
||||||
|
# Node 18 is the current default Node version in hosted runners, so users may still use the toolkit with it when running tests (see https://github.com/actions/toolkit/issues/1841)
|
||||||
|
# Node 20 is the currently support Node version for actions - https://docs.github.com/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#runsusing-for-javascript-actions
|
||||||
|
node-version: [18.x, 20.x]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
||||||
runs-on: ${{ matrix.runs-on }}
|
runs-on: ${{ matrix.runs-on }}
|
||||||
@@ -25,10 +29,10 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set Node.js 20.x
|
- name: Set up Node ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|||||||
Generated
+3507
-1895
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -13,7 +13,7 @@
|
|||||||
"lint": "eslint packages/**/*.ts",
|
"lint": "eslint packages/**/*.ts",
|
||||||
"lint-fix": "eslint packages/**/*.ts --fix",
|
"lint-fix": "eslint packages/**/*.ts --fix",
|
||||||
"new-package": "scripts/create-package",
|
"new-package": "scripts/create-package",
|
||||||
"test": "jest --testTimeout 60000"
|
"test": "jest --testTimeout 70000"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.5.4",
|
"@types/jest": "^29.5.4",
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"flow-bin": "^0.115.0",
|
"flow-bin": "^0.115.0",
|
||||||
"jest": "^29.6.4",
|
"jest": "^29.6.4",
|
||||||
"lerna": "^7.1.4",
|
"lerna": "^6.4.1",
|
||||||
"nx": "16.6.0",
|
"nx": "16.6.0",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
"ts-jest": "^29.1.1",
|
"ts-jest": "^29.1.1",
|
||||||
|
|||||||
@@ -41,3 +41,4 @@ Any easy way to test changes for the official upload/download actions is to fork
|
|||||||
1. In the locally cloned fork, link to your local toolkit changes: `npm link @actions/artifact`
|
1. In the locally cloned fork, link to your local toolkit changes: `npm link @actions/artifact`
|
||||||
2. Then, compile your changes with: `npm run release`. The local `dist/index.js` should be updated with your changes.
|
2. Then, compile your changes with: `npm run release`. The local `dist/index.js` should be updated with your changes.
|
||||||
3. Commit and push to your fork, you can then test with a `uses:` in your workflow pointed at your fork.
|
3. Commit and push to your fork, you can then test with a `uses:` in your workflow pointed at your fork.
|
||||||
|
4. The format for the above is `<username>/<repository-name>/@<ref>`, i.e. `me/myrepo/@HEAD`
|
||||||
|
|||||||
@@ -1,5 +1,56 @@
|
|||||||
# @actions/artifact Releases
|
# @actions/artifact Releases
|
||||||
|
|
||||||
|
### 2.3.1
|
||||||
|
|
||||||
|
- Fix comment typo on expectedHash. [#1986](https://github.com/actions/toolkit/pull/1986)
|
||||||
|
|
||||||
|
### 2.3.0
|
||||||
|
|
||||||
|
- Allow ArtifactClient to perform digest comparisons, if supplied. [#1975](https://github.com/actions/toolkit/pull/1975)
|
||||||
|
|
||||||
|
### 2.2.2
|
||||||
|
|
||||||
|
- Default concurrency to 5 for uploading artifacts [#1962](https://github.com/actions/toolkit/pull/1962
|
||||||
|
|
||||||
|
### 2.2.1
|
||||||
|
|
||||||
|
- Add `ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY` and `ACTIONS_ARTIFACT_UPLOAD_TIMEOUT_MS` environment variables [#1928](https://github.com/actions/toolkit/pull/1928)
|
||||||
|
|
||||||
|
### 2.2.0
|
||||||
|
|
||||||
|
- Return artifact digest on upload [#1896](https://github.com/actions/toolkit/pull/1896)
|
||||||
|
|
||||||
|
### 2.1.11
|
||||||
|
|
||||||
|
- Fixed a bug with relative symlinks resolution [#1844](https://github.com/actions/toolkit/pull/1844)
|
||||||
|
- Use native `crypto` [#1815](https://github.com/actions/toolkit/pull/1815)
|
||||||
|
|
||||||
|
### 2.1.10
|
||||||
|
|
||||||
|
- Fixed a regression with symlinks not being automatically resolved [#1830](https://github.com/actions/toolkit/pull/1830)
|
||||||
|
- Fixed a regression with chunk timeout [#1786](https://github.com/actions/toolkit/pull/1786)
|
||||||
|
|
||||||
|
### 2.1.9
|
||||||
|
|
||||||
|
- Fixed artifact upload chunk timeout logic [#1774](https://github.com/actions/toolkit/pull/1774)
|
||||||
|
- Use lazy stream to prevent issues with open file limits [#1771](https://github.com/actions/toolkit/pull/1771)
|
||||||
|
|
||||||
|
### 2.1.8
|
||||||
|
|
||||||
|
- Allows `*.localhost` domains for hostname checks for local development.
|
||||||
|
|
||||||
|
### 2.1.7
|
||||||
|
|
||||||
|
- Update unzip-stream dependency and reverted to using `unzip.Extract()`
|
||||||
|
|
||||||
|
### 2.1.6
|
||||||
|
|
||||||
|
- Will retry on invalid request responses.
|
||||||
|
|
||||||
|
### 2.1.5
|
||||||
|
|
||||||
|
- Bumped `archiver` dependency to 7.0.1
|
||||||
|
|
||||||
### 2.1.4
|
### 2.1.4
|
||||||
|
|
||||||
- Adds info-level logging for zip extraction
|
- Adds info-level logging for zip extraction
|
||||||
@@ -11,9 +62,9 @@
|
|||||||
### 2.1.2
|
### 2.1.2
|
||||||
|
|
||||||
- Updated the stream extract functionality to use `unzip.Parse()` instead of `unzip.Extract()` for greater control of unzipping artifacts
|
- Updated the stream extract functionality to use `unzip.Parse()` instead of `unzip.Extract()` for greater control of unzipping artifacts
|
||||||
|
|
||||||
### 2.1.1
|
### 2.1.1
|
||||||
|
|
||||||
- Updated `isGhes` check to include `.ghe.com` and `.ghe.localhost` as accepted hosts
|
- Updated `isGhes` check to include `.ghe.com` and `.ghe.localhost` as accepted hosts
|
||||||
|
|
||||||
### 2.1.0
|
### 2.1.0
|
||||||
|
|||||||
@@ -116,6 +116,54 @@ describe('artifact-http-client', () => {
|
|||||||
expect(mockPost).toHaveBeenCalledTimes(2)
|
expect(mockPost).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should retry if invalid body response', async () => {
|
||||||
|
const mockPost = jest
|
||||||
|
.fn(() => {
|
||||||
|
const msgSucceeded = new http.IncomingMessage(new net.Socket())
|
||||||
|
msgSucceeded.statusCode = 200
|
||||||
|
return {
|
||||||
|
message: msgSucceeded,
|
||||||
|
readBody: async () => {
|
||||||
|
return Promise.resolve(
|
||||||
|
`{"ok": true, "signedUploadUrl": "http://localhost:8080/upload"}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
const msgFailed = new http.IncomingMessage(new net.Socket())
|
||||||
|
msgFailed.statusCode = 502
|
||||||
|
msgFailed.statusMessage = 'Bad Gateway'
|
||||||
|
return {
|
||||||
|
message: msgFailed,
|
||||||
|
readBody: async () => {
|
||||||
|
return Promise.resolve('💥')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const mockHttpClient = (
|
||||||
|
HttpClient as unknown as jest.Mock
|
||||||
|
).mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
post: mockPost
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const client = internalArtifactTwirpClient(clientOptions)
|
||||||
|
const artifact = await client.CreateArtifact({
|
||||||
|
workflowRunBackendId: '1234',
|
||||||
|
workflowJobRunBackendId: '5678',
|
||||||
|
name: 'artifact',
|
||||||
|
version: 4
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(mockHttpClient).toHaveBeenCalledTimes(1)
|
||||||
|
expect(artifact).toBeDefined()
|
||||||
|
expect(artifact.ok).toBe(true)
|
||||||
|
expect(artifact.signedUploadUrl).toBe('http://localhost:8080/upload')
|
||||||
|
expect(mockPost).toHaveBeenCalledTimes(2)
|
||||||
|
})
|
||||||
|
|
||||||
it('should fail if the request fails 5 times', async () => {
|
it('should fail if the request fails 5 times', async () => {
|
||||||
const mockPost = jest.fn(() => {
|
const mockPost = jest.fn(() => {
|
||||||
const msgFailed = new http.IncomingMessage(new net.Socket())
|
const msgFailed = new http.IncomingMessage(new net.Socket())
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import * as config from '../src/internal/shared/config'
|
import * as config from '../src/internal/shared/config'
|
||||||
|
import os from 'os'
|
||||||
|
|
||||||
|
// Mock the 'os' module
|
||||||
|
jest.mock('os', () => ({
|
||||||
|
cpus: jest.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.resetModules()
|
jest.resetModules()
|
||||||
@@ -20,8 +26,78 @@ describe('isGhes', () => {
|
|||||||
expect(config.isGhes()).toBe(false)
|
expect(config.isGhes()).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should return false when the request domain ends with .localhost', () => {
|
||||||
|
process.env.GITHUB_SERVER_URL = 'https://github.localhost'
|
||||||
|
expect(config.isGhes()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
it('should return false when the request domain is specific to an enterprise', () => {
|
it('should return false when the request domain is specific to an enterprise', () => {
|
||||||
process.env.GITHUB_SERVER_URL = 'https://my-enterprise.github.com'
|
process.env.GITHUB_SERVER_URL = 'https://my-enterprise.github.com'
|
||||||
expect(config.isGhes()).toBe(true)
|
expect(config.isGhes()).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('uploadChunkTimeoutEnv', () => {
|
||||||
|
it('should return default 300000 when no env set', () => {
|
||||||
|
expect(config.getUploadChunkTimeout()).toBe(300000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return value set in ACTIONS_ARTIFACT_UPLOAD_TIMEOUT_MS', () => {
|
||||||
|
process.env.ACTIONS_ARTIFACT_UPLOAD_TIMEOUT_MS = '150000'
|
||||||
|
expect(config.getUploadChunkTimeout()).toBe(150000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw if value set in ACTIONS_ARTIFACT_UPLOAD_TIMEOUT_MS is invalid', () => {
|
||||||
|
process.env.ACTIONS_ARTIFACT_UPLOAD_TIMEOUT_MS = 'abc'
|
||||||
|
expect(() => {
|
||||||
|
config.getUploadChunkTimeout()
|
||||||
|
}).toThrow()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('uploadConcurrencyEnv', () => {
|
||||||
|
it('Concurrency default to 5', () => {
|
||||||
|
;(os.cpus as jest.Mock).mockReturnValue(new Array(4))
|
||||||
|
expect(config.getConcurrency()).toBe(5)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Concurrency max out at 300 on systems with many CPUs', () => {
|
||||||
|
;(os.cpus as jest.Mock).mockReturnValue(new Array(32))
|
||||||
|
process.env.ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY = '301'
|
||||||
|
expect(config.getConcurrency()).toBe(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Concurrency can be set to 32 when cpu num is <= 4', () => {
|
||||||
|
;(os.cpus as jest.Mock).mockReturnValue(new Array(4))
|
||||||
|
process.env.ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY = '32'
|
||||||
|
expect(config.getConcurrency()).toBe(32)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Concurrency can be set 16 * num of cpu when cpu num is > 4', () => {
|
||||||
|
;(os.cpus as jest.Mock).mockReturnValue(new Array(6))
|
||||||
|
process.env.ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY = '96'
|
||||||
|
expect(config.getConcurrency()).toBe(96)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Concurrency can be overridden by env var ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY', () => {
|
||||||
|
;(os.cpus as jest.Mock).mockReturnValue(new Array(4))
|
||||||
|
process.env.ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY = '10'
|
||||||
|
expect(config.getConcurrency()).toBe(10)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw with invalid value of ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY', () => {
|
||||||
|
;(os.cpus as jest.Mock).mockReturnValue(new Array(4))
|
||||||
|
process.env.ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY = 'abc'
|
||||||
|
expect(() => {
|
||||||
|
config.getConcurrency()
|
||||||
|
}).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw if ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY is < 1', () => {
|
||||||
|
;(os.cpus as jest.Mock).mockReturnValue(new Array(4))
|
||||||
|
process.env.ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY = '0'
|
||||||
|
expect(() => {
|
||||||
|
config.getConcurrency()
|
||||||
|
}).toThrow()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -200,14 +200,12 @@ describe('download-artifact', () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
await expect(
|
const response = await downloadArtifactPublic(
|
||||||
downloadArtifactPublic(
|
fixtures.artifactID,
|
||||||
fixtures.artifactID,
|
fixtures.repositoryOwner,
|
||||||
fixtures.repositoryOwner,
|
fixtures.repositoryName,
|
||||||
fixtures.repositoryName,
|
fixtures.token
|
||||||
fixtures.token
|
)
|
||||||
)
|
|
||||||
).rejects.toBeInstanceOf(Error)
|
|
||||||
|
|
||||||
expect(downloadArtifactMock).toHaveBeenCalledWith({
|
expect(downloadArtifactMock).toHaveBeenCalledWith({
|
||||||
owner: fixtures.repositoryOwner,
|
owner: fixtures.repositoryOwner,
|
||||||
@@ -223,6 +221,16 @@ describe('download-artifact', () => {
|
|||||||
expect(mockGetArtifactMalicious).toHaveBeenCalledWith(
|
expect(mockGetArtifactMalicious).toHaveBeenCalledWith(
|
||||||
fixtures.blobStorageUrl
|
fixtures.blobStorageUrl
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ensure path traversal was not possible
|
||||||
|
expect(
|
||||||
|
fs.existsSync(path.join(fixtures.workspaceDir, 'x/etc/hosts'))
|
||||||
|
).toBe(true)
|
||||||
|
expect(
|
||||||
|
fs.existsSync(path.join(fixtures.workspaceDir, 'y/etc/hosts'))
|
||||||
|
).toBe(true)
|
||||||
|
|
||||||
|
expect(response.downloadPath).toBe(fixtures.workspaceDir)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should successfully download an artifact to user defined path', async () => {
|
it('should successfully download an artifact to user defined path', async () => {
|
||||||
@@ -311,14 +319,6 @@ describe('download-artifact', () => {
|
|||||||
|
|
||||||
const mockGet = jest.fn(async () => {
|
const mockGet = jest.fn(async () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// Resolve with a 200 status code immediately
|
|
||||||
resolve({
|
|
||||||
message: msg,
|
|
||||||
readBody: async () => {
|
|
||||||
return Promise.resolve(`{"ok": true}`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Reject with an error after 31 seconds
|
// Reject with an error after 31 seconds
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
reject(new Error('Request timeout'))
|
reject(new Error('Request timeout'))
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as github from '@actions/github'
|
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 type {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/parameters-and-response-types'
|
||||||
import {
|
import {
|
||||||
listArtifactsInternal,
|
listArtifactsInternal,
|
||||||
@@ -10,13 +9,13 @@ import {ArtifactServiceClientJSON, Timestamp} from '../src/generated'
|
|||||||
import * as util from '../src/internal/shared/util'
|
import * as util from '../src/internal/shared/util'
|
||||||
import {noopLogs} from './common'
|
import {noopLogs} from './common'
|
||||||
import {Artifact} from '../src/internal/shared/interfaces'
|
import {Artifact} from '../src/internal/shared/interfaces'
|
||||||
|
import {RequestInterface} from '@octokit/types'
|
||||||
|
|
||||||
type MockedListWorkflowRunArtifacts = jest.MockedFunction<
|
type MockedRequest = jest.MockedFunction<RequestInterface<object>>
|
||||||
RestEndpointMethods['actions']['listWorkflowRunArtifacts']
|
|
||||||
>
|
|
||||||
|
|
||||||
jest.mock('@actions/github', () => ({
|
jest.mock('@actions/github', () => ({
|
||||||
getOctokit: jest.fn().mockReturnValue({
|
getOctokit: jest.fn().mockReturnValue({
|
||||||
|
request: jest.fn(),
|
||||||
rest: {
|
rest: {
|
||||||
actions: {
|
actions: {
|
||||||
listWorkflowRunArtifacts: jest.fn()
|
listWorkflowRunArtifacts: jest.fn()
|
||||||
@@ -81,10 +80,10 @@ describe('list-artifact', () => {
|
|||||||
|
|
||||||
describe('public', () => {
|
describe('public', () => {
|
||||||
it('should return a list of artifacts', async () => {
|
it('should return a list of artifacts', async () => {
|
||||||
const mockListArtifacts = github.getOctokit(fixtures.token).rest.actions
|
const mockRequest = github.getOctokit(fixtures.token)
|
||||||
.listWorkflowRunArtifacts as MockedListWorkflowRunArtifacts
|
.request as MockedRequest
|
||||||
|
|
||||||
mockListArtifacts.mockResolvedValueOnce({
|
mockRequest.mockResolvedValueOnce({
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {},
|
headers: {},
|
||||||
url: '',
|
url: '',
|
||||||
@@ -105,10 +104,10 @@ describe('list-artifact', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should return the latest artifact when latest is specified', async () => {
|
it('should return the latest artifact when latest is specified', async () => {
|
||||||
const mockListArtifacts = github.getOctokit(fixtures.token).rest.actions
|
const mockRequest = github.getOctokit(fixtures.token)
|
||||||
.listWorkflowRunArtifacts as MockedListWorkflowRunArtifacts
|
.request as MockedRequest
|
||||||
|
|
||||||
mockListArtifacts.mockResolvedValueOnce({
|
mockRequest.mockResolvedValueOnce({
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {},
|
headers: {},
|
||||||
url: '',
|
url: '',
|
||||||
@@ -129,10 +128,10 @@ describe('list-artifact', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('can return empty artifacts', async () => {
|
it('can return empty artifacts', async () => {
|
||||||
const mockListArtifacts = github.getOctokit(fixtures.token).rest.actions
|
const mockRequest = github.getOctokit(fixtures.token)
|
||||||
.listWorkflowRunArtifacts as MockedListWorkflowRunArtifacts
|
.request as MockedRequest
|
||||||
|
|
||||||
mockListArtifacts.mockResolvedValueOnce({
|
mockRequest.mockResolvedValueOnce({
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {},
|
headers: {},
|
||||||
url: '',
|
url: '',
|
||||||
@@ -156,10 +155,10 @@ describe('list-artifact', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should fail if non-200 response', async () => {
|
it('should fail if non-200 response', async () => {
|
||||||
const mockListArtifacts = github.getOctokit(fixtures.token).rest.actions
|
const mockRequest = github.getOctokit(fixtures.token)
|
||||||
.listWorkflowRunArtifacts as MockedListWorkflowRunArtifacts
|
.request as MockedRequest
|
||||||
|
|
||||||
mockListArtifacts.mockRejectedValue(new Error('boom'))
|
mockRequest.mockRejectedValueOnce(new Error('boom'))
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
listArtifactsPublic(
|
listArtifactsPublic(
|
||||||
|
|||||||
@@ -1,257 +1,173 @@
|
|||||||
import * as uploadZipSpecification from '../src/internal/upload/upload-zip-specification'
|
import * as uploadZipSpecification from '../src/internal/upload/upload-zip-specification'
|
||||||
import * as zip from '../src/internal/upload/zip'
|
import * as zip from '../src/internal/upload/zip'
|
||||||
import * as util from '../src/internal/shared/util'
|
import * as util from '../src/internal/shared/util'
|
||||||
import * as retention from '../src/internal/upload/retention'
|
|
||||||
import * as config from '../src/internal/shared/config'
|
import * as config from '../src/internal/shared/config'
|
||||||
import {Timestamp, ArtifactServiceClientJSON} from '../src/generated'
|
import {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'
|
import {FilesNotFoundError} from '../src/internal/shared/errors'
|
||||||
|
import {BlockBlobUploadStreamOptions} from '@azure/storage-blob'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
import * as path from 'path'
|
||||||
|
import unzip from 'unzip-stream'
|
||||||
|
|
||||||
|
const uploadStreamMock = jest.fn()
|
||||||
|
const blockBlobClientMock = jest.fn().mockImplementation(() => ({
|
||||||
|
uploadStream: uploadStreamMock
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.mock('@azure/storage-blob', () => ({
|
||||||
|
BlobClient: jest.fn().mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
getBlockBlobClient: blockBlobClientMock
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
const fixtures = {
|
||||||
|
uploadDirectory: path.join(__dirname, '_temp', 'plz-upload'),
|
||||||
|
files: [
|
||||||
|
{name: 'file1.txt', content: 'test 1 file content'},
|
||||||
|
{name: 'file2.txt', content: 'test 2 file content'},
|
||||||
|
{name: 'file3.txt', content: 'test 3 file content'},
|
||||||
|
{
|
||||||
|
name: 'real.txt',
|
||||||
|
content: 'from a symlink'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'relative.txt',
|
||||||
|
content: 'from a symlink',
|
||||||
|
symlink: 'real.txt',
|
||||||
|
relative: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'absolute.txt',
|
||||||
|
content: 'from a symlink',
|
||||||
|
symlink: 'real.txt',
|
||||||
|
relative: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
backendIDs: {
|
||||||
|
workflowRunBackendId: '67dbcc20-e851-4452-a7c3-2cc0d2e0ec67',
|
||||||
|
workflowJobRunBackendId: '5f49179d-3386-4c38-85f7-00f8138facd0'
|
||||||
|
},
|
||||||
|
runtimeToken: 'test-token',
|
||||||
|
resultsServiceURL: 'http://results.local',
|
||||||
|
inputs: {
|
||||||
|
artifactName: 'test-artifact',
|
||||||
|
files: [
|
||||||
|
'/home/user/files/plz-upload/file1.txt',
|
||||||
|
'/home/user/files/plz-upload/file2.txt',
|
||||||
|
'/home/user/files/plz-upload/dir/file3.txt'
|
||||||
|
],
|
||||||
|
rootDirectory: '/home/user/files/plz-upload'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe('upload-artifact', () => {
|
describe('upload-artifact', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
fs.mkdirSync(fixtures.uploadDirectory, {
|
||||||
|
recursive: true
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const file of fixtures.files) {
|
||||||
|
if (file.symlink) {
|
||||||
|
let symlinkPath = file.symlink
|
||||||
|
if (!file.relative) {
|
||||||
|
symlinkPath = path.join(fixtures.uploadDirectory, file.symlink)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(path.join(fixtures.uploadDirectory, file.name))) {
|
||||||
|
fs.symlinkSync(
|
||||||
|
symlinkPath,
|
||||||
|
path.join(fixtures.uploadDirectory, file.name),
|
||||||
|
'file'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(fixtures.uploadDirectory, file.name),
|
||||||
|
file.content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
noopLogs()
|
noopLogs()
|
||||||
|
jest
|
||||||
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
||||||
|
.mockReturnValue()
|
||||||
|
jest
|
||||||
|
.spyOn(util, 'getBackendIdsFromToken')
|
||||||
|
.mockReturnValue(fixtures.backendIDs)
|
||||||
|
jest
|
||||||
|
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
||||||
|
.mockReturnValue(
|
||||||
|
fixtures.files.map(file => ({
|
||||||
|
sourcePath: path.join(fixtures.uploadDirectory, file.name),
|
||||||
|
destinationPath: file.name,
|
||||||
|
stats: new fs.Stats()
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
jest.spyOn(config, 'getRuntimeToken').mockReturnValue(fixtures.runtimeToken)
|
||||||
|
jest
|
||||||
|
.spyOn(config, 'getResultsServiceUrl')
|
||||||
|
.mockReturnValue(fixtures.resultsServiceURL)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.restoreAllMocks()
|
jest.restoreAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should successfully upload an artifact', () => {
|
it('should reject if there are no files to upload', async () => {
|
||||||
const mockDate = new Date('2020-01-01')
|
|
||||||
jest
|
|
||||||
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
|
||||||
.mockReturnValue()
|
|
||||||
jest
|
|
||||||
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
|
||||||
.mockReturnValue([
|
|
||||||
{
|
|
||||||
sourcePath: '/home/user/files/plz-upload/file1.txt',
|
|
||||||
destinationPath: 'file1.txt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sourcePath: '/home/user/files/plz-upload/file2.txt',
|
|
||||||
destinationPath: 'file2.txt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
|
|
||||||
destinationPath: 'dir/file3.txt'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(zip, 'createZipUploadStream')
|
|
||||||
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
|
|
||||||
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
|
|
||||||
workflowRunBackendId: '1234',
|
|
||||||
workflowJobRunBackendId: '5678'
|
|
||||||
})
|
|
||||||
jest
|
|
||||||
.spyOn(retention, 'getExpiration')
|
|
||||||
.mockReturnValue(Timestamp.fromDate(mockDate))
|
|
||||||
jest
|
|
||||||
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
|
||||||
.mockReturnValue(
|
|
||||||
Promise.resolve({
|
|
||||||
ok: true,
|
|
||||||
signedUploadUrl: 'https://signed-upload-url.com'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
jest.spyOn(blobUpload, 'uploadZipToBlobStorage').mockReturnValue(
|
|
||||||
Promise.resolve({
|
|
||||||
uploadSize: 1234,
|
|
||||||
sha256Hash: 'test-sha256-hash'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
jest
|
|
||||||
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
|
|
||||||
.mockReturnValue(Promise.resolve({ok: true, artifactId: '1'}))
|
|
||||||
|
|
||||||
// ArtifactHttpClient mocks
|
|
||||||
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
|
||||||
jest
|
|
||||||
.spyOn(config, 'getResultsServiceUrl')
|
|
||||||
.mockReturnValue('https://test-url.com')
|
|
||||||
|
|
||||||
const uploadResp = uploadArtifact(
|
|
||||||
'test-artifact',
|
|
||||||
[
|
|
||||||
'/home/user/files/plz-upload/file1.txt',
|
|
||||||
'/home/user/files/plz-upload/file2.txt',
|
|
||||||
'/home/user/files/plz-upload/dir/file3.txt'
|
|
||||||
],
|
|
||||||
'/home/user/files/plz-upload'
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(uploadResp).resolves.toEqual({size: 1234, id: 1})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should throw an error if the root directory is invalid', () => {
|
|
||||||
jest
|
|
||||||
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
|
||||||
.mockImplementation(() => {
|
|
||||||
throw new Error('Invalid root directory')
|
|
||||||
})
|
|
||||||
|
|
||||||
const uploadResp = uploadArtifact(
|
|
||||||
'test-artifact',
|
|
||||||
[
|
|
||||||
'/home/user/files/plz-upload/file1.txt',
|
|
||||||
'/home/user/files/plz-upload/file2.txt',
|
|
||||||
'/home/user/files/plz-upload/dir/file3.txt'
|
|
||||||
],
|
|
||||||
'/home/user/files/plz-upload'
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(uploadResp).rejects.toThrow('Invalid root directory')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should reject if there are no files to upload', () => {
|
|
||||||
jest
|
|
||||||
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
|
||||||
.mockReturnValue()
|
|
||||||
jest
|
jest
|
||||||
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
||||||
|
.mockClear()
|
||||||
.mockReturnValue([])
|
.mockReturnValue([])
|
||||||
|
|
||||||
const uploadResp = uploadArtifact(
|
const uploadResp = uploadArtifact(
|
||||||
'test-artifact',
|
fixtures.inputs.artifactName,
|
||||||
[
|
fixtures.inputs.files,
|
||||||
'/home/user/files/plz-upload/file1.txt',
|
fixtures.inputs.rootDirectory
|
||||||
'/home/user/files/plz-upload/file2.txt',
|
|
||||||
'/home/user/files/plz-upload/dir/file3.txt'
|
|
||||||
],
|
|
||||||
'/home/user/files/plz-upload'
|
|
||||||
)
|
)
|
||||||
expect(uploadResp).rejects.toThrowError(FilesNotFoundError)
|
await expect(uploadResp).rejects.toThrowError(FilesNotFoundError)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should reject if no backend IDs are found', () => {
|
it('should reject if no backend IDs are found', async () => {
|
||||||
jest
|
jest.spyOn(util, 'getBackendIdsFromToken').mockRestore()
|
||||||
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
|
||||||
.mockReturnValue()
|
|
||||||
jest
|
|
||||||
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
|
||||||
.mockReturnValue([
|
|
||||||
{
|
|
||||||
sourcePath: '/home/user/files/plz-upload/file1.txt',
|
|
||||||
destinationPath: 'file1.txt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sourcePath: '/home/user/files/plz-upload/file2.txt',
|
|
||||||
destinationPath: 'file2.txt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
|
|
||||||
destinationPath: 'dir/file3.txt'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(zip, 'createZipUploadStream')
|
|
||||||
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
|
|
||||||
|
|
||||||
const uploadResp = uploadArtifact(
|
const uploadResp = uploadArtifact(
|
||||||
'test-artifact',
|
fixtures.inputs.artifactName,
|
||||||
[
|
fixtures.inputs.files,
|
||||||
'/home/user/files/plz-upload/file1.txt',
|
fixtures.inputs.rootDirectory
|
||||||
'/home/user/files/plz-upload/file2.txt',
|
|
||||||
'/home/user/files/plz-upload/dir/file3.txt'
|
|
||||||
],
|
|
||||||
'/home/user/files/plz-upload'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(uploadResp).rejects.toThrow()
|
await expect(uploadResp).rejects.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return false if the creation request fails', () => {
|
it('should return false if the creation request fails', async () => {
|
||||||
const mockDate = new Date('2020-01-01')
|
|
||||||
jest
|
|
||||||
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
|
||||||
.mockReturnValue()
|
|
||||||
jest
|
|
||||||
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
|
||||||
.mockReturnValue([
|
|
||||||
{
|
|
||||||
sourcePath: '/home/user/files/plz-upload/file1.txt',
|
|
||||||
destinationPath: 'file1.txt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sourcePath: '/home/user/files/plz-upload/file2.txt',
|
|
||||||
destinationPath: 'file2.txt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
|
|
||||||
destinationPath: 'dir/file3.txt'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(zip, 'createZipUploadStream')
|
.spyOn(zip, 'createZipUploadStream')
|
||||||
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
|
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
|
||||||
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
|
|
||||||
workflowRunBackendId: '1234',
|
|
||||||
workflowJobRunBackendId: '5678'
|
|
||||||
})
|
|
||||||
jest
|
|
||||||
.spyOn(retention, 'getExpiration')
|
|
||||||
.mockReturnValue(Timestamp.fromDate(mockDate))
|
|
||||||
jest
|
jest
|
||||||
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
||||||
.mockReturnValue(Promise.resolve({ok: false, signedUploadUrl: ''}))
|
.mockReturnValue(Promise.resolve({ok: false, signedUploadUrl: ''}))
|
||||||
|
|
||||||
// ArtifactHttpClient mocks
|
|
||||||
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
|
||||||
jest
|
|
||||||
.spyOn(config, 'getResultsServiceUrl')
|
|
||||||
.mockReturnValue('https://test-url.com')
|
|
||||||
|
|
||||||
const uploadResp = uploadArtifact(
|
const uploadResp = uploadArtifact(
|
||||||
'test-artifact',
|
fixtures.inputs.artifactName,
|
||||||
[
|
fixtures.inputs.files,
|
||||||
'/home/user/files/plz-upload/file1.txt',
|
fixtures.inputs.rootDirectory
|
||||||
'/home/user/files/plz-upload/file2.txt',
|
|
||||||
'/home/user/files/plz-upload/dir/file3.txt'
|
|
||||||
],
|
|
||||||
'/home/user/files/plz-upload'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(uploadResp).rejects.toThrow()
|
await expect(uploadResp).rejects.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return false if blob storage upload is unsuccessful', () => {
|
it('should return false if blob storage upload is unsuccessful', async () => {
|
||||||
const mockDate = new Date('2020-01-01')
|
|
||||||
jest
|
|
||||||
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
|
||||||
.mockReturnValue()
|
|
||||||
jest
|
|
||||||
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
|
||||||
.mockReturnValue([
|
|
||||||
{
|
|
||||||
sourcePath: '/home/user/files/plz-upload/file1.txt',
|
|
||||||
destinationPath: 'file1.txt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sourcePath: '/home/user/files/plz-upload/file2.txt',
|
|
||||||
destinationPath: 'file2.txt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
|
|
||||||
destinationPath: 'dir/file3.txt'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(zip, 'createZipUploadStream')
|
.spyOn(zip, 'createZipUploadStream')
|
||||||
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
|
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
|
||||||
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
|
|
||||||
workflowRunBackendId: '1234',
|
|
||||||
workflowJobRunBackendId: '5678'
|
|
||||||
})
|
|
||||||
jest
|
|
||||||
.spyOn(retention, 'getExpiration')
|
|
||||||
.mockReturnValue(Timestamp.fromDate(mockDate))
|
|
||||||
jest
|
jest
|
||||||
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
||||||
.mockReturnValue(
|
.mockReturnValue(
|
||||||
@@ -264,57 +180,19 @@ describe('upload-artifact', () => {
|
|||||||
.spyOn(blobUpload, 'uploadZipToBlobStorage')
|
.spyOn(blobUpload, 'uploadZipToBlobStorage')
|
||||||
.mockReturnValue(Promise.reject(new Error('boom')))
|
.mockReturnValue(Promise.reject(new Error('boom')))
|
||||||
|
|
||||||
// ArtifactHttpClient mocks
|
|
||||||
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
|
||||||
jest
|
|
||||||
.spyOn(config, 'getResultsServiceUrl')
|
|
||||||
.mockReturnValue('https://test-url.com')
|
|
||||||
|
|
||||||
const uploadResp = uploadArtifact(
|
const uploadResp = uploadArtifact(
|
||||||
'test-artifact',
|
fixtures.inputs.artifactName,
|
||||||
[
|
fixtures.inputs.files,
|
||||||
'/home/user/files/plz-upload/file1.txt',
|
fixtures.inputs.rootDirectory
|
||||||
'/home/user/files/plz-upload/file2.txt',
|
|
||||||
'/home/user/files/plz-upload/dir/file3.txt'
|
|
||||||
],
|
|
||||||
'/home/user/files/plz-upload'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(uploadResp).rejects.toThrow()
|
await expect(uploadResp).rejects.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should reject if finalize artifact fails', () => {
|
it('should reject if finalize artifact fails', async () => {
|
||||||
const mockDate = new Date('2020-01-01')
|
|
||||||
jest
|
|
||||||
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
|
||||||
.mockReturnValue()
|
|
||||||
jest
|
|
||||||
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
|
||||||
.mockReturnValue([
|
|
||||||
{
|
|
||||||
sourcePath: '/home/user/files/plz-upload/file1.txt',
|
|
||||||
destinationPath: 'file1.txt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sourcePath: '/home/user/files/plz-upload/file2.txt',
|
|
||||||
destinationPath: 'file2.txt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
|
|
||||||
destinationPath: 'dir/file3.txt'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(zip, 'createZipUploadStream')
|
.spyOn(zip, 'createZipUploadStream')
|
||||||
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
|
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
|
||||||
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
|
|
||||||
workflowRunBackendId: '1234',
|
|
||||||
workflowJobRunBackendId: '5678'
|
|
||||||
})
|
|
||||||
jest
|
|
||||||
.spyOn(retention, 'getExpiration')
|
|
||||||
.mockReturnValue(Timestamp.fromDate(mockDate))
|
|
||||||
jest
|
jest
|
||||||
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
||||||
.mockReturnValue(
|
.mockReturnValue(
|
||||||
@@ -333,22 +211,163 @@ describe('upload-artifact', () => {
|
|||||||
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
|
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
|
||||||
.mockReturnValue(Promise.resolve({ok: false, artifactId: ''}))
|
.mockReturnValue(Promise.resolve({ok: false, artifactId: ''}))
|
||||||
|
|
||||||
// ArtifactHttpClient mocks
|
|
||||||
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
|
||||||
jest
|
|
||||||
.spyOn(config, 'getResultsServiceUrl')
|
|
||||||
.mockReturnValue('https://test-url.com')
|
|
||||||
|
|
||||||
const uploadResp = uploadArtifact(
|
const uploadResp = uploadArtifact(
|
||||||
'test-artifact',
|
fixtures.inputs.artifactName,
|
||||||
[
|
fixtures.inputs.files,
|
||||||
'/home/user/files/plz-upload/file1.txt',
|
fixtures.inputs.rootDirectory
|
||||||
'/home/user/files/plz-upload/file2.txt',
|
|
||||||
'/home/user/files/plz-upload/dir/file3.txt'
|
|
||||||
],
|
|
||||||
'/home/user/files/plz-upload'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(uploadResp).rejects.toThrow()
|
await expect(uploadResp).rejects.toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should successfully upload an artifact', async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
||||||
|
.mockRestore()
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
||||||
|
.mockReturnValue(
|
||||||
|
Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
signedUploadUrl: 'https://signed-upload-url.local'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
jest
|
||||||
|
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
|
||||||
|
.mockReturnValue(
|
||||||
|
Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
artifactId: '1'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
let loadedBytes = 0
|
||||||
|
const uploadedZip = path.join(
|
||||||
|
fixtures.uploadDirectory,
|
||||||
|
'..',
|
||||||
|
'uploaded.zip'
|
||||||
|
)
|
||||||
|
uploadStreamMock.mockImplementation(
|
||||||
|
async (
|
||||||
|
stream: NodeJS.ReadableStream,
|
||||||
|
bufferSize?: number,
|
||||||
|
maxConcurrency?: number,
|
||||||
|
options?: BlockBlobUploadStreamOptions
|
||||||
|
) => {
|
||||||
|
const {onProgress} = options || {}
|
||||||
|
|
||||||
|
if (fs.existsSync(uploadedZip)) {
|
||||||
|
fs.unlinkSync(uploadedZip)
|
||||||
|
}
|
||||||
|
const uploadedZipStream = fs.createWriteStream(uploadedZip)
|
||||||
|
|
||||||
|
onProgress?.({loadedBytes: 0})
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
stream.on('data', chunk => {
|
||||||
|
loadedBytes += chunk.length
|
||||||
|
uploadedZipStream.write(chunk)
|
||||||
|
onProgress?.({loadedBytes})
|
||||||
|
})
|
||||||
|
stream.on('end', () => {
|
||||||
|
onProgress?.({loadedBytes})
|
||||||
|
uploadedZipStream.end()
|
||||||
|
resolve({})
|
||||||
|
})
|
||||||
|
stream.on('error', err => {
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const {id, size, digest} = await uploadArtifact(
|
||||||
|
fixtures.inputs.artifactName,
|
||||||
|
fixtures.files.map(file =>
|
||||||
|
path.join(fixtures.uploadDirectory, file.name)
|
||||||
|
),
|
||||||
|
fixtures.uploadDirectory
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(id).toBe(1)
|
||||||
|
expect(size).toBe(loadedBytes)
|
||||||
|
expect(digest).toBeDefined()
|
||||||
|
expect(digest).toHaveLength(64)
|
||||||
|
|
||||||
|
const extractedDirectory = path.join(
|
||||||
|
fixtures.uploadDirectory,
|
||||||
|
'..',
|
||||||
|
'extracted'
|
||||||
|
)
|
||||||
|
if (fs.existsSync(extractedDirectory)) {
|
||||||
|
fs.rmdirSync(extractedDirectory, {recursive: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
const extract = new Promise((resolve, reject) => {
|
||||||
|
fs.createReadStream(uploadedZip)
|
||||||
|
.pipe(unzip.Extract({path: extractedDirectory}))
|
||||||
|
.on('close', () => {
|
||||||
|
resolve(true)
|
||||||
|
})
|
||||||
|
.on('error', err => {
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(extract).resolves.toBe(true)
|
||||||
|
for (const file of fixtures.files) {
|
||||||
|
const filePath = path.join(extractedDirectory, file.name)
|
||||||
|
expect(fs.existsSync(filePath)).toBe(true)
|
||||||
|
expect(fs.readFileSync(filePath, 'utf8')).toBe(file.content)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error uploading blob chunks get delayed', async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
||||||
|
.mockReturnValue(
|
||||||
|
Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
signedUploadUrl: 'https://signed-upload-url.local'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
jest
|
||||||
|
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
|
||||||
|
.mockReturnValue(
|
||||||
|
Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
artifactId: '1'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
jest
|
||||||
|
.spyOn(config, 'getResultsServiceUrl')
|
||||||
|
.mockReturnValue('https://results.local')
|
||||||
|
|
||||||
|
jest.spyOn(config, 'getUploadChunkTimeout').mockReturnValue(2_000)
|
||||||
|
|
||||||
|
uploadStreamMock.mockImplementation(
|
||||||
|
async (
|
||||||
|
stream: NodeJS.ReadableStream,
|
||||||
|
bufferSize?: number,
|
||||||
|
maxConcurrency?: number,
|
||||||
|
options?: BlockBlobUploadStreamOptions
|
||||||
|
) => {
|
||||||
|
const {onProgress, abortSignal} = options || {}
|
||||||
|
onProgress?.({loadedBytes: 0})
|
||||||
|
return new Promise(resolve => {
|
||||||
|
abortSignal?.addEventListener('abort', () => {
|
||||||
|
resolve({})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const uploadResp = uploadArtifact(
|
||||||
|
fixtures.inputs.artifactName,
|
||||||
|
fixtures.inputs.files,
|
||||||
|
fixtures.inputs.rootDirectory
|
||||||
|
)
|
||||||
|
|
||||||
|
await expect(uploadResp).rejects.toThrow('Upload progress stalled.')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -305,4 +305,22 @@ describe('Search', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Upload Specification - Includes symlinks', async () => {
|
||||||
|
const targetPath = path.join(root, 'link-dir', 'symlink-me.txt')
|
||||||
|
await fs.mkdir(path.dirname(targetPath), {recursive: true})
|
||||||
|
await fs.writeFile(targetPath, 'symlink file content')
|
||||||
|
|
||||||
|
const uploadPath = path.join(root, 'upload-dir', 'symlink.txt')
|
||||||
|
await fs.mkdir(path.dirname(uploadPath), {recursive: true})
|
||||||
|
await fs.symlink(targetPath, uploadPath, 'file')
|
||||||
|
|
||||||
|
const specifications = getUploadZipSpecification([uploadPath], root)
|
||||||
|
expect(specifications.length).toEqual(1)
|
||||||
|
expect(specifications[0].sourcePath).toEqual(uploadPath)
|
||||||
|
expect(specifications[0].destinationPath).toEqual(
|
||||||
|
path.join('/upload-dir', 'symlink.txt')
|
||||||
|
)
|
||||||
|
expect(specifications[0].stats.isSymbolicLink()).toBe(true)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -40,4 +40,4 @@
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/artifact.ts:7](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/artifact.ts#L7)
|
[src/artifact.ts:7](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/artifact.ts#L7)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ Error.constructor
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/errors.ts:24](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/errors.ts#L24)
|
[src/internal/shared/errors.ts:24](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/errors.ts#L24)
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ single DeleteArtifactResponse object
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/client.ts:248](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/client.ts#L248)
|
[src/internal/client.ts:248](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/client.ts#L248)
|
||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ single DownloadArtifactResponse object
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/client.ts:138](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/client.ts#L138)
|
[src/internal/client.ts:138](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/client.ts#L138)
|
||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ If there are multiple artifacts with the same name in the same workflow run this
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/client.ts:212](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/client.ts#L212)
|
[src/internal/client.ts:212](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/client.ts#L212)
|
||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ ListArtifactResponse object
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/client.ts:176](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/client.ts#L176)
|
[src/internal/client.ts:176](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/client.ts#L176)
|
||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
@@ -190,4 +190,4 @@ single UploadArtifactResponse object
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/client.ts:113](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/client.ts#L113)
|
[src/internal/client.ts:113](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/client.ts#L113)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ Error.constructor
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/errors.ts:4](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/errors.ts#L4)
|
[src/internal/shared/errors.ts:4](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/errors.ts#L4)
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ Error.constructor
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/errors.ts:2](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/errors.ts#L2)
|
[src/internal/shared/errors.ts:2](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/errors.ts#L2)
|
||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ Error.constructor
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/errors.ts:31](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/errors.ts#L31)
|
[src/internal/shared/errors.ts:31](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/errors.ts#L31)
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ Error.constructor
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/errors.ts:17](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/errors.ts#L17)
|
[src/internal/shared/errors.ts:17](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/errors.ts#L17)
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ Error.constructor
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/errors.ts:42](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/errors.ts#L42)
|
[src/internal/shared/errors.ts:42](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/errors.ts#L42)
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ Error.constructor
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/errors.ts:40](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/errors.ts#L40)
|
[src/internal/shared/errors.ts:40](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/errors.ts#L40)
|
||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
@@ -198,4 +198,4 @@ ___
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/errors.ts:49](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/errors.ts#L49)
|
[src/internal/shared/errors.ts:49](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/errors.ts#L49)
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ Error.constructor
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/errors.ts:62](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/errors.ts#L62)
|
[src/internal/shared/errors.ts:62](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/errors.ts#L62)
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
@@ -181,4 +181,4 @@ ___
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/errors.ts:68](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/errors.ts#L68)
|
[src/internal/shared/errors.ts:68](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/errors.ts#L68)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ The time when the artifact was created
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/interfaces.ts:123](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/interfaces.ts#L123)
|
[src/internal/shared/interfaces.ts:128](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L128)
|
||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ The ID of the artifact
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/interfaces.ts:113](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/interfaces.ts#L113)
|
[src/internal/shared/interfaces.ts:118](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L118)
|
||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ The name of the artifact
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/interfaces.ts:108](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/interfaces.ts#L108)
|
[src/internal/shared/interfaces.ts:113](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L113)
|
||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
@@ -59,4 +59,4 @@ The size of the artifact in bytes
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/interfaces.ts:118](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/interfaces.ts#L118)
|
[src/internal/shared/interfaces.ts:123](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L123)
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ single DeleteArtifactResponse object
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/client.ts:103](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/client.ts#L103)
|
[src/internal/client.ts:103](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/client.ts#L103)
|
||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ single DownloadArtifactResponse object
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/client.ts:89](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/client.ts#L89)
|
[src/internal/client.ts:89](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/client.ts#L89)
|
||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ If there are multiple artifacts with the same name in the same workflow run this
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/client.ts:75](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/client.ts#L75)
|
[src/internal/client.ts:75](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/client.ts#L75)
|
||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ ListArtifactResponse object
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/client.ts:57](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/client.ts#L57)
|
[src/internal/client.ts:57](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/client.ts#L57)
|
||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
@@ -156,4 +156,4 @@ single UploadArtifactResponse object
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/client.ts:40](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/client.ts#L40)
|
[src/internal/client.ts:40](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/client.ts#L40)
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ The id of the artifact that was deleted
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/interfaces.ts:158](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/interfaces.ts#L158)
|
[src/internal/shared/interfaces.ts:163](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L163)
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ Denotes where the artifact will be downloaded to. If not specified then the arti
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/interfaces.ts:98](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/interfaces.ts#L98)
|
[src/internal/shared/interfaces.ts:103](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L103)
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ The path where the artifact was downloaded to
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/interfaces.ts:88](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/interfaces.ts#L88)
|
[src/internal/shared/interfaces.ts:93](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L93)
|
||||||
|
|||||||
@@ -27,4 +27,4 @@ The criteria for finding Artifact(s) out of the scope of the current run.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/interfaces.ts:131](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/interfaces.ts#L131)
|
[src/internal/shared/interfaces.ts:136](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L136)
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ Metadata about the artifact that was found
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/interfaces.ts:57](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/interfaces.ts#L57)
|
[src/internal/shared/interfaces.ts:62](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L62)
|
||||||
|
|||||||
@@ -21,4 +21,4 @@ In the case of reruns, this can be useful to avoid duplicates
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/interfaces.ts:68](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/interfaces.ts#L68)
|
[src/internal/shared/interfaces.ts:73](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L73)
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ A list of artifacts that were found
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/interfaces.ts:78](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/interfaces.ts#L78)
|
[src/internal/shared/interfaces.ts:83](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L83)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ For large files that are not easily compressed, a value of 0 is recommended for
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/interfaces.ts:47](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/interfaces.ts#L47)
|
[src/internal/shared/interfaces.ts:52](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L52)
|
||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
@@ -52,4 +52,4 @@ input of 0 assumes default retention setting.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/interfaces.ts:36](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/interfaces.ts#L36)
|
[src/internal/shared/interfaces.ts:41](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L41)
|
||||||
|
|||||||
@@ -8,11 +8,24 @@ Response from the server when an artifact is uploaded
|
|||||||
|
|
||||||
### Properties
|
### Properties
|
||||||
|
|
||||||
|
- [digest](UploadArtifactResponse.md#digest)
|
||||||
- [id](UploadArtifactResponse.md#id)
|
- [id](UploadArtifactResponse.md#id)
|
||||||
- [size](UploadArtifactResponse.md#size)
|
- [size](UploadArtifactResponse.md#size)
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
|
### digest
|
||||||
|
|
||||||
|
• `Optional` **digest**: `string`
|
||||||
|
|
||||||
|
The SHA256 digest of the artifact that was created. Not provided if no artifact was uploaded
|
||||||
|
|
||||||
|
#### Defined in
|
||||||
|
|
||||||
|
[src/internal/shared/interfaces.ts:19](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L19)
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
### id
|
### id
|
||||||
|
|
||||||
• `Optional` **id**: `number`
|
• `Optional` **id**: `number`
|
||||||
@@ -22,7 +35,7 @@ This ID can be used as input to other APIs to download, delete or get more infor
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/interfaces.ts:14](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/interfaces.ts#L14)
|
[src/internal/shared/interfaces.ts:14](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L14)
|
||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
@@ -34,4 +47,4 @@ Total size of the artifact in bytes. Not provided if no artifact was uploaded
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[src/internal/shared/interfaces.ts:8](https://github.com/actions/toolkit/blob/daf23ba/packages/artifact/src/internal/shared/interfaces.ts#L8)
|
[src/internal/shared/interfaces.ts:8](https://github.com/actions/toolkit/blob/f522fdf/packages/artifact/src/internal/shared/interfaces.ts#L8)
|
||||||
|
|||||||
Generated
+539
-319
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/artifact",
|
"name": "@actions/artifact",
|
||||||
"version": "2.1.4",
|
"version": "2.3.1",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"description": "Actions artifact lib",
|
"description": "Actions artifact lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -49,10 +49,8 @@
|
|||||||
"@octokit/plugin-retry": "^3.0.9",
|
"@octokit/plugin-retry": "^3.0.9",
|
||||||
"@octokit/request-error": "^5.0.0",
|
"@octokit/request-error": "^5.0.0",
|
||||||
"@protobuf-ts/plugin": "^2.2.3-alpha.1",
|
"@protobuf-ts/plugin": "^2.2.3-alpha.1",
|
||||||
"archiver": "^5.3.1",
|
"archiver": "^7.0.1",
|
||||||
"crypto": "^1.0.1",
|
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"twirp-ts": "^2.5.0",
|
|
||||||
"unzip-stream": "^0.3.1"
|
"unzip-stream": "^0.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export * from './google/protobuf/timestamp'
|
export * from './google/protobuf/timestamp'
|
||||||
export * from './google/protobuf/wrappers'
|
export * from './google/protobuf/wrappers'
|
||||||
export * from './results/api/v1/artifact'
|
export * from './results/api/v1/artifact'
|
||||||
export * from './results/api/v1/artifact.twirp'
|
export * from './results/api/v1/artifact.twirp-client'
|
||||||
|
|||||||
@@ -15,6 +15,66 @@ import { MessageType } from "@protobuf-ts/runtime";
|
|||||||
import { Int64Value } from "../../../google/protobuf/wrappers";
|
import { Int64Value } from "../../../google/protobuf/wrappers";
|
||||||
import { StringValue } from "../../../google/protobuf/wrappers";
|
import { StringValue } from "../../../google/protobuf/wrappers";
|
||||||
import { Timestamp } from "../../../google/protobuf/timestamp";
|
import { Timestamp } from "../../../google/protobuf/timestamp";
|
||||||
|
/**
|
||||||
|
* @generated from protobuf message github.actions.results.api.v1.MigrateArtifactRequest
|
||||||
|
*/
|
||||||
|
export interface MigrateArtifactRequest {
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: string workflow_run_backend_id = 1;
|
||||||
|
*/
|
||||||
|
workflowRunBackendId: string;
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: string name = 2;
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: google.protobuf.Timestamp expires_at = 3;
|
||||||
|
*/
|
||||||
|
expiresAt?: Timestamp;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated from protobuf message github.actions.results.api.v1.MigrateArtifactResponse
|
||||||
|
*/
|
||||||
|
export interface MigrateArtifactResponse {
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: bool ok = 1;
|
||||||
|
*/
|
||||||
|
ok: boolean;
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: string signed_upload_url = 2;
|
||||||
|
*/
|
||||||
|
signedUploadUrl: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated from protobuf message github.actions.results.api.v1.FinalizeMigratedArtifactRequest
|
||||||
|
*/
|
||||||
|
export interface FinalizeMigratedArtifactRequest {
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: string workflow_run_backend_id = 1;
|
||||||
|
*/
|
||||||
|
workflowRunBackendId: string;
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: string name = 2;
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: int64 size = 3;
|
||||||
|
*/
|
||||||
|
size: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated from protobuf message github.actions.results.api.v1.FinalizeMigratedArtifactResponse
|
||||||
|
*/
|
||||||
|
export interface FinalizeMigratedArtifactResponse {
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: bool ok = 1;
|
||||||
|
*/
|
||||||
|
ok: boolean;
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: int64 artifact_id = 2;
|
||||||
|
*/
|
||||||
|
artifactId: string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @generated from protobuf message github.actions.results.api.v1.CreateArtifactRequest
|
* @generated from protobuf message github.actions.results.api.v1.CreateArtifactRequest
|
||||||
*/
|
*/
|
||||||
@@ -169,6 +229,12 @@ export interface ListArtifactsResponse_MonolithArtifact {
|
|||||||
* @generated from protobuf field: google.protobuf.Timestamp created_at = 6;
|
* @generated from protobuf field: google.protobuf.Timestamp created_at = 6;
|
||||||
*/
|
*/
|
||||||
createdAt?: Timestamp;
|
createdAt?: Timestamp;
|
||||||
|
/**
|
||||||
|
* The SHA-256 digest of the artifact, calculated on upload for upload-artifact v4 & newer
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: google.protobuf.StringValue digest = 7;
|
||||||
|
*/
|
||||||
|
digest?: StringValue;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @generated from protobuf message github.actions.results.api.v1.GetSignedArtifactURLRequest
|
* @generated from protobuf message github.actions.results.api.v1.GetSignedArtifactURLRequest
|
||||||
@@ -227,6 +293,236 @@ export interface DeleteArtifactResponse {
|
|||||||
artifactId: string;
|
artifactId: string;
|
||||||
}
|
}
|
||||||
// @generated message type with reflection information, may provide speed optimized methods
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class MigrateArtifactRequest$Type extends MessageType<MigrateArtifactRequest> {
|
||||||
|
constructor() {
|
||||||
|
super("github.actions.results.api.v1.MigrateArtifactRequest", [
|
||||||
|
{ no: 1, name: "workflow_run_backend_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 2, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 3, name: "expires_at", kind: "message", T: () => Timestamp }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<MigrateArtifactRequest>): MigrateArtifactRequest {
|
||||||
|
const message = { workflowRunBackendId: "", name: "" };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<MigrateArtifactRequest>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MigrateArtifactRequest): MigrateArtifactRequest {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* string workflow_run_backend_id */ 1:
|
||||||
|
message.workflowRunBackendId = reader.string();
|
||||||
|
break;
|
||||||
|
case /* string name */ 2:
|
||||||
|
message.name = reader.string();
|
||||||
|
break;
|
||||||
|
case /* google.protobuf.Timestamp expires_at */ 3:
|
||||||
|
message.expiresAt = Timestamp.internalBinaryRead(reader, reader.uint32(), options, message.expiresAt);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
let u = options.readUnknownField;
|
||||||
|
if (u === "throw")
|
||||||
|
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||||
|
let d = reader.skip(wireType);
|
||||||
|
if (u !== false)
|
||||||
|
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryWrite(message: MigrateArtifactRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* string workflow_run_backend_id = 1; */
|
||||||
|
if (message.workflowRunBackendId !== "")
|
||||||
|
writer.tag(1, WireType.LengthDelimited).string(message.workflowRunBackendId);
|
||||||
|
/* string name = 2; */
|
||||||
|
if (message.name !== "")
|
||||||
|
writer.tag(2, WireType.LengthDelimited).string(message.name);
|
||||||
|
/* google.protobuf.Timestamp expires_at = 3; */
|
||||||
|
if (message.expiresAt)
|
||||||
|
Timestamp.internalBinaryWrite(message.expiresAt, writer.tag(3, WireType.LengthDelimited).fork(), options).join();
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message github.actions.results.api.v1.MigrateArtifactRequest
|
||||||
|
*/
|
||||||
|
export const MigrateArtifactRequest = new MigrateArtifactRequest$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class MigrateArtifactResponse$Type extends MessageType<MigrateArtifactResponse> {
|
||||||
|
constructor() {
|
||||||
|
super("github.actions.results.api.v1.MigrateArtifactResponse", [
|
||||||
|
{ no: 1, name: "ok", kind: "scalar", T: 8 /*ScalarType.BOOL*/ },
|
||||||
|
{ no: 2, name: "signed_upload_url", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<MigrateArtifactResponse>): MigrateArtifactResponse {
|
||||||
|
const message = { ok: false, signedUploadUrl: "" };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<MigrateArtifactResponse>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MigrateArtifactResponse): MigrateArtifactResponse {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* bool ok */ 1:
|
||||||
|
message.ok = reader.bool();
|
||||||
|
break;
|
||||||
|
case /* string signed_upload_url */ 2:
|
||||||
|
message.signedUploadUrl = reader.string();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
let u = options.readUnknownField;
|
||||||
|
if (u === "throw")
|
||||||
|
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||||
|
let d = reader.skip(wireType);
|
||||||
|
if (u !== false)
|
||||||
|
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryWrite(message: MigrateArtifactResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* bool ok = 1; */
|
||||||
|
if (message.ok !== false)
|
||||||
|
writer.tag(1, WireType.Varint).bool(message.ok);
|
||||||
|
/* string signed_upload_url = 2; */
|
||||||
|
if (message.signedUploadUrl !== "")
|
||||||
|
writer.tag(2, WireType.LengthDelimited).string(message.signedUploadUrl);
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message github.actions.results.api.v1.MigrateArtifactResponse
|
||||||
|
*/
|
||||||
|
export const MigrateArtifactResponse = new MigrateArtifactResponse$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class FinalizeMigratedArtifactRequest$Type extends MessageType<FinalizeMigratedArtifactRequest> {
|
||||||
|
constructor() {
|
||||||
|
super("github.actions.results.api.v1.FinalizeMigratedArtifactRequest", [
|
||||||
|
{ no: 1, name: "workflow_run_backend_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 2, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 3, name: "size", kind: "scalar", T: 3 /*ScalarType.INT64*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<FinalizeMigratedArtifactRequest>): FinalizeMigratedArtifactRequest {
|
||||||
|
const message = { workflowRunBackendId: "", name: "", size: "0" };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<FinalizeMigratedArtifactRequest>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: FinalizeMigratedArtifactRequest): FinalizeMigratedArtifactRequest {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* string workflow_run_backend_id */ 1:
|
||||||
|
message.workflowRunBackendId = reader.string();
|
||||||
|
break;
|
||||||
|
case /* string name */ 2:
|
||||||
|
message.name = reader.string();
|
||||||
|
break;
|
||||||
|
case /* int64 size */ 3:
|
||||||
|
message.size = reader.int64().toString();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
let u = options.readUnknownField;
|
||||||
|
if (u === "throw")
|
||||||
|
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||||
|
let d = reader.skip(wireType);
|
||||||
|
if (u !== false)
|
||||||
|
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryWrite(message: FinalizeMigratedArtifactRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* string workflow_run_backend_id = 1; */
|
||||||
|
if (message.workflowRunBackendId !== "")
|
||||||
|
writer.tag(1, WireType.LengthDelimited).string(message.workflowRunBackendId);
|
||||||
|
/* string name = 2; */
|
||||||
|
if (message.name !== "")
|
||||||
|
writer.tag(2, WireType.LengthDelimited).string(message.name);
|
||||||
|
/* int64 size = 3; */
|
||||||
|
if (message.size !== "0")
|
||||||
|
writer.tag(3, WireType.Varint).int64(message.size);
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message github.actions.results.api.v1.FinalizeMigratedArtifactRequest
|
||||||
|
*/
|
||||||
|
export const FinalizeMigratedArtifactRequest = new FinalizeMigratedArtifactRequest$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class FinalizeMigratedArtifactResponse$Type extends MessageType<FinalizeMigratedArtifactResponse> {
|
||||||
|
constructor() {
|
||||||
|
super("github.actions.results.api.v1.FinalizeMigratedArtifactResponse", [
|
||||||
|
{ no: 1, name: "ok", kind: "scalar", T: 8 /*ScalarType.BOOL*/ },
|
||||||
|
{ no: 2, name: "artifact_id", kind: "scalar", T: 3 /*ScalarType.INT64*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<FinalizeMigratedArtifactResponse>): FinalizeMigratedArtifactResponse {
|
||||||
|
const message = { ok: false, artifactId: "0" };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<FinalizeMigratedArtifactResponse>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: FinalizeMigratedArtifactResponse): FinalizeMigratedArtifactResponse {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* bool ok */ 1:
|
||||||
|
message.ok = reader.bool();
|
||||||
|
break;
|
||||||
|
case /* int64 artifact_id */ 2:
|
||||||
|
message.artifactId = reader.int64().toString();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
let u = options.readUnknownField;
|
||||||
|
if (u === "throw")
|
||||||
|
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||||
|
let d = reader.skip(wireType);
|
||||||
|
if (u !== false)
|
||||||
|
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryWrite(message: FinalizeMigratedArtifactResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* bool ok = 1; */
|
||||||
|
if (message.ok !== false)
|
||||||
|
writer.tag(1, WireType.Varint).bool(message.ok);
|
||||||
|
/* int64 artifact_id = 2; */
|
||||||
|
if (message.artifactId !== "0")
|
||||||
|
writer.tag(2, WireType.Varint).int64(message.artifactId);
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message github.actions.results.api.v1.FinalizeMigratedArtifactResponse
|
||||||
|
*/
|
||||||
|
export const FinalizeMigratedArtifactResponse = new FinalizeMigratedArtifactResponse$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
class CreateArtifactRequest$Type extends MessageType<CreateArtifactRequest> {
|
class CreateArtifactRequest$Type extends MessageType<CreateArtifactRequest> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("github.actions.results.api.v1.CreateArtifactRequest", [
|
super("github.actions.results.api.v1.CreateArtifactRequest", [
|
||||||
@@ -608,7 +904,8 @@ class ListArtifactsResponse_MonolithArtifact$Type extends MessageType<ListArtifa
|
|||||||
{ 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 }
|
{ no: 6, name: "created_at", kind: "message", T: () => Timestamp },
|
||||||
|
{ no: 7, name: "digest", kind: "message", T: () => StringValue }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
create(value?: PartialMessage<ListArtifactsResponse_MonolithArtifact>): ListArtifactsResponse_MonolithArtifact {
|
create(value?: PartialMessage<ListArtifactsResponse_MonolithArtifact>): ListArtifactsResponse_MonolithArtifact {
|
||||||
@@ -641,6 +938,9 @@ class ListArtifactsResponse_MonolithArtifact$Type extends MessageType<ListArtifa
|
|||||||
case /* google.protobuf.Timestamp created_at */ 6:
|
case /* google.protobuf.Timestamp created_at */ 6:
|
||||||
message.createdAt = Timestamp.internalBinaryRead(reader, reader.uint32(), options, message.createdAt);
|
message.createdAt = Timestamp.internalBinaryRead(reader, reader.uint32(), options, message.createdAt);
|
||||||
break;
|
break;
|
||||||
|
case /* google.protobuf.StringValue digest */ 7:
|
||||||
|
message.digest = StringValue.internalBinaryRead(reader, reader.uint32(), options, message.digest);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
let u = options.readUnknownField;
|
let u = options.readUnknownField;
|
||||||
if (u === "throw")
|
if (u === "throw")
|
||||||
@@ -671,6 +971,9 @@ class ListArtifactsResponse_MonolithArtifact$Type extends MessageType<ListArtifa
|
|||||||
/* google.protobuf.Timestamp created_at = 6; */
|
/* google.protobuf.Timestamp created_at = 6; */
|
||||||
if (message.createdAt)
|
if (message.createdAt)
|
||||||
Timestamp.internalBinaryWrite(message.createdAt, writer.tag(6, WireType.LengthDelimited).fork(), options).join();
|
Timestamp.internalBinaryWrite(message.createdAt, writer.tag(6, WireType.LengthDelimited).fork(), options).join();
|
||||||
|
/* google.protobuf.StringValue digest = 7; */
|
||||||
|
if (message.digest)
|
||||||
|
StringValue.internalBinaryWrite(message.digest, writer.tag(7, 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);
|
||||||
@@ -912,5 +1215,7 @@ export const ArtifactService = new ServiceType("github.actions.results.api.v1.Ar
|
|||||||
{ name: "FinalizeArtifact", options: {}, I: FinalizeArtifactRequest, O: FinalizeArtifactResponse },
|
{ name: "FinalizeArtifact", options: {}, I: FinalizeArtifactRequest, O: FinalizeArtifactResponse },
|
||||||
{ name: "ListArtifacts", options: {}, I: ListArtifactsRequest, O: ListArtifactsResponse },
|
{ name: "ListArtifacts", options: {}, I: ListArtifactsRequest, O: ListArtifactsResponse },
|
||||||
{ name: "GetSignedArtifactURL", options: {}, I: GetSignedArtifactURLRequest, O: GetSignedArtifactURLResponse },
|
{ name: "GetSignedArtifactURL", options: {}, I: GetSignedArtifactURLRequest, O: GetSignedArtifactURLResponse },
|
||||||
{ name: "DeleteArtifact", options: {}, I: DeleteArtifactRequest, O: DeleteArtifactResponse }
|
{ name: "DeleteArtifact", options: {}, I: DeleteArtifactRequest, O: DeleteArtifactResponse },
|
||||||
]);
|
{ name: "MigrateArtifact", options: {}, I: MigrateArtifactRequest, O: MigrateArtifactResponse },
|
||||||
|
{ name: "FinalizeMigratedArtifact", options: {}, I: FinalizeMigratedArtifactRequest, O: FinalizeMigratedArtifactResponse }
|
||||||
|
]);
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
import {
|
||||||
|
CreateArtifactRequest,
|
||||||
|
CreateArtifactResponse,
|
||||||
|
FinalizeArtifactRequest,
|
||||||
|
FinalizeArtifactResponse,
|
||||||
|
ListArtifactsRequest,
|
||||||
|
ListArtifactsResponse,
|
||||||
|
GetSignedArtifactURLRequest,
|
||||||
|
GetSignedArtifactURLResponse,
|
||||||
|
DeleteArtifactRequest,
|
||||||
|
DeleteArtifactResponse,
|
||||||
|
} from "./artifact";
|
||||||
|
|
||||||
|
//==================================//
|
||||||
|
// Client Code //
|
||||||
|
//==================================//
|
||||||
|
|
||||||
|
interface Rpc {
|
||||||
|
request(
|
||||||
|
service: string,
|
||||||
|
method: string,
|
||||||
|
contentType: "application/json" | "application/protobuf",
|
||||||
|
data: object | Uint8Array
|
||||||
|
): Promise<object | Uint8Array>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ArtifactServiceClient {
|
||||||
|
CreateArtifact(
|
||||||
|
request: CreateArtifactRequest
|
||||||
|
): Promise<CreateArtifactResponse>;
|
||||||
|
FinalizeArtifact(
|
||||||
|
request: FinalizeArtifactRequest
|
||||||
|
): Promise<FinalizeArtifactResponse>;
|
||||||
|
ListArtifacts(request: ListArtifactsRequest): Promise<ListArtifactsResponse>;
|
||||||
|
GetSignedArtifactURL(
|
||||||
|
request: GetSignedArtifactURLRequest
|
||||||
|
): Promise<GetSignedArtifactURLResponse>;
|
||||||
|
DeleteArtifact(
|
||||||
|
request: DeleteArtifactRequest
|
||||||
|
): Promise<DeleteArtifactResponse>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ArtifactServiceClientJSON implements ArtifactServiceClient {
|
||||||
|
private readonly rpc: Rpc;
|
||||||
|
constructor(rpc: Rpc) {
|
||||||
|
this.rpc = rpc;
|
||||||
|
this.CreateArtifact.bind(this);
|
||||||
|
this.FinalizeArtifact.bind(this);
|
||||||
|
this.ListArtifacts.bind(this);
|
||||||
|
this.GetSignedArtifactURL.bind(this);
|
||||||
|
this.DeleteArtifact.bind(this);
|
||||||
|
}
|
||||||
|
CreateArtifact(
|
||||||
|
request: CreateArtifactRequest
|
||||||
|
): Promise<CreateArtifactResponse> {
|
||||||
|
const data = CreateArtifactRequest.toJson(request, {
|
||||||
|
useProtoFieldName: true,
|
||||||
|
emitDefaultValues: false,
|
||||||
|
});
|
||||||
|
const promise = this.rpc.request(
|
||||||
|
"github.actions.results.api.v1.ArtifactService",
|
||||||
|
"CreateArtifact",
|
||||||
|
"application/json",
|
||||||
|
data as object
|
||||||
|
);
|
||||||
|
return promise.then((data) =>
|
||||||
|
CreateArtifactResponse.fromJson(data as any, {
|
||||||
|
ignoreUnknownFields: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FinalizeArtifact(
|
||||||
|
request: FinalizeArtifactRequest
|
||||||
|
): Promise<FinalizeArtifactResponse> {
|
||||||
|
const data = FinalizeArtifactRequest.toJson(request, {
|
||||||
|
useProtoFieldName: true,
|
||||||
|
emitDefaultValues: false,
|
||||||
|
});
|
||||||
|
const promise = this.rpc.request(
|
||||||
|
"github.actions.results.api.v1.ArtifactService",
|
||||||
|
"FinalizeArtifact",
|
||||||
|
"application/json",
|
||||||
|
data as object
|
||||||
|
);
|
||||||
|
return promise.then((data) =>
|
||||||
|
FinalizeArtifactResponse.fromJson(data as any, {
|
||||||
|
ignoreUnknownFields: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListArtifacts(request: ListArtifactsRequest): Promise<ListArtifactsResponse> {
|
||||||
|
const data = ListArtifactsRequest.toJson(request, {
|
||||||
|
useProtoFieldName: true,
|
||||||
|
emitDefaultValues: false,
|
||||||
|
});
|
||||||
|
const promise = this.rpc.request(
|
||||||
|
"github.actions.results.api.v1.ArtifactService",
|
||||||
|
"ListArtifacts",
|
||||||
|
"application/json",
|
||||||
|
data as object
|
||||||
|
);
|
||||||
|
return promise.then((data) =>
|
||||||
|
ListArtifactsResponse.fromJson(data as any, { ignoreUnknownFields: true })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GetSignedArtifactURL(
|
||||||
|
request: GetSignedArtifactURLRequest
|
||||||
|
): Promise<GetSignedArtifactURLResponse> {
|
||||||
|
const data = GetSignedArtifactURLRequest.toJson(request, {
|
||||||
|
useProtoFieldName: true,
|
||||||
|
emitDefaultValues: false,
|
||||||
|
});
|
||||||
|
const promise = this.rpc.request(
|
||||||
|
"github.actions.results.api.v1.ArtifactService",
|
||||||
|
"GetSignedArtifactURL",
|
||||||
|
"application/json",
|
||||||
|
data as object
|
||||||
|
);
|
||||||
|
return promise.then((data) =>
|
||||||
|
GetSignedArtifactURLResponse.fromJson(data as any, {
|
||||||
|
ignoreUnknownFields: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteArtifact(
|
||||||
|
request: DeleteArtifactRequest
|
||||||
|
): Promise<DeleteArtifactResponse> {
|
||||||
|
const data = DeleteArtifactRequest.toJson(request, {
|
||||||
|
useProtoFieldName: true,
|
||||||
|
emitDefaultValues: false,
|
||||||
|
});
|
||||||
|
const promise = this.rpc.request(
|
||||||
|
"github.actions.results.api.v1.ArtifactService",
|
||||||
|
"DeleteArtifact",
|
||||||
|
"application/json",
|
||||||
|
data as object
|
||||||
|
);
|
||||||
|
return promise.then((data) =>
|
||||||
|
DeleteArtifactResponse.fromJson(data as any, {
|
||||||
|
ignoreUnknownFields: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ArtifactServiceClientProtobuf implements ArtifactServiceClient {
|
||||||
|
private readonly rpc: Rpc;
|
||||||
|
constructor(rpc: Rpc) {
|
||||||
|
this.rpc = rpc;
|
||||||
|
this.CreateArtifact.bind(this);
|
||||||
|
this.FinalizeArtifact.bind(this);
|
||||||
|
this.ListArtifacts.bind(this);
|
||||||
|
this.GetSignedArtifactURL.bind(this);
|
||||||
|
this.DeleteArtifact.bind(this);
|
||||||
|
}
|
||||||
|
CreateArtifact(
|
||||||
|
request: CreateArtifactRequest
|
||||||
|
): Promise<CreateArtifactResponse> {
|
||||||
|
const data = CreateArtifactRequest.toBinary(request);
|
||||||
|
const promise = this.rpc.request(
|
||||||
|
"github.actions.results.api.v1.ArtifactService",
|
||||||
|
"CreateArtifact",
|
||||||
|
"application/protobuf",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
return promise.then((data) =>
|
||||||
|
CreateArtifactResponse.fromBinary(data as Uint8Array)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FinalizeArtifact(
|
||||||
|
request: FinalizeArtifactRequest
|
||||||
|
): Promise<FinalizeArtifactResponse> {
|
||||||
|
const data = FinalizeArtifactRequest.toBinary(request);
|
||||||
|
const promise = this.rpc.request(
|
||||||
|
"github.actions.results.api.v1.ArtifactService",
|
||||||
|
"FinalizeArtifact",
|
||||||
|
"application/protobuf",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
return promise.then((data) =>
|
||||||
|
FinalizeArtifactResponse.fromBinary(data as Uint8Array)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListArtifacts(request: ListArtifactsRequest): Promise<ListArtifactsResponse> {
|
||||||
|
const data = ListArtifactsRequest.toBinary(request);
|
||||||
|
const promise = this.rpc.request(
|
||||||
|
"github.actions.results.api.v1.ArtifactService",
|
||||||
|
"ListArtifacts",
|
||||||
|
"application/protobuf",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
return promise.then((data) =>
|
||||||
|
ListArtifactsResponse.fromBinary(data as Uint8Array)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GetSignedArtifactURL(
|
||||||
|
request: GetSignedArtifactURLRequest
|
||||||
|
): Promise<GetSignedArtifactURLResponse> {
|
||||||
|
const data = GetSignedArtifactURLRequest.toBinary(request);
|
||||||
|
const promise = this.rpc.request(
|
||||||
|
"github.actions.results.api.v1.ArtifactService",
|
||||||
|
"GetSignedArtifactURL",
|
||||||
|
"application/protobuf",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
return promise.then((data) =>
|
||||||
|
GetSignedArtifactURLResponse.fromBinary(data as Uint8Array)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteArtifact(
|
||||||
|
request: DeleteArtifactRequest
|
||||||
|
): Promise<DeleteArtifactResponse> {
|
||||||
|
const data = DeleteArtifactRequest.toBinary(request);
|
||||||
|
const promise = this.rpc.request(
|
||||||
|
"github.actions.results.api.v1.ArtifactService",
|
||||||
|
"DeleteArtifact",
|
||||||
|
"application/protobuf",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
return promise.then((data) =>
|
||||||
|
DeleteArtifactResponse.fromBinary(data as Uint8Array)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,976 +0,0 @@
|
|||||||
import {
|
|
||||||
TwirpContext,
|
|
||||||
TwirpServer,
|
|
||||||
RouterEvents,
|
|
||||||
TwirpError,
|
|
||||||
TwirpErrorCode,
|
|
||||||
Interceptor,
|
|
||||||
TwirpContentType,
|
|
||||||
chainInterceptors,
|
|
||||||
} from "twirp-ts";
|
|
||||||
import {
|
|
||||||
CreateArtifactRequest,
|
|
||||||
CreateArtifactResponse,
|
|
||||||
FinalizeArtifactRequest,
|
|
||||||
FinalizeArtifactResponse,
|
|
||||||
ListArtifactsRequest,
|
|
||||||
ListArtifactsResponse,
|
|
||||||
GetSignedArtifactURLRequest,
|
|
||||||
GetSignedArtifactURLResponse,
|
|
||||||
DeleteArtifactRequest,
|
|
||||||
DeleteArtifactResponse,
|
|
||||||
} from "./artifact";
|
|
||||||
|
|
||||||
//==================================//
|
|
||||||
// Client Code //
|
|
||||||
//==================================//
|
|
||||||
|
|
||||||
interface Rpc {
|
|
||||||
request(
|
|
||||||
service: string,
|
|
||||||
method: string,
|
|
||||||
contentType: "application/json" | "application/protobuf",
|
|
||||||
data: object | Uint8Array
|
|
||||||
): Promise<object | Uint8Array>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ArtifactServiceClient {
|
|
||||||
CreateArtifact(
|
|
||||||
request: CreateArtifactRequest
|
|
||||||
): Promise<CreateArtifactResponse>;
|
|
||||||
FinalizeArtifact(
|
|
||||||
request: FinalizeArtifactRequest
|
|
||||||
): Promise<FinalizeArtifactResponse>;
|
|
||||||
ListArtifacts(request: ListArtifactsRequest): Promise<ListArtifactsResponse>;
|
|
||||||
GetSignedArtifactURL(
|
|
||||||
request: GetSignedArtifactURLRequest
|
|
||||||
): Promise<GetSignedArtifactURLResponse>;
|
|
||||||
DeleteArtifact(
|
|
||||||
request: DeleteArtifactRequest
|
|
||||||
): Promise<DeleteArtifactResponse>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ArtifactServiceClientJSON implements ArtifactServiceClient {
|
|
||||||
private readonly rpc: Rpc;
|
|
||||||
constructor(rpc: Rpc) {
|
|
||||||
this.rpc = rpc;
|
|
||||||
this.CreateArtifact.bind(this);
|
|
||||||
this.FinalizeArtifact.bind(this);
|
|
||||||
this.ListArtifacts.bind(this);
|
|
||||||
this.GetSignedArtifactURL.bind(this);
|
|
||||||
this.DeleteArtifact.bind(this);
|
|
||||||
}
|
|
||||||
CreateArtifact(
|
|
||||||
request: CreateArtifactRequest
|
|
||||||
): Promise<CreateArtifactResponse> {
|
|
||||||
const data = CreateArtifactRequest.toJson(request, {
|
|
||||||
useProtoFieldName: true,
|
|
||||||
emitDefaultValues: false,
|
|
||||||
});
|
|
||||||
const promise = this.rpc.request(
|
|
||||||
"github.actions.results.api.v1.ArtifactService",
|
|
||||||
"CreateArtifact",
|
|
||||||
"application/json",
|
|
||||||
data as object
|
|
||||||
);
|
|
||||||
return promise.then((data) =>
|
|
||||||
CreateArtifactResponse.fromJson(data as any, {
|
|
||||||
ignoreUnknownFields: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
FinalizeArtifact(
|
|
||||||
request: FinalizeArtifactRequest
|
|
||||||
): Promise<FinalizeArtifactResponse> {
|
|
||||||
const data = FinalizeArtifactRequest.toJson(request, {
|
|
||||||
useProtoFieldName: true,
|
|
||||||
emitDefaultValues: false,
|
|
||||||
});
|
|
||||||
const promise = this.rpc.request(
|
|
||||||
"github.actions.results.api.v1.ArtifactService",
|
|
||||||
"FinalizeArtifact",
|
|
||||||
"application/json",
|
|
||||||
data as object
|
|
||||||
);
|
|
||||||
return promise.then((data) =>
|
|
||||||
FinalizeArtifactResponse.fromJson(data as any, {
|
|
||||||
ignoreUnknownFields: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ListArtifacts(request: ListArtifactsRequest): Promise<ListArtifactsResponse> {
|
|
||||||
const data = ListArtifactsRequest.toJson(request, {
|
|
||||||
useProtoFieldName: true,
|
|
||||||
emitDefaultValues: false,
|
|
||||||
});
|
|
||||||
const promise = this.rpc.request(
|
|
||||||
"github.actions.results.api.v1.ArtifactService",
|
|
||||||
"ListArtifacts",
|
|
||||||
"application/json",
|
|
||||||
data as object
|
|
||||||
);
|
|
||||||
return promise.then((data) =>
|
|
||||||
ListArtifactsResponse.fromJson(data as any, { ignoreUnknownFields: true })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
GetSignedArtifactURL(
|
|
||||||
request: GetSignedArtifactURLRequest
|
|
||||||
): Promise<GetSignedArtifactURLResponse> {
|
|
||||||
const data = GetSignedArtifactURLRequest.toJson(request, {
|
|
||||||
useProtoFieldName: true,
|
|
||||||
emitDefaultValues: false,
|
|
||||||
});
|
|
||||||
const promise = this.rpc.request(
|
|
||||||
"github.actions.results.api.v1.ArtifactService",
|
|
||||||
"GetSignedArtifactURL",
|
|
||||||
"application/json",
|
|
||||||
data as object
|
|
||||||
);
|
|
||||||
return promise.then((data) =>
|
|
||||||
GetSignedArtifactURLResponse.fromJson(data as any, {
|
|
||||||
ignoreUnknownFields: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteArtifact(
|
|
||||||
request: DeleteArtifactRequest
|
|
||||||
): Promise<DeleteArtifactResponse> {
|
|
||||||
const data = DeleteArtifactRequest.toJson(request, {
|
|
||||||
useProtoFieldName: true,
|
|
||||||
emitDefaultValues: false,
|
|
||||||
});
|
|
||||||
const promise = this.rpc.request(
|
|
||||||
"github.actions.results.api.v1.ArtifactService",
|
|
||||||
"DeleteArtifact",
|
|
||||||
"application/json",
|
|
||||||
data as object
|
|
||||||
);
|
|
||||||
return promise.then((data) =>
|
|
||||||
DeleteArtifactResponse.fromJson(data as any, {
|
|
||||||
ignoreUnknownFields: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ArtifactServiceClientProtobuf implements ArtifactServiceClient {
|
|
||||||
private readonly rpc: Rpc;
|
|
||||||
constructor(rpc: Rpc) {
|
|
||||||
this.rpc = rpc;
|
|
||||||
this.CreateArtifact.bind(this);
|
|
||||||
this.FinalizeArtifact.bind(this);
|
|
||||||
this.ListArtifacts.bind(this);
|
|
||||||
this.GetSignedArtifactURL.bind(this);
|
|
||||||
this.DeleteArtifact.bind(this);
|
|
||||||
}
|
|
||||||
CreateArtifact(
|
|
||||||
request: CreateArtifactRequest
|
|
||||||
): Promise<CreateArtifactResponse> {
|
|
||||||
const data = CreateArtifactRequest.toBinary(request);
|
|
||||||
const promise = this.rpc.request(
|
|
||||||
"github.actions.results.api.v1.ArtifactService",
|
|
||||||
"CreateArtifact",
|
|
||||||
"application/protobuf",
|
|
||||||
data
|
|
||||||
);
|
|
||||||
return promise.then((data) =>
|
|
||||||
CreateArtifactResponse.fromBinary(data as Uint8Array)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
FinalizeArtifact(
|
|
||||||
request: FinalizeArtifactRequest
|
|
||||||
): Promise<FinalizeArtifactResponse> {
|
|
||||||
const data = FinalizeArtifactRequest.toBinary(request);
|
|
||||||
const promise = this.rpc.request(
|
|
||||||
"github.actions.results.api.v1.ArtifactService",
|
|
||||||
"FinalizeArtifact",
|
|
||||||
"application/protobuf",
|
|
||||||
data
|
|
||||||
);
|
|
||||||
return promise.then((data) =>
|
|
||||||
FinalizeArtifactResponse.fromBinary(data as Uint8Array)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ListArtifacts(request: ListArtifactsRequest): Promise<ListArtifactsResponse> {
|
|
||||||
const data = ListArtifactsRequest.toBinary(request);
|
|
||||||
const promise = this.rpc.request(
|
|
||||||
"github.actions.results.api.v1.ArtifactService",
|
|
||||||
"ListArtifacts",
|
|
||||||
"application/protobuf",
|
|
||||||
data
|
|
||||||
);
|
|
||||||
return promise.then((data) =>
|
|
||||||
ListArtifactsResponse.fromBinary(data as Uint8Array)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
GetSignedArtifactURL(
|
|
||||||
request: GetSignedArtifactURLRequest
|
|
||||||
): Promise<GetSignedArtifactURLResponse> {
|
|
||||||
const data = GetSignedArtifactURLRequest.toBinary(request);
|
|
||||||
const promise = this.rpc.request(
|
|
||||||
"github.actions.results.api.v1.ArtifactService",
|
|
||||||
"GetSignedArtifactURL",
|
|
||||||
"application/protobuf",
|
|
||||||
data
|
|
||||||
);
|
|
||||||
return promise.then((data) =>
|
|
||||||
GetSignedArtifactURLResponse.fromBinary(data as Uint8Array)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteArtifact(
|
|
||||||
request: DeleteArtifactRequest
|
|
||||||
): Promise<DeleteArtifactResponse> {
|
|
||||||
const data = DeleteArtifactRequest.toBinary(request);
|
|
||||||
const promise = this.rpc.request(
|
|
||||||
"github.actions.results.api.v1.ArtifactService",
|
|
||||||
"DeleteArtifact",
|
|
||||||
"application/protobuf",
|
|
||||||
data
|
|
||||||
);
|
|
||||||
return promise.then((data) =>
|
|
||||||
DeleteArtifactResponse.fromBinary(data as Uint8Array)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//==================================//
|
|
||||||
// Server Code //
|
|
||||||
//==================================//
|
|
||||||
|
|
||||||
export interface ArtifactServiceTwirp<T extends TwirpContext = TwirpContext> {
|
|
||||||
CreateArtifact(
|
|
||||||
ctx: T,
|
|
||||||
request: CreateArtifactRequest
|
|
||||||
): Promise<CreateArtifactResponse>;
|
|
||||||
FinalizeArtifact(
|
|
||||||
ctx: T,
|
|
||||||
request: FinalizeArtifactRequest
|
|
||||||
): Promise<FinalizeArtifactResponse>;
|
|
||||||
ListArtifacts(
|
|
||||||
ctx: T,
|
|
||||||
request: ListArtifactsRequest
|
|
||||||
): Promise<ListArtifactsResponse>;
|
|
||||||
GetSignedArtifactURL(
|
|
||||||
ctx: T,
|
|
||||||
request: GetSignedArtifactURLRequest
|
|
||||||
): Promise<GetSignedArtifactURLResponse>;
|
|
||||||
DeleteArtifact(
|
|
||||||
ctx: T,
|
|
||||||
request: DeleteArtifactRequest
|
|
||||||
): Promise<DeleteArtifactResponse>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ArtifactServiceMethod {
|
|
||||||
CreateArtifact = "CreateArtifact",
|
|
||||||
FinalizeArtifact = "FinalizeArtifact",
|
|
||||||
ListArtifacts = "ListArtifacts",
|
|
||||||
GetSignedArtifactURL = "GetSignedArtifactURL",
|
|
||||||
DeleteArtifact = "DeleteArtifact",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ArtifactServiceMethodList = [
|
|
||||||
ArtifactServiceMethod.CreateArtifact,
|
|
||||||
ArtifactServiceMethod.FinalizeArtifact,
|
|
||||||
ArtifactServiceMethod.ListArtifacts,
|
|
||||||
ArtifactServiceMethod.GetSignedArtifactURL,
|
|
||||||
ArtifactServiceMethod.DeleteArtifact,
|
|
||||||
];
|
|
||||||
|
|
||||||
export function createArtifactServiceServer<
|
|
||||||
T extends TwirpContext = TwirpContext
|
|
||||||
>(service: ArtifactServiceTwirp<T>) {
|
|
||||||
return new TwirpServer<ArtifactServiceTwirp, T>({
|
|
||||||
service,
|
|
||||||
packageName: "github.actions.results.api.v1",
|
|
||||||
serviceName: "ArtifactService",
|
|
||||||
methodList: ArtifactServiceMethodList,
|
|
||||||
matchRoute: matchArtifactServiceRoute,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchArtifactServiceRoute<T extends TwirpContext = TwirpContext>(
|
|
||||||
method: string,
|
|
||||||
events: RouterEvents<T>
|
|
||||||
) {
|
|
||||||
switch (method) {
|
|
||||||
case "CreateArtifact":
|
|
||||||
return async (
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<
|
|
||||||
T,
|
|
||||||
CreateArtifactRequest,
|
|
||||||
CreateArtifactResponse
|
|
||||||
>[]
|
|
||||||
) => {
|
|
||||||
ctx = { ...ctx, methodName: "CreateArtifact" };
|
|
||||||
await events.onMatch(ctx);
|
|
||||||
return handleArtifactServiceCreateArtifactRequest(
|
|
||||||
ctx,
|
|
||||||
service,
|
|
||||||
data,
|
|
||||||
interceptors
|
|
||||||
);
|
|
||||||
};
|
|
||||||
case "FinalizeArtifact":
|
|
||||||
return async (
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<
|
|
||||||
T,
|
|
||||||
FinalizeArtifactRequest,
|
|
||||||
FinalizeArtifactResponse
|
|
||||||
>[]
|
|
||||||
) => {
|
|
||||||
ctx = { ...ctx, methodName: "FinalizeArtifact" };
|
|
||||||
await events.onMatch(ctx);
|
|
||||||
return handleArtifactServiceFinalizeArtifactRequest(
|
|
||||||
ctx,
|
|
||||||
service,
|
|
||||||
data,
|
|
||||||
interceptors
|
|
||||||
);
|
|
||||||
};
|
|
||||||
case "ListArtifacts":
|
|
||||||
return async (
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<
|
|
||||||
T,
|
|
||||||
ListArtifactsRequest,
|
|
||||||
ListArtifactsResponse
|
|
||||||
>[]
|
|
||||||
) => {
|
|
||||||
ctx = { ...ctx, methodName: "ListArtifacts" };
|
|
||||||
await events.onMatch(ctx);
|
|
||||||
return handleArtifactServiceListArtifactsRequest(
|
|
||||||
ctx,
|
|
||||||
service,
|
|
||||||
data,
|
|
||||||
interceptors
|
|
||||||
);
|
|
||||||
};
|
|
||||||
case "GetSignedArtifactURL":
|
|
||||||
return async (
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<
|
|
||||||
T,
|
|
||||||
GetSignedArtifactURLRequest,
|
|
||||||
GetSignedArtifactURLResponse
|
|
||||||
>[]
|
|
||||||
) => {
|
|
||||||
ctx = { ...ctx, methodName: "GetSignedArtifactURL" };
|
|
||||||
await events.onMatch(ctx);
|
|
||||||
return handleArtifactServiceGetSignedArtifactURLRequest(
|
|
||||||
ctx,
|
|
||||||
service,
|
|
||||||
data,
|
|
||||||
interceptors
|
|
||||||
);
|
|
||||||
};
|
|
||||||
case "DeleteArtifact":
|
|
||||||
return async (
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<
|
|
||||||
T,
|
|
||||||
DeleteArtifactRequest,
|
|
||||||
DeleteArtifactResponse
|
|
||||||
>[]
|
|
||||||
) => {
|
|
||||||
ctx = { ...ctx, methodName: "DeleteArtifact" };
|
|
||||||
await events.onMatch(ctx);
|
|
||||||
return handleArtifactServiceDeleteArtifactRequest(
|
|
||||||
ctx,
|
|
||||||
service,
|
|
||||||
data,
|
|
||||||
interceptors
|
|
||||||
);
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
events.onNotFound();
|
|
||||||
const msg = `no handler found`;
|
|
||||||
throw new TwirpError(TwirpErrorCode.BadRoute, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleArtifactServiceCreateArtifactRequest<
|
|
||||||
T extends TwirpContext = TwirpContext
|
|
||||||
>(
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<T, CreateArtifactRequest, CreateArtifactResponse>[]
|
|
||||||
): Promise<string | Uint8Array> {
|
|
||||||
switch (ctx.contentType) {
|
|
||||||
case TwirpContentType.JSON:
|
|
||||||
return handleArtifactServiceCreateArtifactJSON<T>(
|
|
||||||
ctx,
|
|
||||||
service,
|
|
||||||
data,
|
|
||||||
interceptors
|
|
||||||
);
|
|
||||||
case TwirpContentType.Protobuf:
|
|
||||||
return handleArtifactServiceCreateArtifactProtobuf<T>(
|
|
||||||
ctx,
|
|
||||||
service,
|
|
||||||
data,
|
|
||||||
interceptors
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
const msg = "unexpected Content-Type";
|
|
||||||
throw new TwirpError(TwirpErrorCode.BadRoute, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleArtifactServiceFinalizeArtifactRequest<
|
|
||||||
T extends TwirpContext = TwirpContext
|
|
||||||
>(
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<
|
|
||||||
T,
|
|
||||||
FinalizeArtifactRequest,
|
|
||||||
FinalizeArtifactResponse
|
|
||||||
>[]
|
|
||||||
): Promise<string | Uint8Array> {
|
|
||||||
switch (ctx.contentType) {
|
|
||||||
case TwirpContentType.JSON:
|
|
||||||
return handleArtifactServiceFinalizeArtifactJSON<T>(
|
|
||||||
ctx,
|
|
||||||
service,
|
|
||||||
data,
|
|
||||||
interceptors
|
|
||||||
);
|
|
||||||
case TwirpContentType.Protobuf:
|
|
||||||
return handleArtifactServiceFinalizeArtifactProtobuf<T>(
|
|
||||||
ctx,
|
|
||||||
service,
|
|
||||||
data,
|
|
||||||
interceptors
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
const msg = "unexpected Content-Type";
|
|
||||||
throw new TwirpError(TwirpErrorCode.BadRoute, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleArtifactServiceListArtifactsRequest<
|
|
||||||
T extends TwirpContext = TwirpContext
|
|
||||||
>(
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<T, ListArtifactsRequest, ListArtifactsResponse>[]
|
|
||||||
): Promise<string | Uint8Array> {
|
|
||||||
switch (ctx.contentType) {
|
|
||||||
case TwirpContentType.JSON:
|
|
||||||
return handleArtifactServiceListArtifactsJSON<T>(
|
|
||||||
ctx,
|
|
||||||
service,
|
|
||||||
data,
|
|
||||||
interceptors
|
|
||||||
);
|
|
||||||
case TwirpContentType.Protobuf:
|
|
||||||
return handleArtifactServiceListArtifactsProtobuf<T>(
|
|
||||||
ctx,
|
|
||||||
service,
|
|
||||||
data,
|
|
||||||
interceptors
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
const msg = "unexpected Content-Type";
|
|
||||||
throw new TwirpError(TwirpErrorCode.BadRoute, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleArtifactServiceGetSignedArtifactURLRequest<
|
|
||||||
T extends TwirpContext = TwirpContext
|
|
||||||
>(
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<
|
|
||||||
T,
|
|
||||||
GetSignedArtifactURLRequest,
|
|
||||||
GetSignedArtifactURLResponse
|
|
||||||
>[]
|
|
||||||
): Promise<string | Uint8Array> {
|
|
||||||
switch (ctx.contentType) {
|
|
||||||
case TwirpContentType.JSON:
|
|
||||||
return handleArtifactServiceGetSignedArtifactURLJSON<T>(
|
|
||||||
ctx,
|
|
||||||
service,
|
|
||||||
data,
|
|
||||||
interceptors
|
|
||||||
);
|
|
||||||
case TwirpContentType.Protobuf:
|
|
||||||
return handleArtifactServiceGetSignedArtifactURLProtobuf<T>(
|
|
||||||
ctx,
|
|
||||||
service,
|
|
||||||
data,
|
|
||||||
interceptors
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
const msg = "unexpected Content-Type";
|
|
||||||
throw new TwirpError(TwirpErrorCode.BadRoute, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleArtifactServiceDeleteArtifactRequest<
|
|
||||||
T extends TwirpContext = TwirpContext
|
|
||||||
>(
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<T, DeleteArtifactRequest, DeleteArtifactResponse>[]
|
|
||||||
): Promise<string | Uint8Array> {
|
|
||||||
switch (ctx.contentType) {
|
|
||||||
case TwirpContentType.JSON:
|
|
||||||
return handleArtifactServiceDeleteArtifactJSON<T>(
|
|
||||||
ctx,
|
|
||||||
service,
|
|
||||||
data,
|
|
||||||
interceptors
|
|
||||||
);
|
|
||||||
case TwirpContentType.Protobuf:
|
|
||||||
return handleArtifactServiceDeleteArtifactProtobuf<T>(
|
|
||||||
ctx,
|
|
||||||
service,
|
|
||||||
data,
|
|
||||||
interceptors
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
const msg = "unexpected Content-Type";
|
|
||||||
throw new TwirpError(TwirpErrorCode.BadRoute, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function handleArtifactServiceCreateArtifactJSON<
|
|
||||||
T extends TwirpContext = TwirpContext
|
|
||||||
>(
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<T, CreateArtifactRequest, CreateArtifactResponse>[]
|
|
||||||
) {
|
|
||||||
let request: CreateArtifactRequest;
|
|
||||||
let response: CreateArtifactResponse;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body = JSON.parse(data.toString() || "{}");
|
|
||||||
request = CreateArtifactRequest.fromJson(body, {
|
|
||||||
ignoreUnknownFields: true,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
const msg = "the json request could not be decoded";
|
|
||||||
throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interceptors && interceptors.length > 0) {
|
|
||||||
const interceptor = chainInterceptors(...interceptors) as Interceptor<
|
|
||||||
T,
|
|
||||||
CreateArtifactRequest,
|
|
||||||
CreateArtifactResponse
|
|
||||||
>;
|
|
||||||
response = await interceptor(ctx, request!, (ctx, inputReq) => {
|
|
||||||
return service.CreateArtifact(ctx, inputReq);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
response = await service.CreateArtifact(ctx, request!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify(
|
|
||||||
CreateArtifactResponse.toJson(response, {
|
|
||||||
useProtoFieldName: true,
|
|
||||||
emitDefaultValues: false,
|
|
||||||
}) as string
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleArtifactServiceFinalizeArtifactJSON<
|
|
||||||
T extends TwirpContext = TwirpContext
|
|
||||||
>(
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<
|
|
||||||
T,
|
|
||||||
FinalizeArtifactRequest,
|
|
||||||
FinalizeArtifactResponse
|
|
||||||
>[]
|
|
||||||
) {
|
|
||||||
let request: FinalizeArtifactRequest;
|
|
||||||
let response: FinalizeArtifactResponse;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body = JSON.parse(data.toString() || "{}");
|
|
||||||
request = FinalizeArtifactRequest.fromJson(body, {
|
|
||||||
ignoreUnknownFields: true,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
const msg = "the json request could not be decoded";
|
|
||||||
throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interceptors && interceptors.length > 0) {
|
|
||||||
const interceptor = chainInterceptors(...interceptors) as Interceptor<
|
|
||||||
T,
|
|
||||||
FinalizeArtifactRequest,
|
|
||||||
FinalizeArtifactResponse
|
|
||||||
>;
|
|
||||||
response = await interceptor(ctx, request!, (ctx, inputReq) => {
|
|
||||||
return service.FinalizeArtifact(ctx, inputReq);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
response = await service.FinalizeArtifact(ctx, request!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify(
|
|
||||||
FinalizeArtifactResponse.toJson(response, {
|
|
||||||
useProtoFieldName: true,
|
|
||||||
emitDefaultValues: false,
|
|
||||||
}) as string
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleArtifactServiceListArtifactsJSON<
|
|
||||||
T extends TwirpContext = TwirpContext
|
|
||||||
>(
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<T, ListArtifactsRequest, ListArtifactsResponse>[]
|
|
||||||
) {
|
|
||||||
let request: ListArtifactsRequest;
|
|
||||||
let response: ListArtifactsResponse;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body = JSON.parse(data.toString() || "{}");
|
|
||||||
request = ListArtifactsRequest.fromJson(body, {
|
|
||||||
ignoreUnknownFields: true,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
const msg = "the json request could not be decoded";
|
|
||||||
throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interceptors && interceptors.length > 0) {
|
|
||||||
const interceptor = chainInterceptors(...interceptors) as Interceptor<
|
|
||||||
T,
|
|
||||||
ListArtifactsRequest,
|
|
||||||
ListArtifactsResponse
|
|
||||||
>;
|
|
||||||
response = await interceptor(ctx, request!, (ctx, inputReq) => {
|
|
||||||
return service.ListArtifacts(ctx, inputReq);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
response = await service.ListArtifacts(ctx, request!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify(
|
|
||||||
ListArtifactsResponse.toJson(response, {
|
|
||||||
useProtoFieldName: true,
|
|
||||||
emitDefaultValues: false,
|
|
||||||
}) as string
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleArtifactServiceGetSignedArtifactURLJSON<
|
|
||||||
T extends TwirpContext = TwirpContext
|
|
||||||
>(
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<
|
|
||||||
T,
|
|
||||||
GetSignedArtifactURLRequest,
|
|
||||||
GetSignedArtifactURLResponse
|
|
||||||
>[]
|
|
||||||
) {
|
|
||||||
let request: GetSignedArtifactURLRequest;
|
|
||||||
let response: GetSignedArtifactURLResponse;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body = JSON.parse(data.toString() || "{}");
|
|
||||||
request = GetSignedArtifactURLRequest.fromJson(body, {
|
|
||||||
ignoreUnknownFields: true,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
const msg = "the json request could not be decoded";
|
|
||||||
throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interceptors && interceptors.length > 0) {
|
|
||||||
const interceptor = chainInterceptors(...interceptors) as Interceptor<
|
|
||||||
T,
|
|
||||||
GetSignedArtifactURLRequest,
|
|
||||||
GetSignedArtifactURLResponse
|
|
||||||
>;
|
|
||||||
response = await interceptor(ctx, request!, (ctx, inputReq) => {
|
|
||||||
return service.GetSignedArtifactURL(ctx, inputReq);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
response = await service.GetSignedArtifactURL(ctx, request!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify(
|
|
||||||
GetSignedArtifactURLResponse.toJson(response, {
|
|
||||||
useProtoFieldName: true,
|
|
||||||
emitDefaultValues: false,
|
|
||||||
}) as string
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleArtifactServiceDeleteArtifactJSON<
|
|
||||||
T extends TwirpContext = TwirpContext
|
|
||||||
>(
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<T, DeleteArtifactRequest, DeleteArtifactResponse>[]
|
|
||||||
) {
|
|
||||||
let request: DeleteArtifactRequest;
|
|
||||||
let response: DeleteArtifactResponse;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body = JSON.parse(data.toString() || "{}");
|
|
||||||
request = DeleteArtifactRequest.fromJson(body, {
|
|
||||||
ignoreUnknownFields: true,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
const msg = "the json request could not be decoded";
|
|
||||||
throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interceptors && interceptors.length > 0) {
|
|
||||||
const interceptor = chainInterceptors(...interceptors) as Interceptor<
|
|
||||||
T,
|
|
||||||
DeleteArtifactRequest,
|
|
||||||
DeleteArtifactResponse
|
|
||||||
>;
|
|
||||||
response = await interceptor(ctx, request!, (ctx, inputReq) => {
|
|
||||||
return service.DeleteArtifact(ctx, inputReq);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
response = await service.DeleteArtifact(ctx, request!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify(
|
|
||||||
DeleteArtifactResponse.toJson(response, {
|
|
||||||
useProtoFieldName: true,
|
|
||||||
emitDefaultValues: false,
|
|
||||||
}) as string
|
|
||||||
);
|
|
||||||
}
|
|
||||||
async function handleArtifactServiceCreateArtifactProtobuf<
|
|
||||||
T extends TwirpContext = TwirpContext
|
|
||||||
>(
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<T, CreateArtifactRequest, CreateArtifactResponse>[]
|
|
||||||
) {
|
|
||||||
let request: CreateArtifactRequest;
|
|
||||||
let response: CreateArtifactResponse;
|
|
||||||
|
|
||||||
try {
|
|
||||||
request = CreateArtifactRequest.fromBinary(data);
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
const msg = "the protobuf request could not be decoded";
|
|
||||||
throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interceptors && interceptors.length > 0) {
|
|
||||||
const interceptor = chainInterceptors(...interceptors) as Interceptor<
|
|
||||||
T,
|
|
||||||
CreateArtifactRequest,
|
|
||||||
CreateArtifactResponse
|
|
||||||
>;
|
|
||||||
response = await interceptor(ctx, request!, (ctx, inputReq) => {
|
|
||||||
return service.CreateArtifact(ctx, inputReq);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
response = await service.CreateArtifact(ctx, request!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Buffer.from(CreateArtifactResponse.toBinary(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleArtifactServiceFinalizeArtifactProtobuf<
|
|
||||||
T extends TwirpContext = TwirpContext
|
|
||||||
>(
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<
|
|
||||||
T,
|
|
||||||
FinalizeArtifactRequest,
|
|
||||||
FinalizeArtifactResponse
|
|
||||||
>[]
|
|
||||||
) {
|
|
||||||
let request: FinalizeArtifactRequest;
|
|
||||||
let response: FinalizeArtifactResponse;
|
|
||||||
|
|
||||||
try {
|
|
||||||
request = FinalizeArtifactRequest.fromBinary(data);
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
const msg = "the protobuf request could not be decoded";
|
|
||||||
throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interceptors && interceptors.length > 0) {
|
|
||||||
const interceptor = chainInterceptors(...interceptors) as Interceptor<
|
|
||||||
T,
|
|
||||||
FinalizeArtifactRequest,
|
|
||||||
FinalizeArtifactResponse
|
|
||||||
>;
|
|
||||||
response = await interceptor(ctx, request!, (ctx, inputReq) => {
|
|
||||||
return service.FinalizeArtifact(ctx, inputReq);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
response = await service.FinalizeArtifact(ctx, request!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Buffer.from(FinalizeArtifactResponse.toBinary(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleArtifactServiceListArtifactsProtobuf<
|
|
||||||
T extends TwirpContext = TwirpContext
|
|
||||||
>(
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<T, ListArtifactsRequest, ListArtifactsResponse>[]
|
|
||||||
) {
|
|
||||||
let request: ListArtifactsRequest;
|
|
||||||
let response: ListArtifactsResponse;
|
|
||||||
|
|
||||||
try {
|
|
||||||
request = ListArtifactsRequest.fromBinary(data);
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
const msg = "the protobuf request could not be decoded";
|
|
||||||
throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interceptors && interceptors.length > 0) {
|
|
||||||
const interceptor = chainInterceptors(...interceptors) as Interceptor<
|
|
||||||
T,
|
|
||||||
ListArtifactsRequest,
|
|
||||||
ListArtifactsResponse
|
|
||||||
>;
|
|
||||||
response = await interceptor(ctx, request!, (ctx, inputReq) => {
|
|
||||||
return service.ListArtifacts(ctx, inputReq);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
response = await service.ListArtifacts(ctx, request!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Buffer.from(ListArtifactsResponse.toBinary(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleArtifactServiceGetSignedArtifactURLProtobuf<
|
|
||||||
T extends TwirpContext = TwirpContext
|
|
||||||
>(
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<
|
|
||||||
T,
|
|
||||||
GetSignedArtifactURLRequest,
|
|
||||||
GetSignedArtifactURLResponse
|
|
||||||
>[]
|
|
||||||
) {
|
|
||||||
let request: GetSignedArtifactURLRequest;
|
|
||||||
let response: GetSignedArtifactURLResponse;
|
|
||||||
|
|
||||||
try {
|
|
||||||
request = GetSignedArtifactURLRequest.fromBinary(data);
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
const msg = "the protobuf request could not be decoded";
|
|
||||||
throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interceptors && interceptors.length > 0) {
|
|
||||||
const interceptor = chainInterceptors(...interceptors) as Interceptor<
|
|
||||||
T,
|
|
||||||
GetSignedArtifactURLRequest,
|
|
||||||
GetSignedArtifactURLResponse
|
|
||||||
>;
|
|
||||||
response = await interceptor(ctx, request!, (ctx, inputReq) => {
|
|
||||||
return service.GetSignedArtifactURL(ctx, inputReq);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
response = await service.GetSignedArtifactURL(ctx, request!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Buffer.from(GetSignedArtifactURLResponse.toBinary(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleArtifactServiceDeleteArtifactProtobuf<
|
|
||||||
T extends TwirpContext = TwirpContext
|
|
||||||
>(
|
|
||||||
ctx: T,
|
|
||||||
service: ArtifactServiceTwirp,
|
|
||||||
data: Buffer,
|
|
||||||
interceptors?: Interceptor<T, DeleteArtifactRequest, DeleteArtifactResponse>[]
|
|
||||||
) {
|
|
||||||
let request: DeleteArtifactRequest;
|
|
||||||
let response: DeleteArtifactResponse;
|
|
||||||
|
|
||||||
try {
|
|
||||||
request = DeleteArtifactRequest.fromBinary(data);
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
const msg = "the protobuf request could not be decoded";
|
|
||||||
throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interceptors && interceptors.length > 0) {
|
|
||||||
const interceptor = chainInterceptors(...interceptors) as Interceptor<
|
|
||||||
T,
|
|
||||||
DeleteArtifactRequest,
|
|
||||||
DeleteArtifactResponse
|
|
||||||
>;
|
|
||||||
response = await interceptor(ctx, request!, (ctx, inputReq) => {
|
|
||||||
return service.DeleteArtifact(ctx, inputReq);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
response = await service.DeleteArtifact(ctx, request!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Buffer.from(DeleteArtifactResponse.toBinary(response));
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
|
import * as crypto from 'crypto'
|
||||||
import * as stream from 'stream'
|
import * as stream from 'stream'
|
||||||
import {createWriteStream} from 'fs'
|
|
||||||
import * as path from 'path'
|
|
||||||
import * as github from '@actions/github'
|
import * as github from '@actions/github'
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import * as httpClient from '@actions/http-client'
|
import * as httpClient from '@actions/http-client'
|
||||||
import unzip from 'unzip-stream'
|
import unzip from 'unzip-stream'
|
||||||
import {
|
import {
|
||||||
DownloadArtifactOptions,
|
DownloadArtifactOptions,
|
||||||
DownloadArtifactResponse
|
DownloadArtifactResponse,
|
||||||
|
StreamExtractResponse
|
||||||
} from '../shared/interfaces'
|
} from '../shared/interfaces'
|
||||||
import {getUserAgentString} from '../shared/user-agent'
|
import {getUserAgentString} from '../shared/user-agent'
|
||||||
import {getGitHubWorkspaceDir} from '../shared/config'
|
import {getGitHubWorkspaceDir} from '../shared/config'
|
||||||
@@ -40,18 +41,15 @@ async function exists(path: string): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function streamExtract(url: string, directory: string): Promise<void> {
|
async function streamExtract(
|
||||||
|
url: string,
|
||||||
|
directory: string
|
||||||
|
): Promise<StreamExtractResponse> {
|
||||||
let retryCount = 0
|
let retryCount = 0
|
||||||
while (retryCount < 5) {
|
while (retryCount < 5) {
|
||||||
try {
|
try {
|
||||||
await streamExtractExternal(url, directory)
|
return await streamExtractExternal(url, directory)
|
||||||
return
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.message.includes('Malformed extraction path')) {
|
|
||||||
throw new Error(
|
|
||||||
`Artifact download failed with unretryable error: ${error.message}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
retryCount++
|
retryCount++
|
||||||
core.debug(
|
core.debug(
|
||||||
`Failed to download artifact after ${retryCount} retries due to ${error.message}. Retrying in 5 seconds...`
|
`Failed to download artifact after ${retryCount} retries due to ${error.message}. Retrying in 5 seconds...`
|
||||||
@@ -67,7 +65,7 @@ async function streamExtract(url: string, directory: string): Promise<void> {
|
|||||||
export async function streamExtractExternal(
|
export async function streamExtractExternal(
|
||||||
url: string,
|
url: string,
|
||||||
directory: string
|
directory: string
|
||||||
): Promise<void> {
|
): Promise<StreamExtractResponse> {
|
||||||
const client = new httpClient.HttpClient(getUserAgentString())
|
const client = new httpClient.HttpClient(getUserAgentString())
|
||||||
const response = await client.get(url)
|
const response = await client.get(url)
|
||||||
if (response.message.statusCode !== 200) {
|
if (response.message.statusCode !== 200) {
|
||||||
@@ -77,6 +75,7 @@ export async function streamExtractExternal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const timeout = 30 * 1000 // 30 seconds
|
const timeout = 30 * 1000 // 30 seconds
|
||||||
|
let sha256Digest: string | undefined = undefined
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const timerFn = (): void => {
|
const timerFn = (): void => {
|
||||||
@@ -86,9 +85,14 @@ export async function streamExtractExternal(
|
|||||||
}
|
}
|
||||||
const timer = setTimeout(timerFn, timeout)
|
const timer = setTimeout(timerFn, timeout)
|
||||||
|
|
||||||
const createdDirectories = new Set<string>()
|
const hashStream = crypto.createHash('sha256').setEncoding('hex')
|
||||||
createdDirectories.add(directory)
|
const passThrough = new stream.PassThrough()
|
||||||
response.message
|
|
||||||
|
response.message.pipe(passThrough)
|
||||||
|
passThrough.pipe(hashStream)
|
||||||
|
const extractStream = passThrough
|
||||||
|
|
||||||
|
extractStream
|
||||||
.on('data', () => {
|
.on('data', () => {
|
||||||
timer.refresh()
|
timer.refresh()
|
||||||
})
|
})
|
||||||
@@ -99,48 +103,15 @@ export async function streamExtractExternal(
|
|||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
reject(error)
|
reject(error)
|
||||||
})
|
})
|
||||||
.pipe(unzip.Parse())
|
.pipe(unzip.Extract({path: directory}))
|
||||||
.pipe(
|
.on('close', () => {
|
||||||
new stream.Transform({
|
|
||||||
objectMode: true,
|
|
||||||
transform: async (entry, _, callback) => {
|
|
||||||
const fullPath = path.normalize(path.join(directory, entry.path))
|
|
||||||
if (!directory.endsWith(path.sep)) {
|
|
||||||
directory += path.sep
|
|
||||||
}
|
|
||||||
if (!fullPath.startsWith(directory)) {
|
|
||||||
reject(new Error(`Malformed extraction path: ${fullPath}`))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.type === 'Directory') {
|
|
||||||
if (!createdDirectories.has(fullPath)) {
|
|
||||||
createdDirectories.add(fullPath)
|
|
||||||
await resolveOrCreateDirectory(fullPath).then(() => {
|
|
||||||
entry.autodrain()
|
|
||||||
callback()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
entry.autodrain()
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
core.info(`Extracting artifact entry: ${fullPath}`)
|
|
||||||
if (!createdDirectories.has(path.dirname(fullPath))) {
|
|
||||||
createdDirectories.add(path.dirname(fullPath))
|
|
||||||
await resolveOrCreateDirectory(path.dirname(fullPath))
|
|
||||||
}
|
|
||||||
|
|
||||||
const writeStream = createWriteStream(fullPath)
|
|
||||||
writeStream.on('finish', callback)
|
|
||||||
writeStream.on('error', reject)
|
|
||||||
entry.pipe(writeStream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.on('finish', async () => {
|
|
||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
resolve()
|
if (hashStream) {
|
||||||
|
hashStream.end()
|
||||||
|
sha256Digest = hashStream.read() as string
|
||||||
|
core.info(`SHA256 digest of downloaded artifact is ${sha256Digest}`)
|
||||||
|
}
|
||||||
|
resolve({sha256Digest: `sha256:${sha256Digest}`})
|
||||||
})
|
})
|
||||||
.on('error', (error: Error) => {
|
.on('error', (error: Error) => {
|
||||||
reject(error)
|
reject(error)
|
||||||
@@ -159,6 +130,8 @@ export async function downloadArtifactPublic(
|
|||||||
|
|
||||||
const api = github.getOctokit(token)
|
const api = github.getOctokit(token)
|
||||||
|
|
||||||
|
let digestMismatch = false
|
||||||
|
|
||||||
core.info(
|
core.info(
|
||||||
`Downloading artifact '${artifactId}' from '${repositoryOwner}/${repositoryName}'`
|
`Downloading artifact '${artifactId}' from '${repositoryOwner}/${repositoryName}'`
|
||||||
)
|
)
|
||||||
@@ -188,13 +161,20 @@ export async function downloadArtifactPublic(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
core.info(`Starting download of artifact to: ${downloadPath}`)
|
core.info(`Starting download of artifact to: ${downloadPath}`)
|
||||||
await streamExtract(location, downloadPath)
|
const extractResponse = await streamExtract(location, downloadPath)
|
||||||
core.info(`Artifact download completed successfully.`)
|
core.info(`Artifact download completed successfully.`)
|
||||||
|
if (options?.expectedHash) {
|
||||||
|
if (options?.expectedHash !== extractResponse.sha256Digest) {
|
||||||
|
digestMismatch = true
|
||||||
|
core.debug(`Computed digest: ${extractResponse.sha256Digest}`)
|
||||||
|
core.debug(`Expected digest: ${options.expectedHash}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Unable to download and extract artifact: ${error.message}`)
|
throw new Error(`Unable to download and extract artifact: ${error.message}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {downloadPath}
|
return {downloadPath, digestMismatch}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadArtifactInternal(
|
export async function downloadArtifactInternal(
|
||||||
@@ -205,6 +185,8 @@ export async function downloadArtifactInternal(
|
|||||||
|
|
||||||
const artifactClient = internalArtifactTwirpClient()
|
const artifactClient = internalArtifactTwirpClient()
|
||||||
|
|
||||||
|
let digestMismatch = false
|
||||||
|
|
||||||
const {workflowRunBackendId, workflowJobRunBackendId} =
|
const {workflowRunBackendId, workflowJobRunBackendId} =
|
||||||
getBackendIdsFromToken()
|
getBackendIdsFromToken()
|
||||||
|
|
||||||
@@ -240,13 +222,20 @@ export async function downloadArtifactInternal(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
core.info(`Starting download of artifact to: ${downloadPath}`)
|
core.info(`Starting download of artifact to: ${downloadPath}`)
|
||||||
await streamExtract(signedUrl, downloadPath)
|
const extractResponse = await streamExtract(signedUrl, downloadPath)
|
||||||
core.info(`Artifact download completed successfully.`)
|
core.info(`Artifact download completed successfully.`)
|
||||||
|
if (options?.expectedHash) {
|
||||||
|
if (options?.expectedHash !== extractResponse.sha256Digest) {
|
||||||
|
digestMismatch = true
|
||||||
|
core.debug(`Computed digest: ${extractResponse.sha256Digest}`)
|
||||||
|
core.debug(`Expected digest: ${options.expectedHash}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Unable to download and extract artifact: ${error.message}`)
|
throw new Error(`Unable to download and extract artifact: ${error.message}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {downloadPath}
|
return {downloadPath, digestMismatch}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveOrCreateDirectory(
|
async function resolveOrCreateDirectory(
|
||||||
|
|||||||
@@ -68,7 +68,10 @@ export async function getArtifactPublic(
|
|||||||
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
|
createdAt: artifact.created_at
|
||||||
|
? new Date(artifact.created_at)
|
||||||
|
: undefined,
|
||||||
|
digest: artifact.digest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,7 +118,8 @@ export async function getArtifactInternal(
|
|||||||
size: Number(artifact.size),
|
size: Number(artifact.size),
|
||||||
createdAt: artifact.createdAt
|
createdAt: artifact.createdAt
|
||||||
? Timestamp.toDate(artifact.createdAt)
|
? Timestamp.toDate(artifact.createdAt)
|
||||||
: undefined
|
: undefined,
|
||||||
|
digest: artifact.digest?.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,14 +41,17 @@ export async function listArtifactsPublic(
|
|||||||
const github = getOctokit(token, opts, retry, requestLog)
|
const github = getOctokit(token, opts, retry, requestLog)
|
||||||
|
|
||||||
let currentPageNumber = 1
|
let currentPageNumber = 1
|
||||||
const {data: listArtifactResponse} =
|
|
||||||
await github.rest.actions.listWorkflowRunArtifacts({
|
const {data: listArtifactResponse} = await github.request(
|
||||||
|
'GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts',
|
||||||
|
{
|
||||||
owner: repositoryOwner,
|
owner: repositoryOwner,
|
||||||
repo: repositoryName,
|
repo: repositoryName,
|
||||||
run_id: workflowRunId,
|
run_id: workflowRunId,
|
||||||
per_page: paginationCount,
|
per_page: paginationCount,
|
||||||
page: currentPageNumber
|
page: currentPageNumber
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
let numberOfPages = Math.ceil(
|
let numberOfPages = Math.ceil(
|
||||||
listArtifactResponse.total_count / paginationCount
|
listArtifactResponse.total_count / paginationCount
|
||||||
@@ -67,27 +70,32 @@ export async function listArtifactsPublic(
|
|||||||
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
|
createdAt: artifact.created_at
|
||||||
|
? new Date(artifact.created_at)
|
||||||
|
: undefined,
|
||||||
|
digest: (artifact as ArtifactResponse).digest
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// Move to the next page
|
||||||
|
currentPageNumber++
|
||||||
// Iterate over any remaining pages
|
// Iterate over any remaining pages
|
||||||
for (
|
for (
|
||||||
currentPageNumber;
|
currentPageNumber;
|
||||||
currentPageNumber < numberOfPages;
|
currentPageNumber < numberOfPages;
|
||||||
currentPageNumber++
|
currentPageNumber++
|
||||||
) {
|
) {
|
||||||
currentPageNumber++
|
|
||||||
debug(`Fetching page ${currentPageNumber} of artifact list`)
|
debug(`Fetching page ${currentPageNumber} of artifact list`)
|
||||||
|
|
||||||
const {data: listArtifactResponse} =
|
const {data: listArtifactResponse} = await github.request(
|
||||||
await github.rest.actions.listWorkflowRunArtifacts({
|
'GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts',
|
||||||
|
{
|
||||||
owner: repositoryOwner,
|
owner: repositoryOwner,
|
||||||
repo: repositoryName,
|
repo: repositoryName,
|
||||||
run_id: workflowRunId,
|
run_id: workflowRunId,
|
||||||
per_page: paginationCount,
|
per_page: paginationCount,
|
||||||
page: currentPageNumber
|
page: currentPageNumber
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
for (const artifact of listArtifactResponse.artifacts) {
|
for (const artifact of listArtifactResponse.artifacts) {
|
||||||
artifacts.push({
|
artifacts.push({
|
||||||
@@ -96,7 +104,8 @@ export async function listArtifactsPublic(
|
|||||||
size: artifact.size_in_bytes,
|
size: artifact.size_in_bytes,
|
||||||
createdAt: artifact.created_at
|
createdAt: artifact.created_at
|
||||||
? new Date(artifact.created_at)
|
? new Date(artifact.created_at)
|
||||||
: undefined
|
: undefined,
|
||||||
|
digest: (artifact as ArtifactResponse).digest
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,7 +141,8 @@ export async function listArtifactsInternal(
|
|||||||
size: Number(artifact.size),
|
size: Number(artifact.size),
|
||||||
createdAt: artifact.createdAt
|
createdAt: artifact.createdAt
|
||||||
? Timestamp.toDate(artifact.createdAt)
|
? Timestamp.toDate(artifact.createdAt)
|
||||||
: undefined
|
: undefined,
|
||||||
|
digest: artifact.digest?.value
|
||||||
}))
|
}))
|
||||||
|
|
||||||
if (latest) {
|
if (latest) {
|
||||||
@@ -146,6 +156,18 @@ export async function listArtifactsInternal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exists so that we don't have to use 'any' when receiving the artifact list from the GitHub API.
|
||||||
|
* The digest field is not present in OpenAPI/types at time of writing, which necessitates this change.
|
||||||
|
*/
|
||||||
|
interface ArtifactResponse {
|
||||||
|
name: string
|
||||||
|
id: number
|
||||||
|
size_in_bytes: number
|
||||||
|
created_at?: string
|
||||||
|
digest?: string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters a list of artifacts to only include the latest artifact for each name
|
* Filters a list of artifacts to only include the latest artifact for each name
|
||||||
* @param artifacts The artifacts to filter
|
* @param artifacts The artifacts to filter
|
||||||
|
|||||||
@@ -102,7 +102,6 @@ class ArtifactHttpClient implements Rpc {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof SyntaxError) {
|
if (error instanceof SyntaxError) {
|
||||||
debug(`Raw Body: ${rawBody}`)
|
debug(`Raw Body: ${rawBody}`)
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error instanceof UsageError) {
|
if (error instanceof UsageError) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import os from 'os'
|
import os from 'os'
|
||||||
|
import {info} from '@actions/core'
|
||||||
|
|
||||||
// Used for controlling the highWaterMark value of the zip that is being streamed
|
// Used for controlling the highWaterMark value of the zip that is being streamed
|
||||||
// The same value is used as the chunk size that is use during upload to blob storage
|
// The same value is used as the chunk size that is use during upload to blob storage
|
||||||
@@ -30,10 +31,10 @@ export function isGhes(): boolean {
|
|||||||
|
|
||||||
const hostname = ghUrl.hostname.trimEnd().toUpperCase()
|
const hostname = ghUrl.hostname.trimEnd().toUpperCase()
|
||||||
const isGitHubHost = hostname === 'GITHUB.COM'
|
const isGitHubHost = hostname === 'GITHUB.COM'
|
||||||
const isGheHost =
|
const isGheHost = hostname.endsWith('.GHE.COM')
|
||||||
hostname.endsWith('.GHE.COM') || hostname.endsWith('.GHE.LOCALHOST')
|
const isLocalHost = hostname.endsWith('.LOCALHOST')
|
||||||
|
|
||||||
return !isGitHubHost && !isGheHost
|
return !isGitHubHost && !isGheHost && !isLocalHost
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getGitHubWorkspaceDir(): string {
|
export function getGitHubWorkspaceDir(): string {
|
||||||
@@ -44,16 +45,55 @@ export function getGitHubWorkspaceDir(): string {
|
|||||||
return ghWorkspaceDir
|
return ghWorkspaceDir
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mimics behavior of azcopy: https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azcopy-optimize
|
// The maximum value of concurrency is 300.
|
||||||
// If your machine has fewer than 5 CPUs, then the value of this variable is set to 32.
|
// This value can be changed with ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY variable.
|
||||||
// Otherwise, the default value is equal to 16 multiplied by the number of CPUs. The maximum value of this variable is 300.
|
|
||||||
export function getConcurrency(): number {
|
export function getConcurrency(): number {
|
||||||
const numCPUs = os.cpus().length
|
const numCPUs = os.cpus().length
|
||||||
|
let concurrencyCap = 32
|
||||||
|
|
||||||
if (numCPUs <= 4) {
|
if (numCPUs > 4) {
|
||||||
return 32
|
const concurrency = 16 * numCPUs
|
||||||
|
concurrencyCap = concurrency > 300 ? 300 : concurrency
|
||||||
}
|
}
|
||||||
|
|
||||||
const concurrency = 16 * numCPUs
|
const concurrencyOverride = process.env['ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY']
|
||||||
return concurrency > 300 ? 300 : concurrency
|
if (concurrencyOverride) {
|
||||||
|
const concurrency = parseInt(concurrencyOverride)
|
||||||
|
if (isNaN(concurrency) || concurrency < 1) {
|
||||||
|
throw new Error(
|
||||||
|
'Invalid value set for ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY env variable'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (concurrency < concurrencyCap) {
|
||||||
|
info(
|
||||||
|
`Set concurrency based on the value set in ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY.`
|
||||||
|
)
|
||||||
|
return concurrency
|
||||||
|
}
|
||||||
|
|
||||||
|
info(
|
||||||
|
`ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY is higher than the cap of ${concurrencyCap} based on the number of cpus. Set it to the maximum value allowed.`
|
||||||
|
)
|
||||||
|
return concurrencyCap
|
||||||
|
}
|
||||||
|
|
||||||
|
// default concurrency to 5
|
||||||
|
return 5
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUploadChunkTimeout(): number {
|
||||||
|
const timeoutVar = process.env['ACTIONS_ARTIFACT_UPLOAD_TIMEOUT_MS']
|
||||||
|
if (!timeoutVar) {
|
||||||
|
return 300000 // 5 minutes
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeout = parseInt(timeoutVar)
|
||||||
|
if (isNaN(timeout)) {
|
||||||
|
throw new Error(
|
||||||
|
'Invalid value set for ACTIONS_ARTIFACT_UPLOAD_TIMEOUT_MS env variable'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return timeout
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ export interface UploadArtifactResponse {
|
|||||||
* This ID can be used as input to other APIs to download, delete or get more information about an artifact: https://docs.github.com/en/rest/actions/artifacts
|
* This ID can be used as input to other APIs to download, delete or get more information about an artifact: https://docs.github.com/en/rest/actions/artifacts
|
||||||
*/
|
*/
|
||||||
id?: number
|
id?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SHA256 digest of the artifact that was created. Not provided if no artifact was uploaded
|
||||||
|
*/
|
||||||
|
digest?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,6 +91,11 @@ export interface DownloadArtifactResponse {
|
|||||||
* The path where the artifact was downloaded to
|
* The path where the artifact was downloaded to
|
||||||
*/
|
*/
|
||||||
downloadPath?: string
|
downloadPath?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the digest of the downloaded artifact does not match the expected hash
|
||||||
|
*/
|
||||||
|
digestMismatch?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,6 +106,20 @@ export interface DownloadArtifactOptions {
|
|||||||
* Denotes where the artifact will be downloaded to. If not specified then the artifact is download to GITHUB_WORKSPACE
|
* Denotes where the artifact will be downloaded to. If not specified then the artifact is download to GITHUB_WORKSPACE
|
||||||
*/
|
*/
|
||||||
path?: string
|
path?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hash that was computed for the artifact during upload. If provided, the outcome of the download
|
||||||
|
* will provide a digestMismatch property indicating whether the hash of the downloaded artifact
|
||||||
|
* matches the expected hash.
|
||||||
|
*/
|
||||||
|
expectedHash?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StreamExtractResponse {
|
||||||
|
/**
|
||||||
|
* The SHA256 hash of the downloaded file
|
||||||
|
*/
|
||||||
|
sha256Digest?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -121,6 +145,11 @@ export interface Artifact {
|
|||||||
* The time when the artifact was created
|
* The time when the artifact was created
|
||||||
*/
|
*/
|
||||||
createdAt?: Date
|
createdAt?: Date
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The digest of the artifact, computed at time of upload.
|
||||||
|
*/
|
||||||
|
digest?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import {BlobClient, BlockBlobUploadStreamOptions} from '@azure/storage-blob'
|
import {BlobClient, BlockBlobUploadStreamOptions} from '@azure/storage-blob'
|
||||||
import {TransferProgressEvent} from '@azure/core-http'
|
import {TransferProgressEvent} from '@azure/core-http'
|
||||||
import {ZipUploadStream} from './zip'
|
import {ZipUploadStream} from './zip'
|
||||||
import {getUploadChunkSize, getConcurrency} from '../shared/config'
|
import {
|
||||||
|
getUploadChunkSize,
|
||||||
|
getConcurrency,
|
||||||
|
getUploadChunkTimeout
|
||||||
|
} 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'
|
||||||
@@ -24,6 +28,22 @@ export async function uploadZipToBlobStorage(
|
|||||||
zipUploadStream: ZipUploadStream
|
zipUploadStream: ZipUploadStream
|
||||||
): Promise<BlobUploadResponse> {
|
): Promise<BlobUploadResponse> {
|
||||||
let uploadByteCount = 0
|
let uploadByteCount = 0
|
||||||
|
let lastProgressTime = Date.now()
|
||||||
|
const abortController = new AbortController()
|
||||||
|
|
||||||
|
const chunkTimer = async (interval: number): Promise<void> =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
if (Date.now() - lastProgressTime > interval) {
|
||||||
|
reject(new Error('Upload progress stalled.'))
|
||||||
|
}
|
||||||
|
}, interval)
|
||||||
|
|
||||||
|
abortController.signal.addEventListener('abort', () => {
|
||||||
|
clearInterval(timer)
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const maxConcurrency = getConcurrency()
|
const maxConcurrency = getConcurrency()
|
||||||
const bufferSize = getUploadChunkSize()
|
const bufferSize = getUploadChunkSize()
|
||||||
@@ -37,11 +57,13 @@ export async function uploadZipToBlobStorage(
|
|||||||
const uploadCallback = (progress: TransferProgressEvent): void => {
|
const uploadCallback = (progress: TransferProgressEvent): void => {
|
||||||
core.info(`Uploaded bytes ${progress.loadedBytes}`)
|
core.info(`Uploaded bytes ${progress.loadedBytes}`)
|
||||||
uploadByteCount = progress.loadedBytes
|
uploadByteCount = progress.loadedBytes
|
||||||
|
lastProgressTime = Date.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: BlockBlobUploadStreamOptions = {
|
const options: BlockBlobUploadStreamOptions = {
|
||||||
blobHTTPHeaders: {blobContentType: 'zip'},
|
blobHTTPHeaders: {blobContentType: 'zip'},
|
||||||
onProgress: uploadCallback
|
onProgress: uploadCallback,
|
||||||
|
abortSignal: abortController.signal
|
||||||
}
|
}
|
||||||
|
|
||||||
let sha256Hash: string | undefined = undefined
|
let sha256Hash: string | undefined = undefined
|
||||||
@@ -54,18 +76,22 @@ 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 {
|
try {
|
||||||
await blockBlobClient.uploadStream(
|
await Promise.race([
|
||||||
uploadStream,
|
blockBlobClient.uploadStream(
|
||||||
bufferSize,
|
uploadStream,
|
||||||
maxConcurrency,
|
bufferSize,
|
||||||
options
|
maxConcurrency,
|
||||||
)
|
options
|
||||||
|
),
|
||||||
|
chunkTimer(getUploadChunkTimeout())
|
||||||
|
])
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (NetworkError.isNetworkErrorCode(error?.code)) {
|
if (NetworkError.isNetworkErrorCode(error?.code)) {
|
||||||
throw new NetworkError(error?.code)
|
throw new NetworkError(error?.code)
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error
|
throw error
|
||||||
|
} finally {
|
||||||
|
abortController.abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
core.info('Finished uploading artifact content to blob storage!')
|
core.info('Finished uploading artifact content to blob storage!')
|
||||||
@@ -79,7 +105,6 @@ export async function uploadZipToBlobStorage(
|
|||||||
`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 {
|
return {
|
||||||
uploadSize: uploadByteCount,
|
uploadSize: uploadByteCount,
|
||||||
sha256Hash
|
sha256Hash
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ export async function uploadArtifact(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
size: uploadResult.uploadSize,
|
size: uploadResult.uploadSize,
|
||||||
|
digest: uploadResult.sha256Hash,
|
||||||
id: Number(artifactId)
|
id: Number(artifactId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ export interface UploadZipSpecification {
|
|||||||
* The destination path in a zip for a file
|
* The destination path in a zip for a file
|
||||||
*/
|
*/
|
||||||
destinationPath: string
|
destinationPath: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information about the file
|
||||||
|
* https://nodejs.org/api/fs.html#class-fsstats
|
||||||
|
*/
|
||||||
|
stats: fs.Stats
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,10 +81,11 @@ export function getUploadZipSpecification(
|
|||||||
- file3.txt
|
- file3.txt
|
||||||
*/
|
*/
|
||||||
for (let file of filesToZip) {
|
for (let file of filesToZip) {
|
||||||
if (!fs.existsSync(file)) {
|
const stats = fs.lstatSync(file, {throwIfNoEntry: false})
|
||||||
|
if (!stats) {
|
||||||
throw new Error(`File ${file} does not exist`)
|
throw new Error(`File ${file} does not exist`)
|
||||||
}
|
}
|
||||||
if (!fs.statSync(file).isDirectory()) {
|
if (!stats.isDirectory()) {
|
||||||
// Normalize and resolve, this allows for either absolute or relative paths to be used
|
// Normalize and resolve, this allows for either absolute or relative paths to be used
|
||||||
file = normalize(file)
|
file = normalize(file)
|
||||||
file = resolve(file)
|
file = resolve(file)
|
||||||
@@ -94,7 +101,8 @@ export function getUploadZipSpecification(
|
|||||||
|
|
||||||
specification.push({
|
specification.push({
|
||||||
sourcePath: file,
|
sourcePath: file,
|
||||||
destinationPath: uploadPath
|
destinationPath: uploadPath,
|
||||||
|
stats
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Empty directory
|
// Empty directory
|
||||||
@@ -103,7 +111,8 @@ export function getUploadZipSpecification(
|
|||||||
|
|
||||||
specification.push({
|
specification.push({
|
||||||
sourcePath: null,
|
sourcePath: null,
|
||||||
destinationPath: directoryPath
|
destinationPath: directoryPath,
|
||||||
|
stats
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as stream from 'stream'
|
import * as stream from 'stream'
|
||||||
|
import {realpath} from 'fs/promises'
|
||||||
import * as archiver from 'archiver'
|
import * as archiver from 'archiver'
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {createReadStream} from 'fs'
|
|
||||||
import {UploadZipSpecification} from './upload-zip-specification'
|
import {UploadZipSpecification} from './upload-zip-specification'
|
||||||
import {getUploadChunkSize} from '../shared/config'
|
import {getUploadChunkSize} from '../shared/config'
|
||||||
|
|
||||||
@@ -43,8 +43,14 @@ export async function createZipUploadStream(
|
|||||||
|
|
||||||
for (const file of uploadSpecification) {
|
for (const file of uploadSpecification) {
|
||||||
if (file.sourcePath !== null) {
|
if (file.sourcePath !== null) {
|
||||||
// Add a normal file to the zip
|
// Check if symlink and resolve the source path
|
||||||
zip.append(createReadStream(file.sourcePath), {
|
let sourcePath = file.sourcePath
|
||||||
|
if (file.stats.isSymbolicLink()) {
|
||||||
|
sourcePath = await realpath(file.sourcePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the file to the zip
|
||||||
|
zip.file(sourcePath, {
|
||||||
name: file.destinationPath
|
name: file.destinationPath
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+32
-13
@@ -12,6 +12,9 @@ Once the attestation has been created and signed, it will be uploaded to the GH
|
|||||||
attestations API and associated with the repository from which the workflow was
|
attestations API and associated with the repository from which the workflow was
|
||||||
initiated.
|
initiated.
|
||||||
|
|
||||||
|
See [Using artifact attestations to establish provenance for builds](https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)
|
||||||
|
for more information on artifact attestations.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### `attest`
|
### `attest`
|
||||||
@@ -29,8 +32,7 @@ async function run() {
|
|||||||
const ghToken = core.getInput('gh-token');
|
const ghToken = core.getInput('gh-token');
|
||||||
|
|
||||||
const attestation = await attest({
|
const attestation = await attest({
|
||||||
subjectName: 'my-artifact-name',
|
subjects: [{name: 'my-artifact-name', digest: { 'sha256': '36ab4667...'}}],
|
||||||
subjectDigest: { 'sha256': '36ab4667...'},
|
|
||||||
predicateType: 'https://in-toto.io/attestation/release',
|
predicateType: 'https://in-toto.io/attestation/release',
|
||||||
predicate: { . . . },
|
predicate: { . . . },
|
||||||
token: ghToken
|
token: ghToken
|
||||||
@@ -46,11 +48,12 @@ The `attest` function supports the following options:
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export type AttestOptions = {
|
export type AttestOptions = {
|
||||||
// The name of the subject to be attested.
|
// Deprecated. Use 'subjects' instead.
|
||||||
subjectName: string
|
subjectName?: string
|
||||||
// The digest of the subject to be attested. Should be a map of digest
|
// Deprecated. Use 'subjects' instead.
|
||||||
// algorithms to their hex-encoded values.
|
subjectDigest?: Record<string, string>
|
||||||
subjectDigest: Record<string, string>
|
// Collection of subjects to be attested
|
||||||
|
subjects?: Subject[]
|
||||||
// URI identifying the content type of the predicate being attested.
|
// URI identifying the content type of the predicate being attested.
|
||||||
predicateType: string
|
predicateType: string
|
||||||
// Predicate to be attested.
|
// Predicate to be attested.
|
||||||
@@ -60,9 +63,18 @@ export type AttestOptions = {
|
|||||||
// Sigstore instance to use for signing. Must be one of "public-good" or
|
// Sigstore instance to use for signing. Must be one of "public-good" or
|
||||||
// "github".
|
// "github".
|
||||||
sigstore?: 'public-good' | 'github'
|
sigstore?: 'public-good' | 'github'
|
||||||
|
// HTTP headers to include in request to attestations API.
|
||||||
|
headers?: {[header: string]: string | number | undefined}
|
||||||
// Whether to skip writing the attestation to the GH attestations API.
|
// Whether to skip writing the attestation to the GH attestations API.
|
||||||
skipWrite?: boolean
|
skipWrite?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Subject = {
|
||||||
|
// Name of the subject.
|
||||||
|
name: string
|
||||||
|
// Digests of the subject. Should be a map of digest algorithms to their hex-encoded values.
|
||||||
|
digest: Record<string, string>
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `attestProvenance`
|
### `attestProvenance`
|
||||||
@@ -100,18 +112,25 @@ The `attestProvenance` function supports the following options:
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export type AttestProvenanceOptions = {
|
export type AttestProvenanceOptions = {
|
||||||
// The name of the subject to be attested.
|
// Deprecated. Use 'subjects' instead.
|
||||||
subjectName: string
|
subjectName?: string
|
||||||
// The digest of the subject to be attested. Should be a map of digest
|
// Deprecated. Use 'subjects' instead.
|
||||||
// algorithms to their hex-encoded values.
|
subjectDigest?: Record<string, string>
|
||||||
subjectDigest: Record<string, string>
|
// Collection of subjects to be attested
|
||||||
// GitHub token for writing attestations.
|
subjects?: Subject[]
|
||||||
|
// URI identifying the content type of the predicate being attested.
|
||||||
token: string
|
token: string
|
||||||
// Sigstore instance to use for signing. Must be one of "public-good" or
|
// Sigstore instance to use for signing. Must be one of "public-good" or
|
||||||
// "github".
|
// "github".
|
||||||
sigstore?: 'public-good' | 'github'
|
sigstore?: 'public-good' | 'github'
|
||||||
|
// HTTP headers to include in request to attestations API.
|
||||||
|
headers?: {[header: string]: string | number | undefined}
|
||||||
// Whether to skip writing the attestation to the GH attestations API.
|
// Whether to skip writing the attestation to the GH attestations API.
|
||||||
skipWrite?: boolean
|
skipWrite?: boolean
|
||||||
|
// Issuer URL responsible for minting the OIDC token from which the
|
||||||
|
// provenance data is read. Defaults to
|
||||||
|
// 'https://token.actions.githubusercontent.com".
|
||||||
|
issuer?: string
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,56 @@
|
|||||||
# @actions/attest Releases
|
# @actions/attest Releases
|
||||||
|
|
||||||
|
### 1.6.0
|
||||||
|
|
||||||
|
- Update `buildSLSAProvenancePredicate` to populate `workflow.ref` field from the `ref` claim in the OIDC token [#1969](https://github.com/actions/toolkit/pull/1969)
|
||||||
|
|
||||||
|
### 1.5.0
|
||||||
|
|
||||||
|
- Bump @actions/core from 1.10.1 to 1.11.1 [#1847](https://github.com/actions/toolkit/pull/1847)
|
||||||
|
- Bump @sigstore/bundle from 2.3.2 to 3.0.0 [#1846](https://github.com/actions/toolkit/pull/1846)
|
||||||
|
- Bump @sigstore/sign from 2.3.2 to 3.0.0 [#1846](https://github.com/actions/toolkit/pull/1846)
|
||||||
|
- Support for generating multi-subject attestations [#1864](https://github.com/actions/toolkit/pull/1865)
|
||||||
|
- Fix bug in `buildSLSAProvenancePredicate` related to `workflow_ref` OIDC token claims containing the "@" symbol in the tag name [#1863](https://github.com/actions/toolkit/pull/1863)
|
||||||
|
|
||||||
|
### 1.4.2
|
||||||
|
|
||||||
|
- Fix bug in `buildSLSAProvenancePredicate`/`attestProvenance` when generating provenance statement for enterprise account using customized OIDC issuer value [#1823](https://github.com/actions/toolkit/pull/1823)
|
||||||
|
|
||||||
|
### 1.4.1
|
||||||
|
|
||||||
|
- Bump @actions/http-client from 2.2.1 to 2.2.3 [#1805](https://github.com/actions/toolkit/pull/1805)
|
||||||
|
|
||||||
|
### 1.4.0
|
||||||
|
|
||||||
|
- Add new `headers` parameter to the `attest` and `attestProvenance` functions [#1790](https://github.com/actions/toolkit/pull/1790)
|
||||||
|
- Update `buildSLSAProvenancePredicate`/`attestProvenance` to automatically derive default OIDC issuer URL from current execution context [#1796](https://github.com/actions/toolkit/pull/1796)
|
||||||
|
### 1.3.1
|
||||||
|
|
||||||
|
- Fix bug with proxy support when retrieving JWKS for OIDC issuer [#1776](https://github.com/actions/toolkit/pull/1776)
|
||||||
|
|
||||||
|
### 1.3.0
|
||||||
|
|
||||||
|
- Dynamic construction of Sigstore API URLs [#1735](https://github.com/actions/toolkit/pull/1735)
|
||||||
|
- Switch to new GH provenance build type [#1745](https://github.com/actions/toolkit/pull/1745)
|
||||||
|
- Fetch existing Rekor entry on 409 conflict error [#1759](https://github.com/actions/toolkit/pull/1759)
|
||||||
|
- Bump @sigstore/bundle from 2.3.0 to 2.3.2 [#1738](https://github.com/actions/toolkit/pull/1738)
|
||||||
|
- Bump @sigstore/sign from 2.3.0 to 2.3.2 [#1738](https://github.com/actions/toolkit/pull/1738)
|
||||||
|
|
||||||
|
### 1.2.1
|
||||||
|
|
||||||
|
- Retry request on attestation persistence failure [#1725](https://github.com/actions/toolkit/pull/1725)
|
||||||
|
|
||||||
|
### 1.2.0
|
||||||
|
|
||||||
|
- Generate attestations using the v0.3 Sigstore bundle format [#1701](https://github.com/actions/toolkit/pull/1701)
|
||||||
|
- Bump @sigstore/bundle from 2.2.0 to 2.3.0 [#1701](https://github.com/actions/toolkit/pull/1701)
|
||||||
|
- Bump @sigstore/sign from 2.2.3 to 2.3.0 [#1701](https://github.com/actions/toolkit/pull/1701)
|
||||||
|
- Remove dependency on make-fetch-happen [#1714](https://github.com/actions/toolkit/pull/1714)
|
||||||
|
|
||||||
|
### 1.1.0
|
||||||
|
|
||||||
|
- Updates the `attestProvenance` function to retrieve a token from the GitHub OIDC provider and use the token claims to populate the provenance statement [#1693](https://github.com/actions/toolkit/pull/1693)
|
||||||
|
|
||||||
### 1.0.0
|
### 1.0.0
|
||||||
|
|
||||||
- Initial release
|
- Initial release
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`buildIntotoStatement returns a provenance hydrated from env vars 1`] = `
|
exports[`buildIntotoStatement returns an intoto statement 1`] = `
|
||||||
{
|
{
|
||||||
"_type": "https://in-toto.io/Statement/v1",
|
"_type": "https://in-toto.io/Statement/v1",
|
||||||
"predicate": {
|
"predicate": {
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`buildSLSAProvenancePredicate returns a provenance hydrated from env vars 1`] = `
|
exports[`provenance functions buildSLSAProvenancePredicate returns a provenance hydrated from an OIDC token 1`] = `
|
||||||
{
|
{
|
||||||
"params": {
|
"params": {
|
||||||
"buildDefinition": {
|
"buildDefinition": {
|
||||||
"buildType": "https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1",
|
"buildType": "https://actions.github.io/buildtypes/workflow/v1",
|
||||||
"externalParameters": {
|
"externalParameters": {
|
||||||
"workflow": {
|
"workflow": {
|
||||||
"path": ".github/workflows/main.yml",
|
"path": ".github/workflows/main.yml",
|
||||||
"ref": "main",
|
"ref": "refs/heads/main",
|
||||||
"repository": "https://github.com/owner/repo",
|
"repository": "https://foo.ghe.com/owner/repo",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"internalParameters": {
|
"internalParameters": {
|
||||||
@@ -17,6 +17,7 @@ exports[`buildSLSAProvenancePredicate returns a provenance hydrated from env var
|
|||||||
"event_name": "push",
|
"event_name": "push",
|
||||||
"repository_id": "repo-id",
|
"repository_id": "repo-id",
|
||||||
"repository_owner_id": "owner-id",
|
"repository_owner_id": "owner-id",
|
||||||
|
"runner_environment": "github-hosted",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"resolvedDependencies": [
|
"resolvedDependencies": [
|
||||||
@@ -24,16 +25,16 @@ exports[`buildSLSAProvenancePredicate returns a provenance hydrated from env var
|
|||||||
"digest": {
|
"digest": {
|
||||||
"gitCommit": "babca52ab0c93ae16539e5923cb0d7403b9a093b",
|
"gitCommit": "babca52ab0c93ae16539e5923cb0d7403b9a093b",
|
||||||
},
|
},
|
||||||
"uri": "git+https://github.com/owner/repo@refs/heads/main",
|
"uri": "git+https://foo.ghe.com/owner/repo@refs/heads/main",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"runDetails": {
|
"runDetails": {
|
||||||
"builder": {
|
"builder": {
|
||||||
"id": "https://github.com/actions/runner/github-hosted",
|
"id": "https://foo.ghe.com/owner/workflows/.github/workflows/publish.yml@main",
|
||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"invocationId": "https://github.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
|
"invocationId": "https://foo.ghe.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import {attest} from '../src/attest'
|
||||||
|
|
||||||
|
describe('attest', () => {
|
||||||
|
describe('when no subject information is provided', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
const options = {
|
||||||
|
predicateType: 'foo',
|
||||||
|
predicate: {bar: 'baz'},
|
||||||
|
token: 'token'
|
||||||
|
}
|
||||||
|
expect(attest(options)).rejects.toThrowError(
|
||||||
|
'Must provide either subjectName and subjectDigest or subjects'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import {signingEndpoints} from '../src/endpoints'
|
||||||
|
|
||||||
|
describe('signingEndpoints', () => {
|
||||||
|
const originalEnv = process.env
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env = originalEnv
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when using github.com', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
process.env = {
|
||||||
|
...originalEnv,
|
||||||
|
GITHUB_SERVER_URL: 'https://github.com'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns expected endpoints', async () => {
|
||||||
|
const endpoints = signingEndpoints('github')
|
||||||
|
|
||||||
|
expect(endpoints.fulcioURL).toEqual('https://fulcio.githubapp.com')
|
||||||
|
expect(endpoints.tsaServerURL).toEqual('https://timestamp.githubapp.com')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when using custom domain', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
process.env = {
|
||||||
|
...originalEnv,
|
||||||
|
GITHUB_SERVER_URL: 'https://foo.bar.com'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns a expected endpoints', async () => {
|
||||||
|
const endpoints = signingEndpoints('github')
|
||||||
|
|
||||||
|
expect(endpoints.fulcioURL).toEqual('https://fulcio.foo.bar.com')
|
||||||
|
expect(endpoints.tsaServerURL).toEqual('https://timestamp.foo.bar.com')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -16,8 +16,8 @@ describe('buildIntotoStatement', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
it('returns a provenance hydrated from env vars', () => {
|
it('returns an intoto statement', () => {
|
||||||
const statement = buildIntotoStatement(subject, predicate)
|
const statement = buildIntotoStatement([subject], predicate)
|
||||||
expect(statement).toMatchSnapshot()
|
expect(statement).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import * as jose from 'jose'
|
||||||
|
import nock from 'nock'
|
||||||
|
import {getIDTokenClaims} from '../src/oidc'
|
||||||
|
|
||||||
|
describe('getIDTokenClaims', () => {
|
||||||
|
const originalEnv = process.env
|
||||||
|
const issuer = 'https://example.com'
|
||||||
|
const audience = 'nobody'
|
||||||
|
const requestToken = 'token'
|
||||||
|
const openidConfigPath = '/.well-known/openid-configuration'
|
||||||
|
const jwksPath = '/.well-known/jwks.json'
|
||||||
|
const tokenPath = '/token'
|
||||||
|
const openIDConfig = {jwks_uri: `${issuer}${jwksPath}`}
|
||||||
|
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||||
|
let key: any
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
process.env = {
|
||||||
|
...originalEnv,
|
||||||
|
ACTIONS_ID_TOKEN_REQUEST_URL: `${issuer}${tokenPath}?`,
|
||||||
|
ACTIONS_ID_TOKEN_REQUEST_TOKEN: requestToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate JWT signing key
|
||||||
|
key = await jose.generateKeyPair('PS256')
|
||||||
|
|
||||||
|
// Create JWK and JWKS
|
||||||
|
const jwk = await jose.exportJWK(key.publicKey)
|
||||||
|
const jwks = {keys: [jwk]}
|
||||||
|
|
||||||
|
nock(issuer).get(openidConfigPath).reply(200, openIDConfig)
|
||||||
|
nock(issuer).get(jwksPath).reply(200, jwks)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env = originalEnv
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when ID token is valid', () => {
|
||||||
|
const claims = {
|
||||||
|
iss: issuer,
|
||||||
|
aud: audience,
|
||||||
|
ref: 'ref',
|
||||||
|
sha: 'sha',
|
||||||
|
repository: 'repo',
|
||||||
|
event_name: 'push',
|
||||||
|
job_workflow_ref: 'job_workflow_ref',
|
||||||
|
workflow_ref: 'workflow',
|
||||||
|
repository_id: '1',
|
||||||
|
repository_owner_id: '1',
|
||||||
|
runner_environment: 'github-hosted',
|
||||||
|
run_id: '1',
|
||||||
|
run_attempt: '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const jwt = await new jose.SignJWT(claims)
|
||||||
|
.setProtectedHeader({alg: 'PS256'})
|
||||||
|
.sign(key.privateKey)
|
||||||
|
|
||||||
|
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns the ID token claims', async () => {
|
||||||
|
const result = await getIDTokenClaims(issuer)
|
||||||
|
expect(result).toEqual(claims)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when ID token is valid (w/ enterprise slug)', () => {
|
||||||
|
const claims = {
|
||||||
|
iss: `${issuer}/foo-bar`,
|
||||||
|
aud: audience,
|
||||||
|
ref: 'ref',
|
||||||
|
sha: 'sha',
|
||||||
|
repository: 'repo',
|
||||||
|
event_name: 'push',
|
||||||
|
job_workflow_ref: 'job_workflow_ref',
|
||||||
|
workflow_ref: 'workflow',
|
||||||
|
repository_id: '1',
|
||||||
|
repository_owner_id: '1',
|
||||||
|
runner_environment: 'github-hosted',
|
||||||
|
run_id: '1',
|
||||||
|
run_attempt: '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const jwt = await new jose.SignJWT(claims)
|
||||||
|
.setProtectedHeader({alg: 'PS256'})
|
||||||
|
.sign(key.privateKey)
|
||||||
|
|
||||||
|
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns the ID token claims', async () => {
|
||||||
|
const result = await getIDTokenClaims(issuer)
|
||||||
|
expect(result).toEqual(claims)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when ID token is missing the "iss" claim', () => {
|
||||||
|
const claims = {
|
||||||
|
aud: audience
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const jwt = await new jose.SignJWT(claims)
|
||||||
|
.setProtectedHeader({alg: 'PS256'})
|
||||||
|
.sign(key.privateKey)
|
||||||
|
|
||||||
|
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error', async () => {
|
||||||
|
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/missing "iss"/i)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when ID token is missing required claims', () => {
|
||||||
|
const claims = {
|
||||||
|
iss: issuer,
|
||||||
|
aud: audience
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const jwt = await new jose.SignJWT(claims)
|
||||||
|
.setProtectedHeader({alg: 'PS256'})
|
||||||
|
.sign(key.privateKey)
|
||||||
|
|
||||||
|
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error', async () => {
|
||||||
|
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/missing claims/i)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when ID has the wrong issuer', () => {
|
||||||
|
const claims = {foo: 'bar', iss: 'foo', aud: 'nobody'}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const jwt = await new jose.SignJWT(claims)
|
||||||
|
.setProtectedHeader({alg: 'PS256'})
|
||||||
|
.sign(key.privateKey)
|
||||||
|
|
||||||
|
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error', async () => {
|
||||||
|
await expect(getIDTokenClaims(issuer)).rejects.toThrow(
|
||||||
|
/unexpected "iss"/i
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when ID has the wrong audience', () => {
|
||||||
|
const claims = {foo: 'bar', iss: issuer, aud: 'bar'}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const jwt = await new jose.SignJWT(claims)
|
||||||
|
.setProtectedHeader({alg: 'PS256'})
|
||||||
|
.sign(key.privateKey)
|
||||||
|
|
||||||
|
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throw an error', async () => {
|
||||||
|
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/unexpected "aud"/)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when openid config cannot be retrieved', () => {
|
||||||
|
const claims = {foo: 'bar', iss: issuer, aud: 'nobody'}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const jwt = await new jose.SignJWT(claims)
|
||||||
|
.setProtectedHeader({alg: 'PS256'})
|
||||||
|
.sign(key.privateKey)
|
||||||
|
|
||||||
|
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
|
||||||
|
|
||||||
|
// Disable the openid config endpoint
|
||||||
|
nock.removeInterceptor({
|
||||||
|
proto: 'https',
|
||||||
|
hostname: 'example.com',
|
||||||
|
port: '443',
|
||||||
|
method: 'GET',
|
||||||
|
path: openidConfigPath
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error', async () => {
|
||||||
|
await expect(getIDTokenClaims(issuer)).rejects.toThrow(
|
||||||
|
/failed to get id/i
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,213 +1,248 @@
|
|||||||
import * as github from '@actions/github'
|
import * as github from '@actions/github'
|
||||||
import {mockFulcio, mockRekor, mockTSA} from '@sigstore/mock'
|
import {mockFulcio, mockRekor, mockTSA} from '@sigstore/mock'
|
||||||
|
import * as jose from 'jose'
|
||||||
import nock from 'nock'
|
import nock from 'nock'
|
||||||
import {SIGSTORE_GITHUB, SIGSTORE_PUBLIC_GOOD} from '../src/endpoints'
|
import {MockAgent, setGlobalDispatcher} from 'undici'
|
||||||
|
import {SIGSTORE_PUBLIC_GOOD, signingEndpoints} from '../src/endpoints'
|
||||||
import {attestProvenance, buildSLSAProvenancePredicate} from '../src/provenance'
|
import {attestProvenance, buildSLSAProvenancePredicate} from '../src/provenance'
|
||||||
|
|
||||||
// Dummy workflow environment
|
describe('provenance functions', () => {
|
||||||
const env = {
|
|
||||||
GITHUB_REPOSITORY: 'owner/repo',
|
|
||||||
GITHUB_REF: 'refs/heads/main',
|
|
||||||
GITHUB_SHA: 'babca52ab0c93ae16539e5923cb0d7403b9a093b',
|
|
||||||
GITHUB_WORKFLOW_REF: 'owner/repo/.github/workflows/main.yml@main',
|
|
||||||
GITHUB_SERVER_URL: 'https://github.com',
|
|
||||||
GITHUB_EVENT_NAME: 'push',
|
|
||||||
GITHUB_REPOSITORY_ID: 'repo-id',
|
|
||||||
GITHUB_REPOSITORY_OWNER_ID: 'owner-id',
|
|
||||||
GITHUB_RUN_ID: 'run-id',
|
|
||||||
GITHUB_RUN_ATTEMPT: 'run-attempt',
|
|
||||||
RUNNER_ENVIRONMENT: 'github-hosted'
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('buildSLSAProvenancePredicate', () => {
|
|
||||||
it('returns a provenance hydrated from env vars', () => {
|
|
||||||
const predicate = buildSLSAProvenancePredicate(env)
|
|
||||||
expect(predicate).toMatchSnapshot()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('attestProvenance', () => {
|
|
||||||
// Capture original environment variables so we can restore them after each
|
|
||||||
// test
|
|
||||||
const originalEnv = process.env
|
const originalEnv = process.env
|
||||||
|
const issuer = 'https://token.actions.foo.ghe.com'
|
||||||
|
const audience = 'nobody'
|
||||||
|
const jwksPath = '/.well-known/jwks.json'
|
||||||
|
const tokenPath = '/token'
|
||||||
|
|
||||||
// Subject to attest
|
// MockAgent for mocking @actions/github
|
||||||
const subjectName = 'subjective'
|
const mockAgent = new MockAgent()
|
||||||
const subjectDigest = {
|
setGlobalDispatcher(mockAgent)
|
||||||
sha256: '7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32'
|
|
||||||
|
const claims = {
|
||||||
|
iss: issuer,
|
||||||
|
aud: 'nobody',
|
||||||
|
repository: 'owner/repo',
|
||||||
|
ref: 'refs/heads/main',
|
||||||
|
sha: 'babca52ab0c93ae16539e5923cb0d7403b9a093b',
|
||||||
|
job_workflow_ref: 'owner/workflows/.github/workflows/publish.yml@main',
|
||||||
|
workflow_ref: 'owner/repo/.github/workflows/main.yml@main',
|
||||||
|
event_name: 'push',
|
||||||
|
repository_id: 'repo-id',
|
||||||
|
repository_owner_id: 'owner-id',
|
||||||
|
run_id: 'run-id',
|
||||||
|
run_attempt: 'run-attempt',
|
||||||
|
runner_environment: 'github-hosted'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fake an OIDC token
|
const mockIssuer = async (claims: jose.JWTPayload): Promise<void> => {
|
||||||
const oidcPayload = {sub: '[email protected]', iss: ''}
|
// Generate JWT signing key
|
||||||
const oidcToken = `.${Buffer.from(JSON.stringify(oidcPayload)).toString(
|
const key = await jose.generateKeyPair('PS256')
|
||||||
'base64'
|
|
||||||
)}.}`
|
|
||||||
|
|
||||||
const tokenURL = 'https://token.url'
|
// Create JWK, JWKS, and JWT
|
||||||
const attestationID = '1234567890'
|
const jwk = await jose.exportJWK(key.publicKey)
|
||||||
|
const jwks = {keys: [jwk]}
|
||||||
|
const jwt = await new jose.SignJWT(claims)
|
||||||
|
.setProtectedHeader({alg: 'PS256'})
|
||||||
|
.sign(key.privateKey)
|
||||||
|
|
||||||
|
// Mock OpenID configuration and JWKS endpoints
|
||||||
|
nock(issuer)
|
||||||
|
.get('/.well-known/openid-configuration')
|
||||||
|
.reply(200, {jwks_uri: `${issuer}${jwksPath}`})
|
||||||
|
nock(issuer).get(jwksPath).reply(200, jwks)
|
||||||
|
|
||||||
|
// Mock OIDC token endpoint for populating the provenance
|
||||||
|
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
jest.clearAllMocks()
|
|
||||||
|
|
||||||
nock(tokenURL)
|
|
||||||
.get('/')
|
|
||||||
.query({audience: 'sigstore'})
|
|
||||||
.reply(200, {value: oidcToken})
|
|
||||||
|
|
||||||
// Set-up GHA environment variables
|
|
||||||
process.env = {
|
process.env = {
|
||||||
...originalEnv,
|
...originalEnv,
|
||||||
...env,
|
ACTIONS_ID_TOKEN_REQUEST_URL: `${issuer}${tokenPath}?`,
|
||||||
ACTIONS_ID_TOKEN_REQUEST_URL: tokenURL,
|
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token',
|
||||||
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token'
|
GITHUB_SERVER_URL: 'https://foo.ghe.com',
|
||||||
|
GITHUB_REPOSITORY: claims.repository
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await mockIssuer(claims)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
// Restore the original environment
|
|
||||||
process.env = originalEnv
|
process.env = originalEnv
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('when using the github Sigstore instance', () => {
|
describe('buildSLSAProvenancePredicate', () => {
|
||||||
const {fulcioURL, tsaServerURL} = SIGSTORE_GITHUB
|
it('returns a provenance hydrated from an OIDC token', async () => {
|
||||||
|
const predicate = await buildSLSAProvenancePredicate()
|
||||||
beforeEach(async () => {
|
expect(predicate).toMatchSnapshot()
|
||||||
// Mock Sigstore
|
|
||||||
await mockFulcio({baseURL: fulcioURL, strict: false})
|
|
||||||
await mockTSA({baseURL: tsaServerURL})
|
|
||||||
|
|
||||||
// Mock GH attestations API
|
|
||||||
nock('https://api.github.com')
|
|
||||||
.post(/^\/repos\/.*\/.*\/attestations$/)
|
|
||||||
.reply(201, {id: attestationID})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('when the sigstore instance is explicitly set', () => {
|
|
||||||
it('attests provenance', async () => {
|
|
||||||
const attestation = await attestProvenance({
|
|
||||||
subjectName,
|
|
||||||
subjectDigest,
|
|
||||||
token: 'token',
|
|
||||||
sigstore: 'github'
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(attestation).toBeDefined()
|
|
||||||
expect(attestation.bundle).toBeDefined()
|
|
||||||
expect(attestation.certificate).toMatch(/-----BEGIN CERTIFICATE-----/)
|
|
||||||
expect(attestation.tlogID).toBeUndefined()
|
|
||||||
expect(attestation.attestationID).toBe(attestationID)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('when the sigstore instance is inferred from the repo visibility', () => {
|
|
||||||
const savedRepository = github.context.payload.repository
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
||||||
github.context.payload.repository = {visibility: 'private'} as any
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
github.context.payload.repository = savedRepository
|
|
||||||
})
|
|
||||||
|
|
||||||
it('attests provenance', async () => {
|
|
||||||
const attestation = await attestProvenance({
|
|
||||||
subjectName,
|
|
||||||
subjectDigest,
|
|
||||||
token: 'token'
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(attestation).toBeDefined()
|
|
||||||
expect(attestation.bundle).toBeDefined()
|
|
||||||
expect(attestation.certificate).toMatch(/-----BEGIN CERTIFICATE-----/)
|
|
||||||
expect(attestation.tlogID).toBeUndefined()
|
|
||||||
expect(attestation.attestationID).toBe(attestationID)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('when using the public-good Sigstore instance', () => {
|
describe('attestProvenance', () => {
|
||||||
const {fulcioURL, rekorURL} = SIGSTORE_PUBLIC_GOOD
|
// Subject to attest
|
||||||
|
const subjectName = 'subjective'
|
||||||
|
const subjectDigest = {
|
||||||
|
sha256: '7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fake an OIDC token
|
||||||
|
const oidcPayload = {sub: '[email protected]', iss: ''}
|
||||||
|
const oidcToken = `.${Buffer.from(JSON.stringify(oidcPayload)).toString(
|
||||||
|
'base64'
|
||||||
|
)}.}`
|
||||||
|
|
||||||
|
const attestationID = '1234567890'
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// Mock Sigstore
|
nock(issuer)
|
||||||
await mockFulcio({baseURL: fulcioURL, strict: false})
|
.get(tokenPath)
|
||||||
await mockRekor({baseURL: rekorURL})
|
.query({audience: 'sigstore'})
|
||||||
|
.reply(200, {value: oidcToken})
|
||||||
// Mock GH attestations API
|
|
||||||
nock('https://api.github.com')
|
|
||||||
.post(/^\/repos\/.*\/.*\/attestations$/)
|
|
||||||
.reply(201, {id: attestationID})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('when the sigstore instance is explicitly set', () => {
|
describe('when using the github Sigstore instance', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const {fulcioURL, tsaServerURL} = signingEndpoints('github')
|
||||||
|
|
||||||
|
// Mock Sigstore
|
||||||
|
await mockFulcio({baseURL: fulcioURL, strict: false})
|
||||||
|
await mockTSA({baseURL: tsaServerURL})
|
||||||
|
|
||||||
|
mockAgent
|
||||||
|
.get('https://api.github.com')
|
||||||
|
.intercept({
|
||||||
|
path: /^\/repos\/.*\/.*\/attestations$/,
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
.reply(201, {id: attestationID})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when the sigstore instance is explicitly set', () => {
|
||||||
|
it('attests provenance', async () => {
|
||||||
|
const attestation = await attestProvenance({
|
||||||
|
subjects: [{name: subjectName, digest: subjectDigest}],
|
||||||
|
token: 'token',
|
||||||
|
sigstore: 'github'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(attestation).toBeDefined()
|
||||||
|
expect(attestation.bundle).toBeDefined()
|
||||||
|
expect(attestation.certificate).toMatch(/-----BEGIN CERTIFICATE-----/)
|
||||||
|
expect(attestation.tlogID).toBeUndefined()
|
||||||
|
expect(attestation.attestationID).toBe(attestationID)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when the sigstore instance is inferred from the repo visibility', () => {
|
||||||
|
const savedRepository = github.context.payload.repository
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||||
|
github.context.payload.repository = {visibility: 'private'} as any
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
github.context.payload.repository = savedRepository
|
||||||
|
})
|
||||||
|
|
||||||
|
it('attests provenance', async () => {
|
||||||
|
const attestation = await attestProvenance({
|
||||||
|
subjects: [{name: subjectName, digest: subjectDigest}],
|
||||||
|
token: 'token'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(attestation).toBeDefined()
|
||||||
|
expect(attestation.bundle).toBeDefined()
|
||||||
|
expect(attestation.certificate).toMatch(/-----BEGIN CERTIFICATE-----/)
|
||||||
|
expect(attestation.tlogID).toBeUndefined()
|
||||||
|
expect(attestation.attestationID).toBe(attestationID)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when using the public-good Sigstore instance', () => {
|
||||||
|
const {fulcioURL, rekorURL} = SIGSTORE_PUBLIC_GOOD
|
||||||