import * as ec2 from '@aws-cdk/aws-ec2' ; import * as ecs from '@aws-cdk/aws-ecs' ; import * as iam from '@aws-cdk/aws-iam' ; import * as cdk from '@aws-cdk/core' ; import { IEnvironment } from './environment' ; import { EnvironmentCapacityType , ServiceBuild } from './extensions/extension-interfaces' ; import { ServiceDescription } from './service-description' ; import { Construct } from '@aws-cdk/core' ; /**
 * connectToProps will have all the extra parameters which are required for connecting services.
 */ export interface ConnectToProps { /**
     * local_bind_port is the local port that this application should
     * use when calling the upstream service in ECS Consul Mesh Extension
     * Currently, this parameter will only be used in the ECSConsulMeshExtension
     * https://github.com/aws-ia/ecs-consul-mesh-extension
     */ readonly local_bind_port ? : number ; } /**
 * The settings for an ECS Service.
 */ export interface ServiceProps { /**
     * The ServiceDescription used to build the service.
     */ readonly serviceDescription : ServiceDescription ; /**
     * The environment to launch the service in.
     */ readonly environment : IEnvironment ; default readonly taskRole ? : iam . IRole ; default readonly desiredCount ? : number ; default readonly autoScaleTaskCount ? : AutoScalingOptions ; } export interface AutoScalingOptions { default readonly minTaskCount ? : number ; /**
      * The maximum number of tasks when scaling out.
      */ readonly maxTaskCount : number ; /**
     * The target value for CPU utilization across all tasks in the service.
     */ readonly targetCpuUtilization ? : number ; /**
     * The target value for memory utilization across all tasks in the service.
     */ readonly targetMemoryUtilization ? : number ; } /**
 * This Service construct serves as a Builder class for an ECS service. It
 * supports various extensions and keeps track of any mutating state, allowing
 * it to build up an ECS service progressively.
 */ export class Service extends Construct { /**
     * The underlying ECS service that was created.
     */ public ecsService ! : ecs . Ec2Service | ecs . FargateService ; /**
     * The name of the service.
     */ public readonly id : string ; /**
     * The VPC where this service should be placed.
     */ public readonly vpc : ec2 . IVpc ; /**
     * The cluster that is providing capacity for this service.
     * [disable-awslint:ref-via-interface]
     */ public readonly cluster : ecs . ICluster ; /**
     * The capacity type that this service will use.
     * Valid values are EC2 or FARGATE.
     */ public readonly capacityType : EnvironmentCapacityType ; /**
     * The ServiceDescription used to build this service.
     */ public readonly serviceDescription : ServiceDescription ; /**
     * The environment where this service was launched.
     */ public readonly environment : IEnvironment ; /**
     * The scalable attribute representing task count.
     */ public readonly scalableTaskCount ? : ecs . ScalableTaskCount ; /**
     * The flag to track if auto scaling policies have been configured
     * for the service.
     */ private autoScalingPoliciesEnabled : boolean = false ; /**
     * The generated task definition for this service. It is only
     * generated after .prepare() has been executed.
     */ protected taskDefinition ! : ecs . TaskDefinition ; /**
     * The list of URLs associated with this service.
     */ private urls : Record < string , string > = { } ; private readonly scope : cdk . Construct ; constructor ( scope , id , props ) { super ( scope , id ) ; this . scope = scope ; this . id = id ; this . environment = props . environment ; this . vpc = props . environment . vpc ; this . cluster = props . environment . cluster ; this . capacityType = props . environment . capacityType ; this . serviceDescription = props . serviceDescription ; const containerextension = this . serviceDescription . get ( 'service-container' ) ; if ( ! containerextension ) { throw new Error ( `Service '${ this . id }' must have a Container extension` ) ; } for ( const extensions in this . serviceDescription . extensions ) { if ( this . serviceDescription . extensions [ extensions ] ) { this . serviceDescription . extensions [ extensions ] . prehook ( this , this . scope ) ; } } for ( const extensions in this . serviceDescription . extensions ) { if ( this . serviceDescription . extensions [ extensions ] ) { this . serviceDescription . extensions [ extensions ] . addHooks ( ) ; } } let taskDefProps = { cpu : '256' , memory : '512' , taskRole : props . taskRole , compatibility : ecs . Compatibility . EC2_AND_FARGATE , } as ecs . TaskDefinitionProps ; for ( const extensions in this . serviceDescription . extensions ) { if ( this . serviceDescription . extensions [ extensions ] ) { taskDefProps = this . serviceDescription . extensions [ extensions ] . modifyTaskDefinitionProps ( taskDefProps ) ; } } this . taskDefinition = new ecs . TaskDefinition ( this . scope , `${ this . id }-task-definition` , taskDefProps ) ; for ( const extensions in this . serviceDescription . extensions ) { if ( this . serviceDescription . extensions [ extensions ] ) { this . serviceDescription . extensions [ extensions ] . useTaskDefinition ( this . taskDefinition ) ; } } for ( const extensions in this . serviceDescription . extensions ) { if ( this . serviceDescription . extensions [ extensions ] ) { this . serviceDescription . extensions [ extensions ] . resolveContainerDependencies ( ) ; } } let serviceProps = { cluster : this . cluster , taskDefinition : this . taskDefinition , minHealthyPercent : 100 , maxHealthyPercent : 200 , desiredCount : props . desiredCount ?? 1 , } as ServiceBuild ; for ( const extensions in this . serviceDescription . extensions ) { if ( this . serviceDescription . extensions [ extensions ] ) { serviceProps = this . serviceDescription . extensions [ extensions ] . modifyServiceProps ( serviceProps ) ; } } if ( serviceProps . maxHealthyPercent && serviceProps . desiredCount && serviceProps . minHealthyPercent && serviceProps . minHealthyPercent == 100 ) { if ( serviceProps . desiredCount == 1 ) { serviceProps = { ... serviceProps , maxHealthyPercent : Math . max ( 200 , serviceProps . maxHealthyPercent ) , } ; } else if ( serviceProps . desiredCount <= 3 ) { serviceProps = { ... serviceProps , maxHealthyPercent : Math . max ( 150 , serviceProps . maxHealthyPercent ) , } ; } else { serviceProps = { ... serviceProps , maxHealthyPercent : Math . max ( 125 , serviceProps . maxHealthyPercent ) , } ; } } if ( props . autoScaleTaskCount || this . autoScalingPoliciesEnabled ) { serviceProps = { ... serviceProps , desiredCount : undefined , } ; } if ( this . capacityType === EnvironmentCapacityType . EC2 ) { this . ecsService = new ecs . Ec2Service ( this . scope , `${ this . id }-service` , serviceProps ) ; } else if ( this . capacityType === EnvironmentCapacityType . FARGATE ) { this . ecsService = new ecs . FargateService ( this . scope , `${ this . id }-service` , serviceProps ) ; } else { throw new Error ( `Unknown capacity type for service ${ this . id }` ) ; } if ( props . autoScaleTaskCount ) { this . scalableTaskCount = this . ecsService . autoScaleTaskCount ( { maxCapacity : props . autoScaleTaskCount . maxTaskCount , minCapacity : props . autoScaleTaskCount . minTaskCount , } ) ; if ( props . autoScaleTaskCount . targetCpuUtilization ) { const targetCpuUtilizationPercent = props . autoScaleTaskCount . targetCpuUtilization ; this . scalableTaskCount . scaleOnCpuUtilization ( `${ this . id }-target-cpu-utilization-${ targetCpuUtilizationPercent }` , { targetUtilizationPercent : targetCpuUtilizationPercent , } ) ; this . enableAutoScalingPolicy ( ) ; } if ( props . autoScaleTaskCount . targetMemoryUtilization ) { const targetMemoryUtilizationPercent = props . autoScaleTaskCount . targetMemoryUtilization ; this . scalableTaskCount . scaleOnMemoryUtilization ( `${ this . id }-target-memory-utilization-${ targetMemoryUtilizationPercent }` , { targetUtilizationPercent : targetMemoryUtilizationPercent , } ) ; this . enableAutoScalingPolicy ( ) ; } } for ( const extensions in this . serviceDescription . extensions ) { if ( this . serviceDescription . extensions [ extensions ] ) { this . serviceDescription . extensions [ extensions ] . useService ( this . ecsService ) ; } } if ( this . scalableTaskCount && ! this . autoScalingPoliciesEnabled ) { throw Error ( `The auto scaling target for the service '${ this . id }' has been created but no auto scaling policies have been configured.` ) ; } } param service public connectTo ( service , connectToProps = { } ) { for ( const extensions in this . serviceDescription . extensions ) { if ( this . serviceDescription . extensions [ extensions ] ) { this . serviceDescription . extensions [ extensions ] . connectToService ( service , connectToProps ) ; } } } param urlName param url public addURL ( urlName , url ) { this . urls [ urlName ] = url ; } param urlName public getURL ( urlName ) { if ( ! this . urls [ urlName ] ) { throw new Error ( `Unable to find a URL with name '${ urlName }'` ) ; } return this . urls [ urlName ] ; } /**
     * This helper method is used to set the `autoScalingPoliciesEnabled` attribute
     * whenever an auto scaling policy is configured for the service.
     */ public enableAutoScalingPolicy ( ) { this . autoScalingPoliciesEnabled = true ; } }