# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > Serverless SaaS - Cost by tenant Globals: Function: Timeout: 29 Resources: CURBucket: Type: AWS::S3::Bucket DeletionPolicy : Retain Properties: BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: 'AES256' PublicAccessBlockConfiguration: BlockPublicAcls: True BlockPublicPolicy: True IgnorePublicAcls: True RestrictPublicBuckets: True AWSCURDatabase: Type: 'AWS::Glue::Database' Properties: DatabaseInput: Name: !Sub 'costexplorerdb' CatalogId: !Ref AWS::AccountId AWSCURCrawlerComponentFunction: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - glue.amazonaws.com Action: - 'sts:AssumeRole' Path: / ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSGlueServiceRole' Policies: - PolicyName: AWSCURCrawlerComponentFunction PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: !Sub 'arn:${AWS::Partition}:logs:*:*:*' - Effect: Allow Action: - 'glue:UpdateDatabase' - 'glue:UpdatePartition' - 'glue:CreateTable' - 'glue:UpdateTable' - 'glue:ImportCatalogToGlue' Resource: '*' - Effect: Allow Action: - 's3:GetObject' - 's3:PutObject' Resource: !Sub '${CURBucket.Arn}*' - PolicyName: AWSCURKMSDecryption PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'kms:Decrypt' Resource: '*' AWSCURCrawlerLambdaExecutor: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' Path: / Policies: - PolicyName: AWSCURCrawlerLambdaExecutor PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: !Sub 'arn:${AWS::Partition}:logs:*:*:*' - Effect: Allow Action: - 'glue:StartCrawler' Resource: '*' AWSCURCrawler: Type: 'AWS::Glue::Crawler' DependsOn: - AWSCURDatabase - AWSCURCrawlerComponentFunction Properties: Name: AWSCURCrawler-Multi-tenant Description: A recurring crawler that keeps your CUR table in Athena up-to-date. Role: !GetAtt AWSCURCrawlerComponentFunction.Arn DatabaseName: !Ref AWSCURDatabase Targets: S3Targets: - Path: !Sub 's3://${CURBucket}/curoutput' Exclusions: - '**.json' - '**.yml' - '**.sql' - '**.csv' - '**.gz' - '**.zip' SchemaChangePolicy: UpdateBehavior: UPDATE_IN_DATABASE DeleteBehavior: DELETE_FROM_DATABASE AWSCURInitializer: Type: 'AWS::Lambda::Function' DependsOn: AWSCURCrawler Properties: Code: ZipFile: > const AWS = require('aws-sdk'); const response = require('./cfn-response'); exports.handler = function(event, context, callback) { if (event.RequestType === 'Delete') { response.send(event, context, response.SUCCESS); } else { const glue = new AWS.Glue(); glue.startCrawler({ Name: 'AWSCURCrawler-Multi-tenant' }, function(err, data) { if (err) { const responseData = JSON.parse(this.httpResponse.body); if (responseData['__type'] == 'CrawlerRunningException') { callback(null, responseData.Message); } else { const responseString = JSON.stringify(responseData); if (event.ResponseURL) { response.send(event, context, response.FAILED,{ msg: responseString }); } else { callback(responseString); } } } else { if (event.ResponseURL) { response.send(event, context, response.SUCCESS); } else { callback(null, response.SUCCESS); } } }); } }; Handler: 'index.handler' Timeout: 30 Runtime: nodejs16.x ReservedConcurrentExecutions: 1 Role: !GetAtt AWSCURCrawlerLambdaExecutor.Arn TenantCostandUsageAttributionTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: Date AttributeType: N - AttributeName: TenantId#ServiceName AttributeType: S KeySchema: - AttributeName: Date KeyType: HASH - AttributeName: TenantId#ServiceName KeyType: RANGE BillingMode: PAY_PER_REQUEST TableName: TenantCostAndUsageAttribution QueryLogInsightsExecutionRole: Type: AWS::IAM::Role Properties: RoleName: query-log-insights-execution-role Path: '/' AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: query-log-insight-lab7 PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - logs:GetQueryResults - logs:StartQuery - logs:StopQuery - logs:FilterLogEvents - logs:DescribeLogGroups - cloudformation:ListStackResources Resource: - "*" - Effect: Allow Action: - s3:* Resource: - !Sub 'arn:aws:s3:::${CURBucket}*' - Effect: Allow Action: - dynamodb:* Resource: - !GetAtt TenantCostandUsageAttributionTable.Arn - Effect: Allow Action: - Athena:* Resource: - "*" - Effect: Allow Action: - glue:* Resource: - "*" GetDynamoDBUsageAndCostByTenant: Type: AWS::Serverless::Function DependsOn: QueryLogInsightsExecutionRole Properties: CodeUri: TenantUsageAndCost/ Handler: tenant_usage_and_cost.calculate_daily_dynamodb_attribution_by_tenant Runtime: python3.9 Role: !GetAtt QueryLogInsightsExecutionRole.Arn Environment: Variables: ATHENA_S3_OUTPUT: !Ref CURBucket Events: ScheduledEvent: Type: Schedule Properties: Name: CalculateDynamoUsageAndCostByTenant Schedule: rate(5 minutes) GetLambdaUsageAndCostByTenant: Type: AWS::Serverless::Function DependsOn: QueryLogInsightsExecutionRole Properties: CodeUri: TenantUsageAndCost/ Handler: tenant_usage_and_cost.calculate_daily_lambda_attribution_by_tenant Runtime: python3.9 Role: !GetAtt QueryLogInsightsExecutionRole.Arn Environment: Variables: ATHENA_S3_OUTPUT: !Ref CURBucket Events: ScheduledEvent: Type: Schedule Properties: Name: CalculateLambdaUsageAndCostByTenant Schedule: rate(5 minutes) Outputs: CURBucketname: Description: The name of S3 bucket name Value: !Ref CURBucket Export: Name: "CURBucketname" AWSCURInitializerFunctionName: Description: Function name of CUR initializer Value: !Ref AWSCURInitializer Export: Name: "AWSCURInitializerFunctionName"