Skip to content

Commit a5c9a7d

Browse files
authored
fix: avoid the show version information in cloudformation stacks name
* fix: avoid the show version information in cloudformation stacks name * chore: add build package in action workflow * chore: update descriptions * fix: support update ECS parent templates * fix: pickup files * fix: space characters * fix: simplified status output message * fix: fixed update cluster failed error
1 parent 9cecbe2 commit a5c9a7d

File tree

11 files changed

+147
-161
lines changed

11 files changed

+147
-161
lines changed

src/emd/cfn/codepipeline/template.yaml

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
AWSTemplateFormatVersion: '2010-09-09'
2-
Description: CodePipeline for model deployment
2+
Description: |
3+
Easy Model Deployer bootstrap environment.
4+
If you delete this stack, you will not be able to deploy any new models.
5+
36
Parameters:
47
ArtifactBucketName:
58
Type: String
@@ -195,7 +198,7 @@ Resources:
195198
phases:
196199
pre_build:
197200
commands:
198-
- echo Build started on `date`
201+
- echo model build pipeline started on `date`
199202
build:
200203
commands:
201204
- |-
@@ -214,21 +217,24 @@ Resources:
214217
pip install --upgrade pip
215218
pip install -r requirements.txt
216219
python pipeline.py --region $region --model_id $model_id --model_tag $ModelTag --framework_type $FrameworkType --service_type $service --backend_type $backend_name --model_s3_bucket $model_s3_bucket --instance_type $instance_type --extra_params "$extra_params" --skip_deploy
217-
cd ..
218-
echo pipeline build completed on `date`
220+
# cd ..
221+
echo model build pipeline completed on `date`
219222
220223
post_build:
221224
commands:
222225
- |-
226+
echo post build started on `date`
223227
SERVICE_TYPE=$(echo "$ServiceType" | tr '[:upper:]' '[:lower:]')
228+
if [ -f ../cfn/$ServiceType/post_build.py ]; then
229+
# copy post_build.py to pipeline so that the post_build.py can use the same module
230+
cp ../cfn/$ServiceType/post_build.py $ServiceType_post_build.py
231+
python $ServiceType_post_build.py --region $region --model_id $model_id --model_tag $ModelTag --framework_type $FrameworkType --service_type $service --backend_type $backend_name --model_s3_bucket $model_s3_bucket --instance_type $instance_type --extra_params "$extra_params"
232+
fi
233+
cd ..
224234
cp cfn/$ServiceType/template.yaml template.yaml
225235
cp pipeline/parameters.json parameters.json
226-
if [ -f cfn/$ServiceType/post_build.py ]; then
227-
cp cfn/$ServiceType/post_build.py post_build.py
228-
python post_build.py --region $region --model_id $model_id --model_tag $ModelTag --framework_type $FrameworkType --service_type $service --backend_type $backend_name --model_s3_bucket $model_s3_bucket --instance_type $instance_type --extra_params "$extra_params"
229-
fi
230236
cat parameters.json
231-
echo Build completed on `date`
237+
echo post build completed on `date`
232238
233239
artifacts:
234240
files:

src/emd/cfn/ecs/post_build.py

Lines changed: 102 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -3,160 +3,167 @@
33
import json
44
import os
55
import argparse
6+
from emd.models.utils.serialize_utils import load_extra_params
67

78
# Post build script for ECS, it will deploy the VPC and ECS cluster.
8-
9-
CFN_ROOT_PATH = 'cfn'
9+
CFN_ROOT_PATH = "../cfn"
1010
WAIT_SECONDS = 10
11-
# CFN_ROOT_PATH = '../../cfn'
12-
JSON_DOUBLE_QUOTE_REPLACE = '<!>'
13-
14-
def load_extra_params(string):
15-
string = string.replace(JSON_DOUBLE_QUOTE_REPLACE,'"')
16-
try:
17-
return json.loads(string)
18-
except json.JSONDecodeError:
19-
raise argparse.ArgumentTypeError(f"Invalid dictionary format: {string}")
2011

21-
def dump_extra_params(d:dict):
22-
return json.dumps(d).replace('"', JSON_DOUBLE_QUOTE_REPLACE)
2312

