Skip to content

Commit

Permalink
Merge pull request #911 - Improve dependency review support
Browse files Browse the repository at this point in the history
  • Loading branch information
bigdaz authored Oct 1, 2023
2 parents d1b726d + 4241e05 commit 842c587
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 73 deletions.
107 changes: 98 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -546,8 +546,6 @@ You enable GitHub Dependency Graph support by setting the `dependency-graph` act
| `generate-and-submit` | As per `generate`, but any generated dependency graph snapshots will be submitted at the end of the job. |
| `download-and-submit` | Download any previously saved dependency graph snapshots, submitting them via the Dependency Submission API. This can be useful to collect all snapshots in a matrix of builds and submit them in one step. |

Dependency Graph _submission_ (but not generation) requires the `contents: write` permission, which may need to be explicitly enabled in the workflow file.

Example of a simple workflow that generates and submits a dependency graph:
```yaml
name: Submit dependency graph
Expand All @@ -566,14 +564,62 @@ jobs:
uses: gradle/gradle-build-action@v2
with:
dependency-graph: generate-and-submit
- name: Run a build, generating the dependency graph snapshot which will be submitted
- name: Run a build and generate the dependency graph which will be submitted post-job
run: ./gradlew build
```

The `contents: write` permission is not required to generate the dependency graph, but is required in order to submit the graph via the GitHub API.
The `contents: write` permission is not required to generate the dependency graph, but is required in order to submit the graph via the GitHub API. This permission will need to be explicitly enabled in the workflow file for dependency graph submission to succeed.

