Skip to content

Commit 983d26e

Browse files
authoredOct 4, 2022
feat(gamelift): add Build L2 constructs for GameLift (#22313)
Following [aws-cdk-rfcs](aws/aws-cdk-rfcs#436) I have written the first `Build` L2 resource which create a GameLift Build element based on a S3 location. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 63d3c54 commit 983d26e

File tree

20 files changed

+1419
-31
lines changed

20 files changed

+1419
-31
lines changed
 

‎packages/@aws-cdk/aws-gamelift/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,6 @@ junit.xml
2121
!**/*.integ.snapshot/**/asset.*/*.d.ts
2222

2323
!**/*.integ.snapshot/**/asset.*/**
24+
25+
#include game build js file
26+
!test/my-game-build/*.js

‎packages/@aws-cdk/aws-gamelift/README.md

+75-23
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,83 @@
99
>
1010
> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib
1111
12+
![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge)
13+
14+
> The APIs of higher level constructs in this module are experimental and under active development.
15+
> They are subject to non-backward compatible changes or removal in any future version. These are
16+
> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be
17+
> announced in the release notes. This means that while you may use them, you may need to update
18+
> your source code when upgrading to a newer version of this package.
19+
1220
---
1321

1422
<!--END STABILITY BANNER-->
1523

16-
This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project.
17-
18-
```ts nofixture
19-
import * as gamelift from '@aws-cdk/aws-gamelift';
24+
[Amazon GameLift](https://docs.aws.amazon.com/gamelift/latest/developerguide/gamelift-intro.html) is a service used
25+
to deploy, operate, and scale dedicated, low-cost servers in the cloud for session-based multiplayer games. Built
26+
on AWS global computing infrastructure, GameLift helps deliver high-performance, high-reliability game servers
27+
while dynamically scaling your resource usage to meet worldwide player demand.
28+
29+
GameLift is composed of three main components:
30+
31+
* GameLift FlexMatch which is a customizable matchmaking service for
32+
multiplayer games. With FlexMatch, you can
33+
build a custom set of rules that defines what a multiplayer match looks like
34+
for your game, and determines how to
35+
evaluate and select compatible players for each match. You can also customize
36+
key aspects of the matchmaking
37+
process to fit your game, including fine-tuning the matching algorithm.
38+
39+
* GameLift hosting for custom or realtime servers which helps you deploy,
40+
operate, and scale dedicated game servers. It regulates the resources needed to
41+
host games, finds available game servers to host new game sessions, and puts
42+
players into games.
43+
44+
* GameLift FleetIQ to optimize the use of low-cost Amazon Elastic Compute Cloud
45+
(Amazon EC2) Spot Instances for cloud-based game hosting. With GameLift
46+
FleetIQ, you can work directly with your hosting resources in Amazon EC2 and
47+
Amazon EC2 Auto Scaling while taking advantage of GameLift optimizations to
48+
deliver inexpensive, resilient game hosting for your players
49+
50+
This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. It allows you to define components for your matchmaking
51+
configuration or game server fleet management system.
52+
53+
## GameLift Hosting
54+
55+
### Defining a GameLift Fleet
56+
57+
GameLift helps you deploy, operate, and scale dedicated game servers for
58+
session-based multiplayer games. It helps you regulate the resources needed to
59+
host your games, finds available game servers to host new game sessions, and
60+
puts players into games.
61+
62+
### Uploading builds and scripts to GameLift
63+
64+
Before deploying your GameLift-enabled multiplayer game servers for hosting with the GameLift service, you need to upload
65+
your game server files. This section provides guidance on preparing and uploading custom game server build
66+
files or Realtime Servers server script files. When you upload files, you create a GameLift build or script resource, which
67+
you then deploy on fleets of hosting resources.
68+
69+
### Upload a custom server build to GameLift
70+
71+
Before uploading your configured game server to GameLift for hosting, package the game build files into a build directory.
72+
This directory must include all components required to run your game servers and host game sessions, including the following:
73+
74+
* Game server binaries – The binary files required to run the game server. A build can include binaries for multiple game
75+
servers built to run on the same platform. For a list of supported platforms, see [Download Amazon GameLift SDKs](https://docs.aws.amazon.com/gamelift/latest/developerguide/gamelift-supported.html).
76+
77+
* Dependencies – Any dependent files that your game server executables require to run. Examples include assets, configuration
78+
files, and dependent libraries.
79+
80+
* Install script – A script file to handle tasks that are required to fully install your game build on GameLift hosting
81+
servers. Place this file at the root of the build directory. GameLift runs the install script as part of fleet creation.
82+
83+
You can set up any application in your build, including your install script, to access your resources securely on other AWS
84+
services.
85+
86+
```ts
87+
declare const bucket: s3.Bucket;
88+
new gamelift.Build(this, 'Build', {
89+
content: gamelift.Content.fromBucket(bucket, "sample-asset-key")
90+
});
2091
```
21-
22-
<!--BEGIN CFNONLY DISCLAIMER-->
23-
24-
There are no official hand-written ([L2](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) constructs for this service yet. Here are some suggestions on how to proceed:
25-
26-
- Search [Construct Hub for GameLift construct libraries](https://constructs.dev/search?q=gamelift)
27-
- Use the automatically generated [L1](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_l1_using) constructs, in the same way you would use [the CloudFormation AWS::GameLift resources](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_GameLift.html) directly.
28-
29-
30-
<!--BEGIN CFNONLY DISCLAIMER-->
31-
32-
There are no hand-written ([L2](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) constructs for this service yet.
33-
However, you can still use the automatically generated [L1](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_l1_using) constructs, and use this service exactly as you would using CloudFormation directly.
34-
35-
For more information on the resources and properties available for this service, see the [CloudFormation documentation for AWS::GameLift](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_GameLift.html).
36-
37-
(Read the [CDK Contributing Guide](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and submit an RFC if you are interested in contributing to this construct library.)
38-
39-
<!--END CFNONLY DISCLAIMER-->
+207
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
import * as iam from '@aws-cdk/aws-iam';
2+
import * as s3 from '@aws-cdk/aws-s3';
3+
import * as s3_assets from '@aws-cdk/aws-s3-assets';
4+
import * as cdk from '@aws-cdk/core';
5+
import { Construct } from 'constructs';
6+
import { Content } from './content';
7+
import { CfnBuild } from './gamelift.generated';
8+
9+
/**
10+
* Represents a GameLift server build.
11+
*/
12+
export interface IBuild extends cdk.IResource, iam.IGrantable {
13+
14+
/**
15+
* The Identifier of the build.
16+
*
17+
* @attribute
18+
*/
19+
readonly buildId: string;
20+
}
21+
22+
/**
23+
* Base class for new and imported GameLift server build.
24+
*/
25+
export abstract class BuildBase extends cdk.Resource implements IBuild {
26+
/**
27+
* The Identifier of the build.
28+
*/
29+
public abstract readonly buildId: string;
30+
31+
public abstract readonly grantPrincipal: iam.IPrincipal;
32+
}
33+
34+
/**
35+
* The operating system that the game server binaries are built to run on.
36+
*/
37+
export enum OperatingSystem {
38+
AMAZON_LINUX = 'AMAZON_LINUX',
39+
AMAZON_LINUX_2 = 'AMAZON_LINUX_2',
40+
WINDOWS_2012 = 'WINDOWS_2012'
41+
}
42+
43+
44+
/**
45+
* Represents a Build content defined outside of this stack.
46+
*/
47+
export interface BuildAttributes {
48+
/**
49+
* The identifier of the build
50+
*/
51+
readonly buildId: string;
52+
/**
53+
* The IAM role assumed by GameLift to access server build in S3.
54+
* @default - undefined
55+
*/
56+
readonly role?: iam.IRole;
57+
}
58+
59+
/**
60+
* Properties for a new build
61+
*/
62+
export interface BuildProps {
63+
/**
64+
* Name of this build
65+
*
66+
* @default No name
67+
*/
68+
readonly buildName?: string;
69+
70+
/**
71+
* Version of this build
72+
*
73+
* @default No version
74+
*/
75+
readonly buildVersion?: string;
76+
77+
/**
78+
* The operating system that the game server binaries are built to run on.
79+
*
80+
* @default No version
81+
*/
82+
readonly operatingSystem?: OperatingSystem;
83+
84+
/**
85+
* The game build file storage
86+
*/
87+
readonly content: Content;
88+
89+
/**
90+
* The IAM role assumed by GameLift to access server build in S3.
91+
* If providing a custom role, it needs to trust the GameLift service principal (gamelift.amazonaws.com) and be granted sufficient permissions
92+
* to have Read access to a specific key content into a specific S3 bucket.
93+
* Below an example of required permission:
94+
* {
95+
* "Version": "2012-10-17",
96+
* "Statement": [{
97+
* "Effect": "Allow",
98+
* "Action": [
99+
* "s3:GetObject",
100+
* "s3:GetObjectVersion"
101+
* ],
102+
* "Resource": "arn:aws:s3:::bucket-name/object-name"
103+
* }]
104+
*}
105+
*
106+
* @see https://docs.aws.amazon.com/gamelift/latest/developerguide/security_iam_id-based-policy-examples.html#security_iam_id-based-policy-examples-access-storage-loc
107+
*
108+
* @default - a role will be created with default permissions.
109+
*/
110+
readonly role?: iam.IRole;
111+
}
112+
113+
/**
114+
* A GameLift build, that is installed and runs on instances in an Amazon GameLift fleet. It consists of
115+
* a zip file with all of the components of the game server build.
116+
*
117+
* @see https://docs.aws.amazon.com/gamelift/latest/developerguide/gamelift-build-cli-uploading.html
118+
*
119+
* @resource AWS::GameLift::Build
120+
*/
121+
export class Build extends BuildBase {
122+
123+
/**
124+
* Create a new Build from s3 content
125+
*/
126+
static fromBucket(scope: Construct, id: string, bucket: s3.IBucket, key: string, objectVersion?: string) {
127+
return new Build(scope, id, {
128+
content: Content.fromBucket(bucket, key, objectVersion),
129+
});
130+
}
131+
132+
/**
133+
* Create a new Build from asset content
134+
*/
135+
static fromAsset(scope: Construct, id: string, path: string, options?: s3_assets.AssetOptions) {
136+
return new Build(scope, id, {
137+
content: Content.fromAsset(path, options),
138+
});
139+
}
140+
141+
/**
142+
* Import a build into CDK using its identifier
143+
*/
144+
static fromBuildId(scope: Construct, id: string, buildId: string): IBuild {
145+
return this.fromBuildAttributes(scope, id, { buildId });
146+
}
147+
148+
/**
149+
* Import an existing build from its attributes.
150+
*/
151+
static fromBuildAttributes(scope: Construct, id: string, attrs: BuildAttributes): IBuild {
152+
class Import extends BuildBase {
153+
public readonly buildId = attrs.buildId;
154+
public readonly grantPrincipal = attrs.role ?? new iam.UnknownPrincipal({ resource: this });
155+
}
156+
157+
return new Import(scope, id);
158+
}
159+
160+
/**
161+
* The Identifier of the build.
162+
*/
163+
public readonly buildId: string;
164+
165+
/**
166+
* The IAM role GameLift assumes to acccess server build content.
167+
*/
168+
public readonly role: iam.IRole;
169+
170+
/**
171+
* The principal this GameLift Build is using.
172+
*/
173+
public readonly grantPrincipal: iam.IPrincipal;
174+
175+
constructor(scope: Construct, id: string, props: BuildProps) {
176+
super(scope, id, {
177+
physicalName: props.buildName,
178+
});
179+
180+
if (props.buildName && !cdk.Token.isUnresolved(props.buildName)) {
181+
if (props.buildName.length > 1024) {
182+
throw new Error(`Build name can not be longer than 1024 characters but has ${props.buildName.length} characters.`);
183+
}
184+
}
185+
this.role = props.role ?? new iam.Role(this, 'ServiceRole', {
186+
assumedBy: new iam.ServicePrincipal('gamelift.amazonaws.com'),
187+
});
188+
this.grantPrincipal = this.role;
189+
const content = props.content.bind(this, this.role);
190+
191+
const resource = new CfnBuild(this, 'Resource', {
192+
name: props.buildName,
193+
version: props.buildVersion,
194+
operatingSystem: props.operatingSystem,
195+
storageLocation: {
196+
bucket: content.s3Location && content.s3Location.bucketName,
197+
key: content.s3Location && content.s3Location.objectKey,
198+
objectVersion: content.s3Location && content.s3Location.objectVersion,
199+
roleArn: this.role.roleArn,
200+
},
201+
});
202+
203+
this.buildId = resource.ref;
204+
}
205+
206+
207+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import * as iam from '@aws-cdk/aws-iam';
2+
import * as s3 from '@aws-cdk/aws-s3';
3+
import * as s3_assets from '@aws-cdk/aws-s3-assets';
4+
import * as cdk from '@aws-cdk/core';
5+
import { Construct } from 'constructs';
6+
7+
/**
8+
* Before deploying your GameLift-enabled multiplayer game servers for hosting with the GameLift service, you need to upload your game server files.
9+
* The class helps you on preparing and uploading custom game server build files or Realtime Servers server script files.
10+
*/
11+
export abstract class Content {
12+
/**
13+
* Game content as an S3 object.
14+
* @param bucket The S3 bucket
15+
* @param key The object key
16+
* @param objectVersion Optional S3 ob ject version
17+
*/
18+
public static fromBucket(bucket: s3.IBucket, key: string, objectVersion?: string): S3Content {
19+
return new S3Content(bucket, key, objectVersion);
20+
}
21+
22+
23+
/**
24+
* Loads the game content from a local disk path.
25+
*
26+
* @param path Either a directory with the game content bundle or a .zip file
27+
*/
28+
public static fromAsset(path: string, options?: s3_assets.AssetOptions): AssetContent {
29+
return new AssetContent(path, options);
30+
}
31+
32+
/**
33+
* Called when the Build is initialized to allow this object to bind
34+
*/
35+
public abstract bind(scope: Construct, grantable: iam.IGrantable): ContentConfig;
36+
37+
}
38+
39+
/**
40+
* Result of binding `Content` into a `Build`.
41+
*/
42+
export interface ContentConfig {
43+
/**
44+
* The location of the content in S3.
45+
*/
46+
readonly s3Location: s3.Location;
47+
}
48+
49+
/**
50+
* Game content from an S3 archive.
51+
*/
52+
export class S3Content extends Content {
53+
54+
constructor(private readonly bucket: s3.IBucket, private key: string, private objectVersion?: string) {
55+
super();
56+
if (!bucket.bucketName) {
57+
throw new Error('bucketName is undefined for the provided bucket');
58+
}
59+
}
60+
61+
public bind(_scope: Construct, grantable: iam.IGrantable): ContentConfig {
62+
this.bucket.grantRead(grantable, this.key);
63+
return {
64+
s3Location: {
65+
bucketName: this.bucket.bucketName,
66+
objectKey: this.key,
67+
objectVersion: this.objectVersion,
68+
},
69+
};
70+
}
71+
}
72+
73+
/**
74+
* Game content from a local directory.
75+
*/
76+
export class AssetContent extends Content {
77+
private asset?: s3_assets.Asset;
78+
79+
/**
80+
* @param path The path to the asset file or directory.
81+
*/
82+
constructor(public readonly path: string, private readonly options: s3_assets.AssetOptions = { }) {
83+
super();
84+
}
85+
86+
public bind(scope: Construct, grantable: iam.IGrantable): ContentConfig {
87+
// If the same AssetContent is used multiple times, retain only the first instantiation.
88+
if (!this.asset) {
89+
this.asset = new s3_assets.Asset(scope, 'Content', {
90+
path: this.path,
91+
...this.options,
92+
});
93+
} else if (cdk.Stack.of(this.asset) !== cdk.Stack.of(scope)) {
94+
throw new Error(`Asset is already associated with another stack '${cdk.Stack.of(this.asset).stackName}'. ` +
95+
'Create a new Content instance for every stack.');
96+
}
97+
this.asset.grantRead(grantable);
98+
99+
if (!this.asset.isZipArchive) {
100+
throw new Error(`Asset must be a .zip file or a directory (${this.path})`);
101+
}
102+
103+
return {
104+
s3Location: {
105+
bucketName: this.asset.s3BucketName,
106+
objectKey: this.asset.s3ObjectKey,
107+
},
108+
};
109+
}
110+
}
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1+
export * from './content';
2+
export * from './build';
3+
14
// AWS::GameLift CloudFormation Resources:
25
export * from './gamelift.generated';

‎packages/@aws-cdk/aws-gamelift/package.json

+32-2
Original file line numberDiff line numberDiff line change
@@ -82,24 +82,54 @@
8282
"devDependencies": {
8383
"@aws-cdk/assertions": "0.0.0",
8484
"@aws-cdk/cdk-build-tools": "0.0.0",
85+
"@aws-cdk/integ-runner": "0.0.0",
8586
"@aws-cdk/cfn2ts": "0.0.0",
8687
"@aws-cdk/pkglint": "0.0.0",
87-
"@types/jest": "^27.5.2"
88+
"@aws-cdk/cx-api": "0.0.0",
89+
"@types/jest": "^27.5.2",
90+
"jest": "^27.5.1"
8891
},
8992
"dependencies": {
9093
"@aws-cdk/core": "0.0.0",
94+
"@aws-cdk/aws-cloudwatch": "0.0.0",
95+
"@aws-cdk/aws-ec2": "0.0.0",
96+
"@aws-cdk/aws-events": "0.0.0",
97+
"@aws-cdk/aws-iam": "0.0.0",
98+
"@aws-cdk/aws-kms": "0.0.0",
99+
"@aws-cdk/aws-logs": "0.0.0",
100+
"@aws-cdk/aws-s3": "0.0.0",
101+
"@aws-cdk/aws-s3-assets": "0.0.0",
102+
"@aws-cdk/aws-sns": "0.0.0",
103+
"@aws-cdk/region-info": "0.0.0",
91104
"constructs": "^10.0.0"
92105
},
93106
"homepage": "https://github.com/aws/aws-cdk",
94107
"peerDependencies": {
95108
"@aws-cdk/core": "0.0.0",
109+
"@aws-cdk/aws-cloudwatch": "0.0.0",
110+
"@aws-cdk/aws-ec2": "0.0.0",
111+
"@aws-cdk/aws-events": "0.0.0",
112+
"@aws-cdk/aws-iam": "0.0.0",
113+
"@aws-cdk/aws-kms": "0.0.0",
114+
"@aws-cdk/aws-logs": "0.0.0",
115+
"@aws-cdk/aws-s3": "0.0.0",
116+
"@aws-cdk/aws-s3-assets": "0.0.0",
117+
"@aws-cdk/aws-sns": "0.0.0",
118+
"@aws-cdk/region-info": "0.0.0",
96119
"constructs": "^10.0.0"
97120
},
98121
"engines": {
99122
"node": ">= 14.15.0"
100123
},
124+
"awslint": {
125+
"exclude": [
126+
"docs-public-apis:@aws-cdk/aws-gamelift.OperatingSystem.AMAZON_LINUX",
127+
"docs-public-apis:@aws-cdk/aws-gamelift.OperatingSystem.AMAZON_LINUX_2",
128+
"docs-public-apis:@aws-cdk/aws-gamelift.OperatingSystem.WINDOWS_2012"
129+
]
130+
},
101131
"stability": "experimental",
102-
"maturity": "cfn-only",
132+
"maturity": "experimental",
103133
"awscdkio": {
104134
"announce": false
105135
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Fixture with packages imported, but nothing else
2+
import { Construct } from 'constructs';
3+
import { Duration, Size, Stack } from '@aws-cdk/core';
4+
import * as gamelift from '@aws-cdk/aws-gamelift';
5+
import * as s3 from '@aws-cdk/aws-s3';
6+
import * as kms from '@aws-cdk/aws-kms';
7+
import * as iam from '@aws-cdk/aws-iam';
8+
import * as path from 'path';
9+
10+
class Fixture extends Stack {
11+
constructor(scope: Construct, id: string) {
12+
super(scope, id);
13+
14+
/// here
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log('Hello World');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"version": "21.0.0",
3+
"files": {
4+
"6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7": {
5+
"source": {
6+
"path": "asset.6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7",
7+
"packaging": "zip"
8+
},
9+
"destinations": {
10+
"current_account-current_region": {
11+
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12+
"objectKey": "6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7.zip",
13+
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
14+
}
15+
}
16+
},
17+
"9c561e93c7a2947a15dba683670660e922cf493e17b2a6f8ca03cf221442c222": {
18+
"source": {
19+
"path": "aws-gamelift-build.template.json",
20+
"packaging": "file"
21+
},
22+
"destinations": {
23+
"current_account-current_region": {
24+
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
25+
"objectKey": "9c561e93c7a2947a15dba683670660e922cf493e17b2a6f8ca03cf221442c222.json",
26+
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
27+
}
28+
}
29+
}
30+
},
31+
"dockerImages": {}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
{
2+
"Resources": {
3+
"BuildServiceRole1F57E904": {
4+
"Type": "AWS::IAM::Role",
5+
"Properties": {
6+
"AssumeRolePolicyDocument": {
7+
"Statement": [
8+
{
9+
"Action": "sts:AssumeRole",
10+
"Effect": "Allow",
11+
"Principal": {
12+
"Service": "gamelift.amazonaws.com"
13+
}
14+
}
15+
],
16+
"Version": "2012-10-17"
17+
}
18+
}
19+
},
20+
"BuildServiceRoleDefaultPolicyCB7101C6": {
21+
"Type": "AWS::IAM::Policy",
22+
"Properties": {
23+
"PolicyDocument": {
24+
"Statement": [
25+
{
26+
"Action": [
27+
"s3:GetBucket*",
28+
"s3:GetObject*",
29+
"s3:List*"
30+
],
31+
"Effect": "Allow",
32+
"Resource": [
33+
{
34+
"Fn::Join": [
35+
"",
36+
[
37+
"arn:",
38+
{
39+
"Ref": "AWS::Partition"
40+
},
41+
":s3:::",
42+
{
43+
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
44+
},
45+
"/*"
46+
]
47+
]
48+
},
49+
{
50+
"Fn::Join": [
51+
"",
52+
[
53+
"arn:",
54+
{
55+
"Ref": "AWS::Partition"
56+
},
57+
":s3:::",
58+
{
59+
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
60+
}
61+
]
62+
]
63+
}
64+
]
65+
}
66+
],
67+
"Version": "2012-10-17"
68+
},
69+
"PolicyName": "BuildServiceRoleDefaultPolicyCB7101C6",
70+
"Roles": [
71+
{
72+
"Ref": "BuildServiceRole1F57E904"
73+
}
74+
]
75+
}
76+
},
77+
"Build45A36621": {
78+
"Type": "AWS::GameLift::Build",
79+
"Properties": {
80+
"StorageLocation": {
81+
"Bucket": {
82+
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
83+
},
84+
"Key": "6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7.zip",
85+
"RoleArn": {
86+
"Fn::GetAtt": [
87+
"BuildServiceRole1F57E904",
88+
"Arn"
89+
]
90+
}
91+
}
92+
}
93+
}
94+
},
95+
"Parameters": {
96+
"BootstrapVersion": {
97+
"Type": "AWS::SSM::Parameter::Value<String>",
98+
"Default": "/cdk-bootstrap/hnb659fds/version",
99+
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
100+
}
101+
},
102+
"Rules": {
103+
"CheckBootstrapVersion": {
104+
"Assertions": [
105+
{
106+
"Assert": {
107+
"Fn::Not": [
108+
{
109+
"Fn::Contains": [
110+
[
111+
"1",
112+
"2",
113+
"3",
114+
"4",
115+
"5"
116+
],
117+
{
118+
"Ref": "BootstrapVersion"
119+
}
120+
]
121+
}
122+
]
123+
},
124+
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
125+
}
126+
]
127+
}
128+
}
129+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"version":"21.0.0"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"version": "21.0.0",
3+
"testCases": {
4+
"integ.build": {
5+
"stacks": [
6+
"aws-gamelift-build"
7+
],
8+
"diffAssets": false,
9+
"stackUpdateWorkflow": true
10+
}
11+
},
12+
"synthContext": {},
13+
"enableLookups": false
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{
2+
"version": "21.0.0",
3+
"artifacts": {
4+
"Tree": {
5+
"type": "cdk:tree",
6+
"properties": {
7+
"file": "tree.json"
8+
}
9+
},
10+
"aws-gamelift-build.assets": {
11+
"type": "cdk:asset-manifest",
12+
"properties": {
13+
"file": "aws-gamelift-build.assets.json",
14+
"requiresBootstrapStackVersion": 6,
15+
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version"
16+
}
17+
},
18+
"aws-gamelift-build": {
19+
"type": "aws:cloudformation:stack",
20+
"environment": "aws://unknown-account/unknown-region",
21+
"properties": {
22+
"templateFile": "aws-gamelift-build.template.json",
23+
"validateOnSynth": false,
24+
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
25+
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
26+
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/9c561e93c7a2947a15dba683670660e922cf493e17b2a6f8ca03cf221442c222.json",
27+
"requiresBootstrapStackVersion": 6,
28+
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
29+
"additionalDependencies": [
30+
"aws-gamelift-build.assets"
31+
],
32+
"lookupRole": {
33+
"arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}",
34+
"requiresBootstrapStackVersion": 8,
35+
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version"
36+
}
37+
},
38+
"dependencies": [
39+
"aws-gamelift-build.assets"
40+
],
41+
"metadata": {
42+
"/aws-gamelift-build/Build/ServiceRole/Resource": [
43+
{
44+
"type": "aws:cdk:logicalId",
45+
"data": "BuildServiceRole1F57E904"
46+
}
47+
],
48+
"/aws-gamelift-build/Build/ServiceRole/DefaultPolicy/Resource": [
49+
{
50+
"type": "aws:cdk:logicalId",
51+
"data": "BuildServiceRoleDefaultPolicyCB7101C6"
52+
}
53+
],
54+
"/aws-gamelift-build/Build/Resource": [
55+
{
56+
"type": "aws:cdk:logicalId",
57+
"data": "Build45A36621"
58+
}
59+
]
60+
},
61+
"displayName": "aws-gamelift-build"
62+
}
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
{
2+
"version": "tree-0.1",
3+
"tree": {
4+
"id": "App",
5+
"path": "",
6+
"children": {
7+
"Tree": {
8+
"id": "Tree",
9+
"path": "Tree",
10+
"constructInfo": {
11+
"fqn": "constructs.Construct",
12+
"version": "10.1.33"
13+
}
14+
},
15+
"build-test-assets": {
16+
"id": "build-test-assets",
17+
"path": "build-test-assets",
18+
"children": {
19+
"Build": {
20+
"id": "Build",
21+
"path": "build-test-assets/Build",
22+
"children": {
23+
"Service Role": {
24+
"id": "Service Role",
25+
"path": "build-test-assets/Build/Service Role",
26+
"children": {
27+
"Resource": {
28+
"id": "Resource",
29+
"path": "build-test-assets/Build/Service Role/Resource",
30+
"attributes": {
31+
"aws:cdk:cloudformation:type": "AWS::IAM::Role",
32+
"aws:cdk:cloudformation:props": {
33+
"assumeRolePolicyDocument": {
34+
"Statement": [
35+
{
36+
"Action": "sts:AssumeRole",
37+
"Effect": "Allow",
38+
"Principal": {
39+
"Service": "gamelift.amazonaws.com"
40+
}
41+
}
42+
],
43+
"Version": "2012-10-17"
44+
}
45+
}
46+
},
47+
"constructInfo": {
48+
"fqn": "@aws-cdk/aws-iam.CfnRole",
49+
"version": "0.0.0"
50+
}
51+
},
52+
"DefaultPolicy": {
53+
"id": "DefaultPolicy",
54+
"path": "build-test-assets/Build/Service Role/DefaultPolicy",
55+
"children": {
56+
"Resource": {
57+
"id": "Resource",
58+
"path": "build-test-assets/Build/Service Role/DefaultPolicy/Resource",
59+
"attributes": {
60+
"aws:cdk:cloudformation:type": "AWS::IAM::Policy",
61+
"aws:cdk:cloudformation:props": {
62+
"policyDocument": {
63+
"Statement": [
64+
{
65+
"Action": [
66+
"s3:GetObject*",
67+
"s3:GetBucket*",
68+
"s3:List*"
69+
],
70+
"Effect": "Allow",
71+
"Resource": [
72+
{
73+
"Fn::Join": [
74+
"",
75+
[
76+
"arn:",
77+
{
78+
"Ref": "AWS::Partition"
79+
},
80+
":s3:::",
81+
{
82+
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
83+
}
84+
]
85+
]
86+
},
87+
{
88+
"Fn::Join": [
89+
"",
90+
[
91+
"arn:",
92+
{
93+
"Ref": "AWS::Partition"
94+
},
95+
":s3:::",
96+
{
97+
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
98+
},
99+
"/*"
100+
]
101+
]
102+
}
103+
]
104+
}
105+
],
106+
"Version": "2012-10-17"
107+
},
108+
"policyName": "BuildServiceRoleDefaultPolicy90803718",
109+
"roles": [
110+
{
111+
"Ref": "BuildServiceRole4643E19E"
112+
}
113+
]
114+
}
115+
},
116+
"constructInfo": {
117+
"fqn": "@aws-cdk/aws-iam.CfnPolicy",
118+
"version": "0.0.0"
119+
}
120+
}
121+
},
122+
"constructInfo": {
123+
"fqn": "@aws-cdk/aws-iam.Policy",
124+
"version": "0.0.0"
125+
}
126+
}
127+
},
128+
"constructInfo": {
129+
"fqn": "@aws-cdk/aws-iam.Role",
130+
"version": "0.0.0"
131+
}
132+
},
133+
"Content": {
134+
"id": "Content",
135+
"path": "build-test-assets/Build/Content",
136+
"children": {
137+
"Stage": {
138+
"id": "Stage",
139+
"path": "build-test-assets/Build/Content/Stage",
140+
"constructInfo": {
141+
"fqn": "@aws-cdk/core.AssetStaging",
142+
"version": "0.0.0"
143+
}
144+
},
145+
"AssetBucket": {
146+
"id": "AssetBucket",
147+
"path": "build-test-assets/Build/Content/AssetBucket",
148+
"constructInfo": {
149+
"fqn": "@aws-cdk/aws-s3.BucketBase",
150+
"version": "0.0.0"
151+
}
152+
}
153+
},
154+
"constructInfo": {
155+
"fqn": "@aws-cdk/aws-s3-assets.Asset",
156+
"version": "0.0.0"
157+
}
158+
},
159+
"Resource": {
160+
"id": "Resource",
161+
"path": "build-test-assets/Build/Resource",
162+
"attributes": {
163+
"aws:cdk:cloudformation:type": "AWS::GameLift::Build",
164+
"aws:cdk:cloudformation:props": {
165+
"storageLocation": {
166+
"bucket": {
167+
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
168+
},
169+
"key": "6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7.zip",
170+
"roleArn": {
171+
"Fn::GetAtt": [
172+
"BuildServiceRole4643E19E",
173+
"Arn"
174+
]
175+
}
176+
}
177+
}
178+
},
179+
"constructInfo": {
180+
"fqn": "@aws-cdk/aws-gamelift.CfnBuild",
181+
"version": "0.0.0"
182+
}
183+
}
184+
},
185+
"constructInfo": {
186+
"fqn": "@aws-cdk/aws-gamelift.Build",
187+
"version": "0.0.0"
188+
}
189+
}
190+
},
191+
"constructInfo": {
192+
"fqn": "@aws-cdk/core.Stack",
193+
"version": "0.0.0"
194+
}
195+
}
196+
},
197+
"constructInfo": {
198+
"fqn": "@aws-cdk/core.App",
199+
"version": "0.0.0"
200+
}
201+
}
202+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
import * as path from 'path';
2+
import { Template } from '@aws-cdk/assertions';
3+
import * as iam from '@aws-cdk/aws-iam';
4+
import * as s3 from '@aws-cdk/aws-s3';
5+
import * as cdk from '@aws-cdk/core';
6+
import * as cxapi from '@aws-cdk/cx-api';
7+
import * as gamelift from '../lib';
8+
import { OperatingSystem } from '../lib';
9+
10+
describe('build', () => {
11+
const buildId = 'test-identifier';
12+
const buildName = 'test-build';
13+
let stack: cdk.Stack;
14+
15+
beforeEach(() => {
16+
const app = new cdk.App({ context: { [cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: false } });
17+
stack = new cdk.Stack(app);
18+
});
19+
20+
describe('.fromBuildId()', () => {
21+
test('with required fields', () => {
22+
const build = gamelift.Build.fromBuildId(stack, 'ImportedBuild', buildId);
23+
24+
expect(build.buildId).toEqual(buildId);
25+
expect(build.grantPrincipal).toEqual(new iam.UnknownPrincipal({ resource: build }));
26+
});
27+
});
28+
29+
describe('.fromBuildAttributes()', () => {
30+
test('with required attrs only', () => {
31+
const build = gamelift.Build.fromBuildAttributes(stack, 'ImportedBuild', { buildId });
32+
33+
expect(build.buildId).toEqual(buildId);
34+
expect(build.grantPrincipal).toEqual(new iam.UnknownPrincipal({ resource: build }));
35+
});
36+
37+
test('with all attrs', () => {
38+
const role = iam.Role.fromRoleArn(stack, 'Role', 'arn:aws:iam::123456789012:role/TestRole');
39+
const build = gamelift.Build.fromBuildAttributes(stack, 'ImportedBuild', { buildId, role });
40+
41+
expect(buildId).toEqual(buildId);
42+
expect(build.grantPrincipal).toEqual(role);
43+
});
44+
});
45+
46+
describe('new', () => {
47+
const localAsset = path.join(__dirname, 'my-game-build');
48+
const contentBucketName = 'bucketname';
49+
const contentBucketAccessStatement = {
50+
Action: [
51+
's3:GetObject*',
52+
's3:GetBucket*',
53+
's3:List*',
54+
],
55+
Effect: 'Allow',
56+
Resource: [
57+
{
58+
'Fn::Join': [
59+
'',
60+
[
61+
'arn:',
62+
{
63+
Ref: 'AWS::Partition',
64+
},
65+
`:s3:::${contentBucketName}`,
66+
],
67+
],
68+
},
69+
{
70+
'Fn::Join': [
71+
'',
72+
[
73+
'arn:',
74+
{
75+
Ref: 'AWS::Partition',
76+
},
77+
`:s3:::${contentBucketName}/content`,
78+
],
79+
],
80+
},
81+
],
82+
};
83+
let contentBucket: s3.IBucket;
84+
let content: gamelift.Content;
85+
let build: gamelift.Build;
86+
let defaultProps: gamelift.BuildProps;
87+
88+
beforeEach(() => {
89+
contentBucket = s3.Bucket.fromBucketName(stack, 'ContentBucket', contentBucketName);
90+
content = gamelift.Content.fromBucket(contentBucket, 'content');
91+
defaultProps = {
92+
content,
93+
};
94+
});
95+
96+
describe('.fromAsset()', () => {
97+
test('should create a new build from asset', () => {
98+
build = gamelift.Build.fromAsset(stack, 'ImportedBuild', localAsset);
99+
100+
expect(stack.node.metadata.find(m => m.type === 'aws:cdk:asset')).toBeDefined();
101+
Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', {
102+
StorageLocation: {
103+
Bucket: {
104+
Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3Bucket72AA8348',
105+
},
106+
},
107+
});
108+
109+
});
110+
});
111+
112+
describe('.fromBucket()', () => {
113+
test('should create a new build from bucket', () => {
114+
build = gamelift.Build.fromBucket(stack, 'ImportedBuild', contentBucket, 'content');
115+
116+
Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', {
117+
StorageLocation: {
118+
Bucket: 'bucketname',
119+
Key: 'content',
120+
},
121+
});
122+
123+
});
124+
});
125+
126+
describe('with necessary props only', () => {
127+
beforeEach(() => {
128+
build = new gamelift.Build(stack, 'Build', defaultProps);
129+
});
130+
131+
test('should create a role and use it with the build', () => {
132+
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', {
133+
AssumeRolePolicyDocument: {
134+
Statement: [
135+
{
136+
Action: 'sts:AssumeRole',
137+
Effect: 'Allow',
138+
Principal: {
139+
Service: 'gamelift.amazonaws.com',
140+
},
141+
},
142+
],
143+
Version: '2012-10-17',
144+
},
145+
});
146+
147+
// Role policy should grant reading from the assets bucket
148+
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
149+
PolicyDocument: {
150+
Statement: [
151+
contentBucketAccessStatement,
152+
],
153+
},
154+
Roles: [
155+
{
156+
Ref: 'BuildServiceRole1F57E904',
157+
},
158+
],
159+
});
160+
161+
// check the build using the role
162+
Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', {
163+
StorageLocation: {
164+
Bucket: 'bucketname',
165+
Key: 'content',
166+
RoleArn: {
167+
'Fn::GetAtt': [
168+
'BuildServiceRole1F57E904',
169+
'Arn',
170+
],
171+
},
172+
},
173+
});
174+
});
175+
176+
test('should return correct buildId from CloudFormation', () => {
177+
expect(stack.resolve(build.buildId)).toEqual({ Ref: 'Build45A36621' });
178+
});
179+
180+
test('with a custom role should use it and set it in CloudFormation', () => {
181+
const role = iam.Role.fromRoleArn(stack, 'Role', 'arn:aws:iam::123456789012:role/TestRole');
182+
build = new gamelift.Build(stack, 'BuildWithRole', {
183+
...defaultProps,
184+
role,
185+
});
186+
187+
expect(build.grantPrincipal).toEqual(role);
188+
Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', {
189+
StorageLocation: {
190+
RoleArn: role.roleArn,
191+
},
192+
});
193+
});
194+
195+
test('with a custom buildName should set it in CloudFormation', () => {
196+
build = new gamelift.Build(stack, 'BuildWithName', {
197+
...defaultProps,
198+
buildName: buildName,
199+
});
200+
201+
Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', {
202+
Name: buildName,
203+
});
204+
});
205+
206+
test('with all optional attributes should set it in CloudFormation', () => {
207+
build = new gamelift.Build(stack, 'BuildWithName', {
208+
...defaultProps,
209+
buildName: buildName,
210+
operatingSystem: OperatingSystem.AMAZON_LINUX_2,
211+
buildVersion: '1.0',
212+
});
213+
214+
Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', {
215+
Name: buildName,
216+
OperatingSystem: OperatingSystem.AMAZON_LINUX_2,
217+
Version: '1.0',
218+
});
219+
});
220+
221+
test('with an incorrect buildName (>1024)', () => {
222+
let incorrectBuildName = '';
223+
for (let i = 0; i < 1025; i++) {
224+
incorrectBuildName += 'A';
225+
}
226+
227+
expect(() => new gamelift.Build(stack, 'BuildWithWrongName', {
228+
content,
229+
buildName: incorrectBuildName,
230+
})).toThrow(/Build name can not be longer than 1024 characters but has 1025 characters./);
231+
});
232+
});
233+
});
234+
});
235+
236+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
import * as path from 'path';
2+
import { Template } from '@aws-cdk/assertions';
3+
import * as s3 from '@aws-cdk/aws-s3';
4+
import * as cdk from '@aws-cdk/core';
5+
import * as cxapi from '@aws-cdk/cx-api';
6+
import * as gamelift from '../lib';
7+
8+
describe('Code', () => {
9+
let stack: cdk.Stack;
10+
let content: gamelift.Content;
11+
12+
beforeEach(() => {
13+
const app = new cdk.App({ context: { [cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: false } });
14+
stack = new cdk.Stack(app, 'Stack');
15+
});
16+
17+
describe('.fromBucket()', () => {
18+
const key = 'content';
19+
let bucket: s3.IBucket;
20+
21+
test('with valid bucket name and key and bound by build sets the right path and grants the build permissions to read from it', () => {
22+
bucket = s3.Bucket.fromBucketName(stack, 'Bucket', 'bucketname');
23+
content = gamelift.Content.fromBucket(bucket, key);
24+
new gamelift.Build(stack, 'Build1', {
25+
content: content,
26+
});
27+
28+
Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', {
29+
StorageLocation: {
30+
Bucket: 'bucketname',
31+
Key: 'content',
32+
},
33+
});
34+
35+
// Role policy should grant reading from the assets bucket
36+
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
37+
PolicyDocument: {
38+
Statement: [
39+
{
40+
Action: [
41+
's3:GetObject*',
42+
's3:GetBucket*',
43+
's3:List*',
44+
],
45+
Effect: 'Allow',
46+
Resource: [
47+
{
48+
'Fn::Join': [
49+
'',
50+
[
51+
'arn:',
52+
{
53+
Ref: 'AWS::Partition',
54+
},
55+
':s3:::bucketname',
56+
],
57+
],
58+
},
59+
{
60+
'Fn::Join': [
61+
'',
62+
[
63+
'arn:',
64+
{
65+
Ref: 'AWS::Partition',
66+
},
67+
':s3:::bucketname/content',
68+
],
69+
],
70+
},
71+
],
72+
},
73+
],
74+
},
75+
Roles: [
76+
{
77+
Ref: 'Build1ServiceRole24FABCB7',
78+
},
79+
],
80+
});
81+
});
82+
});
83+
84+
describe('.fromAsset()', () => {
85+
const directoryPath = path.join(__dirname, 'my-game-build');
86+
87+
beforeEach(() => {
88+
content = gamelift.Content.fromAsset(directoryPath);
89+
});
90+
91+
test("with valid and existing file path and bound to job sets job's script location and permissions stack metadata", () => {
92+
new gamelift.Build(stack, 'Build1', {
93+
content: content,
94+
});
95+
96+
expect(stack.node.metadata.find(m => m.type === 'aws:cdk:asset')).toBeDefined();
97+
Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', {
98+
StorageLocation: {
99+
Bucket: {
100+
Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3Bucket72AA8348',
101+
},
102+
Key: {
103+
'Fn::Join': [
104+
'',
105+
[
106+
{
107+
'Fn::Select': [
108+
0,
109+
{
110+
'Fn::Split': [
111+
'||',
112+
{
113+
Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3VersionKey720D3160',
114+
},
115+
],
116+
},
117+
],
118+
},
119+
{
120+
'Fn::Select': [
121+
1,
122+
{
123+
'Fn::Split': [
124+
'||',
125+
{
126+
Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3VersionKey720D3160',
127+
},
128+
],
129+
},
130+
],
131+
},
132+
],
133+
],
134+
},
135+
RoleArn: {
136+
'Fn::GetAtt': [
137+
'Build1ServiceRole24FABCB7',
138+
'Arn',
139+
],
140+
},
141+
},
142+
});
143+
// Role policy should grant reading from the assets bucket
144+
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
145+
PolicyDocument: {
146+
Statement: [
147+
{
148+
Action: [
149+
's3:GetObject*',
150+
's3:GetBucket*',
151+
's3:List*',
152+
],
153+
Effect: 'Allow',
154+
Resource: [
155+
{
156+
'Fn::Join': [
157+
'',
158+
[
159+
'arn:',
160+
{
161+
Ref: 'AWS::Partition',
162+
},
163+
':s3:::',
164+
{
165+
Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3Bucket72AA8348',
166+
},
167+
],
168+
],
169+
},
170+
{
171+
'Fn::Join': [
172+
'',
173+
[
174+
'arn:',
175+
{
176+
Ref: 'AWS::Partition',
177+
},
178+
':s3:::',
179+
{
180+
Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3Bucket72AA8348',
181+
},
182+
'/*',
183+
],
184+
],
185+
},
186+
],
187+
},
188+
],
189+
},
190+
Roles: [
191+
{
192+
Ref: 'Build1ServiceRole24FABCB7',
193+
},
194+
],
195+
});
196+
});
197+
198+
test('with an unsupported file path throws', () => {
199+
// GIVEN
200+
const fileAsset = gamelift.Content.fromAsset(path.join(__dirname, 'my-game-build', 'index.js'));
201+
202+
// THEN
203+
expect(() => new gamelift.Build(stack, 'Build1', { content: fileAsset }))
204+
.toThrow(/Asset must be a \.zip file or a directory/);
205+
});
206+
207+
test('used in more than 1 build in the same stack should be reused', () => {
208+
new gamelift.Build(stack, 'Build1', {
209+
content: content,
210+
});
211+
new gamelift.Build(stack, 'Build2', {
212+
content: content,
213+
});
214+
const StorageLocation = {
215+
Bucket: {
216+
Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3Bucket72AA8348',
217+
},
218+
Key: {
219+
'Fn::Join': [
220+
'',
221+
[
222+
{
223+
'Fn::Select': [
224+
0,
225+
{
226+
'Fn::Split': [
227+
'||',
228+
{
229+
Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3VersionKey720D3160',
230+
},
231+
],
232+
},
233+
],
234+
},
235+
{
236+
'Fn::Select': [
237+
1,
238+
{
239+
'Fn::Split': [
240+
'||',
241+
{
242+
Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3VersionKey720D3160',
243+
},
244+
],
245+
},
246+
],
247+
},
248+
],
249+
],
250+
},
251+
RoleArn: {
252+
'Fn::GetAtt': [
253+
'Build1ServiceRole24FABCB7',
254+
'Arn',
255+
],
256+
},
257+
};
258+
259+
expect(stack.node.metadata.find(m => m.type === 'aws:cdk:asset')).toBeDefined();
260+
// Job1 and Job2 use reuse the asset
261+
Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', {
262+
StorageLocation,
263+
});
264+
Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', {
265+
StorageLocation,
266+
});
267+
});
268+
269+
test('throws if trying to rebind in another stack', () => {
270+
new gamelift.Build(stack, 'Build1', {
271+
content,
272+
});
273+
const differentStack = new cdk.Stack();
274+
275+
expect(() => new gamelift.Build(differentStack, 'Build2', {
276+
content,
277+
})).toThrow(/Asset is already associated with another stack/);
278+
});
279+
});
280+
});

‎packages/@aws-cdk/aws-gamelift/test/gamelift.test.ts

-6
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as path from 'path';
2+
import * as cdk from '@aws-cdk/core';
3+
import * as gamelift from '../lib';
4+
5+
const app = new cdk.App();
6+
7+
const stack = new cdk.Stack(app, 'aws-gamelift-build');
8+
9+
new gamelift.Build(stack, 'Build', {
10+
content: gamelift.Content.fromAsset(path.join(__dirname, 'my-game-build')),
11+
});
12+
13+
app.synth();
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log('Hello World');

0 commit comments

Comments
 (0)
Please sign in to comment.