Skip to content

Commit

Permalink
[FEATURE] Added support of workflow artifact's retention policy (#398)
Browse files Browse the repository at this point in the history
Signed-off-by: Viacheslav Kudinov <viacheslav@kudinov.tech>
  • Loading branch information
ViacheslavKudinov committed Mar 20, 2023
1 parent 8a5a132 commit 448520c
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 43 deletions.
12 changes: 11 additions & 1 deletion .github/workflows/test.yml
Expand Up @@ -31,7 +31,7 @@ jobs:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: Vampire/setup-wsl@v1
- uses: Vampire/setup-wsl@v2
if: ${{ matrix.os == 'windows-latest' }}
with:
distribution: Alpine
Expand Down Expand Up @@ -117,3 +117,13 @@ jobs:
- uses: ./publish-sbom # anchore/sbom-action/publish-sbom
with:
sbom-artifact-match: "^dont-match-anything$"

- uses: ./ # anchore/sbom-action with artifact retention
name: "One day artifact retention test"
id: one-day
with:
image: alpine:latest
upload-artifact-retention: 1
artifact-name: one-day.sbom.spdx
output-file: one-day-sbom.spdx
format: spdx
29 changes: 17 additions & 12 deletions README.md
Expand Up @@ -123,17 +123,22 @@ use the `artifact-name` parameter:
The main [SBOM action](action.yml), responsible for generating SBOMs
and uploading them as workflow artifacts and release assets.

