# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

AWSTemplateFormatVersion: "2010-09-09"
Description: |
  Initialiser template used to bring up the install ServiceCatalog-Puppet
  {"version": "0.250.0", "framework": "servicecatalog-puppet", "role": "initialiser"}

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "General"
        Parameters:
          - EnabledRegions
          - AWSSTSRegionalEndPoints
          - ShouldCollectCloudformationEvents
          - ShouldForwardEventsToEventbridge
          - ShouldForwardFailuresToOpscenter
          - CodeBuildEnvironmentImage
      - Label:
          default: "Options"
        Parameters:
          - IsCachingEnabled
          - ShouldValidate
          - PollForSourceChanges
          - ShouldDeleteRollbackCompleteStacks
      - Label:
          default: "Source Code Management - choose which one of the following sources you will use:"
        Parameters:
          - SCMSourceProvider
      - Label:
          default: "SCM Option 1: CodeCommit"
        Parameters:
          - SCMRepositoryName
          - SCMBranchName
      - Label:
          default: "SCM Option 2: CodeStarSourceConnection"
        Parameters:
          - SCMConnectionArn
          - SCMFullRepositoryId
          - SCMBranchName
      - Label:
          default: "SCM Option 3: S3"
        Parameters:
          - SCMBucketName
          - SCMObjectKey
          - SCMShouldCreateRepo
      - Label:
          default: "SCM Option 4: Custom"
        Parameters:
          - SCMCustomSourceActionGitUrl
          - SCMCustomSourceActionGitWebHookIPAddress
          - SCMCustomSourceActionCustomActionTypeVersion
          - SCMCustomSourceActionCustomActionTypeProvider
      - Label:
          default: "Permissions"
        Parameters:
          - PuppetCodePipelineRolePermissionBoundary
          - SourceRolePermissionsBoundary
          - PuppetGenerateRolePermissionBoundary
          - PuppetDeployRolePermissionBoundary
          - PuppetProvisioningRolePermissionsBoundary
          - CloudFormationDeployRolePermissionsBoundary
          - PuppetRolePermissionBoundary
      - Label:
          default: "ServiceCatalog"
        Parameters:
          - ShouldShareTagOptions
          - ShouldSharePrincipals
      - Label:
          default: "Stacks"
        Parameters:
          - ShouldUseStacksServiceRole
      - Label:
          default: "Workflow"
        Parameters:
          - DeployEnvironmentComputeType
          - SpokeDeployEnvironmentComputeType
          - DeployNumWorkers
          - PuppetRoleName
          - PuppetRolePath
      - Label:
          default: "Advanced (Do not change unless told to do so)"
        Parameters:
          - Version