24-
def wait_for_stack_completion(client, stack_id, stack_name):
13+
def wait_for_stack_completion(client, stack_name):
2514
while True:
26-
stack_status = client.describe_stacks(StackName=stack_id)['Stacks'][0]['StackStatus']
27-
if stack_status in ['CREATE_COMPLETE', 'UPDATE_COMPLETE']:
15+
response = client.describe_stacks(StackName=stack_name)
16+
stack_status = response["Stacks"][0]["StackStatus"]
17+
while stack_status.endswith("IN_PROGRESS"):
18+
print(
19+
f"Stack {stack_name} is currently {stack_status}. Waiting for completion..."
20+
)
21+
time.sleep(WAIT_SECONDS)
22+
response = client.describe_stacks(StackName=stack_name)
23+
stack_status = response["Stacks"][0]["StackStatus"]
24+
25+
if stack_status in ["CREATE_COMPLETE", "UPDATE_COMPLETE"]:
2826
print(f"Stack {stack_name} deployment complete")
2927
break
30-
elif stack_status in ['CREATE_IN_PROGRESS', 'UPDATE_IN_PROGRESS']:
31-
print(f"Stack {stack_name} is still being deployed...")
32-
time.sleep(WAIT_SECONDS)
3328
else:
34-
raise Exception(f"Stack {stack_name} deployment failed with status {stack_status}")
29+
raise Exception(
30+
f"Post build stage failed. The stack {stack_name} is in an unexpected status: {stack_status}. Please visit the AWS CloudFormation Console to delete the stack."
31+
)
32+
3533

3634
def get_stack_outputs(client, stack_name):
3735
response = client.describe_stacks(StackName=stack_name)
38-
return response['Stacks'][0].get('Outputs', [])
36+
return response["Stacks"][0].get("Outputs", [])
37+
3938

4039
def create_or_update_stack(client, stack_name, template_path, parameters=[]):
4140
try:
41+
wait_for_stack_completion(client, stack_name)
4242
response = client.describe_stacks(StackName=stack_name)
43-
stack_status = response['Stacks'][0]['StackStatus']
44-
if stack_status in ['ROLLBACK_COMPLETE', 'ROLLBACK_FAILED', 'DELETE_FAILED']:
45-
print(f"Stack {stack_name} is in {stack_status} state. Deleting the stack to allow for recreation.")
46-
client.delete_stack(StackName=stack_name)
47-
while True:
48-
try:
49-
response = client.describe_stacks(StackName=stack_name)
50-
stack_status = response['Stacks'][0]['StackStatus']
51-
if stack_status == 'DELETE_IN_PROGRESS':
52-
print(f"Stack {stack_name} is being deleted...")
53-
time.sleep(WAIT_SECONDS)
54-
else:
55-
raise Exception(f"Unexpected status {stack_status} while waiting for stack deletion.")
56-
except client.exceptions.ClientError as e:
57-
if 'does not exist' in str(e):
58-
print(f"Stack {stack_name} successfully deleted.")
59-
break
60-
else:
61-
raise
62-
while stack_status not in ['CREATE_COMPLETE', 'UPDATE_COMPLETE']:
63-
if stack_status in ['CREATE_IN_PROGRESS', 'UPDATE_IN_PROGRESS']:
64-
print(f"Stack {stack_name} is currently {stack_status}. Waiting for it to complete...")
65-
time.sleep(WAIT_SECONDS)
66-
response = client.describe_stacks(StackName=stack_name)
67-
stack_status = response['Stacks'][0]['StackStatus']
68-
else:
69-
raise Exception(f"Stack {stack_name} is in an unexpected state: {stack_status}")
70-
print(f"Stack {stack_name} already exists with status {stack_status}")
43+
stack_status = response["Stacks"][0]["StackStatus"]
44+
45+
if stack_status in ["CREATE_COMPLETE", "UPDATE_COMPLETE"]:
46+
print(f"Stack {stack_name} already exists. Proceeding with update.")
47+
with open(template_path, "r") as template_file:
48+
template_body = template_file.read()
49+
50+
response = client.update_stack(
51+
StackName=stack_name,
52+
TemplateBody=template_body,
53+
Capabilities=["CAPABILITY_NAMED_IAM"],
54+
Parameters=parameters
55+
)
56+
57+
print(f"Started update of stack {stack_name}")
58+
wait_for_stack_completion(client, stack_name)
59+
7160
except client.exceptions.ClientError as e:
72-
if 'does not exist' in str(e):
61+
if "does not exist" in str(e):
7362
print(f"Stack {stack_name} does not exist. Proceeding with creation.")
74-
with open(template_path, 'r') as template_file:
63+
with open(template_path, "r") as template_file:
7564
template_body = template_file.read()
7665

7766
response = client.create_stack(
7867
StackName=stack_name,
7968
TemplateBody=template_body,
80-
Capabilities=['CAPABILITY_NAMED_IAM'],
81-
Parameters=parameters
69+
Capabilities=["CAPABILITY_NAMED_IAM"],
70+
Parameters=parameters,
71+
EnableTerminationProtection=True,
8272
)
8373

84-
stack_id = response['StackId']
74+
stack_id = response["StackId"]
8575
print(f"Started deployment of stack {stack_name} with ID {stack_id}")
86-
wait_for_stack_completion(client, stack_id, stack_name)
76+
wait_for_stack_completion(client, stack_name)
8777
else:
88-
raise
78+
raise Exception(
79+
f"Post build stage failed. The stack {stack_name} is in an unexpected status: {stack_status}. Please visit the AWS CloudFormation Console to delete the stack."
80+
)
81+
8982

