Skip to content
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

draft(scans): Implement Scans #132

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion internal/api/graphql/gqlgen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,8 @@ models:
supportGroups:
resolver: true
services:
resolver: true
resolver: true
Scan:
fields:
users:
resolver: true
1,195 changes: 1,034 additions & 161 deletions internal/api/graphql/graph/generated.go

Large diffs are not rendered by default.

82 changes: 82 additions & 0 deletions internal/api/graphql/graph/model/models_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions internal/api/graphql/graph/resolver/scan.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions internal/api/graphql/graph/schema/scan.graphqls
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors
# SPDX-License-Identifier: Apache-2.0

type Scan implements Node {
id: ID!
type: String
scope: String
users(filter: UserFilter, first: Int, after: String): UserConnection
}

input ScanInput {
type: String
scope: String
status: ScanStatusValues
}

input ScanFilter {
# TODO: Filter also by ID
status: [String]
}

enum ScanStatusValues {
inProgress
fail
success
}

type ScanConnection implements Connection {
totalCount: Int!
edges: [ScanEdge]
pageInfo: PageInfo
}

type ScanEdge implements Edge {
node: Scan!
cursor: String
}
5 changes: 5 additions & 0 deletions internal/app/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ type Heureka interface {
UpdateComponentVersion(*entity.ComponentVersion) (*entity.ComponentVersion, error)
DeleteComponentVersion(int64) error

ListScans(*entity.ScanFilter, *entity.ListOptions) (*entity.List[entity.ScanResult], error)
CreateScan(*entity.Scan) (*entity.Scan, error)
UpdateScan(*entity.Scan) (*entity.Scan, error)
DeleteScan(int64) error

GetSeverity(*entity.SeverityFilter) (*entity.Severity, error)
Shutdown() error
}
28 changes: 28 additions & 0 deletions internal/app/scan.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors
// SPDX-License-Identifier: Apache-2.0

package app

import (
"github.wdf.sap.corp/cc/heureka/internal/entity"
)

// TODO: Implement me
func (h *HeurekaApp) ListScans(filter *entity.ScanFilter, options *entity.ListOptions) (*entity.List[entity.ScanResult], error) {
return nil, nil
}

// TODO: Implement me
func (h *HeurekaApp) CreateScan(scan *entity.Scan) (*entity.Scan, error) {
return nil, nil
}

// TODO: Implement me
func (h *HeurekaApp) UpdateScan(component *entity.Scan) (*entity.Scan, error) {
return nil, nil
}

// TODO: Implement me
func (h *HeurekaApp) DeleteScan(id int64) error {
return nil
}
21 changes: 20 additions & 1 deletion internal/database/mariadb/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ type DatabaseRow interface {
ActivityHasIssueRow |
ActivityHasServiceRow |
IssueRepositoryServiceRow |
IssueMatchChangeRow
IssueMatchChangeRow |
ScanRow
}

