Skip to content

Commit bd48af0

Browse files
authored
Add table aws_s3_directory_bucket Closes #2614 (#2618)
1 parent 14f10a2 commit bd48af0

File tree

3 files changed

+605
-0
lines changed

3 files changed

+605
-0
lines changed

aws/plugin.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,7 @@ func Plugin(ctx context.Context) *plugin.Plugin {
605605
"aws_s3_account_settings": tableAwsS3AccountSettings(ctx),
606606
"aws_s3_bucket_intelligent_tiering_configuration": tableAwsS3BucketIntelligentTieringConfiguration(ctx),
607607
"aws_s3_bucket": tableAwsS3Bucket(ctx),
608+
"aws_s3_directory_bucket": tableAwsS3DirectoryBucket(ctx),
608609
"aws_s3_multi_region_access_point": tableAwsS3MultiRegionAccessPoint(ctx),
609610
"aws_s3_multipart_upload": tableAwsS3MultipartUpload(ctx),
610611
"aws_s3_object_version": tableAwsS3ObjectVersion(ctx),
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
package aws
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"strings"
8+
9+
"github.com/aws/aws-sdk-go-v2/service/s3"
10+
"github.com/aws/aws-sdk-go-v2/service/s3/types"
11+
"github.com/aws/smithy-go"
12+
"github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto"
13+
"github.com/turbot/steampipe-plugin-sdk/v5/plugin"
14+
"github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform"
15+
)
16+
17+
func tableAwsS3DirectoryBucket(_ context.Context) *plugin.Table {
18+
return &plugin.Table{
19+
Name: "aws_s3_directory_bucket",
20+
Description: "AWS S3 Directory Bucket",
21+
List: &plugin.ListConfig{
22+
Hydrate: listS3DirectoryBuckets,
23+
Tags: map[string]string{"service": "s3", "action": "ListDirectoryBuckets"},
24+
},
25+
GetMatrixItemFunc: SupportedRegionMatrix(AWS_S3_SERVICE_ID),
26+
HydrateConfig: []plugin.HydrateConfig{
27+
{
28+
Func: getS3DirectoryBucketPolicy,
29+
Tags: map[string]string{"service": "s3", "action": "GetBucketPolicy"},
30+
},
31+
{
32+
Func: getS3DirectoryBucketLifecycle,
33+
Tags: map[string]string{"service": "s3", "action": "GetBucketLifecycleConfiguration"},
34+
},
35+
{
36+
Func: getS3DirectoryBucketEncryption,
37+
Tags: map[string]string{"service": "s3", "action": "GetBucketEncryption"},
38+
},
39+
},
40+
Columns: awsRegionalColumns([]*plugin.Column{
41+
{
42+
Name: "name",
43+
Description: "The name of the directory bucket.",
44+
Type: proto.ColumnType_STRING,
45+
},
46+
{
47+
Name: "arn",
48+
Description: "The ARN of the directory bucket.",
49+
Type: proto.ColumnType_STRING,
50+
Hydrate: getS3DirectoryBucketArn,
51+
Transform: transform.FromValue(),
52+
},
53+
{
54+
Name: "creation_date",
55+
Description: "The date when the directory bucket was created.",
56+
Type: proto.ColumnType_TIMESTAMP,
57+
},
58+
{
59+
Name: "policy",
60+
Description: "The resource IAM access document for the directory bucket.",
61+
Type: proto.ColumnType_JSON,
62+
Hydrate: getS3DirectoryBucketPolicy,
63+
Transform: transform.FromField("Policy").Transform(transform.UnmarshalYAML),
64+
},
65+
{
66+
Name: "policy_std",
67+
Description: "Contains the policy in a canonical form for easier searching.",
68+
Type: proto.ColumnType_JSON,
69+
Hydrate: getS3DirectoryBucketPolicy,
70+
Transform: transform.FromField("Policy").Transform(policyToCanonical),
71+
},
72+
{
73+
Name: "lifecycle_rules",
74+
Description: "The lifecycle configuration information of the directory bucket.",
75+
Type: proto.ColumnType_JSON,
76+
Hydrate: getS3DirectoryBucketLifecycle,
77+
Transform: transform.FromField("Rules"),
78+
},
79+
{
80+
Name: "server_side_encryption_configuration",
81+
Description: "The default encryption configuration for the directory bucket.",
82+
Type: proto.ColumnType_JSON,
83+
Hydrate: getS3DirectoryBucketEncryption,
84+
Transform: transform.FromField("ServerSideEncryptionConfiguration.Rules"),
85+
},
86+
87+
// Steampipe standard columns
88+
{
89+
Name: "title",
90+
Description: resourceInterfaceDescription("title"),
91+
Type: proto.ColumnType_STRING,
92+
Transform: transform.FromField("Name"),
93+
},
94+
{
95+
Name: "akas",
96+
Description: resourceInterfaceDescription("akas"),
97+
Type: proto.ColumnType_JSON,
98+
Hydrate: getS3DirectoryBucketArn,
99+
Transform: transform.FromValue().Transform(transform.EnsureStringArray),
100+
},
101+
}),
102+
}
103+
}
104+
105+
//// LIST FUNCTION
106+
107+
func listS3DirectoryBuckets(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
108+
// Get the region from the matrix
109+
region := d.EqualsQualString(matrixKeyRegion)
110+
111+
// Create service
112+
svc, err := S3Client(ctx, d, region)
113+
if err != nil {
114+
plugin.Logger(ctx).Error("aws_s3_directory_bucket.listS3DirectoryBuckets", "get_client_error", err, "region", region)
115+
return nil, err
116+
}
117+
118+
input := &s3.ListDirectoryBucketsInput{}
119+
120+
// Paginate through results
121+
paginator := s3.NewListDirectoryBucketsPaginator(svc, input, func(o *s3.ListDirectoryBucketsPaginatorOptions) {
122+
o.Limit = 1000
123+
})
124+
125+
for paginator.HasMorePages() {
126+
// apply rate limiting
127+
d.WaitForListRateLimit(ctx)
128+
129+
output, err := paginator.NextPage(ctx)
130+
if err != nil {
131+
// For unsupported region we are encountering Error: aws: operation error S3: ListDirectoryBuckets, https response error StatusCode: 0, RequestID: , HostID: , request send failed, Get "https://s3express-control.ap-southeast-3.amazonaws.com/?max-directory-buckets=1000&x-id=ListDirectoryBuckets": lookup s3express-control.ap-southeast-3.amazonaws.com on 192.168.31.1:53: no such host
132+
if strings.Contains(err.Error(), "no such host") {
133+
return nil, nil
134+
}
135+
plugin.Logger(ctx).Error("aws_s3_directory_bucket.listS3DirectoryBuckets", "api_error", err, "region", region)
136+
return nil, err
137+
}
138+
139+
for _, bucket := range output.Buckets {
140+
d.StreamListItem(ctx, bucket)
141+
142+
// Context may get cancelled due to manual cancellation or if the limit has been reached
143+
if d.RowsRemaining(ctx) == 0 {
144+
return nil, nil
145+
}
146+
}
147+
}
148+
149+
return nil, nil
150+
}
151+
152+
func getS3DirectoryBucketPolicy(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
153+
bucket := h.Item.(types.Bucket)
154+
region := d.EqualsQualString(matrixKeyRegion)
155+
156+
// Create service
157+
svc, err := S3Client(ctx, d, region)
158+
if err != nil {
159+
plugin.Logger(ctx).Error("aws_s3_directory_bucket.getS3DirectoryBucketPolicy", "get_client_error", err, "region", region)
160+
return nil, err
161+
}
162+
163+
params := &s3.GetBucketPolicyInput{
164+
Bucket: bucket.Name,
165+
}
166+
167+
policy, err := svc.GetBucketPolicy(ctx, params)
168+
if err != nil {
169+
var ae smithy.APIError
170+
if errors.As(err, &ae) {
171+
if ae.ErrorCode() == "NoSuchBucketPolicy" {
172+
return nil, nil
173+
}
174+
}
175+
plugin.Logger(ctx).Error("aws_s3_directory_bucket.getS3DirectoryBucketPolicy", "api_error", err, "bucket", *bucket.Name)
176+
return nil, err
177+
}
178+
179+
return policy, nil
180+
}
181+
182+
func getS3DirectoryBucketLifecycle(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
183+
bucket := h.Item.(types.Bucket)
184+
region := d.EqualsQualString(matrixKeyRegion)
185+
186+
// Create service
187+
svc, err := S3Client(ctx, d, region)
188+
if err != nil {
189+
plugin.Logger(ctx).Error("aws_s3_directory_bucket.getS3DirectoryBucketLifecycle", "get_client_error", err, "region", region)
190+
return nil, err
191+
}
192+
193+
params := &s3.GetBucketLifecycleConfigurationInput{
194+
Bucket: bucket.Name,
195+
}
196+
197+
lifecycleConfiguration, err := svc.GetBucketLifecycleConfiguration(ctx, params)
198+
if err != nil {
199+
var ae smithy.APIError
200+
if errors.As(err, &ae) {
201+
if ae.ErrorCode() == "NoSuchLifecycleConfiguration" {
202+
return nil, nil
203+
}
204+
}
205+
plugin.Logger(ctx).Error("aws_s3_directory_bucket.getS3DirectoryBucketLifecycle", "api_error", err, "bucket", *bucket.Name)
206+
return nil, err
207+
}
208+
209+
return lifecycleConfiguration, nil
210+
}
211+
212+
func getS3DirectoryBucketEncryption(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
213+
bucket := h.Item.(types.Bucket)
214+
region := d.EqualsQualString(matrixKeyRegion)
215+
216+
// Create service
217+
svc, err := S3Client(ctx, d, region)
218+
if err != nil {
219+
plugin.Logger(ctx).Error("aws_s3_directory_bucket.getS3DirectoryBucketEncryption", "get_client_error", err, "region", region)
220+
return nil, err
221+
}
222+
223+
params := &s3.GetBucketEncryptionInput{
224+
Bucket: bucket.Name,
225+
}
226+
227+
encryptionConfiguration, err := svc.GetBucketEncryption(ctx, params)
228+
if err != nil {
229+
var ae smithy.APIError
230+
if errors.As(err, &ae) {
231+
if ae.ErrorCode() == "ServerSideEncryptionConfigurationNotFoundError" {
232+
return nil, nil
233+
}
234+
}
235+
plugin.Logger(ctx).Error("aws_s3_directory_bucket.getS3DirectoryBucketEncryption", "api_error", err, "bucket", *bucket.Name)
236+
return nil, err
237+
}
238+
239+
return encryptionConfiguration, nil
240+
}
241+
242+
func getS3DirectoryBucketArn(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
243+
bucket := h.Item.(types.Bucket)
244+
245+
region := d.EqualsQualString(matrixKeyRegion)
246+
247+
commonInfo, err := getCommonColumns(ctx, d, h)
248+
if err != nil {
249+
return nil, err
250+
}
251+
252+
commonData := commonInfo.(*awsCommonColumnData)
253+
254+
arn := fmt.Sprintf("arn:%s:s3express:%s:%s:bucket/%s", commonData.Partition, region, commonData.AccountId, *bucket.Name)
255+
256+
return arn, nil
257+
}

0 commit comments

Comments
 (0)