AWSTemplateFormatVersion: "2010-09-09"
Parameters:
  CertificateArn:
    Type: String
    AllowedPattern: '^\S+$'
    Description: ARN of an existing certificate which will be attached to the ALB created by the stack, to serve HTTPS traffic (mandatory).
  JenkinsUsername:
    Type: String
    Default: developer
    AllowedPattern: '^\S+$'
  JenkinsDomain:
    Type: String
    AllowedPattern: '^\S+$'
    Description: Domain for your Jenkins instance e.g. jenkins.tomgregory.com (mandatory).
  HostedZoneId:
    Type: String
    Description: Route 53 hosted zone id in which to register Jenkins (optional).
Description: Provision the required resources for blog post example '5 ways to inject secrets from AWS into Jenkins pipelines'.
  Wait for creation to complete before testing. Jenkins deployed from this template is NOT production ready.
Conditions:
  HasHostedZoneId: !Not [!Equals [!Ref HostedZoneId, '']]
Resources:
  VPCStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: https://tomgregory-cloudformation-examples.s3-eu-west-1.amazonaws.com/simple-vpc.yml
  LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Subnets:
        - !GetAtt VPCStack.Outputs.PublicSubnet1
        - !GetAtt VPCStack.Outputs.PublicSubnet2
      SecurityGroups:
        - !Ref LoadBalancerSecurityGroup
  LoadBalancerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: LoadBalancerSecurityGroup
      GroupDescription: Security group for load balancer
      VpcId: !GetAtt VPCStack.Outputs.VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 8080
          ToPort: 8080
          DestinationSecurityGroupId: !Ref JenkinsSecurityGroup
  LoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      Certificates:
        - CertificateArn: !Ref CertificateArn
      DefaultActions:
        - Type: forward
          ForwardConfig:
            TargetGroups:
              - TargetGroupArn: !Ref JenkinsTargetGroup
      LoadBalancerArn: !Ref LoadBalancer
      Port: 443
      Protocol: HTTPS
  JenkinsTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckPath: /login
      Name: JenkinsTargetGroup
      Port: 8080
      Protocol: HTTP
      TargetType: ip
      VpcId: !GetAtt VPCStack.Outputs.VPC
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 10
  JenkinsSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: JenkinsSecurityGroup
      GroupDescription: Security group for Jenkins
      VpcId: !GetAtt VPCStack.Outputs.VPC
  JenkinsLoadBalancerSecurityGroupIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      IpProtocol: tcp
      FromPort: 8080
      ToPort: 8080
      GroupId: !Ref JenkinsSecurityGroup
      SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup
  ECSCluster:
    Type: AWS::ECS::Cluster
  JenkinsExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
      Policies:
        - PolicyName: secrets-manager-access
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - secretsmanager:GetSecretValue
                Resource:
                  - !Ref JenkinsPasswordSecret
                  - !Ref JenkinsPipelineSecret
        - PolicyName: parameter-store-access
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ssm:GetParameters
                Resource: !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${JenkinsPipelineParameter}'
  JenkinsRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
      Policies:
        - PolicyName: secrets-manager-access
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - secretsmanager:GetSecretValue
                Resource: !Ref JenkinsPipelineSecret
              - Effect: Allow
                Action:
                  - secretsmanager:ListSecrets
                Resource: '*'
        - PolicyName: parameter-store-access
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ssm:GetParameter
                Resource: !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${JenkinsPipelineParameter}'
  JenkinsTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: !Sub jenkins-task
      Cpu: 512
      Memory: 1024
      NetworkMode: awsvpc
      TaskRoleArn: !Ref JenkinsRole
      ExecutionRoleArn: !Ref JenkinsExecutionRole
      RequiresCompatibilities:
        - FARGATE
        - EC2
      ContainerDefinitions:
        - Name: jenkins
          Image: tkgregory/jenkins-ecs-credentials:latest
          PortMappings:
            - ContainerPort: 8080
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref CloudwatchLogsGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: jenkins
          Environment:
            - Name: JENKINS_USERNAME
              Value: !Ref JenkinsUsername
            - Name: SECRET_FROM_ENVIRONMENT_VARIABLE
              Value: 'secret value from an environment variable'
          Secrets:
            - Name: JENKINS_PASSWORD
              ValueFrom: !Ref JenkinsPasswordSecret
            - Name: SECRET_FROM_ENVIRONMENT_VARIABLE_INJECTED_FROM_SECRETS_MANAGER
              ValueFrom: !Ref JenkinsPipelineSecret
            - Name: SECRET_FROM_ENVIRONMENT_VARIABLE_INJECTED_FROM_PARAMETER_STORE
              ValueFrom: !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${JenkinsPipelineParameter}'
  CloudwatchLogsGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Join ['-', [ECSLogGroup, !Ref 'AWS::StackName']]
      RetentionInDays: 14
  JenkinsService:
    Type: AWS::ECS::Service
    DependsOn: LoadBalancerListener
    Properties:
      Cluster: !Ref ECSCluster
      TaskDefinition: !Ref JenkinsTaskDefinition
      DesiredCount: 1
      HealthCheckGracePeriodSeconds: 300
      LaunchType: FARGATE
      PlatformVersion: 1.4.0
      DeploymentConfiguration:
        MinimumHealthyPercent: 0
        MaximumPercent: 100
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          Subnets:
            - !GetAtt VPCStack.Outputs.PublicSubnet1
            - !GetAtt VPCStack.Outputs.PublicSubnet2
          SecurityGroups:
            - !GetAtt JenkinsSecurityGroup.GroupId
      LoadBalancers:
        - ContainerName: jenkins
          ContainerPort: 8080
          TargetGroupArn: !Ref JenkinsTargetGroup
  JenkinsPasswordSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: JenkinsPasswordSecret
      GenerateSecretString:
        PasswordLength: 30
        ExcludeCharacters: '"@/\'
  JenkinsPipelineSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: JenkinsPipelineSecret
      SecretString: secret value from Secrets Manager
      Tags:
        - Key: jenkins:credentials:type
          Value: string
  JenkinsPipelineParameter:
    Type: AWS::SSM::Parameter
    Properties:
      Name: JenkinsPipelineParameter
      Type: String
      Value: secret value from Parameter Store
  RecordSetGroup:
    Type: AWS::Route53::RecordSetGroup
    Condition: HasHostedZoneId
    Properties:
      HostedZoneId: !Ref HostedZoneId
      RecordSets:
        - ResourceRecords:
            - !GetAtt LoadBalancer.DNSName
          TTL: 60
          Type: CNAME
          Name: !Sub ${JenkinsDomain}.
Outputs:
  LoadBalancerDNSName:
    Value: !GetAtt LoadBalancer.DNSName