Skip to content

Commit deebd8d

Browse files
authored
Merge pull request #38 from awslabs/jjbain-bring-your-own-arn
Jjbain bring your own arn
2 parents e8070a6 + 8d30886 commit deebd8d

File tree

12 files changed

+1412
-13
lines changed

12 files changed

+1412
-13
lines changed

backend/backend/handlers/pipelines/createPipeline.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,17 @@ def upload_Pipeline(body):
6565
print("Setting Time Stamp")
6666
dtNow = datetime.datetime.utcnow().strftime('%B %d %Y - %H:%M:%S')
6767

68+
userResource = {
69+
'isProvided': False,
70+
'resourceId': ''
71+
}
72+
if body['containerUri'] != None:
73+
userResource['isProvided'] = True
74+
userResource['resourceId'] = body['containerUri']
75+
elif body['lambdaName'] != None:
76+
userResource['isProvided'] = True
77+
userResource['resourceId'] = body['lambdaName']
78+
6879
item = {
6980
'databaseId': body['databaseId'],
7081
'pipelineId':body['pipelineId'],
@@ -73,13 +84,17 @@ def upload_Pipeline(body):
7384
'description': body['description'],
7485
'dateCreated': json.dumps(dtNow),
7586
'pipelineType':body['pipelineType'],
87+
'userProvidedResource': json.dumps(userResource),
7688
'enabled':False #Not doing anything with this yet
7789
}
7890
table.put_item(
7991
Item=item,
8092
ConditionExpression='attribute_not_exists(databaseId) and attribute_not_exists(pipelineId)'
8193
)
82-
94+
#If a lambda function name or ECR container URI was provided by the user, creation is not necessary
95+
if userResource['isProvided'] == True:
96+
return json.dumps({"message": 'Succeeded'})
97+
8398
print("Running CFT")
8499
if body['pipelineType']=='SageMaker':
85100
createSagemakerPipeline(body)
@@ -186,7 +201,7 @@ def lambda_handler(event, context):
186201
}
187202
print(event['body'])
188203
if isinstance(event['body'], str):
189-
event['body'] = json.loads(event['body'])
204+
event['body'] = json.loads(event['body'])
190205
try:
191206
reqs=['databaseId','pipelineId','description','assetType','pipelineType','outputType']
192207
for r in reqs:

backend/backend/handlers/pipelines/pipelineService.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,21 @@ def delete_pipeline(databaseId, pipelineId):
9797
if "#deleted" in databaseId:
9898
return response
9999
item = get_pipeline(databaseId, pipelineId)
100+
100101
if item:
101102
print("Deleting pipeline: ", item)
102-
if item['pipelineType'] == 'SageMaker':
103-
delete_stack(item['pipelineId'])
104-
else: #Lambda Pipeline
105-
delete_lambda(item['pipelineId'])
103+
try:
104+
userResource = json.loads(item['userProvidedResource'])
105+
if userResource['isProvided'] == False:
106+
if item['pipelineType'] == 'SageMaker':
107+
delete_stack(item['pipelineId'])
108+
else: #Lambda Pipeline
109+
delete_lambda(item['pipelineId'])
110+
except KeyError: #For pipelines created before user provided resources were implemented
111+
if item['pipelineType'] == 'SageMaker':
112+
delete_stack(item['pipelineId'])
113+
else: #Lambda Pipeline
114+
delete_lambda(item['pipelineId'])
106115