Parameters:
  CodeBuildEnvironmentImage:
    Type: String
    Default: "aws/codebuild/standard:7.0"
  EnabledRegions:
    Type: String
    Description: |
      Space or comma seperated list of AWS Regions for which your puppet should operate in
  ShouldCollectCloudformationEvents:
    Type: String
    AllowedValues:
      - True
      - False
    Default: False
  ShouldForwardEventsToEventbridge:
    Type: String
    AllowedValues:
      - True
      - False
    Default: False
  ShouldForwardFailuresToOpscenter:
    Type: String
    AllowedValues:
      - True
      - False
    Default: True
  ShouldShareTagOptions:
    Type: String
    Description: |
      What value should ShareTagOptions be when creating portfolio shares with AWS Service Catalog
    AllowedValues:
      - True
      - False
    Default: True
  ShouldSharePrincipals:
    Type: String
    Description: |
      What value should SharePrincipals be when creating portfolio shares with AWS Service Catalog
    AllowedValues:
      - True
      - False
    Default: True
  AWSSTSRegionalEndPoints:
    Type: String
    Description: |
      This setting specifies how the SDK determines the AWS service endpoint that it uses to talk to the AWS Security Token Service (AWS STS).
    AllowedValues:
      - legacy
      - regional
    Default: legacy
  ShouldUseStacksServiceRole:
    Type: String
    Description: |
      Setting this to True will ensure stacks in your manifest use a service role (PuppetStackRole) instead of the PuppetRole
    AllowedValues:
      - True
      - False
    Default: True
  PuppetCodePipelineRolePermissionBoundary:
    Type: String
    Description: IAM Permission Boundary to apply to the PuppetCodePipelineRole
    Default: "arn:aws:iam::aws:policy/AdministratorAccess"
  SourceRolePermissionsBoundary:
    Type: String
    Description: IAM Permission Boundary to apply to the SourceRole
    Default: "arn:aws:iam::aws:policy/AdministratorAccess"
  PuppetGenerateRolePermissionBoundary:
    Type: String
    Description: IAM Permission Boundary to apply to the PuppetGenerateRole
    Default: "arn:aws:iam::aws:policy/AdministratorAccess"
  PuppetDeployRolePermissionBoundary:
    Type: String
    Description: IAM Permission Boundary to apply to the PuppetDeployRole
    Default: "arn:aws:iam::aws:policy/AdministratorAccess"
  PuppetProvisioningRolePermissionsBoundary:
    Type: String
    Description: IAM Permission Boundary to apply to the PuppetProvisioningRole
    Default: "arn:aws:iam::aws:policy/AdministratorAccess"
  CloudFormationDeployRolePermissionsBoundary:
    Type: String
    Description: IAM Permission Boundary to apply to the CloudFormationDeployRole
    Default: "arn:aws:iam::aws:policy/AdministratorAccess"
  PuppetRolePermissionBoundary:
    Type: String
    Description: IAM Permission Boundary to apply to the PuppetRole
    Default: "arn:aws:iam::aws:policy/AdministratorAccess"
  DeployEnvironmentComputeType:
    Type: String
    Description: The AWS CodeBuild Environment Compute Type
    Default: BUILD_GENERAL1_SMALL
  SpokeDeployEnvironmentComputeType:
    Type: String
    Description: The AWS CodeBuild Environment Compute Type to use in spokes with spoke execution mode
    Default: BUILD_GENERAL1_SMALL
  DeployNumWorkers:
    Type: Number
    Description: Number of workers that should be used when running a deploy
    Default: 10
  PuppetRoleName:
    Type: String
    Default: PuppetRole
  PuppetRolePath:
    Type: String
    Default: /servicecatalog-puppet/
  SCMSourceProvider:
    Type: String
    AllowedValues:
      - CodeCommit
      - S3
      - CodeStarSourceConnection
      - Custom
    Description: For Github.com, GitHub Enterprise and BitBucket Cloud support use CodeStarSourceConnection
    Default: CodeCommit
  SCMRepositoryName:
    Type: String
    Default: ServiceCatalogPuppet
  SCMBranchName:
    Type: String
    Default: main
    Description: The branch name to use for the service-catalog-puppet-pipeline
  SCMConnectionArn:
    Type: String
    Description: The Arn of the CodeStar Connection already created in the account
  SCMFullRepositoryId:
    Type: String
    Description: The full repository id for the git repo to use.  This varies based on which git provider you use
  SCMBucketName:
    Type: String
  SCMObjectKey:
    Type: String
  SCMCustomSourceActionGitUrl:
    Type: String
  SCMCustomSourceActionGitWebHookIPAddress:
    Type: String
  SCMCustomSourceActionCustomActionTypeVersion:
    Type: String
    Default: CustomVersion1
  SCMCustomSourceActionCustomActionTypeProvider:
    Type: String
    Default: CustomProvider1
  SCMShouldCreateRepo:
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "true"
    Description: "Setting this to True means the tool creates the repo and manages it. Changing from True to False will cause already created repos to be deleted."
  ShouldValidate:
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "true"
    Description: "Setting this to true adds a validate stage to the pipeline where the manifest file validated before the deploy occurs."
  IsCachingEnabled:
    Type: String
    AllowedValues:
      - "True"
      - "False"
    Default: "False"
    Description: "Setting this to true enables the caching of some workflow tasks, reducing overall execution times."
  PollForSourceChanges:
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "true"
    Description: "Should the main pipeline poll SCM for changes"
  ShouldDeleteRollbackCompleteStacks:
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "true"
    Description: "Setting this to true will tell the framework to delete stacks in ROLLBACK_COMPLETE status for you."
  Version:
    Type: String
    Default: aws-service-catalog-puppet


