Skip to content

Commit

Permalink
Added the option to add cloudwatch alarms on errors for lambdas
Browse files Browse the repository at this point in the history
  • Loading branch information
omerh committed Feb 27, 2020
1 parent 52b6de7 commit d180117
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 18 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ awsctl get certificates --region ue-east-1 # Get all expiring and expired cer
awsctl delete certificates --region all --yes # Delete all unused certificates from the account
```

Cloudwatch Alarms

>Currently tested on `Errors` metric for lambda
```bash
# Single lambda
set cloudwatchalarm --resource lambda --metric errors --region eu-west-2 --arn arn:aws:lambda:eu-west-2:000000000000:function:test --threshold 3 --action arn:aws:sns:eu-west-2:000000000000:SNSToSlack --yes

# All lambdas
set cloudwatchalarm --resource lambda --metric errors --region eu-west-2 --threshold 3 --action arn:aws:sns:eu-west-2:000000000000:SNSToSlack --yes

# All lambdas in all regions
set cloudwatchalarm --resource lambda --metric errors --region all --threshold 3 --action arn:aws:sns:eu-west-2:000000000000:SNSToSlack --yes
```

For any missing action please open an issue for a feature request.

### Contributing
Expand Down
2 changes: 1 addition & 1 deletion cmd/awsctl/cmd/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ func init() {
getCmd.AddCommand(getRdsCmd)
getCmd.AddCommand(getRdsSnapshots)
getCmd.AddCommand(getAcmCertCmd)
rootCmd.AddCommand(getCmd)
getCmd.AddCommand(getCloudwatchAlarmCmd)
}
33 changes: 33 additions & 0 deletions cmd/awsctl/cmd/get_cloudwatch_alarm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package cmd

import (
"fmt"

"github.com/omerh/awsctl/pkg/helpers"
"github.com/spf13/cobra"
)

var getCloudwatchAlarmCmd = &cobra.Command{
Use: "cloudwatchalarm",
Short: "Get cloudwatch alarms",
Run: func(cmd *cobra.Command, Args []string) {
region, _ := cmd.Flags().GetString("region")
// out, _ := cmd.Flags().GetString("out")

if region == "all" {
awsRegions, _ := helpers.GetAllAwsRegions()
for _, r := range awsRegions {
alarms := helpers.ListCloudwatchAlarms(r)
fmt.Println(alarms)
}
return
}

if region == "" {
region = helpers.GetDefaultAwsRegion()
}

alarms := helpers.ListCloudwatchAlarms(region)
fmt.Println(alarms)
},
}
1 change: 1 addition & 0 deletions cmd/awsctl/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ var rootCmd = &cobra.Command{
func init() {
// Commands
rootCmd.AddCommand(setCmd)
rootCmd.AddCommand(getCmd)

// Flags
rootCmd.PersistentFlags().StringP("region", "r", "", "aws region/all")
Expand Down
105 changes: 91 additions & 14 deletions cmd/awsctl/cmd/set_cloudwatch_alarm.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package cmd

import (
"fmt"
"log"

"github.com/aws/aws-sdk-go/service/cloudwatch"
"github.com/aws/aws-sdk-go/service/lambda"
"github.com/omerh/awsctl/pkg/helpers"
"github.com/spf13/cobra"
)
Expand All @@ -10,31 +15,103 @@ var cmdCloudwatchAlarm = &cobra.Command{
Short: "Find and Set cloudwatch alarms to aws resources",
Run: func(cmd *cobra.Command, args []string) {
// Command params
// apply, _ := cmd.Flags().GetBool("yes")

apply, _ := cmd.Flags().GetBool("yes")
region, _ := cmd.Flags().GetString("region")
resource, _ := cmd.Flags().GetString("resource")
metric, _ := cmd.Flags().GetString("metric")
arn, _ := cmd.Flags().GetString("arn")
threshold, _ := cmd.Flags().GetFloat64("threshold")
action, _ := cmd.Flags().GetString("action")

// if region == "all" {
// awsRegions, _ := helpers.GetAllAwsRegions()
// for _, r := range awsRegions {
var namespace, metricName string
// Resource type
if resource == "lambda" {
namespace = "AWS/Lambda"
} else {
fmt.Println("Currently support only resource type lambda")
return
}

// }
// return
// }
// Metric type
if metric == "errors" {
metricName = "Errors"
// } else if metric == "latency" {
// metricName = "Latency"
} else {
fmt.Println("Currently supporting metrics: errors")
}

// if region == "" {
// region = helpers.GetDefaultAwsRegion()
// }
// Start function
if region == "all" {
awsRegions, _ := helpers.GetAllAwsRegions()
for _, r := range awsRegions {
// All alarms in cloudwatch
allAlarms := helpers.ListCloudwatchAlarms(r)
// Filtered alarms according to the namespace of the alarms
alarms := filterCloudwatchAlarmsForNamespace(allAlarms, namespace)
// All lambdas in a region
lambdasSlice := helpers.GetAllLmbdasInRegion(r, arn)
checkOrCreateCloudwatchAlarm(alarms, lambdasSlice, metricName, namespace, threshold, action, r, apply)
}
return
}

helpers.GetAllLmbdasInRegion(region)
if region == "" {
region = helpers.GetDefaultAwsRegion()
}

// All alarms in cloudwatch
allAlarms := helpers.ListCloudwatchAlarms(region)
// Filtered alarms according to the namespace of the alarms
alarms := filterCloudwatchAlarmsForNamespace(allAlarms, namespace)
// All lambdas in a region
lambdasSlice := helpers.GetAllLmbdasInRegion(region, arn)
checkOrCreateCloudwatchAlarm(alarms, lambdasSlice, metricName, namespace, threshold, action, region, apply)
},
}

func init() {
// Flags
cmdCloudwatchAlarm.Flags().String("resource", "", "Type of resource to set the alarm")
cmdCloudwatchAlarm.MarkFlagRequired("resource")
cmdCloudwatchAlarm.Flags().String("type", "", "Type for alarm to set")
cmdCloudwatchAlarm.MarkFlagRequired("type")
cmdCloudwatchAlarm.Flags().String("metric", "", "Type for alarm to set")
cmdCloudwatchAlarm.MarkFlagRequired("metric")
cmdCloudwatchAlarm.Flags().String("arn", "", "Resource ARN")
cmdCloudwatchAlarm.Flags().Float64("threshold", 0, "Threshold float")
cmdCloudwatchAlarm.MarkFlagRequired("threshold")
cmdCloudwatchAlarm.Flags().String("action", "", "The action to do once alert is on, like sns arn")
}

func checkIfLambdaAlarmExistsInCloudwatch(alarms []*cloudwatch.MetricAlarm, lambda *lambda.FunctionConfiguration, metricName string) bool {
for _, a := range alarms {
if *a.Dimensions[0].Value == *lambda.FunctionName && *a.MetricName == metricName {
fmt.Printf("Lambda alarm for %v on metric %v already exists\n", *lambda.FunctionName, metricName)
return true
}
}
return false
}

func filterCloudwatchAlarmsForNamespace(allalarms []*cloudwatch.MetricAlarm, namespace string) (alarms []*cloudwatch.MetricAlarm) {
for _, a := range allalarms {
if *a.Namespace == namespace {
alarms = append(alarms, a)
}
}
return alarms
}

func checkOrCreateCloudwatchAlarm(alarms []*cloudwatch.MetricAlarm, lambdasSlice []*lambda.FunctionConfiguration, metricName string, namespace string, threshold float64, action string, region string, apply bool) {
for _, l := range lambdasSlice {
alarmExists := checkIfLambdaAlarmExistsInCloudwatch(alarms, l, metricName)
if !alarmExists {
// fmt.Printf("No alarm for %v, Creating one for %v\n", *l.FunctionName, metricName)
if apply {
helpers.CreateLambdaCloudwatchAlarm(region, *l.FunctionName, metricName, namespace, threshold, action)
log.Printf("Alarm was created for %v on metric %v", *l.FunctionName, metricName)
} else {
fmt.Printf("Pass --yes for creating the monitoring alarm")
}
}
}
}
69 changes: 69 additions & 0 deletions pkg/helpers/cloudwatch_alarms.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package helpers

import (
"fmt"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudwatch"
)

// ListCloudwatchAlarms list all cloudwatch alarms
func ListCloudwatchAlarms(region string) []*cloudwatch.MetricAlarm {
awsSession, _ := InitAwsSession(region)
svc := cloudwatch.New(awsSession)
input := &cloudwatch.DescribeAlarmsInput{}
result, _ := svc.DescribeAlarms(input)
cloudwatchAlarms := result.MetricAlarms

for result.NextToken != nil {
input = &cloudwatch.DescribeAlarmsInput{
NextToken: result.NextToken,
}
result, _ = svc.DescribeAlarms(input)
for _, c := range result.MetricAlarms {
cloudwatchAlarms = append(cloudwatchAlarms, c)
}
}

return cloudwatchAlarms
}

// CreateLambdaCloudwatchAlarm will create a new alarm for cloudwatch
func CreateLambdaCloudwatchAlarm(region string, functionName string, metricName string, namespace string, threshold float64, action string) bool {
awsSession, _ := InitAwsSession(region)
svc := cloudwatch.New(awsSession)
input := &cloudwatch.PutMetricAlarmInput{
AlarmName: aws.String(fmt.Sprintf("%v on %v", metricName, functionName)),
ComparisonOperator: aws.String(cloudwatch.ComparisonOperatorGreaterThanOrEqualToThreshold),
EvaluationPeriods: aws.Int64(1),
MetricName: aws.String(metricName),
Namespace: aws.String(namespace),
Period: aws.Int64(60),
Statistic: aws.String(cloudwatch.StatisticSum),
Threshold: aws.Float64(threshold),
ActionsEnabled: aws.Bool(true),
AlarmDescription: aws.String(fmt.Sprintf("%v on %v greater than %v", metricName, functionName, threshold)),

Dimensions: []*cloudwatch.Dimension{
{
Name: aws.String("FunctionName"),
Value: aws.String(functionName),
},
},

AlarmActions: []*string{
aws.String(action),
},
}

// Debug input
// fmt.Println(input)

_, err := svc.PutMetricAlarm(input)
if err != nil {
fmt.Println("Error", err)
return false
}

return true
}
3 changes: 0 additions & 3 deletions pkg/helpers/lambda.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package helpers

import (
"fmt"

"github.com/aws/aws-sdk-go/service/lambda"
)

Expand Down Expand Up @@ -30,7 +28,6 @@ func GetAllLmbdasInRegion(region string, arn string) []*lambda.FunctionConfigura
if len(arn) > 0 {
for _, l := range lambdas {
if *l.FunctionArn == arn {
fmt.Println("match")
lambdas = nil
lambdas = append(lambdas, l)
return lambdas
Expand Down

0 comments on commit d180117

Please sign in to comment.