AWSTemplateFormatVersion: '2010-09-09'

# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Transform: AWS::Serverless-2016-10-31
Description: >
  Configure periodic replication schedule for your Amazon FSx for OpenZFS file system volumes.

Parameters:
  SourceVolumeID:
    Description: Amazon FSx for OpenZFS Source Volume ID
    Type: String
    AllowedPattern: "^fsvol-[A-Za-z0-9]+"

  TargetVolumeID:
    Description: Amazon FSx for OpenZFS Target Volume ID
    Type: String
    AllowedPattern: "^fsvol-[A-Za-z0-9]+"

  # Schedule for creating snapshot and calling CopySnapshotAndUpdateVolume API
  CronSchedule:
    Description: CRON schedule for snapshots (default every 6 hours)
    Type: String
    Default: "0 0/6 * * ? *"

  # Value of snapshot Name (shows in Name column in snapshots list in console)
  SnapshotName:
    Description: The name of snapshots starts with (shown in FSx console)
    Type: String
    Default: "fsx_scheduled_snapshot"
    AllowedPattern: "^[a-zA-Z0-9_:.-]{1,179}$"

  # CopySnapshotAndUpdateVolume - "Options" parameter.
  # Comma (,) separated values such as "DELETE_INTERMEDIATE_SNAPSHOTS,DELETE_INTERMEDIATE_DATA,DELETE_CLONED_VOLUMES"
  Options:
    Description: Options parameter value for the CopySnapshotAndUpdateVolume API
    Type: String
    Default: ''

  # CopySnapshotAndUpdateVolume - "CopyStrategy" parameter.
  CopyStrategy:
    Description: CopyStrategy parameter value for the CopySnapshotAndUpdateVolume API
    Type: String
    Default: INCREMENTAL_COPY

  # Email for notifications
  Email:
    Description: Email for CopySnapshotAndUpdateVolume notifications
    Type: String

  # If customer wants notification for successful snapshots
  SuccessNotification:
    Description: Do you want to be notified for successful replication initiation? For failures, you will always be notified
    Type: String
    AllowedValues:
      - "Yes"
      - "No"
    Default: "Yes"

  # Layer ARN
  LambdaLayerARN:
    Description: AWS Lambda Layer ARN having the AWS Python SDK released on or after 11/26/2023
    Type: String

