Skip to content
Open
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
63 changes: 63 additions & 0 deletions packages/@aws-cdk/aws-sagemaker-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,66 @@ productionVariant.metricModelLatency().createAlarm(this, 'ModelLatencyAlarm', {
evaluationPeriods: 3,
});
```

## Pipeline

You can import existing SageMaker Pipelines into your CDK application to use with other AWS services like EventBridge Scheduler.

### Importing an existing Pipeline

Import a pipeline by ARN:

```typescript
import * as sagemaker from '@aws-cdk/aws-sagemaker-alpha';

const pipeline = sagemaker.Pipeline.fromPipelineArn(this, 'MyPipeline',
'arn:aws:sagemaker:us-east-1:123456789012:pipeline/my-pipeline');
```

Import a pipeline by name (must be in the same account and region as the stack):

```typescript
import * as sagemaker from '@aws-cdk/aws-sagemaker-alpha';

const pipeline = sagemaker.Pipeline.fromPipelineName(this, 'MyPipeline', 'my-pipeline');
```

### Using with EventBridge Scheduler

You can use imported pipelines with EventBridge Scheduler to trigger pipeline executions on a schedule:

```typescript
import * as scheduler from 'aws-cdk-lib/aws-scheduler';
import * as targets from 'aws-cdk-lib/aws-scheduler-targets';
import * as sagemaker from '@aws-cdk/aws-sagemaker-alpha';
import { Duration } from 'aws-cdk-lib';

const pipeline = sagemaker.Pipeline.fromPipelineName(this, 'MyPipeline', 'my-pipeline');

new scheduler.Schedule(this, 'PipelineSchedule', {
schedule: scheduler.ScheduleExpression.rate(Duration.hours(12)),
target: new targets.SageMakerStartPipelineExecution(pipeline, {
pipelineParameterList: [
{ name: 'InputDataPath', value: 's3://my-bucket/input/' },
{ name: 'ModelName', value: 'my-model-v1' }
]
})
});
```

### IAM Permissions

Grant permissions to start pipeline executions:

```typescript
import * as iam from 'aws-cdk-lib/aws-iam';
import * as sagemaker from '@aws-cdk/aws-sagemaker-alpha';

const pipeline = sagemaker.Pipeline.fromPipelineName(this, 'MyPipeline', 'my-pipeline');
const role = new iam.Role(this, 'PipelineRole', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com')
});

