Skip to content

Commit

Permalink
add stop sbom scanning API (#20200)
Browse files Browse the repository at this point in the history
* add stop sbom scanning API

1. [UI] support to stop sbom scanning #20200
2. add type for stop scanning api, make it able to support both vulnerability and sbom.
3. refactor the db query to support multiple extra attributes.

Signed-off-by: wang yan <[email protected]>
Signed-off-by: xuelichao <[email protected]>
Co-authored-by: xuelichao <[email protected]>
  • Loading branch information
wy65701436 and xuelichao authored Apr 9, 2024
1 parent be648ea commit 461a5fa
Show file tree
Hide file tree
Showing 46 changed files with 2,356 additions and 90 deletions.
14 changes: 14 additions & 0 deletions api/v2.0/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1206,6 +1206,12 @@ paths:
- $ref: '#/parameters/projectName'
- $ref: '#/parameters/repositoryName'
- $ref: '#/parameters/reference'
- name: scanType
in: body
required: true
schema:
$ref: '#/definitions/ScanType'
description: 'The scan type: Vulnerabilities, SBOM'
responses:
'202':
$ref: '#/responses/202'
Expand Down Expand Up @@ -9980,3 +9986,11 @@ definitions:
items:
type: string
description: Links of the vulnerability

ScanType:
type: object
properties:
scan_type:
type: string
description: 'The scan type for the scan request. Two options are currently supported, vulnerability and sbom'
enum: [ vulnerability, sbom ]
5 changes: 3 additions & 2 deletions src/controller/scan/base_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,15 +340,16 @@ func (bc *basicController) Scan(ctx context.Context, artifact *ar.Artifact, opti
}

// Stop scan job of a given artifact
func (bc *basicController) Stop(ctx context.Context, artifact *ar.Artifact) error {
func (bc *basicController) Stop(ctx context.Context, artifact *ar.Artifact, capType string) error {
if artifact == nil {
return errors.New("nil artifact to stop scan")
}
query := q.New(q.KeyWords{"extra_attrs.artifact.digest": artifact.Digest})
query := q.New(q.KeyWords{"vendor_type": job.ImageScanJobVendorType, "extra_attrs.artifact.digest": artifact.Digest, "extra_attrs.enabled_capabilities.type": capType})
executions, err := bc.execMgr.List(ctx, query)
if err != nil {
return err
}

if len(executions) == 0 {
message := fmt.Sprintf("no scan job for artifact digest=%v", artifact.Digest)
return errors.BadRequestError(nil).WithMessage(message)
Expand Down
8 changes: 4 additions & 4 deletions src/controller/scan/base_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ func (suite *ControllerTestSuite) TestScanControllerScan() {
func (suite *ControllerTestSuite) TestScanControllerStop() {
{
// artifact not provieded
suite.Require().Error(suite.c.Stop(context.TODO(), nil))
suite.Require().Error(suite.c.Stop(context.TODO(), nil, "vulnerability"))
}

{
Expand All @@ -393,7 +393,7 @@ func (suite *ControllerTestSuite) TestScanControllerStop() {

ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{})

suite.Require().NoError(suite.c.Stop(ctx, suite.artifact))
suite.Require().NoError(suite.c.Stop(ctx, suite.artifact, "vulnerability"))
}

{
Expand All @@ -403,7 +403,7 @@ func (suite *ControllerTestSuite) TestScanControllerStop() {

ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{})

suite.Require().Error(suite.c.Stop(ctx, suite.artifact))
suite.Require().Error(suite.c.Stop(ctx, suite.artifact, "vulnerability"))
}

{
Expand All @@ -412,7 +412,7 @@ func (suite *ControllerTestSuite) TestScanControllerStop() {

ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{})

suite.Require().Error(suite.c.Stop(ctx, suite.artifact))
suite.Require().Error(suite.c.Stop(ctx, suite.artifact, "vulnerability"))
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/controller/scan/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ type Controller interface {
// Arguments:
// ctx context.Context : the context for this method
// artifact *artifact.Artifact : the artifact whose scan job to be stopped
// capType string : the capability type of the scanner, vulnerability or SBOM.
//
// Returns:
// error : non nil error if any errors occurred
Stop(ctx context.Context, artifact *artifact.Artifact) error
Stop(ctx context.Context, artifact *artifact.Artifact, capType string) error

// GetReport gets the reports for the given artifact identified by the digest
//
Expand Down
118 changes: 77 additions & 41 deletions src/pkg/task/dao/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,12 @@ func (e *executionDAO) refreshStatus(ctx context.Context, id int64) (bool, strin
return status != execution.Status, status, false, err
}

type jsonbStru struct {
keyPrefix string
key string
value interface{}
}

func (e *executionDAO) querySetter(ctx context.Context, query *q.Query) (orm.QuerySeter, error) {
qs, err := orm.QuerySetter(ctx, &Execution{}, query)
if err != nil {
Expand All @@ -352,39 +358,32 @@ func (e *executionDAO) querySetter(ctx context.Context, query *q.Query) (orm.Que
// append the filter for "extra attrs"
if query != nil && len(query.Keywords) > 0 {
var (
key string
keyPrefix string
value interface{}
jsonbStrus []jsonbStru
args []interface{}
)
for key, value = range query.Keywords {
if strings.HasPrefix(key, "ExtraAttrs.") {
keyPrefix = "ExtraAttrs."
break

for key, value := range query.Keywords {
if strings.HasPrefix(key, "ExtraAttrs.") && key != "ExtraAttrs." {
jsonbStrus = append(jsonbStrus, jsonbStru{
keyPrefix: "ExtraAttrs.",
key: key,
value: value,
})
}
if strings.HasPrefix(key, "extra_attrs.") {
keyPrefix = "extra_attrs."
break
if strings.HasPrefix(key, "extra_attrs.") && key != "extra_attrs." {
jsonbStrus = append(jsonbStrus, jsonbStru{
keyPrefix: "extra_attrs.",
key: key,
value: value,
})
}
}
if len(keyPrefix) == 0 || keyPrefix == key {
if len(jsonbStrus) == 0 {
return qs, nil
}

// key with keyPrefix supports multi-level query operator on PostgreSQL JSON data
// examples:
// key = extra_attrs.id,
// ==> sql = "select id from execution where extra_attrs->>?=?", args = {id, value}
// key = extra_attrs.artifact.digest
// ==> sql = "select id from execution where extra_attrs->?->>?=?", args = {artifact, id, value}
// key = extra_attrs.a.b.c
// ==> sql = "select id from execution where extra_attrs->?->?->>?=?", args = {a, b, c, value}
keys := strings.Split(strings.TrimPrefix(key, keyPrefix), ".")
var args []interface{}
for _, item := range keys {
args = append(args, item)
}
args = append(args, value)
inClause, err := orm.CreateInClause(ctx, buildInClauseSQLForExtraAttrs(keys), args...)
idSQL, args := buildInClauseSQLForExtraAttrs(jsonbStrus)
inClause, err := orm.CreateInClause(ctx, idSQL, args...)
if err != nil {
return nil, err
}
Expand All @@ -395,23 +394,60 @@ func (e *executionDAO) querySetter(ctx context.Context, query *q.Query) (orm.Que
}

// Param keys is strings.Split() after trim "extra_attrs."/"ExtraAttrs." prefix
func buildInClauseSQLForExtraAttrs(keys []string) string {
switch len(keys) {
case 0:
// won't fall into this case, as the if condition on "keyPrefix == key"
// act as a place holder to ensure "default" is equivalent to "len(keys) >= 2"
return ""
case 1:
return "select id from execution where extra_attrs->>?=?"
default:
// len(keys) >= 2
elements := make([]string, len(keys)-1)
for i := range elements {
elements[i] = "?"
// key with keyPrefix supports multi-level query operator on PostgreSQL JSON data
// examples:
// key = extra_attrs.id,
//
// ==> sql = "select id from execution where extra_attrs->>?=?", args = {id, value}
//
// key = extra_attrs.artifact.digest
//
// ==> sql = "select id from execution where extra_attrs->?->>?=?", args = {artifact, id, value}
//
// key = extra_attrs.a.b.c
//
// ==> sql = "select id from execution where extra_attrs->?->?->>?=?", args = {a, b, c, value}
func buildInClauseSQLForExtraAttrs(jsonbStrus []jsonbStru) (string, []interface{}) {
if len(jsonbStrus) == 0 {
return "", nil
}

var cond string
var args []interface{}
sql := "select id from execution where"

for i, jsonbStr := range jsonbStrus {
if jsonbStr.key == "" || jsonbStr.value == "" {
return "", nil
}
keys := strings.Split(strings.TrimPrefix(jsonbStr.key, jsonbStr.keyPrefix), ".")
if len(keys) == 1 {
if i == 0 {
cond += "extra_attrs->>?=?"
} else {
cond += " and extra_attrs->>?=?"
}
}
if len(keys) >= 2 {
elements := make([]string, len(keys)-1)
for i := range elements {
elements[i] = "?"
}
s := strings.Join(elements, "->")
if i == 0 {
cond += fmt.Sprintf("extra_attrs->%s->>?=?", s)
} else {
cond += fmt.Sprintf(" and extra_attrs->%s->>?=?", s)
}
}

for _, item := range keys {
args = append(args, item)
}
s := strings.Join(elements, "->")
return fmt.Sprintf("select id from execution where extra_attrs->%s->>?=?", s)
args = append(args, jsonbStr.value)
}

return fmt.Sprintf("%s %s", sql, cond), args
}

func buildExecStatusOutdateKey(id int64, vendor string) string {
Expand Down
32 changes: 23 additions & 9 deletions src/pkg/task/dao/execution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,22 +395,36 @@ func TestExecutionDAOSuite(t *testing.T) {
}

func Test_buildInClauseSQLForExtraAttrs(t *testing.T) {
type args struct {
keys []string
}
tests := []struct {
name string
args args
args []jsonbStru
want string
}{
{"extra_attrs.", args{[]string{}}, ""},
{"extra_attrs.id", args{[]string{"id"}}, "select id from execution where extra_attrs->>?=?"},
{"extra_attrs.artifact.digest", args{[]string{"artifact", "digest"}}, "select id from execution where extra_attrs->?->>?=?"},
{"extra_attrs.a.b.c", args{[]string{"a", "b", "c"}}, "select id from execution where extra_attrs->?->?->>?=?"},
{"extra_attrs.", []jsonbStru{}, ""},
{"extra_attrs.", []jsonbStru{{}}, ""},
{"extra_attrs.id", []jsonbStru{{
keyPrefix: "extra_attrs.",
key: "extra_attrs.id",
value: "1",
}}, "select id from execution where extra_attrs->>?=?"},
{"extra_attrs.artifact.digest", []jsonbStru{{
keyPrefix: "extra_attrs.",
key: "extra_attrs.artifact.digest",
value: "sha256:1234",
}}, "select id from execution where extra_attrs->?->>?=?"},
{"extra_attrs.a.b.c", []jsonbStru{{
keyPrefix: "extra_attrs.",
key: "extra_attrs.a.b.c",
value: "test_value_1",
}, {
keyPrefix: "extra_attrs.",
key: "extra_attrs.d.e",
value: "test_value_2",
}}, "select id from execution where extra_attrs->?->?->>?=? and extra_attrs->?->>?=?"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := buildInClauseSQLForExtraAttrs(tt.args.keys); got != tt.want {
if got, _ := buildInClauseSQLForExtraAttrs(tt.args); got != tt.want {
t.Errorf("buildInClauseSQLForExtraAttrs() = %v, want %v", got, tt.want)
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ export enum ADDITIONS {
SUMMARY = 'readme.md',
VALUES = 'values.yaml',
DEPENDENCIES = 'dependencies',
SBOMS = 'sboms',
}
Loading

0 comments on commit 461a5fa

Please sign in to comment.