9083
def update_parameters_file(parameters_path, updates):
91-
with open(parameters_path, 'r') as file:
84+
with open(parameters_path, "r") as file:
9285
data = json.load(file)
9386

94-
data['Parameters'].update(updates)
87+
data["Parameters"].update(updates)
9588

96-
with open(parameters_path, 'w') as file:
89+
with open(parameters_path, "w") as file:
9790
json.dump(data, file, indent=4)
9891

92+
9993
def deploy_vpc_template(region):
100-
client = boto3.client('cloudformation', region_name=region)
101-
stack_name = 'EMD-VPC'
102-
template_path = f'{CFN_ROOT_PATH}/vpc/template.yaml'
94+
client = boto3.client("cloudformation", region_name=region)
95+
stack_name = "EMD-VPC"
96+
template_path = f"{CFN_ROOT_PATH}/vpc/template.yaml"
10397
create_or_update_stack(client, stack_name, template_path)
10498
outputs = get_stack_outputs(client, stack_name)
10599
vpc_id = None
106100
subnets = None
107101
for output in outputs:
108-
if output['OutputKey'] == 'VPCID':
109-
vpc_id = output['OutputValue']
110-
elif output['OutputKey'] == 'Subnets':
111-
subnets = output['OutputValue']
112-
update_parameters_file('parameters.json', {'VPCID': vpc_id, 'Subnets': subnets})
102+
if output["OutputKey"] == "VPCID":
103+
vpc_id = output["OutputValue"]
104+
elif output["OutputKey"] == "Subnets":
105+
subnets = output["OutputValue"]
106+
update_parameters_file("parameters.json", {"VPCID": vpc_id, "Subnets": subnets})
113107
return vpc_id, subnets
114108

115109

116110
def deploy_ecs_cluster_template(region, vpc_id, subnets):
117-
client = boto3.client('cloudformation', region_name=region)
118-
stack_name = 'EMD-ECS-Cluster'
119-
template_path = f'{CFN_ROOT_PATH}/ecs/cluster.yaml'
120-
create_or_update_stack(client, stack_name, template_path, [
121-
{
122-
'ParameterKey': 'VPCID',
123-
'ParameterValue': vpc_id,
124-
},
125-
{
126-
'ParameterKey': 'Subnets',
127-
'ParameterValue': subnets,
128-
},
129-
])
111+
client = boto3.client("cloudformation", region_name=region)
112+
stack_name = "EMD-ECS-Cluster"
113+
template_path = f"{CFN_ROOT_PATH}/ecs/cluster.yaml"
114+
create_or_update_stack(
115+
client,
116+
stack_name,
117+
template_path,
118+
[
119+
{
120+
"ParameterKey": "VPCID",
121+
"ParameterValue": vpc_id,
122+
},
123+
{
124+
"ParameterKey": "Subnets",
125+
"ParameterValue": subnets,
126+
},
127+
],
128+
)
130129

131130
outputs = get_stack_outputs(client, stack_name)
132131
for output in outputs:
133-
update_parameters_file('parameters.json', {output['OutputKey']: output['OutputValue']})
132+
update_parameters_file(
133+
"parameters.json", {output["OutputKey"]: output["OutputValue"]}
134+
)
134135

135136

136137
def post_build():
137138
parser = argparse.ArgumentParser()
138-
parser.add_argument('--region', type=str, required=False)
139-
parser.add_argument('--model_id', type=str, required=False)
140-
parser.add_argument('--model_tag', type=str, required=False)
141-
parser.add_argument('--framework_type', type=str, required=False)
142-
parser.add_argument('--service_type', type=str, required=False)
143-
parser.add_argument('--backend_type', type=str, required=False)
144-
parser.add_argument('--model_s3_bucket', type=str, required=False)
145-
parser.add_argument('--instance_type', type=str, required=False)
146-
parser.add_argument('--extra_params', type=load_extra_params, required=False, default=os.environ.get("extra_params","{}"))
139+
parser.add_argument("--region", type=str, required=False)
140+
parser.add_argument("--model_id", type=str, required=False)
141+
parser.add_argument("--model_tag", type=str, required=False)
142+
parser.add_argument("--framework_type", type=str, required=False)
143+
parser.add_argument("--service_type", type=str, required=False)
144+
parser.add_argument("--backend_type", type=str, required=False)
145+
parser.add_argument("--model_s3_bucket", type=str, required=False)
146+
parser.add_argument("--instance_type", type=str, required=False)
147+
parser.add_argument(
148+
"--extra_params",
149+
type=load_extra_params,
150+
required=False,
151+
default=os.environ.get("extra_params", "{}"),
152+
)
147153

