Skip to content

Commit

Permalink
feat(gamelift): add support for buildArn output attribute (#23070)
Browse files Browse the repository at this point in the history
Add support to buildArn output attribute to Build L2 construct.
----

### 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*
  • Loading branch information
stevehouel committed Nov 24, 2022
1 parent cc957d6 commit 08f2995
Show file tree
Hide file tree
Showing 12 changed files with 310 additions and 71 deletions.
5 changes: 4 additions & 1 deletion packages/@aws-cdk/aws-gamelift/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,12 @@ services.

```ts
declare const bucket: s3.Bucket;
new gamelift.Build(this, 'Build', {
const build = new gamelift.Build(this, 'Build', {
content: gamelift.Content.fromBucket(bucket, "sample-asset-key")
});

new CfnOutput(this, 'BuildArn', { value: build.buildArn });
new CfnOutput(this, 'BuildId', { value: build.buildId });
```

#### Upload a realtime server Script
Expand Down
77 changes: 71 additions & 6 deletions packages/@aws-cdk/aws-gamelift/lib/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ export interface IBuild extends cdk.IResource, iam.IGrantable {
* @attribute
*/
readonly buildId: string;

/**
* The ARN of the build
*
* @attribute
*/
readonly buildArn: string;
}

/**
Expand All @@ -33,6 +40,10 @@ export abstract class BuildBase extends cdk.Resource implements IBuild {
* The Identifier of the build.
*/
public abstract readonly buildId: string;
/**
* The ARN of the build.
*/
public abstract readonly buildArn: string;

public abstract readonly grantPrincipal: iam.IPrincipal;
}
Expand All @@ -52,12 +63,25 @@ export enum OperatingSystem {
*/
export interface BuildAttributes {
/**
* The identifier of the build
* The ARN of the build
*
* At least one of `buildArn` and `buildId` must be provided.
*
* @default derived from `buildId`.
*/
readonly buildId: string;
readonly buildArn?: string;

/**
* The identifier of the build
*
* At least one of `buildId` and `buildArn` must be provided.
*
* @default derived from `buildArn`.
*/
readonly buildId?: string;
/**
* The IAM role assumed by GameLift to access server build in S3.
* @default - undefined
* @default the imported fleet cannot be granted access to other resources as an `iam.IGrantable`.
*/
readonly role?: iam.IRole;
}
Expand Down Expand Up @@ -151,15 +175,45 @@ export class Build extends BuildBase {
return this.fromBuildAttributes(scope, id, { buildId });
}

/**
* Import a build into CDK using its ARN
*/
static fromBuildArn(scope: Construct, id: string, buildArn: string): IBuild {
return this.fromBuildAttributes(scope, id, { buildArn });
}

/**
* Import an existing build from its attributes.
*/
static fromBuildAttributes(scope: Construct, id: string, attrs: BuildAttributes): IBuild {
if (!attrs.buildId && !attrs.buildArn) {
throw new Error('Either buildId or buildArn must be provided in BuildAttributes');
}
const buildId = attrs.buildId ??
cdk.Stack.of(scope).splitArn(attrs.buildArn!, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName;

if (!buildId) {
throw new Error(`No build identifier found in ARN: '${attrs.buildArn}'`);
}

const buildArn = attrs.buildArn ?? cdk.Stack.of(scope).formatArn({
service: 'gamelift',
resource: 'build',
resourceName: attrs.buildId,
arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME,
});
class Import extends BuildBase {
public readonly buildId = attrs.buildId;
public readonly buildId = buildId!;
public readonly buildArn = buildArn;
public readonly grantPrincipal = attrs.role ?? new iam.UnknownPrincipal({ resource: this });
}
public readonly role = attrs.role;

constructor(s: Construct, i: string) {
super(s, i, {
environmentFromArn: buildArn,
});
}
}
return new Import(scope, id);
}

Expand All @@ -168,6 +222,11 @@ export class Build extends BuildBase {
*/
public readonly buildId: string;

/**
* The ARN of the build.
*/
public readonly buildArn: string;

/**
* The IAM role GameLift assumes to acccess server build content.
*/
Expand Down Expand Up @@ -209,7 +268,13 @@ export class Build extends BuildBase {

resource.node.addDependency(this.role);

this.buildId = resource.ref;
this.buildId = this.getResourceNameAttribute(resource.ref);
this.buildArn = cdk.Stack.of(scope).formatArn({
service: 'gamelift',
resource: 'build',
resourceName: this.buildId,
arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME,
});
}


Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-gamelift/rosetta/default.ts-fixture
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Fixture with packages imported, but nothing else
import { Construct } from 'constructs';
import { Duration, Size, Stack } from '@aws-cdk/core';
import { Duration, Size, Stack, CfnOutput } from '@aws-cdk/core';
import * as gamelift from '@aws-cdk/aws-gamelift';
import * as s3 from '@aws-cdk/aws-s3';
import * as ec2 from '@aws-cdk/aws-ec2';
Expand Down
124 changes: 90 additions & 34 deletions packages/@aws-cdk/aws-gamelift/test/build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,12 @@ import * as cxapi from '@aws-cdk/cx-api';
import * as gamelift 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 buildName = 'test-build';
let stack: cdk.Stack;
const contentBucketAccessStatement = {
Action: [
's3:GetObject',
Expand All @@ -70,6 +38,8 @@ describe('build', () => {
let defaultProps: gamelift.BuildProps;

beforeEach(() => {
const app = new cdk.App({ context: { [cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: false } });
stack = new cdk.Stack(app);
contentBucket = s3.Bucket.fromBucketName(stack, 'ContentBucket', contentBucketName);
content = gamelift.Content.fromBucket(contentBucket, 'content');
defaultProps = {
Expand Down Expand Up @@ -215,6 +185,92 @@ describe('build', () => {
});
});
});

describe('test import methods', () => {
test('Build.fromBuildArn', () => {
// GIVEN
const stack2 = new cdk.Stack();

// WHEN
const imported = gamelift.Build.fromBuildArn(stack2, 'Imported', 'arn:aws:gamelift:us-east-1:123456789012:build/sample-build-id');

// THEN
expect(imported.buildArn).toEqual('arn:aws:gamelift:us-east-1:123456789012:build/sample-build-id');
expect(imported.buildId).toEqual('sample-build-id');
});

test('Build.fromBuildId', () => {
// GIVEN
const stack = new cdk.Stack();

// WHEN
const imported = gamelift.Build.fromBuildId(stack, 'Imported', 'sample-build-id');

// THEN
expect(stack.resolve(imported.buildArn)).toStrictEqual({
'Fn::Join': ['', [
'arn:',
{ Ref: 'AWS::Partition' },
':gamelift:',
{ Ref: 'AWS::Region' },
':',
{ Ref: 'AWS::AccountId' },
':build/sample-build-id',
]],
});
expect(stack.resolve(imported.buildId)).toStrictEqual('sample-build-id');
});
});

describe('Build.fromBuildAttributes()', () => {
let stack: cdk.Stack;
const buildId = 'build-test-identifier';
const buildArn = `arn:aws:gamelift:build-region:123456789012:build/${buildId}`;

beforeEach(() => {
const app = new cdk.App();
stack = new cdk.Stack(app, 'Base', {
env: { account: '111111111111', region: 'stack-region' },
});
});

describe('', () => {
test('with required attrs only', () => {
const importedFleet = gamelift.Build.fromBuildAttributes(stack, 'ImportedBuild', { buildArn });

expect(importedFleet.buildId).toEqual(buildId);
expect(importedFleet.buildArn).toEqual(buildArn);
expect(importedFleet.env.account).toEqual('123456789012');
expect(importedFleet.env.region).toEqual('build-region');
});

test('with missing attrs', () => {
expect(() => gamelift.Build.fromBuildAttributes(stack, 'ImportedBuild', { }))
.toThrow(/Either buildId or buildArn must be provided in BuildAttributes/);
});

test('with invalid ARN', () => {
expect(() => gamelift.Build.fromBuildAttributes(stack, 'ImportedBuild', { buildArn: 'arn:aws:gamelift:build-region:123456789012:build' }))
.toThrow(/No build identifier found in ARN: 'arn:aws:gamelift:build-region:123456789012:build'/);
});
});

describe('for an build in a different account and region', () => {
let build: gamelift.IBuild;

beforeEach(() => {
build = gamelift.Build.fromBuildAttributes(stack, 'ImportedBuild', { buildArn });
});

test("the build's region is taken from the ARN", () => {
expect(build.env.region).toBe('build-region');
});

test("the build's account is taken from the ARN", () => {
expect(build.env.account).toBe('123456789012');
});
});
});
});


This file was deleted.

Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
# make sure the gameserver is executable
/usr/bin/chmod +x /local/game/TestApplicationServer
exit 0
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
{
"version": "21.0.0",
"files": {
"6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7": {
"b95e4173bc399a8f686a4951aa26e01de1ed1e9d981ee1a7f18a15512dbdcb37": {
"source": {
"path": "asset.6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7",
"path": "asset.b95e4173bc399a8f686a4951aa26e01de1ed1e9d981ee1a7f18a15512dbdcb37",
"packaging": "zip"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "6019bfc8ab05a24b0ae9b5d8f4585cbfc7d1c30a23286d0b25ce7066a368a5d7.zip",
"objectKey": "b95e4173bc399a8f686a4951aa26e01de1ed1e9d981ee1a7f18a15512dbdcb37.zip",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
},
"56a977de7626326c13fb108674329fc1a0952d0c525384c951169c7c75812e47": {
"2bd5bde6b8c6af8bf7ca2e03bf58bcf2fbd6a755101d9747a72238d65e0d8230": {
"source": {
"path": "aws-gamelift-build.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "56a977de7626326c13fb108674329fc1a0952d0c525384c951169c7c75812e47.json",
"objectKey": "2bd5bde6b8c6af8bf7ca2e03bf58bcf2fbd6a755101d9747a72238d65e0d8230.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
Expand Down
Loading

0 comments on commit 08f2995

Please sign in to comment.