---
AWSTemplateFormatVersion: 2010-09-09
Description: 'Master manifest for ADP Visual Attributes usecase'
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: General configuration
        Parameters:
          - ADPS3BucketName
          - ADPS3BucketRegion
          - BasePlatformVersion
          - UseCaseVersion
          - DnsZoneType
          - EnableExternalAccess
          - PytorchModelsBucket
      - Label:
          default: Application configuration
        Parameters:
          - DockerVAImage
          - DockerRegistry
      - Label:
          default: Notebook-Instance configuration
        Parameters:
          - NotebookInstanceName
          - GitHubRepository
          - NotebookInstanceType
          - MlflowSetExperiment
          - MlflowServiceImageRepository

Parameters:
  ADPS3BucketName:
    Type: String
    AllowedPattern: ^[0-9a-z]+([0-9a-z-]*[0-9a-z])*$
    ConstraintDescription:
      'Bucket name can include numbers, lowercase letters, and hyphens (-).
       It cannot start or end with a hyphen (-).'
    Description:
      'ADP bucket name with cloudformation scripts can include numbers,
      lowercase letters, and hyphens (-). It cannot start or end with a hyphen (-).'
    Default: 'griddynamics-analytical-data-platform-releases'
  ADPS3BucketRegion:
    Type: String
    Description: 'The AWS Region where the ADP S3 bucket (ADPS3BucketName) is hosted.'
    Default: 'us-east-1'
  BasePlatformVersion:
    Type: String
    Description: 'BasePlatform build version.'
    Default: '2023-02-16-marketplace'
  UseCaseVersion:
    Type: String
    Description: 'UseCase build version.'
    Default: '2023-02-16-marketplace'
  DockerVAImage:
    Type: String
    Description: 'Docker image name for Visual Attributes application.'
    Default: 'griddynamics/adp-visual-attributes'
  DockerRegistry:
    Type: String
    Description: 'Docker registry.'
    Default: '125667932402.dkr.ecr.us-east-1.amazonaws.com'
  EnableExternalAccess:
    Type: String
    Description:
      'Allow or deny access to UI from outside of VPC (disables ELB for EKS-based applications).
      Disabled by default to minimize unauthorized access.'
    AllowedValues: [ "Enabled", "Disabled" ]
    Default: 'Disabled'
  DnsZoneType:
    Type: String
    Description:
      'Should match with ADP Base Platform settings. Choose public if you already delegated responsibility for the subdomain to Route 53. This subdomain
      should be configured in Route53HostedZoneName. Otherwise choose a private DNS zone.'
    AllowedValues: [ "public", "private" ]
    Default: 'private'
  NotebookInstanceType:
    Description: 'The EC2 instance type for the data lake Amazon SageMaker Notebook instance.'
    ConstraintDescription: 'Must select a valid Notebook instance type'
    Type: String
    AllowedValues:
      - ml.t2.medium
      - ml.t2.large
      - ml.t2.xlarge
      - ml.t3.medium
      - ml.t3.large
    Default: 'ml.t3.large'
  GitHubRepository:
    Description: '[OPTIONAL] The Git repository associated with the notebook instance as its default code repository'
    Type: String
  PytorchModelsBucket:
    Description: 'Path to Visual Attributes PyTorch Models bucket'
    Type: String
    Default: 'adp-rnd-ml-models/visual-attribution/pytorch-models-casted'
  MlflowSetExperiment:
    Description: 'Name of the Experiment in MLflow'
    Type: String
    Default: 'adp-va-mlflow'
  NotebookInstanceName:
    Type: String
    Description: 'Name of the Amazon SageMaker Notebook instance'
    Default: 'adp-visual-attributes-instance'
  MlflowServiceImageRepository:
    Type: String
    Default: 'griddynamics/adp-mlflow-churn-predictions'

Conditions:
  RunningInAnotherRegion: !Not [!Equals [!Ref ADPS3BucketRegion, !Ref "AWS::Region"]]
  EnableExternalAccessCondition: !And
    - !Equals [!Ref DnsZoneType, 'private']
    - !Equals [!Ref EnableExternalAccess, 'Enabled']