148154
args = parser.parse_args()
149155

150-
service_params = args.extra_params.get('service_params',{})
156+
service_params = args.extra_params.get("service_params", {})
151157

152-
if 'vpc_id' not in service_params:
158+
if "vpc_id" not in service_params:
153159
vpc_id, subnets = deploy_vpc_template(args.region)
154160
else:
155-
vpc_id = service_params.get('vpc_id')
156-
subnets = service_params.get('subnet_ids')
157-
update_parameters_file('parameters.json', {'VPCID': vpc_id, 'Subnets': subnets})
161+
vpc_id = service_params.get("vpc_id")
162+
subnets = service_params.get("subnet_ids")
163+
update_parameters_file("parameters.json", {"VPCID": vpc_id, "Subnets": subnets})
158164

159165
deploy_ecs_cluster_template(args.region, vpc_id, subnets)
160166

167+
161168
if __name__ == "__main__":
162169
post_build()

src/emd/commands/deploy.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -521,19 +521,19 @@ def deploy(
521521
raise typer.Exit(0)
522522

523523
# log the deployment parameters
524-
engine_info = model.find_current_engine(engine_type)
525-
framework_info = model.find_current_framework(framework_type)
526-
527-
engine_info_str = json.dumps(engine_info,indent=2,ensure_ascii=False)
528-
framework_info_str = json.dumps(framework_info, indent=2, ensure_ascii=False)
529-
extra_params_info = json.dumps(extra_params, indent=2, ensure_ascii=False)
530-
console.print(f"[bold blue]Deployment parameters:[/bold blue]")
531-
console.print(f"[bold blue]model_id: {model_id},model_tag: {model_tag}[/bold blue]")
532-
console.print(f"[bold blue]instance_type: {instance_type}[/bold blue]")
533-
console.print(f"[bold blue]service_type: {service_type}[/bold blue]")
534-
console.print(f"[bold blue]engine info:\n {engine_info_str}[/bold blue]")
535-
console.print(f"[bold blue]framework info:\n {framework_info_str}[/bold blue]")
536-
console.print(f"[bold blue]extra_params:\n {extra_params_info}[/bold blue]")
524+
# engine_info = model.find_current_engine(engine_type)
525+
# framework_info = model.find_current_framework(framework_type)
526+
527+
# engine_info_str = json.dumps(engine_info,indent=2,ensure_ascii=False)
528+
# framework_info_str = json.dumps(framework_info, indent=2, ensure_ascii=False)
529+
# extra_params_info = json.dumps(extra_params, indent=2, ensure_ascii=False)
530+
# console.print(f"[bold blue]Deployment parameters:[/bold blue]")
531+
# console.print(f"[bold blue]model_id: {model_id},model_tag: {model_tag}[/bold blue]")
532+
# console.print(f"[bold blue]instance_type: {instance_type}[/bold blue]")
533+
# console.print(f"[bold blue]service_type: {service_type}[/bold blue]")
534+
# console.print(f"[bold blue]engine info:\n {engine_info_str}[/bold blue]")
535+
# console.print(f"[bold blue]framework info:\n {framework_info_str}[/bold blue]")
536+
# console.print(f"[bold blue]extra_params:\n {extra_params_info}[/bold blue]")
537537
# Start pipeline execution
538538
if service_type != ServiceType.LOCAL:
539539
response = sdk_deploy(

src/emd/commands/destroy.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from rich.console import Console
33
from rich.panel import Panel
44

5-
from emd.constants import MODEL_DEFAULT_TAG, VERSION_MODIFY
5+
from emd.constants import MODEL_DEFAULT_TAG
66
from typing_extensions import Annotated
77
from emd.sdk.destroy import destroy as sdk_destroy
88
from emd.utils.decorators import catch_aws_credential_errors,check_emd_env_exist,load_aws_profile
@@ -25,11 +25,7 @@ def destroy(
2525
],
2626
model_tag: Annotated[
2727
str, typer.Argument(help="Model tag")
28-
] = MODEL_DEFAULT_TAG,
29-
model_deploy_version: Annotated[
30-
str, typer.Option("-v", "--deploy-version", help="The version of the model deployment to destroy"),
31-
] = VERSION_MODIFY
28+
] = MODEL_DEFAULT_TAG
3229
):
33-
model_deploy_version = convert_version_name_to_stack_name(model_deploy_version)
3430
# console.print("[bold blue]Checking AWS environment...[/bold blue]")
35-
sdk_destroy(model_id,model_tag=model_tag,waiting_until_complete=True, model_deploy_version=model_deploy_version)
31+
sdk_destroy(model_id,model_tag=model_tag,waiting_until_complete=True)

0 commit comments

Comments
 (0)