Untitled

 avatar
unknown
yaml
2 years ago
9.3 kB
5
Indexable
---
AWSTemplateFormatVersion: '2010-09-09'
Description: Template to create demo Custom Widget Lambda function. Change the stack name to set the name of the Lambda function. Once your stack is created, go to the CloudWatch Console Add widget modal to continue with your custom widget creation.

Parameters:
  DoCreateExampleDashboard:
    Description: Do you want an example dashboard created that shows how the custom widget works?
    Type: String
    AllowedValues: [ 'Yes', 'No']
    Default: 'No'

Conditions:
  CreateExampleDashboard: !Equals ["Yes", !Ref DoCreateExampleDashboard]

Resources:
  lambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
          // SPDX-License-Identifier: MIT-0
          
          // CloudWatch Custom Widget sample: display sample Cost Explorer report
          const aws = require('aws-sdk');
          
          const DOCS = `## Display Cost Explorer report
          Displays a report on cost of each AWS service for the selected time range.`;
          
          const PALETTE = ['#1f77b4','#ff7f0e','#2ca02c','#d62728','#9467bd','#8c564b','#e377c2','#7f7f7f','#bcbd22','#17becf','#aec7e8','#ffbb78','#98df8a','#ff9896','#c5b0d5','#c49c94','#f7b6d2','#c7c7c7','#dbdb8d','#9edae5' ];
          const CSS = `<style>table{width:100%}td,th{font-family:Amazon Ember,Helvetica Neue,Roboto,Arial,sans-serif;font-size:14px;white-space:nowrap;text-align:center;padding:3px;border-bottom:1px solid #f2f2f2}td:first-child{text-align:right;width:1px}td:nth-child(2){width:1px;font-weight:700}td div{border-radius:6px;height:18px}tr:hover{background:#fbf8e9!important;transition:all .1s ease-in-out}th{text-align:center;border-bottom:1px solid #ccc;background-color:#fafafa;height:40px}.cwdb-theme-dark td,.cwdb-theme-dark th{color:white;background-color:#2A2E33;}</style>`;
          
          const getCostResults = async (start, end) => {
              const ce = new aws.CostExplorer({ region: 'us-east-1' });
              let NextPageToken = null;
              let costs = [];
              do {    // paginate until no next page token
                  const params = { TimePeriod: { Start: start, End: end }, Granularity: 'DAILY', GroupBy: [  { Type: 'DIMENSION', Key: 'SERVICE' } ], Metrics: [ 'UnblendedCost' ], NextPageToken };
                  const response = await ce.getCostAndUsage(params).promise();
                  costs = costs.concat(response.ResultsByTime);
                  NextPageToken = response.NextPageToken;
              } while (NextPageToken);
              return costs;
          };
          
          const tableStart = (totalCost, start, end) => {
              return `<table class="cwdb-no-default-styles"><thead><tr><th>Service</th><th colspan=2><a href="/cost-management/home?region=us-east-1#/dashboard" target=_blank>Total Cost: $${totalCost.toLocaleString(undefined, {maximumFractionDigits: 0 })} (${start} - ${end})</a></td></tr></thead>`;
          };
          
          const collate = costResults => {
              const scs = {};
              let totalCost = 0;
              costResults.forEach(result => {
                  result.Groups.forEach(group => {
                      const serviceName = group.Keys[0];
                      let serviceCost = scs[serviceName] || 0;
                      let currentDayCost = parseFloat(group.Metrics.UnblendedCost.Amount) || 0;
                      totalCost += currentDayCost;
                      serviceCost += parseFloat(group.Metrics.UnblendedCost.Amount);
                      scs[serviceName] = serviceCost;
                  });
              });
              const sortedScs = Object.entries(scs).sort(([,a],[,b]) => b-a);
              const maxServiceCost = Math.max(...sortedScs.map(cost => cost[1]));
              return { totalCost, serviceCosts: sortedScs, maxServiceCost };
          };
          
          const getCostResultsHtml = (costResults, start, end) => {
              costResults.sort((a, b) => a.TimePeriod.Start.localeCompare(b.TimePeriod.Start));
              const  { totalCost, serviceCosts, maxServiceCost } = collate(costResults);
              let html = tableStart(totalCost, start, end);
          
              serviceCosts.forEach((serviceEntry, i) => {
                  const [ serviceName, serviceCost ] = serviceEntry;
                  const percent = (serviceCost / totalCost * 100).toFixed(2);
                  const maxPercent = (serviceCost / maxServiceCost * 100).toFixed(2);
                  const color = PALETTE[i % PALETTE.length];
                  html += `<tr><td>${serviceName}</td><td title="${percent}%">$${serviceCost.toLocaleString(undefined, {maximumFractionDigits: 2 })}</td><td title="${percent}%"><div style="background-color: ${color}; width: ${maxPercent}%;"></div></td></tr>`;
              });
              return `${html}</table>`;
          };
          
          exports.handler = async (event) => {
              if (event.describe) {
                  return DOCS;
              }
              const widgetContext = event.widgetContext;
              const timeRange = widgetContext.timeRange.zoom || widgetContext.timeRange;
              const start = timeRange.start;
              const end = timeRange.end;
              const minTimeDiff = Math.max(end - start, 24 * 60 * 60 * 1000);
              const newStart = end - minTimeDiff;
              const startStr = new Date(newStart).toISOString().split('T')[0];
              const endStr = new Date(end).toISOString().split('T')[0];
              const dailyCosts = await getCostResults(startStr, endStr);
              return CSS + getCostResultsHtml(dailyCosts, startStr, endStr);
          };
      Description: "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved."
      FunctionName: !Sub ${AWS::StackName}
      Handler: index.handler
      MemorySize: 128
      Role: !GetAtt lambdaIAMRole.Arn
      Runtime: nodejs12.x
      Timeout: 60
      Tags:
        - Key: cw-custom-widget
          Value: describe:readOnly

  lambdaIAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
              - sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
      Policies:
        - PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Effect: Allow
                Resource:
                  - !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${AWS::StackName}:*
          PolicyName: lambda
        - PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Action:
                  - ce:GetCostAndUsage
                Effect: Allow
                Resource:
                  - "*"
          PolicyName: costExplorerGetCostAndUsage

  lambdaLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${AWS::StackName}
      RetentionInDays: 7

  demoDashboard:
    Type: AWS::CloudWatch::Dashboard
    Condition: CreateExampleDashboard
    Properties:
      DashboardName: !Sub ${AWS::StackName}-${AWS::Region}
      DashboardBody:
        !Sub
          >
          {
              "start": "-P30D",
              "widgets": [
                  {
                      "type": "custom",
                      "width": 24,
                      "height": 8,
                      "properties": {
                          "endpoint": "${lambdaFunction.Arn}",
                          "updateOn": {
                              "refresh": true,
                              "timeRange": true
                          },
                          "title": "Cost Explorer Report - change time range to see report change"
                      }
                  },
                  {
                      "type": "metric",
                      "width": 24,
                      "height": 6,
                      "properties": {
                          "metrics": [
                              [ { "expression": "SEARCH('{AWS/Billing,Currency,ServiceName} MetricName=\"EstimatedCharges\"', 'Average', 86400)", "id": "e1", "region": "us-east-1", "visible": false } ],
                              [ { "expression": "SORT(e1, MAX, DESC)", "label": "[${!MAX}]", "id": "e2", "region": "us-east-1" } ]
                          ],
                          "view": "timeSeries",
                          "stacked": false,
                          "region": "us-east-1",
                          "stat": "Average",
                          "period": 300,
                          "title": "Max daily cost - over time",
                          "yAxis": {
                              "left": {
                                  "label": "$",
                                  "showUnits": false
                              }
                          }
                      }
                  }
              ]
          }
Editor is loading...