> [!IMPORTANT]
> The above configuration will work for workflows that run as a result of commits to a repository branch,
> but not when a workflow is triggered by a PR from a repository fork.
> This is because the `contents: write` permission is not available when executing a workflow
> for a PR submitted from a forked repository.
> For a configuration that supports this setup, see [Dependency Graphs for pull request workflows](#dependency-graphs-for-pull-request-workflows).

### Integrating the `dependency-review-action`

The GitHub [dependency-review-action](https://github.com/actions/dependency-review-action) helps you
understand dependency changes (and the security impact of these changes) for a pull request.
For the `dependency-review-action` to succeed, it must run _after_ the dependency graph has been submitted for a PR.

When using `generate-and-submit`, dependency graph files are submitted at the end of the job, after all steps have been
executed. For this reason, the `dependency-review-action` must be executed in a dependent job,
and not as a subsequent step in the job that generates the dependency graph.

Example of a pull request workflow that executes a build for a pull request and runs the `dependency-review-action`:

The above configuration will work for workflows that run as a result of commits to a repository branch, but not when a workflow is triggered by a PR from a repository fork.
For a configuration that supports this setup, see [Dependency Graphs for pull request workflows](#dependency-graphs-for-pull-request-workflows).
```yaml
name: PR check
on:
pull_request:
permissions:
contents: write
# Note that this permission will not be available if the PR is from a forked repository
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Gradle to generate and submit dependency graphs
uses: gradle/gradle-build-action@v2
with:
dependency-graph: generate-and-submit
- name: Run a build and generate the dependency graph which will be submitted post-job
run: ./gradlew build
dependency-review:
needs: build
runs-on: ubuntu-latest
- name: Perform dependency review
uses: actions/dependency-review-action@v3
```

See [Dependency Graphs for pull request workflows](#dependency-graphs-for-pull-request-workflows) for a more complex
(and less functional) example that will work for pull requests submitted from forked repositories.

## Limiting the scope of the dependency graph

Expand Down Expand Up @@ -682,6 +728,9 @@ Note: when `download-and-submit` is used in a workflow triggered via [workflow_r
```yaml
name: run-build-and-generate-dependency-snapshot
on:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
Expand All @@ -693,6 +742,13 @@ jobs:
dependency-graph: generate # Only generate in this job
- name: Run a build, generating the dependency graph snapshot which will be submitted
run: ./gradlew build
dependency-review:
needs: build
runs-on: ubuntu-latest
- name: Perform dependency review
uses: actions/dependency-review-action@v3
```

***Dependent workflow file***
Expand All @@ -705,15 +761,48 @@ on:
types: [completed]
jobs:
submit-snapshots:
submit-dependency-graph:
runs-on: ubuntu-latest
steps:
- name: Retrieve dependency graph artifact and submit
uses: gradle/gradle-build-action@v2
- name: Retrieve dependency graph artifact and submit
uses: gradle/gradle-build-action@v2
with:
dependency-graph: download-and-submit
```

### Integrating `dependency-review-action` for pull request workflows

The GitHub [dependency-review-action](https://github.com/actions/dependency-review-action) helps you
understand dependency changes (and the security impact of these changes) for a pull request.

To integrate the `dependency-review-action` into the pull request workflows above, a separate workflow should be added.
This workflow will be triggered directly on `pull_request`, but will need to wait until the dependency graph results are
submitted before the dependency review can complete. How long to wait is controlled by the `retry-on-snapshot-warnings` input parameters.

Here's an example of a separate "Dependency Review" workflow that will wait for 10 minutes for the PR check workflow to complete.

```yaml
name: dependency-review
on:
pull_request:
permissions:
contents: read
pull-requests: write
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: 'Dependency Review'
uses: actions/dependency-review-action@v3
with:
retry-on-snapshot-warnings: true
retry-on-snapshot-warnings-timeout: 600
```

The `retry-on-snapshot-warnings-timeout` (in seconds) needs to be long enough to allow the entire `run-build-and-generate-dependency-snapshot` and `submit-dependency-snapshot` workflows (above) to complete.

## Gradle version compatibility

The GitHub Dependency Graph plugin should be compatible with all versions of Gradle >= 5.0, and has been tested against
Expand Down
70 changes: 47 additions & 23 deletions dist/main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70674,38 +70674,44 @@ const artifact = __importStar(__nccwpck_require__(2605));
const github = __importStar(__nccwpck_require__(5438));
const glob = __importStar(__nccwpck_require__(8090));
const toolCache = __importStar(__nccwpck_require__(7784));
const request_error_1 = __nccwpck_require__(537);
const path = __importStar(__nccwpck_require__(1017));
const fs_1 = __importDefault(__nccwpck_require__(7147));
const layout = __importStar(__nccwpck_require__(8182));
const input_params_1 = __nccwpck_require__(3885);
const DEPENDENCY_GRAPH_ARTIFACT = 'dependency-graph';
function setup(option) {
if (option === input_params_1.DependencyGraphOption.Disabled || option === input_params_1.DependencyGraphOption.DownloadAndSubmit) {
return;
}
core.info('Enabling dependency graph generation');
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED', 'true');
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR', getJobCorrelator());
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_ID', github.context.runId);
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_REF', github.context.ref);
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_SHA', getShaFromContext());
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_WORKSPACE', layout.workspaceDirectory());
core.exportVariable('DEPENDENCY_GRAPH_REPORT_DIR', path.resolve(layout.workspaceDirectory(), 'dependency-graph-reports'));
return __awaiter(this, void 0, void 0, function* () {
if (option === input_params_1.DependencyGraphOption.Disabled) {
return;
}
if (option === input_params_1.DependencyGraphOption.DownloadAndSubmit) {
yield downloadAndSubmitDependencyGraphs();
return;
}
core.info('Enabling dependency graph generation');
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED', 'true');
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR', getJobCorrelator());
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_ID', github.context.runId);
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_REF', github.context.ref);
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_SHA', getShaFromContext());
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_WORKSPACE', layout.workspaceDirectory());
core.exportVariable('DEPENDENCY_GRAPH_REPORT_DIR', path.resolve(layout.workspaceDirectory(), 'dependency-graph-reports'));
});
}
exports.setup = setup;
function complete(option) {
return __awaiter(this, void 0, void 0, function* () {
switch (option) {
case input_params_1.DependencyGraphOption.Disabled:
case input_params_1.DependencyGraphOption.DownloadAndSubmit:
return;
case input_params_1.DependencyGraphOption.Generate:
yield uploadDependencyGraphs();
return;
case input_params_1.DependencyGraphOption.GenerateAndSubmit:
yield submitDependencyGraphs(yield uploadDependencyGraphs());
return;
case input_params_1.DependencyGraphOption.DownloadAndSubmit:
yield downloadAndSubmitDependencyGraphs();
}
});
}
Expand All @@ -70729,18 +70735,36 @@ function downloadAndSubmitDependencyGraphs() {
}
function submitDependencyGraphs(dependencyGraphFiles) {
return __awaiter(this, void 0, void 0, function* () {
const octokit = getOctokit();
for (const jsonFile of dependencyGraphFiles) {
const jsonContent = fs_1.default.readFileSync(jsonFile, 'utf8');
const jsonObject = JSON.parse(jsonContent);
jsonObject.owner = github.context.repo.owner;
jsonObject.repo = github.context.repo.repo;
const response = yield octokit.request('POST /repos/{owner}/{repo}/dependency-graph/snapshots', jsonObject);
const relativeJsonFile = getRelativePathFromWorkspace(jsonFile);
core.notice(`Submitted ${relativeJsonFile}: ${response.data.message}`);
try {
yield submitDependencyGraphFile(jsonFile);
}
catch (error) {
if (error instanceof request_error_1.RequestError) {
const relativeJsonFile = getRelativePathFromWorkspace(jsonFile);
core.warning(`Failed to submit dependency graph ${relativeJsonFile}.\n` +
"Please ensure that the 'contents: write' permission is available for the workflow job.\n" +
"Note that this permission is never available for a 'pull_request' trigger from a repository fork.");
}
else {
throw error;
}
}
}
});
}
function submitDependencyGraphFile(jsonFile) {
return __awaiter(this, void 0, void 0, function* () {
const octokit = getOctokit();
const jsonContent = fs_1.default.readFileSync(jsonFile, 'utf8');
const jsonObject = JSON.parse(jsonContent);
jsonObject.owner = github.context.repo.owner;
jsonObject.repo = github.context.repo.repo;
const response = yield octokit.request('POST /repos/{owner}/{repo}/dependency-graph/snapshots', jsonObject);
const relativeJsonFile = getRelativePathFromWorkspace(jsonFile);
core.notice(`Submitted ${relativeJsonFile}: ${response.data.message}`);
});
}
function retrieveDependencyGraphs(workspaceDirectory) {
return __awaiter(this, void 0, void 0, function* () {
if (github.context.payload.workflow_run) {
Expand Down Expand Up @@ -71657,7 +71681,7 @@ function setup() {
const cacheListener = new cache_reporting_1.CacheListener();
yield caches.restore(gradleUserHome, cacheListener);
core.saveState(CACHE_LISTENER, cacheListener.stringify());
dependencyGraph.setup(params.getDependencyGraphOption());
yield dependencyGraph.setup(params.getDependencyGraphOption());
});
}
exports.setup = setup;
Expand All @@ -71679,7 +71703,7 @@ function complete() {
else {
(0, job_summary_1.logJobSummary)(buildResults, cacheListener);
}
dependencyGraph.complete(params.getDependencyGraphOption());
yield dependencyGraph.complete(params.getDependencyGraphOption());
});
}
exports.complete = complete;
Expand Down
2 changes: 1 addition & 1 deletion dist/main/index.js.map

Large diffs are not rendered by default.

70 changes: 47 additions & 23 deletions dist/post/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70674,38 +70674,44 @@ const artifact = __importStar(__nccwpck_require__(2605));
const github = __importStar(__nccwpck_require__(5438));
const glob = __importStar(__nccwpck_require__(8090));
const toolCache = __importStar(__nccwpck_require__(7784));
const request_error_1 = __nccwpck_require__(537);
const path = __importStar(__nccwpck_require__(1017));
const fs_1 = __importDefault(__nccwpck_require__(7147));
const layout = __importStar(__nccwpck_require__(8182));
const input_params_1 = __nccwpck_require__(3885);
const DEPENDENCY_GRAPH_ARTIFACT = 'dependency-graph';
function setup(option) {
if (option === input_params_1.DependencyGraphOption.Disabled || option === input_params_1.DependencyGraphOption.DownloadAndSubmit) {
return;
}
core.info('Enabling dependency graph generation');
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED', 'true');
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR', getJobCorrelator());
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_ID', github.context.runId);
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_REF', github.context.ref);
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_SHA', getShaFromContext());
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_WORKSPACE', layout.workspaceDirectory());
core.exportVariable('DEPENDENCY_GRAPH_REPORT_DIR', path.resolve(layout.workspaceDirectory(), 'dependency-graph-reports'));
return __awaiter(this, void 0, void 0, function* () {
if (option === input_params_1.DependencyGraphOption.Disabled) {
return;
}
if (option === input_params_1.DependencyGraphOption.DownloadAndSubmit) {
yield downloadAndSubmitDependencyGraphs();
return;
}
core.info('Enabling dependency graph generation');
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED', 'true');
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR', getJobCorrelator());
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_ID', github.context.runId);
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_REF', github.context.ref);
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_SHA', getShaFromContext());
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_WORKSPACE', layout.workspaceDirectory());
core.exportVariable('DEPENDENCY_GRAPH_REPORT_DIR', path.resolve(layout.workspaceDirectory(), 'dependency-graph-reports'));
});
}
exports.setup = setup;
function complete(option) {
return __awaiter(this, void 0, void 0, function* () {
switch (option) {
case input_params_1.DependencyGraphOption.Disabled:
case input_params_1.DependencyGraphOption.DownloadAndSubmit:
return;
case input_params_1.DependencyGraphOption.Generate:
yield uploadDependencyGraphs();
return;
case input_params_1.DependencyGraphOption.GenerateAndSubmit:
yield submitDependencyGraphs(yield uploadDependencyGraphs());
return;
case input_params_1.DependencyGraphOption.DownloadAndSubmit:
yield downloadAndSubmitDependencyGraphs();
}
});
}
Expand All @@ -70729,18 +70735,36 @@ function downloadAndSubmitDependencyGraphs() {
}
function submitDependencyGraphs(dependencyGraphFiles) {
return __awaiter(this, void 0, void 0, function* () {
const octokit = getOctokit();
for (const jsonFile of dependencyGraphFiles) {
const jsonContent = fs_1.default.readFileSync(jsonFile, 'utf8');
const jsonObject = JSON.parse(jsonContent);
jsonObject.owner = github.context.repo.owner;
jsonObject.repo = github.context.repo.repo;
const response = yield octokit.request('POST /repos/{owner}/{repo}/dependency-graph/snapshots', jsonObject);
const relativeJsonFile = getRelativePathFromWorkspace(jsonFile);
core.notice(`Submitted ${relativeJsonFile}: ${response.data.message}`);
try {
yield submitDependencyGraphFile(jsonFile);
}
catch (error) {
if (error instanceof request_error_1.RequestError) {
const relativeJsonFile = getRelativePathFromWorkspace(jsonFile);
core.warning(`Failed to submit dependency graph ${relativeJsonFile}.\n` +
"Please ensure that the 'contents: write' permission is available for the workflow job.\n" +
"Note that this permission is never available for a 'pull_request' trigger from a repository fork.");
}
else {
throw error;
}
}
}
});
}
function submitDependencyGraphFile(jsonFile) {
return __awaiter(this, void 0, void 0, function* () {
const octokit = getOctokit();
const jsonContent = fs_1.default.readFileSync(jsonFile, 'utf8');
const jsonObject = JSON.parse(jsonContent);
jsonObject.owner = github.context.repo.owner;
jsonObject.repo = github.context.repo.repo;
const response = yield octokit.request('POST /repos/{owner}/{repo}/dependency-graph/snapshots', jsonObject);
const relativeJsonFile = getRelativePathFromWorkspace(jsonFile);
core.notice(`Submitted ${relativeJsonFile}: ${response.data.message}`);
});
}
function retrieveDependencyGraphs(workspaceDirectory) {
return __awaiter(this, void 0, void 0, function* () {
if (github.context.payload.workflow_run) {
Expand Down Expand Up @@ -71289,7 +71313,7 @@ function setup() {
const cacheListener = new cache_reporting_1.CacheListener();
yield caches.restore(gradleUserHome, cacheListener);
core.saveState(CACHE_LISTENER, cacheListener.stringify());
dependencyGraph.setup(params.getDependencyGraphOption());
yield dependencyGraph.setup(params.getDependencyGraphOption());
});
}
exports.setup = setup;
Expand All @@ -71311,7 +71335,7 @@ function complete() {
else {
(0, job_summary_1.logJobSummary)(buildResults, cacheListener);
}
dependencyGraph.complete(params.getDependencyGraphOption());
yield dependencyGraph.complete(params.getDependencyGraphOption());
});
}
exports.complete = complete;
Expand Down
2 changes: 1 addition & 1 deletion dist/post/index.js.map

Large diffs are not rendered by default.

Loading

0 comments on commit 842c587

Please sign in to comment.