Skip to content

Commit 94cff49

Browse files
authored
security: add commands for cspm scanning preview (#1806)
* security: add commands for cspm scanning preview Adds the commands for the CSPM scanning preview, and pulls in the latest godo changes to support that. * security: fix failing test cases * security: add integration test
1 parent fd4297e commit 94cff49

File tree

20 files changed

+998
-58
lines changed

20 files changed

+998
-58
lines changed

args.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,17 @@ const (
661661
// ArgAlertPolicySlackURLs are the Slack URLs to send alerts to.
662662
ArgAlertPolicySlackURLs = "slack-urls"
663663

664+
// Security Args
665+
666+
// ArgSecurityScanResources are the resources to scan.
667+
ArgSecurityScanResources = "resources"
668+
// ArgSecurityScanFindingSeverity filters findings by severity.
669+
ArgSecurityScanFindingSeverity = "severity"
670+
// ArgSecurityScanFindingType filters findings by type.
671+
ArgSecurityScanFindingType = "type"
672+
// ArgSecurityFindingUUID is the finding UUID for finding operations.
673+
ArgSecurityFindingUUID = "finding-uuid"
674+
664675
// ArgTokenValidationServer is the server used to validate an OAuth token
665676
ArgTokenValidationServer = "token-validation-server"
666677

commands/command_config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ type CmdConfig struct {
8686
GradientAI func() do.GradientAIService
8787
Nfs func() do.NfsService
8888
NfsActions func() do.NfsActionsService
89+
Security func() do.SecurityService
8990
}
9091

9192
// NewCmdConfig creates an instance of a CmdConfig.
@@ -152,6 +153,7 @@ func NewCmdConfig(ns string, dc doctl.Config, out io.Writer, args []string, init
152153
c.GradientAI = func() do.GradientAIService { return do.NewGradientAIService(godoClient) }
153154
c.Nfs = func() do.NfsService { return do.NewNfsService(godoClient) }
154155
c.NfsActions = func() do.NfsActionsService { return do.NewNfsActionsService(godoClient) }
156+
c.Security = func() do.SecurityService { return do.NewSecurityService(godoClient) }
155157
return nil
156158
},
157159

commands/commands_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ type tcMocks struct {
294294
gradientAI *domocks.MockGradientAIService
295295
nfs *domocks.MockNfsService
296296
nfsActions *domocks.MockNfsActionsService
297+
security *domocks.MockSecurityService
297298
}
298299

299300
func withTestClient(t *testing.T, tFn testFn) {
@@ -351,6 +352,7 @@ func withTestClient(t *testing.T, tFn testFn) {
351352
gradientAI: domocks.NewMockGradientAIService(ctrl),
352353
nfs: domocks.NewMockNfsService(ctrl),
353354
nfsActions: domocks.NewMockNfsActionsService(ctrl),
355+
security: domocks.NewMockSecurityService(ctrl),
354356
}
355357

356358
testConfig := doctl.NewTestConfig()
@@ -416,6 +418,7 @@ func withTestClient(t *testing.T, tFn testFn) {
416418
GradientAI: func() do.GradientAIService { return tm.gradientAI },
417419
Nfs: func() do.NfsService { return tm.nfs },
418420
NfsActions: func() do.NfsActionsService { return tm.nfsActions },
421+
Security: func() do.SecurityService { return tm.security },
419422
}
420423

421424
tFn(config, tm)

commands/displayers/security.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
Copyright 2026 The Doctl Authors All rights reserved.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package displayers
15+
16+
import (
17+
"io"
18+
19+
"github.com/digitalocean/doctl/do"
20+
)
21+
22+
// A SecurityScan is the displayer for showing the results of a single CSPM
23+
// scan.
24+
type SecurityScan struct {
25+
Scan do.Scan
26+
}
27+
28+
var _ Displayable = &SecurityScan{}
29+
30+
func (s *SecurityScan) JSON(out io.Writer) error {
31+
return writeJSON(s.Scan, out)
32+
}
33+
34+
func (s *SecurityScan) Cols() []string {
35+
return []string{"Rule ID", "Name", "Affected Resources", "Found At", "Severity"}
36+
}
37+
38+
func (s *SecurityScan) ColMap() map[string]string {
39+
return map[string]string{
40+
"Rule ID": "Rule ID",
41+
"Name": "Name",
42+
"Affected Resources": "Affected Resources",
43+
"Found At": "Found At",
44+
"Severity": "Severity",
45+
}
46+
}
47+
48+
func (s *SecurityScan) KV() []map[string]any {
49+
out := make([]map[string]any, 0, len(s.Scan.Findings))
50+
51+
for _, finding := range s.Scan.Findings {
52+
o := map[string]any{
53+
"Rule ID": finding.RuleUUID,
54+
"Name": finding.Name,
55+
"Affected Resources": finding.AffectedResourcesCount,
56+
"Found At": finding.FoundAt,
57+
"Severity": finding.Severity,
58+
}
59+
out = append(out, o)
60+
}
61+
62+
return out
63+
}
64+
65+
// SecurityScans is the displayer for showing the results of multiple CSPM
66+
// scans.
67+
type SecurityScans struct {
68+
Scans do.Scans
69+
}
70+
71+
var _ Displayable = &SecurityScans{}
72+
73+
func (s *SecurityScans) JSON(out io.Writer) error {
74+
return writeJSON(s.Scans, out)
75+
}
76+
77+
func (s *SecurityScans) Cols() []string {
78+
return []string{"ID", "Status", "Created At"}
79+
}
80+
81+
func (s *SecurityScans) ColMap() map[string]string {
82+
return map[string]string{
83+
"ID": "ID",
84+
"Status": "Status",
85+
"Created At": "Created At",
86+
}
87+
}
88+
89+
func (s *SecurityScans) KV() []map[string]any {
90+
out := make([]map[string]any, 0, len(s.Scans))
91+
92+
for _, scan := range s.Scans {
93+
o := map[string]any{
94+
"ID": scan.ID,
95+
"Status": scan.Status,
96+
"Created At": scan.CreatedAt,
97+
}
98+
out = append(out, o)
99+
}
100+
101+
return out
102+
}
103+
104+
type SecurityAffectedResource struct {
105+
AffectedResources do.AffectedResources
106+
}
107+
108+
var _ Displayable = &SecurityAffectedResource{}
109+
110+
func (s *SecurityAffectedResource) JSON(out io.Writer) error {
111+
return writeJSON(s.AffectedResources, out)
112+
}
113+
114+
func (s *SecurityAffectedResource) Cols() []string {
115+
return []string{"URN", "Name", "Type"}
116+
}
117+
118+
func (s *SecurityAffectedResource) ColMap() map[string]string {
119+
return map[string]string{
120+
"URN": "URN",
121+
"Name": "Name",
122+
"Type": "Type",
123+
}
124+
}
125+
126+
func (s *SecurityAffectedResource) KV() []map[string]any {
127+
out := make([]map[string]any, 0, len(s.AffectedResources))
128+
129+
for _, resource := range s.AffectedResources {
130+
o := map[string]any{
131+
"URN": resource.URN,
132+
"Name": resource.Name,
133+
"Type": resource.Type,
134+
}
135+
out = append(out, o)
136+
}
137+
138+
return out
139+
}

commands/doit.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ func addCommands() {
193193
DoitCmd.AddCommand(Spaces())
194194
DoitCmd.AddCommand(GradientAI())
195195
DoitCmd.AddCommand(Nfs())
196+
DoitCmd.AddCommand(Security())
196197
}
197198

198199
func computeCmd() *Command {

0 commit comments

Comments
 (0)