AWSTemplateFormatVersion: 2010-09-09
Description: Deploys a Redis Elasticache cluster and supporting resources for running Kion.
Metadata:
  'AWS::CloudFormation::Interface':
    ParameterGroups:
      - Label:
          default: Network Configuration
        Parameters:
          - VpcId
          - Subnets
      - Label:
          default: Cache Configuration
        Parameters:
          - EngineType
          - EngineVersion
      - Label:
          default: Security Configuration
        Parameters:
          - ElasticacheKMSKeyArn
          - ExtraSecurityGroups
      - Label:
          default: Authentication Configuration
        Parameters:
          - UseAuth
          - Username
          - Password
    ParameterLabels:
      VpcId:
        default: VPC
      Subnets:
        default: Subnets
      EngineType:
        default: Cache Engine Type
      EngineVersion:
        default: Cache Engine Version
      ElasticacheKMSKeyArn:
        default: Cache Encryption KMS Key
      ExtraSecurityGroups:
        default: Extra Security Groups
      UseAuth:
        default: Enable Authentication
      Username:
        default: Cache Username
      Password:
        default: Cache Password

Parameters:

  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: The VPC ID in which the Redis cache will be deployed.

  Subnets:
    Type: 'List<AWS::EC2::Subnet::Id>'
    Description: >-
      The subnets in which to deploy the Redis cache. They must belong to the selected VPC and be reachable from the Kion nodes.

  EngineType:
    Type: String
    Description: |
      The cache engine to use for the Redis cache cluster.
    AllowedValues:
      - valkey
      - redis
    Default: valkey

  EngineVersion:
    Type: String
    Description: |
      The version of the cache engine to use. For Valkey, use a format like '8' or '7'. For Redis, use a format like '7.2' or '6.2'.
    Default: '8'

  ElasticacheKMSKeyArn:
    Type: String
    Description: |
      KMS Key ID for encrypting the Redis cache data at rest. Leave blank to use the default AWS-managed key.
    Default: ''

  ExtraSecurityGroups:
    Type: CommaDelimitedList
    Description: |
      Optional list of additional security group IDs to apply to the Elasticache cluster (maximum 3).
    Default: ''

  UseAuth:
    Type: String
    Description: |
      Enable authentication for the cache. If enabled, Username and Password are required.
    AllowedValues:
      - 'true'
      - 'false'
    Default: 'true'

  Username:
    Type: String
    Description: |
      Username for authenticating to the cache. Required if UseAuth is true.
    Default: ''
    NoEcho: false

  Password:
    Type: String
    Description: |
      Password for authenticating to the cache. Required if UseAuth is true.
      Must be 16-128 printable characters.
    Default: ''
    NoEcho: true
    AllowedPattern: '^$|^[!*+\-\.0-9A-Z\[\\\]a-z~]{16,128}$'
    ConstraintDescription: 'Password must be 16-128 characters from the following set: !*+-.0-9A-Z[\]a-z~'

Rules:

  ValidateAuthParams:
    Metadata:
      cfn-lint:
        config:
          ignore_checks:
            - W1030
    RuleCondition: !Equals [!Ref UseAuth, 'true']
    Assertions:
      - Assert: !Not [!Equals [!Ref Username, '']]
        AssertDescription: 'Username is required when UseAuth is true'
      - Assert: !Not [!Equals [!Ref Password, '']]
        AssertDescription: 'Password is required when UseAuth is true'

Conditions:

  UseDefaultKMSKey: !Equals [!Ref ElasticacheKMSKeyArn, '']
  HasExtraSecurityGroups: !Not [!Equals [!Join ['', !Ref ExtraSecurityGroups], '']]
  UseAuthentication: !Equals [!Ref UseAuth, 'true']

Resources:

  ElasticacheSubnetGroup:
    Type: AWS::ElastiCache::SubnetGroup
    Properties:
      Description: 'Kion Redis cache subnet group'
      SubnetIds: !Ref Subnets

  ElasticacheSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VpcId
      GroupName: !Sub '${AWS::StackName}-sg'
      GroupDescription: 'Kion Redis cache security group'
      Tags:
        - Key: Name
          Value: kion-elasticache-sg

  ElasticacheSecurityGroupIngressRule:
    Type: 'AWS::EC2::SecurityGroupIngress'
    Properties:
      GroupId: !GetAtt ElasticacheSecurityGroup.GroupId
      SourceSecurityGroupId: !GetAtt ElasticacheSecurityGroup.GroupId
      IpProtocol: 'tcp'
      FromPort: 6379
      ToPort: 6379

  KionCacheUser:
    Type: AWS::ElastiCache::User
    Metadata:
      cfn-lint:
        config:
          ignore_checks:
            - W1030
    Condition: UseAuthentication
    Properties:
      UserId: !Sub '${AWS::StackName}-user'
      UserName: !Ref Username
      Engine: !Ref EngineType
      AccessString: 'on ~* +@all'
      Passwords:
        - !Ref Password

  KionCacheUserGroup:
    Type: AWS::ElastiCache::UserGroup
    Condition: UseAuthentication
    Properties:
      UserGroupId: !Sub '${AWS::StackName}-usergroup'
      Engine: !Ref EngineType
      UserIds:
        - !Ref KionCacheUser

  RedisCacheCluster:
    Type: AWS::ElastiCache::ServerlessCache
    Properties:
      Description: 'Kion Serverless Cache Cluster'
      Engine: !Ref EngineType
      KmsKeyId: !If
        - UseDefaultKMSKey
        - ''
        - !Ref ElasticacheKMSKeyArn
      MajorEngineVersion: !Ref EngineVersion
      SecurityGroupIds: !If
        - HasExtraSecurityGroups
        - !Split
          - ','
          - !Sub
            - '${MainSG},${ExtraSGs}'
            - MainSG: !GetAtt ElasticacheSecurityGroup.GroupId
              ExtraSGs: !Join [',', !Ref ExtraSecurityGroups]
        - - !GetAtt ElasticacheSecurityGroup.GroupId
      ServerlessCacheName: !Sub '${AWS::StackName}-cache'
      SnapshotRetentionLimit: 3
      SubnetIds: !Ref Subnets
      UserGroupId: !If
        - UseAuthentication
        - !Ref KionCacheUserGroup
        - !Ref 'AWS::NoValue'

Outputs:

  RedisCacheEndpoint:
    Description: |
      DNS endpoint of the Redis cache cluster.
    Value: !Sub '${RedisCacheCluster.Endpoint.Address}:${RedisCacheCluster.Endpoint.Port}'

  RedisCacheSecurityGroup:
    Description: |
      Security group for the Redis cache cluster.
    Value: !GetAtt ElasticacheSecurityGroup.GroupId

  RedisCacheUsername:
    Condition: UseAuthentication
    Description: |
      Username for authenticating to the Redis cache cluster.
    Value: !Ref Username

  RedisCacheUserGroupId:
    Condition: UseAuthentication
    Description: |
      User group ID for the Redis cache cluster.
    Value: !Ref KionCacheUserGroup
