Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gamelift): add BuildFleet L2 Construct for GameLift #22735

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Implement CfnFleet resource creation
  • Loading branch information
stevehouel committed Oct 27, 2022
commit 5a43c9195bc93961ad8afa4506c89b0f6d9226cc
102 changes: 47 additions & 55 deletions packages/@aws-cdk/aws-gamelift/lib/build-fleet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ 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, FleetAttributes } from './fleet-base';
import { FleetBase, FleetProps, IFleet } from './fleet-base';
import { CfnFleet } from './gamelift.generated';
import { InboundPermission } from './inbound-permission';

/**
*
* Represents a GameLift Fleet used to run a custom game build.
*/
export interface IBuildFleet extends IFleet {}

Expand Down Expand Up @@ -46,42 +47,15 @@ export class BuildFleet extends FleetBase implements IBuildFleet {
/**
* Import an existing fleet from its identifier.
*/
static fromFleetId(scope: Construct, id: string, fleetId: string): IBuildFleet {
return this.fromFleetAttributes(scope, id, { fleetId });
static fromBuildFleetId(scope: Construct, id: string, buildFleetId: string): IBuildFleet {
return this.fromFleetAttributes(scope, id, { fleetId: buildFleetId });
}

/**
* Import an existing fleet from its ARN.
*/
static fromFleetArn(scope: Construct, id: string, fleetArn: string): IBuildFleet {
return this.fromFleetAttributes(scope, id, { fleetArn });
}

/**
* Import an existing fleet from its attributes.
*/
static fromFleetAttributes(scope: Construct, id: string, attrs: FleetAttributes): IBuildFleet {
if (!attrs.fleetId && !attrs.fleetId) {
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 });
}
return new Import(scope, id);
static fromBuildFleetArn(scope: Construct, id: string, buildFleetArn: string): IBuildFleet {
return this.fromFleetAttributes(scope, id, { fleetArn: buildFleetArn });
}

/**
Expand All @@ -90,14 +64,14 @@ export class BuildFleet extends FleetBase implements IBuildFleet {
public readonly fleetId: string;

/**
* The name of the fleet.
* The ARN of the fleet.
*/
public readonly fleetName: string;
public readonly fleetArn: string;

/**
* The ARN of the fleet.
* The build content of the fleet
*/
public readonly fleetArn: string;
public readonly content: IBuild;

/**
* The IAM role GameLift assumes by fleet instances to access AWS ressources.
Expand Down Expand Up @@ -137,23 +111,41 @@ export class BuildFleet extends FleetBase implements IBuildFleet {
if (props.locations && props.locations?.length > 100) {
throw new Error(`No more than 100 locations are allowed per fleet, given ${props.locations.length}`);
}
}
}

/**
* Given an opaque (token) ARN, returns a CloudFormation expression that extracts the script
* identifier from the ARN.
*
* Script ARNs look like this:
*
* arn:aws:gamelift:region:account-id:script/script-identifier
*
* ..which means that in order to extract the `script-identifier` component from the ARN, we can
* split the ARN using ":" and select the component in index 5 then split using "/" and select the component in index 1.
*
* @returns the script identifier from his ARN
*/
function extractIdFromArn(arn: string) {
const splitValue = cdk.Fn.select(5, cdk.Fn.split(':', arn));
return cdk.Fn.select(1, cdk.Fn.split('/', splitValue));
this.content = props.content;
this.role = props.role ?? new iam.Role(this, 'ServiceRole', {
assumedBy: new iam.ServicePrincipal('gamelift.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: props.inboundPermission.map((value) => value.toJson()),
ec2InstanceType: props.instanceType.toString(),
fleetType: props.useSpot ? 'SPOT' : 'ON_DEMAND',
instanceRoleArn: this.role.roleArn,
locations: props.locations && props.locations.map((value) => value.toJson()),
maxSize: props.maxSize,
minSize: props.minSize,
name: this.physicalName,
newGameSessionProtectionPolicy: props.protectNewGameSession ? 'FULL_PROTECTION' : 'NO_PROTECTION',
peerVpcAwsAccountId: props.peerVpc && props.peerVpc.env.account,
peerVpcId: props.peerVpc && props.peerVpc.vpcId,
resourceCreationLimitPolicy: props.resourceCreationLimitPolicy && props.resourceCreationLimitPolicy.toJson(),
runtimeConfiguration: props.runtimeConfigurations.toJson(),
});

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,
});
}
}
46 changes: 38 additions & 8 deletions packages/@aws-cdk/aws-gamelift/lib/fleet-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ 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 { Location } from './location';
import { ResourceCreationLimitPolicy } from './resource-creation-limit-policy';
import { RuntimeConfiguration } from './runtime-configuration';

/**
Expand Down Expand Up @@ -169,7 +171,7 @@ export interface FleetProps {
*
* @see https://docs.aws.amazon.com/gamelift/latest/developerguide/fleets-multiprocess.html
*/
readonly runtimeConfigurations: RuntimeConfiguration[];
readonly runtimeConfigurations: RuntimeConfiguration;

