From 983d26e4e7cbb40fe1148ec635efe8093d850835 Mon Sep 17 00:00:00 2001 From: Steve HOUEL Date: Tue, 4 Oct 2022 12:27:11 +0200 Subject: [PATCH] feat(gamelift): add Build L2 constructs for GameLift (#22313) Following [aws-cdk-rfcs](https://github.com/aws/aws-cdk-rfcs/issues/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* --- packages/@aws-cdk/aws-gamelift/.gitignore | 3 + packages/@aws-cdk/aws-gamelift/README.md | 98 ++++-- packages/@aws-cdk/aws-gamelift/lib/build.ts | 207 +++++++++++++ packages/@aws-cdk/aws-gamelift/lib/content.ts | 110 +++++++ packages/@aws-cdk/aws-gamelift/lib/index.ts | 3 + packages/@aws-cdk/aws-gamelift/package.json | 34 ++- .../aws-gamelift/rosetta/default.ts-fixture | 16 + .../index.js | 1 + .../aws-gamelift-build.assets.json | 32 ++ .../aws-gamelift-build.template.json | 129 ++++++++ .../test/build.integ.snapshot/cdk.out | 1 + .../test/build.integ.snapshot/integ.json | 14 + .../test/build.integ.snapshot/manifest.json | 64 ++++ .../test/build.integ.snapshot/tree.json | 202 +++++++++++++ .../@aws-cdk/aws-gamelift/test/build.test.ts | 236 +++++++++++++++ .../aws-gamelift/test/content.test.ts | 280 ++++++++++++++++++ .../aws-gamelift/test/gamelift.test.ts | 6 - .../@aws-cdk/aws-gamelift/test/integ.build.ts | 13 + .../aws-gamelift/test/my-game-build.zip | Bin 0 -> 419 bytes .../aws-gamelift/test/my-game-build/index.js | 1 + 20 files changed, 1419 insertions(+), 31 deletions(-) create mode 100644 packages/@aws-cdk/aws-gamelift/lib/build.ts create mode 100644 packages/@aws-cdk/aws-gamelift/lib/content.ts create mode 100644 packages/@aws-cdk/aws-gamelift/rosetta/default.ts-fixture create mode 100644 packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/asset.6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7/index.js create mode 100644 packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/aws-gamelift-build.assets.json create mode 100644 packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/aws-gamelift-build.template.json create mode 100644 packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/integ.json create mode 100644 packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/tree.json create mode 100644 packages/@aws-cdk/aws-gamelift/test/build.test.ts create mode 100644 packages/@aws-cdk/aws-gamelift/test/content.test.ts delete mode 100644 packages/@aws-cdk/aws-gamelift/test/gamelift.test.ts create mode 100644 packages/@aws-cdk/aws-gamelift/test/integ.build.ts create mode 100644 packages/@aws-cdk/aws-gamelift/test/my-game-build.zip create mode 100644 packages/@aws-cdk/aws-gamelift/test/my-game-build/index.js diff --git a/packages/@aws-cdk/aws-gamelift/.gitignore b/packages/@aws-cdk/aws-gamelift/.gitignore index 6d05bba61dfa7..ff82c8959aad2 100644 --- a/packages/@aws-cdk/aws-gamelift/.gitignore +++ b/packages/@aws-cdk/aws-gamelift/.gitignore @@ -21,3 +21,6 @@ junit.xml !**/*.integ.snapshot/**/asset.*/*.d.ts !**/*.integ.snapshot/**/asset.*/** + +#include game build js file +!test/my-game-build/*.js diff --git a/packages/@aws-cdk/aws-gamelift/README.md b/packages/@aws-cdk/aws-gamelift/README.md index 5cc87f2e9eafc..3d84f4b418ebb 100644 --- a/packages/@aws-cdk/aws-gamelift/README.md +++ b/packages/@aws-cdk/aws-gamelift/README.md @@ -9,31 +9,83 @@ > > [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib +![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) + +> The APIs of higher level constructs in this module are experimental and under active development. +> They are subject to non-backward compatible changes or removal in any future version. These are +> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be +> announced in the release notes. This means that while you may use them, you may need to update +> your source code when upgrading to a newer version of this package. + --- -This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. - -```ts nofixture -import * as gamelift from '@aws-cdk/aws-gamelift'; +[Amazon GameLift](https://docs.aws.amazon.com/gamelift/latest/developerguide/gamelift-intro.html) is a service used +to deploy, operate, and scale dedicated, low-cost servers in the cloud for session-based multiplayer games. Built +on AWS global computing infrastructure, GameLift helps deliver high-performance, high-reliability game servers +while dynamically scaling your resource usage to meet worldwide player demand. + +GameLift is composed of three main components: + +* GameLift FlexMatch which is a customizable matchmaking service for +multiplayer games. With FlexMatch, you can +build a custom set of rules that defines what a multiplayer match looks like +for your game, and determines how to +evaluate and select compatible players for each match. You can also customize +key aspects of the matchmaking +process to fit your game, including fine-tuning the matching algorithm. + +* GameLift hosting for custom or realtime servers which helps you deploy, +operate, and scale dedicated game servers. It regulates the resources needed to +host games, finds available game servers to host new game sessions, and puts +players into games. + +* GameLift FleetIQ to optimize the use of low-cost Amazon Elastic Compute Cloud +(Amazon EC2) Spot Instances for cloud-based game hosting. With GameLift +FleetIQ, you can work directly with your hosting resources in Amazon EC2 and +Amazon EC2 Auto Scaling while taking advantage of GameLift optimizations to +deliver inexpensive, resilient game hosting for your players + +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 +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 +your game server files. This section provides guidance on preparing and uploading custom game server build +files or Realtime Servers server script files. When you upload files, you create a GameLift build or script resource, which +you then deploy on fleets of hosting resources. + +### Upload a custom server build to GameLift + +Before uploading your configured game server to GameLift for hosting, package the game build files into a build directory. +This directory must include all components required to run your game servers and host game sessions, including the following: + +* Game server binaries – The binary files required to run the game server. A build can include binaries for multiple game +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). + +* Dependencies – Any dependent files that your game server executables require to run. Examples include assets, configuration +files, and dependent libraries. + +* Install script – A script file to handle tasks that are required to fully install your game build on GameLift hosting +servers. Place this file at the root of the build directory. GameLift runs the install script as part of fleet creation. + +You can set up any application in your build, including your install script, to access your resources securely on other AWS +services. + +```ts +declare const bucket: s3.Bucket; +new gamelift.Build(this, 'Build', { + content: gamelift.Content.fromBucket(bucket, "sample-asset-key") +}); ``` - - - -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: - -- Search [Construct Hub for GameLift construct libraries](https://constructs.dev/search?q=gamelift) -- 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. - - - - -There are no hand-written ([L2](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) constructs for this service yet. -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. - -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). - -(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.) - - diff --git a/packages/@aws-cdk/aws-gamelift/lib/build.ts b/packages/@aws-cdk/aws-gamelift/lib/build.ts new file mode 100644 index 0000000000000..12c066880473c --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/lib/build.ts @@ -0,0 +1,207 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as s3_assets from '@aws-cdk/aws-s3-assets'; +import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { Content } from './content'; +import { CfnBuild } from './gamelift.generated'; + +/** + * Represents a GameLift server build. + */ +export interface IBuild extends cdk.IResource, iam.IGrantable { + + /** + * The Identifier of the build. + * + * @attribute + */ + readonly buildId: string; +} + +/** + * Base class for new and imported GameLift server build. + */ +export abstract class BuildBase extends cdk.Resource implements IBuild { + /** + * The Identifier of the build. + */ + public abstract readonly buildId: string; + + public abstract readonly grantPrincipal: iam.IPrincipal; +} + +/** + * The operating system that the game server binaries are built to run on. + */ +export enum OperatingSystem { + AMAZON_LINUX = 'AMAZON_LINUX', + AMAZON_LINUX_2 = 'AMAZON_LINUX_2', + WINDOWS_2012 = 'WINDOWS_2012' +} + + +/** + * Represents a Build content defined outside of this stack. + */ +export interface BuildAttributes { + /** + * The identifier of the build + */ + readonly buildId: string; + /** + * The IAM role assumed by GameLift to access server build in S3. + * @default - undefined + */ + readonly role?: iam.IRole; +} + +/** + * Properties for a new build + */ +export interface BuildProps { + /** + * Name of this build + * + * @default No name + */ + readonly buildName?: string; + + /** + * Version of this build + * + * @default No version + */ + readonly buildVersion?: string; + + /** + * The operating system that the game server binaries are built to run on. + * + * @default No version + */ + readonly operatingSystem?: OperatingSystem; + + /** + * The game build file storage + */ + readonly content: Content; + + /** + * The IAM role assumed by GameLift to access server build in S3. + * If providing a custom role, it needs to trust the GameLift service principal (gamelift.amazonaws.com) and be granted sufficient permissions + * to have Read access to a specific key content into a specific S3 bucket. + * Below an example of required permission: + * { + * "Version": "2012-10-17", + * "Statement": [{ + * "Effect": "Allow", + * "Action": [ + * "s3:GetObject", + * "s3:GetObjectVersion" + * ], + * "Resource": "arn:aws:s3:::bucket-name/object-name" + * }] + *} + * + * @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 + * + * @default - a role will be created with default permissions. + */ + readonly role?: iam.IRole; +} + +/** + * A GameLift build, that is installed and runs on instances in an Amazon GameLift fleet. It consists of + * a zip file with all of the components of the game server build. + * + * @see https://docs.aws.amazon.com/gamelift/latest/developerguide/gamelift-build-cli-uploading.html + * + * @resource AWS::GameLift::Build + */ +export class Build extends BuildBase { + + /** + * Create a new Build from s3 content + */ + static fromBucket(scope: Construct, id: string, bucket: s3.IBucket, key: string, objectVersion?: string) { + return new Build(scope, id, { + content: Content.fromBucket(bucket, key, objectVersion), + }); + } + + /** + * Create a new Build from asset content + */ + static fromAsset(scope: Construct, id: string, path: string, options?: s3_assets.AssetOptions) { + return new Build(scope, id, { + content: Content.fromAsset(path, options), + }); + } + + /** + * Import a build into CDK using its identifier + */ + static fromBuildId(scope: Construct, id: string, buildId: string): IBuild { + return this.fromBuildAttributes(scope, id, { buildId }); + } + + /** + * Import an existing build from its attributes. + */ + static fromBuildAttributes(scope: Construct, id: string, attrs: BuildAttributes): IBuild { + class Import extends BuildBase { + public readonly buildId = attrs.buildId; + public readonly grantPrincipal = attrs.role ?? new iam.UnknownPrincipal({ resource: this }); + } + + return new Import(scope, id); + } + + /** + * The Identifier of the build. + */ + public readonly buildId: string; + + /** + * The IAM role GameLift assumes to acccess server build content. + */ + public readonly role: iam.IRole; + + /** + * The principal this GameLift Build is using. + */ + public readonly grantPrincipal: iam.IPrincipal; + + constructor(scope: Construct, id: string, props: BuildProps) { + super(scope, id, { + physicalName: props.buildName, + }); + + if (props.buildName && !cdk.Token.isUnresolved(props.buildName)) { + if (props.buildName.length > 1024) { + throw new Error(`Build name can not be longer than 1024 characters but has ${props.buildName.length} characters.`); + } + } + this.role = props.role ?? new iam.Role(this, 'ServiceRole', { + assumedBy: new iam.ServicePrincipal('gamelift.amazonaws.com'), + }); + this.grantPrincipal = this.role; + const content = props.content.bind(this, this.role); + + const resource = new CfnBuild(this, 'Resource', { + name: props.buildName, + version: props.buildVersion, + operatingSystem: props.operatingSystem, + storageLocation: { + bucket: content.s3Location && content.s3Location.bucketName, + key: content.s3Location && content.s3Location.objectKey, + objectVersion: content.s3Location && content.s3Location.objectVersion, + roleArn: this.role.roleArn, + }, + }); + + this.buildId = resource.ref; + } + + +} diff --git a/packages/@aws-cdk/aws-gamelift/lib/content.ts b/packages/@aws-cdk/aws-gamelift/lib/content.ts new file mode 100644 index 0000000000000..3c510757b3880 --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/lib/content.ts @@ -0,0 +1,110 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as s3_assets from '@aws-cdk/aws-s3-assets'; +import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; + +/** + * Before deploying your GameLift-enabled multiplayer game servers for hosting with the GameLift service, you need to upload your game server files. + * The class helps you on preparing and uploading custom game server build files or Realtime Servers server script files. + */ +export abstract class Content { + /** + * Game content as an S3 object. + * @param bucket The S3 bucket + * @param key The object key + * @param objectVersion Optional S3 ob ject version + */ + public static fromBucket(bucket: s3.IBucket, key: string, objectVersion?: string): S3Content { + return new S3Content(bucket, key, objectVersion); + } + + + /** + * Loads the game content from a local disk path. + * + * @param path Either a directory with the game content bundle or a .zip file + */ + public static fromAsset(path: string, options?: s3_assets.AssetOptions): AssetContent { + return new AssetContent(path, options); + } + + /** + * Called when the Build is initialized to allow this object to bind + */ + public abstract bind(scope: Construct, grantable: iam.IGrantable): ContentConfig; + +} + +/** + * Result of binding `Content` into a `Build`. + */ +export interface ContentConfig { + /** + * The location of the content in S3. + */ + readonly s3Location: s3.Location; +} + +/** + * Game content from an S3 archive. + */ +export class S3Content extends Content { + + constructor(private readonly bucket: s3.IBucket, private key: string, private objectVersion?: string) { + super(); + if (!bucket.bucketName) { + throw new Error('bucketName is undefined for the provided bucket'); + } + } + + public bind(_scope: Construct, grantable: iam.IGrantable): ContentConfig { + this.bucket.grantRead(grantable, this.key); + return { + s3Location: { + bucketName: this.bucket.bucketName, + objectKey: this.key, + objectVersion: this.objectVersion, + }, + }; + } +} + +/** + * Game content from a local directory. + */ +export class AssetContent extends Content { + private asset?: s3_assets.Asset; + + /** + * @param path The path to the asset file or directory. + */ + constructor(public readonly path: string, private readonly options: s3_assets.AssetOptions = { }) { + super(); + } + + public bind(scope: Construct, grantable: iam.IGrantable): ContentConfig { + // If the same AssetContent is used multiple times, retain only the first instantiation. + if (!this.asset) { + this.asset = new s3_assets.Asset(scope, 'Content', { + path: this.path, + ...this.options, + }); + } else if (cdk.Stack.of(this.asset) !== cdk.Stack.of(scope)) { + throw new Error(`Asset is already associated with another stack '${cdk.Stack.of(this.asset).stackName}'. ` + + 'Create a new Content instance for every stack.'); + } + this.asset.grantRead(grantable); + + if (!this.asset.isZipArchive) { + throw new Error(`Asset must be a .zip file or a directory (${this.path})`); + } + + return { + s3Location: { + bucketName: this.asset.s3BucketName, + objectKey: this.asset.s3ObjectKey, + }, + }; + } +} diff --git a/packages/@aws-cdk/aws-gamelift/lib/index.ts b/packages/@aws-cdk/aws-gamelift/lib/index.ts index 843bf2236645e..cf989967b922d 100644 --- a/packages/@aws-cdk/aws-gamelift/lib/index.ts +++ b/packages/@aws-cdk/aws-gamelift/lib/index.ts @@ -1,2 +1,5 @@ +export * from './content'; +export * from './build'; + // AWS::GameLift CloudFormation Resources: export * from './gamelift.generated'; diff --git a/packages/@aws-cdk/aws-gamelift/package.json b/packages/@aws-cdk/aws-gamelift/package.json index 19f96730ef99e..6f133644d1d2b 100644 --- a/packages/@aws-cdk/aws-gamelift/package.json +++ b/packages/@aws-cdk/aws-gamelift/package.json @@ -82,24 +82,54 @@ "devDependencies": { "@aws-cdk/assertions": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", + "@aws-cdk/integ-runner": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^27.5.2" + "@aws-cdk/cx-api": "0.0.0", + "@types/jest": "^27.5.2", + "jest": "^27.5.1" }, "dependencies": { "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-cloudwatch": "0.0.0", + "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-events": "0.0.0", + "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-kms": "0.0.0", + "@aws-cdk/aws-logs": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/aws-s3-assets": "0.0.0", + "@aws-cdk/aws-sns": "0.0.0", + "@aws-cdk/region-info": "0.0.0", "constructs": "^10.0.0" }, "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-cloudwatch": "0.0.0", + "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-events": "0.0.0", + "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-kms": "0.0.0", + "@aws-cdk/aws-logs": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/aws-s3-assets": "0.0.0", + "@aws-cdk/aws-sns": "0.0.0", + "@aws-cdk/region-info": "0.0.0", "constructs": "^10.0.0" }, "engines": { "node": ">= 14.15.0" }, + "awslint": { + "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" + ] + }, "stability": "experimental", - "maturity": "cfn-only", + "maturity": "experimental", "awscdkio": { "announce": false }, diff --git a/packages/@aws-cdk/aws-gamelift/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-gamelift/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..4a4c332becb50 --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/rosetta/default.ts-fixture @@ -0,0 +1,16 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Duration, Size, Stack } from '@aws-cdk/core'; +import * as gamelift from '@aws-cdk/aws-gamelift'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as kms from '@aws-cdk/aws-kms'; +import * as iam from '@aws-cdk/aws-iam'; +import * as path from 'path'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/asset.6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7/index.js b/packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/asset.6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7/index.js new file mode 100644 index 0000000000000..73c02658c48d9 --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/asset.6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7/index.js @@ -0,0 +1 @@ +console.log('Hello World'); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/aws-gamelift-build.assets.json b/packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/aws-gamelift-build.assets.json new file mode 100644 index 0000000000000..d27cd073bc49a --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/aws-gamelift-build.assets.json @@ -0,0 +1,32 @@ +{ + "version": "21.0.0", + "files": { + "6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7": { + "source": { + "path": "asset.6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "9c561e93c7a2947a15dba683670660e922cf493e17b2a6f8ca03cf221442c222": { + "source": { + "path": "aws-gamelift-build.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "9c561e93c7a2947a15dba683670660e922cf493e17b2a6f8ca03cf221442c222.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/build.integ.snapshot/aws-gamelift-build.template.json b/packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/aws-gamelift-build.template.json new file mode 100644 index 0000000000000..394b49a1b66ff --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/aws-gamelift-build.template.json @@ -0,0 +1,129 @@ +{ + "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:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "/*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + } + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "BuildServiceRoleDefaultPolicyCB7101C6", + "Roles": [ + { + "Ref": "BuildServiceRole1F57E904" + } + ] + } + }, + "Build45A36621": { + "Type": "AWS::GameLift::Build", + "Properties": { + "StorageLocation": { + "Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "Key": "6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7.zip", + "RoleArn": { + "Fn::GetAtt": [ + "BuildServiceRole1F57E904", + "Arn" + ] + } + } + } + } + }, + "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/build.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/build.integ.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/build.integ.snapshot/integ.json b/packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/integ.json new file mode 100644 index 0000000000000..f646149706bea --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/integ.json @@ -0,0 +1,14 @@ +{ + "version": "21.0.0", + "testCases": { + "integ.build": { + "stacks": [ + "aws-gamelift-build" + ], + "diffAssets": false, + "stackUpdateWorkflow": true + } + }, + "synthContext": {}, + "enableLookups": false +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..43fe5eb776640 --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/manifest.json @@ -0,0 +1,64 @@ +{ + "version": "21.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "aws-gamelift-build.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-gamelift-build.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-gamelift-build": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-gamelift-build.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}/9c561e93c7a2947a15dba683670660e922cf493e17b2a6f8ca03cf221442c222.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-gamelift-build.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.assets" + ], + "metadata": { + "/aws-gamelift-build/Build/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "BuildServiceRole1F57E904" + } + ], + "/aws-gamelift-build/Build/ServiceRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "BuildServiceRoleDefaultPolicyCB7101C6" + } + ], + "/aws-gamelift-build/Build/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Build45A36621" + } + ] + }, + "displayName": "aws-gamelift-build" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/tree.json b/packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/tree.json new file mode 100644 index 0000000000000..96d14a552e490 --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/build.integ.snapshot/tree.json @@ -0,0 +1,202 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.33" + } + }, + "build-test-assets": { + "id": "build-test-assets", + "path": "build-test-assets", + "children": { + "Build": { + "id": "Build", + "path": "build-test-assets/Build", + "children": { + "Service Role": { + "id": "Service Role", + "path": "build-test-assets/Build/Service Role", + "children": { + "Resource": { + "id": "Resource", + "path": "build-test-assets/Build/Service Role/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": "build-test-assets/Build/Service Role/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "build-test-assets/Build/Service Role/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "policyName": "BuildServiceRoleDefaultPolicy90803718", + "roles": [ + { + "Ref": "BuildServiceRole4643E19E" + } + ] + } + }, + "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": "build-test-assets/Build/Content", + "children": { + "Stage": { + "id": "Stage", + "path": "build-test-assets/Build/Content/Stage", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "build-test-assets/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" + } + }, + "Resource": { + "id": "Resource", + "path": "build-test-assets/Build/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::GameLift::Build", + "aws:cdk:cloudformation:props": { + "storageLocation": { + "bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "key": "6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7.zip", + "roleArn": { + "Fn::GetAtt": [ + "BuildServiceRole4643E19E", + "Arn" + ] + } + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-gamelift.CfnBuild", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-gamelift.Build", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "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/build.test.ts b/packages/@aws-cdk/aws-gamelift/test/build.test.ts new file mode 100644 index 0000000000000..7bf763ea2ebfc --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/build.test.ts @@ -0,0 +1,236 @@ +import * as path from 'path'; +import { Template } from '@aws-cdk/assertions'; +import * as iam from '@aws-cdk/aws-iam'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as cxapi from '@aws-cdk/cx-api'; +import * as gamelift from '../lib'; +import { OperatingSystem } from '../lib'; + +describe('build', () => { + const buildId = 'test-identifier'; + const buildName = 'test-build'; + let stack: cdk.Stack; + + beforeEach(() => { + const app = new cdk.App({ context: { [cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: false } }); + stack = new cdk.Stack(app); + }); + + describe('.fromBuildId()', () => { + test('with required fields', () => { + const build = gamelift.Build.fromBuildId(stack, 'ImportedBuild', buildId); + + expect(build.buildId).toEqual(buildId); + expect(build.grantPrincipal).toEqual(new iam.UnknownPrincipal({ resource: build })); + }); + }); + + describe('.fromBuildAttributes()', () => { + test('with required attrs only', () => { + const build = gamelift.Build.fromBuildAttributes(stack, 'ImportedBuild', { buildId }); + + expect(build.buildId).toEqual(buildId); + expect(build.grantPrincipal).toEqual(new iam.UnknownPrincipal({ resource: build })); + }); + + test('with all attrs', () => { + const role = iam.Role.fromRoleArn(stack, 'Role', 'arn:aws:iam::123456789012:role/TestRole'); + const build = gamelift.Build.fromBuildAttributes(stack, 'ImportedBuild', { buildId, role }); + + expect(buildId).toEqual(buildId); + expect(build.grantPrincipal).toEqual(role); + }); + }); + + describe('new', () => { + const localAsset = path.join(__dirname, 'my-game-build'); + const contentBucketName = 'bucketname'; + const contentBucketAccessStatement = { + Action: [ + 's3:GetObject*', + 's3:GetBucket*', + 's3:List*', + ], + Effect: 'Allow', + Resource: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + `:s3:::${contentBucketName}`, + ], + ], + }, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + `:s3:::${contentBucketName}/content`, + ], + ], + }, + ], + }; + let contentBucket: s3.IBucket; + let content: gamelift.Content; + let build: gamelift.Build; + let defaultProps: gamelift.BuildProps; + + beforeEach(() => { + contentBucket = s3.Bucket.fromBucketName(stack, 'ContentBucket', contentBucketName); + content = gamelift.Content.fromBucket(contentBucket, 'content'); + defaultProps = { + content, + }; + }); + + describe('.fromAsset()', () => { + test('should create a new build from asset', () => { + build = gamelift.Build.fromAsset(stack, 'ImportedBuild', localAsset); + + expect(stack.node.metadata.find(m => m.type === 'aws:cdk:asset')).toBeDefined(); + Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', { + StorageLocation: { + Bucket: { + Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3Bucket72AA8348', + }, + }, + }); + + }); + }); + + describe('.fromBucket()', () => { + test('should create a new build from bucket', () => { + build = gamelift.Build.fromBucket(stack, 'ImportedBuild', contentBucket, 'content'); + + Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', { + StorageLocation: { + Bucket: 'bucketname', + Key: 'content', + }, + }); + + }); + }); + + describe('with necessary props only', () => { + beforeEach(() => { + build = new gamelift.Build(stack, 'Build', defaultProps); + }); + + test('should create a role and use it with the build', () => { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'gamelift.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + }); + + // Role policy should grant reading from the assets bucket + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + contentBucketAccessStatement, + ], + }, + Roles: [ + { + Ref: 'BuildServiceRole1F57E904', + }, + ], + }); + + // check the build using the role + Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', { + StorageLocation: { + Bucket: 'bucketname', + Key: 'content', + RoleArn: { + 'Fn::GetAtt': [ + 'BuildServiceRole1F57E904', + 'Arn', + ], + }, + }, + }); + }); + + test('should return correct buildId from CloudFormation', () => { + expect(stack.resolve(build.buildId)).toEqual({ Ref: 'Build45A36621' }); + }); + + test('with a custom role should use it and set it in CloudFormation', () => { + const role = iam.Role.fromRoleArn(stack, 'Role', 'arn:aws:iam::123456789012:role/TestRole'); + build = new gamelift.Build(stack, 'BuildWithRole', { + ...defaultProps, + role, + }); + + expect(build.grantPrincipal).toEqual(role); + Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', { + StorageLocation: { + RoleArn: role.roleArn, + }, + }); + }); + + test('with a custom buildName should set it in CloudFormation', () => { + build = new gamelift.Build(stack, 'BuildWithName', { + ...defaultProps, + buildName: buildName, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', { + Name: buildName, + }); + }); + + test('with all optional attributes should set it in CloudFormation', () => { + build = new gamelift.Build(stack, 'BuildWithName', { + ...defaultProps, + buildName: buildName, + operatingSystem: OperatingSystem.AMAZON_LINUX_2, + buildVersion: '1.0', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', { + Name: buildName, + OperatingSystem: OperatingSystem.AMAZON_LINUX_2, + Version: '1.0', + }); + }); + + test('with an incorrect buildName (>1024)', () => { + let incorrectBuildName = ''; + for (let i = 0; i < 1025; i++) { + incorrectBuildName += 'A'; + } + + expect(() => new gamelift.Build(stack, 'BuildWithWrongName', { + content, + buildName: incorrectBuildName, + })).toThrow(/Build name can not be longer than 1024 characters but has 1025 characters./); + }); + }); + }); +}); + + diff --git a/packages/@aws-cdk/aws-gamelift/test/content.test.ts b/packages/@aws-cdk/aws-gamelift/test/content.test.ts new file mode 100644 index 0000000000000..0c1f6e0617121 --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/content.test.ts @@ -0,0 +1,280 @@ +import * as path from 'path'; +import { Template } from '@aws-cdk/assertions'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as cxapi from '@aws-cdk/cx-api'; +import * as gamelift from '../lib'; + +describe('Code', () => { + let stack: cdk.Stack; + let content: gamelift.Content; + + beforeEach(() => { + const app = new cdk.App({ context: { [cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: false } }); + stack = new cdk.Stack(app, 'Stack'); + }); + + describe('.fromBucket()', () => { + const key = 'content'; + let bucket: s3.IBucket; + + test('with valid bucket name and key and bound by build sets the right path and grants the build permissions to read from it', () => { + bucket = s3.Bucket.fromBucketName(stack, 'Bucket', 'bucketname'); + content = gamelift.Content.fromBucket(bucket, key); + new gamelift.Build(stack, 'Build1', { + content: content, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', { + StorageLocation: { + Bucket: 'bucketname', + Key: 'content', + }, + }); + + // Role policy should grant reading from the assets bucket + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 's3:GetObject*', + 's3:GetBucket*', + 's3:List*', + ], + Effect: 'Allow', + Resource: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':s3:::bucketname', + ], + ], + }, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':s3:::bucketname/content', + ], + ], + }, + ], + }, + ], + }, + Roles: [ + { + Ref: 'Build1ServiceRole24FABCB7', + }, + ], + }); + }); + }); + + describe('.fromAsset()', () => { + const directoryPath = path.join(__dirname, 'my-game-build'); + + beforeEach(() => { + content = gamelift.Content.fromAsset(directoryPath); + }); + + test("with valid and existing file path and bound to job sets job's script location and permissions stack metadata", () => { + new gamelift.Build(stack, 'Build1', { + content: content, + }); + + expect(stack.node.metadata.find(m => m.type === 'aws:cdk:asset')).toBeDefined(); + Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', { + StorageLocation: { + Bucket: { + Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3Bucket72AA8348', + }, + Key: { + 'Fn::Join': [ + '', + [ + { + 'Fn::Select': [ + 0, + { + 'Fn::Split': [ + '||', + { + Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3VersionKey720D3160', + }, + ], + }, + ], + }, + { + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + '||', + { + Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3VersionKey720D3160', + }, + ], + }, + ], + }, + ], + ], + }, + RoleArn: { + 'Fn::GetAtt': [ + 'Build1ServiceRole24FABCB7', + 'Arn', + ], + }, + }, + }); + // Role policy should grant reading from the assets bucket + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 's3:GetObject*', + 's3:GetBucket*', + 's3:List*', + ], + Effect: 'Allow', + Resource: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':s3:::', + { + Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3Bucket72AA8348', + }, + ], + ], + }, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':s3:::', + { + Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3Bucket72AA8348', + }, + '/*', + ], + ], + }, + ], + }, + ], + }, + Roles: [ + { + Ref: 'Build1ServiceRole24FABCB7', + }, + ], + }); + }); + + test('with an unsupported file path throws', () => { + // GIVEN + const fileAsset = gamelift.Content.fromAsset(path.join(__dirname, 'my-game-build', 'index.js')); + + // THEN + expect(() => new gamelift.Build(stack, 'Build1', { content: fileAsset })) + .toThrow(/Asset must be a \.zip file or a directory/); + }); + + test('used in more than 1 build in the same stack should be reused', () => { + new gamelift.Build(stack, 'Build1', { + content: content, + }); + new gamelift.Build(stack, 'Build2', { + content: content, + }); + const StorageLocation = { + Bucket: { + Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3Bucket72AA8348', + }, + Key: { + 'Fn::Join': [ + '', + [ + { + 'Fn::Select': [ + 0, + { + 'Fn::Split': [ + '||', + { + Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3VersionKey720D3160', + }, + ], + }, + ], + }, + { + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + '||', + { + Ref: 'AssetParameters6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7S3VersionKey720D3160', + }, + ], + }, + ], + }, + ], + ], + }, + RoleArn: { + 'Fn::GetAtt': [ + 'Build1ServiceRole24FABCB7', + 'Arn', + ], + }, + }; + + expect(stack.node.metadata.find(m => m.type === 'aws:cdk:asset')).toBeDefined(); + // Job1 and Job2 use reuse the asset + Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', { + StorageLocation, + }); + Template.fromStack(stack).hasResourceProperties('AWS::GameLift::Build', { + StorageLocation, + }); + }); + + test('throws if trying to rebind in another stack', () => { + new gamelift.Build(stack, 'Build1', { + content, + }); + const differentStack = new cdk.Stack(); + + expect(() => new gamelift.Build(differentStack, 'Build2', { + content, + })).toThrow(/Asset is already associated with another stack/); + }); + }); +}); diff --git a/packages/@aws-cdk/aws-gamelift/test/gamelift.test.ts b/packages/@aws-cdk/aws-gamelift/test/gamelift.test.ts deleted file mode 100644 index 465c7bdea0693..0000000000000 --- a/packages/@aws-cdk/aws-gamelift/test/gamelift.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import '@aws-cdk/assertions'; -import {} from '../lib'; - -test('No tests are specified for this package', () => { - expect(true).toBe(true); -}); diff --git a/packages/@aws-cdk/aws-gamelift/test/integ.build.ts b/packages/@aws-cdk/aws-gamelift/test/integ.build.ts new file mode 100644 index 0000000000000..d216cbccd9212 --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/integ.build.ts @@ -0,0 +1,13 @@ +import * as path from 'path'; +import * as cdk from '@aws-cdk/core'; +import * as gamelift from '../lib'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'aws-gamelift-build'); + +new gamelift.Build(stack, 'Build', { + content: gamelift.Content.fromAsset(path.join(__dirname, 'my-game-build')), +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-gamelift/test/my-game-build.zip b/packages/@aws-cdk/aws-gamelift/test/my-game-build.zip new file mode 100644 index 0000000000000000000000000000000000000000..4a13be08c2721f5ebc49f7a00b844dd5ec909d5e GIT binary patch literal 419 zcmWIWW@Zs#0D+~wwxM7Kl;C4fV92f1O;5~C)lDkR%t_G?4dG>AZ{KU0+yTTOy0n6u zfsy40BLf52i~z7%91I)`E0N5S2AU^^VOC~dN@|5(Rx#MjexO+sffz)indyD*y!N^C zo>xzw^uHeP-Q(=Zli?ia^-p;QUf;sT5a7+u@kLl-yDZQOkTn6`j7%cTxV;1Q8N*vg q5Q|jLLCl7E57}Oj_h4X2qYsAJ#CkTso0Sb@4igX_0Mchc90mXZ_+Z)q literal 0 HcmV?d00001 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 new file mode 100644 index 0000000000000..73c02658c48d9 --- /dev/null +++ b/packages/@aws-cdk/aws-gamelift/test/my-game-build/index.js @@ -0,0 +1 @@ +console.log('Hello World'); \ No newline at end of file