AWSTemplateFormatVersion: '2010-09-09'
Description: >
  Grafana AWS Logs: Forward CloudWatch logs to Grafana Cloud Loki, via AWS Firehose.

Parameters:
  FallbackS3BucketName:
    Description: Name of the S3 bucket where failed batches will be written to.
    Type: String
    Default: ""
  FirehoseStreamName:
    Description: Name of the AWS Kinesis Firehose delivery stream.
    Type: String
    Default: ""
  TargetEndpoint:
    Description: Target endpoint for delivering logs to.
    Type: String
    Default: ""
  LogsInstanceID:
    Description: Grafana Loki instance ID.
    Type: String
    Default: ""
  LogsWriteToken:
    Description: Grafana Cloud token used to write to Loki.
    Type: String
    Default: ""
    NoEcho: true
  SubscriptionFilter:
    Description: Specify a subscription filter for the CloudWatch Log Group that sends log data to the firehose delivery stream (ex. /aws/lambda/my-log-group).
    Type: String
    Default: ""

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
    - Label:
        default: "Grafana Cloud Loki configuration"
      Parameters:
      - TargetEndpoint
      - LogsInstanceID
      - LogsWriteToken
    - Label:
        default: "Kinesis Firehose configuration"
      Parameters:
      - FallbackS3BucketName
      - FirehoseStreamName

Resources:
  # Fallback s3 bucket for when Firehose deliveries fail
  FirehoseFallbackS3Bucket:
    Type: 'AWS::S3::Bucket'
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      BucketName: !Ref FallbackS3BucketName

  # IAM roles
  FirehoseRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - firehose.amazonaws.com
          Action:
          - sts:AssumeRole
      Description: "Grafana Cloud Firehose Role"
      Policies:
      - PolicyName: S3AndLogs
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - logs:PutLogEvents
            Resource: arn:aws:logs:*:*:*
          - Effect: Allow
            Action:
            - s3:AbortMultipartUpload
            - s3:GetBucketLocation
            - s3:GetObject
            - s3:ListBucket
            - s3:ListBucketMultipartUploads
            - s3:PutObject
            Resource: 
            - !Sub 'arn:aws:s3:::${FallbackS3BucketName}'
            - !Sub 'arn:aws:s3:::${FallbackS3BucketName}/*'
      RoleName: GrafanaAWSLogsRoleFirehose

  SubscriptionFilterRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - logs.amazonaws.com
          Action:
          - sts:AssumeRole
          Condition:
            StringLike:
              aws:SourceArn: !Sub 'arn:aws:logs:*:${AWS::AccountId}:*'
      Description: "Role that allows SubscriptionFilters to write logs to Firehose"
      Policies:
      - PolicyName: FirehoseWrite
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - firehose:PutRecord
            Resource: !GetAtt DeliveryStream.Arn
      RoleName: SubscriptionFiltersFirehoseWrite

  # Delivery stream, main component used for sending logs
  DeliveryStream:
    Type: AWS::KinesisFirehose::DeliveryStream
    Properties:
      DeliveryStreamName: !Ref FirehoseStreamName
      HttpEndpointDestinationConfiguration:
        EndpointConfiguration:
          Url: !Ref TargetEndpoint
          AccessKey: !Sub '${LogsInstanceID}:${LogsWriteToken}'
        BufferingHints:
          # Buffer incoming data for the specified period of time, in seconds, before delivering it to the destination
          # Setting to 1 minute to keep a low enough latency between log production and actual time they are processed in Loki
          IntervalInSeconds: 60
          # Buffer incoming data to the specified size, in MBs, before delivering it to the destination
          SizeInMBs: 1
        RequestConfiguration:
          ContentEncoding: GZIP
        RoleARN: !GetAtt FirehoseRole.Arn

        # these blocks configured the fallback s3 bucket destination
        S3BackupMode: FailedDataOnly
        S3Configuration:
          BucketARN: !GetAtt FirehoseFallbackS3Bucket.Arn
          RoleARN: !GetAtt FirehoseRole.Arn
          CompressionFormat: GZIP
          BufferingHints:
            IntervalInSeconds: 300
            SizeInMBs: 5

  # SubscriptionFilter used to send logs data to the firehose delivery stream, and from there, to Grafana Cloud
  FirehoseSubscriptionFilter:
    Type: AWS::Logs::SubscriptionFilter
    Properties:
      DestinationArn: !GetAtt DeliveryStream.Arn
      FilterPattern: ""
      LogGroupName: !Ref SubscriptionFilter
      RoleArn: !GetAtt SubscriptionFilterRole.Arn

Outputs:
  DeliveryStreamArn:
    Description: "Firehose Delivery Stream ARN, to be used as destination for SubscriptionFilters"
    Value: !GetAtt DeliveryStream.Arn
