Skip to content

Commit 5892b5f

Browse files
committed
feat: add DB cluster snapshot support and region wide scan flag; bump external package.
1 parent 8dd58f1 commit 5892b5f

File tree

10 files changed

+359
-15
lines changed

10 files changed

+359
-15
lines changed

.golangci.yaml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
version: "2"
22
linters:
33
default: all
4+
disable:
5+
- dupl
6+
- funlen
47
settings:
58
cyclop:
69
max-complexity: 11
@@ -20,11 +23,15 @@ linters:
2023
exclusions:
2124
generated: disable
2225
rules:
26+
- path: gen/
27+
linters:
28+
- exhaustruct
29+
- revive
2330
- path: main.go
2431
linters:
2532
- funlen
2633
- lll
27-
- path: _string.go
34+
- path: (gen_.*|_string).go
2835
linters:
2936
- gochecknoglobals
3037
- path: _test.go

gen.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright 2025 variHQ OÜ
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
4+
package main
5+
6+
//go:generate go run ./gen/regions/main.go

gen/regions/main.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright 2025 variHQ OÜ
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
4+
package main
5+
6+
import (
7+
"context"
8+
_ "embed"
9+
"fmt"
10+
"log/slog"
11+
"os"
12+
"sort"
13+
"text/template"
14+
15+
"github.com/aws/aws-sdk-go-v2/aws"
16+
"github.com/aws/aws-sdk-go-v2/config"
17+
"github.com/aws/aws-sdk-go-v2/service/ec2"
18+
)
19+
20+
//go:embed regions.gotpl
21+
var templateContent string
22+
23+
type regionTemplate struct {
24+
Regions []string
25+
}
26+
27+
const useRegion = "eu-west-1"
28+
29+
func main() {
30+
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
31+
AddSource: false,
32+
Level: slog.LevelDebug,
33+
})))
34+
35+
ctx := context.Background()
36+
37+
cnf, errGetConfig := config.LoadDefaultConfig(ctx, config.WithRegion(useRegion))
38+
if errGetConfig != nil {
39+
slog.Error("failed to get AWS config", slog.String("error", errGetConfig.Error()))
40+
41+
return
42+
}
43+
44+
regions, errGetRegions := getActiveRegions(ctx, cnf)
45+
if errGetRegions != nil {
46+
slog.Error("failed to get active regions", slog.String("error", errGetRegions.Error()))
47+
48+
return
49+
}
50+
51+
slog.Info("found regions", slog.Int("count", len(regions)))
52+
53+
target, errCreateFile := os.Create("./gen_regions.go")
54+
if errCreateFile != nil {
55+
slog.Error("failed to create target file", slog.String("error", errCreateFile.Error()))
56+
57+
return
58+
}
59+
60+
defer func(target *os.File) {
61+
errCloseFile := target.Close()
62+
if errCloseFile != nil {
63+
slog.Error("failed to close target file", slog.String("error", errCloseFile.Error()))
64+
}
65+
}(target)
66+
67+
supportedRegionsTemplate := template.Must(template.New("").Parse(templateContent))
68+
69+
if errTemplate := supportedRegionsTemplate.Execute(target, regionTemplate{Regions: regions}); errTemplate != nil {
70+
slog.Error("failed to execute template", slog.String("error", errTemplate.Error()))
71+
72+
return
73+
}
74+
75+
slog.Warn("generated file with regions")
76+
}
77+
78+
// getActiveRegions return a list of active regions in the current AWS account.
79+
func getActiveRegions(ctx context.Context, cfg aws.Config) ([]string, error) {
80+
client := ec2.NewFromConfig(cfg)
81+
82+
regions, err := client.DescribeRegions(ctx, &ec2.DescribeRegionsInput{
83+
AllRegions: aws.Bool(false),
84+
})
85+
if err != nil {
86+
return nil, fmt.Errorf("failed to describe regions: %w", err)
87+
}
88+
89+
activeRegions := make([]string, 0, len(regions.Regions))
90+
91+
for _, region := range regions.Regions {
92+
slog.Debug("found region", slog.String("name", *region.RegionName))
93+
activeRegions = append(activeRegions, *region.RegionName)
94+
}
95+
96+
slog.Debug("found regions", slog.Int("count", len(activeRegions)))
97+
98+
sort.Strings(activeRegions)
99+
100+
return activeRegions, nil
101+
}

gen/regions/regions.gotpl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Package gen Code generated. DO NOT EDIT.
2+
package main
3+
4+
var supportedRegions = []string{
5+
{{- range .Regions }}
6+
{{ printf "%q" . }},
7+
{{- end }}
8+
}

gen_regions.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2025 variHQ OÜ
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
4+
// Package gen Code generated. DO NOT EDIT.
5+
package main
6+
7+
var supportedRegions = []string{
8+
"ap-northeast-1",
9+
"ap-northeast-2",
10+
"ap-northeast-3",
11+
"ap-south-1",
12+
"ap-southeast-1",
13+
"ap-southeast-2",
14+
"ca-central-1",
15+
"eu-central-1",
16+
"eu-north-1",
17+
"eu-west-1",
18+
"eu-west-2",
19+
"eu-west-3",
20+
"sa-east-1",
21+
"us-east-1",
22+
"us-east-2",
23+
"us-west-1",
24+
"us-west-2",
25+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/aws/aws-sdk-go-v2/service/rds v1.96.0
1010
github.com/aws/aws-sdk-go-v2/service/ssm v1.59.0
1111
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19
12-
golang.org/x/sync v0.14.0
12+
golang.org/x/sync v0.15.0
1313
)
1414

