Skip to content

Commit c090577

Browse files
authored
feat: refactor EBS volumes and Snapshot and add tests (#732)
* feat: refactor EBS and Snapshot and add tests * feat: fix linter issues and variable naming
1 parent db6f9aa commit c090577

File tree

7 files changed

+480
-83
lines changed

7 files changed

+480
-83
lines changed

docs/resources/ec2-snapshot.md

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,36 @@ generated: true
1111
EC2Snapshot
1212
```
1313

14-
15-
16-
### DependsOn
17-
18-
!!! important - Experimental Feature
19-
This resource depends on a resource using the experimental feature. This means that the resource will
20-
only be deleted if all the resources of a particular type are deleted first or reach a terminal state.
21-
22-
- [EC2Image](./ec2-image.md)
14+
## Properties
15+
16+
17+
- `DataEncryptionKeyID`: The data encryption key identifier for the snapshot
18+
- `Description`: The description for the snapshot
19+
- `Encrypted`: Indicates whether the snapshot is encrypted
20+
- `KmsKeyID`: The Amazon Resource Name (ARN) of the AWS KMS key used for encryption
21+
- `OwnerAlias`: The AWS owner alias
22+
- `OwnerID`: The AWS account ID of the EBS snapshot owner
23+
- `Progress`: The progress of the snapshot as a percentage
24+
- `RestoreExpiryTime`: Only for archived snapshots that are temporarily restored
25+
- `SnapshotID`: The ID of the snapshot
26+
- `StartTime`: The time stamp when the snapshot was initiated
27+
- `State`: The snapshot state
28+
- `StateMessage`: Encrypted Amazon EBS snapshots are copied asynchronously
29+
- `StorageTier`: The storage tier in which the snapshot is stored
30+
- `VolumeID`: The ID of the volume that was used to create the snapshot
31+
- `VolumeSize`: The size of the volume in GiB
32+
- `tag:<key>:`: This resource has tags with property `Tags`. These are key/value pairs that are
33+
added as their own property with the prefix of `tag:` (e.g. [tag:example: "value"])
34+
35+
!!! note - Using Properties
36+
Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property
37+
names to write filters for what you want to **keep** and omit from the nuke process.
38+
39+
### String Property
40+
41+
The string representation of a resource is generally the value of the Name, ID or ARN field of the resource. Not all
42+
resources support properties. To write a filter against the string representation, simply omit the `property` field in
43+
the filter.
44+
45+
The string value is always what is used in the output of the log format when a resource is identified.
2346

docs/resources/ec2-volume.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,32 @@ generated: true
1111
EC2Volume
1212
```
1313

14+
## Properties
1415

1516

17+
- `AvailabilityZone`: The Availability Zone in which the volume was created
18+
- `CreateTime`: The time stamp when volume creation was initiated
19+
- `Encrypted`: Indicates whether the volume is encrypted
20+
- `Iops`: The number of I/O operations per second (IOPS)
21+
- `KmsKeyID`: The Amazon Resource Name (ARN) of the AWS KMS key used for encryption
22+
- `MultiAttachEnabled`: Indicates whether Amazon EBS Multi-Attach is enabled
23+
- `Size`: The size of the volume in GiB
24+
- `State`: The state of the volume (creating, available, in-use, deleting, deleted, error)
25+
- `Throughput`: The throughput that the volume supports in MiB/s
26+
- `VolumeID`: The ID of the EBS volume
27+
- `VolumeType`: The volume type (gp2, gp3, io1, io2, st1, sc1, standard)
28+
- `tag:<key>:`: This resource has tags with property `Tags`. These are key/value pairs that are
29+
added as their own property with the prefix of `tag:` (e.g. [tag:example: "value"])
30+
31+
!!! note - Using Properties
32+
Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property
33+
names to write filters for what you want to **keep** and omit from the nuke process.
34+
35+
### String Property
36+
37+
The string representation of a resource is generally the value of the Name, ID or ARN field of the resource. Not all
38+
resources support properties. To write a filter against the string representation, simply omit the `property` field in
39+
the filter.
40+
41+
The string value is always what is used in the output of the log format when a resource is identified.
42+

mkdocs.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,10 @@ nav:
505505
- Neptune Graph: resources/neptune-graph.md
506506
- Neptune Instance: resources/neptune-instance.md
507507
- Neptune Snapshot: resources/neptune-snapshot.md
508+
- Network Firewall Logging Configuration: resources/network-firewall-logging-configuration.md
509+
- Network Firewall Policy: resources/network-firewall-policy.md
510+
- Network Firewall Rule Group: resources/network-firewall-rule-group.md
511+
- Network Firewall: resources/network-firewall.md
508512
- Network Manager Connect Peer: resources/network-manager-connect-peer.md
509513
- Network Manager Core Network: resources/network-manager-core-network.md
510514
- Network Manager Global Network: resources/network-manager-global-network.md

resources/ec2-snapshots.go

Lines changed: 67 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@ package resources
22

33
import (
44
"context"
5-
6-
"fmt"
75
"time"
86

9-
"github.com/aws/aws-sdk-go/aws"
10-
"github.com/aws/aws-sdk-go/service/ec2"
7+
"github.com/aws/aws-sdk-go-v2/service/ec2"
8+
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
119

1210
"github.com/ekristen/libnuke/pkg/registry"
1311
"github.com/ekristen/libnuke/pkg/resource"
@@ -24,65 +22,92 @@ func init() {
2422
Scope: nuke.Account,
2523
Resource: &EC2Snapshot{},
2624
Lister: &EC2SnapshotLister{},
27-
DependsOn: []string{
28-
EC2ImageResource,
29-
},
3025
})
3126
}
3227

