Repository: turbot/steampipe-plugin-aws (language: go)

Issue:
Add table aws_lightsail_bucket
**References**
Terraform [link](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lightsail_bucket)
Go SDK [link](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/lightsail@v1.40.3/types#Bucket)



Discussion / hints:

Fix patch:
diff --git a/aws/plugin.go b/aws/plugin.go
index 6c69f1260..644275621 100644
--- a/aws/plugin.go
+++ b/aws/plugin.go
@@ -368,6 +368,7 @@ func Plugin(ctx context.Context) *plugin.Plugin {
 			"aws_lambda_layer":                                             tableAwsLambdaLayer(ctx),
 			"aws_lambda_layer_version":                                     tableAwsLambdaLayerVersion(ctx),
 			"aws_lambda_version":                                           tableAwsLambdaVersion(ctx),
+			"aws_lightsail_bucket":                                         tableAwsLightsailBucket(ctx),
 			"aws_lightsail_instance":                                       tableAwsLightsailInstance(ctx),
 			"aws_macie2_classification_job":                                tableAwsMacie2ClassificationJob(ctx),
 			"aws_media_store_container":                                    tableAwsMediaStoreContainer(ctx),
diff --git a/aws/table_aws_lightsail_bucket.go b/aws/table_aws_lightsail_bucket.go
new file mode 100644
index 000000000..369bf37de
--- /dev/null
+++ b/aws/table_aws_lightsail_bucket.go
@@ -0,0 +1,213 @@
+package aws
+
+import (
+	"context"
+
+	"github.com/aws/aws-sdk-go-v2/aws"
+	"github.com/aws/aws-sdk-go-v2/service/lightsail"
+	"github.com/aws/aws-sdk-go-v2/service/lightsail/types"
+
+	lightsailv1 "github.com/aws/aws-sdk-go/service/lightsail"
+
+	"github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto"
+	"github.com/turbot/steampipe-plugin-sdk/v5/plugin"
+	"github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform"
+)
+
+//// TABLE DEFINITION
+
+func tableAwsLightsailBucket(_ context.Context) *plugin.Table {
+	return &plugin.Table{
+		Name:        "aws_lightsail_bucket",
+		Description: "AWS Lightsail Bucket",
+		List: &plugin.ListConfig{
+			Hydrate: listLightsailBuckets,
+			KeyColumns: plugin.KeyColumnSlice{
+				{Name: "name", Require: plugin.Optional},
+			},
+			Tags: map[string]string{"service": "lightsail", "action": "GetBuckets"},
+		},
+		GetMatrixItemFunc: SupportedRegionMatrix(lightsailv1.EndpointsID),
+		Columns: awsRegionalColumns([]*plugin.Column{
+			{
+				Name:        "name",
+				Description: "The name of the bucket.",
+				Type:        proto.ColumnType_STRING,
+			},
+			{
+				Name:        "arn",
+				Description: "The Amazon Resource Name (ARN) of the bucket.",
+				Type:        proto.ColumnType_STRING,
+			},
+			{
+				Name:        "created_at",
+				Description: "The timestamp when the bucket was created.",
+				Type:        proto.ColumnType_TIMESTAMP,
+			},
+			{
+				Name:        "state_code",
+				Description: "The state code of the bucket.",
+				Type:        proto.ColumnType_STRING,
+				Transform:   transform.FromField("State.Code"),
+			},
+			{
+				Name:        "state_message",
+				Description: "A message that describes the state of the bucket.",
+				Type:        proto.ColumnType_STRING,
+				Transform:   transform.FromField("State.Message"),
+			},
+			{
+				Name:        "able_to_update_bundle",
+				Description: "Indicates whether the bundle that is currently applied to a bucket can be changed to another bundle. You can update a bucket's bundle only one time within a monthly Amazon Web Services billing cycle.",
+				Type:        proto.ColumnType_BOOL,
+			},
+			{
+				Name:        "bundle_id",
+				Description: "The ID of the bundle currently applied to the bucket. A bucket bundle specifies the monthly cost, storage space, and data transfer quota for a bucket.",
+				Type:        proto.ColumnType_STRING,
+			},
+			{
+				Name:        "object_versioning",
+				Description: "Indicates whether object versioning is enabled for the bucket. The following options can be configured: Enabled, Suspended, NeverEnabled.",
+				Type:        proto.ColumnType_STRING,
+			},
+			{
+				Name:        "resource_type",
+				Description: "The Lightsail resource type of the bucket.",
+				Type:        proto.ColumnType_STRING,
+			},
+			{
+				Name:        "support_code",
+				Description: "The support code for a bucket. Include this code in your email to support when you have questions about a Lightsail bucket.",
+				Type:        proto.ColumnType_STRING,
+			},
+			{
+				Name:        "url",
+				Description: "The URL of the bucket.",
+				Type:        proto.ColumnType_STRING,
+			},
+			{
+				Name:        "location",
+				Description: "An object that describes the location of the bucket, such as the Amazon Web Services Region and Availability Zone.",
+				Type:        proto.ColumnType_JSON,
+			},
+			{
+				Name:        "readonly_access_accounts",
+				Description: "An array of strings that specify the Amazon Web Services account IDs that have read-only access to the bucket.",
+				Type:        proto.ColumnType_JSON,
+			},
+			{
+				Name:        "access_log_config",
+				Description: "An object that describes the access log configuration for the bucket.",
+				Type:        proto.ColumnType_JSON,
+			},
+			{
+				Name:        "access_rules",
+				Description: "An object that describes the access rules of the bucket.",
+				Type:        proto.ColumnType_JSON,
+			},
+			{
+				Name:        "resources_receiving_access",
+				Description: "An array of objects that describe Lightsail instances that have access to the bucket.",
+				Type:        proto.ColumnType_JSON,
+			},
+			{
+				Name:        "tags_src",
+				Description: "A list of tags assigned to the instance.",
+				Type:        proto.ColumnType_JSON,
+				Transform:   transform.FromField("Tags"),
+			},
+
+			// Steampipe standard columns
+			{
+				Name:        "title",
+				Description: resourceInterfaceDescription("title"),
+				Type:        proto.ColumnType_STRING,
+				Transform:   transform.FromField("Name"),
+			},
+			{
+				Name:        "tags",
+				Description: resourceInterfaceDescription("tags"),
+				Type:        proto.ColumnType_JSON,
+				Transform:   transform.FromField("Tags").Transform(getLightsailBucketTurbotTags),
+			},
+			{
+				Name:        "akas",
+				Description: resourceInterfaceDescription("akas"),
+				Type:        proto.ColumnType_JSON,
+				Transform:   transform.FromField("Arn").Transform(transform.EnsureStringArray),
+			},
+		}),
+	}
+}
+
+//// LIST FUNCTION
+
+func listLightsailBuckets(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) {
+
+	// Create Session
+	svc, err := LightsailClient(ctx, d)
+	if err != nil {
+		plugin.Logger(ctx).Error("aws_lightsail_bucket.listLightsailBuckets", "connection_error", err)
+		return nil, err
+	}
+	if svc == nil {
+		// Unsupported region, return no data
+		return nil, nil
+	}
+
+	input := &lightsail.GetBucketsInput{
+		IncludeConnectedResources: aws.Bool(true),
+	}
+
+	if d.EqualsQualString("name") != "" {
+		input.BucketName = aws.String(d.EqualsQualString("name"))
+	}
+
+	// List call
+	for {
+		// apply rate limiting
+		d.WaitForListRateLimit(ctx)
+
+		resp, err := svc.GetBuckets(ctx, input)
+
+		if err != nil {
+			plugin.Logger(ctx).Error("aws_lightsail_bucket.listLightsailBuckets", "query_error", err)
+			return nil, nil
+		}
+
+		for _, item := range resp.Buckets {
+			d.StreamListItem(ctx, item)
+
+			// Context may get cancelled due to manual cancellation or if the limit has been reached
+			if d.RowsRemaining(ctx) == 0 {
+				return nil, nil
+			}
+		}
+
+		if resp.NextPageToken != nil {
+			input.PageToken = resp.NextPageToken
+		} else {
+			break
+		}
+	}
+
+	return nil, nil
+}
+
+//// TRANSFORM FUNCTIONS
+
+func getLightsailBucketTurbotTags(_ context.Context, d *transform.TransformData) (interface{}, error) {
+	tags := d.Value.([]types.Tag)
+	var turbotTagsMap map[string]string
+	if tags == nil {
+		return nil, nil
+	}
+
+	turbotTagsMap = map[string]string{}
+	for _, i := range tags {
+		turbotTagsMap[*i.Key] = *i.Value
+	}
+
+	return &turbotTagsMap, nil
+}
diff --git a/docs/tables/aws_lightsail_bucket.md b/docs/tables/aws_lightsail_bucket.md
new file mode 100644
index 000000000..4fdc979d9
--- /dev/null
+++ b/docs/tables/aws_lightsail_bucket.md
@@ -0,0 +1,177 @@
+---
+title: "Steampipe Table: aws_lightsail_bucket - Query AWS Lightsail Buckets using SQL"
+description: "Allows users to query AWS Lightsail Buckets and retrieve detailed information such as bucket configuration, access rules, tags, and more."
+---
+
+# Table: aws_lightsail_bucket - Query AWS Lightsail Buckets using SQL
+
+The AWS Lightsail Bucket is part of the Amazon Lightsail service, offering simple object storage solutions for small to medium-sized workloads. It provides an easy-to-use interface for storing and managing data, ideal for applications that require consistent storage performance, like web hosting or backups.
+
+## Table Usage Guide
+
+The `aws_lightsail_bucket` table in Steampipe provides detailed information about the buckets within AWS Lightsail. This table allows DevOps engineers, cloud architects, and developers to query various bucket-specific details, including configuration settings, access permissions, and associated tags. You can use this table to gather insights on buckets, such as those configured with specific access rules, buckets located in particular regions, or buckets with certain tags. The schema outlines various attributes of the Lightsail bucket for you, including the bucket name, creation timestamp, access rules, and more.
+
+## Examples
+
+### Basic info
+Get an overview of the buckets.
+
+```sql+postgres
+select
+  name,
+  arn,
+  state_code,
+  created_at
+from
+  aws_lightsail_bucket;
+```
+
+```sql+sqlite
+select
+  name,
+  arn,
+  state_code,
+  created_at
+from
+  aws_lightsail_bucket;
+```
+
+### Count of buckets by region
+Identify the distribution of your Lightsail buckets across different AWS regions to optimize data storage and retrieval.
+
+```sql+postgres
+select
+  region,
+  count(*) as bucket_count
+from
+  aws_lightsail_bucket
+group by
+  region;
+```
+
+```sql+sqlite
+select
+  region,
+  count(*) as bucket_count
+from
+  aws_lightsail_bucket
+group by
+  region;
+```
+
+### List buckets with public access
+Review your Lightsail buckets that have public access enabled to ensure they are appropriately secured.
+
+```sql+postgres
+select
+  name,
+  region,
+  access_rules ->> 'GetObject' as public_access
+from
+  aws_lightsail_bucket
+where
+  access_rules ->> 'GetObject' = 'public';
+```
+
+```sql+sqlite
+select
+  name,
+  region,
+  json_extract(access_rules, '$.GetObject') as public_access
+from
+  aws_lightsail_bucket
+where
+  json_extract(access_rules, '$.GetObject') = 'public';
+```
+
+### List buckets created within the last 30 days
+Monitor newly created Lightsail buckets to track changes in your storage environment.
+
+```sql+postgres
+select
+  name,
+  created_at
+from
+  aws_lightsail_bucket
+where
+  created_at >= (current_date - interval '30' day);
+```
+
+```sql+sqlite
+select
+  name,
+  created_at
+from
+  aws_lightsail_bucket
+where
+  created_at >= date('now','-30 day');
+```
+
+### Buckets without tags
+Identify Lightsail buckets that do not have any tags assigned to ensure that all resources are properly categorized.
+
+```sql+postgres
+select
+  name,
+  tags
+from
+  aws_lightsail_bucket
+where
+  tags is null or tags = '[]';
+```
+
+```sql+sqlite
+select
+  name,
+  tags
+from
+  aws_lightsail_bucket
+where
+  tags is null or tags = '[]';
+```
+
+### Details of buckets with versioning enabled
+Explore the configuration of Lightsail buckets that have object versioning enabled to manage data retention effectively.
+
+```sql+postgres
+select
+  name,
+  object_versioning
+from
+  aws_lightsail_bucket
+where
+  object_versioning = 'Enabled';
+```
+
+```sql+sqlite
+select
+  name,
+  object_versioning
+from
+  aws_lightsail_bucket
+where
+  object_versioning = 'Enabled';
+```
+
+### Get access log config details for the buckets
+Retrieve details about the access log configuration for each Lightsail bucket, including whether access logging is enabled, the destination for the logs, and any configured prefix.
+
+```sql+postgres
+select
+  name,
+  access_log_config ->> 'Enabled' as access_log_enabled,
+  access_log_config ->> 'Destination' as access_log_destination,
+  access_log_config ->> 'Prefix' as access_log_prefix
+from
+  aws_lightsail_bucket;
+```
+
+```sql+sqlite
+select
+  name,
+  json_extract(access_log_config, '$.Enabled') as access_log_enabled,
+  json_extract(access_log_config, '$.Destination') as access_log_destination,
+  json_extract(access_log_config, '$.Prefix') as access_log_prefix
+from
+  aws_lightsail_bucket;
+```


Test patch:
diff --git a/aws-test/tests/aws_lightsail_bucket/dependencies.txt b/aws-test/tests/aws_lightsail_bucket/dependencies.txt
new file mode 100644
index 000000000..e69de29bb
diff --git a/aws-test/tests/aws_lightsail_bucket/test-get-expected.json b/aws-test/tests/aws_lightsail_bucket/test-get-expected.json
new file mode 100644
index 000000000..19185961d
--- /dev/null
+++ b/aws-test/tests/aws_lightsail_bucket/test-get-expected.json
@@ -0,0 +1,7 @@
+[
+  {
+    "arn": "{{ output.resource_aka.value }}",
+    "name": "{{ output.resource_name.value }}",
+    "title": "{{ output.resource_name.value }}"
+  }
+]
diff --git a/aws-test/tests/aws_lightsail_bucket/test-get-query.sql b/aws-test/tests/aws_lightsail_bucket/test-get-query.sql
new file mode 100644
index 000000000..814a6f684
--- /dev/null
+++ b/aws-test/tests/aws_lightsail_bucket/test-get-query.sql
@@ -0,0 +1,3 @@
+select name, title, arn
+from aws_lightsail_bucket
+where name = '{{ resourceName }}'
diff --git a/aws-test/tests/aws_lightsail_bucket/test-get-tags-expected.json b/aws-test/tests/aws_lightsail_bucket/test-get-tags-expected.json
new file mode 100644
index 000000000..eff92022e
--- /dev/null
+++ b/aws-test/tests/aws_lightsail_bucket/test-get-tags-expected.json
@@ -0,0 +1,7 @@
+[
+  {
+    "tags": {
+      "Name": "{{resourceName}}"
+    }
+  }
+]
diff --git a/aws-test/tests/aws_lightsail_bucket/test-get-tags-query.sql b/aws-test/tests/aws_lightsail_bucket/test-get-tags-query.sql
new file mode 100644
index 000000000..b189d431f
--- /dev/null
+++ b/aws-test/tests/aws_lightsail_bucket/test-get-tags-query.sql
@@ -0,0 +1,3 @@
+select tags
+from aws_lightsail_bucket
+where name = '{{ output.resource_name.value }}'
diff --git a/aws-test/tests/aws_lightsail_bucket/test-list-expected.json b/aws-test/tests/aws_lightsail_bucket/test-list-expected.json
new file mode 100644
index 000000000..1b38b77cc
--- /dev/null
+++ b/aws-test/tests/aws_lightsail_bucket/test-list-expected.json
@@ -0,0 +1,10 @@
+[
+  {
+    "akas": [
+      "{{ output.resource_aka.value }}"
+    ],
+    "arn": "{{ output.resource_aka.value }}",
+    "name": "{{ output.resource_name.value }}",
+    "title": "{{  output.resource_name.value }}"
+  }
+]
diff --git a/aws-test/tests/aws_lightsail_bucket/test-list-query.sql b/aws-test/tests/aws_lightsail_bucket/test-list-query.sql
new file mode 100644
index 000000000..3ca0e57fd
--- /dev/null
+++ b/aws-test/tests/aws_lightsail_bucket/test-list-query.sql
@@ -0,0 +1,3 @@
+select name, arn, title, akas
+from aws_lightsail_bucket
+where akas::text = '["{{ output.resource_aka.value }}"]'
diff --git a/aws-test/tests/aws_lightsail_bucket/test-turbot-expected.json b/aws-test/tests/aws_lightsail_bucket/test-turbot-expected.json
new file mode 100644
index 000000000..c44f30d28
--- /dev/null
+++ b/aws-test/tests/aws_lightsail_bucket/test-turbot-expected.json
@@ -0,0 +1,10 @@
+[
+  {
+    "account_id": "{{output.account_id.value}}",
+    "akas": [
+      "{{ output.resource_aka.value }}"
+    ],
+    "region": "{{output.aws_region.value}}",
+    "title": "{{ output.resource_name.value }}"
+  }
+]
diff --git a/aws-test/tests/aws_lightsail_bucket/test-turbot-query.sql b/aws-test/tests/aws_lightsail_bucket/test-turbot-query.sql
new file mode 100644
index 000000000..0d0b59c45
--- /dev/null
+++ b/aws-test/tests/aws_lightsail_bucket/test-turbot-query.sql
@@ -0,0 +1,3 @@
+select title, akas, region, account_id
+from aws_lightsail_bucket
+where name = '{{ output.resource_name.value }}'
\ No newline at end of file
diff --git a/aws-test/tests/aws_lightsail_bucket/variables.json b/aws-test/tests/aws_lightsail_bucket/variables.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/aws-test/tests/aws_lightsail_bucket/variables.json
@@ -0,0 +1,1 @@
+{}
diff --git a/aws-test/tests/aws_lightsail_bucket/variables.tf b/aws-test/tests/aws_lightsail_bucket/variables.tf
new file mode 100644
index 000000000..12a633ac0
--- /dev/null
+++ b/aws-test/tests/aws_lightsail_bucket/variables.tf
@@ -0,0 +1,73 @@
+
+variable "resource_name" {
+  type        = string
+  default     = "turbot-test-20200125-create-update"
+  description = "Name of the resource used throughout the test."
+}
+
+variable "aws_profile" {
+  type        = string
+  default     = "default"
+  description = "AWS credentials profile used for the test. Default is to use the default profile."
+}
+
+variable "aws_region" {
+  type        = string
+  default     = "ap-south-1"
+  description = "AWS region used for the test. Does not work with default region in config, so must be defined here."
+}
+
+variable "aws_region_alternate" {
+  type        = string
+  default     = "us-east-2"
+  description = "Alternate AWS region used for tests that require two regions (e.g. DynamoDB global tables)."
+}
+
+provider "aws" {
+  profile = var.aws_profile
+  region  = var.aws_region
+}
+
+provider "aws" {
+  alias   = "alternate"
+  profile = var.aws_profile
+  region  = var.aws_region_alternate
+}
+
+data "aws_partition" "current" {}
+data "aws_caller_identity" "current" {}
+data "aws_region" "primary" {}
+data "aws_region" "alternate" {
+  provider = aws.alternate
+}
+
+data "null_data_source" "resource" {
+  inputs = {
+    scope = "arn:${data.aws_partition.current.partition}:::${data.aws_caller_identity.current.account_id}"
+  }
+}
+
+resource "aws_lightsail_bucket" "named_test_resource" {
+  name      = var.resource_name
+  bundle_id = "small_1_0"
+  tags = {
+    Name = var.resource_name
+  }
+}
+
+output "resource_aka" {
+  value = aws_lightsail_bucket.named_test_resource.arn
+}
+
+output "account_id" {
+  value = data.aws_caller_identity.current.account_id
+}
+
+output "aws_region" {
+  value = data.aws_region.primary.name
+}
+
+output "resource_name" {
+  value = var.resource_name
+}
+