Skip to content

Commit a673558

Browse files
committed
fix(vulnerability): address priority API review feedback
1 parent ba9c7cf commit a673558

6 files changed

Lines changed: 145 additions & 32 deletions

File tree

internal/graph/gengql/root_.generated.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/graph/schema/issues.graphqls

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,13 +263,20 @@ type ExternalIngressCriticalVulnerabilityIssue implements Issue & Node {
263263

264264
"Raised when a workload with external ingresses has one or more urgent vulnerability-priority findings."
265265
type ExternalIngressUrgentVulnerabilityIssue implements Issue & Node {
266+
"The globally unique identifier for this issue."
266267
id: ID!
268+
"The team environment where the affected workload is deployed."
267269
teamEnvironment: TeamEnvironment!
270+
"The severity assigned to this issue."
268271
severity: Severity!
272+
"A human-readable description of the issue."
269273
message: String!
270274

275+
"The workload with urgent vulnerabilities and external ingresses."
271276
workload: Workload!
277+
"Number of urgent vulnerabilities on the workload."
272278
priorityUrgent: Int!
279+
"External ingress URLs that expose the workload."
273280
ingresses: [String!]!
274281
}
275282

internal/vulnerability/queries.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,25 @@ func normalizeFromDate(from time.Time) time.Time {
492492
return time.Date(from.Year(), from.Month(), from.Day(), 2, 0, 0, 0, time.UTC)
493493
}
494494

495+
func environmentNameFromFilter(filter *TeamVulnerabilitySummaryFilter) (*string, error) {
496+
if filter == nil {
497+
return nil, nil
498+
}
499+
if filter.EnvironmentName != nil && len(filter.Environments) > 0 {
500+
return nil, apierror.Errorf("environmentName and environments cannot be used together")
501+
}
502+
if filter.EnvironmentName != nil {
503+
return filter.EnvironmentName, nil
504+
}
505+
if len(filter.Environments) > 1 {
506+
return nil, apierror.Errorf("environments supports exactly one value")
507+
}
508+
if len(filter.Environments) == 1 {
509+
return &filter.Environments[0], nil
510+
}
511+
return nil, nil
512+
}
513+
495514
func getVulnerabilityHistory(ctx context.Context, opts []vulnerabilities.Option) (*ImageVulnerabilityHistory, error) {
496515
resp, err := fromContext(ctx).manager.Client.GetVulnerabilitySummaryTimeSeries(ctx, opts...)
497516
if err != nil {

internal/vulnerability/queries_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,40 @@ package vulnerability
22

33
import "testing"
44

5+
func TestEnvironmentNameFromFilter(t *testing.T) {
6+
dev := "dev"
7+
tests := []struct {
8+
name string
9+
filter *TeamVulnerabilitySummaryFilter
10+
want *string
11+
wantErr bool
12+
}{
13+
{name: "nil filter"},
14+
{name: "environment name", filter: &TeamVulnerabilitySummaryFilter{EnvironmentName: &dev}, want: &dev},
15+
{name: "deprecated single environment", filter: &TeamVulnerabilitySummaryFilter{Environments: []string{"dev"}}, want: &dev},
16+
{name: "multiple deprecated environments", filter: &TeamVulnerabilitySummaryFilter{Environments: []string{"dev", "prod"}}, wantErr: true},
17+
{name: "both environment filters", filter: &TeamVulnerabilitySummaryFilter{EnvironmentName: &dev, Environments: []string{"prod"}}, wantErr: true},
18+
}
19+
20+
for _, tt := range tests {
21+
t.Run(tt.name, func(t *testing.T) {
22+
got, err := environmentNameFromFilter(tt.filter)
23+
if (err != nil) != tt.wantErr {
24+
t.Fatalf("environmentNameFromFilter() error = %v, wantErr %v", err, tt.wantErr)
25+
}
26+
if got == nil || tt.want == nil {
27+
if got != tt.want {
28+
t.Fatalf("environmentNameFromFilter() = %v, want %v", got, tt.want)
29+
}
30+
return
31+
}
32+
if *got != *tt.want {
33+
t.Fatalf("environmentNameFromFilter() = %q, want %q", *got, *tt.want)
34+
}
35+
})
36+
}
37+
}
38+
539
func TestSplitImage(t *testing.T) {
640
tests := []struct {
741
name string

internal/vulnerability/transform.go

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -67,49 +67,52 @@ func toWorkloadVulnerabilitySummary(w *vulnerabilities.WorkloadSummary) *Workloa
6767
}
6868

6969
v13sSummary := w.GetVulnerabilitySummary()
70-
if v13sSummary == nil {
71-
v13sSummary = &vulnerabilities.Summary{}
70+
71+
return &WorkloadVulnerabilitySummary{
72+
Summary: toImageVulnerabilitySummary(v13sSummary),
73+
HasSbom: v13sSummary.GetHasSbom(),
74+
TeamSlug: slug.Slug(w.GetWorkload().GetNamespace()),
75+
EnvironmentName: environmentmapper.EnvironmentName(w.GetWorkload().GetCluster()),
76+
WorkloadReference: &workload.Reference{
77+
Name: w.GetWorkload().GetName(),
78+
Type: wType,
79+
},
80+
}
81+
}
82+
83+
func toImageVulnerabilitySummary(summary *vulnerabilities.Summary) *ImageVulnerabilitySummary {
84+
if summary == nil {
85+
summary = &vulnerabilities.Summary{}
7286
}
7387

7488
var lastUpdated *time.Time
75-
if ts := v13sSummary.GetLastUpdated(); ts != nil {
89+
if ts := summary.GetLastUpdated(); ts != nil {
7690
t := ts.AsTime()
7791
lastUpdated = &t
7892
}
7993

80-
summary := &ImageVulnerabilitySummary{
81-
Critical: int(v13sSummary.Critical),
82-
High: int(v13sSummary.High),
83-
Medium: int(v13sSummary.Medium),
84-
Low: int(v13sSummary.Low),
85-
Unassigned: int(v13sSummary.Unassigned),
86-
Total: int(v13sSummary.Total),
87-
RiskScore: int(v13sSummary.RiskScore),
94+
return &ImageVulnerabilitySummary{
95+
Critical: int(summary.GetCritical()),
96+
High: int(summary.GetHigh()),
97+
Medium: int(summary.GetMedium()),
98+
Low: int(summary.GetLow()),
99+
Unassigned: int(summary.GetUnassigned()),
100+
Total: int(summary.GetTotal()),
101+
RiskScore: int(summary.GetRiskScore()),
88102
LastUpdated: lastUpdated,
89-
StaleImageTag: v13sSummary.StaleImageTag,
103+
StaleImageTag: summary.StaleImageTag,
90104
CountsBySeverity: ImageVulnerabilitySummaryCountsBySeverity{
91-
Critical: int(v13sSummary.Critical),
92-
High: int(v13sSummary.High),
93-
Medium: int(v13sSummary.Medium),
94-
Low: int(v13sSummary.Low),
95-
Unassigned: int(v13sSummary.Unassigned),
105+
Critical: int(summary.GetCritical()),
106+
High: int(summary.GetHigh()),
107+
Medium: int(summary.GetMedium()),
108+
Low: int(summary.GetLow()),
109+
Unassigned: int(summary.GetUnassigned()),
96110
},
97111
CountsByPriority: ImageVulnerabilitySummaryCountsByPriority{
98-
Urgent: int(v13sSummary.ActNow),
99-
HighRisk: int(v13sSummary.HighRisk),
100-
ElevatedRisk: int(v13sSummary.ElevatedRisk),
101-
Monitor: int(v13sSummary.Monitor),
102-
},
103-
}
104-
105-
return &WorkloadVulnerabilitySummary{
106-
Summary: summary,
107-
HasSbom: v13sSummary.GetHasSbom(),
108-
TeamSlug: slug.Slug(w.GetWorkload().GetNamespace()),
109-
EnvironmentName: environmentmapper.EnvironmentName(w.GetWorkload().GetCluster()),
110-
WorkloadReference: &workload.Reference{
111-
Name: w.GetWorkload().GetName(),
112-
Type: wType,
112+
Urgent: int(summary.GetActNow()),
113+
HighRisk: int(summary.GetHighRisk()),
114+
ElevatedRisk: int(summary.GetElevatedRisk()),
115+
Monitor: int(summary.GetMonitor()),
113116
},
114117
}
115118
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package vulnerability
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/nais/v13s/pkg/api/vulnerabilities"
8+
"google.golang.org/protobuf/types/known/timestamppb"
9+
)
10+
11+
func TestToImageVulnerabilitySummary(t *testing.T) {
12+
lastUpdated := time.Date(2026, time.June, 30, 12, 0, 0, 0, time.UTC)
13+
staleImageTag := "previous"
14+
15+
got := toImageVulnerabilitySummary(&vulnerabilities.Summary{
16+
Critical: 1,
17+
High: 2,
18+
Medium: 3,
19+
Low: 4,
20+
Unassigned: 5,
21+
Total: 15,
22+
RiskScore: 42,
23+
ActNow: 6,
24+
HighRisk: 7,
25+
ElevatedRisk: 8,
26+
Monitor: 9,
27+
LastUpdated: timestamppb.New(lastUpdated),
28+
StaleImageTag: &staleImageTag,
29+
})
30+
31+
if got.CountsBySeverity != (ImageVulnerabilitySummaryCountsBySeverity{Critical: 1, High: 2, Medium: 3, Low: 4, Unassigned: 5}) {
32+
t.Fatalf("CountsBySeverity = %#v", got.CountsBySeverity)
33+
}
34+
if got.CountsByPriority != (ImageVulnerabilitySummaryCountsByPriority{Urgent: 6, HighRisk: 7, ElevatedRisk: 8, Monitor: 9}) {
35+
t.Fatalf("CountsByPriority = %#v", got.CountsByPriority)
36+
}
37+
if got.Total != 15 || got.RiskScore != 42 || got.StaleImageTag == nil || *got.StaleImageTag != "previous" {
38+
t.Fatalf("summary metadata = %#v", got)
39+
}
40+
if got.LastUpdated == nil || !got.LastUpdated.Equal(lastUpdated) {
41+
t.Fatalf("LastUpdated = %v, want %v", got.LastUpdated, lastUpdated)
42+
}
43+
}

0 commit comments

Comments
 (0)