107116
item['databaseId'] = databaseId + "#deleted"
108117
table.put_item(

backend/backend/handlers/workflows/createWorkflow.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,18 @@ def create_step_function(pipelines, databaseId, workflowId):
268268

269269

270270
def create_sagemaker_step(databaseId, region, role, account_id, job_names, instance_type, i, pipeline, input_s3_uri, output_s3_uri):
271-
image_uri = account_id+'.dkr.ecr.'+region + \
272-
'.amazonaws.com/'+pipeline['name']
271+
272+
try:
273+
userResource = json.loads(pipeline['userProvidedResource'])
274+
if userResource['isProvided'] == False:
275+
image_uri = account_id+'.dkr.ecr.'+region + \
276+
'.amazonaws.com/'+pipeline['name']
277+
else:
278+
image_uri = userResource['resourceId']
279+
except KeyError: #For pipelines created before user provided resources were implemented
280+
image_uri = account_id+'.dkr.ecr.'+region + \
281+
'.amazonaws.com/'+pipeline['name']
282+
273283
processor = Processor(
274284
role=role,
275285
image_uri=image_uri,
@@ -307,6 +317,15 @@ def create_sagemaker_step(databaseId, region, role, account_id, job_names, insta
307317

308318

309319
def create_lambda_step(pipeline, input_s3_uri, output_s3_uri):
320+
try:
321+
userResource = json.loads(pipeline['userProvidedResource'])
322+
if userResource['isProvided'] == False:
323+
functionName = pipeline['name']
324+
else:
325+
functionName = userResource['resourceId']
326+
except KeyError: #For pipelines created before user provided resources were implemented
327+
functionName = pipeline['name']
328+
310329
lambda_payload = {
311330
"body": {
312331
"inputPath.$": input_s3_uri,
@@ -317,6 +336,6 @@ def create_lambda_step(pipeline, input_s3_uri, output_s3_uri):
317336
state_id="{}-{}".format(pipeline['name'], uuid.uuid1().hex),
318337
parameters={
319338
# replace with the name of your function
320-
"FunctionName": pipeline['name'],
339+
"FunctionName": functionName,
321340
"Payload": lambda_payload
322341
})

infra/lib/storage-builder.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Duration } from "aws-cdk-lib";
1111
import { BlockPublicAccess } from "aws-cdk-lib/aws-s3";
1212
import { Construct } from "constructs";
1313
import { requireTLSAddToResourcePolicy } from "./security";
14+
import { NagSuppressions } from "cdk-nag";
1415

1516
export interface storageResources {
1617
s3: {
@@ -33,7 +34,6 @@ export interface storageResources {
3334
export function storageResourcesBuilder(scope: Construct, staging_bucket?: string): storageResources {
3435
const accessLogsBucket = new s3.Bucket(scope, "AccessLogsBucket", {
3536
encryption: s3.BucketEncryption.S3_MANAGED,
36-
serverAccessLogsPrefix: "access-log-bucket-logs/",
3737
versioned: true,
3838
blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
3939
});
@@ -45,6 +45,10 @@ export function storageResourcesBuilder(scope: Construct, staging_bucket?: strin
4545

4646
requireTLSAddToResourcePolicy(accessLogsBucket);
4747

48+
NagSuppressions.addResourceSuppressions(accessLogsBucket, [
49+
{id: 'AwsSolutions-S1', reason: 'This is an access logs bucket, we do not want access logs to be reporting to itself, causing a loop.'}
50+
]);
51+
4852
const assetBucket = new s3.Bucket(scope, "AssetBucket", {
4953
cors: [
5054
{

web/src/components/createupdate/CreateUpdateElement.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export default function CreateUpdateElement(props) {
125125
if (open) {
126126
const newFormErrors = Object.assign({}, formErrors);
127127
const formPropNames = Object.keys(formValues);
128+
console.log(formPropNames)
128129
for (let i = 0; i < formPropNames.length; i++) {
129130
const formPropName = formPropNames[i];
130131
//ignore values without validation on entity
@@ -173,6 +174,9 @@ export default function CreateUpdateElement(props) {
173174
} else {
174175
acc[cur] = formValues[cur];
175176
}
177+
if (acc[cur] == undefined || acc[cur] == null){
178+
acc[cur] = null;
179+
}
176180
return acc;
177181
},
178182
{}
@@ -269,13 +273,22 @@ export default function CreateUpdateElement(props) {
269273
elementDefinition,
270274
options,
271275
defaultOption,
276+
appearsWhen,
272277
} = controlDefinition;
273278
const { FormElement, elementProps } = elementDefinition;
274279
if (!formValues[id] && defaultOption) {
275280
handleUpdateFormValues(id, defaultOption);
276281
}
282+
283+
//find the form field that isn't toggled based on pipeline type, then clear it.
284+
const optionalFieldHidden = appearsWhen && formValues[appearsWhen[0]] && appearsWhen[1] != formValues[appearsWhen[0]]["label"]
285+
if(formValues[id] && optionalFieldHidden){
286+
console.log(id+ ": "+ formValues[id])
287+
formValues[id] = ""
288+
}
289+
277290
return (
278-
<div key={i}>
291+
optionalFieldHidden || <div key={i}>
279292
<FormField
280293
constraintText={constraintText}
281294
label={label}

web/src/components/createupdate/CreateUpdateWorkflow.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export default function CreateUpdateWorkflow(props) {
6565
useEffect(() => {
6666
const getData = async () => {
6767
const items = await fetchDatabaseWorkflows({databaseId: databaseId});
68+
console.log(items)
6869
if (items !== false && Array.isArray(items)) {
6970
setReload(false);
7071
const currentItem = items.find(
@@ -78,7 +79,8 @@ export default function CreateUpdateWorkflow(props) {
7879
return {
7980
value: item.name,
8081
type: item.pipelineType,
81-
outputType: item.outputType
82+
outputType: item.outputType,
83+
userProvidedResource: item.userProvidedResource
8284
};
8385
}
8486
);
@@ -142,7 +144,7 @@ export default function CreateUpdateWorkflow(props) {
142144
setActiveTab("pipelines");
143145
} else {
144146
const functions = workflowPipelines.map((item) => {
145-
return { name: item.value, pipelineType: item.type, outputType: item.outputType };
147+
return { name: item.value, pipelineType: item.type, outputType: item.outputType, userProvidedResource: item.userProvidedResource };
146148
});
147149
const config = {
148150
body: {

web/src/components/createupdate/entity-types/EntityPropTypes.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ export const validateFileType = (value) => {
4747
return result[0] === String(value);
4848
};
4949

50+
export const validateContainerUri = value => {
51+
const regex = /^[0-9]{12}.dkr.ecr.+?amazonaws.com\/.+/g;
52+
const result = String(value).match(regex);
53+
if (result === null) {
54+
return false;
55+
}
56+
return result[0] === String(value);
57+
}
58+
5059
export const fileTypePropType = function (props, propName) {
5160
//check if prop is supplied by option element
5261
let testValue = props[propName];
@@ -233,6 +242,37 @@ export const typedObjectArrayPropType = function (type, props, propName) {
233242

234243
return null;
235244
};
245+
246+
export const containerUriPropType = function(props, propName){
247+
//check if prop is supplied by option element
248+
let testValue = props[propName];
249+
if (testValue && testValue.hasOwnProperty("value"))
250+
testValue = testValue.value;
251+
console.log(testValue);
252+
if (!validateContainerUri(testValue)) {
253+
if(!verifyIsNotEmpty(testValue) || testValue == null){
254+
return null;
255+
}
256+
return new Error(
257+
`Invalid value for ${propName}. Enter a valid Amazon ECR image Uri ACCOUNT_NUMBER.dkr.ecr.REGION.amazonaws.com/IMAGE_NAME`
258+
);
259+
}
260+
return null;
261+
}
262+
263+
export const validateLambdaName = function(props, propName){
264+
//check if prop is supplied by option element
265+
let testValue = props[propName];
266+
if (testValue && testValue.hasOwnProperty("value"))
267+
testValue = testValue.value;
268+
269+
if(!verifyIsNotEmpty(testValue)){
270+
return null;
271+
}
272+
273+
return stringMaxLength.bind(null, 64)
274+
}
275+
236276
export const EntityPropTypes = {
237277
ENTITY_ID: entityIdPropType,
238278
ENTITY_ID_ARRAY: entityIdArrayPropType,
@@ -242,6 +282,8 @@ export const EntityPropTypes = {
242282
STRING_64: stringMaxLength.bind(null, 64),
243283
STRING_128: stringMaxLength.bind(null, 128),
244284
STRING_256: stringMaxLength.bind(null, 256),
285+
CONTAINER_URI: containerUriPropType,
286+
LAMBDA_NAME: validateLambdaName,
245287
BOOL: boolPropType,
246288
OBJECT: objectPropType,
247289
TYPED_OBJECT: typedObjectPropType,

web/src/components/createupdate/entity-types/PipelineEntity.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,17 @@ export default function PipelineEntity(props) {
1111
databaseId,
1212
description,
1313
pipelineType,
14+
externalContainerUri,
15+
externalLambdaName,
1416
assetType,
1517
outputType,
1618
} = props;
1719
this.pipelineId = pipelineId;
1820
this.databaseId = databaseId;
1921
this.description = description;
2022
this.pipelineType = pipelineType;
23+
this.externalContainerUri = externalContainerUri;
24+
this.externalLambdaName = externalLambdaName;
2125
this.assetType = assetType;
2226
this.outputType = outputType;
2327
}
@@ -29,4 +33,6 @@ PipelineEntity.propTypes = {
2933
pipelineType: EntityPropTypes.STRING_32,
3034
assetType: EntityPropTypes.FILE_TYPE,
3135
outputType: EntityPropTypes.FILE_TYPE,
36+
containerUri: EntityPropTypes.CONTAINER_URI,
37+
lambdaName: EntityPropTypes.LAMBDA_NAME,
3238
};

web/src/components/createupdate/form-definitions/PipelineFormDefinition.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export const PipelineFormDefinition = new FormDefinition({
5555
formElement: Input,
5656
elementProps: { autoFocus: true },
5757
}),
58+
required: true,
5859
}),
5960
new ControlDefinition({
6061
label: "Database Name",
@@ -64,6 +65,7 @@ export const PipelineFormDefinition = new FormDefinition({
6465
formElement: DatabaseSelector,
6566
elementProps: {},
6667
}),
68+
required: true,
6769
}),
6870
new ControlDefinition({
6971
label: "Pipeline Type",
@@ -77,6 +79,29 @@ export const PipelineFormDefinition = new FormDefinition({
7779
disabled: false,
7880
},
7981
}),
82+
required: true,
83+
}),
84+
new ControlDefinition({
85+
label: "Container Uri (Optional)",
86+
id: "containerUri",
87+
constraintText: "ACCOUNT_NUMBER.dkr.ecr.REGION.amazonaws.com/IMAGE_NAME. If you do not provide an image stored in Amazon ECR, an Amazon Sagemaker notebook instance will be provisioned on your behalf with steps to deploy one.",
88+
elementDefinition: new ElementDefinition({
89+
formElement: Input,
90+
elementProps: { autoFocus: false },
91+
}),
92+
appearsWhen: ["pipelineType", "SageMaker"],
93+
required: false,
94+
}),
95+
new ControlDefinition({
96+
label: "Lambda Function Name (Optional)",
97+
id: "lambdaName",
98+
constraintText: "If no name is provided a template lambda function will be deployed on your behalf",
99+
elementDefinition: new ElementDefinition({
100+
formElement: Input,
101+
elementProps: { autoFocus: false },
102+
}),
103+
appearsWhen: ["pipelineType", "Lambda"],
104+
required: false,
80105
}),
81106
new ControlDefinition({
82107
label: "Description",
@@ -86,6 +111,7 @@ export const PipelineFormDefinition = new FormDefinition({
86111
formElement: Textarea,
87112
elementProps: { rows: 4 },
88113
}),
114+
required: true,
89115
}),
90116
new ControlDefinition({
91117
label: "Input Filetype",
@@ -96,6 +122,7 @@ export const PipelineFormDefinition = new FormDefinition({
96122
formElement: Select,
97123
elementProps: {},
98124
}),
125+
required: true,
99126
}),
100127
new ControlDefinition({
101128
label: "Output Filetype",
@@ -106,6 +133,7 @@ export const PipelineFormDefinition = new FormDefinition({
106133
formElement: Select,
107134
elementProps: {},
108135
}),
136+
required: true,
109137
}),
110138
],
111139
});

web/src/components/createupdate/form-definitions/types/ControlDefinition.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ export default function ControlDefinition(props) {
1515
elementDefinition,
1616
options,
1717
defaultOption,
18+
appearsWhen,
1819
} = props;
1920
this.id = id;
2021
this.label = label;
2122
this.constraintText = constraintText;
2223
this.elementDefinition = elementDefinition;
2324
this.options = options;
2425
this.defaultOption = defaultOption;
26+
this.appearsWhen = appearsWhen;
2527
}
2628

2729
ControlDefinition.propTypes = {
@@ -31,4 +33,5 @@ ControlDefinition.propTypes = {
3133
elementDefinition: PropTypes.instanceOf(ElementDefinition),
3234
options: PropTypes.arrayOf(OptionDefinition),
3335
defaultOption: PropTypes.instanceOf(OptionDefinition),
36+
appearsWhen: PropTypes.array,
3437
};

0 commit comments

Comments
 (0)