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(Sagemaker) Allow Sagemaker Endpoint to be defined via L2 construct #20941

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
46 changes: 44 additions & 2 deletions packages/@aws-cdk/aws-sagemaker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,58 @@
>
> [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.

---

<!--END STABILITY BANNER-->

This module is part of the [AWS Cloud Development Kit](https:/aws/aws-cdk) project.

## Installation

Import to your project:

```ts nofixture
import * as sagemaker from '@aws-cdk/aws-sagemaker';
```

## Endpoint

### Basic definiton

In order to define a Endpoint, you must provide the name of an endpoint config. This following codes allow you to define a basic AWS Sagemaker Endpoints:

```ts
const endpoint = new sagemaker.Endpoint(this, 'MyEndpoint', {
endpointName: 'MyEndpoint',
endpointConfigName: 'MyEndpointConfig',
});
```

### Applying tags to endpoints

You can apply [tags](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resource-tags.html) to endpoint resources, example codes:

```ts
new sagemaker.Endpoint(this, 'MyEndpoint', {
endpointName: 'MyEndpoint',
endpointConfigName: 'MyEndpointConfig',
tags: {
'key1': 'value1',
'key2': 'value2',
},
});
```

---

<!--BEGIN CFNONLY DISCLAIMER-->

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:
Expand All @@ -29,11 +71,11 @@ There are no official hand-written ([L2](https://docs.aws.amazon.com/cdk/latest/

<!--BEGIN CFNONLY DISCLAIMER-->

There are no hand-written ([L2](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) constructs for this service yet.
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::SageMaker](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_SageMaker.html).

(Read the [CDK Contributing Guide](https:/aws/aws-cdk/blob/main/CONTRIBUTING.md) and submit an RFC if you are interested in contributing to this construct library.)

<!--END CFNONLY DISCLAIMER-->
<!--END CFNONLY DISCLAIMER-->
93 changes: 93 additions & 0 deletions packages/@aws-cdk/aws-sagemaker/lib/endpoint-base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import * as iam from '@aws-cdk/aws-iam';
import { IResource, Resource, ResourceProps } from '@aws-cdk/core';
import { Construct } from 'constructs';

/**
* Represents an Sagemaker endpoint resource.
*/
export interface IEndpoint extends IResource {
/**
* The ARN of the endpoint.
*
* @attribute
*/
readonly endpointArn: string;

/**
* The name of the endpoint.
*
* @attribute
*/
readonly endpointName: string;

/**
* Grant access to invoke the endpoint to the given identity.
*
* This will grant the following permissions:
* - sagemaker:InvokeEndpoint
*
* @param grantee Principal to grant send rights to
*/
grantInvoke(grantee: iam.IGrantable): iam.Grant;
}

/**
* Reference to a new or existing Amazon Sagemaker endpoint.
*/
export abstract class EndpointBase extends Resource implements IEndpoint {

/**
* The ARN of the endpoint.
*
* @attribute
*/
public abstract readonly endpointArn: string;

/**
* The name of the endpoint.
*
* @attribute
*/
public abstract readonly endpointName: string;

constructor(scope: Construct, id: string, props: ResourceProps = {}) {
super(scope, id, props);
}

/**
* Grant access to invoke the endpoint to the given identity.
*
* This will grant the following permissions:
* - sagemaker:InvokeEndpoint
*
* @param grantee Principal to grant send rights to
*/
public grantInvoke(grantee: iam.IGrantable) {
return iam.Grant.addToPrincipal({
grantee,
actions: [
'sagemaker:InvokeEndpoint',
],
resourceArns: [
this.endpointArn,
],
});
}
}

/**
* Reference to a endpoint
*/
export interface EndpointAttributes {
/**
* The ARN of the endpoint.
*/
readonly endpointArn: string;

/**
* The name of the endpoint.
*
* @default - if endpoint name is not specified, the name will be derived from the endpoint ARN
*/
readonly endpointName?: string;
}
105 changes: 105 additions & 0 deletions packages/@aws-cdk/aws-sagemaker/lib/endpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { ArnFormat, Stack, CfnTag } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { EndpointAttributes, EndpointBase, IEndpoint } from './endpoint-base';
import { CfnEndpoint } from './sagemaker.generated';
import { validateEndpointProps } from './validate-props';

/**
* Properties for creating a new Endpoint.
*/
export interface EndpointProps {
/**
* A physical name for the endpoint.
*
* @default CloudFormation-generated name
*/
readonly endpointName?: string;

/**
* The name for the configuration for the endpoint.
*/
readonly endpointConfigName: string;

/**
* Tags to apply to the endpoint.
*
* @default {}
*/
readonly tags?: { [key: string]: string };
}

/**
* A new Amazon Sagemaker endpoint.
*/
export class Endpoint extends EndpointBase {
/**
* Import an existing endpoint from endpoint attributes.
*
* @param scope the parent Construct for this new Construct
* @param id the logical ID of this new Construct
* @param attrs the properties of the referenced endpoint
* @returns a Construct representing a reference to an existing endpoint
*/
public static fromEndpointAttributes(scope: Construct, id: string, attrs: EndpointAttributes): IEndpoint {

class ImportedEndpoint extends EndpointBase {
public readonly endpointArn = attrs.endpointArn;
public readonly endpointName = attrs.endpointName || this.parseEndpointNameFromArn(attrs.endpointArn);

private parseEndpointNameFromArn(endpointArn: string): string {
const stack = Stack.of(scope);
const parsedArn = stack.splitArn(endpointArn, ArnFormat.SLASH_RESOURCE_NAME);
if (!parsedArn.resourceName) {
throw new Error(`Can not get endpoint name from ARN ${endpointArn}, please provide endpoint name.`);
}
return parsedArn.resourceName!!;
}
}

return new ImportedEndpoint(scope, id);
}

/**
* The ARN of the endpoint.
*/
public readonly endpointArn: string;

/**
* The physical name of the endpoint.
*/
public readonly endpointName: string;

constructor(scope: Construct, id: string, props: EndpointProps) {
super(scope, id, {
physicalName: props.endpointName,
});

validateEndpointProps(props);

const endpoint = new CfnEndpoint(this, 'Resource', {
endpointName: this.physicalName,
endpointConfigName: props.endpointConfigName,
tags: this.renderTags(props.tags),
});

this.endpointArn = this.getResourceArnAttribute(endpoint.ref, {
service: 'sagemaker',
resource: 'endpoint',
resourceName: this.physicalName,
});
this.endpointName = this.getResourceNameAttribute(endpoint.attrEndpointName);
}

/**
* Render the configured Tags to be added to the endpoint properties.
*/
private renderTags(tags?: { [key: string]: string }): CfnTag[] | undefined {
if (!tags) { return undefined; }
return Object.keys(tags).map((key) => {
return {
key: key,
value: tags[key],
};
});
}
}
3 changes: 3 additions & 0 deletions packages/@aws-cdk/aws-sagemaker/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export * from './endpoint';
export * from './endpoint-base';

// AWS::SageMaker CloudFormation Resources:
export * from './sagemaker.generated';
19 changes: 19 additions & 0 deletions packages/@aws-cdk/aws-sagemaker/lib/validate-props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { EndpointProps } from './index';

export function validateEndpointProps(props: EndpointProps) {
const defaultNameMaxLength: number = 63;
const defaultNamePattern: RegExp = /^[a-zA-Z0-9](-*[a-zA-Z0-9]){0,62}/;
validateName('Endpoint name', props.endpointName, defaultNameMaxLength, defaultNamePattern);
validateName('Endpoint config name', props.endpointConfigName, defaultNameMaxLength, defaultNamePattern);
}

function validateName(label: string, name: string | undefined, maxLength: number, pattern: RegExp) {
if (name === undefined) { return; }

if (name.length > maxLength) {
throw new Error(`${label} can not be longer than ${maxLength} characters.`);
}
if (!pattern.test(name)) {
throw new Error(`${label} can contain only letters, numbers, hyphens with no spaces.`);
}
}
9 changes: 7 additions & 2 deletions packages/@aws-cdk/aws-sagemaker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,21 +85,26 @@
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/cfn2ts": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^27.5.2"
"@types/jest": "^27.5.2",
"jest": "^27.5.1"
},
"dependencies": {
"@aws-cdk/core": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-cloudwatch": "0.0.0",
"constructs": "^10.0.0"
},
"peerDependencies": {
"@aws-cdk/core": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-cloudwatch": "0.0.0",
"constructs": "^10.0.0"
},
"engines": {
"node": ">= 14.15.0"
},
"stability": "experimental",
"maturity": "cfn-only",
"maturity": "experimental",
"awscdkio": {
"announce": false
},
Expand Down
12 changes: 12 additions & 0 deletions packages/@aws-cdk/aws-sagemaker/rosetta/default.ts-fixture
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Fixture with packages imported, but nothing else
import { Construct } from 'constructs';
import { Stack } from '@aws-cdk/core';
import sagemaker = require('@aws-cdk/aws-sagemaker');

class Fixture extends Stack {
constructor(scope: Construct, id: string) {
super(scope, id);

/// here
}
}
Loading