-
Notifications
You must be signed in to change notification settings - Fork 20
/
Copy pathprovision.sh
executable file
·438 lines (387 loc) · 16 KB
/
provision.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
#!/bin/bash
# Function to validate password complexity
validate_password() {
local password=$1
[[ ${#password} -ge 8 && ${#password} -le 41 ]] &&
[[ "$password" =~ [A-Za-z] ]] &&
[[ "$password" =~ [0-9] ]] &&
[[ "$password" =~ [^A-Za-z0-9] ]]
}
# Function to prompt for password with validation
prompt_password() {
local prompt_text=$1
local confirm_text=$2
while true; do
read -s -p "$prompt_text" password
echo
if validate_password "$password"; then
read -s -p "$confirm_text" password_confirm
echo
if [ "$password" = "$password_confirm" ]; then
echo "$password"
break
else
echo "Error: Passwords do not match. Please try again."
fi
else
echo "Error: Password must be 12-41 characters with at least one letter, one number, and one symbol."
fi
done
}
# Function to save environment to JSON
save_environment() {
local suffix=$1
mkdir -p .envs
cat > ".envs/${suffix}.json" << EOF
{
"version": "${VERSION}",
"project_name": "${PROJECT_NAME}",
"table_suffix": "${TABLE_SUFFIX}",
"client_name": "${CLIENT_NAME}",
"opensearch_user": "${OPENSEARCH_USER}",
"opensearch_password": "${OPENSEARCH_PASSWORD}",
"nginx_password": "${NGINX_PASSWORD}",
"region": "${REGION}",
"prerequisites_met": "${PREREQUISITES_MET}",
"need_opensearch": "${NEED_OPENSEARCH}"
}
EOF
echo "Environment configuration saved to .envs/${suffix}.json"
}
# Function to load environment from JSON
load_environment() {
local env_file=".envs/$1.json"
if [ -f "$env_file" ]; then
VERSION=$(jq -r '.version' "$env_file")
PROJECT_NAME=$(jq -r '.project_name' "$env_file")
TABLE_SUFFIX=$(jq -r '.table_suffix' "$env_file")
CLIENT_NAME=$(jq -r '.client_name' "$env_file")
OPENSEARCH_USER=$(jq -r '.opensearch_user' "$env_file")
OPENSEARCH_PASSWORD=$(jq -r '.opensearch_password' "$env_file")
NGINX_PASSWORD=$(jq -r '.nginx_password' "$env_file")
REGION=$(jq -r '.region' "$env_file")
PREREQUISITES_MET=$(jq -r '.prerequisites_met' "$env_file")
NEED_OPENSEARCH=$(jq -r '.need_opensearch' "$env_file")
else
echo "Environment file not found: $env_file"
exit 1
fi
}
# Function to list available environments
list_environments() {
if [ -d ".envs" ]; then
local envs=($(ls .envs/*.json 2>/dev/null | xargs -n 1 basename | sed 's/\.json$//'))
if [ ${#envs[@]} -eq 0 ]; then
echo "No environments found"
return 1
fi
echo "Available environments:"
for env in "${envs[@]}"; do
echo " - $env"
done
return 0
else
echo "No environments found"
return 1
fi
}
# Function to build and push Docker images
build_and_push_images() {
local suffix=$1
local region=$2
echo "Building and pushing Docker images for environment ${suffix}..."
# Get AWS account ID
local account_id=$(aws sts get-caller-identity --query Account --output text)
# Login to ECR
aws ecr get-login-password --region "$region" | docker login --username AWS --password-stdin ${account_id}.dkr.ecr."$region".amazonaws.com
# Create repositories if they don't exist
echo "Ensuring ECR repositories exist..."
local repos=("flotorch-app" "flotorch-indexing" "flotorch-retriever" "flotorch-evaluation" "flotorch-runtime" "flotorch-costcompute")
for repo in "${repos[@]}"; do
local repo_name="${repo}-${suffix}"
if ! aws ecr describe-repositories --repository-names "$repo_name" --region "$region" >/dev/null 2>&1; then
echo "Creating repository: $repo_name"
aws ecr create-repository --repository-name "$repo_name" --region "$region" --image-scanning-configuration scanOnPush=true
else
echo "Repository $repo_name already exists"
fi
done
# Build and push Docker images
echo "Building and pushing Docker images..."
docker build --platform linux/amd64 -t ${account_id}.dkr.ecr."$region".amazonaws.com/flotorch-app-"$suffix":latest -f app/Dockerfile --push .
docker build --platform linux/amd64 -t ${account_id}.dkr.ecr."$region".amazonaws.com/flotorch-indexing-"$suffix":latest -f indexing/fargate_indexing.Dockerfile --push .
docker build --platform linux/amd64 -t ${account_id}.dkr.ecr."$region".amazonaws.com/flotorch-retriever-"$suffix":latest -f retriever/fargate_retriever.Dockerfile --push .
docker build --platform linux/amd64 -t ${account_id}.dkr.ecr."$region".amazonaws.com/flotorch-evaluation-"$suffix":latest -f evaluation/fargate_evaluation.Dockerfile --push .
docker build --platform linux/amd64 -t ${account_id}.dkr.ecr."$region".amazonaws.com/flotorch-runtime-"$suffix":latest -f opensearch/opensearch.Dockerfile --push .
# Build cost compute image
cd lambda_handlers
docker build --platform linux/amd64 -t ${account_id}.dkr.ecr."$region".amazonaws.com/flotorch-costcompute-"$suffix":latest -f cost_handler/Dockerfile --push .
cd ..
echo "Docker images updated successfully"
}
# Function to update CloudFormation stack
update_cfn_stack() {
local region="$1"
local version="$2"
local stack_name="${PROJECT_NAME}"
echo "Updating CloudFormation stack '${stack_name}'..."
aws cloudformation update-stack \
--stack-name "$stack_name" \
--template-url "https://flotorch-public.s3.us-east-1.amazonaws.com/${version}/templates/master-template.yaml" \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
--disable-rollback \
--region "$region"
if [ $? -eq 0 ]; then
echo "Stack update initiated successfully. Please check AWS Console for status."
else
echo "Error: Failed to update CloudFormation stack"
exit 1
fi
}
# Function to create OpenSearch service-linked role
create_opensearch_service_role() {
echo "Creating OpenSearch service-linked role..."
aws iam get-role --role-name "AWSServiceRoleForAmazonOpenSearchService" >/dev/null 2>&1
if [ $? -ne 0 ]; then
aws iam create-service-linked-role --aws-service-name es.amazonaws.com || {
echo "Failed to create OpenSearch service-linked role"
return 1
}
echo "OpenSearch service-linked role created successfully"
else
echo "OpenSearch service-linked role already exists"
fi
}
# Function to update an existing environment
update_environment() {
local update_suffix=$1
echo "Loading environment ${update_suffix}..."
load_environment "$update_suffix"
echo "FloTorch Deployment Update Configuration"
echo "----------------------------------------"
echo "Current values shown in brackets. Press Enter to keep current value."
# Get Prerequisites confirmation with current value as default
while true; do
read -p "Subscribed to FloTorch on AWS Marketplace? (yes/no) [${PREREQUISITES_MET}]: " new_prerequisites
new_prerequisites=${new_prerequisites:-$PREREQUISITES_MET}
if [[ "$new_prerequisites" =~ ^(yes|no)$ ]]; then
PREREQUISITES_MET=$new_prerequisites
break
else
echo "Error: Please enter either 'yes' or 'no'"
fi
done
# Get OpenSearch confirmation with current value as default
while true; do
read -p "Do you need OpenSearch? (yes/no) [${NEED_OPENSEARCH}]: " new_opensearch
new_opensearch=${new_opensearch:-$NEED_OPENSEARCH}
if [[ "$new_opensearch" =~ ^(yes|no)$ ]]; then
NEED_OPENSEARCH=$new_opensearch
break
else
echo "Error: Please enter either 'yes' or 'no'"
fi
done
# Keep VERSION as is or allow update
read -p "Enter Version [${VERSION}]: " new_version
VERSION=${new_version:-$VERSION}
# Get Project Name with current value as default
read -p "Enter Project Name [${PROJECT_NAME}]: " new_project_name
PROJECT_NAME=${new_project_name:-$PROJECT_NAME}
# Table Suffix should not be changeable during update as it would break references
echo "Table Suffix: ${TABLE_SUFFIX} (cannot be changed during update)"
# Validate Client Name with current value as default
while true; do
read -p "Enter Client Name [${CLIENT_NAME}]: " new_client_name
new_client_name=${new_client_name:-$CLIENT_NAME}
if [[ "$new_client_name" =~ ^[a-z0-9-]{3,20}$ ]]; then
CLIENT_NAME=$new_client_name
break
else
echo "Error: Must be 3-20 lowercase letters, numbers, or hyphens"
fi
done
# Only ask for OpenSearch credentials if NEED_OPENSEARCH is yes
if [ "$NEED_OPENSEARCH" = "yes" ]; then
read -p "Enter OpenSearch admin username [${OPENSEARCH_USER}]: " new_opensearch_user
OPENSEARCH_USER=${new_opensearch_user:-$OPENSEARCH_USER}
# For passwords, always prompt but allow keeping the old value
read -s -p "Enter OpenSearch admin password (leave empty to keep current): " new_opensearch_password
echo
if [ -n "$new_opensearch_password" ]; then
if validate_password "$new_opensearch_password"; then
OPENSEARCH_PASSWORD=$new_opensearch_password
else
echo "Invalid password format. Keeping existing password."
fi
fi
fi
# Get NGINX password
read -s -p "Enter NGINX password (leave empty to keep current): " new_nginx_password
echo
if [ -n "$new_nginx_password" ]; then
if validate_password "$new_nginx_password"; then
NGINX_PASSWORD=$new_nginx_password
else
echo "Invalid password format. Keeping existing password."
fi
fi
# Get Region with current value as default
while true; do
read -p "Enter AWS region [${REGION}]: " new_region
new_region=${new_region:-$REGION}
if [[ "$new_region" =~ ^[a-z]{2}-[a-z]+-[0-9]{1}$ ]]; then
REGION=$new_region
break
else
echo "Error: Invalid region format. Please use format like us-east-1"
fi
done
# Save updated environment
save_environment "$TABLE_SUFFIX"
# If prerequisites are not met, build and push Docker images
if [ "$PREREQUISITES_MET" = "no" ]; then
build_and_push_images "$TABLE_SUFFIX" "$REGION"
fi
# Update CloudFormation stack
echo -e "\nUpdating CloudFormation stack..."
aws cloudformation update-stack \
--stack-name $PROJECT_NAME \
--template-url "https://flotorch-public.s3.us-east-1.amazonaws.com/${VERSION}/templates/master-template.yaml" \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
--disable-rollback \
--region "$REGION" \
--parameters \
ParameterKey=PrerequisitesMet,ParameterValue="$PREREQUISITES_MET" \
ParameterKey=NeedOpensearch,ParameterValue="$NEED_OPENSEARCH" \
ParameterKey=ProjectName,ParameterValue="$PROJECT_NAME" \
ParameterKey=TableSuffix,ParameterValue="$TABLE_SUFFIX" \
ParameterKey=ClientName,ParameterValue="$CLIENT_NAME" \
ParameterKey=OpenSearchAdminUser,ParameterValue="$OPENSEARCH_USER" \
ParameterKey=OpenSearchAdminPassword,ParameterValue="$OPENSEARCH_PASSWORD" \
ParameterKey=NginxAuthPassword,ParameterValue="$NGINX_PASSWORD"
echo -e "\nUpdate initiated. Check AWS CloudFormation console for progress."
}
# Check if any environments exist
if [ -d ".envs" ] && [ "$(ls -A .envs 2>/dev/null)" ]; then
echo "Existing environments found."
while true; do
read -p "Do you want to create a new environment or update an existing one? (new/update): " ACTION
if [[ "$ACTION" =~ ^(new|update)$ ]]; then
break
else
echo "Error: Please enter either 'new' or 'update'"
fi
done
if [ "$ACTION" = "update" ]; then
if list_environments; then
while true; do
read -p "Enter the environment suffix to update: " UPDATE_SUFFIX
if [ -f ".envs/${UPDATE_SUFFIX}.json" ]; then
update_environment "$UPDATE_SUFFIX"
exit 0
else
echo "Error: Environment ${UPDATE_SUFFIX} not found"
fi
done
fi
fi
fi
# Collect parameters interactively for new environment
echo "FloTorch Deployment Configuration"
echo "----------------------------------"
# Get Prerequisites confirmation
while true; do
read -p "Subscribed to FloTorch on AWS Marketplace? (yes/no): " PREREQUISITES_MET
if [[ "$PREREQUISITES_MET" =~ ^(yes|no)$ ]]; then
break
else
echo "Error: Please enter either 'yes' or 'no'"
fi
done
# Get OpenSearch confirmation
while true; do
read -p "Do you need OpenSearch? (yes/no) [yes]: " NEED_OPENSEARCH
NEED_OPENSEARCH=${NEED_OPENSEARCH:-yes}
if [[ "$NEED_OPENSEARCH" =~ ^(yes|no)$ ]]; then
break
else
echo "Error: Please enter either 'yes' or 'no'"
fi
done
VERSION="latest"
# Get Project Name
read -p "Enter Project Name [flotorch]: " PROJECT_NAME
PROJECT_NAME=${PROJECT_NAME:-flotorch}
# Validate Table Suffix
while true; do
read -p "Enter Table Suffix (exactly 6 lowercase letters): " TABLE_SUFFIX
if [[ "$TABLE_SUFFIX" =~ ^[a-z]{6}$ ]]; then
break
else
echo "Error: Must contain exactly 6 lowercase letters"
fi
done
# Validate Client Name
while true; do
read -p "Enter Client Name [flotorch]: " CLIENT_NAME
CLIENT_NAME=${CLIENT_NAME:-flotorch}
if [[ "$CLIENT_NAME" =~ ^[a-z0-9-]{3,20}$ ]]; then
break
else
echo "Error: Must be 3-20 lowercase letters, numbers, or hyphens"
fi
done
# Only ask for OpenSearch credentials if NEED_OPENSEARCH is yes
OPENSEARCH_USER="admin"
OPENSEARCH_PASSWORD="Flotorch@123"
if [ "$NEED_OPENSEARCH" = "yes" ]; then
read -p "Enter OpenSearch admin username [admin]: " OPENSEARCH_USER
OPENSEARCH_USER=${OPENSEARCH_USER:-admin}
# Create OpenSearch service-linked role if needed
create_opensearch_service_role || {
echo "Failed to create OpenSearch service-linked role. Please ensure you have sufficient IAM permissions."
exit 1
}
read -s -p "Enter OpenSearch admin password: " OPENSEARCH_PASSWORD
echo
fi
# Get NGINX password
read -s -p "Enter NGINX password: " NGINX_PASSWORD
echo
# Get Region
while true; do
read -p "Enter AWS region [us-east-1]: " REGION
REGION=${REGION:-us-east-1}
if [[ "$REGION" =~ ^[a-z]{2}-[a-z]+-[0-9]{1}$ ]]; then
break
else
echo "Error: Invalid region format. Please use format like us-east-1"
fi
done
# Create .envs directory if it doesn't exist
mkdir -p .envs
# Save environment variables to JSON file
save_environment "$TABLE_SUFFIX"
# If prerequisites are not met, build and push Docker images
if [ "$PREREQUISITES_MET" = "no" ]; then
build_and_push_images "$TABLE_SUFFIX" "$REGION"
fi
# Execute CloudFormation deployment
echo -e "\nStarting CloudFormation deployment..."
aws cloudformation create-stack \
--stack-name $PROJECT_NAME \
--template-url "https://flotorch-public.s3.us-east-1.amazonaws.com/${VERSION}/templates/master-template.yaml" \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
--disable-rollback \
--region "$REGION" \
--parameters \
ParameterKey=PrerequisitesMet,ParameterValue="$PREREQUISITES_MET" \
ParameterKey=NeedOpensearch,ParameterValue="$NEED_OPENSEARCH" \
ParameterKey=ProjectName,ParameterValue="$PROJECT_NAME" \
ParameterKey=TableSuffix,ParameterValue="$TABLE_SUFFIX" \
ParameterKey=ClientName,ParameterValue="$CLIENT_NAME" \
ParameterKey=OpenSearchAdminUser,ParameterValue="$OPENSEARCH_USER" \
ParameterKey=OpenSearchAdminPassword,ParameterValue="$OPENSEARCH_PASSWORD" \
ParameterKey=NginxAuthPassword,ParameterValue="$NGINX_PASSWORD"
echo -e "\nDeployment initiated. Check AWS CloudFormation console for progress."