type IssueRow struct {
Expand Down Expand Up @@ -732,3 +733,21 @@ type IssueRepositoryServiceRow struct {
DeletedAt sql.NullTime `db:"issuerepositoryservice_deleted_at" json:"deleted_at,omitempty"`
UpdatedAt sql.NullTime `db:"issuerepositoryservice_updated_at" json:"updated_at"`
}

type ScanRow struct {
ScanId sql.NullInt64 `db:"scan_id" json:"scan_id"`
ScanType sql.NullString `db:"scan_type" json:"scan_type"`
Scope sql.NullString `db:"scope" json:"scope"`
StartedAt sql.NullTime `db:"created_at" json:"created_at"`
FinishedAt sql.NullTime `db:"finished_at" json:"finished_at"`
}

func (sr *ScanRow) AsScan() entity.Scan {
return entity.Scan{
Id: GetInt64Value(sr.ScanId),
Type: entity.NewScanTypeValue(GetStringValue(sr.ScanType)),
Scope: GetStringValue(sr.Scope),
StartedAt: GetTimeValue(sr.StartedAt),
FinishedAt: GetTimeValue(sr.FinishedAt),
}
}
135 changes: 135 additions & 0 deletions internal/database/mariadb/scan.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors
// SPDX-License-Identifier: Apache-2.0

package mariadb

import (
"fmt"

"github.com/jmoiron/sqlx"
"github.com/sirupsen/logrus"
"github.wdf.sap.corp/cc/heureka/internal/entity"
)

// GetScans ...
func (s *SqlDatabase) GetScans(filter *entity.ScanFilter) ([]entity.Scan, error) {
l := logrus.WithFields(logrus.Fields{
"event": "database.GetComponents",
})

baseQuery := `
SELECT S.* FROM Scans S
%s
%s
%s GROUP BY C.scan_id ORDER BY C.scan_id LIMIT ?
`
filter = s.ensureScanFilter(filter)

stmt, filterParameters, err := s.buildScanStatement(baseQuery, filter, true, l)

if err != nil {
return nil, err
}

defer stmt.Close()

return performListScan(
stmt,
filterParameters,
l,
func(l []entity.Scan, e ScanRow) []entity.Scan {
return append(l, e.AsScan())
},
)

}

// ensureScanFilter ...
func (s *SqlDatabase) ensureScanFilter(f *entity.ScanFilter) *entity.ScanFilter {
var first = 1000
var after int64 = 0
if f == nil {
return &entity.ScanFilter{
Paginated: entity.Paginated{
First: &first,
After: &after,
},
Id: nil,
Scope: nil,
}
}
if f.After == nil {
f.After = &after
}
if f.First == nil {
f.First = &first
}
return f
}

// getScanFilterString ...
func (s *SqlDatabase) getScanFilterString(filter *entity.ScanFilter) string {
var fl []string
fl = append(fl, buildFilterQuery(filter.Id, "S.scan_id = ?", OP_OR))
fl = append(fl, "S.scan_finished_at IS NULL")

return combineFilterQueries(fl, OP_AND)
}

func (s *SqlDatabase) getScanJoins(filter *entity.ScanFilter) string {
joins := ""
if len(filter.Id) > 0 {
joins = fmt.Sprintf("%s\n%s", joins, "LEFT JOIN Users U on S.scan_id = U.user_id")
}
return joins
}

// buildScanStatement ...
func (s *SqlDatabase) buildScanStatement(baseQuery string, filter *entity.ScanFilter, withCursor bool, l *logrus.Entry) (*sqlx.Stmt, []interface{}, error) {
var query string
filter = s.ensureScanFilter(filter)
l.WithFields(logrus.Fields{"filter": filter})

filterStr := s.getScanFilterString(filter)
joins := s.getScanJoins(filter)
cursor := getCursor(filter.Paginated, filterStr, "S.scan_id > ?")

whereClause := ""
if filterStr != "" || withCursor {
whereClause = fmt.Sprintf("WHERE %s", filterStr)
}

// construct final query
if withCursor {
query = fmt.Sprintf(baseQuery, joins, whereClause, cursor.Statement)
} else {
query = fmt.Sprintf(baseQuery, joins, whereClause)
}

//construct prepared statement and if where clause does exist add parameters
var stmt *sqlx.Stmt
var err error

stmt, err = s.db.Preparex(query)
if err != nil {
msg := ERROR_MSG_PREPARED_STMT
l.WithFields(
logrus.Fields{
"error": err,
"query": query,
"stmt": stmt,
}).Error(msg)
return nil, nil, fmt.Errorf("%s", msg)
}

//adding parameters
var filterParameters []interface{}
filterParameters = buildQueryParameters(filterParameters, filter.Id)
if withCursor {
filterParameters = append(filterParameters, cursor.Value)
filterParameters = append(filterParameters, cursor.Limit)
}

return stmt, filterParameters, nil

}
Loading