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

Issue:
Add table aws_iot_*
Add new tables (plural!!)
- `aws_iot_core_things`
- `aws_iot_core_thing_groups`
- `aws_iot_core_thing_types`
- `aws_iot_core_fleet_metrics`
- `aws_iot_greengrass_devices`
- `aws_iot_greengrass_components`
- `aws_iot_greengrass_deployments`
- `aws_iot_greengrass_devices`
- `aws_iot_greengrass_groups`

**References**
[AWS IoT Core](https://aws.amazon.com/iot-core/?nc=sn&loc=2&dn=3)
[AWS IoT Greengrass](https://aws.amazon.com/greengrass/?nc=sn&loc=2&dn=2)


Discussion / hints:
Thank you, @jolo-dev, for bringing this issue to our attention and for your interest towards Steampipe. We will take a look into it.

Fix patch:
diff --git a/aws/plugin.go b/aws/plugin.go
index 3c54b2660..9712eed45 100644
--- a/aws/plugin.go
+++ b/aws/plugin.go
@@ -341,6 +341,7 @@ func Plugin(ctx context.Context) *plugin.Plugin {
 			"aws_inspector_exclusion":                                      tableAwsInspectorExclusion(ctx),
 			"aws_inspector_finding":                                        tableAwsInspectorFinding(ctx),
 			"aws_iot_thing":                                                tableAwsIoTThing(ctx),
+			"aws_iot_fleet_metric":                                         tableAwsIoTFleetMetric(ctx),
 			"aws_kinesis_consumer":                                         tableAwsKinesisConsumer(ctx),
 			"aws_kinesis_firehose_delivery_stream":                         tableAwsKinesisFirehoseDeliveryStream(ctx),
 			"aws_kinesis_stream":                                           tableAwsKinesisStream(ctx),
diff --git a/aws/table_aws_iot_fleet_metric.go b/aws/table_aws_iot_fleet_metric.go
new file mode 100644
index 000000000..5ac2c0763
--- /dev/null
+++ b/aws/table_aws_iot_fleet_metric.go
@@ -0,0 +1,299 @@
+package aws
+
+import (
+	"context"
+
+	"github.com/aws/aws-sdk-go-v2/aws"
+	"github.com/aws/aws-sdk-go-v2/service/iot"
+	"github.com/aws/aws-sdk-go-v2/service/iot/types"
+
+	iotv1 "github.com/aws/aws-sdk-go/service/iot"
+
+	"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 tableAwsIoTFleetMetric(_ context.Context) *plugin.Table {
+	return &plugin.Table{
+		Name:        "aws_iot_fleet_metric",
+		Description: "AWS IoT Fleet Metric",
+		Get: &plugin.GetConfig{
+			KeyColumns: plugin.SingleColumn("metric_name"),
+			Hydrate:    getIoTFleetMetric,
+			Tags:       map[string]string{"service": "iot", "action": "DescribeFleetMetric"},
+			IgnoreConfig: &plugin.IgnoreConfig{
+				ShouldIgnoreErrorFunc: shouldIgnoreErrors([]string{"ResourceNotFoundException"}),
+			},
+		},
+		List: &plugin.ListConfig{
+			Hydrate: listIoTFleetMetrics,
+			Tags:    map[string]string{"service": "iot", "action": "ListFleetMetrics"},
+		},
+		HydrateConfig: []plugin.HydrateConfig{
+			{
+				Func: getIoTFleetMetric,
+				Tags: map[string]string{"service": "iot", "action": "DescribeFleetMetric"},
+			},
+			{
+				Func: getIoTFleetMetricTags,
+				Tags: map[string]string{"service": "iot", "action": "ListTagsForResource"},
+			},
+		},
+		GetMatrixItemFunc: SupportedRegionMatrix(iotv1.EndpointsID),
+		Columns: awsRegionalColumns([]*plugin.Column{
+			{
+				Name:        "metric_name",
+				Description: "The name of the fleet metric to describe.",
+				Type:        proto.ColumnType_STRING,
+			},
+			{
+				Name:        "arn",
+				Description: "The ARN of the fleet metric to describe.",
+				Type:        proto.ColumnType_STRING,
+				Transform:   transform.FromField("MetricArn"),
+			},
+			{
+				Name:        "index_name",
+				Description: "The name of the index to search.",
+				Type:        proto.ColumnType_STRING,
+				Hydrate:     getIoTFleetMetric,
+			},
+			{
+				Name:        "description",
+				Description: "The fleet metric description.",
+				Type:        proto.ColumnType_STRING,
+				Hydrate:     getIoTFleetMetric,
+			},
+			{
+				Name:        "creation_date",
+				Description: "The date when the fleet metric is created.",
+				Type:        proto.ColumnType_TIMESTAMP,
+				Hydrate:     getIoTFleetMetric,
+				Transform:   transform.FromField("CreationDate"),
+			},
+			{
+				Name:        "last_modified_date",
+				Description: "The date when the fleet metric is last modified.",
+				Type:        proto.ColumnType_TIMESTAMP,
+				Hydrate:     getIoTFleetMetric,
+			},
+			{
+				Name:        "aggregation_field",
+				Description: "The field to aggregate.",
+				Type:        proto.ColumnType_STRING,
+				Hydrate:     getIoTFleetMetric,
+			},
+			{
+				Name:        "aggregation_type_name",
+				Description: "The name of the aggregation type.",
+				Type:        proto.ColumnType_STRING,
+				Hydrate:     getIoTFleetMetric,
+				Transform:   transform.FromField("AggregationType.Name"),
+			},
+			{
+				Name:        "period",
+				Description: "The time in seconds between fleet metric emissions. Range [60(1 min), 86400(1 day)] and must be multiple of 60.",
+				Type:        proto.ColumnType_INT,
+				Hydrate:     getIoTFleetMetric,
+			},
+			{
+				Name:        "query_string",
+				Description: "The search query string.",
+				Type:        proto.ColumnType_STRING,
+				Hydrate:     getIoTFleetMetric,
+			},
+			{
+				Name:        "query_version",
+				Description: "The search query version.",
+				Type:        proto.ColumnType_STRING,
+				Hydrate:     getIoTFleetMetric,
+			},
+			{
+				Name:        "unit",
+				Description: "Used to support unit transformation such as milliseconds to seconds. The unit must be supported by CW metric (https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html)",
+				Type:        proto.ColumnType_STRING,
+				Hydrate:     getIoTFleetMetric,
+			},
+			{
+				Name:        "version",
+				Description: "The version of the fleet metric.",
+				Type:        proto.ColumnType_INT,
+				Hydrate:     getIoTFleetMetric,
+			},
+			{
+				Name:        "aggregation_type_values",
+				Description: "A list of the values of aggregation types.",
+				Type:        proto.ColumnType_JSON,
+				Hydrate:     getIoTFleetMetric,
+				Transform:   transform.FromField("AggregationType.Values"),
+			},
+			{
+				Name:        "tags_src",
+				Description: "A list of tags currently associated with the thing type.",
+				Type:        proto.ColumnType_JSON,
+				Hydrate:     getIoTFleetMetricTags,
+				Transform:   transform.FromField("Tags"),
+			},
+
+			// Steampipe standard columns
+			{
+				Name:        "title",
+				Description: resourceInterfaceDescription("title"),
+				Type:        proto.ColumnType_STRING,
+				Transform:   transform.FromField("MetricName"),
+			},
+			{
+				Name:        "tags",
+				Description: resourceInterfaceDescription("tags"),
+				Type:        proto.ColumnType_JSON,
+				Hydrate:     getIoTFleetMetricTags,
+				Transform:   transform.From(iotFleetMetricTagListToTagsMap),
+			},
+			{
+				Name:        "akas",
+				Description: resourceInterfaceDescription("akas"),
+				Type:        proto.ColumnType_JSON,
+				Transform:   transform.FromField("MetricArn").Transform(transform.EnsureStringArray),
+			},
+		}),
+	}
+}
+
+//// LIST FUNCTION
+
+func listIoTFleetMetrics(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) {
+	// Create Session
+	svc, err := IoTClient(ctx, d)
+	if err != nil {
+		plugin.Logger(ctx).Error("aws_iot_fleet_metric.listIoTFleetMetrics", "connection_error", err)
+		return nil, err
+	}
+	if svc == nil {
+		// Unsupported region, return no data
+		return nil, nil
+	}
+
+	// Limiting the results
+	maxLimit := int32(250)
+	if d.QueryContext.Limit != nil {
+		limit := int32(*d.QueryContext.Limit)
+		if limit < maxLimit {
+			maxLimit = limit
+		}
+	}
+
+	input := &iot.ListFleetMetricsInput{
+		MaxResults: aws.Int32(maxLimit),
+	}
+
+	paginator := iot.NewListFleetMetricsPaginator(svc, input, func(o *iot.ListFleetMetricsPaginatorOptions) {
+		o.Limit = maxLimit
+		o.StopOnDuplicateToken = true
+	})
+
+	// List call
+	for paginator.HasMorePages() {
+		// apply rate limiting
+		d.WaitForListRateLimit(ctx)
+
+		output, err := paginator.NextPage(ctx)
+		if err != nil {
+			plugin.Logger(ctx).Error("aws_iot_fleet_metric.listIoTFleetMetrics", "api_error", err)
+			return nil, err
+		}
+
+		for _, item := range output.FleetMetrics {
+			d.StreamListItem(ctx, item)
+
+			// Context can be cancelled due to manual cancellation or the limit has been hit
+			if d.RowsRemaining(ctx) == 0 {
+				return nil, nil
+			}
+		}
+	}
+
+	return nil, err
+}
+
+//// HYDRATE FUNCTIONS
+
+func getIoTFleetMetric(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
+	metricName := ""
+	if h.Item != nil {
+		t := h.Item.(types.FleetMetricNameAndArn)
+		metricName = *t.MetricName
+	} else {
+		metricName = d.EqualsQualString("metric_name")
+	}
+
+	if metricName == "" {
+		return nil, nil
+	}
+
+	// Create service
+	svc, err := IoTClient(ctx, d)
+	if err != nil {
+		plugin.Logger(ctx).Error("aws_iot_fleet_metric.getIoTFleetMetric", "connection_error", err)
+		return nil, err
+	}
+
+	params := &iot.DescribeFleetMetricInput{
+		MetricName: aws.String(metricName),
+	}
+
+	resp, err := svc.DescribeFleetMetric(ctx, params)
+	if err != nil {
+		plugin.Logger(ctx).Error("aws_iot_fleet_metric.getIoTFleetMetric", "api_error", err)
+		return nil, err
+	}
+
+	return resp, nil
+}
+
+func getIoTFleetMetricTags(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
+	typeArn := ""
+	switch item := h.Item.(type) {
+	case *iot.DescribeThingTypeOutput:
+		typeArn = *item.ThingTypeArn
+	case types.ThingTypeDefinition:
+		typeArn = *item.ThingTypeArn
+	}
+
+	// Create service
+	svc, err := IoTClient(ctx, d)
+	if err != nil {
+		plugin.Logger(ctx).Error("aws_iot_fleet_metric.getIoTFleetMetricTags", "connection_error", err)
+		return nil, err
+	}
+
+	params := &iot.ListTagsForResourceInput{
+		ResourceArn: aws.String(typeArn),
+	}
+
+	endpointTags, err := svc.ListTagsForResource(ctx, params)
+	if err != nil {
+		plugin.Logger(ctx).Error("aws_iot_fleet_metric.getIoTFleetMetricTags", "api_error", err)
+		return nil, err
+	}
+
+	return endpointTags, nil
+}
+
+//// TRANSFORM FUNCTIONS
+
+func iotFleetMetricTagListToTagsMap(_ context.Context, d *transform.TransformData) (interface{}, error) {
+	data := d.HydrateItem.(*iot.ListTagsForResourceOutput)
+
+	// Mapping the resource tags inside turbotTags
+	if data.Tags != nil {
+		turbotTagsMap := map[string]string{}
+		for _, i := range data.Tags {
+			turbotTagsMap[*i.Key] = *i.Value
+		}
+		return turbotTagsMap, nil
+	}
+	return nil, nil
+}
diff --git a/docs/tables/aws_iot_fleet_metric.md b/docs/tables/aws_iot_fleet_metric.md
new file mode 100644
index 000000000..e6539c0c2
--- /dev/null
+++ b/docs/tables/aws_iot_fleet_metric.md
@@ -0,0 +1,99 @@
+---
+title: "Steampipe Table: aws_iot_fleet_metric - Query AWS IoT Fleet Metrics using SQL"
+description: "Allows users to query AWS IoT Fleet Metrics to gain insights into each fleet metric's configuration, including ARN, creation date, and aggregation information."
+---
+
+# Table: aws_iot_fleet_metric - Query AWS IoT Fleet Metrics using SQL
+
+AWS IoT Fleet Metrics, part of AWS IoT Device Management, allows for the monitoring and management of the status and performance of your IoT device fleet. It enables the definition of custom metrics based on device data reported to AWS IoT Core, such as device states or telemetry data. These metrics are instrumental in providing insights into various aspects of your IoT fleet, aiding in decisions related to device maintenance, operations, and performance optimization.
+
+## Table Usage Guide
+
+The `aws_iot_fleet_metric` table can be utilized to access detailed information about custom metrics defined for IoT device fleets. This table is essential for IoT administrators and analysts who need to oversee fleet performance, status, and operational metrics within AWS.
+
+## Examples
+
+### Basic info
+Retrieve fundamental details about AWS IoT Fleet Metrics, including metric names, associated ARNs, index names, and dates of creation and modification.
+
+```sql+postgres
+select
+  metric_name,
+  arn,
+  index_name,
+  creation_date,
+  last_modified_date
+from
+  aws_iot_fleet_metric;
+```
+
+```sql+sqlite
+select
+  metric_name,
+  arn,
+  index_name,
+  creation_date,
+  last_modified_date
+from
+  aws_iot_fleet_metric;
+```
+
+### Group fleet metrics by aggregation type name
+Group fleet metrics by their aggregation type name. This query is useful for analyzing metrics across different aggregation types, providing a broader view of fleet data categorization.
+
+```sql+postgres
+select
+  metric_name,
+  aggregation_field,
+  creation_date,
+  aggregation_type_name,
+  query_string
+from
+  aws_iot_fleet_metric
+group by
+  aggregation_type_name;
+```
+
+```sql+sqlite
+select
+  metric_name,
+  aggregation_field,
+  creation_date,
+  aggregation_type_name,
+  query_string
+from
+  aws_iot_fleet_metric
+group by
+  aggregation_type_name;
+```
+
+### List fleet metrics updated in the last 30 days
+Find fleet metrics that have been updated within the last 30 days. This query assists in identifying recent changes or updates in fleet metric configurations.
+
+```sql+postgres
+select
+  metric_name,
+  index_name,
+  creation_date,
+  last_modified_date,
+  query_version,
+  version
+from
+  aws_iot_fleet_metric
+where
+  last_modified_date >= now() - interval '30 days';
+```
+
+```sql+sqlite
+select
+  metric_name,
+  index_name,
+  creation_date,
+  last_modified_date,
+  query_version,
+  version
+from
+  aws_iot_fleet_metric
+where
+  datetime(last_modified_date) >= datetime('now', '-30 days');
+```


Test patch: