Skip to content

Commit 05cc88f

Browse files
authored
Updates to Code Dx GitHub Action (#3)
* Added support for specifying branch during analysis * Naming changes: Renamed "Code Dx" -> "SRM" * Test: Update main.yml * Test: Update upload name in main.yml * Update README.md and revert test commit * Addressing review comments * Added `project-name` config support Also addressing previous review comments * Minor update to README and error log message * Added `dist` folder changes * Minor update to log message and dist files
1 parent 82de540 commit 05cc88f

8 files changed

Lines changed: 430 additions & 190 deletions

File tree

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
unzip pmd-bin-6.38.0.zip
3131
./pmd-bin-6.38.0/bin/run.sh pmd -d webgoat -f sarif -R rulesets/java/quickstart.xml -r pmd-sarif.json -failOnViolation false
3232
33-
- name: Code Dx Upload
33+
- name: SRM Upload
3434
uses: './codedx-action'
3535
with:
3636
server-url: ${{ secrets.CDX_SERVER_URL }}

README.md

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# GitHub Action for Code Dx
1+
# GitHub Action for SRM
22

3-
This GitHub action can be used to push source code, binaries, and scan results to a [Code Dx](https://codedx.com) instance from within a GitHub workflow; source and binaries are automatically scanned by Code Dx using its built-in analysis tools.
3+
This GitHub action can be used to push source code, binaries, and scan results to an [SRM](https://www.synopsys.com/software-integrity/software-risk-manager.html) instance from within a GitHub workflow; source and binaries are automatically scanned by SRM using its built-in analysis tools.
44

55
## Features and Behavior
66

@@ -11,28 +11,35 @@ The Action can optionally wait for analysis completion, writing the final status
1111
The workflow will be set to fail if:
1212

1313
- The source/binaries glob(s) fail to match any files
14-
- There are any errors when contacting your Code Dx server
14+
- There are any errors when contacting your SRM server
1515
- The analysis ends in failure
1616

1717
## Requirements
1818

19-
- A deployed, licensed instance of Code Dx (any license)
20-
- Access from GitHub to Code Dx via HTTP, or via HTTPS with a recognizable certificate (use the `ca-cert` param if not using a public CA)
21-
- A Project in Code Dx to store results
19+
- A deployed, licensed instance of SRM (any license)
20+
- Access from GitHub to SRM via HTTP, or via HTTPS with a recognizable certificate (use the `ca-cert` param if not using a public CA)
21+
- A Project in SRM to store results
2222
- An API Key or Personal Access Token with "Create" permissions for the Project
2323

2424
## Action Inputs
2525

26-
| Input Name | Description | Default Value | Required |
27-
|----------------------------|----------------------------------------------------------------------------------------------------------|---------------|----------|
28-
| `server-url` | The URL for the Code Dx server (typically ends with `/codedx`) | | Yes |
29-
| `api-key` | An API Key or Personal Access Token to use when connecting to Code Dx | | Yes |
30-
| `project-id` | The ID of a project (an integer) created in Code Dx | | Yes |
31-
| `source-and-binaries-glob` | A comma-separated-list of file globs matching source and binary files to be packaged and sent to Code Dx | `undefined` | No |
32-
| `tool-outputs-glob` | A comma-separated list of file globs matching tool output/scan result files | `undefined` | No |
33-
| `wait-for-completion` | Whether to wait for the analysis to complete before exiting | `false` | No |
34-
| `ca-cert` | A custom CA cert to use for HTTPS connections to Code Dx | `undefined` | No |
35-
| `dry-run` | Whether to submit an analysis (false/undefined) or only test the connection and credentials (true) | `undefined` | No |
26+
| Input Name | Description | Default Value | Required |
27+
|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|-----------------|
28+
| `server-url` | The URL for the SRM server (typically ends with `/srm`) | | Yes |
29+
| `api-key` | An API Key or Personal Access Token to use when connecting to SRM | | Yes |
30+
| `project-id` | The ID of a project (an integer) created in SRM | `undefined` | Yes<sup>1</sup> |
31+
| `project-name` | The name of a project created in SRM | `undefined` | Yes<sup>1</sup> |
32+
| `base-branch-name` | The parent branch name of a project created in SRM | `undefined` | No<sup>2</sup> |
33+
| `target-branch-name` | The target branch name of a project created in SRM. <br/>SRM automatically creates the branch if it does not exist yet in the project, and the new branch will be created from `base-branch-name` | `undefined` | No |
34+
| `source-and-binaries-glob` | A comma-separated-list of file globs matching source and binary files to be packaged and sent to SRM | `undefined` | No |
35+
| `tool-outputs-glob` | A comma-separated list of file globs matching tool output/scan result files | `undefined` | No |
36+
| `wait-for-completion` | Whether to wait for the analysis to complete before exiting | `false` | No |
37+
| `ca-cert` | A custom CA cert to use for HTTPS connections to SRM | `undefined` | No |
38+
| `dry-run` | Whether to submit an analysis (false/undefined) or only test the connection and credentials (true) | `undefined` | No |
39+
40+
**Notes**
41+
1. Either `project-id` or `project-name` is required. An error will be thrown if neither is specified or both are specified.
42+
2. `base-branch-name` is required if `target-branch-name` is specified and doesn't exist yet in the project.
3643

3744
## Sample Workflow
3845

@@ -45,7 +52,7 @@ jobs:
4552

4653
steps:
4754
- uses: actions/checkout@v2
48-
- name: Code Dx Upload
55+
- name: SRM Upload
4956
uses: 'codedx/codedx-github-action@v1.1'
5057
with:
5158
server-url: ${{ secrets.CDX_SERVER_URL }}

action.yml

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
1-
name: 'Code Dx Analysis'
2-
description: 'Analyze your source code and binaries with Code Dx'
1+
name: 'SRM Analysis'
2+
description: 'Analyze your source code and binaries with SRM'
33
inputs:
44
# main config options
55
server-url:
6-
description: 'the URL for the Code Dx server (typically ends with `/codedx`)'
6+
description: 'the URL for the SRM server (typically ends with `/srm`)'
77
required: true
88
api-key:
9-
description: 'an API key or Personal Access Token (PAT) to use when connecting to Code Dx'
9+
description: 'an API key or Personal Access Token (PAT) to use when connecting to SRM'
1010
required: true
1111
project-id:
12-
description: 'the ID of a project (an integer) created in Code Dx'
13-
required: true
12+
description: 'the ID of a project (an integer) created in SRM. This is required if `project-name` is not specified.'
13+
required: false
14+
project-name:
15+
description: 'the name of a project created in SRM. This is required if `project-id` is not specified.'
16+
required: false
17+
base-branch-name:
18+
description: 'the parent branch name of a project created in SRM'
19+
required: false
20+
target-branch-name:
21+
description: 'the target branch name of a project created in SRM. SRM automatically creates the branch if it does not exist yet in the project, and the new branch will be created from `base-branch-name`'
22+
required: false
1423
source-and-binaries-glob:
15-
description: 'a file glob matching source and binary files (accepts multiple comma-separated globs). if not set, no source/binary files will be sent to Code Dx'
24+
description: 'a file glob matching source and binary files (accepts multiple comma-separated globs). if not set, no source/binary files will be sent to SRM'
1625
required: true
1726
tool-outputs-glob:
1827
description: 'a file glob matching output files (ie scan results) from an analysis tool (accepts multiple comma-separated globs)'
@@ -24,7 +33,7 @@ inputs:
2433
required: false
2534
default: false
2635
ca-cert:
27-
description: 'a custom CA cert to use for HTTPS requests to Code Dx'
36+
description: 'a custom CA cert to use for HTTPS requests to SRM'
2837
required: false
2938
dry-run:
3039
description: 'whether to submit an analysis (false/undefined), or only test the connection and credentials (true). an error in validation will fail the build.'

analyze.js

Lines changed: 92 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const _ = require('underscore')
44
const archiver = require('archiver')
55
const fs = require('fs')
66
const FormData = require('form-data')
7-
const CodeDxApiClient = require('./codedx')
7+
const { SrmApiClient, checkIfBranchPresent } = require('./srm')
88
const path = require('path')
99

1010
const getConfig = require('./config').get
@@ -74,7 +74,7 @@ async function prepareInputsZip(inputsGlob, targetFile) {
7474
}
7575

7676
async function attachInputsZip(inputGlobs, formData, tmpDir) {
77-
const zipTarget = path.join(tmpDir, "codedx-inputfiles.zip")
77+
const zipTarget = path.join(tmpDir, "srm-inputfiles.zip")
7878
const numFiles = await prepareInputsZip(inputGlobs, zipTarget)
7979
if (numFiles == 0) {
8080
core.warning("No files were matched by the 'source-and-binaries-glob' values, skipping source/binaries ZIP attachment")
@@ -104,19 +104,100 @@ async function attachScanFiles(scanGlobs, formData) {
104104
}
105105
}
106106

107+
async function validateBranches(branches, config) {
108+
const baseBranchPresent = checkIfBranchPresent(branches, config.baseBranchName)
109+
const targetBranchPresent = checkIfBranchPresent(branches, config.targetBranchName)
110+
111+
if (targetBranchPresent) {
112+
core.info("Using existing SRM branch: " + config.targetBranchName);
113+
// Not necessary, base branch is currently ignored in the backend if the target
114+
// branch already exists. just setting to null to safeguard in case of future changes
115+
config.baseBranchName = null
116+
} else {
117+
// Base branch is not defined; throw error
118+
if (!config.baseBranchName) {
119+
throw new Error("A parent branch must be specified when using a new target branch");
120+
}
121+
122+
// Base branch is defined but is not present; cannot create target branch, so throw error
123+
if (!baseBranchPresent) {
124+
throw new Error("The specified parent branch does not exist: " + config.baseBranchName);
125+
}
126+
127+
// If base branch is defined and present, create the target branch off the base
128+
core.info(`Analysis will create a new branch named '${config.targetBranchName}' based on the branch '${config.baseBranchName}'`);
129+
}
130+
}
131+
132+
async function getProjectId(config, client) {
133+
// If projectId is defined and not name, simply return the id
134+
if (config.projectId && !config.projectName) {
135+
return config.projectId
136+
} else if (!config.projectId && config.projectName) {
137+
// If project name is defined, retrieve the corresponding project id first.
138+
// If multiple projects with the same name exist, throw an error since it is ambiguous which project the user wants.
139+
const projects = await client.getSrmProjects()
140+
const matchedProjectIds = projects.filter(project => project.name == config.projectName).map(project => project.id)
141+
142+
if (matchedProjectIds.length == 1) {
143+
return matchedProjectIds[0]
144+
} else if (matchedProjectIds.length == 0) {
145+
throw new Error(`No projects with the name '${config.projectName}'.`)
146+
} else {
147+
throw new Error(`Multiple projects with the name '${config.projectName}'. Unable to determine which project to use. Try specifying with 'project-id' instead.`)
148+
}
149+
} else if (!config.projectId && !config.projectName) {
150+
// If neither is defined, throw error
151+
throw new Error(`No projects specified. Make sure to specify either 'project-id' or 'project-name'.`)
152+
} else {
153+
// If both are defined, throw error
154+
throw new Error(`Both 'project-id' and 'project-name' are specified. Unable to determine which project to use. Make sure to specify either 'project-id' or 'project-name'.`)
155+
}
156+
}
157+
107158
// most @actions toolkit packages have async methods
108159
module.exports = async function run() {
109160
const config = getConfig()
161+
const MinimumVersionForBranching = '2022.4.3'
162+
163+
const client = new SrmApiClient(config.serverUrl, config.apiKey, config.caCert)
164+
core.info("Checking connection to SRM...")
110165

111-
const client = new CodeDxApiClient(config.serverUrl, config.apiKey, config.caCert)
112-
core.info("Checking connection to Code Dx...")
166+
const srmVersion = await client.testConnection()
167+
core.info("Confirmed - using SRM " + srmVersion)
113168

114-
const codedxVersion = await client.testConnection()
115-
core.info("Confirmed - using Code Dx " + codedxVersion)
169+
const projectId = await getProjectId(config, client)
116170

117171
core.info("Checking API key permissions...")
118-
await client.validatePermissions(config.projectId)
119-
core.info("Connection to Code Dx server is OK.")
172+
await client.validatePermissions(projectId)
173+
core.info("Connection to SRM server is OK.")
174+
175+
const branches = await client.getProjectBranches(projectId)
176+
if (config.targetBranchName) {
177+
core.info("Validating branch selection...")
178+
// First check if the SRM version being used supports branching
179+
if (srmVersion.localeCompare(MinimumVersionForBranching, undefined, { numeric: true }) < 0) {
180+
core.info(
181+
"The connected SRM server with version " + srmVersion + " does not support project branches. " +
182+
"The minimum required version is " + MinimumVersionForBranching + ". The target branch and base " +
183+
"branch options will be ignored."
184+
)
185+
// Set both branch configs to null since we will be ignoring them
186+
config.baseBranchName = null
187+
config.targetBranchName = null
188+
} else {
189+
await validateBranches(branches, config)
190+
core.info("Branch selection is OK.")
191+
}
192+
} else {
193+
// If target branch is not defined, use the default branch. This also ensures even if
194+
// base branch is defined, the default branch is not created as a child of the base branch
195+
const defaultBranch = branches.filter(branch => branch.isDefault)[0].name
196+
197+
core.info(`No target branch was defined. Using default target branch '${defaultBranch}'`)
198+
config.targetBranchName = defaultBranch
199+
config.baseBranchName = null
200+
}
120201

121202
if (config.dryRun) {
122203
core.info("dry-run is enabled, exiting without analysis")
@@ -139,8 +220,8 @@ module.exports = async function run() {
139220
core.info("Scan files glob not specified, skipping scan file attachment")
140221
}
141222

142-
core.info("Uploading to Code Dx...")
143-
const { analysisId, jobId } = await client.runAnalysis(config.projectId, formData)
223+
core.info("Uploading to SRM...")
224+
const { analysisId, jobId } = await client.runAnalysis(projectId, config.baseBranchName, config.targetBranchName, formData)
144225

145226
core.info("Started analysis #" + analysisId)
146227

@@ -155,7 +236,7 @@ module.exports = async function run() {
155236
if (lastStatus == JobStatus.COMPLETED) {
156237
core.info("Analysis finished! Completed with status: " + lastStatus)
157238
} else {
158-
throw new Error(`Analysis finished with non-complete status: ${lastStatus}. See Code Dx server logs/visual log for more details.`)
239+
throw new Error(`Analysis finished with non-complete status: ${lastStatus}. See SRM server logs/visual log for more details.`)
159240
}
160241
}
161242
}

config.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ class Config {
1616
constructor() {
1717
this.serverUrl = core.getInput('server-url', { required: true })
1818
this.apiKey = core.getInput('api-key', { required: true })
19-
this.projectId = core.getInput('project-id', { required: true })
19+
this.projectId = core.getInput('project-id')
20+
this.projectName = core.getInput('project-name')
21+
this.baseBranchName = core.getInput('base-branch-name')
22+
this.targetBranchName = core.getInput('target-branch-name')
2023
this.inputGlobs = core.getInput('source-and-binaries-glob')
2124
this.scanGlobs = core.getInput('tool-outputs-glob')
2225

@@ -42,6 +45,8 @@ class Config {
4245
throw new Error("Invalid value for projectId, expected a number but got a " + (typeof this.projectId))
4346
}
4447
}
48+
49+
this.projectName = this.projectName.trim()
4550
}
4651
}
4752

0 commit comments

Comments
 (0)