2020-02-06 17:16:34 +02:00
package cmd
import (
"encoding/json"
"fmt"
"io"
"os"
2020-08-07 13:03:38 +02:00
"path"
2020-02-06 17:16:34 +02:00
"path/filepath"
"regexp"
2021-06-23 15:05:00 +02:00
"strconv"
2020-02-06 17:16:34 +02:00
"strings"
"time"
2020-10-05 17:46:44 +02:00
"github.com/pkg/errors"
2020-02-06 17:16:34 +02:00
"github.com/SAP/jenkins-library/pkg/command"
2023-06-14 09:11:33 +02:00
"github.com/SAP/jenkins-library/pkg/docker"
2020-02-06 17:16:34 +02:00
piperDocker "github.com/SAP/jenkins-library/pkg/docker"
"github.com/SAP/jenkins-library/pkg/log"
2022-03-23 11:02:00 +02:00
"github.com/SAP/jenkins-library/pkg/piperutils"
2020-02-06 17:16:34 +02:00
"github.com/SAP/jenkins-library/pkg/protecode"
"github.com/SAP/jenkins-library/pkg/telemetry"
2021-06-23 15:05:00 +02:00
"github.com/SAP/jenkins-library/pkg/toolrecord"
2022-01-19 11:30:59 +02:00
"github.com/SAP/jenkins-library/pkg/versioning"
2020-02-06 17:16:34 +02:00
)
2020-09-02 10:41:12 +02:00
const (
2023-06-14 09:11:33 +02:00
webReportPath = "%s/#/product/%v/"
scanResultFile = "protecodescan_vulns.json"
stepResultFile = "protecodeExecuteScan.json"
dockerConfigFile = ".pipeline/docker/config.json"
2020-09-02 10:41:12 +02:00
)
2020-02-06 17:16:34 +02:00
2022-03-23 11:02:00 +02:00
type protecodeUtils interface {
piperutils . FileUtils
piperDocker . Download
}
type protecodeUtilsBundle struct {
* piperutils . Files
* piperDocker . Client
}
2020-02-06 17:16:34 +02:00
2020-09-02 10:41:12 +02:00
func protecodeExecuteScan ( config protecodeExecuteScanOptions , telemetryData * telemetry . CustomData , influx * protecodeExecuteScanInflux ) {
2020-02-06 17:16:34 +02:00
c := command . Command { }
// reroute command output to loging framework
2020-05-06 13:35:40 +02:00
c . Stdout ( log . Writer ( ) )
c . Stderr ( log . Writer ( ) )
2020-02-06 17:16:34 +02:00
2022-03-23 11:02:00 +02:00
//create client for sending api request
log . Entry ( ) . Debug ( "Create protecode client" )
client := createProtecodeClient ( & config )
2022-05-13 18:56:41 +02:00
dClientOptions := piperDocker . ClientOptions { ImageName : config . ScanImage , RegistryURL : config . DockerRegistryURL , LocalPath : config . FilePath , ImageFormat : "legacy" }
2022-03-23 11:02:00 +02:00
dClient := & piperDocker . Client { }
dClient . SetOptions ( dClientOptions )
utils := protecodeUtilsBundle {
Client : dClient ,
Files : & piperutils . Files { } ,
}
2021-03-18 11:32:03 +02:00
influx . step_data . fields . protecode = false
2022-03-23 11:02:00 +02:00
if err := runProtecodeScan ( & config , influx , client , utils , "./cache" ) ; err != nil {
2020-10-05 17:46:44 +02:00
log . Entry ( ) . WithError ( err ) . Fatal ( "Failed to execute protecode scan." )
2020-09-02 10:41:12 +02:00
}
2021-03-18 11:32:03 +02:00
influx . step_data . fields . protecode = true
2020-02-06 17:16:34 +02:00
}
2022-03-23 11:02:00 +02:00
func runProtecodeScan ( config * protecodeExecuteScanOptions , influx * protecodeExecuteScanInflux , client protecode . Protecode , utils protecodeUtils , cachePath string ) error {
// make sure cache exists
if err := utils . MkdirAll ( cachePath , 0755 ) ; err != nil {
return err
}
2023-06-14 09:11:33 +02:00
if err := correctDockerConfigEnvVar ( config , utils ) ; err != nil {
return err
}
2022-03-23 11:02:00 +02:00
2020-02-06 17:16:34 +02:00
var fileName , filePath string
2020-10-05 17:46:44 +02:00
var err error
2022-03-23 11:02:00 +02:00
2021-06-17 09:40:21 +02:00
if len ( config . FetchURL ) == 0 && len ( config . FilePath ) == 0 {
2022-03-23 11:02:00 +02:00
log . Entry ( ) . Debugf ( "Get docker image: %v, %v, %v" , config . ScanImage , config . DockerRegistryURL , config . FilePath )
fileName , filePath , err = getDockerImage ( utils , config , cachePath )
2020-10-05 17:46:44 +02:00
if err != nil {
return errors . Wrap ( err , "failed to get Docker image" )
}
2020-02-06 17:16:34 +02:00
if len ( config . FilePath ) <= 0 {
( * config ) . FilePath = filePath
log . Entry ( ) . Debugf ( "Filepath for upload image: %v" , config . FilePath )
}
2021-06-17 09:40:21 +02:00
} else if len ( config . FilePath ) > 0 {
parts := strings . Split ( config . FilePath , "/" )
pathFragment := strings . Join ( parts [ : len ( parts ) - 1 ] , "/" )
if len ( pathFragment ) > 0 {
( * config ) . FilePath = pathFragment
} else {
( * config ) . FilePath = "./"
}
fileName = parts [ len ( parts ) - 1 ]
2021-09-13 11:13:48 +02:00
} else if len ( config . FetchURL ) > 0 {
// Get filename from a fetch URL
fileName = filepath . Base ( config . FetchURL )
2022-10-10 10:55:21 +02:00
log . Entry ( ) . Debugf ( "[DEBUG] ===> Filename from fetch URL: %v" , fileName )
2020-02-06 17:16:34 +02:00
}
log . Entry ( ) . Debug ( "Execute protecode scan" )
2022-03-23 11:02:00 +02:00
if err := executeProtecodeScan ( influx , client , config , fileName , utils ) ; err != nil {
2020-09-02 10:41:12 +02:00
return err
}
2020-02-06 17:16:34 +02:00
2022-07-21 09:04:21 +02:00
defer func ( ) { _ = utils . FileRemove ( config . FilePath ) } ( )
2020-02-06 17:16:34 +02:00
2022-03-23 11:02:00 +02:00
if err := utils . RemoveAll ( cachePath ) ; err != nil {
2020-02-06 17:16:34 +02:00
log . Entry ( ) . Warnf ( "Error during cleanup folder %v" , err )
}
return nil
}
2020-04-08 12:55:46 +02:00
// TODO: extract to version utils
2020-02-06 17:16:34 +02:00
func handleArtifactVersion ( artifactVersion string ) string {
matches , _ := regexp . MatchString ( "([\\d\\.]){1,}-[\\d]{14}([\\Wa-z\\d]{41})?" , artifactVersion )
if matches {
split := strings . SplitN ( artifactVersion , "." , 2 )
2020-04-08 12:55:46 +02:00
log . Entry ( ) . WithField ( "old" , artifactVersion ) . WithField ( "new" , split [ 0 ] ) . Debug ( "Trimming version to major version digit." )
2020-02-06 17:16:34 +02:00
return split [ 0 ]
}
return artifactVersion
}
2022-03-23 11:02:00 +02:00
func getDockerImage ( utils protecodeUtils , config * protecodeExecuteScanOptions , cachePath string ) ( string , string , error ) {
2022-08-05 16:16:36 +02:00
m := regexp . MustCompile ( ` [\s@:/] ` )
2020-02-06 17:16:34 +02:00
2022-03-23 11:02:00 +02:00
tarFileName := fmt . Sprintf ( "%s.tar" , m . ReplaceAllString ( config . ScanImage , "-" ) )
tarFilePath , err := filepath . Abs ( filepath . Join ( cachePath , tarFileName ) )
2020-02-06 17:16:34 +02:00
if err != nil {
2022-03-23 11:02:00 +02:00
return "" , "" , err
2020-02-06 17:16:34 +02:00
}
2022-03-23 11:02:00 +02:00
if _ , err = utils . DownloadImage ( config . ScanImage , tarFilePath ) ; err != nil {
return "" , "" , errors . Wrap ( err , "failed to download docker image" )
2020-02-06 17:16:34 +02:00
}
2022-03-23 11:02:00 +02:00
return filepath . Base ( tarFilePath ) , filepath . Dir ( tarFilePath ) , nil
2020-02-06 17:16:34 +02:00
}
2022-03-23 11:02:00 +02:00
func executeProtecodeScan ( influx * protecodeExecuteScanInflux , client protecode . Protecode , config * protecodeExecuteScanOptions , fileName string , utils protecodeUtils ) error {
reportPath := "./"
2021-09-13 11:13:48 +02:00
log . Entry ( ) . Debugf ( "[DEBUG] ===> Load existing product Group:%v, VerifyOnly:%v, Filename:%v, replaceProductId:%v" , config . Group , config . VerifyOnly , fileName , config . ReplaceProductID )
2022-07-21 09:04:21 +02:00
var productID int
2021-09-13 11:13:48 +02:00
// If replaceProductId is not provided then switch to automatic existing product detection
if config . ReplaceProductID > 0 {
log . Entry ( ) . Infof ( "replaceProductID has been provided (%v) and checking ..." , config . ReplaceProductID )
// Validate provided product id, if not valid id then throw an error
if client . VerifyProductID ( config . ReplaceProductID ) {
log . Entry ( ) . Infof ( "replaceProductID has been checked and it's valid" )
productID = config . ReplaceProductID
} else {
log . Entry ( ) . Debugf ( "[DEBUG] ===> ReplaceProductID doesn't exist" )
return fmt . Errorf ( "ERROR -> the product id is not valid '%d'" , config . ReplaceProductID )
}
} else {
// Get existing product id by filename
log . Entry ( ) . Infof ( "replaceProductID is not provided and automatic search starts from group: %v ... " , config . Group )
productID = client . LoadExistingProduct ( config . Group , fileName )
2022-01-19 11:30:59 +02:00
if productID > 0 {
log . Entry ( ) . Infof ( "Automatic search completed and found following product id: %v" , productID )
} else {
log . Entry ( ) . Infof ( "Automatic search completed but not found any similar product scan, now starts new scan creation" )
}
}
2021-09-13 11:13:48 +02:00
// check if no existing is found
2022-03-23 11:02:00 +02:00
productID = uploadScanOrDeclareFetch ( utils , * config , productID , client , fileName )
2021-09-13 11:13:48 +02:00
2020-02-06 17:16:34 +02:00
if productID <= 0 {
2020-09-02 10:41:12 +02:00
return fmt . Errorf ( "the product id is not valid '%d'" , productID )
2020-02-06 17:16:34 +02:00
}
2021-09-13 11:13:48 +02:00
2020-02-06 17:16:34 +02:00
//pollForResult
log . Entry ( ) . Debugf ( "Poll for scan result %v" , productID )
result := client . PollForResult ( productID , config . TimeoutMinutes )
2020-09-02 10:41:12 +02:00
// write results to file
2020-02-06 17:16:34 +02:00
jsonData , _ := json . Marshal ( result )
2022-07-21 09:04:21 +02:00
if err := utils . FileWrite ( filepath . Join ( reportPath , scanResultFile ) , jsonData , 0644 ) ; err != nil {
log . Entry ( ) . Warningf ( "failed to write result file: %v" , err )
}
2020-02-06 17:16:34 +02:00
//check if result is ok else notify
2020-09-02 10:41:12 +02:00
if protecode . HasFailed ( result ) {
log . SetErrorCategory ( log . ErrorService )
return fmt . Errorf ( "protecode scan failed: %v/products/%v" , config . ServerURL , productID )
2020-02-06 17:16:34 +02:00
}
2020-09-02 10:41:12 +02:00
2020-02-06 17:16:34 +02:00
//loadReport
log . Entry ( ) . Debugf ( "Load report %v for %v" , config . ReportFileName , productID )
resp := client . LoadReport ( config . ReportFileName , productID )
2022-03-23 11:02:00 +02:00
buf , err := io . ReadAll ( * resp )
if err != nil {
return fmt . Errorf ( "unable to process protecode report %v" , err )
}
if err = utils . FileWrite ( config . ReportFileName , buf , 0644 ) ; err != nil {
2020-09-02 10:41:12 +02:00
log . Entry ( ) . Warningf ( "failed to write report: %s" , err )
2020-02-06 17:16:34 +02:00
}
2022-03-23 11:02:00 +02:00
2020-02-06 17:16:34 +02:00
//clean scan from server
log . Entry ( ) . Debugf ( "Delete scan %v for %v" , config . CleanupMode , productID )
client . DeleteScan ( config . CleanupMode , productID )
//count vulnerabilities
2020-09-02 10:41:12 +02:00
log . Entry ( ) . Debug ( "Parse scan result" )
2020-02-06 17:16:34 +02:00
parsedResult , vulns := client . ParseResultForInflux ( result . Result , config . ExcludeCVEs )
log . Entry ( ) . Debug ( "Write report to filesystem" )
2020-09-02 10:41:12 +02:00
if err := protecode . WriteReport (
protecode . ReportData {
ServerURL : config . ServerURL ,
FailOnSevereVulnerabilities : config . FailOnSevereVulnerabilities ,
ExcludeCVEs : config . ExcludeCVEs ,
Target : config . ReportFileName ,
Vulnerabilities : vulns ,
ProductID : fmt . Sprintf ( "%v" , productID ) ,
2022-03-23 11:02:00 +02:00
} , reportPath , stepResultFile , parsedResult , utils ) ; err != nil {
2020-09-02 10:41:12 +02:00
log . Entry ( ) . Warningf ( "failed to write report: %v" , err )
}
log . Entry ( ) . Debug ( "Write influx data" )
setInfluxData ( influx , parsedResult )
2020-02-06 17:16:34 +02:00
2020-08-07 13:03:38 +02:00
// write reports JSON
2022-03-23 11:02:00 +02:00
reports := [ ] piperutils . Path {
2020-08-07 13:03:38 +02:00
{ Target : config . ReportFileName , Mandatory : true } ,
2020-09-02 10:41:12 +02:00
{ Target : stepResultFile , Mandatory : true } ,
{ Target : scanResultFile , Mandatory : true } ,
2020-08-07 13:03:38 +02:00
}
// write links JSON
2021-06-23 15:05:00 +02:00
webuiURL := fmt . Sprintf ( webReportPath , config . ServerURL , productID )
2022-03-23 11:02:00 +02:00
links := [ ] piperutils . Path {
2021-06-23 15:05:00 +02:00
{ Name : "Protecode WebUI" , Target : webuiURL } ,
2020-08-07 13:03:38 +02:00
{ Name : "Protecode Report" , Target : path . Join ( "artifact" , config . ReportFileName ) , Scope : "job" } ,
}
2021-06-23 15:05:00 +02:00
2021-07-12 12:20:25 +02:00
// write custom report
scanReport := protecode . CreateCustomReport ( fileName , productID , parsedResult , vulns )
2022-03-23 11:02:00 +02:00
paths , err := protecode . WriteCustomReports ( scanReport , fileName , fmt . Sprint ( productID ) , utils )
2021-07-12 12:20:25 +02:00
if err != nil {
// do not fail - consider failing later on
log . Entry ( ) . Warning ( "failed to create custom HTML/MarkDown file ..." , err )
} else {
reports = append ( reports , paths ... )
}
2021-06-23 15:05:00 +02:00
// create toolrecord file
2022-08-09 10:57:02 +02:00
toolRecordFileName , err := createToolRecordProtecode ( utils , "./" , config , productID , webuiURL )
2021-06-23 15:05:00 +02:00
if err != nil {
// do not fail until the framework is well established
log . Entry ( ) . Warning ( "TR_PROTECODE: Failed to create toolrecord file ..." , err )
} else {
2022-03-23 11:02:00 +02:00
reports = append ( reports , piperutils . Path { Target : toolRecordFileName } )
2021-06-23 15:05:00 +02:00
}
2022-08-09 10:57:02 +02:00
piperutils . PersistReportsAndLinks ( "protecodeExecuteScan" , "" , utils , reports , links )
2020-08-07 13:03:38 +02:00
2020-09-02 10:41:12 +02:00
if config . FailOnSevereVulnerabilities && protecode . HasSevereVulnerabilities ( result . Result , config . ExcludeCVEs ) {
log . SetErrorCategory ( log . ErrorCompliance )
return fmt . Errorf ( "the product is not compliant" )
2022-07-12 11:43:24 +02:00
} else if protecode . HasSevereVulnerabilities ( result . Result , config . ExcludeCVEs ) {
log . Entry ( ) . Infof ( "policy violation(s) found - step will only create data but not fail due to setting failOnSevereVulnerabilities: false" )
2020-09-02 10:41:12 +02:00
}
return nil
2020-02-06 17:16:34 +02:00
}
func setInfluxData ( influx * protecodeExecuteScanInflux , result map [ string ] int ) {
2021-03-10 17:00:53 +02:00
influx . protecode_data . fields . historical_vulnerabilities = result [ "historical_vulnerabilities" ]
influx . protecode_data . fields . triaged_vulnerabilities = result [ "triaged_vulnerabilities" ]
influx . protecode_data . fields . excluded_vulnerabilities = result [ "excluded_vulnerabilities" ]
influx . protecode_data . fields . minor_vulnerabilities = result [ "minor_vulnerabilities" ]
influx . protecode_data . fields . major_vulnerabilities = result [ "major_vulnerabilities" ]
influx . protecode_data . fields . vulnerabilities = result [ "vulnerabilities" ]
2020-02-06 17:16:34 +02:00
}
2022-03-23 11:02:00 +02:00
func createProtecodeClient ( config * protecodeExecuteScanOptions ) protecode . Protecode {
2020-02-06 17:16:34 +02:00
var duration time . Duration = time . Duration ( time . Minute * 1 )
if len ( config . TimeoutMinutes ) > 0 {
dur , err := time . ParseDuration ( fmt . Sprintf ( "%vm" , config . TimeoutMinutes ) )
if err != nil {
log . Entry ( ) . Warnf ( "Failed to parse timeout %v, switched back to default timeout %v minutes" , config . TimeoutMinutes , duration )
} else {
duration = dur
}
}
pc := protecode . Protecode { }
protecodeOptions := protecode . Options {
2022-10-10 10:55:21 +02:00
ServerURL : config . ServerURL ,
Logger : log . Entry ( ) . WithField ( "package" , "SAP/jenkins-library/pkg/protecode" ) ,
Duration : duration ,
Username : config . Username ,
Password : config . Password ,
UserAPIKey : config . UserAPIKey ,
2020-02-06 17:16:34 +02:00
}
pc . SetOptions ( protecodeOptions )
return pc
}
2020-09-02 10:41:12 +02:00
2022-03-23 11:02:00 +02:00
func uploadScanOrDeclareFetch ( utils protecodeUtils , config protecodeExecuteScanOptions , productID int , client protecode . Protecode , fileName string ) int {
2021-09-13 11:13:48 +02:00
// check if product doesn't exist then create a new one.
if productID <= 0 {
log . Entry ( ) . Infof ( "New product creation started ... " )
2022-03-23 11:02:00 +02:00
productID = uploadFile ( utils , config , productID , client , fileName , false )
2021-09-13 11:13:48 +02:00
log . Entry ( ) . Infof ( "New product has been successfully created: %v" , productID )
return productID
// In case product already exists and "VerifyOnly (reuseExisting)" is false then we replace binary without creating a new product.
} else if ( productID > 0 ) && ! config . VerifyOnly {
log . Entry ( ) . Infof ( "Product already exists and 'VerifyOnly (reuseExisting)' is false then product (%v) binary and scan result will be replaced without creating a new product." , productID )
2022-03-23 11:02:00 +02:00
productID = uploadFile ( utils , config , productID , client , fileName , true )
2021-09-13 11:13:48 +02:00
return productID
// If product already exists and "reuseExisting" option is enabled then return the latest similar scan result.
} else {
log . Entry ( ) . Infof ( "VerifyOnly (reuseExisting) option is enabled and returned productID: %v" , productID )
return productID
}
}
2022-03-23 11:02:00 +02:00
func uploadFile ( utils protecodeUtils , config protecodeExecuteScanOptions , productID int , client protecode . Protecode , fileName string , replaceBinary bool ) int {
2021-09-13 11:13:48 +02:00
2022-01-19 11:30:59 +02:00
// get calculated version for Version field
version := getProcessedVersion ( & config )
2021-06-15 22:29:24 +02:00
if len ( config . FetchURL ) > 0 {
log . Entry ( ) . Debugf ( "Declare fetch url %v" , config . FetchURL )
2022-11-03 18:53:23 +02:00
resultData := client . DeclareFetchURL ( config . CleanupMode , config . Group , config . CustomDataJSONMap , config . FetchURL , version , productID , replaceBinary )
2021-09-13 11:13:48 +02:00
productID = resultData . Result . ProductID
2021-06-15 22:29:24 +02:00
} else {
log . Entry ( ) . Debugf ( "Upload file path: %v" , config . FilePath )
if len ( config . FilePath ) <= 0 {
2021-09-13 11:13:48 +02:00
log . Entry ( ) . Fatalf ( "There is no file path configured for upload : %v" , config . FilePath )
2021-06-15 22:29:24 +02:00
}
pathToFile := filepath . Join ( config . FilePath , fileName )
2022-03-23 11:02:00 +02:00
if exists , err := utils . FileExists ( pathToFile ) ; err != nil && ! exists {
2021-06-15 22:29:24 +02:00
log . Entry ( ) . Fatalf ( "There is no file for upload: %v" , pathToFile )
2020-02-06 17:16:34 +02:00
}
2021-06-15 22:29:24 +02:00
combinedFileName := fileName
if len ( config . PullRequestName ) > 0 {
combinedFileName = fmt . Sprintf ( "%v_%v" , config . PullRequestName , fileName )
}
2022-11-03 18:53:23 +02:00
resultData := client . UploadScanFile ( config . CleanupMode , config . Group , config . CustomDataJSONMap , pathToFile , combinedFileName , version , productID , replaceBinary )
2021-09-13 11:13:48 +02:00
productID = resultData . Result . ProductID
2020-02-06 17:16:34 +02:00
}
2021-09-13 11:13:48 +02:00
return productID
2020-02-06 17:16:34 +02:00
}
2023-06-14 09:11:33 +02:00
func correctDockerConfigEnvVar ( config * protecodeExecuteScanOptions , utils protecodeUtils ) error {
var err error
2020-08-12 14:57:11 +02:00
path := config . DockerConfigJSON
2023-06-14 09:11:33 +02:00
if len ( config . DockerConfigJSON ) > 0 && len ( config . DockerRegistryURL ) > 0 && len ( config . ContainerRegistryPassword ) > 0 && len ( config . ContainerRegistryUser ) > 0 {
path , err = docker . CreateDockerConfigJSON ( config . DockerRegistryURL , config . ContainerRegistryUser , config . ContainerRegistryPassword , dockerConfigFile , config . DockerConfigJSON , utils )
}
if err != nil {
return errors . Wrapf ( err , "failed to create / update docker config json file" )
}
2020-05-06 16:07:10 +02:00
if len ( path ) > 0 {
2020-08-11 14:42:08 +02:00
log . Entry ( ) . Infof ( "Docker credentials configuration: %v" , path )
2020-05-06 16:07:10 +02:00
path , _ := filepath . Abs ( path )
2020-08-11 14:42:08 +02:00
// use parent directory
2020-05-06 16:07:10 +02:00
path = filepath . Dir ( path )
os . Setenv ( "DOCKER_CONFIG" , path )
2020-08-11 14:42:08 +02:00
} else {
log . Entry ( ) . Info ( "Docker credentials configuration: NONE" )
2020-05-06 16:07:10 +02:00
}
2023-06-14 09:11:33 +02:00
return nil
2020-05-06 16:07:10 +02:00
}
2020-09-02 15:00:55 +02:00
2022-01-19 11:30:59 +02:00
// Calculate version based on versioning model and artifact version or return custom scan version provided by user
func getProcessedVersion ( config * protecodeExecuteScanOptions ) string {
processedVersion := config . CustomScanVersion
if len ( processedVersion ) > 0 {
log . Entry ( ) . Infof ( "Using custom version: %v" , processedVersion )
} else {
if len ( config . VersioningModel ) > 0 {
processedVersion = versioning . ApplyVersioningModel ( config . VersioningModel , config . Version )
} else {
// By default 'major' if <config.VersioningModel> not provided
processedVersion = versioning . ApplyVersioningModel ( "major" , config . Version )
}
}
return processedVersion
}
2021-06-23 15:05:00 +02:00
// create toolrecord file for protecode
// todo: check if group and product names can be retrieved
2022-08-09 10:57:02 +02:00
func createToolRecordProtecode ( utils protecodeUtils , workspace string , config * protecodeExecuteScanOptions , productID int , webuiURL string ) ( string , error ) {
record := toolrecord . New ( utils , workspace , "protecode" , config . ServerURL )
2021-07-23 08:48:48 +02:00
groupURL := config . ServerURL + "/#/groups/" + config . Group
2021-06-23 15:05:00 +02:00
err := record . AddKeyData ( "group" ,
config . Group ,
2021-07-23 08:48:48 +02:00
config . Group , // todo figure out display name
groupURL )
2021-06-23 15:05:00 +02:00
if err != nil {
return "" , err
}
err = record . AddKeyData ( "product" ,
strconv . Itoa ( productID ) ,
2021-07-23 08:48:48 +02:00
strconv . Itoa ( productID ) , // todo figure out display name
2021-06-23 15:05:00 +02:00
webuiURL )
if err != nil {
return "" , err
}
err = record . Persist ( )
if err != nil {
return "" , err
}
return record . GetFileName ( ) , nil
}