Logo

dev-resources.site

for different kinds of informations.

AWS Budgets: Update alert thresholds unlimitedly with Lambda

Published at
12/27/2022
Categories
aws
budget
lambda
cloudformation
Author
shimo_s3
Categories
4 categories in total
aws
open
budget
open
lambda
open
cloudformation
open
Author
8 person written this
shimo_s3
open
AWS Budgets: Update alert thresholds unlimitedly with Lambda

Motivation

When I use AWS Budgets, I can receive notifications when the billing is above a threshold.

The facts are that:

  1. We can set only 10 alert thresholds for one budget.
  2. Two budgets are free but we are charged for using more budgets.

For personal use, usually, I'm not charged so much (around 10 USD per month). I want to be notified every 1 USD but there is a limitation of 10 alerts like above fact.

I this post, I share how to update the alert threshold incremently when AWS Budgets triggers.

Note from the pricing page:

Your first two action-enabled budgets are free (regardless of the number of actions you configure per budget) per month. Afterwards each subsequent action-enabled budget will incur a $0.10 daily cost.

Architecture

image of Budgets threshold increment

  1. EventBridge triggers Lambda(setBudget) at the 1st of months. This Lambda delete and re-create the budget to initialize for a month and create 10 alert thresholds: 1, 2, 3, ... 10 USD thresholds, for example.

  2. When the billing is above the threshold, the user is notified via SNS email. At the same time, the second Lambda function is triggered and updates the threshold. If the triggered threshold is 1 USD, the Lambda function update the threshold to 11 USD.

CloudFormation template

  • Deploy from the Console. Create stack -> Upload a template -> Choose this file.
  • Enter Stack name and parameters: budget name, email, increment.
  • Leave the rest default. Choose Next, Next, check IAM acknowledge, and Submit.
  • When starting in the middle of the month, manually run the SetBudgetHandler Lambda function. Note that you'll get notified of all alerts of the current used cost.
AWSTemplateFormatVersion: "2010-09-09"
Description: "Increment alert threshold of AWS Budgets"
Parameters:
  budgetname:
    Type: String
    Default: increment-notification
  increment:
    Type: String
    Default: 1
  email:
    Type: String
    AllowedPattern: "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$"
    ConstraintDescription: Must be a valid email address.
