-
Notifications
You must be signed in to change notification settings - Fork 33
feat: Support Sourceanalyzer tool registration and performing local scans via command and fcli action #935
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev/v3.x
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| # yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json | ||
|
|
||
| author: Fortify | ||
| usage: | ||
| header: Local Source Analyzer translate/scan with optional SSC upload | ||
| description: | | ||
| This action automates a local Fortify Source Analyzer workflow: | ||
| 1) Optionally update local Source Analyzer rulepacks using the registered Source Analyzer installation | ||
| 2) Translate source code using the specified build ID | ||
| 3) Scan the translated project and generate an FPR | ||
| 4) Optionally upload the generated FPR to SSC for the given application version | ||
| 5) Optionally wait until the uploaded SSC artifact processing is complete (unless --skip-wait is used) | ||
|
|
||
| config: | ||
| output: immediate | ||
| rest.target.default: ssc | ||
| run.fcli.status.log.default: true # By default, we log all exit statuses | ||
| run.fcli.status.check.default: true | ||
|
|
||
| cli.options: | ||
| buildId: | ||
| names: --build-id, -b | ||
| description: | | ||
| Fortify Source Analyzer build ID to use for this translation and scan. | ||
| required: true | ||
| sourceDir: | ||
| names: --source-dir, -d | ||
| description: | | ||
| Source directory to translate/scan. | ||
| required: true | ||
| default: . | ||
| fpr: | ||
| names: --fpr, -o | ||
| description: | | ||
| Output FPR file path. | ||
| required: true | ||
| default: audit.fpr | ||
| sourceAnalyzerVersion: | ||
| names: --source-analyzer-version, -v | ||
| description: | | ||
| Optional Source Analyzer version to run. If omitted, the default | ||
| registered Source Analyzer installation is used. | ||
| required: false | ||
| appVersion: | ||
| names: --app-version, --av | ||
| description: | | ||
| Optional SSC application version (app-name:version-name) to upload | ||
| the generated FPR to. When specified, the action will attempt to | ||
| ensure the application version exists in SSC and upload the FPR | ||
| using the current SSC session. | ||
| required: false | ||
| updateRulePacks: | ||
| names: --update-rule-packs | ||
| description: | | ||
| When set, run 'fcli tool sourceanalyzer update-rule-packs' before | ||
| translation and scan to update local Source Analyzer rulepacks. | ||
| required: false | ||
| default: false | ||
| type: boolean | ||
| skipWait: | ||
| names: --skip-wait | ||
| description: >- | ||
| By default, the action will wait for the uploaded SSC artifact processing | ||
| to complete. Use this option to skip waiting for processing to complete. | ||
| required: false | ||
| type: boolean | ||
| translateExtraOpts: | ||
| names: --translate-extra-opts | ||
| description: | | ||
| Extra options to pass only to the translation (sourceanalyzer) step. | ||
| required: false | ||
| default: ${#extraOpts('SOURCEANALYZER_TRANSLATE_EXTRA_OPTS')} | ||
| scanExtraOpts: | ||
| names: --scan-extra-opts | ||
| description: | | ||
| Extra options to pass only to the scan (sourceanalyzer) step. | ||
| required: false | ||
| default: ${#extraOpts('SOURCEANALYZER_SCAN_EXTRA_OPTS')} | ||
|
|
||
| steps: | ||
| - var.set: | ||
| scan.buildId: ${cli.buildId} | ||
| scan.fpr: ${#resolveAgainstCurrentWorkDir(cli.fpr)} | ||
| scan.sourceDir: ${#resolveAgainstCurrentWorkDir(cli.sourceDir)} | ||
| global.sourceanalyzerPublish.fcliVarName: sourceanalyzer_scan_${#action.runID().replace('-','_')} | ||
| global.sourceanalyzerPublish.waitForCmd: 'fcli ssc artifact wait-for ::${global.sourceanalyzerPublish.fcliVarName}::' | ||
|
|
||
| # Optionally update rulepacks before translate and scan | ||
| - if: ${cli.updateRulePacks==true} | ||
| run.fcli: | ||
| UPDATE_RULEPACKS: | ||
| cmd: > | ||
| fcli tool sourceanalyzer update-rule-packs ${#opt("--version", cli.sourceAnalyzerVersion)} | ||
|
|
||
| # 1) Translate | ||
| - run.fcli: | ||
| TRANSLATE: | ||
| cmd: > | ||
| fcli tool sourceanalyzer run ${#opt("--version", cli.sourceAnalyzerVersion)} -- -b "${scan.buildId}" ${cli.extraOpts} ${cli.translateExtraOpts} "${scan.sourceDir}" | ||
|
|
||
| # 2) Scan | ||
| - run.fcli: | ||
| SCAN: | ||
| cmd: > | ||
| fcli tool sourceanalyzer run ${#opt("--version", cli.sourceAnalyzerVersion)} -- -b "${scan.buildId}" -scan -f "${scan.fpr}" ${cli.extraOpts} ${cli.scanExtraOpts} | ||
|
|
||
| # 3) In case app version is mentioned, ensure it exists and upload the FPR to SSC | ||
| - if: ${!#isBlank(cli.appVersion)} | ||
| do: | ||
| - run.fcli: | ||
| SSC_SETUP_APPVERSION: | ||
| cmd: ${#actionCmd('SETUP', 'ssc', 'setup-appversion')} "--av=${cli.appVersion}" | ||
| - run.fcli: | ||
| SSC_UPLOAD_FPR: | ||
| cmd: > | ||
| fcli ssc artifact upload --av="${cli.appVersion}" -f "${scan.fpr}" --store ${global.sourceanalyzerPublish.fcliVarName} | ||
| - if: ${!cli.skipWait} | ||
| run.fcli: | ||
| WAIT: ${global.sourceanalyzerPublish.waitForCmd} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,13 +19,14 @@ | |
| import com.fortify.cli.tool._common.helper.Tool; | ||
| import com.fortify.cli.tool._common.helper.ToolInstallationDescriptor; | ||
| import com.fortify.cli.tool._common.helper.ToolInstallationOutputDescriptor; | ||
| import com.fortify.cli.tool.definitions.helper.ToolDefinitionVersionDescriptor; | ||
| import com.fortify.cli.tool.definitions.helper.ToolDefinitionsHelper; | ||
|
|
||
| import picocli.CommandLine.Parameters; | ||
|
|
||
| /** | ||
| * Abstract base class for tool 'get' commands that retrieve information about | ||
| * a specific tool version. Similar to AbstractToolListCommand but returns a | ||
| * Abstract base class for tool 'get' commands that retrieve information about | ||
| * a specific tool version. Similar to AbstractToolListCommand but returns a | ||
| * single record instead of a list. | ||
| * | ||
| * Subclasses must implement: | ||
|
|
@@ -34,50 +35,78 @@ | |
| * @author Ruud Senden | ||
| */ | ||
| public abstract class AbstractToolGetCommand extends AbstractOutputCommand implements IJsonNodeSupplier { | ||
|
|
||
| @Parameters(index = "0", descriptionKey = "fcli.tool.get.version") | ||
| private String requestedVersion; | ||
|
|
||
| @Override | ||
| public final JsonNode getJsonNode() { | ||
| var toolName = getTool().getToolName(); | ||
| var toolDefinition = ToolDefinitionsHelper.getToolDefinitionRootDescriptor(toolName); | ||
|
|
||
| var tool = getTool(); | ||
| var toolName = tool.getToolName(); | ||
| var optDefinition = ToolDefinitionsHelper.tryGetToolDefinitionRootDescriptor(toolName); | ||
|
|
||
| //TODO - Check the need of definitions for other tools as well, and if this is the best way to handle this (e.g. should we check for the presence of definitions in the list command instead?) | ||
| if (tool == Tool.SOURCE_ANALYZER && optDefinition.isEmpty()) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't want hardcoded tool names in generic base classes. Instead, either:
I think I like the latter better, as then we have centralized configuration as to whether tool definitions are required, and any abstract base class can check for this info. For example, if we ever update any other base classes with optional tool definitions behavior, we only need to update the base class, not the individual command classes. Not completely sure whether Same comment applies to other generic classes like |
||
| return getJsonNodeWithoutDefinitions(toolName); | ||
| } | ||
|
|
||
| var toolDefinition = optDefinition.orElseGet( | ||
| () -> ToolDefinitionsHelper.getToolDefinitionRootDescriptor(toolName)); | ||
|
|
||
| // Resolve version (handles aliases like 'latest') | ||
| var versionDescriptor = toolDefinition.getVersion(requestedVersion); | ||
|
|
||
| // Load installation descriptor if tool is installed | ||
| var installationDescriptor = ToolInstallationDescriptor.load(toolName, versionDescriptor); | ||
|
|
||
| // Check if this is the default (last installed) version | ||
| var lastInstalledDescriptor = ToolInstallationDescriptor.loadLastModified(toolName); | ||
| boolean isDefault = isDefaultVersion(installationDescriptor, lastInstalledDescriptor); | ||
|
|
||
| // Create output descriptor | ||
| var outputDescriptor = new ToolInstallationOutputDescriptor( | ||
| toolName, | ||
| versionDescriptor, | ||
| installationDescriptor, | ||
| "", | ||
| isDefault | ||
| ); | ||
|
|
||
| toolName, | ||
| versionDescriptor, | ||
| installationDescriptor, | ||
| "", | ||
| isDefault); | ||
|
|
||
| return JsonHelper.getObjectMapper().valueToTree(outputDescriptor); | ||
| } | ||
|
|
||
| private JsonNode getJsonNodeWithoutDefinitions(String toolName) { | ||
| ToolDefinitionVersionDescriptor versionDescriptor = new ToolDefinitionVersionDescriptor(); | ||
| versionDescriptor.setVersion(requestedVersion); | ||
| versionDescriptor.setStable(true); | ||
| versionDescriptor.setAliases(new String[0]); | ||
|
|
||
| var installationDescriptor = ToolInstallationDescriptor.load(toolName, versionDescriptor); | ||
| var lastInstalledDescriptor = ToolInstallationDescriptor.loadLastModified(toolName); | ||
| boolean isDefault = isDefaultVersion(installationDescriptor, lastInstalledDescriptor); | ||
|
|
||
| var outputDescriptor = new ToolInstallationOutputDescriptor( | ||
| toolName, | ||
| versionDescriptor, | ||
| installationDescriptor, | ||
| "", | ||
| isDefault); | ||
| return JsonHelper.getObjectMapper().valueToTree(outputDescriptor); | ||
| } | ||
|
|
||
| @Override | ||
| public final boolean isSingular() { | ||
| return true; | ||
| } | ||
|
|
||
| private boolean isDefaultVersion(ToolInstallationDescriptor installationDescriptor, ToolInstallationDescriptor lastInstalledDescriptor) { | ||
|
|
||
| private boolean isDefaultVersion(ToolInstallationDescriptor installationDescriptor, | ||
| ToolInstallationDescriptor lastInstalledDescriptor) { | ||
| if (installationDescriptor == null || lastInstalledDescriptor == null) { | ||
| return false; | ||
| } | ||
| return installationDescriptor.getInstallDir() != null | ||
| return installationDescriptor.getInstallDir() != null | ||
| && installationDescriptor.getInstallDir().equals(lastInstalledDescriptor.getInstallDir()); | ||
| } | ||
|
|
||
| /** | ||
| * @return Tool enum entry for this tool | ||
| */ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's skip adding this action for now, I need to think more about how to best fit this into fcli for all possible scenarios (FoD/SSC, local translation, local scan, ...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. I'll keep changes in a private branch for future reference.