1515
require (

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/Xv
3030
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
3131
github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k=
3232
github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
33-
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
34-
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
33+
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
34+
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=

main.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ var version = "dev"
1616

1717
func main() {
1818
var (
19-
target = flag.String("target", "self", "target AWS account ID")
20-
listScanners = flag.Bool("list-scanners", false, "list available resource types")
21-
showVersion = flag.Bool("version", false, "show version")
22-
verbose = flag.Bool("verbose", false, "verbose log output")
23-
regionVars StringSlice
24-
scannersVars StringSlice
19+
target = flag.String("target", "self", "target AWS account ID")
20+
listScanners = flag.Bool("list-scanners", false, "list available resource types")
21+
showVersion = flag.Bool("version", false, "show version")
22+
verbose = flag.Bool("verbose", false, "verbose log output")
23+
scanAllRegions = flag.Bool("region-all", false, "scan all regions")
24+
regionVars StringSlice
25+
scannersVars StringSlice
2526
)
2627

2728
flag.Var(
@@ -58,6 +59,12 @@ func main() {
5859
return
5960
}
6061

62+
if *scanAllRegions {
63+
slog.Debug("scan all regions")
64+
65+
regionVars = supportedRegions
66+
}
67+
6168
ctx := context.TODO()
6269

6370
app, err := newApp(

scan_rds_snapshot.go

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ import (
1515
)
1616

1717
var (
18-
_ rds.DescribeDBSnapshotsAPIClient = (rdsSnapshotClient)(nil)
19-
_ runner = (*rdsSnapshotScan)(nil)
18+
_ rds.DescribeDBClusterSnapshotsAPIClient = (rdsSnapshotClient)(nil)
19+
_ rds.DescribeDBSnapshotsAPIClient = (rdsSnapshotClient)(nil)
20+
_ runner = (*rdsSnapshotScan)(nil)
2021
)
2122

2223
type rdsSnapshotClient interface {
24+
rds.DescribeDBClusterSnapshotsAPIClient
2325
rds.DescribeDBSnapshotsAPIClient
2426
}
2527

@@ -46,6 +48,22 @@ func (r *rdsSnapshotScan) scan(ctx context.Context, target string) ([]Result, er
4648
return nil, fmt.Errorf("%w: target account ID is required", errEmptyTarget)
4749
}
4850

51+
outputSnapShoots, err := r.scanSnapshots(ctx, target)
52+
if err != nil {
53+
return nil, err
54+
}
55+
56+
outputClusterSnapShoots, err := r.scanClusterSnapshots(ctx, target)
57+
if err != nil {
58+
return nil, err
59+
}
60+
61+
outputSnapShoots = append(outputSnapShoots, outputClusterSnapShoots...)
62+
63+
return outputSnapShoots, nil
64+
}
65+
66+
func (r *rdsSnapshotScan) scanSnapshots(ctx context.Context, target string) ([]Result, error) {
4967
slog.Debug(
5068
"starting RDS snapshot scan",
5169
slog.String("region", r.region),
@@ -102,3 +120,67 @@ func (r *rdsSnapshotScan) scan(ctx context.Context, target string) ([]Result, er
102120

103121
return output, nil
104122
}
123+
124+
func (r *rdsSnapshotScan) scanClusterSnapshots(
125+
ctx context.Context,
126+
target string,
127+
) ([]Result, error) {
128+
slog.Debug(
129+
"starting RDS cluster snapshot scan",
130+
slog.String("region", r.region),
131+
slog.String("target", target),
132+
)
133+
134+
var output []Result
135+
136+
paginator := rds.NewDescribeDBClusterSnapshotsPaginator(
137+
r.client,
138+
&rds.DescribeDBClusterSnapshotsInput{
139+
DBClusterIdentifier: nil,
140+
DBClusterSnapshotIdentifier: nil,
141+
DbClusterResourceId: nil,
142+
Filters: nil,
143+
IncludePublic: aws.Bool(true),
144+
IncludeShared: aws.Bool(true),
145+
Marker: nil,
146+
MaxRecords: nil,
147+
SnapshotType: nil,
148+
},
149+
)
150+
for paginator.HasMorePages() {
151+
if ctx.Err() != nil {
152+
return nil, fmt.Errorf("%w: %w", errCtxCancelled, ctx.Err())
153+
}
154+
155+
page, err := paginator.NextPage(ctx)
156+
if err != nil {
157+
return nil, fmt.Errorf("failed to fetch RDS cluster snapshots, %w", err)
158+
}
159+
160+
for _, snapshot := range page.DBClusterSnapshots {
161+
if !strings.Contains(*snapshot.DBClusterSnapshotIdentifier, target) {
162+
slog.Debug("skipping RDS cluster snapshots",
163+
slog.String("name", *snapshot.DBClusterSnapshotIdentifier),
164+
slog.String("region", r.region),
165+
)
166+
167+
continue
168+
}
169+
170+
output = append(output, Result{
171+
CreationDate: snapshot.SnapshotCreateTime.Format(time.RFC3339),
172+
Identifier: *snapshot.DBClusterSnapshotIdentifier,
173+
Region: r.region,
174+
RType: r.runType(),
175+
})
176+
}
177+
}
178+
179+
slog.Debug("finished RDS cluster snapshot scan",
180+
slog.Int("count", len(output)),
181+
slog.String("region", r.region),
182+
slog.String("target", target),
183+
)
184+
185+
return output, nil
186+
}

0 commit comments

Comments
 (0)