You've already forked sap-jenkins-library
mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-09-16 09:26:22 +02:00
feat(codeqlExecuteScan): multi language support (#5462)
This commit is contained in:
@@ -8,6 +8,9 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/google/shlex"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/codeql"
|
||||
"github.com/SAP/jenkins-library/pkg/command"
|
||||
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
||||
@@ -15,8 +18,6 @@ import (
|
||||
"github.com/SAP/jenkins-library/pkg/maven"
|
||||
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||
"github.com/google/shlex"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type codeqlExecuteScanUtils interface {
|
||||
@@ -141,7 +142,7 @@ func runCodeqlExecuteScan(config *codeqlExecuteScanOptions, telemetryData *telem
|
||||
var reports []piperutils.Path
|
||||
|
||||
dbCreateCustomFlags := codeql.ParseCustomFlags(config.DatabaseCreateFlags)
|
||||
err := runDatabaseCreate(config, dbCreateCustomFlags, utils)
|
||||
isMultiLang, err := runDatabaseCreate(config, dbCreateCustomFlags, utils)
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Error("failed to create codeql database")
|
||||
return reports, err
|
||||
@@ -154,7 +155,7 @@ func runCodeqlExecuteScan(config *codeqlExecuteScanOptions, telemetryData *telem
|
||||
}
|
||||
|
||||
dbAnalyzeCustomFlags := codeql.ParseCustomFlags(config.DatabaseAnalyzeFlags)
|
||||
scanReports, err := runDatabaseAnalyze(config, dbAnalyzeCustomFlags, utils)
|
||||
scanReports, sarifFiles, err := runDatabaseAnalyze(config, dbAnalyzeCustomFlags, utils, isMultiLang)
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Error("failed to analyze codeql database")
|
||||
return reports, err
|
||||
@@ -194,7 +195,7 @@ func runCodeqlExecuteScan(config *codeqlExecuteScanOptions, telemetryData *telem
|
||||
return reports, fmt.Errorf("failed running upload-results as githubToken was not specified")
|
||||
}
|
||||
|
||||
err = uploadSarifResults(config, token, repoInfo, utils)
|
||||
err = uploadSarifResults(config, token, repoInfo, sarifFiles, utils)
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Error("failed to upload sarif results")
|
||||
return reports, err
|
||||
@@ -242,33 +243,77 @@ func runCodeqlExecuteScan(config *codeqlExecuteScanOptions, telemetryData *telem
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
func runDatabaseCreate(config *codeqlExecuteScanOptions, customFlags map[string]string, utils codeqlExecuteScanUtils) error {
|
||||
cmd, err := prepareCmdForDatabaseCreate(customFlags, config, utils)
|
||||
func runDatabaseCreate(config *codeqlExecuteScanOptions, customFlags map[string]string, utils codeqlExecuteScanUtils) (bool, error) {
|
||||
isMultiLang, cmd, err := prepareCmdForDatabaseCreate(customFlags, config, utils)
|
||||
if err != nil {
|
||||
log.Entry().Error("failed to prepare command for codeql database create")
|
||||
return err
|
||||
return isMultiLang, err
|
||||
}
|
||||
if err = execute(utils, cmd, GeneralConfig.Verbose); err != nil {
|
||||
log.Entry().Error("failed running command codeql database create")
|
||||
return err
|
||||
return isMultiLang, err
|
||||
}
|
||||
return nil
|
||||
return isMultiLang, nil
|
||||
}
|
||||
|
||||
func runDatabaseAnalyze(config *codeqlExecuteScanOptions, customFlags map[string]string, utils codeqlExecuteScanUtils) ([]piperutils.Path, error) {
|
||||
sarifReport, err := executeAnalysis("sarif-latest", "codeqlReport.sarif", customFlags, config, utils)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func runDatabaseAnalyze(config *codeqlExecuteScanOptions, customFlags map[string]string, utils codeqlExecuteScanUtils, isMultiLang bool) ([]piperutils.Path, []string, error) {
|
||||
var reports []piperutils.Path
|
||||
var sarifFiles []string
|
||||
|
||||
if !isMultiLang {
|
||||
sarifReport, sarifPath, err := executeAnalysis("sarif-latest", "codeqlReport.sarif", customFlags, config, utils, config.Database, "")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
reports = append(reports, sarifReport...)
|
||||
if sarifPath != "" {
|
||||
sarifFiles = append(sarifFiles, sarifPath)
|
||||
}
|
||||
|
||||
csvReport, _, err := executeAnalysis("csv", "codeqlReport.csv", customFlags, config, utils, config.Database, "")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
reports = append(reports, csvReport...)
|
||||
return reports, sarifFiles, nil
|
||||
}
|
||||
csvReport, err := executeAnalysis("csv", "codeqlReport.csv", customFlags, config, utils)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
languages := getLanguageList(config)
|
||||
for _, lang := range languages {
|
||||
lang = strings.TrimSpace(lang)
|
||||
if lang == "" {
|
||||
continue
|
||||
}
|
||||
dbPath := filepath.Join(config.Database, lang)
|
||||
|
||||
sarifOut := fmt.Sprintf("%s.sarif", lang)
|
||||
localFlags := cloneFlags(customFlags)
|
||||
if !codeql.IsFlagSetByUser(localFlags, []string{"--sarif-category"}) {
|
||||
localFlags["--sarif-category"] = fmt.Sprintf("--sarif-category=%s", lang)
|
||||
}
|
||||
|
||||
sarifReport, sarifPath, err := executeAnalysis("sarif-latest", sarifOut, localFlags, config, utils, dbPath, lang)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
reports = append(reports, sarifReport...)
|
||||
if sarifPath != "" {
|
||||
sarifFiles = append(sarifFiles, sarifPath)
|
||||
}
|
||||
|
||||
csvOut := fmt.Sprintf("%s.csv", lang)
|
||||
csvReport, _, err := executeAnalysis("csv", csvOut, customFlags, config, utils, dbPath, lang)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
reports = append(reports, csvReport...)
|
||||
}
|
||||
return append(sarifReport, csvReport...), nil
|
||||
|
||||
return reports, sarifFiles, nil
|
||||
}
|
||||
|
||||
func runGithubUploadResults(config *codeqlExecuteScanOptions, repoInfo *codeql.RepoInfo, token string, utils codeqlExecuteScanUtils) (string, error) {
|
||||
cmd := prepareCmdForUploadResults(config, repoInfo, token)
|
||||
func runGithubUploadResults(repoInfo *codeql.RepoInfo, token string, sarifPath string, utils codeqlExecuteScanUtils) (string, error) {
|
||||
cmd := prepareCmdForUploadResults(repoInfo, token, sarifPath)
|
||||
|
||||
var bufferOut, bufferErr bytes.Buffer
|
||||
utils.Stdout(&bufferOut)
|
||||
@@ -289,41 +334,59 @@ func runGithubUploadResults(config *codeqlExecuteScanOptions, repoInfo *codeql.R
|
||||
return url, nil
|
||||
}
|
||||
|
||||
func executeAnalysis(format, reportName string, customFlags map[string]string, config *codeqlExecuteScanOptions, utils codeqlExecuteScanUtils) ([]piperutils.Path, error) {
|
||||
func executeAnalysis(format, reportPath string, customFlags map[string]string, config *codeqlExecuteScanOptions, utils codeqlExecuteScanUtils, databasePath string, langForLog string) ([]piperutils.Path, string, error) {
|
||||
moduleTargetPath := filepath.Join(config.ModulePath, "target")
|
||||
report := filepath.Join(moduleTargetPath, reportName)
|
||||
cmd, err := prepareCmdForDatabaseAnalyze(utils, customFlags, config, format, report)
|
||||
report := filepath.Join(moduleTargetPath, reportPath)
|
||||
cmd, err := prepareCmdForDatabaseAnalyze(utils, customFlags, config, format, report, databasePath)
|
||||
if err != nil {
|
||||
log.Entry().Errorf("failed to prepare command for codeql database analyze (format=%s)", format)
|
||||
return nil, err
|
||||
if langForLog == "" {
|
||||
log.Entry().Errorf("failed to prepare command for codeql database analyze (format=%s)", format)
|
||||
} else {
|
||||
log.Entry().Errorf("failed to prepare command for codeql database analyze (format=%s, lang=%s)", format, langForLog)
|
||||
}
|
||||
return nil, "", err
|
||||
}
|
||||
if err = execute(utils, cmd, GeneralConfig.Verbose); err != nil {
|
||||
log.Entry().Errorf("failed running command codeql database analyze for %s generation", format)
|
||||
return nil, err
|
||||
if langForLog == "" {
|
||||
log.Entry().Errorf("failed running command codeql database analyze for %s generation", format)
|
||||
} else {
|
||||
log.Entry().Errorf("failed running command codeql database analyze for %s generation (lang=%s)", format, langForLog)
|
||||
}
|
||||
return nil, "", err
|
||||
}
|
||||
return []piperutils.Path{
|
||||
{Target: report},
|
||||
}, nil
|
||||
{Target: report},
|
||||
}, func() string {
|
||||
if strings.HasPrefix(format, "sarif") {
|
||||
return report
|
||||
}
|
||||
return ""
|
||||
}(), nil
|
||||
}
|
||||
|
||||
func prepareCmdForDatabaseCreate(customFlags map[string]string, config *codeqlExecuteScanOptions, utils codeqlExecuteScanUtils) ([]string, error) {
|
||||
func prepareCmdForDatabaseCreate(customFlags map[string]string, config *codeqlExecuteScanOptions, utils codeqlExecuteScanUtils) (bool, []string, error) {
|
||||
cmd := []string{"database", "create", config.Database}
|
||||
cmd = codeql.AppendFlagIfNotSetByUser(cmd, []string{"--overwrite", "--no-overwrite"}, []string{"--overwrite"}, customFlags)
|
||||
cmd = codeql.AppendFlagIfNotSetByUser(cmd, []string{"--source-root", "-s"}, []string{"--source-root", "."}, customFlags)
|
||||
cmd = codeql.AppendFlagIfNotSetByUser(cmd, []string{"--working-dir"}, []string{"--working-dir", config.ModulePath}, customFlags)
|
||||
|
||||
isMultiLang := false
|
||||
if !codeql.IsFlagSetByUser(customFlags, []string{"--language", "-l"}) {
|
||||
language := getLangFromBuildTool(config.BuildTool)
|
||||
if len(language) == 0 && len(config.Language) == 0 {
|
||||
if config.BuildTool == "custom" {
|
||||
return nil, fmt.Errorf("as the buildTool is custom. please specify the language parameter")
|
||||
return false, nil, fmt.Errorf("as the buildTool is custom. please specify the language parameter")
|
||||
} else {
|
||||
return nil, fmt.Errorf("the step could not recognize the specified buildTool %s. please specify valid buildtool", config.BuildTool)
|
||||
return false, nil, fmt.Errorf("the step could not recognize the specified buildTool %s. please specify valid buildtool", config.BuildTool)
|
||||
}
|
||||
}
|
||||
if len(language) > 0 {
|
||||
cmd = append(cmd, "--language="+language)
|
||||
} else {
|
||||
if strings.Contains(config.Language, ",") { // coma separation used to specify multiple languages
|
||||
isMultiLang = true
|
||||
cmd = append(cmd, "--db-cluster")
|
||||
}
|
||||
cmd = append(cmd, "--language="+config.Language)
|
||||
}
|
||||
}
|
||||
@@ -341,19 +404,19 @@ func prepareCmdForDatabaseCreate(customFlags map[string]string, config *codeqlEx
|
||||
}
|
||||
cmd = codeql.AppendCustomFlags(cmd, customFlags)
|
||||
|
||||
return cmd, nil
|
||||
return isMultiLang, cmd, nil
|
||||
}
|
||||
|
||||
func prepareCmdForDatabaseAnalyze(utils codeqlExecuteScanUtils, customFlags map[string]string, config *codeqlExecuteScanOptions, format, reportName string) ([]string, error) {
|
||||
cmd := []string{"database", "analyze", "--format=" + format, "--output=" + reportName, config.Database}
|
||||
func prepareCmdForDatabaseAnalyze(utils codeqlExecuteScanUtils, customFlags map[string]string, config *codeqlExecuteScanOptions, format, reportPath, databasePath string) ([]string, error) {
|
||||
cmd := []string{"database", "analyze", "--format=" + format, "--output=" + reportPath, databasePath}
|
||||
cmd = codeql.AppendThreadsAndRam(cmd, config.Threads, config.Ram, customFlags)
|
||||
cmd = codeql.AppendCustomFlags(cmd, customFlags)
|
||||
cmd = appendCodeqlQuerySuite(utils, cmd, config.QuerySuite, config.TransformQuerySuite)
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func prepareCmdForUploadResults(config *codeqlExecuteScanOptions, repoInfo *codeql.RepoInfo, token string) []string {
|
||||
cmd := []string{"github", "upload-results", "--sarif=" + filepath.Join(config.ModulePath, "target", "codeqlReport.sarif")}
|
||||
func prepareCmdForUploadResults(repoInfo *codeql.RepoInfo, token string, sarifPath string) []string {
|
||||
cmd := []string{"github", "upload-results", "--sarif=" + sarifPath}
|
||||
|
||||
//if no git params are passed(commitId, reference, serverUrl, repository), then codeql tries to auto populate it based on git information of the checkout repository.
|
||||
//It also depends on the orchestrator. Some orchestrator keep git information and some not.
|
||||
@@ -380,16 +443,22 @@ func prepareCmdForUploadResults(config *codeqlExecuteScanOptions, repoInfo *code
|
||||
return cmd
|
||||
}
|
||||
|
||||
func uploadSarifResults(config *codeqlExecuteScanOptions, token string, repoInfo *codeql.RepoInfo, utils codeqlExecuteScanUtils) error {
|
||||
sarifUrl, err := runGithubUploadResults(config, repoInfo, token, utils)
|
||||
if err != nil {
|
||||
return err
|
||||
func uploadSarifResults(config *codeqlExecuteScanOptions, token string, repoInfo *codeql.RepoInfo, sarifFiles []string, utils codeqlExecuteScanUtils) error {
|
||||
// fallback
|
||||
if len(sarifFiles) == 0 {
|
||||
sarifFiles = []string{filepath.Join(config.ModulePath, "target", "codeqlReport.sarif")}
|
||||
}
|
||||
|
||||
codeqlSarifUploader := codeql.NewCodeqlSarifUploaderInstance(sarifUrl, token)
|
||||
err = codeql.WaitSarifUploaded(config.SarifCheckMaxRetries, config.SarifCheckRetryInterval, &codeqlSarifUploader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to upload sarif")
|
||||
for _, sarifPath := range sarifFiles {
|
||||
sarifUrl, err := runGithubUploadResults(repoInfo, token, sarifPath, utils)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
codeqlSarifUploader := codeql.NewCodeqlSarifUploaderInstance(sarifUrl, token)
|
||||
if err := codeql.WaitSarifUploaded(config.SarifCheckMaxRetries, config.SarifCheckRetryInterval, &codeqlSarifUploader); err != nil {
|
||||
return errors.Wrapf(err, "failed to upload sarif %s", sarifPath)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -499,3 +568,35 @@ func updateCmdFlag(config *codeqlExecuteScanOptions, customFlags map[string]stri
|
||||
customFlags["--command"] = buildCmd
|
||||
delete(customFlags, "-c")
|
||||
}
|
||||
|
||||
func getLanguageList(config *codeqlExecuteScanOptions) []string {
|
||||
// prefer explicit config.Language if present; otherwise derive from build tool
|
||||
if strings.Contains(config.Language, ",") {
|
||||
parts := strings.Split(config.Language, ",")
|
||||
out := make([]string, 0, len(parts))
|
||||
for _, p := range parts {
|
||||
p = strings.TrimSpace(p)
|
||||
if p != "" {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
if config.Language != "" {
|
||||
return []string{strings.TrimSpace(config.Language)}
|
||||
}
|
||||
// fall back to inferred language (single)
|
||||
inferred := getLangFromBuildTool(config.BuildTool)
|
||||
if inferred != "" {
|
||||
return []string{inferred}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cloneFlags(src map[string]string) map[string]string {
|
||||
dst := make(map[string]string, len(src))
|
||||
for k, v := range src {
|
||||
dst[k] = v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
@@ -284,7 +284,7 @@ func addCodeqlExecuteScanFlags(cmd *cobra.Command, stepConfig *codeqlExecuteScan
|
||||
cmd.Flags().StringVar(&stepConfig.GithubToken, "githubToken", os.Getenv("PIPER_githubToken"), "GitHub personal access token in plain text. NEVER set this parameter in a file commited to a source code repository. This parameter is intended to be used from the command line or set securely via the environment variable listed below. In most pipeline use-cases, you should instead either store the token in Vault (where it can be automatically retrieved by the step from one of the paths listed below) or store it as a Jenkins secret and configure the secret's id via the `githubTokenCredentialsId` parameter.")
|
||||
cmd.Flags().StringVar(&stepConfig.BuildTool, "buildTool", `maven`, "Defines the build tool which is used for building the project.")
|
||||
cmd.Flags().StringVar(&stepConfig.BuildCommand, "buildCommand", os.Getenv("PIPER_buildCommand"), "Command to build the project")
|
||||
cmd.Flags().StringVar(&stepConfig.Language, "language", os.Getenv("PIPER_language"), "The programming language used to analyze.")
|
||||
cmd.Flags().StringVar(&stepConfig.Language, "language", os.Getenv("PIPER_language"), "The programming language used to analyze. Use coma separation and select custom build tool to analyze multiple languages")
|
||||
cmd.Flags().StringVar(&stepConfig.ModulePath, "modulePath", `./`, "Allows providing the path for the module to scan")
|
||||
cmd.Flags().StringVar(&stepConfig.Database, "database", `codeqlDB`, "Path to the CodeQL database to create. This directory will be created, and must not already exist.")
|
||||
cmd.Flags().StringVar(&stepConfig.QuerySuite, "querySuite", os.Getenv("PIPER_querySuite"), "The name of a CodeQL query suite. If omitted, the default query suite for the language of the database being analyzed will be used.")
|
||||
|
@@ -11,9 +11,11 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/codeql"
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||
)
|
||||
|
||||
type codeqlExecuteScanMockUtils struct {
|
||||
@@ -364,9 +366,10 @@ func TestPrepareCmdForDatabaseCreate(t *testing.T) {
|
||||
BuildTool: "maven",
|
||||
BuildCommand: "mvn clean install",
|
||||
}
|
||||
cmd, err := prepareCmdForDatabaseCreate(map[string]string{}, config, newCodeqlExecuteScanTestsUtils())
|
||||
isMultiLang, cmd, err := prepareCmdForDatabaseCreate(map[string]string{}, config, newCodeqlExecuteScanTestsUtils())
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, cmd)
|
||||
assert.False(t, isMultiLang)
|
||||
assert.Equal(t, 10, len(cmd))
|
||||
assert.Equal(t, "database create codeqlDB --overwrite --source-root . --working-dir ./ --language=java --command=mvn clean install",
|
||||
strings.Join(cmd, " "))
|
||||
@@ -379,9 +382,10 @@ func TestPrepareCmdForDatabaseCreate(t *testing.T) {
|
||||
BuildTool: "custom",
|
||||
Language: "javascript",
|
||||
}
|
||||
cmd, err := prepareCmdForDatabaseCreate(map[string]string{}, config, newCodeqlExecuteScanTestsUtils())
|
||||
isMultiLang, cmd, err := prepareCmdForDatabaseCreate(map[string]string{}, config, newCodeqlExecuteScanTestsUtils())
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, cmd)
|
||||
assert.False(t, isMultiLang)
|
||||
assert.Equal(t, 9, len(cmd))
|
||||
assert.Equal(t, "database create codeqlDB --overwrite --source-root . --working-dir ./ --language=javascript",
|
||||
strings.Join(cmd, " "))
|
||||
@@ -393,7 +397,7 @@ func TestPrepareCmdForDatabaseCreate(t *testing.T) {
|
||||
ModulePath: "./",
|
||||
BuildTool: "custom",
|
||||
}
|
||||
_, err := prepareCmdForDatabaseCreate(map[string]string{}, config, newCodeqlExecuteScanTestsUtils())
|
||||
_, _, err := prepareCmdForDatabaseCreate(map[string]string{}, config, newCodeqlExecuteScanTestsUtils())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
@@ -403,7 +407,7 @@ func TestPrepareCmdForDatabaseCreate(t *testing.T) {
|
||||
ModulePath: "./",
|
||||
BuildTool: "test",
|
||||
}
|
||||
_, err := prepareCmdForDatabaseCreate(map[string]string{}, config, newCodeqlExecuteScanTestsUtils())
|
||||
_, _, err := prepareCmdForDatabaseCreate(map[string]string{}, config, newCodeqlExecuteScanTestsUtils())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
@@ -414,9 +418,10 @@ func TestPrepareCmdForDatabaseCreate(t *testing.T) {
|
||||
BuildTool: "test",
|
||||
Language: "javascript",
|
||||
}
|
||||
cmd, err := prepareCmdForDatabaseCreate(map[string]string{}, config, newCodeqlExecuteScanTestsUtils())
|
||||
isMultiLang, cmd, err := prepareCmdForDatabaseCreate(map[string]string{}, config, newCodeqlExecuteScanTestsUtils())
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, cmd)
|
||||
assert.False(t, isMultiLang)
|
||||
assert.Equal(t, 9, len(cmd))
|
||||
assert.Equal(t, "database create codeqlDB --overwrite --source-root . --working-dir ./ --language=javascript",
|
||||
strings.Join(cmd, " "))
|
||||
@@ -431,9 +436,10 @@ func TestPrepareCmdForDatabaseCreate(t *testing.T) {
|
||||
customFlags := map[string]string{
|
||||
"--source-root": "--source-root=customSrcRoot/",
|
||||
}
|
||||
cmd, err := prepareCmdForDatabaseCreate(customFlags, config, newCodeqlExecuteScanTestsUtils())
|
||||
isMultiLang, cmd, err := prepareCmdForDatabaseCreate(customFlags, config, newCodeqlExecuteScanTestsUtils())
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, cmd)
|
||||
assert.False(t, isMultiLang)
|
||||
assert.Equal(t, 8, len(cmd))
|
||||
assert.Equal(t, "database create codeqlDB --overwrite --working-dir ./ --language=javascript --source-root=customSrcRoot/",
|
||||
strings.Join(cmd, " "))
|
||||
@@ -451,14 +457,47 @@ func TestPrepareCmdForDatabaseCreate(t *testing.T) {
|
||||
"--source-root": "--source-root=customSrcRoot/",
|
||||
"-j": "-j=1",
|
||||
}
|
||||
cmd, err := prepareCmdForDatabaseCreate(customFlags, config, newCodeqlExecuteScanTestsUtils())
|
||||
isMultiLang, cmd, err := prepareCmdForDatabaseCreate(customFlags, config, newCodeqlExecuteScanTestsUtils())
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, cmd)
|
||||
assert.False(t, isMultiLang)
|
||||
assert.Equal(t, 10, len(cmd))
|
||||
assert.True(t, "database create codeqlDB --overwrite --working-dir ./ --language=javascript --ram=2000 -j=1 --source-root=customSrcRoot/" == strings.Join(cmd, " ") ||
|
||||
"database create codeqlDB --overwrite --working-dir ./ --language=javascript --ram=2000 --source-root=customSrcRoot/ -j=1" == strings.Join(cmd, " "))
|
||||
})
|
||||
|
||||
t.Run("Multi-language adds --db-cluster", func(t *testing.T) {
|
||||
config := &codeqlExecuteScanOptions{
|
||||
Database: "codeqlDB",
|
||||
ModulePath: "./",
|
||||
BuildTool: "custom",
|
||||
Language: "javascript,python",
|
||||
}
|
||||
isMultiLang, cmd, err := prepareCmdForDatabaseCreate(map[string]string{}, config, newCodeqlExecuteScanTestsUtils())
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, cmd)
|
||||
assert.True(t, isMultiLang)
|
||||
assert.Equal(t, 10, len(cmd))
|
||||
assert.Equal(t, "database create codeqlDB --overwrite --source-root . --working-dir ./ --db-cluster --language=javascript,python",
|
||||
strings.Join(cmd, " "))
|
||||
})
|
||||
|
||||
t.Run("Multi-language with build command", func(t *testing.T) {
|
||||
config := &codeqlExecuteScanOptions{
|
||||
Database: "codeqlDB",
|
||||
ModulePath: "./",
|
||||
BuildTool: "custom",
|
||||
Language: "go,python",
|
||||
BuildCommand: "make build",
|
||||
}
|
||||
isMultiLang, cmd, err := prepareCmdForDatabaseCreate(map[string]string{}, config, newCodeqlExecuteScanTestsUtils())
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, cmd)
|
||||
assert.True(t, isMultiLang)
|
||||
assert.Equal(t, 11, len(cmd))
|
||||
assert.Equal(t, "database create codeqlDB --overwrite --source-root . --working-dir ./ --db-cluster --language=go,python --command=make build",
|
||||
strings.Join(cmd, " "))
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrepareCmdForDatabaseAnalyze(t *testing.T) {
|
||||
@@ -469,7 +508,7 @@ func TestPrepareCmdForDatabaseAnalyze(t *testing.T) {
|
||||
config := &codeqlExecuteScanOptions{
|
||||
Database: "codeqlDB",
|
||||
}
|
||||
cmd, err := prepareCmdForDatabaseAnalyze(utils, map[string]string{}, config, "sarif-latest", "target/codeqlReport.sarif")
|
||||
cmd, err := prepareCmdForDatabaseAnalyze(utils, map[string]string{}, config, "sarif-latest", "target/codeqlReport.sarif", config.Database)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, cmd)
|
||||
assert.Equal(t, 5, len(cmd))
|
||||
@@ -480,7 +519,7 @@ func TestPrepareCmdForDatabaseAnalyze(t *testing.T) {
|
||||
config := &codeqlExecuteScanOptions{
|
||||
Database: "codeqlDB",
|
||||
}
|
||||
cmd, err := prepareCmdForDatabaseAnalyze(utils, map[string]string{}, config, "csv", "target/codeqlReport.csv")
|
||||
cmd, err := prepareCmdForDatabaseAnalyze(utils, map[string]string{}, config, "csv", "target/codeqlReport.csv", config.Database)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, cmd)
|
||||
assert.Equal(t, 5, len(cmd))
|
||||
@@ -492,7 +531,7 @@ func TestPrepareCmdForDatabaseAnalyze(t *testing.T) {
|
||||
Database: "codeqlDB",
|
||||
QuerySuite: "security.ql",
|
||||
}
|
||||
cmd, err := prepareCmdForDatabaseAnalyze(utils, map[string]string{}, config, "sarif-latest", "target/codeqlReport.sarif")
|
||||
cmd, err := prepareCmdForDatabaseAnalyze(utils, map[string]string{}, config, "sarif-latest", "target/codeqlReport.sarif", config.Database)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, cmd)
|
||||
assert.Equal(t, 6, len(cmd))
|
||||
@@ -506,7 +545,7 @@ func TestPrepareCmdForDatabaseAnalyze(t *testing.T) {
|
||||
Threads: "1",
|
||||
Ram: "2000",
|
||||
}
|
||||
cmd, err := prepareCmdForDatabaseAnalyze(utils, map[string]string{}, config, "sarif-latest", "target/codeqlReport.sarif")
|
||||
cmd, err := prepareCmdForDatabaseAnalyze(utils, map[string]string{}, config, "sarif-latest", "target/codeqlReport.sarif", config.Database)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, cmd)
|
||||
assert.Equal(t, 8, len(cmd))
|
||||
@@ -523,7 +562,7 @@ func TestPrepareCmdForDatabaseAnalyze(t *testing.T) {
|
||||
customFlags := map[string]string{
|
||||
"--threads": "--threads=2",
|
||||
}
|
||||
cmd, err := prepareCmdForDatabaseAnalyze(utils, customFlags, config, "sarif-latest", "target/codeqlReport.sarif")
|
||||
cmd, err := prepareCmdForDatabaseAnalyze(utils, customFlags, config, "sarif-latest", "target/codeqlReport.sarif", config.Database)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, cmd)
|
||||
assert.Equal(t, 8, len(cmd))
|
||||
@@ -540,7 +579,7 @@ func TestPrepareCmdForDatabaseAnalyze(t *testing.T) {
|
||||
customFlags := map[string]string{
|
||||
"-j": "-j=2",
|
||||
}
|
||||
cmd, err := prepareCmdForDatabaseAnalyze(utils, customFlags, config, "sarif-latest", "target/codeqlReport.sarif")
|
||||
cmd, err := prepareCmdForDatabaseAnalyze(utils, customFlags, config, "sarif-latest", "target/codeqlReport.sarif", config.Database)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, cmd)
|
||||
assert.Equal(t, 8, len(cmd))
|
||||
@@ -557,7 +596,7 @@ func TestPrepareCmdForDatabaseAnalyze(t *testing.T) {
|
||||
customFlags := map[string]string{
|
||||
"--no-download": "--no-download",
|
||||
}
|
||||
cmd, err := prepareCmdForDatabaseAnalyze(utils, customFlags, config, "sarif-latest", "target/codeqlReport.sarif")
|
||||
cmd, err := prepareCmdForDatabaseAnalyze(utils, customFlags, config, "sarif-latest", "target/codeqlReport.sarif", config.Database)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, cmd)
|
||||
assert.Equal(t, 9, len(cmd))
|
||||
@@ -580,7 +619,7 @@ func TestPrepareCmdForUploadResults(t *testing.T) {
|
||||
Owner: "owner",
|
||||
AnalyzedRef: "refs/heads/branch",
|
||||
}
|
||||
cmd := prepareCmdForUploadResults(config, repoInfo, "token")
|
||||
cmd := prepareCmdForUploadResults(repoInfo, "token", filepath.Join(config.ModulePath, "target", "codeqlReport.sarif"))
|
||||
assert.NotEmpty(t, cmd)
|
||||
assert.Equal(t, 8, len(cmd))
|
||||
})
|
||||
@@ -591,7 +630,7 @@ func TestPrepareCmdForUploadResults(t *testing.T) {
|
||||
ServerUrl: "http://github.com",
|
||||
Repo: "repo",
|
||||
}
|
||||
cmd := prepareCmdForUploadResults(config, repoInfo, "token")
|
||||
cmd := prepareCmdForUploadResults(repoInfo, "token", filepath.Join(config.ModulePath, "target", "codeqlReport.sarif"))
|
||||
assert.NotEmpty(t, cmd)
|
||||
assert.Equal(t, 6, len(cmd))
|
||||
})
|
||||
@@ -604,14 +643,14 @@ func TestPrepareCmdForUploadResults(t *testing.T) {
|
||||
Owner: "owner",
|
||||
AnalyzedRef: "refs/heads/branch",
|
||||
}
|
||||
cmd := prepareCmdForUploadResults(config, repoInfo, "")
|
||||
cmd := prepareCmdForUploadResults(repoInfo, "", filepath.Join(config.ModulePath, "target", "codeqlReport.sarif"))
|
||||
assert.NotEmpty(t, cmd)
|
||||
assert.Equal(t, 7, len(cmd))
|
||||
})
|
||||
|
||||
t.Run("Empty configs and token", func(t *testing.T) {
|
||||
repoInfo := &codeql.RepoInfo{}
|
||||
cmd := prepareCmdForUploadResults(config, repoInfo, "")
|
||||
cmd := prepareCmdForUploadResults(repoInfo, "", filepath.Join(config.ModulePath, "target", "codeqlReport.sarif"))
|
||||
assert.NotEmpty(t, cmd)
|
||||
assert.Equal(t, 3, len(cmd))
|
||||
})
|
||||
@@ -858,3 +897,183 @@ func TestCheckForCompliance(t *testing.T) {
|
||||
assert.NoError(t, checkForCompliance(scanResults, config, repoInfo))
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetLanguageList(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("Comma separated with spaces and empties", func(t *testing.T) {
|
||||
cfg := &codeqlExecuteScanOptions{
|
||||
Language: "javascript, python, ,go ,",
|
||||
BuildTool: "npm",
|
||||
}
|
||||
got := getLanguageList(cfg)
|
||||
assert.Equal(t, []string{"javascript", "python", "go"}, got)
|
||||
})
|
||||
|
||||
t.Run("Single explicit language", func(t *testing.T) {
|
||||
cfg := &codeqlExecuteScanOptions{
|
||||
Language: "python",
|
||||
}
|
||||
got := getLanguageList(cfg)
|
||||
assert.Equal(t, []string{"python"}, got)
|
||||
})
|
||||
|
||||
t.Run("Inferred from build tool", func(t *testing.T) {
|
||||
cfg := &codeqlExecuteScanOptions{
|
||||
BuildTool: "maven",
|
||||
}
|
||||
got := getLanguageList(cfg)
|
||||
assert.Equal(t, []string{"java"}, got)
|
||||
})
|
||||
|
||||
t.Run("None available returns nil", func(t *testing.T) {
|
||||
cfg := &codeqlExecuteScanOptions{}
|
||||
got := getLanguageList(cfg)
|
||||
assert.Nil(t, got)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCloneFlags(t *testing.T) {
|
||||
src := map[string]string{"--threads": "--threads=2", "--foo": "bar"}
|
||||
dst := cloneFlags(src)
|
||||
|
||||
assert.Equal(t, src, dst)
|
||||
|
||||
// check that changes in the dst map does not affect scr
|
||||
dst["--threads"] = "--threads=4"
|
||||
delete(dst, "--foo")
|
||||
|
||||
assert.Equal(t, "--threads=2", src["--threads"])
|
||||
assert.Equal(t, "bar", src["--foo"])
|
||||
}
|
||||
|
||||
func TestRunDatabaseAnalyze_SingleLanguage(t *testing.T) {
|
||||
t.Parallel()
|
||||
var calls []string
|
||||
utils := codeqlExecuteScanMockUtils{
|
||||
ExecMockRunner: &mock.ExecMockRunner{
|
||||
Stub: func(call string, stdoutReturn map[string]string, shouldFailOnCommand map[string]error, stdout io.Writer) error {
|
||||
calls = append(calls, call)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
FilesMock: &mock.FilesMock{},
|
||||
HttpClientMock: &mock.HttpClientMock{},
|
||||
}
|
||||
cfg := &codeqlExecuteScanOptions{
|
||||
Database: "codeqlDB",
|
||||
ModulePath: ".",
|
||||
Language: "javascript",
|
||||
}
|
||||
custom := map[string]string{}
|
||||
|
||||
reports, sarifs, err := runDatabaseAnalyze(cfg, custom, utils, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectSarif := filepath.Join(".", "target", "codeqlReport.sarif")
|
||||
expectCSV := filepath.Join(".", "target", "codeqlReport.csv")
|
||||
|
||||
assert.ElementsMatch(t,
|
||||
[]piperutils.Path{{Target: expectSarif}, {Target: expectCSV}},
|
||||
reports,
|
||||
)
|
||||
assert.Equal(t, []string{expectSarif}, sarifs)
|
||||
|
||||
joined := strings.Join(calls, "\n")
|
||||
assert.Contains(t, joined, "database analyze --format=sarif-latest --output="+expectSarif+" codeqlDB")
|
||||
assert.Contains(t, joined, "database analyze --format=csv --output="+expectCSV+" codeqlDB")
|
||||
}
|
||||
|
||||
func TestRunDatabaseAnalyze_MultiLanguage(t *testing.T) {
|
||||
t.Parallel()
|
||||
var calls []string
|
||||
utils := codeqlExecuteScanMockUtils{
|
||||
ExecMockRunner: &mock.ExecMockRunner{
|
||||
Stub: func(call string, stdoutReturn map[string]string, shouldFailOnCommand map[string]error, stdout io.Writer) error {
|
||||
calls = append(calls, call)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
FilesMock: &mock.FilesMock{},
|
||||
HttpClientMock: &mock.HttpClientMock{},
|
||||
}
|
||||
|
||||
cfg := &codeqlExecuteScanOptions{
|
||||
Database: "codeqlDB",
|
||||
ModulePath: ".",
|
||||
Language: "javascript,python",
|
||||
}
|
||||
custom := map[string]string{}
|
||||
|
||||
reports, sarifs, err := runDatabaseAnalyze(cfg, custom, utils, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
jsSarif := filepath.Join(".", "target", "javascript.sarif")
|
||||
pySarif := filepath.Join(".", "target", "python.sarif")
|
||||
jsCSV := filepath.Join(".", "target", "javascript.csv")
|
||||
pyCSV := filepath.Join(".", "target", "python.csv")
|
||||
|
||||
// reports should include all 4 files
|
||||
assert.ElementsMatch(t,
|
||||
[]piperutils.Path{{Target: jsSarif}, {Target: jsCSV}, {Target: pySarif}, {Target: pyCSV}},
|
||||
reports,
|
||||
)
|
||||
// sarifFiles should be both per-language sarif outputs
|
||||
assert.ElementsMatch(t, []string{jsSarif, pySarif}, sarifs)
|
||||
|
||||
joined := strings.Join(calls, "\n")
|
||||
|
||||
assert.Contains(t, joined, "database analyze --format=sarif-latest --output="+jsSarif+" codeqlDB/javascript")
|
||||
assert.Contains(t, joined, "database analyze --format=sarif-latest --output="+pySarif+" codeqlDB/python")
|
||||
|
||||
assert.Contains(t, joined, "--sarif-category=javascript")
|
||||
assert.Contains(t, joined, "--sarif-category=python")
|
||||
|
||||
assert.Contains(t, joined, "database analyze --format=csv --output="+jsCSV+" codeqlDB/javascript")
|
||||
assert.Contains(t, joined, "database analyze --format=csv --output="+pyCSV+" codeqlDB/python")
|
||||
}
|
||||
|
||||
func TestRunCustomCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("Success: simple command with args", func(t *testing.T) {
|
||||
var calls []string
|
||||
utils := codeqlExecuteScanMockUtils{
|
||||
ExecMockRunner: &mock.ExecMockRunner{
|
||||
Stub: func(call string, _ map[string]string, _ map[string]error, _ io.Writer) error {
|
||||
calls = append(calls, call)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
FilesMock: &mock.FilesMock{},
|
||||
HttpClientMock: &mock.HttpClientMock{},
|
||||
}
|
||||
|
||||
err := runCustomCommand(utils, `echo "hello world"`)
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, calls, 1) {
|
||||
assert.Equal(t, "echo hello world", calls[0])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Parse error: invalid quoting", func(t *testing.T) {
|
||||
utils := newCodeqlExecuteScanTestsUtils() // stub isn't invoked because split fails first
|
||||
err := runCustomCommand(utils, `echo "unterminated`)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Exec error: command fails to run", func(t *testing.T) {
|
||||
utils := codeqlExecuteScanMockUtils{
|
||||
ExecMockRunner: &mock.ExecMockRunner{
|
||||
Stub: func(call string, _ map[string]string, _ map[string]error, _ io.Writer) error {
|
||||
return fmt.Errorf("boom")
|
||||
},
|
||||
},
|
||||
FilesMock: &mock.FilesMock{},
|
||||
HttpClientMock: &mock.HttpClientMock{},
|
||||
}
|
||||
|
||||
err := runCustomCommand(utils, `false --flag`)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
@@ -34,6 +34,11 @@ func AppendFlagIfNotSetByUser(cmd []string, flagToCheck []string, flagToAppend [
|
||||
return cmd
|
||||
}
|
||||
|
||||
// AppendCustomFlags appends custom flags from the flags map to the command slice.
|
||||
// The flags map should contain flags in their complete form (e.g., key: "--flag", value: "--flag=value").
|
||||
// Only non-empty flags (after trimming whitespace) are appended to avoid adding
|
||||
// empty or whitespace-only entries to the command.
|
||||
// Returns a new slice with the original command elements followed by the valid flags.
|
||||
func AppendCustomFlags(cmd []string, flags map[string]string) []string {
|
||||
for _, flag := range flags {
|
||||
if strings.TrimSpace(flag) != "" {
|
||||
@@ -100,6 +105,10 @@ func removeDuplicateFlags(customFlags map[string]string, shortFlags map[string]s
|
||||
}
|
||||
}
|
||||
|
||||
// ParseCustomFlags parses flagsStr and returns a map where each flag is mapped to its complete form.
|
||||
// For flags with values (e.g., "--flag=value"), the key is the flag name and value is the complete flag.
|
||||
// For flags without values (e.g., "--flag"), both key and value are set to the flag name.
|
||||
// Duplicate flags (long/short variants) are removed based on longShortFlagsMap.
|
||||
func ParseCustomFlags(flagsStr string) map[string]string {
|
||||
flagsMap := make(map[string]string)
|
||||
parsedFlags := parseFlags(flagsStr)
|
||||
|
@@ -44,7 +44,7 @@ spec:
|
||||
description: Defines the build tool which is used for building the project.
|
||||
longDescription: |-
|
||||
Based on the build tool the step will try to auto build the project. The step will try to auto select
|
||||
the language and the build command. You can override the language and the build command by specifiying it seperatly.
|
||||
the language and the build command.
|
||||
mandatory: true
|
||||
scope:
|
||||
- GENERAL
|
||||
@@ -68,7 +68,7 @@ spec:
|
||||
- STEPS
|
||||
- name: language
|
||||
type: string
|
||||
description: "The programming language used to analyze."
|
||||
description: "The programming language used to analyze. Use coma separation and select custom build tool to analyze multiple languages"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
@@ -225,10 +225,10 @@ spec:
|
||||
type: string
|
||||
description: "A space-separated string of flags for the 'codeql database analyze' command."
|
||||
longDescription: |-
|
||||
A space-separated string of flags for the 'codeql database analyze' command.
|
||||
A space-separated string of flags for the 'codeql database analyze' command.
|
||||
|
||||
If both long and short forms of the same flag are provided, the long form takes precedence.
|
||||
Example input: "--threads=1 --ram=2000"
|
||||
If both long and short forms of the same flag are provided, the long form takes precedence.
|
||||
Example input: "--threads=1 --ram=2000"
|
||||
scope:
|
||||
- STEPS
|
||||
- STAGES
|
||||
|
Reference in New Issue
Block a user