diff --git a/packages/@aws-cdk/aws-gamelift/.gitignore b/packages/@aws-cdk/aws-gamelift/.gitignore index 867ed81645aff..a6689f7255e0a 100644 --- a/packages/@aws-cdk/aws-gamelift/.gitignore +++ b/packages/@aws-cdk/aws-gamelift/.gitignore @@ -6,6 +6,7 @@ node_modules *.generated.ts dist .jsii +.DS_Store .LAST_BUILD .nyc_output @@ -23,5 +24,4 @@ junit.xml !**/*.snapshot/**/asset.*/** #include game build js file -!test/my-game-build/*.js !test/my-game-script/*.js diff --git a/packages/@aws-cdk/aws-gamelift/README.md b/packages/@aws-cdk/aws-gamelift/README.md index 8a1fb958781d2..986a84f5aa577 100644 --- a/packages/@aws-cdk/aws-gamelift/README.md +++ b/packages/@aws-cdk/aws-gamelift/README.md @@ -52,13 +52,6 @@ configuration or game server fleet management system. ## GameLift Hosting -### Defining a GameLift Fleet - -GameLift helps you deploy, operate, and scale dedicated game servers for -session-based multiplayer games. It helps you regulate the resources needed to -host your games, finds available game servers to host new game sessions, and -puts players into games. - ### Uploading builds and scripts to GameLift Before deploying your GameLift-enabled multiplayer game servers for hosting with the GameLift service, you need to upload @@ -110,6 +103,258 @@ new gamelift.Script(this, 'Script', { }); ``` +### Defining a GameLift Fleet + +#### Creating a custom game server fleet + +Your uploaded game servers are hosted on GameLift virtual computing resources, +called instances. You set up your hosting resources by creating a fleet of +instances and deploying them to run your game servers. You can design a fleet +to fit your game's needs. + +```ts +new gamelift.BuildFleet(this, 'Game server fleet', { + fleetName: 'test-fleet', + content: gamelift.Build.fromAsset(this, 'Build', path.join(__dirname, 'CustomerGameServer')), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: 'test-launch-path', + }], + }, +}); +``` + +### Managing game servers launch configuration + +GameLift uses a fleet's runtime configuration to determine the type and number +of processes to run on each instance in the fleet. At a minimum, a runtime +configuration contains one server process configuration that represents one +game server executable. You can also define additional server process +configurations to run other types of processes related to your game. Each +server process configuration contains the following information: + +* The file name and path of an executable in your game build. + +* Optionally Parameters to pass to the process on launch. + +* The number of processes to run concurrently. + +A GameLift instance is limited to 50 processes running concurrently. + +```ts +declare const build: gamelift.Build; +// Server processes can be delcared in a declarative way through the constructor +const fleet = new gamelift.BuildFleet(this, 'Game server fleet', { + fleetName: 'test-fleet', + content: build, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: '/local/game/GameLiftExampleServer.x86_64', + parameters: '-logFile /local/game/logs/myserver1935.log -port 1935', + concurrentExecutions: 100, + }] + } +}); +``` + +See [Managing how game servers are launched for hosting](https://docs.aws.amazon.com/gamelift/latest/developerguide/fleets-multiprocess.html) +in the *Amazon GameLift Developer Guide*. + +### Defining an instance type + +GameLift uses Amazon Elastic Compute Cloud (Amazon EC2) resources, called +instances, to deploy your game servers and host game sessions for your players. +When setting up a new fleet, you decide what type of instances your game needs +and how to run game server processes on them (using a runtime configuration). All instances in a fleet use the same type of resources and the same runtime +configuration. You can edit a fleet's runtime configuration and other fleet +properties, but the type of resources cannot be changed. + +```ts +declare const build: gamelift.Build; +new gamelift.BuildFleet(this, 'Game server fleet', { + fleetName: 'test-fleet', + content: build, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: '/local/game/GameLiftExampleServer.x86_64', + }] + } +}); +``` + +### Using Spot instances + +When setting up your hosting resources, you have the option of using Spot +Instances, On-Demand Instances, or a combination. + +By default, fleet are using on demand capacity. + +```ts +declare const build: gamelift.Build; +new gamelift.BuildFleet(this, 'Game server fleet', { + fleetName: 'test-fleet', + content: build, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: '/local/game/GameLiftExampleServer.x86_64', + }] + }, + useSpot: true +}); +``` + +### Allowing Ingress traffic + +The allowed IP address ranges and port settings that allow inbound traffic to +access game sessions on this fleet. + +New game sessions are assigned an IP address/port number combination, which +must fall into the fleet's allowed ranges. Fleets with custom game builds must +have permissions explicitly set. For Realtime Servers fleets, GameLift +automatically opens two port ranges, one for TCP messaging and one for UDP. + +```ts +declare const build: gamelift.Build; + +const fleet = new gamelift.BuildFleet(this, 'Game server fleet', { + fleetName: 'test-fleet', + content: build, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: '/local/game/GameLiftExampleServer.x86_64', + }] + }, + ingressRules: [{ + source: gamelift.Peer.anyIpv4(), + port: gamelift.Port.tcpRange(100, 200), + }] +}); +// Allowing a specific CIDR for port 1111 on UDP Protocol +fleet.addIngressRule(gamelift.Peer.ipv4('1.2.3.4/32'), gamelift.Port.udp(1111)); +``` + +### Managing locations + +A single Amazon GameLift fleet has a home Region by default (the Region you +deploy it to), but it can deploy resources to any number of GameLift supported +Regions. Select Regions based on where your players are located and your +latency needs. + +By default, home region is used as default location but we can add new locations if needed and define desired capacity + +```ts +declare const build: gamelift.Build; + +// Locations can be added directly through constructor +const fleet = new gamelift.BuildFleet(this, 'Game server fleet', { + fleetName: 'test-fleet', + content: build, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: '/local/game/GameLiftExampleServer.x86_64', + }] + }, + locations: [ { + region: 'eu-west-1', + capacity: { + desiredCapacity: 5, + minSize: 2, + maxSize: 10 + } + }, { + region: 'us-east-1', + capacity: { + desiredCapacity: 5, + minSize: 2, + maxSize: 10 + } + }] +}); + +// Or through dedicated methods +fleet.addLocation('ap-southeast-1', 5, 2, 10); +``` + +### Specifying an IAM role for a Fleet + +Some GameLift features require you to extend limited access to your AWS +resources. This is done by creating an AWS IAM role. The GameLift Fleet class +automatically created an IAM role with all the minimum necessary permissions +for GameLift to access your ressources. If you wish, you may +specify your own IAM role. + +```ts +declare const build: gamelift.Build; +const role = new iam.Role(this, 'Role', { + assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('gamelift.amazonaws.com')) +}); +role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchAgentServerPolicy')); + +const fleet = new gamelift.BuildFleet(this, 'Game server fleet', { + fleetName: 'test-fleet', + content: build, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: '/local/game/GameLiftExampleServer.x86_64', + }] + }, + role: role +}); + +// Actions can also be grantted through dedicated method +fleet.grant(role, 'gamelift:ListFleets'); +``` + +### Monitoring your Fleet + +GameLift is integrated with CloudWatch, so you can monitor the performance of +your game servers via logs and metrics. + +#### Metrics + +GameLift Fleet sends metrics to CloudWatch so that you can collect and analyze +the activity of your Fleet, including game and player sessions and server +processes. + +You can then use CloudWatch alarms to alert you, for example, when matches has +been rejected (potential matches that were rejected by at least one player +since the last report) exceed a certain thresold which could means that you may +have an issue in your matchmaking rules. + +CDK provides methods for accessing GameLift Fleet metrics with default configuration, +such as `metricActiveInstances`, or `metricIdleInstances` (see [`IFleet`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-gamelift.IFleet.html) +for a full list). CDK also provides a generic `metric` method that can be used +to produce metric configurations for any metric provided by GameLift Fleet, +Game sessions or server processes; the configurations are pre-populated with +the correct dimensions for the matchmaking configuration. + +```ts +declare const fleet: gamelift.BuildFleet; +// Alarm that triggers when the per-second average of not used instances exceed 10% +const instancesUsedRatio = new cloudwatch.MathExpression({ + expression: '1 - (activeInstances / idleInstances)', + usingMetrics: { + activeInstances: fleet.metric('ActiveInstances', { statistic: cloudwatch.Statistic.SUM }), + idleInstances: fleet.metricIdleInstances(), + }, +}); +new cloudwatch.Alarm(this, 'Alarm', { + metric: instancesUsedRatio, + threshold: 0.1, + evaluationPeriods: 3, +}); +``` + +See: [Monitoring Using CloudWatch Metrics](https://docs.aws.amazon.com/gamelift/latest/developerguide/monitoring-cloudwatch.html) +in the *Amazon GameLift Developer Guide*. + ## GameLift FleetIQ The GameLift FleetIQ solution is a game hosting layer that supplements the full @@ -184,7 +429,7 @@ new gamelift.GameServerGroup(this, 'Game server group', { See [Manage game server groups](https://docs.aws.amazon.com/gamelift/latest/fleetiqguide/gsg-integrate-gameservergroup.html) in the *Amazon GameLift FleetIQ Developer Guide*. -### Specifying an IAM role +### Specifying an IAM role for GameLift The GameLift FleetIQ class automatically creates an IAM role with all the minimum necessary permissions for GameLift to access your Amazon EC2 Auto Scaling groups. If you wish, you may diff --git a/packages/@aws-cdk/aws-gamelift/lib/build-fleet.ts b/packages/@aws-cdk/aws-gamelift/lib/build-fleet.ts new file mode 100644 index 0000000000000..5c520a8248801 --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/lib/build-fleet.ts @@ -0,0 +1,205 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { IBuild } from './build'; +import { FleetBase, FleetProps, IFleet } from './fleet-base'; +import { CfnFleet } from './gamelift.generated'; +import { Port, IPeer, IngressRule } from './ingress-rule'; + +/** + * Represents a GameLift Fleet used to run a custom game build. + */ +export interface IBuildFleet extends IFleet {} + +/** + * Properties for a new Gamelift build fleet + */ +export interface BuildFleetProps extends FleetProps { + + /** + * A build to be deployed on the fleet. + * The build must have been successfully uploaded to Amazon GameLift and be in a `READY` status. + * + * This fleet setting cannot be changed once the fleet is created. + */ + readonly content: IBuild; + + /** + * The allowed IP address ranges and port settings that allow inbound traffic to access game sessions on this fleet. + * + * This property must be set before players can connect to game sessions. + * + * @default no inbound traffic allowed + */ + readonly ingressRules?: IngressRule[]; +} + +/** + * A fleet contains Amazon Elastic Compute Cloud (Amazon EC2) instances that GameLift hosts. + * A fleet uses the configuration and scaling logic that you define to run your game server build. You can use a fleet directly without a queue. + * You can also associate multiple fleets with a GameLift queue. + * + * For example, you can use Spot Instance fleets configured with your preferred locations, along with a backup On-Demand Instance fleet with the same locations. + * Using multiple Spot Instance fleets of different instance types reduces the chance of needing On-Demand Instance placement. + * + * @resource AWS::GameLift::Fleet + */ +export class BuildFleet extends FleetBase implements IBuildFleet { + + /** + * Import an existing fleet from its identifier. + */ + static fromBuildFleetId(scope: Construct, id: string, buildFleetId: string): IBuildFleet { + return this.fromFleetAttributes(scope, id, { fleetId: buildFleetId }); + } + + /** + * Import an existing fleet from its ARN. + */ + static fromBuildFleetArn(scope: Construct, id: string, buildFleetArn: string): IBuildFleet { + return this.fromFleetAttributes(scope, id, { fleetArn: buildFleetArn }); + } + + /** + * The Identifier of the fleet. + */ + public readonly fleetId: string; + + /** + * The ARN of the fleet. + */ + public readonly fleetArn: string; + + /** + * The build content of the fleet + */ + public readonly content: IBuild; + + /** + * The IAM role GameLift assumes by fleet instances to access AWS ressources. + */ + public readonly role: iam.IRole; + + /** + * The principal this GameLift fleet is using. + */ + public readonly grantPrincipal: iam.IPrincipal; + + private readonly ingressRules: IngressRule[] = []; + + constructor(scope: Construct, id: string, props: BuildFleetProps) { + super(scope, id, { + physicalName: props.fleetName, + }); + + if (!cdk.Token.isUnresolved(props.fleetName)) { + if (props.fleetName.length > 1024) { + throw new Error(`Fleet name can not be longer than 1024 characters but has ${props.fleetName.length} characters.`); + } + } + + if (props.description && !cdk.Token.isUnresolved(props.description)) { + if (props.description.length > 1024) { + throw new Error(`Fleet description can not be longer than 1024 characters but has ${props.description.length} characters.`); + } + } + + if (props.minSize && props.minSize < 0) { + throw new Error(`The minimum number of instances allowed in the Fleet cannot be lower than 0, given ${props.minSize}`); + } + + if (props.maxSize && props.maxSize < 0) { + throw new Error(`The maximum number of instances allowed in the Fleet cannot be lower than 0, given ${props.maxSize}`); + } + + if (props.peerVpc) { + this.warnVpcPeeringAuthorizations(this); + } + + // Add all locations + if (props.locations && props.locations?.length > 100) { + throw new Error(`No more than 100 locations are allowed per fleet, given ${props.locations.length}`); + } + (props.locations || []).forEach(this.addInternalLocation.bind(this)); + + + // Add all Ingress rules + if (props.ingressRules && props.ingressRules?.length > 50) { + throw new Error(`No more than 50 ingress rules are allowed per fleet, given ${props.ingressRules.length}`); + } + (props.ingressRules || []).forEach(this.addInternalIngressRule.bind(this)); + + this.content = props.content; + this.role = props.role ?? new iam.Role(this, 'ServiceRole', { + assumedBy: new iam.CompositePrincipal( + new iam.ServicePrincipal('gamelift.amazonaws.com'), + new iam.ServicePrincipal('ec2.amazonaws.com')), + }); + this.grantPrincipal = this.role; + + const resource = new CfnFleet(this, 'Resource', { + buildId: this.content.buildId, + certificateConfiguration: { + certificateType: props.useCertificate ? 'GENERATED': 'DISABLED', + }, + description: props.description, + desiredEc2Instances: props.desiredCapacity, + ec2InboundPermissions: cdk.Lazy.any({ produce: () => this.parseIngressRules() }), + ec2InstanceType: props.instanceType.toString(), + fleetType: props.useSpot ? 'SPOT' : 'ON_DEMAND', + instanceRoleArn: this.role.roleArn, + locations: cdk.Lazy.any({ produce: () => this.parseLocations() }), + maxSize: props.maxSize ? props.maxSize : 1, + minSize: props.minSize ? props.minSize : 0, + name: this.physicalName, + newGameSessionProtectionPolicy: props.protectNewGameSession ? 'FullProtection' : 'NoProtection', + peerVpcAwsAccountId: props.peerVpc && props.peerVpc.env.account, + peerVpcId: props.peerVpc && props.peerVpc.vpcId, + resourceCreationLimitPolicy: this.parseResourceCreationLimitPolicy(props), + runtimeConfiguration: this.parseRuntimeConfiguration(props), + }); + + this.fleetId = this.getResourceNameAttribute(resource.ref); + this.fleetArn = cdk.Stack.of(scope).formatArn({ + service: 'gamelift', + resource: 'fleet', + resourceName: this.fleetId, + arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME, + }); + } + + /** + * Adds an ingress rule to allow inbound traffic to access game sessions on this fleet. + * + * @param source A range of allowed IP addresses + * @param port The port range used for ingress traffic + */ + public addIngressRule(source: IPeer, port: Port) { + this.addInternalIngressRule({ + source: source, + port: port, + }); + } + + private addInternalIngressRule(ingressRule: IngressRule) { + if (this.ingressRules.length == 50) { + throw new Error('No more than 50 ingress rules are allowed per fleet'); + } + this.ingressRules.push(ingressRule); + } + + private parseIngressRules(): CfnFleet.IpPermissionProperty[] | undefined { + if (!this.ingressRules || this.ingressRules.length === 0) { + return undefined; + } + + return this.ingressRules.map(parseIngressRule); + + function parseIngressRule(ingressRule: IngressRule): CfnFleet.IpPermissionProperty { + return { + ...ingressRule.port.toJson(), + ...ingressRule.source.toJson(), + }; + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/lib/fleet-base.ts b/packages/@aws-cdk/aws-gamelift/lib/fleet-base.ts new file mode 100644 index 0000000000000..20d515b1994ad --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/lib/fleet-base.ts @@ -0,0 +1,613 @@ +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { GameLiftMetrics } from './gamelift-canned-metrics.generated'; +import { CfnFleet } from './gamelift.generated'; + + +/** + * Current resource capacity settings in a specified fleet or location. + * The location value might refer to a fleet's remote location or its home Region. + */ +export interface LocationCapacity { + /** + * The number of Amazon EC2 instances you want to maintain in the specified fleet location. + * This value must fall between the minimum and maximum size limits. + * + * @default the default value is 0 + */ + readonly desiredCapacity?: number; + /** + * The maximum number of instances that are allowed in the specified fleet location. + * + * @default the default value is 1 + */ + readonly maxSize?: number; + /** + * The minimum number of instances that are allowed in the specified fleet location. + * + * @default the default value is 0 + */ + readonly minSize?: number; +} + +/** + * A remote location where a multi-location fleet can deploy EC2 instances for game hosting. + */ +export interface Location { + /** + * An AWS Region code + */ + readonly region: string; + /** + * Current resource capacity settings in a specified fleet or location. + * The location value might refer to a fleet's remote location or its home Region. + * + * @default no capacity settings on the specified location + */ + readonly capacity?: LocationCapacity; +} + +/** + * Configuration of a fleet server process + */ +export interface ServerProcess { + /** + * The number of server processes using this configuration that run concurrently on each instance. + * Minimum is `1` + * + * @default 1 + */ + readonly concurrentExecutions?: number; + /** + * The location of a game build executable or the Realtime script file that contains the Init() function. Game builds and Realtime scripts are installed on instances at the root: + * - Windows (custom game builds only): `C:\game`. Example: `C:\game\MyGame\server.exe` + * - Linux: `/local/game`. Examples: `/local/game/MyGame/server.exe` or `/local/game/MyRealtimeScript.js` + */ + readonly launchPath: string; + + /** + * An optional list of parameters to pass to the server executable or Realtime script on launch. + * + * @default no parameters + */ + readonly parameters?: string; +} + +/** + * A collection of server process configurations that describe the set of processes to run on each instance in a fleet. + * Server processes run either an executable in a custom game build or a Realtime Servers script. + * GameLift launches the configured processes, manages their life cycle, and replaces them as needed. + * Each instance checks regularly for an updated runtime configuration. + * + * A GameLift instance is limited to 50 processes running concurrently. + * To calculate the total number of processes in a runtime configuration, add the values of the `ConcurrentExecutions` parameter for each `ServerProcess`. + * + * @see https://docs.aws.amazon.com/gamelift/latest/developerguide/fleets-multiprocess.html + */ +export interface RuntimeConfiguration { + /** + * The maximum amount of time allowed to launch a new game session and have it report ready to host players. + * During this time, the game session is in status `ACTIVATING`. + * + * If the game session does not become active before the timeout, it is ended and the game session status is changed to `TERMINATED`. + * + * @default by default game session activation timeout is 300 seconds + */ + readonly gameSessionActivationTimeout?: cdk.Duration; + /** + * The number of game sessions in status `ACTIVATING` to allow on an instance. + * + * This setting limits the instance resources that can be used for new game activations at any one time. + * + * @default no limit + */ + readonly maxConcurrentGameSessionActivations?: number; + + /** + * A collection of server process configurations that identify what server processes to run on each instance in a fleet. + */ + readonly serverProcesses: ServerProcess[]; +} + +/** + * A policy that limits the number of game sessions a player can create on the same fleet. + * This optional policy gives game owners control over how players can consume available game server resources. + * A resource creation policy makes the following statement: "An individual player can create a maximum number of new game sessions within a specified time period". + * + * The policy is evaluated when a player tries to create a new game session. + * For example, assume you have a policy of 10 new game sessions and a time period of 60 minutes. + * On receiving a `CreateGameSession` request, Amazon GameLift checks that the player (identified by CreatorId) has created fewer than 10 game sessions in the past 60 minutes. + */ +export interface ResourceCreationLimitPolicy { + /** + * The maximum number of game sessions that an individual can create during the policy period. + * + * @default no limit on the number of game sessions that an individual can create during the policy period + */ + readonly newGameSessionsPerCreator?: number; + /** + * The time span used in evaluating the resource creation limit policy. + * + * @default no policy period + */ + readonly policyPeriod?: cdk.Duration, +} + +/** + * Represents a Gamelift fleet + */ +export interface IFleet extends cdk.IResource, iam.IGrantable { + /** + * The Identifier of the fleet. + * + * @attribute + */ + readonly fleetId: string; + + /** + * The ARN of the fleet. + * + * @attribute + */ + readonly fleetArn: string; + + /** + * Grant the `grantee` identity permissions to perform `actions`. + */ + grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant; + + /** + * Return the given named metric for this fleet. + */ + metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric; + + /** + * Instances with `ACTIVE` status, which means they are running active server processes. + * The count includes idle instances and those that are hosting one or more game sessions. + * This metric measures current total instance capacity. + * + * This metric can be used with automatic scaling. + */ + metricActiveInstances(props?: cloudwatch.MetricOptions): cloudwatch.Metric; + + /** + * Percentage of all active instances that are idle (calculated as IdleInstances / ActiveInstances). + * This metric can be used for automatic scaling. + */ + metricPercentIdleInstances(props?: cloudwatch.MetricOptions): cloudwatch.Metric; + + /** + * Target number of active instances that GameLift is working to maintain in the fleet. + * With automatic scaling, this value is determined based on the scaling policies currently in force. + * Without automatic scaling, this value is set manually. + * This metric is not available when viewing data for fleet metric groups. + */ + metricDesiredInstances(props?: cloudwatch.MetricOptions): cloudwatch.Metric; + + /** + * Active instances that are currently hosting zero (0) game sessions. + * This metric measures capacity that is available but unused. + * This metric can be used with automatic scaling. + */ + metricIdleInstances(props?: cloudwatch.MetricOptions): cloudwatch.Metric; + + /** + * Number of spot instances that have been interrupted. + */ + metricInstanceInterruptions(props?: cloudwatch.MetricOptions): cloudwatch.Metric; + + /** + * Maximum number of instances that are allowed for the fleet. + * A fleet's instance maximum determines the capacity ceiling during manual or automatic scaling up. + * This metric is not available when viewing data for fleet metric groups. + */ + metricMaxInstances(props?: cloudwatch.MetricOptions): cloudwatch.Metric; + + /** + * Minimum number of instances allowed for the fleet. + * A fleet's instance minimum determines the capacity floor during manual or automatic scaling down. + * This metric is not available when viewing data for fleet metric groups. + */ + metricMinInstances(props?: cloudwatch.MetricOptions): cloudwatch.Metric; +} + +/** + * Properties for a new Gamelift fleet + */ +export interface FleetProps { + + /** + * A descriptive label that is associated with a fleet. Fleet names do not need to be unique. + */ + readonly fleetName: string; + + /** + * A human-readable description of the fleet. + * + * @default no description is provided + */ + readonly description?: string; + + /** + * Indicates whether to use On-Demand or Spot instances for this fleet. + * By default, fleet use on demand capacity. + * + * This property cannot be changed after the fleet is created. + * + * @see https://docs.aws.amazon.com/gamelift/latest/developerguide/gamelift-ec2-instances.html#gamelift-ec2-instances-spot + * + * @default Gamelift fleet use on demand capacity + */ + readonly useSpot?: boolean; + + /** + * Prompts GameLift to generate a TLS/SSL certificate for the fleet. + * GameLift uses the certificates to encrypt traffic between game clients and the game servers running on GameLift. + * + * You can't change this property after you create the fleet. + * + * Additionnal info: + * AWS Certificate Manager (ACM) certificates expire after 13 months. + * Certificate expiration can cause fleets to fail, preventing players from connecting to instances in the fleet. + * We recommend you replace fleets before 13 months, consider using fleet aliases for a smooth transition. + * + * @default TLS/SSL certificate are generated for the fleet + */ + readonly useCertificate?: boolean; + + /** + * The IAM role assumed by GameLift fleet instances to access AWS ressources. + * With a role set, any application that runs on an instance in this fleet can assume the role, including install scripts, server processes, and daemons (background processes). + * If providing a custom role, it needs to trust the GameLift service principal (gamelift.amazonaws.com). + * No permission is required by default. + * + * This property cannot be changed after the fleet is created. + * + * @see https://docs.aws.amazon.com/gamelift/latest/developerguide/gamelift-sdk-server-resources.html + * + * @default - a role will be created with default trust to Gamelift service principal. + */ + readonly role?: iam.IRole; + + /** + * A VPC peering connection between your GameLift-hosted game servers and your other non-GameLift resources. + * Use Amazon Virtual Private Cloud (VPC) peering connections to enable your game servers to communicate directly and privately with your other AWS resources, such as a web service or a repository. + * You can establish VPC peering with any resources that run on AWS and are managed by an AWS account that you have access to. + * The VPC must be in the same Region as your fleet. + * + * Warning: + * Be sure to create a VPC Peering authorization through Gamelift Service API. + * + * @see https://docs.aws.amazon.com/gamelift/latest/developerguide/vpc-peering.html + * + * @default no vpc peering + */ + readonly peerVpc?: ec2.IVpc; + + /** + * The name of an AWS CloudWatch metric group to add this fleet to. + * A metric group is used to aggregate the metrics for multiple fleets. + * You can specify an existing metric group name or set a new name to create a new metric group. + * A fleet can be included in only one metric group at a time. + * + * @default Fleet metrics are aggregated with other fleets in the default metric group + */ + readonly metricGroup?: string; + + /** + * The GameLift-supported Amazon EC2 instance type to use for all fleet instances. + * Instance type determines the computing resources that will be used to host your game servers, including CPU, memory, storage, and networking capacity. + * + * @see http://aws.amazon.com/ec2/instance-types/ for detailed descriptions of Amazon EC2 instance types. + */ + readonly instanceType: ec2.InstanceType; + + /** + * The number of EC2 instances that you want this fleet to host. + * When creating a new fleet, GameLift automatically sets this value to "1" and initiates a single instance. + * Once the fleet is active, update this value to trigger GameLift to add or remove instances from the fleet. + * + * @default Default capacity is 0 + */ + readonly desiredCapacity?: number; + + /** + * The minimum number of instances that are allowed in the specified fleet location. + * + * @default the default is 0 + */ + readonly minSize?: number; + + /** + * The maximum number of instances that are allowed in the specified fleet location. + * + * @default the default is 1 + */ + readonly maxSize?: number; + + /** + * The status of termination protection for active game sessions on the fleet. + * By default, new game sessions are protected and cannot be terminated during a scale-down event. + * + * @default true - Game sessions in `ACTIVE` status cannot be terminated during a scale-down event. + */ + readonly protectNewGameSession? : boolean; + + /** + * A collection of server process configurations that describe the set of processes to run on each instance in a fleet. + * Server processes run either an executable in a custom game build or a Realtime Servers script. + * GameLift launches the configured processes, manages their life cycle, and replaces them as needed. + * Each instance checks regularly for an updated runtime configuration. + * + * A GameLift instance is limited to 50 processes running concurrently. + * To calculate the total number of processes in a runtime configuration, add the values of the ConcurrentExecutions parameter for each ServerProcess. + * + * @see https://docs.aws.amazon.com/gamelift/latest/developerguide/fleets-multiprocess.html + */ + readonly runtimeConfiguration: RuntimeConfiguration; + + /** + * A set of remote locations to deploy additional instances to and manage as part of the fleet. + * This parameter can only be used when creating fleets in AWS Regions that support multiple locations. + * You can add any GameLift-supported AWS Region as a remote location, in the form of an AWS Region code such as `us-west-2`. + * To create a fleet with instances in the home region only, omit this parameter. + * + * @default Create a fleet with instances in the home region only + */ + readonly locations?: Location[]; + + /** + * A policy that limits the number of game sessions that an individual player can create on instances in this fleet within a specified span of time. + * + * @default No resource creation limit policy + */ + readonly resourceCreationLimitPolicy?: ResourceCreationLimitPolicy; +} + +/** + * A full specification of a fleet that can be used to import it fluently into the CDK application. + */ +export interface FleetAttributes { + /** + * The ARN of the fleet + * + * At least one of `fleetArn` and `fleetId` must be provided. + * + * @default derived from `fleetId`. + */ + readonly fleetArn?: string; + + /** + * The identifier of the fleet + * + * At least one of `fleetId` and `fleetArn` must be provided. + * + * @default derived from `fleetArn`. + */ + readonly fleetId?: string; + + /** + * The IAM role assumed by GameLift fleet instances to access AWS ressources. + * + * @default the imported fleet cannot be granted access to other resources as an `iam.IGrantable`. + */ + readonly role?: iam.IRole; +} + +/** + * Base class for new and imported GameLift fleet. + */ +export abstract class FleetBase extends cdk.Resource implements IFleet { + + /** + * Import an existing fleet from its attributes. + */ + static fromFleetAttributes(scope: Construct, id: string, attrs: FleetAttributes): IFleet { + if (!attrs.fleetId && !attrs.fleetArn) { + throw new Error('Either fleetId or fleetArn must be provided in FleetAttributes'); + } + const fleetId = attrs.fleetId ?? + cdk.Stack.of(scope).splitArn(attrs.fleetArn!, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName; + + if (!fleetId) { + throw new Error(`No fleet identifier found in ARN: '${attrs.fleetArn}'`); + } + + const fleetArn = attrs.fleetArn ?? cdk.Stack.of(scope).formatArn({ + service: 'gamelift', + resource: 'fleet', + resourceName: attrs.fleetId, + arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME, + }); + class Import extends FleetBase { + public readonly fleetId = fleetId!; + public readonly fleetArn = fleetArn; + public readonly grantPrincipal = attrs.role ?? new iam.UnknownPrincipal({ resource: this }); + public readonly role = attrs.role; + + constructor(s: Construct, i: string) { + super(s, i, { + environmentFromArn: fleetArn, + }); + } + } + return new Import(scope, id); + } + + /** + * The Identifier of the fleet. + */ + public abstract readonly fleetId: string; + /** + * The ARN of the fleet. + */ + public abstract readonly fleetArn: string; + + /** + * The principal this GameLift fleet is using. + */ + public abstract readonly grantPrincipal: iam.IPrincipal; + + private readonly locations: Location[] = []; + + public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant { + return iam.Grant.addToPrincipal({ + resourceArns: [this.fleetArn], + grantee: grantee, + actions: actions, + }); + } + + public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return new cloudwatch.Metric({ + namespace: 'AWS/GameLift', + metricName: metricName, + dimensionsMap: { + FleetId: this.fleetId, + }, + ...props, + }).attachTo(this); + } + + public metricActiveInstances(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.cannedMetric(GameLiftMetrics.activeInstancesAverage, props); + } + + public metricPercentIdleInstances(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.cannedMetric(GameLiftMetrics.percentIdleInstancesAverage, props); + } + + public metricDesiredInstances(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.cannedMetric(GameLiftMetrics.desiredInstancesAverage, props); + } + + public metricIdleInstances(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.cannedMetric(GameLiftMetrics.idleInstancesAverage, props); + } + + public metricInstanceInterruptions(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.cannedMetric(GameLiftMetrics.instanceInterruptionsSum, props); + } + + public metricMaxInstances(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.cannedMetric(GameLiftMetrics.maxInstancesAverage, props); + } + + public metricMinInstances(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.cannedMetric(GameLiftMetrics.minInstancesAverage, props); + } + + private cannedMetric(fn: (dims: { FleetId: string }) => cloudwatch.MetricProps, props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return new cloudwatch.Metric({ + ...fn({ FleetId: this.fleetId }), + ...props, + }).attachTo(this); + } + + /** + * Adds a remote locations to deploy additional instances to and manage as part of the fleet. + * + * @param region The AWS region to add + */ + public addLocation(region: string, desiredCapacity?: number, minSize?: number, maxSize?: number) { + this.addInternalLocation({ + region: region, + capacity: { + desiredCapacity: desiredCapacity, + minSize: minSize, + maxSize: maxSize, + }, + }); + } + + /** + * Adds a remote locations to deploy additional instances to and manage as part of the fleet. + * + * @param location The location to add + */ + public addInternalLocation(location: Location) { + if (this.locations.length == 100) { + throw new Error('No more than 100 locations are allowed per fleet'); + } + + this.locations.push(location); + } + + protected parseResourceCreationLimitPolicy(props: FleetProps): CfnFleet.ResourceCreationLimitPolicyProperty | undefined { + if (!props.resourceCreationLimitPolicy || + (!props.resourceCreationLimitPolicy.newGameSessionsPerCreator + && !props.resourceCreationLimitPolicy.policyPeriod)) { + return undefined; + } + + return { + newGameSessionsPerCreator: props.resourceCreationLimitPolicy.newGameSessionsPerCreator, + policyPeriodInMinutes: props.resourceCreationLimitPolicy.policyPeriod?.toMinutes(), + }; + } + + protected parseLocations(): CfnFleet.LocationConfigurationProperty[] | undefined { + if (!this.locations || this.locations.length === 0) { + return undefined; + } + + const self = this; + + return this.locations.map(parseLocation); + + function parseLocation(location: Location): CfnFleet.LocationConfigurationProperty { + return { + location: location.region, + locationCapacity: self.parseLocationCapacity(location.capacity), + }; + } + } + + protected parseLocationCapacity(capacity?: LocationCapacity): CfnFleet.LocationCapacityProperty | undefined { + if (!capacity || + (!capacity.desiredCapacity + && !capacity.minSize + && !capacity.maxSize)) { + return undefined; + } + + return { + desiredEc2Instances: capacity.desiredCapacity ?? 0, + minSize: capacity.minSize ?? 0, + maxSize: capacity.maxSize ?? 1, + }; + + } + protected parseRuntimeConfiguration(props: FleetProps): CfnFleet.RuntimeConfigurationProperty | undefined { + if (!props.runtimeConfiguration || + (!props.runtimeConfiguration.gameSessionActivationTimeout + && !props.runtimeConfiguration.maxConcurrentGameSessionActivations + && props.runtimeConfiguration.serverProcesses.length == 0)) { + return undefined; + } + return { + gameSessionActivationTimeoutSeconds: props.runtimeConfiguration.gameSessionActivationTimeout?.toSeconds(), + maxConcurrentGameSessionActivations: props.runtimeConfiguration.maxConcurrentGameSessionActivations, + serverProcesses: props.runtimeConfiguration.serverProcesses.map(parseServerProcess), + }; + + function parseServerProcess(serverProcess: ServerProcess): CfnFleet.ServerProcessProperty { + return { + parameters: serverProcess.parameters, + launchPath: serverProcess.launchPath, + concurrentExecutions: serverProcess.concurrentExecutions ?? 1, + }; + } + } + + protected warnVpcPeeringAuthorizations(scope: Construct): void { + cdk.Annotations.of(scope).addWarning([ + 'To authorize the VPC peering, call the GameLift service API CreateVpcPeeringAuthorization() or use the AWS CLI command create-vpc-peering-authorization.', + 'Make this call using the account that manages your non-GameLift resources.', + 'See: https://docs.aws.amazon.com/gamelift/latest/developerguide/vpc-peering.html', + ].join('\n')); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/lib/index.ts b/packages/@aws-cdk/aws-gamelift/lib/index.ts index c71edfbfe2f08..a6159851e5d3e 100644 --- a/packages/@aws-cdk/aws-gamelift/lib/index.ts +++ b/packages/@aws-cdk/aws-gamelift/lib/index.ts @@ -2,6 +2,9 @@ export * from './content'; export * from './build'; export * from './script'; export * from './game-server-group'; +export * from './ingress-rule'; +export * from './fleet-base'; +export * from './build-fleet'; // AWS::GameLift CloudFormation Resources: export * from './gamelift.generated'; diff --git a/packages/@aws-cdk/aws-gamelift/lib/ingress-rule.ts b/packages/@aws-cdk/aws-gamelift/lib/ingress-rule.ts new file mode 100644 index 0000000000000..63aac18554d55 --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/lib/ingress-rule.ts @@ -0,0 +1,224 @@ +import * as cdk from '@aws-cdk/core'; + +/** + * Protocol for use in Connection Rules + * + * https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml + */ +export enum Protocol { + TCP = 'TCP', + UDP = 'UDP', +} + +/** + * Properties to create a port range + */ +export interface PortProps { + /** + * The protocol for the range + */ + readonly protocol: Protocol; + + /** + * A starting value for a range of allowed port numbers. + * + * For fleets using Windows and Linux builds, only ports 1026-60000 are valid. + */ + readonly fromPort: number; + + /** + * An ending value for a range of allowed port numbers. Port numbers are end-inclusive. + * This value must be higher than `fromPort`. + * + * For fleets using Windows and Linux builds, only ports 1026-60000 are valid. + * + * @default the `fromPort` value + */ + readonly toPort?: number; +} + +/** + * Interface for classes that provide the connection-specification parts of a security group rule + */ +export class Port { + /** + * A single TCP port + */ + public static tcp(port: number): Port { + return new Port({ + protocol: Protocol.TCP, + fromPort: port, + toPort: port, + }); + } + + /** + * A TCP port range + */ + public static tcpRange(startPort: number, endPort: number) { + return new Port({ + protocol: Protocol.TCP, + fromPort: startPort, + toPort: endPort, + }); + } + + /** + * Any TCP traffic + */ + public static allTcp() { + return new Port({ + protocol: Protocol.TCP, + fromPort: 1026, + toPort: 60000, + }); + } + + /** + * A single UDP port + */ + public static udp(port: number): Port { + return new Port({ + protocol: Protocol.UDP, + fromPort: port, + toPort: port, + }); + } + + /** + * A UDP port range + */ + public static udpRange(startPort: number, endPort: number) { + return new Port({ + protocol: Protocol.UDP, + fromPort: startPort, + toPort: endPort, + }); + } + + /** + * Any UDP traffic + */ + public static allUdp() { + return new Port({ + protocol: Protocol.UDP, + fromPort: 1026, + toPort: 60000, + }); + } + + constructor(private readonly props: PortProps) {} + + /** + * Produce the ingress rule JSON for the given connection + */ + public toJson(): any { + return { + protocol: this.props.protocol, + fromPort: this.props.fromPort, + toPort: this.props.toPort, + }; + } +} + +/** + * Interface for classes that provide the peer-specification parts of an inbound permission + */ +export interface IPeer { + + /** + * A unique identifier for this connection peer + */ + readonly uniqueId: string; + + /** + * Produce the ingress rule JSON for the given connection + */ + toJson(): any; +} + +/** + * Peer object factories + * + * The static methods on this object can be used to create peer objects + * which represent a connection partner in inbound permission rules. + * + * Use this object if you need to represent connection partners using plain IP addresses. + */ +export class Peer { + /** + * Create an IPv4 peer from a CIDR + */ + public static ipv4(cidrIp: string): IPeer { + return new CidrIPv4(cidrIp); + } + + /** + * Any IPv4 address + */ + public static anyIpv4(): IPeer { + return new AnyIPv4(); + } + + protected constructor() { + } +} + +/** + * A connection to and from a given IP range + */ +class CidrIPv4 implements IPeer { + public readonly canInlineRule = true; + public readonly uniqueId: string; + + constructor(private readonly cidrIp: string) { + if (!cdk.Token.isUnresolved(cidrIp)) { + const cidrMatch = cidrIp.match(/^(\d{1,3}\.){3}\d{1,3}(\/\d+)?$/); + + if (!cidrMatch) { + throw new Error(`Invalid IPv4 CIDR: "${cidrIp}"`); + } + + if (!cidrMatch[2]) { + throw new Error(`CIDR mask is missing in IPv4: "${cidrIp}". Did you mean "${cidrIp}/32"?`); + } + } + + this.uniqueId = cidrIp; + } + + /** + * Produce the ingress rule JSON for the given connection + */ + public toJson(): any { + return { ipRange: this.cidrIp }; + } +} + +/** + * Any IPv4 address + */ +class AnyIPv4 extends CidrIPv4 { + constructor() { + super('0.0.0.0/0'); + } +} + +/** + * A range of IP addresses and port settings that allow inbound traffic to connect to server processes on an instance in a fleet. + * New game sessions are assigned an IP address/port number combination, which must fall into the fleet's allowed ranges. + * + * Fleets with custom game builds must have permissions explicitly set. + * For Realtime Servers fleets, GameLift automatically opens two port ranges, one for TCP messaging and one for UDP. + */ +export interface IngressRule { + /** + * The port range used for ingress traffic + */ + readonly port: Port; + + /** + * A range of allowed IP addresses . + */ + readonly source: IPeer; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/package.json b/packages/@aws-cdk/aws-gamelift/package.json index 877984f3fdddc..05acd5fcb596d 100644 --- a/packages/@aws-cdk/aws-gamelift/package.json +++ b/packages/@aws-cdk/aws-gamelift/package.json @@ -84,7 +84,6 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/integ-tests": "0.0.0", "@aws-cdk/integ-runner": "0.0.0", - "@aws-cdk/integ-tests": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@aws-cdk/cx-api": "0.0.0", @@ -127,7 +126,9 @@ "exclude": [ "docs-public-apis:@aws-cdk/aws-gamelift.OperatingSystem.AMAZON_LINUX", "docs-public-apis:@aws-cdk/aws-gamelift.OperatingSystem.AMAZON_LINUX_2", - "docs-public-apis:@aws-cdk/aws-gamelift.OperatingSystem.WINDOWS_2012" + "docs-public-apis:@aws-cdk/aws-gamelift.OperatingSystem.WINDOWS_2012", + "docs-public-apis:@aws-cdk/aws-gamelift.Protocol.TCP", + "docs-public-apis:@aws-cdk/aws-gamelift.Protocol.UDP" ] }, "stability": "experimental", diff --git a/packages/@aws-cdk/aws-gamelift/test/build-fleet.test.ts b/packages/@aws-cdk/aws-gamelift/test/build-fleet.test.ts new file mode 100644 index 0000000000000..f309b20bafa3b --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/build-fleet.test.ts @@ -0,0 +1,684 @@ +import * as path from 'path'; +import { Template, Match, Annotations } from '@aws-cdk/assertions'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; +import * as gamelift from '../lib'; + +describe('build fleet', () => { + + describe('new', () => { + let stack: cdk.Stack; + + beforeEach(() => { + stack = new cdk.Stack(); + }); + + test('default build fleet', () => { + new gamelift.BuildFleet(stack, 'MyBuildFleet', { + fleetName: 'test-fleet', + content: gamelift.Build.fromAsset(stack, 'Build', path.join(__dirname, 'my-game-build')), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: 'test-launch-path', + }], + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + AssumeRolePolicyDocument: + { + Statement: + [{ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'gamelift.amazonaws.com' }, + }], + Version: '2012-10-17', + }, + }); + + Template.fromStack(stack).hasResource('AWS::GameLift::Fleet', { + Properties: + { + BuildId: { Ref: 'Build45A36621' }, + NewGameSessionProtectionPolicy: 'NoProtection', + FleetType: 'ON_DEMAND', + EC2InstanceType: 'c4.large', + CertificateConfiguration: { + CertificateType: 'DISABLED', + }, + MaxSize: 1, + MinSize: 0, + RuntimeConfiguration: { + ServerProcesses: [ + { + ConcurrentExecutions: 1, + LaunchPath: 'test-launch-path', + }, + ], + }, + }, + }); + }); + + test('with an incorrect fleet name', () => { + let incorrectFleetName = ''; + for (let i = 0; i < 1025; i++) { + incorrectFleetName += 'A'; + } + + expect(() => new gamelift.BuildFleet(stack, 'MyBuildFleet', { + fleetName: incorrectFleetName, + content: gamelift.Build.fromAsset(stack, 'Build', path.join(__dirname, 'my-game-build')), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: 'test-launch-path', + }], + }, + })).toThrow(/Fleet name can not be longer than 1024 characters but has 1025 characters./); + }); + + test('with an incorrect description', () => { + let incorrectDescription = ''; + for (let i = 0; i < 1025; i++) { + incorrectDescription += 'A'; + } + + expect(() => new gamelift.BuildFleet(stack, 'MyBuildFleet', { + fleetName: 'test-fleet', + description: incorrectDescription, + content: gamelift.Build.fromAsset(stack, 'Build', path.join(__dirname, 'my-game-build')), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: 'test-launch-path', + }], + }, + })).toThrow(/Fleet description can not be longer than 1024 characters but has 1025 characters./); + }); + + test('with an incorrect minSize value', () => { + expect(() => new gamelift.BuildFleet(stack, 'MyBuildFleet', { + fleetName: 'test-fleet', + content: gamelift.Build.fromAsset(stack, 'Build', path.join(__dirname, 'my-game-build')), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: 'test-launch-path', + }], + }, + minSize: -1, + })).toThrow(/The minimum number of instances allowed in the Fleet cannot be lower than 0, given -1/); + }); + + test('with an incorrect maxSize value', () => { + expect(() => new gamelift.BuildFleet(stack, 'MyBuildFleet', { + fleetName: 'test-fleet', + content: gamelift.Build.fromAsset(stack, 'Build', path.join(__dirname, 'my-game-build')), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: 'test-launch-path', + }], + }, + maxSize: -1, + })).toThrow(/The maximum number of instances allowed in the Fleet cannot be lower than 0, given -1/); + }); + + test('with too much locations from constructor', () => { + let incorrectLocations: gamelift.Location[] = []; + for (let i = 0; i < 101; i++) { + incorrectLocations.push({ + region: 'eu-west-1', + }); + } + + expect(() => new gamelift.BuildFleet(stack, 'MyBuildFleet', { + fleetName: 'test-fleet', + locations: incorrectLocations, + content: gamelift.Build.fromAsset(stack, 'Build', path.join(__dirname, 'my-game-build')), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: 'test-launch-path', + }], + }, + })).toThrow(/No more than 100 locations are allowed per fleet, given 101/); + }); + + test('with too much locations', () => { + let locations: gamelift.Location[] = []; + for (let i = 0; i < 100; i++) { + locations.push({ + region: 'eu-west-1', + }); + } + + const fleet = new gamelift.BuildFleet(stack, 'MyBuildFleet', { + fleetName: 'test-fleet', + locations: locations, + content: gamelift.Build.fromAsset(stack, 'Build', path.join(__dirname, 'my-game-build')), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: 'test-launch-path', + }], + }, + }); + + expect(() => fleet.addLocation('eu-west-1')).toThrow(/No more than 100 locations are allowed per fleet/); + }); + + test('with too much ingress rules', () => { + let incorrectIngressRules: gamelift.IngressRule[] = []; + for (let i = 0; i < 51; i++) { + incorrectIngressRules.push({ + source: gamelift.Peer.anyIpv4(), + port: gamelift.Port.tcpRange(100, 200), + }); + } + + expect(() => new gamelift.BuildFleet(stack, 'MyBuildFleet', { + fleetName: 'test-fleet', + ingressRules: incorrectIngressRules, + content: gamelift.Build.fromAsset(stack, 'Build', path.join(__dirname, 'my-game-build')), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: 'test-launch-path', + }], + }, + })).toThrow(/No more than 50 ingress rules are allowed per fleet, given 51/); + }); + }); + + describe('test ingress rules', () => { + let stack: cdk.Stack; + let fleet: gamelift.BuildFleet; + + beforeEach(() => { + stack = new cdk.Stack(); + fleet = new gamelift.BuildFleet(stack, 'MyBuildFleet', { + fleetName: 'test-fleet', + content: gamelift.Build.fromAsset(stack, 'Build', path.join(__dirname, 'my-game-build')), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: 'test-launch-path', + }], + }, + }); + }); + + test('add single tcp port ingress rule', () => { + // WHEN + fleet.addIngressRule(gamelift.Peer.anyIpv4(), gamelift.Port.tcp(144)); + + // THEN + Template.fromStack(stack).hasResource('AWS::GameLift::Fleet', { + Properties: + { + EC2InboundPermissions: [ + { + IpRange: '0.0.0.0/0', + FromPort: 144, + ToPort: 144, + Protocol: 'TCP', + }, + ], + }, + }); + }); + + test('add tcp port range ingress rule', () => { + // WHEN + fleet.addIngressRule(gamelift.Peer.anyIpv4(), gamelift.Port.tcpRange(100, 200)); + + // THEN + Template.fromStack(stack).hasResource('AWS::GameLift::Fleet', { + Properties: + { + + EC2InboundPermissions: [ + { + IpRange: '0.0.0.0/0', + FromPort: 100, + ToPort: 200, + Protocol: 'TCP', + }, + ], + }, + }); + }); + + test('add single udp port ingress rule', () => { + // WHEN + fleet.addIngressRule(gamelift.Peer.anyIpv4(), gamelift.Port.udp(144)); + + // THEN + Template.fromStack(stack).hasResource('AWS::GameLift::Fleet', { + Properties: + { + + EC2InboundPermissions: [ + { + IpRange: '0.0.0.0/0', + FromPort: 144, + ToPort: 144, + Protocol: 'UDP', + }, + ], + + }, + }); + }); + + test('add udp port range ingress rule', () => { + // WHEN + fleet.addIngressRule(gamelift.Peer.anyIpv4(), gamelift.Port.udpRange(100, 200)); + + // THEN + Template.fromStack(stack).hasResource('AWS::GameLift::Fleet', { + Properties: + { + + EC2InboundPermissions: [ + { + IpRange: '0.0.0.0/0', + FromPort: 100, + ToPort: 200, + Protocol: 'UDP', + }, + ], + + }, + }); + }); + + test('add all tcp ingress rule', () => { + // WHEN + fleet.addIngressRule(gamelift.Peer.ipv4('1.2.3.4/5'), gamelift.Port.allTcp()); + + // THEN + Template.fromStack(stack).hasResource('AWS::GameLift::Fleet', { + Properties: + { + + EC2InboundPermissions: [ + { + IpRange: '1.2.3.4/5', + FromPort: 1026, + ToPort: 60000, + Protocol: 'TCP', + }, + ], + + }, + }); + }); + + test('add all udp ingress rule', () => { + // WHEN + fleet.addIngressRule(gamelift.Peer.ipv4('1.2.3.4/5'), gamelift.Port.allUdp()); + + // THEN + Template.fromStack(stack).hasResource('AWS::GameLift::Fleet', { + Properties: + { + + EC2InboundPermissions: [ + { + IpRange: '1.2.3.4/5', + FromPort: 1026, + ToPort: 60000, + Protocol: 'UDP', + }, + ], + + }, + }); + }); + + test('add invalid IPv4 CIDR address', () => { + // WHEN + expect(() => fleet.addIngressRule(gamelift.Peer.ipv4('1.2.3/23'), gamelift.Port.tcp(144))) + .toThrowError('Invalid IPv4 CIDR: \"1.2.3/23\"'); + }); + + test('add IPv4 CIDR address without mask', () => { + // WHEN + expect(() => fleet.addIngressRule(gamelift.Peer.ipv4('1.2.3.4'), gamelift.Port.tcp(144))) + .toThrowError('CIDR mask is missing in IPv4: \"1.2.3.4\". Did you mean \"1.2.3.4/32\"?'); + }); + + test('add too much ingress rules', () => { + for (let i = 0; i < 50; i++) { + fleet.addIngressRule(gamelift.Peer.anyIpv4(), gamelift.Port.tcpRange(100, 200)); + } + expect(() => fleet.addIngressRule(gamelift.Peer.anyIpv4(), gamelift.Port.tcp(144))) + .toThrowError('No more than 50 ingress rules are allowed per fleet'); + }); + + }); + + describe('add locations', () => { + let stack: cdk.Stack; + let fleet: gamelift.BuildFleet; + + beforeEach(() => { + stack = new cdk.Stack(); + fleet = new gamelift.BuildFleet(stack, 'MyBuildFleet', { + fleetName: 'test-fleet', + content: gamelift.Build.fromAsset(stack, 'Build', path.join(__dirname, 'my-game-build')), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: 'test-launch-path', + }], + }, + }); + }); + + test('add new location', () => { + // Add a new location + fleet.addLocation('eu-west-1'); + + Template.fromStack(stack).hasResource('AWS::GameLift::Fleet', { + Properties: + { + Locations: [{ + Location: 'eu-west-1', + }], + }, + }); + }); + + test('add new location with capacity', () => { + // Add a new location + fleet.addLocation('eu-west-1', 3, 1, 4); + + Template.fromStack(stack).hasResource('AWS::GameLift::Fleet', { + Properties: + { + Locations: [{ + Location: 'eu-west-1', + LocationCapacity: { + DesiredEC2Instances: 3, + MinSize: 1, + MaxSize: 4, + }, + }], + }, + }); + }); + }); + + describe('test import methods', () => { + test('BuildFleet.fromBuildFleetArn', () => { + // GIVEN + const stack2 = new cdk.Stack(); + + // WHEN + const imported = gamelift.BuildFleet.fromBuildFleetArn(stack2, 'Imported', 'arn:aws:gamelift:us-east-1:123456789012:fleet/sample-fleet-id'); + + // THEN + expect(imported.fleetArn).toEqual('arn:aws:gamelift:us-east-1:123456789012:fleet/sample-fleet-id'); + expect(imported.fleetId).toEqual('sample-fleet-id'); + expect(imported.grantPrincipal).toEqual(new iam.UnknownPrincipal({ resource: imported })); + }); + + test('BuildFleet.fromFleetId', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const imported = gamelift.BuildFleet.fromBuildFleetId(stack, 'Imported', 'sample-fleet-id'); + + // THEN + expect(stack.resolve(imported.fleetArn)).toStrictEqual({ + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':gamelift:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':fleet/sample-fleet-id', + ]], + }); + expect(stack.resolve(imported.fleetId)).toStrictEqual('sample-fleet-id'); + expect(imported.grantPrincipal).toEqual(new iam.UnknownPrincipal({ resource: imported })); + }); + }); + + test('grant provides access to fleet', () => { + const stack = new cdk.Stack(); + + const role = new iam.Role(stack, 'Role', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + + const fleet = new gamelift.BuildFleet(stack, 'MyBuildFleet', { + fleetName: 'test-fleet', + content: gamelift.Build.fromAsset(stack, 'Build', path.join(__dirname, 'my-game-build')), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: 'test-launch-path', + }], + }, + role: role, + }); + + fleet.grant(role, 'gamelift:ListFleets'); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + Match.objectLike({ + Action: 'gamelift:ListFleets', + Resource: stack.resolve(fleet.fleetArn), + }), + ], + }, + Roles: [stack.resolve(role.roleName)], + }); + }); + + describe('metric methods provide a Metric with configured and attached properties', () => { + let stack: cdk.Stack; + let fleet: gamelift.BuildFleet; + + beforeEach(() => { + stack = new cdk.Stack(undefined, undefined, { env: { account: '000000000000', region: 'us-west-1' } }); + fleet = new gamelift.BuildFleet(stack, 'MyBuildFleet', { + fleetName: 'test-fleet', + content: gamelift.Build.fromAsset(stack, 'Build', path.join(__dirname, 'my-game-build')), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: 'test-launch-path', + }], + }, + }); + }); + + test('metric', () => { + const metric = fleet.metric('ActiveInstances'); + + expect(metric).toMatchObject({ + account: stack.account, + region: stack.region, + namespace: 'AWS/GameLift', + metricName: 'ActiveInstances', + dimensions: { + FleetId: fleet.fleetId, + }, + }); + }); + + test('metricActiveInstances', () => { + const metric = fleet.metricActiveInstances(); + + expect(metric).toMatchObject({ + account: stack.account, + region: stack.region, + namespace: 'AWS/GameLift', + metricName: 'ActiveInstances', + statistic: cloudwatch.Statistic.AVERAGE, + dimensions: { + FleetId: fleet.fleetId, + }, + }); + }); + + test('metricPercentIdleInstances', () => { + const metric = fleet.metricPercentIdleInstances(); + + expect(metric).toMatchObject({ + account: stack.account, + region: stack.region, + namespace: 'AWS/GameLift', + metricName: 'PercentIdleInstances', + statistic: cloudwatch.Statistic.AVERAGE, + dimensions: { + FleetId: fleet.fleetId, + }, + }); + }); + + test('metricDesiredInstances', () => { + const metric = fleet.metricDesiredInstances(); + + expect(metric).toMatchObject({ + account: stack.account, + region: stack.region, + namespace: 'AWS/GameLift', + metricName: 'DesiredInstances', + statistic: cloudwatch.Statistic.AVERAGE, + dimensions: { + FleetId: fleet.fleetId, + }, + }); + }); + + test('metricIdleInstances', () => { + const metric = fleet.metricIdleInstances(); + + expect(metric).toMatchObject({ + account: stack.account, + region: stack.region, + namespace: 'AWS/GameLift', + metricName: 'IdleInstances', + statistic: cloudwatch.Statistic.AVERAGE, + dimensions: { + FleetId: fleet.fleetId, + }, + }); + }); + + test('metricInstanceInterruptions', () => { + const metric = fleet.metricInstanceInterruptions(); + + expect(metric).toMatchObject({ + account: stack.account, + region: stack.region, + namespace: 'AWS/GameLift', + metricName: 'InstanceInterruptions', + statistic: cloudwatch.Statistic.SUM, + dimensions: { + FleetId: fleet.fleetId, + }, + }); + }); + + test('metricMaxInstances', () => { + const metric = fleet.metricMaxInstances(); + + expect(metric).toMatchObject({ + account: stack.account, + region: stack.region, + namespace: 'AWS/GameLift', + metricName: 'MaxInstances', + statistic: cloudwatch.Statistic.AVERAGE, + dimensions: { + FleetId: fleet.fleetId, + }, + }); + }); + + test('metricMinInstances', () => { + const metric = fleet.metricMinInstances(); + + expect(metric).toMatchObject({ + account: stack.account, + region: stack.region, + namespace: 'AWS/GameLift', + metricName: 'MinInstances', + statistic: cloudwatch.Statistic.AVERAGE, + dimensions: { + FleetId: fleet.fleetId, + }, + }); + }); + }); + + describe('test vpc peering', () => { + let warningMessage: string; + + beforeEach(() => { + warningMessage = 'To authorize the VPC peering, call the GameLift service API'; + }); + + test('add vpc peering', () => { + const stack = new cdk.Stack(); + + const vpc = new ec2.Vpc(stack, 'Vpc'); + + new gamelift.BuildFleet(stack, 'MyBuildFleet', { + fleetName: 'test-fleet', + content: gamelift.Build.fromAsset(stack, 'Build', path.join(__dirname, 'my-game-build')), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: 'test-launch-path', + }], + }, + peerVpc: vpc, + }); + + Template.fromStack(stack).hasResource('AWS::GameLift::Fleet', { + Properties: + { + PeerVpcAwsAccountId: { Ref: 'AWS::AccountId' }, + PeerVpcId: { Ref: 'Vpc8378EB38' }, + }, + }); + }); + + test('check warning message', () => { + const stack = new cdk.Stack(); + + const vpc = new ec2.Vpc(stack, 'Vpc'); + + new gamelift.BuildFleet(stack, 'MyBuildFleet', { + fleetName: 'test-fleet', + content: gamelift.Build.fromAsset(stack, 'Build', path.join(__dirname, 'my-game-build')), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C4, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + serverProcesses: [{ + launchPath: 'test-launch-path', + }], + }, + peerVpc: vpc, + }); + Annotations.fromStack(stack).hasWarning('/Default/MyBuildFleet', Match.stringLikeRegexp(warningMessage)); + }); + }); + + +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/test/build.test.ts b/packages/@aws-cdk/aws-gamelift/test/build.test.ts index a206119abddb7..5ea79a17694c3 100644 --- a/packages/@aws-cdk/aws-gamelift/test/build.test.ts +++ b/packages/@aws-cdk/aws-gamelift/test/build.test.ts @@ -85,7 +85,7 @@ describe('build', () => { Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', { StorageLocation: { Bucket: { - Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3Bucket72AA8348', + Ref: 'AssetParametersb95e4173bc399a8f686a4951aa26e01de1ed1e9d981ee1a7f18a15512dbdcb37S3Bucket3626B74C', }, }, }); diff --git a/packages/@aws-cdk/aws-gamelift/test/content.test.ts b/packages/@aws-cdk/aws-gamelift/test/content.test.ts index 226859dcab37a..a88ed8a24876e 100644 --- a/packages/@aws-cdk/aws-gamelift/test/content.test.ts +++ b/packages/@aws-cdk/aws-gamelift/test/content.test.ts @@ -5,7 +5,7 @@ import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import * as gamelift from '../lib'; -describe('Code', () => { +describe('Content', () => { let stack: cdk.Stack; let content: gamelift.Content; @@ -82,7 +82,7 @@ describe('Code', () => { Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', { StorageLocation: { Bucket: { - Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3Bucket72AA8348', + Ref: 'AssetParametersb95e4173bc399a8f686a4951aa26e01de1ed1e9d981ee1a7f18a15512dbdcb37S3Bucket3626B74C', }, Key: { 'Fn::Join': [ @@ -95,7 +95,7 @@ describe('Code', () => { 'Fn::Split': [ '||', { - Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3VersionKey720D3160', + Ref: 'AssetParametersb95e4173bc399a8f686a4951aa26e01de1ed1e9d981ee1a7f18a15512dbdcb37S3VersionKey75334BA8', }, ], }, @@ -108,7 +108,7 @@ describe('Code', () => { 'Fn::Split': [ '||', { - Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3VersionKey720D3160', + Ref: 'AssetParametersb95e4173bc399a8f686a4951aa26e01de1ed1e9d981ee1a7f18a15512dbdcb37S3VersionKey75334BA8', }, ], }, @@ -145,7 +145,7 @@ describe('Code', () => { }, ':s3:::', { - Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3Bucket72AA8348', + Ref: 'AssetParametersb95e4173bc399a8f686a4951aa26e01de1ed1e9d981ee1a7f18a15512dbdcb37S3Bucket3626B74C', }, '/', { @@ -155,7 +155,7 @@ describe('Code', () => { 'Fn::Split': [ '||', { - Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3VersionKey720D3160', + Ref: 'AssetParametersb95e4173bc399a8f686a4951aa26e01de1ed1e9d981ee1a7f18a15512dbdcb37S3VersionKey75334BA8', }, ], }, @@ -168,7 +168,7 @@ describe('Code', () => { 'Fn::Split': [ '||', { - Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3VersionKey720D3160', + Ref: 'AssetParametersb95e4173bc399a8f686a4951aa26e01de1ed1e9d981ee1a7f18a15512dbdcb37S3VersionKey75334BA8', }, ], }, @@ -190,7 +190,7 @@ describe('Code', () => { test('with an unsupported file path throws', () => { // GIVEN - const fileAsset = gamelift.Content.fromAsset(path.join(__dirname, 'my-game-build', 'index.js')); + const fileAsset = gamelift.Content.fromAsset(path.join(__dirname, 'my-game-build', 'TestApplicationServer')); // THEN expect(() => new gamelift.Build(stack, 'Build1', { content: fileAsset })) @@ -206,7 +206,7 @@ describe('Code', () => { }); const StorageLocation = { Bucket: { - Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3Bucket72AA8348', + Ref: 'AssetParametersb95e4173bc399a8f686a4951aa26e01de1ed1e9d981ee1a7f18a15512dbdcb37S3Bucket3626B74C', }, Key: { 'Fn::Join': [ @@ -219,7 +219,7 @@ describe('Code', () => { 'Fn::Split': [ '||', { - Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3VersionKey720D3160', + Ref: 'AssetParametersb95e4173bc399a8f686a4951aa26e01de1ed1e9d981ee1a7f18a15512dbdcb37S3VersionKey75334BA8', }, ], }, @@ -232,7 +232,7 @@ describe('Code', () => { 'Fn::Split': [ '||', { - Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3VersionKey720D3160', + Ref: 'AssetParametersb95e4173bc399a8f686a4951aa26e01de1ed1e9d981ee1a7f18a15512dbdcb37S3VersionKey75334BA8', }, ], }, diff --git a/packages/@aws-cdk/aws-gamelift/test/fleet-base.test.ts b/packages/@aws-cdk/aws-gamelift/test/fleet-base.test.ts new file mode 100644 index 0000000000000..69b074fdd6610 --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/fleet-base.test.ts @@ -0,0 +1,65 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; +import * as gamelift from '../lib'; + +describe('Fleet base', () => { + describe('FleetBase.fromFleetAttributes()', () => { + let stack: cdk.Stack; + const fleetId = 'fleet-test-identifier'; + const fleetArn = `arn:aws:gamelift:fleet-region:123456789012:fleet/${fleetId}`; + + beforeEach(() => { + const app = new cdk.App(); + stack = new cdk.Stack(app, 'Base', { + env: { account: '111111111111', region: 'stack-region' }, + }); + }); + + describe('', () => { + test('with required attrs only', () => { + const importedFleet = gamelift.FleetBase.fromFleetAttributes(stack, 'ImportedScript', { fleetArn }); + + expect(importedFleet.fleetId).toEqual(fleetId); + expect(importedFleet.fleetArn).toEqual(fleetArn); + expect(importedFleet.env.account).toEqual('123456789012'); + expect(importedFleet.env.region).toEqual('fleet-region'); + expect(importedFleet.grantPrincipal).toEqual(new iam.UnknownPrincipal({ resource: importedFleet })); + }); + + test('with all attrs', () => { + const role = iam.Role.fromRoleArn(stack, 'Role', 'arn:aws:iam::123456789012:role/TestRole'); + const importedFleet = gamelift.FleetBase.fromFleetAttributes(stack, 'ImportedScript', { fleetArn, role }); + + expect(importedFleet.fleetId).toEqual(fleetId); + expect(importedFleet.grantPrincipal).toEqual(role); + }); + + test('with missing attrs', () => { + const role = iam.Role.fromRoleArn(stack, 'Role', 'arn:aws:iam::123456789012:role/TestRole'); + expect(() => gamelift.FleetBase.fromFleetAttributes(stack, 'ImportedScript', { role })) + .toThrow(/Either fleetId or fleetArn must be provided in FleetAttributes/); + }); + + test('with invalid ARN', () => { + expect(() => gamelift.FleetBase.fromFleetAttributes(stack, 'ImportedScript', { fleetArn: 'arn:aws:gamelift:fleet-region:123456789012:fleet' })) + .toThrow(/No fleet identifier found in ARN: 'arn:aws:gamelift:fleet-region:123456789012:fleet'/); + }); + }); + + describe('for a fleet in a different account and region', () => { + let fleet: gamelift.IFleet; + + beforeEach(() => { + fleet = gamelift.FleetBase.fromFleetAttributes(stack, 'Fleet', { fleetArn }); + }); + + test("the fleet's region is taken from the ARN", () => { + expect(fleet.env.region).toBe('fleet-region'); + }); + + test("the fleet's account is taken from the ARN", () => { + expect(fleet.env.account).toBe('123456789012'); + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/BuildFleetDefaultTestDeployAssert21E515CD.assets.json b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/BuildFleetDefaultTestDeployAssert21E515CD.assets.json new file mode 100644 index 0000000000000..b65deb8cb34dd --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/BuildFleetDefaultTestDeployAssert21E515CD.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "BuildFleetDefaultTestDeployAssert21E515CD.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/BuildFleetDefaultTestDeployAssert21E515CD.template.json b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/BuildFleetDefaultTestDeployAssert21E515CD.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/BuildFleetDefaultTestDeployAssert21E515CD.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/asset.b9a6ac85861c7bf3d745d9866a46a450a1b14afa77e28d2c2767e74ce4e37c03/.DS_Store b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/asset.b9a6ac85861c7bf3d745d9866a46a450a1b14afa77e28d2c2767e74ce4e37c03/.DS_Store new file mode 100644 index 0000000000000..baab42abfdf28 Binary files /dev/null and b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/asset.b9a6ac85861c7bf3d745d9866a46a450a1b14afa77e28d2c2767e74ce4e37c03/.DS_Store differ diff --git a/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/asset.b9a6ac85861c7bf3d745d9866a46a450a1b14afa77e28d2c2767e74ce4e37c03/TestApplicationServer b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/asset.b9a6ac85861c7bf3d745d9866a46a450a1b14afa77e28d2c2767e74ce4e37c03/TestApplicationServer new file mode 100755 index 0000000000000..a4f885388c109 Binary files /dev/null and b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/asset.b9a6ac85861c7bf3d745d9866a46a450a1b14afa77e28d2c2767e74ce4e37c03/TestApplicationServer differ diff --git a/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/asset.b9a6ac85861c7bf3d745d9866a46a450a1b14afa77e28d2c2767e74ce4e37c03/install.sh b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/asset.b9a6ac85861c7bf3d745d9866a46a450a1b14afa77e28d2c2767e74ce4e37c03/install.sh new file mode 100755 index 0000000000000..1ef448e39373c --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/asset.b9a6ac85861c7bf3d745d9866a46a450a1b14afa77e28d2c2767e74ce4e37c03/install.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# make sure the gameserver is executable +/usr/bin/chmod +x /local/game/TestApplicationServer +exit 0 diff --git a/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/aws-gamelift-build-fleet.assets.json b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/aws-gamelift-build-fleet.assets.json new file mode 100644 index 0000000000000..c23a4362ad5e5 --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/aws-gamelift-build-fleet.assets.json @@ -0,0 +1,32 @@ +{ + "version": "21.0.0", + "files": { + "b9a6ac85861c7bf3d745d9866a46a450a1b14afa77e28d2c2767e74ce4e37c03": { + "source": { + "path": "asset.b9a6ac85861c7bf3d745d9866a46a450a1b14afa77e28d2c2767e74ce4e37c03", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "b9a6ac85861c7bf3d745d9866a46a450a1b14afa77e28d2c2767e74ce4e37c03.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "2e5a0771267aced2e9b389ead86c38dca7b794c5a9f6cc2011d0fb1301a533ec": { + "source": { + "path": "aws-gamelift-build-fleet.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "2e5a0771267aced2e9b389ead86c38dca7b794c5a9f6cc2011d0fb1301a533ec.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/aws-gamelift-build-fleet.template.json b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/aws-gamelift-build-fleet.template.json new file mode 100644 index 0000000000000..232caea84f1d6 --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/aws-gamelift-build-fleet.template.json @@ -0,0 +1,178 @@ +{ + "Resources": { + "BuildServiceRole1F57E904": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "gamelift.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "BuildServiceRoleDefaultPolicyCB7101C6": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject", + "s3:GetObjectVersion" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "/b9a6ac85861c7bf3d745d9866a46a450a1b14afa77e28d2c2767e74ce4e37c03.zip" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "BuildServiceRoleDefaultPolicyCB7101C6", + "Roles": [ + { + "Ref": "BuildServiceRole1F57E904" + } + ] + } + }, + "Build45A36621": { + "Type": "AWS::GameLift::Build", + "Properties": { + "OperatingSystem": "AMAZON_LINUX_2", + "StorageLocation": { + "Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "Key": "b9a6ac85861c7bf3d745d9866a46a450a1b14afa77e28d2c2767e74ce4e37c03.zip", + "RoleArn": { + "Fn::GetAtt": [ + "BuildServiceRole1F57E904", + "Arn" + ] + } + } + }, + "DependsOn": [ + "BuildServiceRoleDefaultPolicyCB7101C6", + "BuildServiceRole1F57E904" + ] + }, + "BuildFleetServiceRole32D49FB4": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": [ + "ec2.amazonaws.com", + "gamelift.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + } + } + }, + "BuildFleet027ED403": { + "Type": "AWS::GameLift::Fleet", + "Properties": { + "BuildId": { + "Ref": "Build45A36621" + }, + "CertificateConfiguration": { + "CertificateType": "DISABLED" + }, + "EC2InboundPermissions": [ + { + "FromPort": 1935, + "IpRange": "0.0.0.0/0", + "Protocol": "TCP", + "ToPort": 1935 + } + ], + "EC2InstanceType": "c5.large", + "FleetType": "ON_DEMAND", + "InstanceRoleARN": { + "Fn::GetAtt": [ + "BuildFleetServiceRole32D49FB4", + "Arn" + ] + }, + "MaxSize": 1, + "MinSize": 0, + "Name": "test-fleet", + "NewGameSessionProtectionPolicy": "NoProtection", + "RuntimeConfiguration": { + "GameSessionActivationTimeoutSeconds": 300, + "MaxConcurrentGameSessionActivations": 1, + "ServerProcesses": [ + { + "ConcurrentExecutions": 1, + "LaunchPath": "/local/game/TestApplicationServer", + "Parameters": "port:1935 gameSessionLengthSeconds:20" + } + ] + } + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/cdk.out b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/integ.json b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/integ.json new file mode 100644 index 0000000000000..efefade1322e4 --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "21.0.0", + "testCases": { + "BuildFleet/DefaultTest": { + "stacks": [ + "aws-gamelift-build-fleet" + ], + "assertionStack": "BuildFleet/DefaultTest/DeployAssert", + "assertionStackName": "BuildFleetDefaultTestDeployAssert21E515CD" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/manifest.json b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/manifest.json new file mode 100644 index 0000000000000..96faab0e8bd38 --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/manifest.json @@ -0,0 +1,135 @@ +{ + "version": "21.0.0", + "artifacts": { + "aws-gamelift-build-fleet.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-gamelift-build-fleet.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-gamelift-build-fleet": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-gamelift-build-fleet.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/2e5a0771267aced2e9b389ead86c38dca7b794c5a9f6cc2011d0fb1301a533ec.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-gamelift-build-fleet.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-gamelift-build-fleet.assets" + ], + "metadata": { + "/aws-gamelift-build-fleet/Build/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "BuildServiceRole1F57E904" + } + ], + "/aws-gamelift-build-fleet/Build/ServiceRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "BuildServiceRoleDefaultPolicyCB7101C6" + } + ], + "/aws-gamelift-build-fleet/Build/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Build45A36621" + } + ], + "/aws-gamelift-build-fleet/BuildFleet/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "BuildFleetServiceRole32D49FB4" + } + ], + "/aws-gamelift-build-fleet/BuildFleet/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "BuildFleet027ED403" + } + ], + "/aws-gamelift-build-fleet/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-gamelift-build-fleet/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-gamelift-build-fleet" + }, + "BuildFleetDefaultTestDeployAssert21E515CD.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "BuildFleetDefaultTestDeployAssert21E515CD.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "BuildFleetDefaultTestDeployAssert21E515CD": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "BuildFleetDefaultTestDeployAssert21E515CD.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "BuildFleetDefaultTestDeployAssert21E515CD.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "BuildFleetDefaultTestDeployAssert21E515CD.assets" + ], + "metadata": { + "/BuildFleet/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/BuildFleet/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "BuildFleet/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/tree.json b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/tree.json new file mode 100644 index 0000000000000..1acae4807bbda --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.js.snapshot/tree.json @@ -0,0 +1,361 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "aws-gamelift-build-fleet": { + "id": "aws-gamelift-build-fleet", + "path": "aws-gamelift-build-fleet", + "children": { + "Build": { + "id": "Build", + "path": "aws-gamelift-build-fleet/Build", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "aws-gamelift-build-fleet/Build/ServiceRole", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-gamelift-build-fleet/Build/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "gamelift.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "aws-gamelift-build-fleet/Build/ServiceRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-gamelift-build-fleet/Build/ServiceRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject", + "s3:GetObjectVersion" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "/b9a6ac85861c7bf3d745d9866a46a450a1b14afa77e28d2c2767e74ce4e37c03.zip" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "BuildServiceRoleDefaultPolicyCB7101C6", + "roles": [ + { + "Ref": "BuildServiceRole1F57E904" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Content": { + "id": "Content", + "path": "aws-gamelift-build-fleet/Build/Content", + "children": { + "Stage": { + "id": "Stage", + "path": "aws-gamelift-build-fleet/Build/Content/Stage", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "aws-gamelift-build-fleet/Build/Content/AssetBucket", + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3-assets.Asset", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "aws-gamelift-build-fleet/Build/AssetBucket", + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketBase", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-gamelift-build-fleet/Build/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::GameLift::Build", + "aws:cdk:cloudformation:props": { + "operatingSystem": "AMAZON_LINUX_2", + "storageLocation": { + "bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "key": "b9a6ac85861c7bf3d745d9866a46a450a1b14afa77e28d2c2767e74ce4e37c03.zip", + "roleArn": { + "Fn::GetAtt": [ + "BuildServiceRole1F57E904", + "Arn" + ] + } + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-gamelift.CfnBuild", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-gamelift.Build", + "version": "0.0.0" + } + }, + "BuildFleet": { + "id": "BuildFleet", + "path": "aws-gamelift-build-fleet/BuildFleet", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "aws-gamelift-build-fleet/BuildFleet/ServiceRole", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-gamelift-build-fleet/BuildFleet/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": [ + "ec2.amazonaws.com", + "gamelift.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-gamelift-build-fleet/BuildFleet/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::GameLift::Fleet", + "aws:cdk:cloudformation:props": { + "buildId": { + "Ref": "Build45A36621" + }, + "certificateConfiguration": { + "certificateType": "DISABLED" + }, + "ec2InboundPermissions": [ + { + "protocol": "TCP", + "fromPort": 1935, + "toPort": 1935, + "ipRange": "0.0.0.0/0" + } + ], + "ec2InstanceType": "c5.large", + "fleetType": "ON_DEMAND", + "instanceRoleArn": { + "Fn::GetAtt": [ + "BuildFleetServiceRole32D49FB4", + "Arn" + ] + }, + "maxSize": 1, + "minSize": 0, + "name": "test-fleet", + "newGameSessionProtectionPolicy": "NoProtection", + "runtimeConfiguration": { + "gameSessionActivationTimeoutSeconds": 300, + "maxConcurrentGameSessionActivations": 1, + "serverProcesses": [ + { + "parameters": "port:1935 gameSessionLengthSeconds:20", + "launchPath": "/local/game/TestApplicationServer", + "concurrentExecutions": 1 + } + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-gamelift.CfnFleet", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-gamelift.BuildFleet", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "aws-gamelift-build-fleet/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "aws-gamelift-build-fleet/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "BuildFleet": { + "id": "BuildFleet", + "path": "BuildFleet", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "BuildFleet/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "BuildFleet/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.140" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "BuildFleet/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "BuildFleet/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "BuildFleet/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.140" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.ts b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.ts new file mode 100644 index 0000000000000..bace85bd30f0f --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/integ.build-fleet.ts @@ -0,0 +1,46 @@ +import * as path from 'path'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as cdk from '@aws-cdk/core'; +import { Duration } from '@aws-cdk/core'; +import { IntegTest } from '@aws-cdk/integ-tests'; +import { Construct } from 'constructs'; +import * as gamelift from '../lib'; + +class TestStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const build = new gamelift.Build(this, 'Build', { + content: gamelift.Content.fromAsset(path.join(__dirname, 'my-game-build')), + operatingSystem: gamelift.OperatingSystem.AMAZON_LINUX_2, + }); + + new gamelift.BuildFleet(this, 'BuildFleet', { + fleetName: 'test-fleet', + content: build, + ingressRules: [{ + source: gamelift.Peer.anyIpv4(), + port: gamelift.Port.tcp(1935), + }], + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.LARGE), + runtimeConfiguration: { + gameSessionActivationTimeout: Duration.seconds(300), + maxConcurrentGameSessionActivations: 1, + serverProcesses: [{ + launchPath: '/local/game/TestApplicationServer', + parameters: 'port:1935 gameSessionLengthSeconds:20', + concurrentExecutions: 1, + }], + }, + }); + } +} + +// Beginning of the test suite +const app = new cdk.App(); +const stack = new TestStack(app, 'aws-gamelift-build-fleet'); +new IntegTest(app, 'BuildFleet', { + testCases: [stack], +}); + +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/test/my-game-build.zip b/packages/@aws-cdk/aws-gamelift/test/my-game-build.zip deleted file mode 100644 index 4a13be08c2721..0000000000000 Binary files a/packages/@aws-cdk/aws-gamelift/test/my-game-build.zip and /dev/null differ diff --git a/packages/@aws-cdk/aws-gamelift/test/my-game-build/TestApplicationServer b/packages/@aws-cdk/aws-gamelift/test/my-game-build/TestApplicationServer new file mode 100755 index 0000000000000..a4f885388c109 Binary files /dev/null and b/packages/@aws-cdk/aws-gamelift/test/my-game-build/TestApplicationServer differ diff --git a/packages/@aws-cdk/aws-gamelift/test/my-game-build/index.js b/packages/@aws-cdk/aws-gamelift/test/my-game-build/index.js deleted file mode 100644 index 73c02658c48d9..0000000000000 --- a/packages/@aws-cdk/aws-gamelift/test/my-game-build/index.js +++ /dev/null @@ -1 +0,0 @@ -console.log('Hello World'); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/test/my-game-build/install.sh b/packages/@aws-cdk/aws-gamelift/test/my-game-build/install.sh new file mode 100755 index 0000000000000..1ef448e39373c --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/my-game-build/install.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# make sure the gameserver is executable +/usr/bin/chmod +x /local/game/TestApplicationServer +exit 0