/**
* A set of remote locations to deploy additional instances to and manage as part of the fleet.
Expand All @@ -180,6 +182,13 @@ export interface FleetProps {
* @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;
}

/**
Expand Down Expand Up @@ -216,6 +225,34 @@ export interface FleetAttributes {
* 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.fleetId) {
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 });
}
return new Import(scope, id);
}

/**
* The Identifier of the fleet.
*/
Expand Down Expand Up @@ -256,11 +293,4 @@ export abstract class FleetBase extends cdk.Resource implements IFleet {
'See: https://docs.aws.amazon.com/gamelift/latest/developerguide/vpc-peering.html',
].join('\n'));
}

private cannedMetric(fn: (dims: { FleetId: string }) => cloudwatch.MetricProps, props?: cloudwatch.MetricOptions): cloudwatch.Metric {
return new cloudwatch.Metric({
...fn({ FleetId: this.fleetId }),
...props,
}).attachTo(this);
}
}
6 changes: 5 additions & 1 deletion packages/@aws-cdk/aws-gamelift/lib/inbound-permission.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as cdk from '@aws-cdk/core';
import { CfnFleet } from './gamelift.generated';

/**
* Protocol for use in Connection Rules
Expand Down Expand Up @@ -227,7 +228,10 @@ export interface InboundPermissionConfig {
export class InboundPermission {
constructor(private readonly props: InboundPermissionConfig) {}

public toJson() {
/**
* Convert a inbound permission entity to its Json representation
*/
public toJson(): CfnFleet.IpPermissionProperty {
return {
...this.props.source.toJson(),
...this.props.port.toJson(),
Expand Down
11 changes: 9 additions & 2 deletions packages/@aws-cdk/aws-gamelift/lib/location.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { CfnFleet } from './gamelift.generated';

/**
* Configuration of a location capacity
*/
Expand Down Expand Up @@ -28,7 +30,10 @@ export interface LocationCapacityConfig {
export class LocationCapacity {
constructor(private readonly props: LocationCapacityConfig) {}

public toJson() {
/**
* Convert a location capacity entity to its Json representation
*/
public toJson(): CfnFleet.LocationCapacityProperty {
return {
desiredEc2Instances: this.props.desiredCapacity,
minSize: this.props.minSize ? this.props.minSize : 0,
Expand All @@ -48,6 +53,8 @@ export interface LocationConfig {
/**
* 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;
}
Expand All @@ -62,7 +69,7 @@ export class Location {
/**
* Convert a location entity to its Json representation
*/
public toJson() {
public toJson(): CfnFleet.LocationConfigurationProperty {
return {
location: this.props.location,
locationCapacity: this.props.capacity && this.props.capacity.toJson(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Duration } from '@aws-cdk/core';
import { CfnFleet } from './gamelift.generated';

/**
* Configiuration of a resource creation limit policy
*/
export interface ResourceCreationLimitPolicyConfig {
/**
* 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?: Duration,
}

/**
* 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 class ResourceCreationLimitPolicy {
constructor(private readonly props: ResourceCreationLimitPolicyConfig) {}

/**
* Convert a resource creation limit policy entity to its Json representation
*/
public toJson(): CfnFleet.ResourceCreationLimitPolicyProperty {
return {
newGameSessionsPerCreator: this.props.newGameSessionsPerCreator,
policyPeriodInMinutes: this.props.policyPeriod && this.props.policyPeriod.toMinutes(),
};
}
}
7 changes: 4 additions & 3 deletions packages/@aws-cdk/aws-gamelift/lib/runtime-configuration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as cdk from '@aws-cdk/core';
import { CfnFleet } from './gamelift.generated';

/**
* Configuration of a fleet server process
Expand Down Expand Up @@ -39,7 +40,7 @@ export class ServerProcess {
/**
* Convert a Server process entity to its Json representation
*/
public toJson() {
public toJson(): CfnFleet.ServerProcessProperty {
return {
parameters: this.props.parameters,
launchPath: this.props.launchPath,
Expand Down Expand Up @@ -99,9 +100,9 @@ export class RuntimeConfiguration {
/**
* Convert a runtime configuration entity to its Json representation
*/
public toJson() {
public toJson(): CfnFleet.RuntimeConfigurationProperty {
return {
gameSessionActivationTimeout: this.props.gameSessionActivationTimeout && this.props.gameSessionActivationTimeout.toSeconds(),
gameSessionActivationTimeoutSeconds: this.props.gameSessionActivationTimeout && this.props.gameSessionActivationTimeout.toSeconds(),
maxConcurrentGameSessionActivations: this.props.maxConcurrentGameSessionActivations,
serverProcesses: this.props.serverProcesses && this.props.serverProcesses.map((serverProcess: ServerProcess) => serverProcess.toJson()),
};
Expand Down
19 changes: 0 additions & 19 deletions packages/@aws-cdk/aws-gamelift/lib/util.ts

This file was deleted.