3328
type EC2SnapshotLister struct{}
3429

35-
func (l *EC2SnapshotLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) {
30+
func (l *EC2SnapshotLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) {
3631
opts := o.(*nuke.ListerOpts)
3732

38-
svc := ec2.New(opts.Session)
33+
svc := ec2.NewFromConfig(*opts.Config)
34+
3935
params := &ec2.DescribeSnapshotsInput{
40-
OwnerIds: []*string{
41-
aws.String("self"),
42-
},
43-
}
44-
resp, err := svc.DescribeSnapshots(params)
45-
if err != nil {
46-
return nil, err
36+
OwnerIds: []string{"self"},
4737
}
48-
4938
resources := make([]resource.Resource, 0)
50-
for _, out := range resp.Snapshots {
51-
resources = append(resources, &EC2Snapshot{
52-
svc: svc,
53-
id: *out.SnapshotId,
54-
startTime: out.StartTime,
55-
tags: out.Tags,
56-
})
39+
40+
for {
41+
resp, err := svc.DescribeSnapshots(ctx, params)
42+
if err != nil {
43+
return nil, err
44+
}
45+
46+
for i := range resp.Snapshots {
47+
snapshot := &resp.Snapshots[i]
48+
resources = append(resources, &EC2Snapshot{
49+
svc: svc,
50+
SnapshotID: snapshot.SnapshotId,
51+
Description: snapshot.Description,
52+
VolumeID: snapshot.VolumeId,
53+
VolumeSize: snapshot.VolumeSize,
54+
State: &snapshot.State,
55+
StateMessage: snapshot.StateMessage,
56+
StartTime: snapshot.StartTime,
57+
Progress: snapshot.Progress,
58+
OwnerID: snapshot.OwnerId,
59+
OwnerAlias: snapshot.OwnerAlias,
60+
Encrypted: snapshot.Encrypted,
61+
KmsKeyID: snapshot.KmsKeyId,
62+
DataEncryptionKeyID: snapshot.DataEncryptionKeyId,
63+
StorageTier: &snapshot.StorageTier,
64+
RestoreExpiryTime: snapshot.RestoreExpiryTime,
65+
Tags: &snapshot.Tags,
66+
})
67+
}
68+
69+
if resp.NextToken == nil {
70+
break
71+
}
72+
params.NextToken = resp.NextToken
5773
}
5874

5975
return resources, nil
6076
}
6177

6278
type EC2Snapshot struct {
63-
svc *ec2.EC2
64-
id string
65-
startTime *time.Time
66-
tags []*ec2.Tag
79+
svc *ec2.Client
80+
SnapshotID *string `description:"The ID of the snapshot"`
81+
Description *string `description:"The description for the snapshot"`
82+
VolumeID *string `description:"The ID of the volume that was used to create the snapshot"`
83+
VolumeSize *int32 `description:"The size of the volume in GiB"`
84+
State *ec2types.SnapshotState `description:"The snapshot state"`
85+
StateMessage *string `description:"Encrypted Amazon EBS snapshots are copied asynchronously"`
86+
StartTime *time.Time `description:"The time stamp when the snapshot was initiated"`
87+
Progress *string `description:"The progress of the snapshot as a percentage"`
88+
OwnerID *string `description:"The AWS account ID of the EBS snapshot owner"`
89+
OwnerAlias *string `description:"The AWS owner alias"`
90+
Encrypted *bool `description:"Indicates whether the snapshot is encrypted"`
91+
KmsKeyID *string `description:"The Amazon Resource Name (ARN) of the AWS KMS key used for encryption"`
92+
DataEncryptionKeyID *string `description:"The data encryption key identifier for the snapshot"`
93+
StorageTier *ec2types.StorageTier `description:"The storage tier in which the snapshot is stored"`
94+
RestoreExpiryTime *time.Time `description:"Only for archived snapshots that are temporarily restored"`
95+
Tags *[]ec2types.Tag `description:"The tags associated with the snapshot"`
6796
}
6897

69-
func (e *EC2Snapshot) Properties() types.Properties {
70-
properties := types.NewProperties()
71-
properties.Set("StartTime", e.startTime.Format(time.RFC3339))
72-
73-
for _, tagValue := range e.tags {
74-
properties.Set(fmt.Sprintf("tag:%v", *tagValue.Key), tagValue.Value)
98+
func (r *EC2Snapshot) Remove(ctx context.Context) error {
99+
params := &ec2.DeleteSnapshotInput{
100+
SnapshotId: r.SnapshotID,
75101
}
76-
return properties
77-
}
78102

79-
func (e *EC2Snapshot) Remove(_ context.Context) error {
80-
_, err := e.svc.DeleteSnapshot(&ec2.DeleteSnapshotInput{
81-
SnapshotId: &e.id,
82-
})
103+
_, err := r.svc.DeleteSnapshot(ctx, params)
83104
return err
84105
}
85106

86-
func (e *EC2Snapshot) String() string {
87-
return e.id
107+
func (r *EC2Snapshot) Properties() types.Properties {
108+
return types.NewPropertiesFromStruct(r)
109+
}
110+
111+
func (r *EC2Snapshot) String() string {
112+
return *r.SnapshotID
88113
}

resources/ec2-snapshots_test.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package resources
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/aws/aws-sdk-go-v2/aws"
8+
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
9+
"github.com/gotidy/ptr"
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func Test_EC2Snapshot_String(t *testing.T) {
14+
a := assert.New(t)
15+
16+
ec2Snapshot := EC2Snapshot{
17+
SnapshotID: ptr.String("snap-1234567890abcdef0"),
18+
}
19+
20+
a.Equal("snap-1234567890abcdef0", ec2Snapshot.String())
21+
}
22+
23+
func Test_EC2Snapshot_Properties(t *testing.T) {
24+
a := assert.New(t)
25+
26+
startTime := time.Now()
27+
restoreExpiryTime := time.Now().Add(24 * time.Hour)
28+
state := ec2types.SnapshotStateCompleted
29+
storageTier := ec2types.StorageTierStandard
30+
31+
ec2Snapshot := EC2Snapshot{
32+
SnapshotID: ptr.String("snap-1234567890abcdef0"),
33+
Description: ptr.String("My snapshot"),
34+
VolumeID: ptr.String("vol-1234567890abcdef0"),
35+
VolumeSize: ptr.Int32(100),
36+
State: &state,
37+
StateMessage: ptr.String("100% complete"),
38+
StartTime: &startTime,
39+
Progress: ptr.String("100%"),
40+
OwnerID: ptr.String("123456789012"),
41+
OwnerAlias: ptr.String("amazon"),
42+
Encrypted: ptr.Bool(true),
43+
KmsKeyID: ptr.String("arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"),
44+
DataEncryptionKeyID: ptr.String("12345678-1234-1234-1234-123456789012"),
45+
StorageTier: &storageTier,
46+
RestoreExpiryTime: &restoreExpiryTime,
47+
Tags: &[]ec2types.Tag{
48+
{
49+
Key: aws.String("Environment"),
50+
Value: aws.String("production"),
51+
},
52+
{
53+
Key: aws.String("Backup"),
54+
Value: aws.String("daily"),
55+
},
56+
},
57+
}
58+
59+
properties := ec2Snapshot.Properties()
60+
61+
a.Equal("snap-1234567890abcdef0", properties.Get("SnapshotID"))
62+
a.Equal("My snapshot", properties.Get("Description"))
63+
a.Equal("vol-1234567890abcdef0", properties.Get("VolumeID"))
64+
a.Equal("100", properties.Get("VolumeSize"))
65+
a.Equal("completed", properties.Get("State"))
66+
a.Equal("100% complete", properties.Get("StateMessage"))
67+
a.Equal("100%", properties.Get("Progress"))
68+
a.Equal("123456789012", properties.Get("OwnerID"))
69+
a.Equal("amazon", properties.Get("OwnerAlias"))
70+
a.Equal("true", properties.Get("Encrypted"))
71+
a.Equal("arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012", properties.Get("KmsKeyID"))
72+
a.Equal("12345678-1234-1234-1234-123456789012", properties.Get("DataEncryptionKeyID"))
73+
a.Equal("standard", properties.Get("StorageTier"))
74+
a.Equal("production", properties.Get("tag:Environment"))
75+
a.Equal("daily", properties.Get("tag:Backup"))
76+
}
77+
78+
func Test_EC2Snapshot_Properties_EmptyTags(t *testing.T) {
79+
a := assert.New(t)
80+
81+
startTime := time.Now()
82+
state := ec2types.SnapshotStatePending
83+
84+
ec2Snapshot := EC2Snapshot{
85+
SnapshotID: ptr.String("snap-1234567890abcdef0"),
86+
Description: ptr.String("Automated backup"),
87+
VolumeID: ptr.String("vol-1234567890abcdef0"),
88+
VolumeSize: ptr.Int32(50),
89+
State: &state,
90+
StartTime: &startTime,
91+
Progress: ptr.String("50%"),
92+
OwnerID: ptr.String("123456789012"),
93+
Encrypted: ptr.Bool(false),
94+
Tags: &[]ec2types.Tag{},
95+
}
96+
97+
properties := ec2Snapshot.Properties()
98+
99+
a.Equal("snap-1234567890abcdef0", properties.Get("SnapshotID"))
100+
a.Equal("Automated backup", properties.Get("Description"))
101+
a.Equal("vol-1234567890abcdef0", properties.Get("VolumeID"))
102+
a.Equal("50", properties.Get("VolumeSize"))
103+
a.Equal("pending", properties.Get("State"))
104+
a.Equal("50%", properties.Get("Progress"))
105+
a.Equal("123456789012", properties.Get("OwnerID"))
106+
a.Equal("false", properties.Get("Encrypted"))
107+
}
108+
109+
func Test_EC2Snapshot_Properties_SpecialCharactersInTags(t *testing.T) {
110+
a := assert.New(t)
111+
112+
startTime := time.Now()
113+
state := ec2types.SnapshotStateCompleted
114+
storageTier := ec2types.StorageTierArchive
115+
116+
ec2Snapshot := EC2Snapshot{
117+
SnapshotID: ptr.String("snap-1234567890abcdef0"),
118+
Description: ptr.String("Weekly backup"),
119+
VolumeID: ptr.String("vol-1234567890abcdef0"),
120+
VolumeSize: ptr.Int32(200),
121+
State: &state,
122+
StartTime: &startTime,
123+
Progress: ptr.String("100%"),
124+
OwnerID: ptr.String("123456789012"),
125+
Encrypted: ptr.Bool(true),
126+
StorageTier: &storageTier,
127+
Tags: &[]ec2types.Tag{
128+
{
129+
Key: aws.String("Environment:Stage"),
130+
Value: aws.String("prod/staging"),
131+
},
132+
{
133+
Key: aws.String("Backup-Schedule"),
134+
Value: aws.String("weekly/monthly"),
135+
},
136+
},
137+
}
138+
139+
properties := ec2Snapshot.Properties()
140+
141+
a.Equal("snap-1234567890abcdef0", properties.Get("SnapshotID"))
142+
a.Equal("Weekly backup", properties.Get("Description"))
143+
a.Equal("vol-1234567890abcdef0", properties.Get("VolumeID"))
144+
a.Equal("200", properties.Get("VolumeSize"))
145+
a.Equal("completed", properties.Get("State"))
146+
a.Equal("100%", properties.Get("Progress"))
147+
a.Equal("123456789012", properties.Get("OwnerID"))
148+
a.Equal("true", properties.Get("Encrypted"))
149+
a.Equal("archive", properties.Get("StorageTier"))
150+
a.Equal("prod/staging", properties.Get("tag:Environment:Stage"))
151+
a.Equal("weekly/monthly", properties.Get("tag:Backup-Schedule"))
152+
}

0 commit comments

Comments
 (0)