Resources:
  MyTopic:
    Type: AWS::SNS::Topic
  MyTopicTokenSubscription:
    Type: AWS::SNS::Subscription
    Properties:
      Protocol: email
      TopicArn: !Ref MyTopic
      Endpoint: !Ref email
  MyTopicPolicy:
    Type: AWS::SNS::TopicPolicy
    Properties:
      PolicyDocument:
        Statement:
          - Action: sns:Publish
            Effect: Allow
            Principal:
              Service: budgets.amazonaws.com
            Resource: !Ref MyTopic
            Sid: "0"
        Version: "2012-10-17"
      Topics:
        - !Ref MyTopic
  UpdateBudgetHandlerServiceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
        Version: "2012-10-17"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
  UpdateBudgetHandlerServiceRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Statement:
          - Action: budgets:*
            Effect: Allow
            Resource: "*"
        Version: "2012-10-17"
      PolicyName: UpdateBudgetHandlerServiceRoleDefaultPolicy
      Roles:
        - !Ref UpdateBudgetHandlerServiceRole
  UpdateBudgetHandler:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import os
          import re

          import boto3

          budget_name = os.environ['BUDGET_NAME']
          increment = os.environ['INCREMENT']

          def handler(event, context):
              print(event)
              client = boto3.client('budgets')
              account_id = context.invoked_function_arn.split(":")[4]
              print("account_id", account_id)

              message = event['Records'][0]['Sns']['Message']
              print("message", message)

              # catch 1 from "Alert Threshold: > $1.00"
              match = re.search(r"Alert Threshold: > \$(\d{1,})\.00", message)
              if not match:
                  print("No Budget Notification. Exit.")
                  return

              current_value_str = match.groups()[0]
              print("current_value", current_value_str)

              current_value = int(current_value_str)
              next_value = current_value + 10 * int(increment)

              response = client.update_notification(
                  AccountId=account_id,
                  BudgetName=budget_name,
                  OldNotification={
                      "NotificationType": "ACTUAL",
                      "ComparisonOperator": "GREATER_THAN",
                      "Threshold": current_value,
                      "ThresholdType": "ABSOLUTE_VALUE",
                  },
                  NewNotification={
                      "NotificationType": "ACTUAL",
                      "ComparisonOperator": "GREATER_THAN",
                      "Threshold": next_value,
                      "ThresholdType": "ABSOLUTE_VALUE",
                  },
              )

      Role: !GetAtt UpdateBudgetHandlerServiceRole.Arn
      Environment:
        Variables:
          BUDGET_NAME: !Ref budgetname
          INCREMENT: !Ref increment
      Handler: index.handler
      Runtime: python3.9
      Timeout: 10
    DependsOn:
      - UpdateBudgetHandlerServiceRoleDefaultPolicy
      - UpdateBudgetHandlerServiceRole
  UpdateBudgetHandlerEventInvokeConfig:
    Type: AWS::Lambda::EventInvokeConfig
    Properties:
      FunctionName: !Ref UpdateBudgetHandler
      Qualifier: $LATEST
      MaximumRetryAttempts: 0
  UpdateBudgetHandlerAllowInvokeStackMyTopic:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt UpdateBudgetHandler.Arn
      Principal: sns.amazonaws.com
      SourceArn: !Ref MyTopic
  UpdateBudgetHandlerMyTopic:
    Type: AWS::SNS::Subscription
    Properties:
      Protocol: lambda
      TopicArn: !Ref MyTopic
      Endpoint: !GetAtt UpdateBudgetHandler.Arn
  SetBudgetHandlerServiceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
        Version: "2012-10-17"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
  SetBudgetHandlerServiceRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Statement:
          - Action: budgets:*
            Effect: Allow
            Resource: "*"
        Version: "2012-10-17"
      PolicyName: SetBudgetHandlerServiceRoleDefaultPolicy
      Roles:
        - !Ref SetBudgetHandlerServiceRole
  SetBudgetHandler:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import os
          import boto3

          budget_name = os.environ['BUDGET_NAME']
          sns_arn = os.environ['SNS_ARN']
          increment = os.environ['INCREMENT']

          client = boto3.client("budgets")


          def handler(event, context):
              account_id = context.invoked_function_arn.split(":")[4]

              # Delete if the budget exists
              try:
                  client.delete_budget(AccountId=account_id, BudgetName=budget_name)
                  print("Deleted the old budget.")
              except client.exceptions.NotFoundException:
                  pass

              # Create new budget
              client.create_budget(
                  AccountId=account_id,
                  Budget={
                      "BudgetName": budget_name,
                      "BudgetLimit": {"Amount": "100.0", "Unit": "USD"},
                      "CostTypes": {
                          "IncludeTax": True,
                          "IncludeSubscription": True,
                          "UseBlended": False,
                          "IncludeRefund": False,
                          "IncludeCredit": False,
                          "IncludeUpfront": True,
                          "IncludeRecurring": True,
                          "IncludeOtherSubscription": True,
                          "IncludeSupport": True,
                          "IncludeDiscount": True,
                          "UseAmortized": False,
                      },
                      "TimeUnit": "MONTHLY",
                      "BudgetType": "COST",
                  },
                  NotificationsWithSubscribers=[
                      {
                          "Notification": {
                              "NotificationType": "ACTUAL",
                              "ComparisonOperator": "GREATER_THAN",
                              "Threshold": threshold,
                              "ThresholdType": "ABSOLUTE_VALUE",
                              "NotificationState": "OK",
                          },
                          "Subscribers": [
                              {"SubscriptionType": "SNS", "Address": sns_arn},
                          ],
                      }
                      for threshold in range(1, 1 + 10 * int(increment), int(increment))
                  ],
              )
              print("Created a new budget.")
      Role: !GetAtt SetBudgetHandlerServiceRole.Arn
      Environment:
        Variables:
          BUDGET_NAME: !Ref budgetname
          SNS_ARN: !Ref MyTopic
          INCREMENT: !Ref increment
      Handler: index.handler
      Runtime: python3.9
      Timeout: 10
    DependsOn:
      - SetBudgetHandlerServiceRoleDefaultPolicy
      - SetBudgetHandlerServiceRole
  SetBudgetHandlerEventInvokeConfig:
    Type: AWS::Lambda::EventInvokeConfig
    Properties:
      FunctionName: !Ref SetBudgetHandler
      Qualifier: $LATEST
      MaximumRetryAttempts: 0
  ScheduleRule:
    Type: AWS::Events::Rule
    Properties:
      ScheduleExpression: cron(0 0 1 * ? *)
      State: ENABLED
      Targets:
        - Arn: !GetAtt SetBudgetHandler.Arn
          Id: Target0
  ScheduleRuleAllowEventRuleStackSetBudgetHandler:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt SetBudgetHandler.Arn
      Principal: events.amazonaws.com
      SourceArn: !GetAtt ScheduleRule.Arn

Enter fullscreen mode Exit fullscreen mode

Summary

I've shared how to update AWS Budgets threshold incrementally.

budget Article's
30 articles in total
Favicon
Never Get Shocked by Your AWS Bill Again! 💰 Setting Up Smart Cost Alerts
Favicon
Best Monthly Budget Planners to Make Financial Management Smarter
Favicon
Building a Developer-Friendly Workspace on a Budget
Favicon
DevOps4Devs: set a budget
Favicon
The Pillars of Site Reliability Engineering Building Resilient Systems
Favicon
💰 Avoiding the $100,000 Surprise: How to Set Up AWS Budgets and Cost Alerts Like a Pro
Favicon
Achieving Financial Freedom: Practical Tips and Strategies
Favicon
How Frontier Airlines Keeps Fares Low
Favicon
Managing Business Finances: Tips for Staying on Top of Your Budget
Favicon
Home-improvement financing tips for your next project
Favicon
The Best Cheap SEO Services india
Favicon
Refurbished Computer Hardware: Cure for the Common Datacenter
Favicon
Revolutionizing Affordable Gaming: Radiance Gaming PC RX 580 8GB RGB
Favicon
Building Budget Planner app with Lyzr SDK
Favicon
How to set up AWS Budget Alerts to prevent surprises
Favicon
ChatGPT-Powered Finance App
Favicon
A guide to crafting an effective budget for SRE investments
Favicon
In what ways can small businesses leverage AI to streamline operations on a budget?
Favicon
What is Wrong with Classical Custom Development
Favicon
Best printer under $200
Favicon
Free AWS Bootcamp: Week 0 - Part 2
Favicon
AWS Budgets: Update alert thresholds unlimitedly with Lambda
Favicon
Performance Budget - Idea, Core Web Vitals, Tools
Favicon
Creating an online budget form 1/5
Favicon
SMB Telecom Services: How To Make Sure You Are Not Overpaying
Favicon
How to avoid unexpected AWS costs
Favicon
Makerwork 001
Favicon
How to Pay off Your $20,000 Loan in One Year or Less
Favicon
Fight Covid By Folding At Home (On A Budget)
Favicon
Where Did My Money Go

Featured ones: