2017-09-25 17:16:40 +08:00
package cloudwatch
import (
2023-05-24 16:19:34 +08:00
"context"
2017-09-25 17:16:40 +08:00
"errors"
2022-06-03 15:24:24 +08:00
"fmt"
2022-07-09 03:39:53 +08:00
"strconv"
2017-09-25 17:16:40 +08:00
"time"
2025-09-19 18:46:40 +08:00
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloudwatch"
cloudwatchtypes "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types"
2021-03-23 23:32:12 +08:00
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
2023-05-26 16:16:01 +08:00
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/kinds/dataquery"
2017-09-25 17:16:40 +08:00
)
2022-04-27 18:41:48 +08:00
type annotationEvent struct {
Title string
Time time . Time
Tags string
Text string
}
2025-09-19 18:46:40 +08:00
func ( ds * DataSource ) executeAnnotationQuery ( ctx context . Context , model DataQueryJson , query backend . DataQuery ) ( * backend . QueryDataResponse , error ) {
2021-03-23 23:32:12 +08:00
result := backend . NewQueryDataResponse ( )
2022-07-09 03:39:53 +08:00
statistic := ""
2021-03-23 23:32:12 +08:00
2022-07-09 03:39:53 +08:00
if model . Statistic != nil {
statistic = * model . Statistic
}
2025-09-19 18:46:40 +08:00
var period int32
2023-04-19 02:56:00 +08:00
if model . Period != nil && * model . Period != "" {
2025-09-19 18:46:40 +08:00
p , err := strconv . ParseInt ( * model . Period , 10 , 32 )
2022-07-09 03:39:53 +08:00
if err != nil {
2024-12-12 17:12:03 +08:00
return nil , backend . DownstreamError ( fmt . Errorf ( "query period must be an int" ) )
2022-07-09 03:39:53 +08:00
}
2025-09-19 18:46:40 +08:00
period = int32 ( p )
2022-07-09 03:39:53 +08:00
}
2023-04-19 02:56:00 +08:00
prefixMatching := false
if model . PrefixMatching != nil {
prefixMatching = * model . PrefixMatching
}
if period == 0 && ! prefixMatching {
2017-09-26 14:45:52 +08:00
period = 300
2017-09-25 17:16:40 +08:00
}
2022-07-09 03:39:53 +08:00
actionPrefix := model . ActionPrefix
alarmNamePrefix := model . AlarmNamePrefix
2025-09-19 18:46:40 +08:00
cli , err := ds . getCWClient ( ctx , model . Region )
2017-09-25 17:16:40 +08:00
if err != nil {
2024-12-12 18:37:11 +08:00
result . Responses [ query . RefID ] = backend . ErrorResponseWithErrorSource ( fmt . Errorf ( "%v: %w" , "failed to get client" , err ) )
2024-12-04 04:02:52 +08:00
return result , nil
2017-09-25 17:16:40 +08:00
}
var alarmNames [ ] * string
2023-04-19 02:56:00 +08:00
metricName := ""
if model . MetricName != nil {
metricName = * model . MetricName
}
2023-05-26 16:16:01 +08:00
dimensions := dataquery . Dimensions { }
if model . Dimensions != nil {
dimensions = * model . Dimensions
}
2023-04-19 02:56:00 +08:00
if prefixMatching {
2017-09-25 17:16:40 +08:00
params := & cloudwatch . DescribeAlarmsInput {
2025-09-19 18:46:40 +08:00
MaxRecords : aws . Int32 ( 100 ) ,
2023-04-19 02:56:00 +08:00
ActionPrefix : actionPrefix ,
AlarmNamePrefix : alarmNamePrefix ,
2017-09-25 17:16:40 +08:00
}
2025-09-19 18:46:40 +08:00
resp , err := cli . DescribeAlarms ( ctx , params )
2017-09-25 17:16:40 +08:00
if err != nil {
2024-12-12 17:12:03 +08:00
result . Responses [ query . RefID ] = backend . ErrorResponseWithErrorSource ( backend . DownstreamError ( fmt . Errorf ( "%v: %w" , "failed to call cloudwatch:DescribeAlarms" , err ) ) )
2024-07-30 21:55:01 +08:00
return result , nil
2017-09-25 17:16:40 +08:00
}
2025-01-14 22:58:38 +08:00
alarmNames = filterAlarms ( resp , model . Namespace , metricName , dimensions , statistic , period )
2017-09-25 17:16:40 +08:00
} else {
2025-01-14 22:58:38 +08:00
if model . Region == "" || model . Namespace == "" || metricName == "" || statistic == "" {
2024-12-12 17:12:03 +08:00
return result , backend . DownstreamError ( errors . New ( "invalid annotations query" ) )
2017-09-25 17:16:40 +08:00
}
2025-09-19 18:46:40 +08:00
var qd [ ] cloudwatchtypes . Dimension
2023-05-26 16:16:01 +08:00
for k , v := range dimensions {
2025-01-14 22:58:38 +08:00
for _ , vvv := range v . ArrayOfString {
2025-09-19 18:46:40 +08:00
qd = append ( qd , cloudwatchtypes . Dimension {
2025-01-14 22:58:38 +08:00
Name : aws . String ( k ) ,
Value : aws . String ( vvv ) ,
} )
2017-09-25 17:16:40 +08:00
}
}
2021-09-08 22:06:43 +08:00
params := & cloudwatch . DescribeAlarmsForMetricInput {
2025-01-14 22:58:38 +08:00
Namespace : aws . String ( model . Namespace ) ,
2023-04-19 02:56:00 +08:00
MetricName : aws . String ( metricName ) ,
2021-09-08 22:06:43 +08:00
Dimensions : qd ,
2025-09-19 18:46:40 +08:00
Statistic : cloudwatchtypes . Statistic ( statistic ) ,
Period : aws . Int32 ( period ) ,
2021-09-08 22:06:43 +08:00
}
2025-09-19 18:46:40 +08:00
resp , err := cli . DescribeAlarmsForMetric ( ctx , params )
2021-09-08 22:06:43 +08:00
if err != nil {
2024-12-12 17:12:03 +08:00
result . Responses [ query . RefID ] = backend . ErrorResponseWithErrorSource ( backend . DownstreamError ( fmt . Errorf ( "%v: %w" , "failed to call cloudwatch:DescribeAlarmsForMetric" , err ) ) )
2024-07-30 21:55:01 +08:00
return result , nil
2021-09-08 22:06:43 +08:00
}
for _ , alarm := range resp . MetricAlarms {
alarmNames = append ( alarmNames , alarm . AlarmName )
2017-09-25 17:16:40 +08:00
}
}
2022-04-27 18:41:48 +08:00
annotations := make ( [ ] * annotationEvent , 0 )
2017-09-25 17:16:40 +08:00
for _ , alarmName := range alarmNames {
params := & cloudwatch . DescribeAlarmHistoryInput {
2017-09-26 23:00:38 +08:00
AlarmName : alarmName ,
2021-03-23 23:32:12 +08:00
StartDate : aws . Time ( query . TimeRange . From ) ,
EndDate : aws . Time ( query . TimeRange . To ) ,
2025-09-19 18:46:40 +08:00
MaxRecords : aws . Int32 ( 100 ) ,
2017-09-25 17:16:40 +08:00
}
2025-09-19 18:46:40 +08:00
resp , err := cli . DescribeAlarmHistory ( ctx , params )
2017-09-25 17:16:40 +08:00
if err != nil {
2024-12-12 17:12:03 +08:00
result . Responses [ query . RefID ] = backend . ErrorResponseWithErrorSource ( backend . DownstreamError ( fmt . Errorf ( "%v: %w" , "failed to call cloudwatch:DescribeAlarmHistory" , err ) ) )
2024-07-30 21:55:01 +08:00
return result , nil
2017-09-25 17:16:40 +08:00
}
for _ , history := range resp . AlarmHistoryItems {
2022-04-27 18:41:48 +08:00
annotations = append ( annotations , & annotationEvent {
Time : * history . Timestamp ,
Title : * history . AlarmName ,
2025-09-19 18:46:40 +08:00
Tags : string ( history . HistoryItemType ) ,
2022-04-27 18:41:48 +08:00
Text : * history . HistorySummary ,
} )
2017-09-25 17:16:40 +08:00
}
}
2021-03-23 23:32:12 +08:00
respD := result . Responses [ query . RefID ]
respD . Frames = append ( respD . Frames , transformAnnotationToTable ( annotations , query ) )
result . Responses [ query . RefID ] = respD
return result , err
2017-09-25 17:16:40 +08:00
}
2022-04-27 18:41:48 +08:00
func transformAnnotationToTable ( annotations [ ] * annotationEvent , query backend . DataQuery ) * data . Frame {
2021-03-23 23:32:12 +08:00
frame := data . NewFrame ( query . RefID ,
2022-04-27 18:41:48 +08:00
data . NewField ( "time" , nil , [ ] time . Time { } ) ,
2021-03-23 23:32:12 +08:00
data . NewField ( "title" , nil , [ ] string { } ) ,
data . NewField ( "tags" , nil , [ ] string { } ) ,
data . NewField ( "text" , nil , [ ] string { } ) ,
)
for _ , a := range annotations {
2022-04-27 18:41:48 +08:00
frame . AppendRow ( a . Time , a . Title , a . Tags , a . Text )
2017-09-25 17:16:40 +08:00
}
2021-03-23 23:32:12 +08:00
frame . Meta = & data . FrameMeta {
2023-08-30 23:46:47 +08:00
Custom : map [ string ] any {
2021-03-23 23:32:12 +08:00
"rowCount" : len ( annotations ) ,
} ,
2017-09-25 17:16:40 +08:00
}
2021-03-23 23:32:12 +08:00
return frame
2017-09-25 17:16:40 +08:00
}
2021-03-08 14:02:49 +08:00
func filterAlarms ( alarms * cloudwatch . DescribeAlarmsOutput , namespace string , metricName string ,
2025-09-19 18:46:40 +08:00
dimensions dataquery . Dimensions , statistic string , period int32 ) [ ] * string {
2017-09-25 17:16:40 +08:00
alarmNames := make ( [ ] * string , 0 )
for _ , alarm := range alarms . MetricAlarms {
if namespace != "" && * alarm . Namespace != namespace {
continue
}
if metricName != "" && * alarm . MetricName != metricName {
continue
}
2021-09-08 22:06:43 +08:00
matchDimension := true
2020-06-29 20:08:32 +08:00
if len ( dimensions ) != 0 {
if len ( alarm . Dimensions ) != len ( dimensions ) {
2021-09-08 22:06:43 +08:00
matchDimension = false
2020-06-29 20:08:32 +08:00
} else {
for _ , d := range alarm . Dimensions {
if _ , ok := dimensions [ * d . Name ] ; ! ok {
2021-09-08 22:06:43 +08:00
matchDimension = false
2020-06-29 20:08:32 +08:00
}
2017-09-26 14:45:52 +08:00
}
2017-09-25 17:16:40 +08:00
}
}
2021-09-08 22:06:43 +08:00
if ! matchDimension {
2017-09-25 17:16:40 +08:00
continue
}
2025-09-19 18:46:40 +08:00
if string ( alarm . Statistic ) != statistic {
2021-09-08 22:06:43 +08:00
continue
2017-09-25 17:16:40 +08:00
}
2017-09-26 14:45:52 +08:00
if period != 0 && * alarm . Period != period {
continue
}
2017-09-25 17:16:40 +08:00
alarmNames = append ( alarmNames , alarm . AlarmName )
}
return alarmNames
}