Skip to content

Commit

Permalink
Merge pull request #3462 from mercedes-benz/feature-3459-client-listJ…
Browse files Browse the repository at this point in the history
…obs-accepts-labels

Feature 3459 client list jobs accepts labels
  • Loading branch information
sven-dmlr authored Sep 27, 2024
2 parents 5586340 + 6b95e10 commit 2dfb09c
Show file tree
Hide file tree
Showing 15 changed files with 188 additions and 142 deletions.
2 changes: 1 addition & 1 deletion sechub-cli/src/mercedes-benz.com/sechub/cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func prepareOptionsFromCommandline(config *Config) {
fileOption, "", "Defines file to read from for actions '"+defineFalsePositivesAction+"', '"+markFalsePositivesAction+"', '"+interactiveMarkFalsePositivesAction+"', '"+unmarkFalsePositivesAction+"'")
flag.StringVar(&config.secHubJobUUID,
jobUUIDOption, "", "SecHub job uuid - Optional for actions '"+getStatusAction+"' or '"+getReportAction+"'")
flag.Func(labelOption, "Define a `SecHub label` for the scan job. (Example: \"key1=value1\") Repeat to define multiple labels.", func(s string) error {
flag.Func(labelOption, "Define a `SecHub label` for scan or filtering. (Example: \"key1=value1\") Repeat to define multiple labels.", func(s string) error {
var err error
config.labels, err = addLabelToList(config.labels, s, true)
if err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@ func InitializeContext() *Context {
printLogoWithVersion(context)

/* load configuration file - maybe there are some settings normally done by cli arguments too */
if context.config.action != showHelpAction {
if context.config.action != showHelpAction && context.config.action != showVersionAction {
loadConfigFile(context)
}

// Add labels defined via cmdline args or env var to config JSON (only for scan jobs)
if len(context.config.labels) > 0 && (context.config.action == scanAction || context.config.action == scanAsynchronAction) {
// Add labels defined via cmdline args or env var to config JSON (only for job related client actions)
if context.config.action == scanAction ||
context.config.action == scanAsynchronAction ||
context.config.action == listJobsAction ||
context.config.action == getReportAction ||
context.config.action == getStatusAction {

err := applyLabelsToConfigJson(context)
if err != nil {
sechubUtil.LogError("Error while processing labels: " + err.Error())
Expand Down
12 changes: 10 additions & 2 deletions sechub-cli/src/mercedes-benz.com/sechub/cli/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,12 @@ func printSecHubJobSummaryAndFailOnTrafficLight(context *Context) {
}

func getSecHubJobList(context *Context, size int) {
// request SecHub job state from server
// Print filtering labels if defined
for key, value := range context.config.labels {
sechubUtil.LogNotice("Filtered by label "+key+"="+value)
}

// Request SecHub job list from server
response := sendWithDefaultHeader("GET", buildGetSecHubJobListAPICall(context, size), context)

data, err := io.ReadAll(response.Body)
Expand Down Expand Up @@ -197,7 +202,9 @@ func getLatestSecHubJobUUID(context *Context, expectedState ...string) string {
getSecHubJobList(context, 5)

if len(context.jobList.List) == 0 {
sechubUtil.LogError("No SecHub jobs found. Have you started a scan?")
sechubUtil.LogWarning("No SecHub jobs found for "+context.config.projectID+". Have you started a scan?")
// Return 0 because we do not regard this as an error
os.Exit(ExitCodeOK)
}

// If not provided: accept any job state
Expand All @@ -213,6 +220,7 @@ func printLatestJobsOfProject(context *Context) {
// get latest jobs into context.jobList
getSecHubJobList(context, SizeOfJobList)

// Print result table
printFormat := "%-36s | %-6s | %-8s | %-6s | %-19s | %-19s\n"
fmt.Printf(printFormat, "SecHub JobUUID", "Status", "Stage", "Result", "Created", "Ended")
fmt.Println("-------------------------------------+--------+----------+--------+---------------------+--------------------")
Expand Down
62 changes: 30 additions & 32 deletions sechub-cli/src/mercedes-benz.com/sechub/cli/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,45 +38,43 @@ func addLabelToList(list map[string]string, labelDefinition string, overrideIfEx
// in the sechub config JSON (context.contentToSend) by context.config.labels
func applyLabelsToConfigJson(context *Context) error {
var err error
if len(context.config.labels) == 0 {
// Nothing to do when context.config.labels is empty
return err
}

// Unmarshal the JSON into a map structure
// because when using SecHubConfig struct then all new serverside json options have to be added to the client struct
var data map[string]interface{}
err = json.Unmarshal(context.contentToSend, &data)
if err != nil {
sechubUtil.LogError(fmt.Sprintf("Could not unmarshal json: %s", err))
return err
}
if context.config.configFileRead {
var data map[string]interface{}
// Unmarshal the JSON into a map structure
// because when using SecHubConfig struct then all new serverside json options have to be added to the client struct
err = json.Unmarshal(context.contentToSend, &data)
if err != nil {
sechubUtil.LogError(fmt.Sprintf("Could not unmarshal json: %s", err))
return err
}

metaData := data["metaData"]
m, ok := metaData.(map[string]interface{})
if ok {
labels := m["labels"]
var l map[string]interface{}
l, ok = labels.(map[string]interface{})
metaData := data["metaData"]
m, ok := metaData.(map[string]interface{})
if ok {
// Add labels from JSON to context.config.labels (no override)
for key, value := range l {
context.config.labels, err = addLabelToList(context.config.labels, fmt.Sprintf("%v=%v", key, value), false)
if err != nil {
return err
labels := m["labels"]
var l map[string]interface{}
l, ok = labels.(map[string]interface{})
if ok {
// Add labels from JSON to context.config.labels (no override)
for key, value := range l {
context.config.labels, err = addLabelToList(context.config.labels, fmt.Sprintf("%v=%v", key, value), false)
if err != nil {
return err
}
}
}
} else {
// initialize m
m = map[string]interface{}{}
}
} else {
// initialize m
m = map[string]interface{}{}
}

// Create/update labels in `data` map
m["labels"] = context.config.labels
data["metaData"] = m
// Create/update labels in `data` map
m["labels"] = context.config.labels
data["metaData"] = m

// Build JSON
context.contentToSend, err = json.Marshal(data)
// Build JSON
context.contentToSend, err = json.Marshal(data)
}
return err
}
64 changes: 64 additions & 0 deletions sechub-cli/src/mercedes-benz.com/sechub/cli/labels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func Example_applyLabelsToConfigJson_with_labels_section_in_json() {
var config Config
context.config = &config

context.config.configFileRead = true
sechubJSON := `
{
"apiVersion": "1.0",
Expand Down Expand Up @@ -145,12 +146,48 @@ func Example_applyLabelsToConfigJson_with_labels_section_in_json() {
// context.contentToSend: {"apiVersion":"1.0","metaData":{"labels":{"key1":"value1x","key2":"value2"}},"project":"myproject"}
}

func Example_applyLabelsToConfigJson_with_labels_section_only_in_json() {
// PREPARE
var context Context
var config Config
context.config = &config

context.config.configFileRead = true
sechubJSON := `
{
"apiVersion": "1.0",
"project": "myproject",
"metaData": {
"labels": {
"key1": "value1",
"key2": "value2"
}
}
}
`
context.contentToSend = []byte(sechubJSON)

// Lables are empty
labels := map[string]string{}
context.config.labels = labels

// EXECUTE
applyLabelsToConfigJson(&context)

// TEST
fmt.Printf("labels: %+v\n", context.config.labels)

// Output:
// labels: map[key1:value1 key2:value2]
}

func Example_applyLabelsToConfigJson_without_labels_section_in_json() {
// PREPARE
var context Context
var config Config
context.config = &config

context.config.configFileRead = true
sechubJSON := `
{
"apiVersion": "1.0",
Expand Down Expand Up @@ -186,6 +223,7 @@ func Example_applyLabelsToConfigJson_without_metadata_section_in_json() {
var config Config
context.config = &config

context.config.configFileRead = true
sechubJSON := `
{
"apiVersion": "1.0",
Expand All @@ -211,3 +249,29 @@ func Example_applyLabelsToConfigJson_without_metadata_section_in_json() {
// labels: map[key1:value1x]
// context.contentToSend: {"apiVersion":"1.0","metaData":{"labels":{"key1":"value1x"}},"project":"myproject"}
}

func Example_applyLabelsToConfigJson_with_no_config_file() {
// PREPARE
var context Context
var config Config
context.config = &config

context.config.configFileRead = false

// These lables must override the above defined ones
labels := map[string]string{
"key1": "value1",
}
context.config.labels = labels

// EXECUTE
applyLabelsToConfigJson(&context)

// TEST
fmt.Printf("labels: %+v\n", context.config.labels)
fmt.Println("context.contentToSend:", string(context.contentToSend))

// Output:
// labels: map[key1:value1]
// context.contentToSend:
}
12 changes: 12 additions & 0 deletions sechub-cli/src/mercedes-benz.com/sechub/cli/urlbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package cli

import (
"fmt"
"net/url"
)

// https://localhost:8443/api/project/testproject/job/
Expand Down Expand Up @@ -60,6 +61,17 @@ func buildGetSecHubJobListAPICall(context *Context, size int) string {
context.contentToSend = nil // Do not send content
context.inputForContentProcessing = nil
apiPart := fmt.Sprintf("project/%s/jobs?size=%d&page=0", context.config.projectID, size)

// Add filtering by labels if defined
metadata_set:=false
for key, value := range context.config.labels {
if ! metadata_set {
apiPart += "&withMetaData=true"
metadata_set = true
}
apiPart += "&metadata.labels."+key+"="+url.QueryEscape(value)
}

return buildAPIUrl(&context.config.server, &apiPart)
}

Expand Down
25 changes: 25 additions & 0 deletions sechub-cli/src/mercedes-benz.com/sechub/cli/urlbuilder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,28 @@ func Example_buildGetSecHubJobListAPICall() {
// Output:
// https://localhost:8443/api/project/testproject/jobs?size=10&page=0
}

func TestBuildGetSecHubJobListAPICallWithLabels(t *testing.T) {
/* prepare */
context := new(Context)
config := new(Config)

context.config = config
config.projectID = "testproject"
config.server = "https://localhost:8443"

labels := map[string]string{
"key1": "value1",
"key2": "Non alphnumeric character$ !",
}
context.config.labels = labels


/* execute */
result := buildGetSecHubJobListAPICall(context, 10)

/* test*/
sechubTestUtil.AssertStringContains(result, "&withMetaData=true", t)
sechubTestUtil.AssertStringContains(result, "&metadata.labels.key1=value1", t)
sechubTestUtil.AssertStringContains(result, "&metadata.labels.key2=Non+alphnumeric+character%24+%21", t)
}
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ sechub getStatus
----

TIP: Defaults to latest job. +
You can select the job by providing the wanted job UUID by adding `-jobUUID ${jobUUID}`
You can select the job by providing the wanted job UUID by adding `-jobUUID ${jobUUID}` +
This action respects defined <<client-configuration-overview,label definitions>> as filter criteria.

===== getReport
Will fetch the report as json. (result will only exist when job is done)
Expand All @@ -166,7 +167,8 @@ sechub getReport
----

TIP: Defaults to latest finished job. +
You can select the job by providing the wanted job UUID by adding `-jobUUID ${jobUUID}`
You can select the job by providing the wanted job UUID by adding `-jobUUID ${jobUUID}` +
This action respects defined <<client-configuration-overview,label definitions>> as filter criteria.

TIP: With `-output ${file-or-directory}` you can define where to place the report. +
Can be a directory, a file name or a file path. +
Expand All @@ -182,7 +184,8 @@ sechub listJobs

TIP: This might be useful for an overview or +
when you want to see your project's scan job queue or +
to find out the job UUID of a certain job.
to find out the job UUID of a certain job. +
This action respects defined <<client-configuration-overview,label definitions>> as filter criteria.


===== defineFalsePositives
Expand Down Expand Up @@ -296,6 +299,7 @@ include::sechub_client_falsepositive_list_example_unmark_jobData+projectData.jso
TIP: <<interactiveUnmarkFalsePositives,interactiveMarkFalsePositives>> might be easier to use


[[client-configuration-overview]]
==== Configuration overview
Basically parameters can be passed to the `{sechub}` client in three ways:

Expand Down Expand Up @@ -349,7 +353,7 @@ In below table, there is an overview of what can be defined where.
- `-jobUUID <string>` +
`{sechub}` job uuid. Optional for actions <<getStatus>> or <<getReport>>
- `-label <label definition>` +
Define a SecHub label for the scan job. (Example: "key1=value1") Repeat to define multiple labels.
Define a SecHub label for scan or filtering. (Example: "key1=value1") Repeat to define multiple labels.
- `-output <path to folder or file>` +
Where to place reports, false-positive files etc. Can be a directory, a file name or a file path (defaults to current working directory and default file names)
- `-project <string>` +
Expand Down Expand Up @@ -381,7 +385,7 @@ Additionally to above cmdline options, you can set some values also via environm
- `SECHUB_APITOKEN` +
The SecHub api token for `SECHUB_USERID`. (same as `-apitoken` option)
- `SECHUB_LABELS` +
Define one or more labels for the scan job. The labels will appear in your SecHub report. Separate multiple labels with ",". (see also `-label` option) +
Define one or more labels for scan or filtering. The labels will appear in your SecHub report. Separate multiple labels with ",". (see also `-label` option) +
Example: `export SECHUB_LABELS="key1=test 1,key2=test 2"`
- `SECHUB_PROJECT` +
The SecHub project id. (same as `-project` option)
Expand Down
Loading

0 comments on commit 2dfb09c

Please sign in to comment.