Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(gamelift): add support for buildArn output attribute #23070

Merged
merged 3 commits into from
Nov 24, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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