Resources:
  #
  # Cloudwatch events/rule to trigger a Lambda function
  #
  FSxEventBridgeRule:
    Type: AWS::Events::Rule
    Properties:
      Description: AWS EventBridge Schedule to trigger a lambda function
      ScheduleExpression: !Sub 'cron(${CronSchedule})'
      State: ENABLED
      Targets:
        - Arn: !GetAtt SnapshotLambda.Arn
          Id: SnapshotLambdaRule

  #
  # Lambda resource based policy to allow EventBridge Rule to trigger a lambda function
  #
  SnapshotLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Sub ${SnapshotLambda}
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com

  #
  # SNS topic to notify
  #
  SNSTopic:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: !Sub ${AWS::StackName}-notification
      TopicName: !Sub ${AWS::StackName}-notification
      Subscription:
      - Endpoint: !Ref Email
        Protocol: "email"

  #
  # Lambda Execution Role
  #
  SnapshotLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub SnapshotLambda-Role-${AWS::StackName}
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
          Action: sts:AssumeRole
      Path: /
      Policies:
      - PolicyName: !Sub SnapshotLambda-Policy-${AWS::StackName}
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - logs:CreateLogGroup
            - logs:CreateLogStream
            - logs:PutLogEvents
            Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*"
          - Effect: Allow
            Action:
            - fsx:CreateSnapshot
            - fsx:DescribeSnapshots
            - fsx:DescribeVolumes
            - fsx:DeleteSnapshot
            - fsx:TagResource
            - fsx:CopySnapshotAndUpdateVolume
            Resource: "*"
          - Effect: Allow
            Action:
            - sns:Publish
            Resource: !GetAtt SNSTopic.TopicArn

  #
  # Lambda function
  #
  SnapshotLambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub ${AWS::StackName}
      Description: Lambda function that creates the snapshot and makes CopySnapshotAndUpdateVolume API call.
      Environment:
        Variables:
          SRC_VOLUME_ID: !Ref SourceVolumeID
          DEST_VOLUME_ID: !Ref TargetVolumeID
          OPTIONS: !Ref Options
          COPY_STRATEGY: !Ref CopyStrategy
          SNS_TOPIC_ARN: !GetAtt SNSTopic.TopicArn
          SUCCESS_NOTIFICATION: !Ref SuccessNotification
          SNAPSHOT_NAME: !Ref SnapshotName
      Handler: index.lambda_handler
      Role: !GetAtt SnapshotLambdaRole.Arn
      Layers:
        - !Ref LambdaLayerARN
      Code:
        ZipFile: |
            import json
            import boto3
            import os
            import time
            import datetime

            session = boto3.session.Session()
            fsx_client = session.client(service_name='fsx')
            sns_client = boto3.client('sns')

            def send_sns_notification(msg, subject):
                    response = sns_client.publish(
                    TopicArn=os.environ.get("SNS_TOPIC_ARN"),
                    Subject=subject,
                    Message=msg
                )

            def lambda_handler(event, context):
                try:
                    # call the FSx snapshot API
                    print ("Creating a snapshot for the volume = " + os.environ.get("SRC_VOLUME_ID"))
                    response = fsx_client.create_snapshot(
                        # append datetime to ensure snap name is unique
                        Name=os.environ.get("SNAPSHOT_NAME") + datetime.datetime.utcnow().strftime("_%Y-%m-%d_%H:%M:%S.%f")[:-3],
                        VolumeId=os.environ.get("SRC_VOLUME_ID"),
                        Tags=[{'Key': 'CreatedBy','Value': context.function_name + "_" + os.environ.get("SRC_VOLUME_ID") },]
                    )
                    print(response)
                    src_snapshot = response["Snapshot"]
                    if os.environ.get('SUCCESS_NOTIFICATION', "No") == 'Yes':
                        print ("Sending SNS notification for successful snapshot creation")
                        msg = "Snapshot Created Successfully\n\n"
                        msg += "Snapshot ID : " + src_snapshot["SnapshotId"] + "\n"
                        msg += "ResourceARN : " + src_snapshot["ResourceARN"] + "\n"
                        msg += "Snapshot Name : " + src_snapshot["Name"] + "\n"
                        msg += "Snapshot Tags : " + json.dumps(src_snapshot["Tags"]) + "\n"
                        msg += "Snapshot Lifecycle : " + src_snapshot["Lifecycle"]
                        send_sns_notification (msg, 'Success Notification: CreateSnapshot')

                except Exception as e:
                    print("The error is: ", e)
                    errMessage = "Error while creating a snapshot from the Source VolumeId = " + os.environ.get("SRC_VOLUME_ID") + "\n"
                    errMessage += "Error = " + str(e)
                    send_sns_notification (errMessage, 'Error Notification: CreateSnapshot')
                    return "Failure"

                # call the FSx describe snapshot API to confirm created snapshot is in AVAILABLE state
                copy_snapshot = False
                for i in range(1, 10):
                    time.sleep(10)
                    print ("Describe Snapshot - Attempt=" + str(i))
                    ret = fsx_client.describe_snapshots(SnapshotIds=[src_snapshot["SnapshotId"]])
                    print("Snapshot = " + src_snapshot["SnapshotId"] + " is in " + ret["Snapshots"][0]["Lifecycle"] +" state.")
                    if ret["Snapshots"][0]["Lifecycle"] == "AVAILABLE":
                        copy_snapshot = True
                        break

                if copy_snapshot:
                    try:
                        print ("Starting copy snapshot operation ...")
                        options_string = os.environ.get("OPTIONS")
                        options = [] if len(options_string) == 0 else list(options_string.split(","))
                        option_list = []
                        for item in options:
                            option_list.append(item.strip())
                        print ("CopySnapshotAndUpdateVolume Options: ")
                        print (option_list)
                        response = fsx_client.copy_snapshot_and_update_volume(
                                VolumeId=os.environ.get("DEST_VOLUME_ID"),
                                SourceSnapshotARN=src_snapshot["ResourceARN"],
                                Options=option_list,
                                CopyStrategy=os.environ.get("COPY_STRATEGY")
                            )
                        print (response)
                        msg = "Started CopySnapshotAndUpdateVolume operation\n\n"
                        msg += "Destination Volume ID : " + os.environ.get("DEST_VOLUME_ID") + "\n"
                        msg += "Source Snapshot ARN : " + src_snapshot["ResourceARN"] 
                        send_sns_notification (msg, 'Success Notification: CopySnapshotAndUpdateVolume')
                        print (msg)
                        return "Success"

                    except Exception as e:
                        print("The error is: ", e)
                        errMessage = "Error while initiating CopySnapshotAndUpdateVolume operation\n\n"
                        errMessage += "Destination Volume ID = " + os.environ.get("DEST_VOLUME_ID") + "\n"
                        errMessage += "Source Snapshot ARN = " + src_snapshot["ResourceARN"] + "\n" + str(e)
                        send_sns_notification (errMessage, 'Error Notification: CopySnapshotAndUpdateVolume')

                return "Failure"
      Runtime: python3.11
      Timeout: 600
#
# Stack output section
#
Outputs:
  SNSTopic:
    Description: SNS Topic
    Value: !Ref 'SNSTopic'
  EventBridgeRule:
    Description: EventBridge Rule
    Value: !GetAtt FSxEventBridgeRule.Arn
  LambdaExecutionRole:
    Description: Lambda Execution Role
    Value: !GetAtt SnapshotLambdaRole.Arn
  LambdaFunction:
    Description: Lambda Function
    Value: !GetAtt SnapshotLambda.Arn

