Skip to content

Commit fee2f72

Browse files
authored
Merge branch 'main' into epolon/bump-pacmak
2 parents 8f2de93 + dc8eb58 commit fee2f72

13 files changed

Lines changed: 1928 additions & 1 deletion

packages/@aws-cdk/aws-sagemaker-alpha/awslint.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"prefer-ref-interface:aws-cdk-lib.aws_ec2.InterfaceVpcEndpointOptions.securityGroups",
1515
"prefer-ref-interface:@aws-cdk/aws-sagemaker-alpha.ScalableInstanceCountProps.role",
1616
"interface-extends-ref:@aws-cdk/aws-sagemaker-alpha.IEndpointConfig",
17-
"interface-extends-ref:@aws-cdk/aws-sagemaker-alpha.IModel"
17+
"interface-extends-ref:@aws-cdk/aws-sagemaker-alpha.IModel",
18+
"no-grants:@aws-cdk/aws-sagemaker-alpha.Pipeline.grantStartPipelineExecution"
1819
]
1920
}

packages/@aws-cdk/aws-sagemaker-alpha/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export * from './endpoint-config';
55
export * from './instance-type';
66
export * from './model';
77
export * from './model-data';
8+
export * from './pipeline';
89
export * from './scalable-instance-count';
910

1011
// AWS::SageMaker CloudFormation Resources:
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import { Arn, Resource, Stack, Token } from 'aws-cdk-lib';
2+
import { Grant } from 'aws-cdk-lib/aws-iam';
3+
import type { IGrantable } from 'aws-cdk-lib/aws-iam';
4+
import type { IPipeline, PipelineReference } from 'aws-cdk-lib/aws-sagemaker';
5+
import { ValidationError } from 'aws-cdk-lib/core/lib/errors';
6+
import type { Construct } from 'constructs';
7+
8+
/**
9+
* Validates a SageMaker Pipeline name according to AWS requirements.
10+
*
11+
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-sagemaker-pipeline.html#cfn-sagemaker-pipeline-pipelinename
12+
*
13+
* @param pipelineName The pipeline name to validate
14+
* @param scope The construct scope for error reporting
15+
* @throws ValidationError if the pipeline name is invalid
16+
*/
17+
function validatePipelineName(pipelineName: string, scope: Construct): void {
18+
// Skip validation for CDK tokens
19+
if (Token.isUnresolved(pipelineName)) {
20+
return;
21+
}
22+
23+
// Check length constraints (1-256 characters)
24+
if (!pipelineName || pipelineName.length === 0 || pipelineName.length > 256) {
25+
throw new ValidationError(`Invalid pipeline name: "${pipelineName}". Pipeline name must be between 1-256 characters.`, scope);
26+
}
27+
28+
// Pattern from AWS docs: ^[a-zA-Z0-9](-*[a-zA-Z0-9])*$
29+
if (!/^[a-zA-Z0-9](-*[a-zA-Z0-9])*$/.test(pipelineName)) {
30+
throw new ValidationError(`Invalid pipeline name: "${pipelineName}". Pipeline name must match pattern: ^[a-zA-Z0-9](-*[a-zA-Z0-9])*$`, scope);
31+
}
32+
}
33+
34+
/**
35+
* Attributes for importing a SageMaker Pipeline
36+
*/
37+
export interface PipelineAttributes {
38+
/**
39+
* The ARN of the pipeline
40+
* @default - Either this or pipelineName must be provided
41+
*/
42+
readonly pipelineArn?: string;
43+
44+
/**
45+
* The name of the pipeline
46+
* @default - Either this or pipelineArn must be provided
47+
*/
48+
readonly pipelineName?: string;
49+
50+
/**
51+
* The account the pipeline is in
52+
* @default - same account as the stack
53+
*/
54+
readonly account?: string;
55+
56+
/**
57+
* The region the pipeline is in
58+
* @default - same region as the stack
59+
*/
60+
readonly region?: string;
61+
}
62+
63+
/**
64+
* Properties for defining a SageMaker Pipeline
65+
*/
66+
export interface PipelineProps {
67+
/**
68+
* The physical name of the pipeline
69+
* @default - CDK generated name
70+
*/
71+
readonly pipelineName?: string;
72+
}
73+
74+
/**
75+
* A SageMaker Pipeline
76+
*
77+
* @resource AWS::SageMaker::Pipeline
78+
*/
79+
export class Pipeline extends Resource implements IPipeline {
80+
/**
81+
* Import a pipeline from its ARN
82+
*
83+
* @param scope The parent construct
84+
* @param id The construct id
85+
* @param pipelineArn The ARN of the pipeline
86+
*/
87+
public static fromPipelineArn(scope: Construct, id: string, pipelineArn: string): IPipeline {
88+
return Pipeline.fromPipelineAttributes(scope, id, { pipelineArn });
89+
}
90+
91+
/**
92+
* Import a pipeline from its name
93+
*
94+
* For this to work, the pipeline must be in the same account and region as the stack.
95+
*
96+
* @param scope The parent construct
97+
* @param id The construct id
98+
* @param pipelineName The name of the pipeline
99+
*/
100+
public static fromPipelineName(scope: Construct, id: string, pipelineName: string): IPipeline {
101+
return Pipeline.fromPipelineAttributes(scope, id, { pipelineName });
102+
}
103+
104+
/**
105+
* Import a pipeline from attributes
106+
*
107+
* @param scope The parent construct
108+
* @param id The construct id
109+
* @param attrs The attributes of the pipeline to import
110+
*/
111+
public static fromPipelineAttributes(scope: Construct, id: string, attrs: PipelineAttributes): IPipeline {
112+
const stack = Stack.of(scope);
113+
114+
// Determine pipeline name and ARN
115+
let pipelineName: string;
116+
let pipelineArn: string;
117+
118+
if (attrs.pipelineArn) {
119+
pipelineArn = attrs.pipelineArn;
120+
pipelineName = Arn.extractResourceName(pipelineArn, 'pipeline');
121+
} else if (attrs.pipelineName !== undefined) {
122+
pipelineName = attrs.pipelineName;
123+
// Validate pipeline name format
124+
validatePipelineName(pipelineName, scope);
125+
126+
pipelineArn = stack.formatArn({
127+
service: 'sagemaker',
128+
resource: 'pipeline',
129+
resourceName: pipelineName,
130+
account: attrs.account,
131+
region: attrs.region,
132+
});
133+
} else {
134+
throw new ValidationError('Either pipelineArn or pipelineName must be provided', scope);
135+
}
136+
137+
class Import extends Resource implements IPipeline {
138+
public readonly pipelineArn = pipelineArn;
139+
public readonly pipelineName = pipelineName;
140+
141+
public get pipelineRef(): PipelineReference {
142+
return { pipelineName: this.pipelineName };
143+
}
144+
145+
public grantStartPipelineExecution(grantee: IGrantable): Grant {
146+
return Grant.addToPrincipal({
147+
grantee,
148+
actions: ['sagemaker:StartPipelineExecution'],
149+
resourceArns: [this.pipelineArn],
150+
});
151+
}
152+
}
153+
154+
return new Import(scope, id, {
155+
account: attrs.account,
156+
region: attrs.region,
157+
});
158+
}
159+
160+
/**
161+
* The ARN of the pipeline.
162+
*/
163+
public readonly pipelineArn!: string;
164+
165+
/**
166+
* The name of the pipeline.
167+
*/
168+
public readonly pipelineName!: string;
169+
170+
/**
171+
* A reference to this pipeline.
172+
*/
173+
public get pipelineRef(): PipelineReference {
174+
return { pipelineName: this.pipelineName };
175+
}
176+
177+
/**
178+
* Create a new Pipeline (not supported - use import methods instead)
179+
* @internal
180+
*/
181+
constructor(scope: Construct, id: string, props?: PipelineProps) {
182+
super(scope, id);
183+
// Suppress unused parameter warning
184+
void props;
185+
throw new ValidationError('Pipeline construct cannot be instantiated directly. Use Pipeline.fromPipelineArn() or Pipeline.fromPipelineName() to import existing pipelines.', scope);
186+
}
187+
188+
/**
189+
* Permits an IAM principal to start this pipeline execution
190+
* @param grantee The principal to grant access to
191+
*/
192+
public grantStartPipelineExecution(grantee: IGrantable): Grant {
193+
return Grant.addToPrincipal({
194+
grantee,
195+
actions: ['sagemaker:StartPipelineExecution'],
196+
resourceArns: [this.pipelineArn],
197+
});
198+
}
199+
}

packages/@aws-cdk/aws-sagemaker-alpha/test/integ.pipeline-import.js.snapshot/aws-cdk-sagemaker-alpha-pipeline-import.assets.json

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)