Resources:
  BasePlatformUtils:
    Type: AWS::CloudFormation::Stack
    Properties:
      Tags:
        - Key: 'Name'
          Value: 'BasePlatformUtils'
        - Key: 'Owner'
          Value: !ImportValue ADPPlatformOwner
        - Key: 'Project'
          Value: 'ADP'
      TemplateURL: !Sub 'https://${ADPS3BucketName}.s3.${ADPS3BucketRegion}.${AWS::URLSuffix}/platform/${BasePlatformVersion}/cloudformation/l0/base/utils.yaml'
      Parameters:
        ResourcePrefix: !ImportValue ADPResourcePrefix
        ADPS3BucketName: !Sub
          - '${BucketName}'
          - BucketName: !If [RunningInAnotherRegion, !ImportValue ADPS3BucketAnotherRegion, !Ref ADPS3BucketName]
        BasePlatformVersion: !Ref BasePlatformVersion

  BasePlatformInputs:
    DependsOn: BasePlatformUtils
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt BasePlatformUtils.Outputs.BasePlatformInputsLambdaArn
      BpStateBucket: !Sub
        - "${ResourcePrefix}-platform-state-${AWS::AccountId}"
        - ResourcePrefix: !ImportValue ADPResourcePrefix

  BasePlatformOutputs:
    DependsOn: BasePlatformInputs
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt BasePlatformUtils.Outputs.BasePlatformOutputsLambdaArn
      BpStateBucket: !Sub
        - "${ResourcePrefix}-platform-state-${AWS::AccountId}"
        - ResourcePrefix: !ImportValue ADPResourcePrefix

  BasePlatformCloudMapOutputs:
    DependsOn: BasePlatformOutputs
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt BasePlatformUtils.Outputs.BasePlatformOutputsLambdaArn
      StackName: "CloudMapStack"
      BpStateBucket: !Sub
        - "${ResourcePrefix}-platform-state-${AWS::AccountId}"
        - ResourcePrefix: !ImportValue ADPResourcePrefix

  BasePlatformVPCOutputs:
    DependsOn: BasePlatformCloudMapOutputs
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt BasePlatformUtils.Outputs.BasePlatformOutputsLambdaArn
      StackName: "VPCStack"
      BpStateBucket: !Sub
        - "${ResourcePrefix}-platform-state-${AWS::AccountId}"
        - ResourcePrefix: !ImportValue ADPResourcePrefix

  BasePlatformKubernetes:
    DependsOn: BasePlatformCloudMapOutputs
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt BasePlatformUtils.Outputs.BasePlatformLambdaOperateArn
      BpStateBucket: !GetAtt BasePlatformUtils.Outputs.BasePlatformStateBucketName
      BpGdComponent: "visual-attributes"
      BpAwsComponent: "Kubernetes"
      BpReferenceComponentName: "KubernetesStack"

  BasePlatformKubernetesExtra:
    DependsOn: BasePlatformKubernetes
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt BasePlatformUtils.Outputs.BasePlatformLambdaOperateArn
      BpStateBucket: !GetAtt BasePlatformUtils.Outputs.BasePlatformStateBucketName
      BpGdComponent: "visual-attributes"
      BpAwsComponent: "KubernetesExtra"
      BpReferenceComponentName: "KubernetesExtraStack"

  LambdaSageMakerExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      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"
        - "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
      Policies:
      - PolicyName: root
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
              - "sagemaker:InvokeEndpoint*"
              - "sagemaker:DescribeEndpoint*"
              - "sagemaker:DeleteModel"
              - "sagemaker:DescribeModel"
              - "sagemaker:DeleteEndpoint*"
              - "sagemaker:UpdateEndpoint"
            Resource:
              - !Sub "arn:aws:sagemaker:${AWS::Region}:${AWS::AccountId}:endpoint/va-*"
              - !Sub "arn:aws:sagemaker:${AWS::Region}:${AWS::AccountId}:endpoint-config/va-*"
              - !Sub "arn:aws:sagemaker:${AWS::Region}:${AWS::AccountId}:model/va-*"
          - Effect: Allow
            Action:
              - "sagemaker:ListEndpoints"
              - "ec2:CreateNetworkInterface"
              - "ec2:DescribeNetworkInterfaces"
              - "ec2:DeleteNetworkInterface"
            Resource: "*"
          - Effect: Allow
            Action:
              - 's3:DeleteObjectTagging'
              - 's3:GetJobTagging'
              - 's3:DeleteObjectVersion'
              - 's3:GetObjectVersionTagging'
              - 's3:ListBucketVersions'
              - 's3:ListJobs'
              - 's3:ListBucket'
              - 's3:DeleteObjectVersionTagging'
              - 's3:DeleteBucketPolicy'
              - 's3:GetObject'
              - 's3:DeleteJobTagging'
              - 's3:ListAllMyBuckets'
              - 's3:GetObjectTagging'
              - 's3:DeleteObject'
            Resource:
              - !Sub 'arn:${AWS::Partition}:s3:::mlflow-sagemaker-${AWS::Region}-${AWS::AccountId}'
              - !Sub 'arn:${AWS::Partition}:s3:::mlflow-sagemaker-${AWS::Region}-${AWS::AccountId}/*'

  BasePlatformMlflow:
    DependsOn: BasePlatformKubernetesExtra
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt BasePlatformUtils.Outputs.BasePlatformLambdaOperateArn
      BpStateBucket: !GetAtt BasePlatformUtils.Outputs.BasePlatformStateBucketName
      BpGdComponent: "visual-attributes"
      BpAwsComponent: "Mlflow"
      BpReferenceComponentName: "MlflowStack"

  SageMakerNotebook:
    DependsOn: BasePlatformMlflow
    Type: AWS::CloudFormation::Stack
    Properties:
      Tags:
        - Key: 'Name'
          Value: 'SageMakerNotebook'
        - Key: 'Owner'
          Value: !ImportValue ADPPlatformOwner
        - Key: 'Project'
          Value: 'ADP'
      TemplateURL: !Sub 'https://${ADPS3BucketName}.s3.${ADPS3BucketRegion}.${AWS::URLSuffix}/platform/${BasePlatformVersion}/cloudformation/l1/ml/main.yaml'
      Parameters:
        DownloadFile: !Sub 'applications/adp/${UseCaseVersion}/notebooks/visual-attributes.ipynb'
        NotebookS3BucketName: !Ref ADPS3BucketName
        NotebookInstanceName: !Ref NotebookInstanceName
        SubnetID: !GetAtt BasePlatformVPCOutputs.PrivateSubnet1AID
        VPCID: !GetAtt BasePlatformVPCOutputs.VPCID
        NotebookInstanceType: !Ref NotebookInstanceType
        MLEngineerGroupName: !GetAtt BasePlatformOutputs.MLEngineerGroupName
        DevOpsEngineerGroupName: !GetAtt BasePlatformOutputs.DevOpsEngineerGroupName
        GitHubRepository: !Ref GitHubRepository
        MlflowTrackingUri: !GetAtt BasePlatformMlflow.MlflowFQDN
        MlflowSetExperiment: !Ref MlflowSetExperiment
        ResourcePrefix: !ImportValue ADPResourcePrefix
        MlflowServiceImageRepository: !Ref MlflowServiceImageRepository

  SMLambdaSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Dummy SG for lambda function
      VpcId: !GetAtt BasePlatformVPCOutputs.VPCID

  SagemakerCreateEPLambda:
    DependsOn: LambdaSageMakerExecutionRole
    Type: "AWS::Lambda::Function"
    Properties:
      Description: Lambda function to update SageMaker Endpoints
      MemorySize: 1024
      Role: !GetAtt [ LambdaSageMakerExecutionRole, Arn ]
      Timeout: 900
      PackageType: Image
      VpcConfig:
        SecurityGroupIds: [!Ref SMLambdaSecurityGroup]
        SubnetIds:
          - !GetAtt BasePlatformVPCOutputs.PrivateSubnet1AID
          - !GetAtt BasePlatformVPCOutputs.PrivateSubnet2AID
          - !GetAtt BasePlatformVPCOutputs.PrivateSubnet3AID
      Code:
        ImageUri: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/griddynamics/vainit-lambda:${BasePlatformVersion}"

  # Use-Case stack to hide custom resources from validator.
  VisualAttributes:
    DependsOn: SageMakerNotebook
    Type: AWS::CloudFormation::Stack
    Properties:
      Tags:
        - Key: 'Name'
          Value: 'Visual-Attributes'
        - Key: 'Owner'
          Value: !ImportValue ADPPlatformOwner
        - Key: 'Project'
          Value: 'ADP'
      TemplateURL: !Sub 'https://${ADPS3BucketName}.s3.${ADPS3BucketRegion}.${AWS::URLSuffix}/platform/${BasePlatformVersion}/cloudformation/l2/case-visual-attributes-eks.yaml'
      Parameters:
        ResourcePrefix: !ImportValue ADPResourcePrefix
        ADPS3BucketName: !Ref ADPS3BucketName
        UseCaseVersion: !Ref UseCaseVersion
        DockerRegistry: !Ref DockerRegistry
        DockerVAImage: !Ref DockerVAImage
        KubeManifestLambdaArn: !GetAtt BasePlatformKubernetes.KubeManifestLambdaArn
        KubeGetLambdaArn: !GetAtt BasePlatformKubernetes.KubeGetLambdaArn
        EKSClusterName: !GetAtt BasePlatformKubernetes.EKSClusterName
        ClusterOID: !GetAtt BasePlatformKubernetes.ClusterOID
        CloudMapNamespaceId: !GetAtt BasePlatformCloudMapOutputs.CloudMapNamespaceId
        Route53HostedZoneName: !GetAtt BasePlatformInputs.Route53HostedZoneName
        ModelPath: !Ref PytorchModelsBucket
        MlflowSetExperiment: !Ref MlflowSetExperiment
        MlflowTrackingUri: !GetAtt BasePlatformMlflow.MlflowFQDN
        MLflowBucket: !GetAtt BasePlatformMlflow.MlflowArtifactS3Bucket
        CreateLambdaArn: !GetAtt [SagemakerCreateEPLambda, Arn]
        MlflowServiceImageRepository: !Ref MlflowServiceImageRepository
        RemoteAccessCIDR: !ImportValue ADPRemoteAccessCIDR
        DnsZoneType: !Ref DnsZoneType
        EnableExternalAccess: !Ref EnableExternalAccess

Outputs:
  VisualAttributesELB:
    Condition: EnableExternalAccessCondition
    Value: !GetAtt VisualAttributes.Outputs.VisualAttributesELB
    Description: Visual Attributes service ELB address (will not work if external access is disabled)
  VisualAttributesFQDN:
    Value: !GetAtt VisualAttributes.Outputs.VisualAttributesFQDN
    Description: Visual Attributes service DNS address (private zone will not be accessible form outside)