Resources:
  InitialiserVersionParam:
    Type: AWS::SSM::Parameter
    Properties:
      Name: service-catalog-puppet-initialiser-version
      Type: String
      Value: '0.103.1--2024-11-02 09:22:36.018679' #InitialiserVersion
      Tags:
        "ServiceCatalogPuppet:Actor": "Framework"

  InitialiserStackNameParam:
    Type: AWS::SSM::Parameter
    Properties:
      Name: service-catalog-puppet-initialiser-stack-name
      Type: String
      Value: !Ref AWS::StackName
      Tags:
        "ServiceCatalogPuppet:Actor": "Framework"

  InitialiserRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: PuppetInitialiser
      Path: /servicecatalog-puppet/
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "codebuild.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - !Sub arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess

  InitialiserProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: servicecatalog-product-puppet-initialiser
      Description: "Initialiser for the framework"
      ServiceRole: !GetAtt InitialiserRole.Arn
      Artifacts:
        Type: NO_ARTIFACTS
      Environment:
        Type: linuxContainer
        ComputeType: BUILD_GENERAL1_SMALL
        Image: !Ref CodeBuildEnvironmentImage
        EnvironmentVariables:
          - Name: VERSION
            Type: PLAINTEXT
            Value: !Ref Version
          - Name: ENABLED_REGIONS
            Type: PLAINTEXT
            Value: !Join [",", !Split [", ", !Ref EnabledRegions]]
          - Name: SHOULD_COLLECT_CLOUDFORMATION_EVENTS
            Type: PLAINTEXT
            Value: !Ref ShouldCollectCloudformationEvents
          - Name: SHOULD_FORWARD_EVENTS_TO_EVENTBRIDGE
            Type: PLAINTEXT
            Value: !Ref ShouldForwardEventsToEventbridge
          - Name: SHOULD_FORWARD_FAILURES_TO_OPSCENTER
            Type: PLAINTEXT
            Value: !Ref ShouldForwardFailuresToOpscenter
          - Name: SHOULD_USE_STACKS_SERVICE_ROLE
            Type: PLAINTEXT
            Value: !Ref ShouldUseStacksServiceRole
          - Name: PUPPET_ACCOUNT_ID
            Type: PLAINTEXT
            Value: !Ref AWS::AccountId
          - Name: PUPPET_CODE_PIPELINE_ROLE_PERMISSION_BOUNDARY
            Type: PLAINTEXT
            Value: !Ref PuppetCodePipelineRolePermissionBoundary
          - Name: SOURCE_ROLE_PERMISSIONS_BOUNDARY
            Type: PLAINTEXT
            Value: !Ref SourceRolePermissionsBoundary
          - Name: PUPPET_GENERATE_ROLE_PERMISSION_BOUNDARY
            Type: PLAINTEXT
            Value: !Ref PuppetGenerateRolePermissionBoundary
          - Name: PUPPET_DEPLOY_ROLE_PERMISSION_BOUNDARY
            Type: PLAINTEXT
            Value: !Ref PuppetDeployRolePermissionBoundary
          - Name: PUPPET_PROVISIONING_ROLE_PERMISSIONS_BOUNDARY
            Type: PLAINTEXT
            Value: !Ref PuppetProvisioningRolePermissionsBoundary
          - Name: CLOUD_FORMATION_DEPLOY_ROLE_PERMISSIONS_BOUNDARY
            Type: PLAINTEXT
            Value: !Ref CloudFormationDeployRolePermissionsBoundary
          - Name: PUPPET_ROLE_PERMISSION_BOUNDARY
            Type: PLAINTEXT
            Value: !Ref PuppetRolePermissionBoundary
          - Name: DEPLOY_ENVIRONMENT_COMPUTE_TYPE
            Type: PLAINTEXT
            Value: !Ref DeployEnvironmentComputeType
          - Name: SPOKE_DEPLOY_ENVIRONMENT_COMPUTE_TYPE
            Type: PLAINTEXT
            Value: !Ref SpokeDeployEnvironmentComputeType
          - Name: DEPLOY_NUM_WORKERS
            Type: PLAINTEXT
            Value: !Ref DeployNumWorkers
          - Name: PARTITION
            Type: PLAINTEXT
            Value: !Ref AWS::Partition
          - Name: PUPPET_ROLE_NAME
            Type: PLAINTEXT
            Value: !Ref PuppetRoleName
          - Name: PUPPET_ROLE_PATH
            Type: PLAINTEXT
            Value: !Ref PuppetRolePath
          - Name: SCM_SOURCE_PROVIDER
            Type: PLAINTEXT
            Value: !Ref SCMSourceProvider
          - Name: SCM_REPOSITORY_NAME
            Type: PLAINTEXT
            Value: !Ref SCMRepositoryName
          - Name: SCM_BRANCH_NAME
            Type: PLAINTEXT
            Value: !Ref SCMBranchName
          - Name: SCM_CONNECTION_ARN
            Type: PLAINTEXT
            Value: !Ref SCMConnectionArn
          - Name: SCM_FULL_REPOSITORY_ID
            Type: PLAINTEXT
            Value: !Ref SCMFullRepositoryId
          - Name: SCM_BUCKET_NAME
            Type: PLAINTEXT
            Value: !Ref SCMBucketName
          - Name: SCM_OBJECT_KEY
            Type: PLAINTEXT
            Value: !Ref SCMObjectKey
          - Name: SCM_CUSTOM_SOURCE_ACTION_GIT_URL
            Type: PLAINTEXT
            Value: !Ref SCMCustomSourceActionGitUrl
          - Name: SCM_CUSTOM_SOURCE_ACTION_GIT_WEB_HOOK_IP_ADDRESS
            Type: PLAINTEXT
            Value: !Ref SCMCustomSourceActionGitWebHookIPAddress
          - Name: SCM_CUSTOM_SOURCE_ACTION_CUSTOM_ACTION_TYPE_VERSION
            Type: PLAINTEXT
            Value: !Ref SCMCustomSourceActionCustomActionTypeVersion
          - Name: SCM_CUSTOM_SOURCE_ACTION_CUSTOM_ACTION_TYPE_PROVIDER
            Type: PLAINTEXT
            Value: !Ref SCMCustomSourceActionCustomActionTypeProvider
          - Name: SCM_SHOULD_CREATE_REPO
            Type: PLAINTEXT
            Value: !Ref SCMShouldCreateRepo
          - Name: SCT_SHOULD_VALIDATE
            Type: PLAINTEXT
            Value: !Ref ShouldValidate
          - Name: IS_CACHING_ENABLED
            Type: PLAINTEXT
            Value: !Ref IsCachingEnabled
          - Name: SCT_POLL_FOR_SOURCE_CHANGES
            Type: PLAINTEXT
            Value: !Ref PollForSourceChanges
          - Name: SHOULD_DELETE_ROLLBACK_COMPLETE_STACKS
            Type: PLAINTEXT
            Value: !Ref ShouldDeleteRollbackCompleteStacks
          - Name: SHOULD_SHARE_TAG_OPTIONS
            Type: PLAINTEXT
            Value: !Ref ShouldShareTagOptions
          - Name: SHOULD_SHARE_PRINCIPALS
            Type: PLAINTEXT
            Value: !Ref ShouldSharePrincipals
          - Name: AWS_STS_REGIONAL_ENDPOINTS
            Type: PLAINTEXT
            Value: !Ref AWSSTSRegionalEndPoints
      Source:
        Type: NO_SOURCE
        BuildSpec: |
          version: 0.2
          phases:
            install:
              runtime-versions:
                python: 3.11
              commands:
                - pip install ${VERSION}
                - servicecatalog-puppet --info set-regions ${ENABLED_REGIONS}
                - servicecatalog-puppet set-config-value should_collect_cloudformation_events ${SHOULD_COLLECT_CLOUDFORMATION_EVENTS}
                - servicecatalog-puppet set-config-value should_forward_events_to_eventbridge ${SHOULD_FORWARD_EVENTS_TO_EVENTBRIDGE}
                - servicecatalog-puppet set-config-value should_forward_failures_to_opscenter ${SHOULD_FORWARD_FAILURES_TO_OPSCENTER}
                - servicecatalog-puppet set-config-value should_use_stacks_service_role ${SHOULD_USE_STACKS_SERVICE_ROLE}
                - servicecatalog-puppet set-config-value should_delete_rollback_complete_stacks ${SHOULD_DELETE_ROLLBACK_COMPLETE_STACKS}
                - servicecatalog-puppet set-config-value is_caching_enabled ${IS_CACHING_ENABLED}
                - servicecatalog-puppet set-config-value spoke_deploy_environment_compute_type ${SPOKE_DEPLOY_ENVIRONMENT_COMPUTE_TYPE}
                - servicecatalog-puppet set-config-value global_share_tag_options_default ${SHOULD_SHARE_TAG_OPTIONS}
                - servicecatalog-puppet set-config-value global_share_principals_default ${SHOULD_SHARE_PRINCIPALS}
                - servicecatalog-puppet set-config-value aws_sts_regional_endpoints ${AWS_STS_REGIONAL_ENDPOINTS}
                - servicecatalog-puppet bootstrap-spoke ${PUPPET_ACCOUNT_ID} --permission-boundary ${PUPPET_ROLE_PERMISSION_BOUNDARY} --puppet-role-name ${PUPPET_ROLE_NAME} --puppet-role-path ${PUPPET_ROLE_PATH}
            build:
              commands:
                - servicecatalog-puppet --info bootstrap

      TimeoutInMinutes: 60

  StartInstallRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: StartPuppetInstallRole
      Path: /servicecatalog-puppet/
      ManagedPolicyArns:
        - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - !Sub arn:${AWS::Partition}:iam::aws:policy/AWSCodeBuildDeveloperAccess
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
            Action:
              - "sts:AssumeRole"

  StartInstallLambda:
    Type: AWS::Lambda::Function
    Properties:
      Role: !GetAtt StartInstallRole.Arn
      Handler: "index.handler"
      Description: Lambda for starting Puppet CodeBuild Job
      Runtime: python3.11
      Timeout: 900
      Code:
        ZipFile: |
          import boto3
          import json
          import logging
          import time
          from urllib.request import Request, urlopen

          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          def handler(event, context):
              request_type = event['RequestType']
              codebuild_projectname = event.get('ResourceProperties').get('ProjectName')

              try:
                  logger.info(request_type)
                  if request_type in ['Create', 'Update']:
                      codebuild_client = boto3.client('codebuild')
                      logger.info("Starting CodeBuild Job: {}".format(codebuild_projectname))
                      codebuild_response = codebuild_client.start_build(
                          projectName = codebuild_projectname
                      )
                      build_id = codebuild_response.get('build').get('id')
                      build_status = 'IN_PROGRESS'
                      while build_status == 'IN_PROGRESS':
                          time.sleep(5)
                          build = codebuild_client.batch_get_builds(ids=[build_id]).get('builds')[0]
                          build_status = build.get('buildStatus')
                      if build_status != 'SUCCEEDED':
                          raise Exception(f"Build did not SUCCEED. Status: {build_status}")
                      send_response(event, context, "SUCCESS",
                                    {"Message": "Updated"})

                  elif request_type == 'Delete':
                      send_response(event, context, "SUCCESS",
                                    {"Message": "Deleted"})

                  else:
                      send_response(event, context, "FAILED",
                                    {"Message": "Unexpected"})

              except Exception as ex:
                  logger.error(ex)
                  send_response(event, context,"FAILED",
                                    {"Message": "Exception"})

          def send_response(e, c, rs, rd):
              r = json.dumps({
                  "Status": rs,
                  "Reason": "CloudWatch Log Stream: " + c.log_stream_name,
                  "PhysicalResourceId": c.log_stream_name,
                  "StackId": e['StackId'],
                  "RequestId": e['RequestId'],
                  "LogicalResourceId": e['LogicalResourceId'],
                  "Data": rd
              })
              d = str.encode(r)
              h = {
                  'content-type': '',
                  'content-length': str(len(d))
              }
              req = Request(e['ResponseURL'], data=d, method='PUT', headers=h)
              r = urlopen(req)
              logger.info("Status message: {} {}".format(r.msg, r.getcode()))

  StartInstall:
    Type: Custom::CustomResource
    DependsOn: InitialiserProject
    Properties:
      ServiceToken: !GetAtt StartInstallLambda.Arn
      ProjectName: !Ref InitialiserProject
      UpdateNonce: !Join
        - ","
        - - !Ref EnabledRegions
          - !Ref ShouldCollectCloudformationEvents
          - !Ref ShouldForwardEventsToEventbridge
          - !Ref ShouldForwardFailuresToOpscenter
          - !Ref SCMSourceProvider
          - !Ref SCMRepositoryName
          - !Ref SCMBranchName
          - !Ref SCMShouldCreateRepo
          - !Ref SCMConnectionArn
          - !Ref SCMFullRepositoryId
          - !Ref SCMBucketName
          - !Ref SCMObjectKey
          - !Ref PuppetCodePipelineRolePermissionBoundary
          - !Ref SourceRolePermissionsBoundary
          - !Ref PuppetGenerateRolePermissionBoundary
          - !Ref PuppetDeployRolePermissionBoundary
          - !Ref PuppetProvisioningRolePermissionsBoundary
          - !Ref CloudFormationDeployRolePermissionsBoundary
          - !Ref PuppetRolePermissionBoundary
          - !Ref DeployEnvironmentComputeType
          - !Ref SpokeDeployEnvironmentComputeType
          - !Ref DeployNumWorkers
          - !Ref PuppetRoleName
          - !Ref PuppetRolePath
          - !GetAtt InitialiserVersionParam.Value
          - !Ref Version
          - !Ref SCMCustomSourceActionGitUrl
          - !Ref SCMCustomSourceActionGitWebHookIPAddress
          - !Ref SCMCustomSourceActionCustomActionTypeVersion
          - !Ref SCMCustomSourceActionCustomActionTypeProvider
          - !Ref ShouldValidate
          - !Ref IsCachingEnabled
          - !Ref PollForSourceChanges
          - !Ref ShouldDeleteRollbackCompleteStacks
          - !Ref ShouldShareTagOptions
          - !Ref ShouldSharePrincipals
          - !Ref AWSSTSRegionalEndPoints
Outputs:
  ServiceCatalogPuppetRepoConsoleURL:
    Value: !Sub "https://${AWS::Region}.console.aws.amazon.com/codesuite/codecommit/repositories/ServiceCatalogPuppet/browse"
  ServiceCatalogPuppetRepoCloneURLSSH:
    Value: !Sub "ssh://git-codecommit.${AWS::Region}.amazonaws.com/v1/repos/ServiceCatalogPuppet"
  ServiceCatalogPuppetRepoCloneURLHTTPS:
    Value: !Sub "https://git-codecommit.${AWS::Region}.amazonaws.com/v1/repos/ServiceCatalogPuppet"
  InitialiserVersion:
    Value: !GetAtt InitialiserVersionParam.Value