| Parameter | Description | Default |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
| `path` | A path on the filesystem to scan. This is mutually exclusive to `file` and `image`. | \<current directory> |
| `file` | A file on the filesystem to scan. This is mutually exclusive to `path` and `image`. | |
| `image` | A container image to scan. This is mutually exclusive to `path` and `file`. See [Scan a container image](#scan-a-container-image) for more information. | |
| `registry-username` | The registry username to use when authenticating to an external registry | |
| `registry-password` | The registry password to use when authenticating to an external registry | |
| `artifact-name` | The name to use for the generated SBOM artifact. See: [Naming the SBOM output](#naming-the-sbom-output) | `sbom-<job>-<step-id>.spdx.json` |
| `output-file` | The location to output a resulting SBOM | |
| `format` | The SBOM format to export. One of: `spdx`, `spdx-json`, `cyclonedx`, `cyclonedx-json` | `spdx-json` |
| `dependency-snapshot` | Whether to upload the SBOM to the GitHub Dependency submission API | `false` |
| Parameter | Description | Default |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
| `path` | A path on the filesystem to scan. This is mutually exclusive to `file` and `image`. | \<current directory> |
| `file` | A file on the filesystem to scan. This is mutually exclusive to `path` and `image`. | |
| `image` | A container image to scan. This is mutually exclusive to `path` and `file`. See [Scan a container image](#scan-a-container-image) for more information. | |
| `registry-username` | The registry username to use when authenticating to an external registry | |
| `registry-password` | The registry password to use when authenticating to an external registry | |
| `artifact-name` | The name to use for the generated SBOM artifact. See: [Naming the SBOM output](#naming-the-sbom-output) | `sbom-<job>-<step-id>.spdx.json` |
| `output-file` | The location to output a resulting SBOM | |
| `format` | The SBOM format to export. One of: `spdx`, `spdx-json`, `cyclonedx`, `cyclonedx-json` | `spdx-json` |
| `dependency-snapshot` | Whether to upload the SBOM to the GitHub Dependency submission API | `false` |
| `upload-artifact` | Upload artifact to workflow | `true` |
| `upload-artifact-retention` | Retention policy in days for uploaded artifact to workflow. | |
| `upload-release-assets` | Upload release assets | `true` |
| `syft-version` | The version of Syft to use | |
| `github-token` | Authorized secret GitHub Personal Access Token. | `github.token` |

### anchore/sbom-action/publish-sbom

Expand Down Expand Up @@ -167,7 +172,7 @@ required to set up a WSL distribution prior to invoking the `sbom-action`, for
example, you can add the small Alpine image:

```yaml
- uses: Vampire/setup-wsl@v1
- uses: Vampire/setup-wsl@v2
with:
distribution: Alpine
```
Expand Down
10 changes: 10 additions & 0 deletions action.yml
Expand Up @@ -58,6 +58,16 @@ inputs:
description: "Upload artifact to workflow"
default: "true"

upload-artifact-retention:
required: false
description: >
Retention policy for uploaded artifact to workflow.
Minimum 1 day.
Maximum 90 days unless changed from the repository settings page.
An input of 0 assumes default retention value.
default: 0

upload-release-assets:
required: false
description: "Upload release assets"
Expand Down
19 changes: 12 additions & 7 deletions dist/attachReleaseAssets/index.js

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

19 changes: 12 additions & 7 deletions dist/downloadSyft/index.js

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

19 changes: 12 additions & 7 deletions dist/runSyftAction/index.js

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

4 changes: 2 additions & 2 deletions package-lock.json

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

20 changes: 16 additions & 4 deletions src/github/GithubClient.ts
@@ -1,4 +1,7 @@
import { create as createArtifactClient } from "@actions/artifact";
import {
create as createArtifactClient,
UploadOptions,
} from "@actions/artifact";
import { DownloadHttpClient } from "@actions/artifact/lib/internal/download-http-client";
import * as core from "@actions/core";
import * as github from "@actions/github";
Expand Down Expand Up @@ -177,13 +180,16 @@ export class GithubClient {
* Uploads a workflow artifact for the current workflow run
* @param name name of the artifact
* @param file file to upload
* @param retention retention days of a artifact
*/
async uploadWorkflowArtifact({
name,
file,
retention,
}: {
name: string;
file: string;
retention?: number;
}): Promise<void> {
const rootDirectory = path.dirname(file);
const client = createArtifactClient();
Expand All @@ -192,14 +198,20 @@ export class GithubClient {
"uploadArtifact:",
name,
file,
retention,
rootDirectory,
core.isDebug() && fs.readdirSync(rootDirectory)
);

const options: UploadOptions = {
continueOnError: false,
};
if (retention) {
options.retentionDays = retention;
}

const info = await suppressOutput(async () =>
client.uploadArtifact(name, [file], rootDirectory, {
continueOnError: false,
})
client.uploadArtifact(name, [file], rootDirectory, options)
);

debugLog("uploadArtifact response:", info);
Expand Down
3 changes: 3 additions & 0 deletions src/github/SyftGithubAction.ts
Expand Up @@ -280,6 +280,8 @@ export async function uploadSbomArtifact(contents: string): Promise<void> {
const filePath = `${tempDir}/${fileName}`;
fs.writeFileSync(filePath, contents);

const retentionDays = parseInt(core.getInput("upload-artifact-retention"));

const outputFile = core.getInput("output-file");
if (outputFile) {
fs.copyFileSync(filePath, outputFile);
Expand All @@ -291,6 +293,7 @@ export async function uploadSbomArtifact(contents: string): Promise<void> {
await client.uploadWorkflowArtifact({
file: filePath,
name: fileName,
retention: retentionDays,
});
}

Expand Down
22 changes: 21 additions & 1 deletion tests/SyftGithubAction.test.ts
Expand Up @@ -123,7 +123,7 @@ describe("Action", () => {

await action.runSyftAction();

expect(fs.existsSync(inputs["output-file"])).toBeTruthy();
expect(fs.existsSync(inputs["output-file"] as string)).toBeTruthy();

await action.attachReleaseAssets();

Expand All @@ -133,6 +133,26 @@ describe("Action", () => {
expect(fs.existsSync(outputFile)).toBeTruthy();
});

it("runs with retention input", async () => {
setData({
inputs: {
image: "org/img",
"upload-artifact": "true",
"upload-artifact-retention": "3",
},
});

await action.runSyftAction();

const { artifacts } = data;

expect(artifacts).toHaveLength(1);

const opts = (artifacts[0] as any).options

expect(opts.retentionDays).toEqual(3)
});

it("runs without uploading anything", async () => {
setData({
inputs: {
Expand Down
6 changes: 4 additions & 2 deletions tests/mocks.ts
Expand Up @@ -9,7 +9,7 @@ export function getMocks() {

workflowRuns: Partial<WorkflowRun>[] = [];

inputs: { [key: string]: string } = {};
inputs: { [key: string]: string | number } = {};

outputs: { [key: string]: string } = {};

Expand Down Expand Up @@ -125,10 +125,12 @@ export function getMocks() {
return {
create() {
return {
uploadArtifact(name: string, file: string) {
uploadArtifact(name: string, file: string, rootDirectory: string, options?: any) {
data.artifacts.push({
name: path.basename(name),
file,
rootDirectory,
options,
} as never);
},
downloadArtifact(name: string, tempPath: string) {
Expand Down

0 comments on commit 448520c

Please sign in to comment.