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(glue): add ExternalTable for use with connections #24753

Merged
merged 54 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
012df48
addition: connection name
Rizxcviii Mar 22, 2023
cfed556
addition: custom location of data
Rizxcviii Mar 22, 2023
13dcaa8
adding connection integ
Rizxcviii Mar 22, 2023
5be4ffc
addition: README
Rizxcviii Mar 23, 2023
2172a27
Merge branch 'main' into feature/glue-connection-for-tables
Rizxcviii Apr 3, 2023
93923a5
Merge branch 'main' into feature/glue-connection-for-tables
Rizxcviii Apr 13, 2023
6f337a5
Merge branch 'main' into feature/glue-connection-for-tables
Rizxcviii Apr 13, 2023
6581bf4
integ
Rizxcviii Apr 14, 2023
37c4965
Merge branch 'main' into feature/glue-connection-for-tables
Rizxcviii Jul 4, 2023
00593b7
updating README to combine 2 examples into one
Rizxcviii Jul 4, 2023
431fbca
grant behaviour incorrect
Rizxcviii Jul 13, 2023
3af0907
adding package by accident
Rizxcviii Jul 13, 2023
3e007ee
Merge branch 'main' into feature/glue-connection-for-tables
Rizxcviii Jul 13, 2023
bac46a6
removing undefined
Rizxcviii Jul 13, 2023
243a0e3
Merge branch 'main' into feature/glue-connection-for-tables
Rizxcviii Aug 1, 2023
501249d
integ test
Rizxcviii Aug 1, 2023
3a43fe8
deprecating table
Rizxcviii Aug 23, 2023
e083cff
deprecating table
Rizxcviii Aug 23, 2023
cc1a180
deprecating
Rizxcviii Aug 23, 2023
6aa651d
deprecating
Rizxcviii Aug 23, 2023
772649d
Table -> S3Table
Rizxcviii Aug 23, 2023
31c0d44
Table -> S3Table
Rizxcviii Aug 23, 2023
5a9b46f
deprecating Table constructu
Rizxcviii Aug 23, 2023
07cb52d
using S3 table
Rizxcviii Aug 23, 2023
d7ea017
new table
Rizxcviii Aug 23, 2023
ea081de
old table deprecated
Rizxcviii Aug 23, 2023
c4317b1
integ test
Rizxcviii Aug 23, 2023
130bd3c
bump
Rizxcviii Aug 24, 2023
261a9a8
Merge branch 'main' into feature/glue-connection-for-tables
Rizxcviii Aug 24, 2023
3fc36cd
Merge branch 'main' into feature/glue-connection-for-tables
Rizxcviii Aug 25, 2023
dc90f58
separating table tests
Rizxcviii Aug 25, 2023
b9ba3f2
moving table encryption to s3 tables only
Rizxcviii Aug 29, 2023
4348400
bug with table description
Rizxcviii Aug 29, 2023
634f717
removing encryption test
Rizxcviii Aug 29, 2023
e259fbd
removing encryption tests
Rizxcviii Aug 30, 2023
8fcc934
adding deprecated table tests
Rizxcviii Aug 30, 2023
eabfa71
updating README
Rizxcviii Aug 31, 2023
d5f762c
incorrect changes
Rizxcviii Sep 1, 2023
dab9f28
english...
Rizxcviii Sep 1, 2023
5abf222
generate -> get
Rizxcviii Sep 1, 2023
7d8a005
moving s3 declaration
Rizxcviii Sep 1, 2023
05cd0ce
using Table.*
Rizxcviii Sep 1, 2023
285de6c
keeping deprecated table the same
Rizxcviii Sep 1, 2023
a8b4948
Making a subclass of S3Table
Rizxcviii Sep 4, 2023
8062297
changing table name
Rizxcviii Sep 4, 2023
da2f878
reverting test file
Rizxcviii Sep 4, 2023
df4d288
integ test
Rizxcviii Sep 4, 2023
8e053a7
removing location prop, keeping this for separate PR
Rizxcviii Sep 6, 2023
4ebc18d
the english language
Rizxcviii Sep 6, 2023
693dae0
Merge branch 'main' into feature/glue-connection-for-tables
mrgrain Sep 11, 2023
f0c455b
splitting external table into own test file
Rizxcviii Sep 11, 2023
45ea77c
compat issues, TableProps is now used for deprecated table
Rizxcviii Sep 11, 2023
447acb9
integ test
Rizxcviii Sep 11, 2023
e12dee6
Merge branch 'main' into feature/glue-connection-for-tables
Rizxcviii Sep 11, 2023
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
20 changes: 20 additions & 0 deletions packages/@aws-cdk/aws-glue-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,26 @@ new glue.Table(this, 'MyTable', {

*Note: you cannot provide a `Bucket` when creating the `Table` if you wish to use server-side encryption (`KMS`, `KMS_MANAGED` or `S3_MANAGED`)*.

### Glue Connections

Glue connections allow external data connections to third party databases and data warehouses. However, these connections can also be assigned to Glue Tables, allowing you to query external data sources using the Glue Data Catalog. The `connection` property of the `Table` class allows you to specify a Glue Connection to be associated with the table. Use this in combination with the `externalDataLocation` property to specify an internal destination that the connection will point to:

```ts
declare const myConnection: glue.Connection;
declare const myDatabase: glue.Database;
new glue.Table(this, 'MyTable', {
connection: myConnection,
externalDataLocation: 'default_db_public_example', // A table in Redshift
// ...
database: myDatabase,
columns: [{
name: 'col1',
type: glue.Schema.STRING,
}],
dataFormat: glue.DataFormat.JSON,
});
```

## Types

A table's schema is a collection of columns, each of which have a `name` and a `type`. Types are recursive structures, consisting of primitive and complex types:
Expand Down
49 changes: 37 additions & 12 deletions packages/@aws-cdk/aws-glue-alpha/lib/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import { ArnFormat, Fn, IResource, Lazy, Names, Resource, Stack } from 'aws-cdk-
import * as cr from 'aws-cdk-lib/custom-resources';
import { AwsCustomResource } from 'aws-cdk-lib/custom-resources';
import { Construct } from 'constructs';
import { IConnection } from './connection';
import { DataFormat } from './data-format';
import { IDatabase } from './database';
import { S3Table } from './s3-table';
import { Column } from './schema';
import { StorageParameter } from './storage-parameter';

Expand Down Expand Up @@ -183,6 +185,13 @@ export interface TableProps {
*/
readonly enablePartitionFiltering?: boolean;

/**
* The connection the table will use when performing reads and writes.
*
* @default - No connection
*/
readonly connection?: IConnection;

/**
* The user-supplied properties for the description of the physical storage of this table. These properties help describe the format of the data that is stored within the crawled data sources.
*
Expand Down Expand Up @@ -221,6 +230,8 @@ export interface TableProps {

/**
* A Glue table.
*
* @deprecate Use {@link S3Table} instead.
*/
export class Table extends Resource implements ITable {

Expand Down Expand Up @@ -262,7 +273,7 @@ export class Table extends Resource implements ITable {
/**
* The type of encryption enabled for the table.
*/
public readonly encryption: TableEncryption;
public readonly encryption?: TableEncryption;

/**
* The KMS key used to secure the data if `encryption` is set to `CSE-KMS` or `SSE-KMS`. Otherwise, `undefined`.
Expand All @@ -272,12 +283,12 @@ export class Table extends Resource implements ITable {
/**
* S3 bucket in which the table's data resides.
*/
public readonly bucket: s3.IBucket;
public readonly bucket?: s3.IBucket;

/**
* S3 Key Prefix under which this table's files are stored in S3.
*/
public readonly s3Prefix: string;
public readonly s3Prefix?: string;

/**
* Name of this table.
Expand Down Expand Up @@ -309,6 +320,11 @@ export class Table extends Resource implements ITable {
*/
public readonly partitionIndexes?: PartitionIndex[];

/**
* The location of the tables' data.
*/
readonly location?: string;

/**
* The tables' storage descriptor properties.
*/
Expand All @@ -331,18 +347,20 @@ export class Table extends Resource implements ITable {

this.database = props.database;
this.dataFormat = props.dataFormat;
this.s3Prefix = props.s3Prefix ?? '';

validateSchema(props.columns, props.partitionKeys);
this.columns = props.columns;
this.partitionKeys = props.partitionKeys;
this.storageParameters = props.storageParameters;

this.compressed = props.compressed ?? false;

this.s3Prefix = props.s3Prefix ?? '';
const { bucket, encryption, encryptionKey } = createBucket(this, props);
this.bucket = bucket;
this.encryption = encryption;
this.encryptionKey = encryptionKey;
this.location = `s3://${bucket.bucketName}/${this.s3Prefix}`;

const tableResource = new CfnTable(this, 'Table', {
catalogId: props.database.catalogId,
Expand All @@ -359,9 +377,10 @@ export class Table extends Resource implements ITable {
'classification': props.dataFormat.classificationString?.value,
'has_encrypted_data': true,
'partition_filtering.enabled': props.enablePartitionFiltering,
'connectionName': props.connection?.connectionName,
},
storageDescriptor: {
location: `s3://${this.bucket.bucketName}/${this.s3Prefix}`,
location: this.location,
compressed: this.compressed,
storedAsSubDirectories: props.storedAsSubDirectories ?? false,
columns: renderColumns(props.columns),
Expand Down Expand Up @@ -473,8 +492,10 @@ export class Table extends Resource implements ITable {
*/
public grantRead(grantee: iam.IGrantable): iam.Grant {
const ret = this.grant(grantee, readPermissions);
if (this.encryptionKey && this.encryption === TableEncryption.CLIENT_SIDE_KMS) { this.encryptionKey.grantDecrypt(grantee); }
this.bucket.grantRead(grantee, this.getS3PrefixForGrant());
if (this.bucket) {
if (this.encryptionKey && this.encryption === TableEncryption.CLIENT_SIDE_KMS) { this.encryptionKey.grantDecrypt(grantee); }
this.bucket.grantRead(grantee, this.getS3PrefixForGrant());
}
return ret;
}

Expand All @@ -485,8 +506,10 @@ export class Table extends Resource implements ITable {
*/
public grantWrite(grantee: iam.IGrantable): iam.Grant {
const ret = this.grant(grantee, writePermissions);
if (this.encryptionKey && this.encryption === TableEncryption.CLIENT_SIDE_KMS) { this.encryptionKey.grantEncrypt(grantee); }
this.bucket.grantWrite(grantee, this.getS3PrefixForGrant());
if (this.bucket) {
if (this.encryptionKey && this.encryption === TableEncryption.CLIENT_SIDE_KMS) { this.encryptionKey.grantEncrypt(grantee); }
this.bucket.grantWrite(grantee, this.getS3PrefixForGrant());
}
return ret;
}

Expand All @@ -497,8 +520,10 @@ export class Table extends Resource implements ITable {
*/
public grantReadWrite(grantee: iam.IGrantable): iam.Grant {
const ret = this.grant(grantee, [...readPermissions, ...writePermissions]);
if (this.encryptionKey && this.encryption === TableEncryption.CLIENT_SIDE_KMS) { this.encryptionKey.grantEncryptDecrypt(grantee); }
this.bucket.grantReadWrite(grantee, this.getS3PrefixForGrant());
if (this.bucket) {
if (this.encryptionKey && this.encryption === TableEncryption.CLIENT_SIDE_KMS) { this.encryptionKey.grantEncryptDecrypt(grantee); }
this.bucket.grantReadWrite(grantee, this.getS3PrefixForGrant());
}
return ret;
}

Expand Down Expand Up @@ -529,7 +554,7 @@ export class Table extends Resource implements ITable {
});
}

private getS3PrefixForGrant() {
protected getS3PrefixForGrant() {
return this.s3Prefix + '*';
}
}
Expand Down
28 changes: 28 additions & 0 deletions packages/@aws-cdk/aws-glue-alpha/test/integ.table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ const bucket = new s3.Bucket(stack, 'DataBucket', {
removalPolicy: cdk.RemovalPolicy.DESTROY,
});

const connection = new glue.Connection(stack, 'MyConnection', {
connectionName: 'my_connection',
type: glue.ConnectionType.JDBC,
properties: {
JDBC_CONNECTION_URL: 'jdbc:mysql://mysql.example.com:3306',
USERNAME: 'username',
PASSWORD: 'password',
},
});

const database = new glue.Database(stack, 'MyDatabase', {
databaseName: 'my_database',
});
Expand Down Expand Up @@ -101,6 +111,24 @@ new glue.Table(stack, 'MyPartitionFilteredTable', {
enablePartitionFiltering: true,
});

new glue.Table(stack, 'MyTableWithConnection', {
database,
bucket,
tableName: 'connection_table',
columns,
dataFormat: glue.DataFormat.JSON,
connection,
});

new glue.Table(stack, 'MyTableWithCustomLocation', {
database,
bucket,
tableName: 'custom_location_table',
columns,
dataFormat: glue.DataFormat.JSON,
externalDataLocation: 'default_db.public.test',
});

new glue.Table(stack, 'MyTableWithStorageDescriptorParameters', {
database,
bucket,
Expand Down
76 changes: 67 additions & 9 deletions packages/@aws-cdk/aws-glue-alpha/test/table.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ test('partitioned JSON table', () => {
});
expect(table.encryption).toEqual(glue.TableEncryption.S3_MANAGED);
expect(table.encryptionKey).toEqual(undefined);
expect(table.bucket.encryptionKey).toEqual(undefined);
expect(table.bucket).not.toEqual(undefined);
expect(table.bucket?.encryptionKey).toEqual(undefined);

Template.fromStack(tableStack).hasResourceProperties('AWS::Glue::Table', {
CatalogId: {
Expand Down Expand Up @@ -163,7 +164,7 @@ test('compressed table', () => {
dataFormat: glue.DataFormat.JSON,
});
expect(table.encryptionKey).toEqual(undefined);
expect(table.bucket.encryptionKey).toEqual(undefined);
expect(table.bucket?.encryptionKey).toEqual(undefined);

Template.fromStack(stack).hasResourceProperties('AWS::Glue::Table', {
CatalogId: {
Expand Down Expand Up @@ -246,7 +247,7 @@ test('encrypted table: SSE-S3', () => {
});
expect(table.encryption).toEqual(glue.TableEncryption.S3_MANAGED);
expect(table.encryptionKey).toEqual(undefined);
expect(table.bucket.encryptionKey).toEqual(undefined);
expect(table.bucket?.encryptionKey).toEqual(undefined);

Template.fromStack(stack).hasResourceProperties('AWS::Glue::Table', {
CatalogId: {
Expand Down Expand Up @@ -320,7 +321,7 @@ test('encrypted table: SSE-KMS (implicitly created key)', () => {
dataFormat: glue.DataFormat.JSON,
});
expect(table.encryption).toEqual(glue.TableEncryption.KMS);
expect(table.encryptionKey).toEqual(table.bucket.encryptionKey);
expect(table.encryptionKey).toEqual(table.bucket?.encryptionKey);

Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', {
Description: 'Created by Default/Table/Bucket',
Expand Down Expand Up @@ -408,7 +409,7 @@ test('encrypted table: SSE-KMS (explicitly created key)', () => {
dataFormat: glue.DataFormat.JSON,
});
expect(table.encryption).toEqual(glue.TableEncryption.KMS);
expect(table.encryptionKey).toEqual(table.bucket.encryptionKey);
expect(table.encryptionKey).toEqual(table.bucket?.encryptionKey);
expect(table.encryptionKey).not.toEqual(undefined);

Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', {
Expand Down Expand Up @@ -494,7 +495,7 @@ test('encrypted table: SSE-KMS_MANAGED', () => {
});
expect(table.encryption).toEqual(glue.TableEncryption.KMS_MANAGED);
expect(table.encryptionKey).toEqual(undefined);
expect(table.bucket.encryptionKey).toEqual(undefined);
expect(table.bucket?.encryptionKey).toEqual(undefined);

Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', {
BucketEncryption: {
Expand Down Expand Up @@ -569,7 +570,7 @@ test('encrypted table: CSE-KMS (implicitly created key)', () => {
});
expect(table.encryption).toEqual(glue.TableEncryption.CLIENT_SIDE_KMS);
expect(table.encryptionKey).not.toEqual(undefined);
expect(table.bucket.encryptionKey).toEqual(undefined);
expect(table.bucket?.encryptionKey).toEqual(undefined);

Template.fromStack(stack).resourceCountIs('AWS::KMS::Key', 1);

Expand Down Expand Up @@ -638,7 +639,7 @@ test('encrypted table: CSE-KMS (explicitly created key)', () => {
});
expect(table.encryption).toEqual(glue.TableEncryption.CLIENT_SIDE_KMS);
expect(table.encryptionKey).not.toEqual(undefined);
expect(table.bucket.encryptionKey).toEqual(undefined);
expect(table.bucket?.encryptionKey).toEqual(undefined);

Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', {
Description: 'MyKey',
Expand Down Expand Up @@ -711,7 +712,7 @@ test('encrypted table: CSE-KMS (explicitly passed bucket and key)', () => {
});
expect(table.encryption).toEqual(glue.TableEncryption.CLIENT_SIDE_KMS);
expect(table.encryptionKey).not.toEqual(undefined);
expect(table.bucket.encryptionKey).toEqual(undefined);
expect(table.bucket?.encryptionKey).toEqual(undefined);

Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', {
Description: 'MyKey',
Expand Down Expand Up @@ -1654,6 +1655,63 @@ test('storage descriptor parameters', () => {
});
});

test('can associate a connection with the glue table', () => {
const app = new cdk.App();
const stack = new cdk.Stack(app, 'Stack');
const database = new glue.Database(stack, 'Database');
const connection = new glue.Connection(stack, 'Connection', {
connectionName: 'my_connection',
type: glue.ConnectionType.JDBC,
properties: {
JDBC_CONNECTION_URL: 'jdbc:server://server:443/connection',
USERNAME: 'username',
PASSWORD: 'password',
},
});
new glue.Table(stack, 'Table', {
database,
tableName: 'my_table',
columns: [{
name: 'col',
type: glue.Schema.STRING,
}],
dataFormat: glue.DataFormat.JSON,
connection,
});

Template.fromStack(stack).hasResourceProperties('AWS::Glue::Table', {
TableInput: {
Parameters: {
connectionName: { Ref: 'Connection89AD5CF5' },
},
},
});
});

test('can associate an external location with the glue table', () => {
const app = new cdk.App();
const stack = new cdk.Stack(app, 'Stack');
const database = new glue.Database(stack, 'Database');
new glue.Table(stack, 'Table', {
database,
tableName: 'my_table',
columns: [{
name: 'col',
type: glue.Schema.STRING,
}],
dataFormat: glue.DataFormat.JSON,
externalDataLocation: 'default_db.public.test',
});

Template.fromStack(stack).hasResourceProperties('AWS::Glue::Table', {
TableInput: {
StorageDescriptor: {
Location: 'default_db.public.test',
},
},
});
});

function createTable(props: Pick<glue.TableProps, Exclude<keyof glue.TableProps, 'database' | 'dataFormat'>>): void {
const stack = new cdk.Stack();
new glue.Table(stack, 'table', {
Expand Down
Loading