// Grant permission to start pipeline execution
pipeline.grantStartPipelineExecution(role);
```
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-sagemaker-alpha/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from './endpoint-config';
export * from './instance-type';
export * from './model';
export * from './model-data';
export * from './pipeline';
export * from './scalable-instance-count';

// AWS::SageMaker CloudFormation Resources:
189 changes: 189 additions & 0 deletions packages/@aws-cdk/aws-sagemaker-alpha/lib/pipeline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { Construct } from 'constructs';
import { Grant, IGrantable } from 'aws-cdk-lib/aws-iam';
import { IPipeline } from 'aws-cdk-lib/aws-sagemaker';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we should duplicate the IPipeline in the alpha module. Tentatively import it from stable module for now. Please advise if we should duplicate instead.

import { Arn, Resource, Stack, Token } from 'aws-cdk-lib';
import { ValidationError } from 'aws-cdk-lib/core/lib/errors';

/**
* Validates a SageMaker Pipeline name according to AWS requirements
* @param pipelineName The pipeline name to validate
* @param scope The construct scope for error reporting
* @throws ValidationError if the pipeline name is invalid
*/
function validatePipelineName(pipelineName: string, scope: Construct): void {
// Skip validation for CDK tokens
if (Token.isUnresolved(pipelineName)) {
return;
}

// Check length constraints (1-256 characters)
if (!pipelineName || pipelineName.length === 0 || pipelineName.length > 256) {
throw new ValidationError(`Invalid pipeline name format: ${pipelineName}. Pipeline name must be between 1-256 characters.`, scope);
}

// Check character pattern: must start and end with alphanumeric, can contain hyphens
if (!/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(pipelineName)) {
throw new ValidationError(`Invalid pipeline name format: ${pipelineName}. Must start and end with alphanumeric characters, can contain hyphens but not consecutive hyphens, underscores, dots, or spaces.`, scope);
}

// Check for consecutive hyphens (not allowed)
if (pipelineName.includes('--')) {
throw new ValidationError(`Invalid pipeline name format: ${pipelineName}. Cannot contain consecutive hyphens.`, scope);
}
}

/**
* Attributes for importing a SageMaker Pipeline
*/
export interface PipelineAttributes {
/**
* The ARN of the pipeline
* @default - Either this or pipelineName must be provided
*/
readonly pipelineArn?: string;

/**
* The name of the pipeline
* @default - Either this or pipelineArn must be provided
*/
readonly pipelineName?: string;

/**
* The account the pipeline is in
* @default - same account as the stack
*/
readonly account?: string;

/**
* The region the pipeline is in
* @default - same region as the stack
*/
readonly region?: string;
}

/**
* Properties for defining a SageMaker Pipeline
*/
export interface PipelineProps {
/**
* The physical name of the pipeline
* @default - CDK generated name
*/
readonly pipelineName?: string;
}

/**
* A SageMaker Pipeline
*
* @resource AWS::SageMaker::Pipeline
*/
export class Pipeline extends Resource implements IPipeline {
/**
* Import a pipeline from its ARN
*
* @param scope The parent construct
* @param id The construct id
* @param pipelineArn The ARN of the pipeline
*/
public static fromPipelineArn(scope: Construct, id: string, pipelineArn: string): IPipeline {
return Pipeline.fromPipelineAttributes(scope, id, { pipelineArn });
}

/**
* Import a pipeline from its name
*
* For this to work, the pipeline must be in the same account and region as the stack.
*
* @param scope The parent construct
* @param id The construct id
* @param pipelineName The name of the pipeline
*/
public static fromPipelineName(scope: Construct, id: string, pipelineName: string): IPipeline {
return Pipeline.fromPipelineAttributes(scope, id, { pipelineName });
}

/**
* Import a pipeline from attributes
*
* @param scope The parent construct
* @param id The construct id
* @param attrs The attributes of the pipeline to import
*/
public static fromPipelineAttributes(scope: Construct, id: string, attrs: PipelineAttributes): IPipeline {
const stack = Stack.of(scope);

// Determine pipeline name and ARN
let pipelineName: string;
let pipelineArn: string;

if (attrs.pipelineArn) {
pipelineArn = attrs.pipelineArn;
pipelineName = Arn.extractResourceName(pipelineArn, 'pipeline');
} else if (attrs.pipelineName !== undefined) {
pipelineName = attrs.pipelineName;
// Validate pipeline name format
validatePipelineName(pipelineName, scope);

pipelineArn = stack.formatArn({
service: 'sagemaker',
resource: 'pipeline',
resourceName: pipelineName,
account: attrs.account,
region: attrs.region,
});
} else {
throw new ValidationError('Either pipelineArn or pipelineName must be provided', scope);
}

class Import extends Resource implements IPipeline {
public readonly pipelineArn = pipelineArn;
public readonly pipelineName = pipelineName;

public grantStartPipelineExecution(grantee: IGrantable): Grant {
return Grant.addToPrincipal({
grantee,
actions: ['sagemaker:StartPipelineExecution'],
resourceArns: [this.pipelineArn],
});
}
}

return new Import(scope, id, {
account: attrs.account,
region: attrs.region,
});
}

/**
* The ARN of the pipeline.
*/
public readonly pipelineArn!: string;

/**
* The name of the pipeline.
*/
public readonly pipelineName!: string;

/**
* Create a new Pipeline (not supported - use import methods instead)
* @internal
*/
constructor(scope: Construct, id: string, props?: PipelineProps) {
super(scope, id);
// Suppress unused parameter warning
void props;
throw new ValidationError('Pipeline construct cannot be instantiated directly. Use Pipeline.fromPipelineArn() or Pipeline.fromPipelineName() to import existing pipelines.', scope);
}

/**
* Permits an IAM principal to start this pipeline execution
* @param grantee The principal to grant access to
*/
public grantStartPipelineExecution(grantee: IGrantable): Grant {
return Grant.addToPrincipal({
grantee,
actions: ['sagemaker:StartPipelineExecution'],
resourceArns: [this.pipelineArn],
});
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading