Skip to content

Commit 5a4fbde

Browse files
author
Yuriy Bezsonov
committed
WIP
1 parent 8bdc923 commit 5a4fbde

File tree

6 files changed

+258
-60
lines changed

6 files changed

+258
-60
lines changed

infra/cdk/src/main/java/sample/com/constructs/Ide.java

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import software.amazon.awscdk.Duration;
99
import software.amazon.awscdk.Fn;
1010
import software.amazon.awscdk.RemovalPolicy;
11+
import software.amazon.awscdk.SecretValue;
1112
import software.amazon.awscdk.services.cloudfront.AllowedMethods;
1213
import software.amazon.awscdk.services.cloudfront.BehaviorOptions;
1314
import software.amazon.awscdk.services.cloudfront.CachePolicy;
@@ -20,7 +21,9 @@
2021
import software.amazon.awscdk.services.cloudfront.origins.HttpOriginProps;
2122
import software.amazon.awscdk.services.ec2.*;
2223
import software.amazon.awscdk.services.iam.*;
23-
24+
import software.amazon.awscdk.services.lambda.Code;
25+
import software.amazon.awscdk.services.lambda.Function;
26+
import software.amazon.awscdk.services.lambda.Runtime;
2427
import software.amazon.awscdk.services.secretsmanager.Secret;
2528
import software.amazon.awscdk.services.secretsmanager.SecretStringGenerator;
2629
import software.constructs.Construct;
@@ -41,6 +44,7 @@ public class Ide extends Construct {
4144
private final Secret ideSecretsManagerPassword;
4245
private final Role workshopRole;
4346
private final Role lambdaRole;
47+
private CustomResource passwordResource;
4448

4549
public static class IdeProps {
4650
private String instanceName = "ide";
@@ -442,15 +446,15 @@ public Ide(final Construct scope, final String id, final IdeProps props) {
442446
// CloudFront doesn't need to wait for bootstrap - it's just infrastructure
443447

444448
// Outputs - these should only be created if bootstrap succeeds
445-
var ideUrlOutput = CfnOutput.Builder.create(this, "IdeUrl")
449+
var ideUrlOutput = CfnOutput.Builder.create(this, "Url")
446450
.value("https://" + distribution.getDistributionDomainName())
447451
.description("Workshop IDE Url")
448452
.exportName(instanceName + "-url")
449453
.build();
450454
ideUrlOutput.getNode().addDependency(waitCondition);
451455

452-
var idePasswordOutput = CfnOutput.Builder.create(this, "IdePassword")
453-
.value(ideSecretsManagerPassword.secretValueFromJson("password").unsafeUnwrap())
456+
var idePasswordOutput = CfnOutput.Builder.create(this, "Password")
457+
.value(getIdePassword(instanceName))
454458
.description("Workshop IDE Password")
455459
.exportName(instanceName + "-password")
456460
.build();
@@ -475,4 +479,27 @@ private String loadFile(String filePath) {
475479
throw new RuntimeException("Failed to load file " + filePath, e);
476480
}
477481
}
482+
483+
private String getIdePassword(String instanceName) {
484+
if (passwordResource == null) {
485+
Function passwordFunction = Function.Builder.create(this, "PasswordExporterFunction")
486+
.code(Code.fromInline(loadFile("/lambda/password-exporter.py")))
487+
.handler("index.lambda_handler")
488+
.runtime(Runtime.PYTHON_3_13)
489+
.timeout(Duration.minutes(3))
490+
.functionName(instanceName + "-password-exporter")
491+
.build();
492+
493+
ideSecretsManagerPassword.grantRead(passwordFunction);
494+
495+
passwordResource = CustomResource.Builder.create(this, "PasswordExporter")
496+
.serviceToken(passwordFunction.getFunctionArn())
497+
.properties(Map.of(
498+
"PasswordName", this.ideSecretsManagerPassword.getSecretName()
499+
))
500+
.build();
501+
}
502+
503+
return passwordResource.getAttString("password");
504+
}
478505
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import boto3
2+
import json
3+
import traceback
4+
import cfnresponse
5+
6+
secretsmanager = boto3.client('secretsmanager')
7+
8+
def lambda_handler(event, context):
9+
print(f'Event: {event}')
10+
responseData = {}
11+
status = cfnresponse.SUCCESS
12+
physical_id = event.get('PhysicalResourceId', 'PasswordExporter')
13+
14+
try:
15+
if event['RequestType'] == 'Delete':
16+
# Nothing to clean up for password export
17+
responseData = {'Message': 'Password exporter deleted'}
18+
cfnresponse.send(event, context, status, responseData, physical_id)
19+
return
20+
21+
if event['RequestType'] in ['Create', 'Update']:
22+
# Get password from Secrets Manager
23+
props = event['ResourceProperties']
24+
password_name = props['PasswordName']
25+
26+
print(f'Retrieving password from secret: {password_name}')
27+
28+
response = secretsmanager.get_secret_value(SecretId=password_name)
29+
secret_data = json.loads(response['SecretString'])
30+
31+
# Return the password value for CloudFormation output
32+
responseData = {
33+
'password': secret_data['password']
34+
}
35+
36+
print('Successfully retrieved password from Secrets Manager')
37+
38+
except Exception as e:
39+
status = cfnresponse.FAILED
40+
tb_err = traceback.format_exc()
41+
print(tb_err)
42+
responseData = {'Error': tb_err}
43+
44+
cfnresponse.send(event, context, status, responseData, physical_id)

infra/scripts/ide/base.sh

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,18 @@ retry_critical() {
3333
if command -v retry_command >/dev/null 2>&1; then
3434
retry_command 5 5 "FAIL" "$@"
3535
else
36-
eval "$*"
36+
local tool_name="$1"
37+
shift
38+
eval "$*" || { echo "💥 FATAL: $tool_name failed"; exit 1; }
3739
fi
3840
}
3941
retry_optional() {
4042
if command -v retry_command >/dev/null 2>&1; then
4143
retry_command 5 5 "LOG" "$@"
4244
else
43-
eval "$*" || echo "⚠️ Warning: $* failed (continuing)"
45+
local tool_name="$1"
46+
shift
47+
eval "$*" || echo "⚠️ Warning: $tool_name failed (continuing)"
4448
fi
4549
}
4650

@@ -62,7 +66,7 @@ download_and_verify() {
6266
local description="$3"
6367

6468
log_info "Downloading $description..."
65-
retry_critical "wget -q '$url' -O '$output'"
69+
retry_critical "$description download" "wget -q '$url' -O '$output'"
6670
}
6771

6872
cd /tmp
@@ -75,7 +79,7 @@ install_java() {
7579
log_info "Installing Java versions 8, 17, 21, 25 and setting ${JAVA_VERSION} as default..."
7680

7781
# Install all Java versions
78-
retry_critical "sudo dnf install -y -q java-1.8.0-amazon-corretto-devel java-17-amazon-corretto-devel java-21-amazon-corretto-devel java-25-amazon-corretto-devel >/dev/null"
82+
retry_critical "Java versions (8,17,21,25)" "sudo dnf install -y -q java-1.8.0-amazon-corretto-devel java-17-amazon-corretto-devel java-21-amazon-corretto-devel java-25-amazon-corretto-devel >/dev/null"
7983

8084
# Set default Java version
8185
sudo update-alternatives --set java /usr/lib/jvm/java-${JAVA_VERSION}-amazon-corretto.x86_64/bin/java
@@ -95,17 +99,17 @@ install_nodejs() {
9599
log_info "Installing Node.js ${NODE_VERSION} and tools..."
96100

97101
# Install NVM
98-
retry_critical "curl -sS -o- https://raw.githubusercontent.com/nvm-sh/nvm/v${NVM_VERSION}/install.sh | bash"
102+
retry_critical "NVM ${NVM_VERSION}" "curl -sS -o- https://raw.githubusercontent.com/nvm-sh/nvm/v${NVM_VERSION}/install.sh | bash"
99103

100104
# Setup NVM environment
101105
export NVM_DIR="$HOME/.nvm"
102106
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
103107
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
104108

105109
# Install Node.js and tools
106-
nvm install ${NODE_VERSION}
107-
nvm install-latest-npm
108-
npm install -g aws-cdk artillery
110+
retry_critical "Node.js ${NODE_VERSION}" "nvm install ${NODE_VERSION}"
111+
retry_critical "npm (latest)" "nvm install-latest-npm"
112+
retry_critical "CDK and Artillery" "npm install -g aws-cdk artillery"
109113

110114
# Verify installations
111115
log_info "Node.js version: $(node -v)"
@@ -124,7 +128,7 @@ install_maven() {
124128
local mvn_filename=apache-maven-${MAVEN_VERSION}-bin.tar.gz
125129
local mvn_url="https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/${mvn_filename}"
126130

127-
retry_critical "curl -sS -4 -L '$mvn_url' | tar -xz"
131+
retry_critical "Maven ${MAVEN_VERSION}" "curl -sS -4 -L '$mvn_url' | tar -xz"
128132

129133
sudo mv "$mvn_foldername" /usr/lib/maven
130134
echo "export M2_HOME=/usr/lib/maven" | sudo tee -a /etc/profile.d/workshop.sh >/dev/null
@@ -142,14 +146,14 @@ install_aws_tools() {
142146
download_and_verify "https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip" "aws-sam-cli-linux-x86_64.zip" "AWS SAM CLI"
143147

144148
unzip -q aws-sam-cli-linux-x86_64.zip -d sam-installation
145-
sudo ./sam-installation/install --update
149+
retry_critical "SAM CLI installation" "sudo ./sam-installation/install --update"
146150
rm -rf ./sam-installation/ aws-sam-cli-linux-x86_64.zip
147151

148152
log_info "SAM CLI version: $(/usr/local/bin/sam --version)"
149153

150154
log_info "Installing Session Manager Plugin..."
151155
download_and_verify "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/linux_64bit/session-manager-plugin.rpm" "session-manager-plugin.rpm" "Session Manager Plugin"
152-
sudo dnf -q install -y session-manager-plugin.rpm
156+
retry_critical "Session Manager Plugin" "sudo dnf -q install -y session-manager-plugin.rpm"
153157
rm session-manager-plugin.rpm
154158
}
155159

@@ -175,7 +179,7 @@ install_kubernetes_tools() {
175179
# log_info "eksctl version: $(eksctl version)"
176180

177181
log_info "Installing Helm ${HELM_VERSION}..."
178-
retry_critical "curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3"
182+
retry_critical "Helm ${HELM_VERSION}" "curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3"
179183
chmod 700 get_helm.sh
180184
./get_helm.sh --version v${HELM_VERSION}
181185
helm completion bash >> ~/.bash_completion
@@ -187,10 +191,10 @@ install_kubernetes_tools() {
187191
sudo mv eks-node-viewer /usr/local/bin
188192

189193
log_info "Installing k9s..."
190-
retry_optional "curl -sS https://webinstall.dev/k9s | bash"
194+
retry_optional "k9s" "curl -sS https://webinstall.dev/k9s | bash"
191195

192196
log_info "Installing e1s..."
193-
retry_optional "curl -sL https://raw.githubusercontent.com/keidarcy/e1s-install/master/cloudshell-install.sh | bash"
197+
retry_optional "e1s" "curl -sL https://raw.githubusercontent.com/keidarcy/e1s-install/master/cloudshell-install.sh | bash"
194198
}
195199

196200
install_kubernetes_tools

infra/scripts/ide/bootstrap.sh

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,22 @@ echo "Full bootstrap started at $(date)"
1313
echo "Parameters: GIT_BRANCH=$GIT_BRANCH, TEMPLATE_TYPE=$TEMPLATE_TYPE"
1414

1515
# Retry utility function
16-
# Usage: retry_command <attempts> <delay> <fail_mode> <command...>
16+
# Usage: retry_command <attempts> <delay> <fail_mode> <tool_name> <command...>
1717
# fail_mode: "FAIL" (exit on failure), "LOG" (log and continue)
1818
retry_command() {
1919
local max_attempts="$1"
2020
local delay="$2"
2121
local fail_mode="$3"
22-
shift 3
22+
local tool_name="$4"
23+
shift 4
2324
local cmd="$*"
2425

2526
for attempt in $(seq 1 $max_attempts); do
26-
echo "Attempt $attempt/$max_attempts: $cmd"
2727
if eval "$cmd"; then
28-
echo "✅ Success on attempt $attempt"
28+
echo "✅ Success: $tool_name"
2929
return 0
3030
fi
31-
echo "❌ Failed on attempt $attempt"
31+
echo "❌ Failed attempt $attempt/$max_attempts: $tool_name"
3232

3333
if [ $attempt -lt $max_attempts ]; then
3434
echo "Waiting ${delay}s before retry..."
@@ -37,10 +37,10 @@ retry_command() {
3737
done
3838

3939
if [ "$fail_mode" = "FAIL" ]; then
40-
echo "💥 FATAL: Command failed after $max_attempts attempts: $cmd"
40+
echo "💥 FATAL: $tool_name failed after $max_attempts attempts"
4141
exit 1
4242
else
43-
echo "⚠️ WARNING: Command failed after $max_attempts attempts (continuing): $cmd"
43+
echo "⚠️ WARNING: $tool_name failed after $max_attempts attempts (continuing)"
4444
return 1
4545
fi
4646
}
@@ -56,10 +56,10 @@ echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing jq (required for secret parsing)
5656
dnf install -y -q jq
5757

5858
echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing AWS CLI..."
59-
retry_critical "curl -LSsf -o /tmp/aws-cli.zip https://awscli.amazonaws.com/awscli-exe-linux-$(uname -m).zip && rm -rf /tmp/aws && unzip -q -d /tmp /tmp/aws-cli.zip && /tmp/aws/install --update && rm -rf /tmp/aws*"
59+
retry_critical "AWS CLI" "curl -LSsf -o /tmp/aws-cli.zip https://awscli.amazonaws.com/awscli-exe-linux-$(uname -m).zip && rm -rf /tmp/aws && unzip -q -d /tmp /tmp/aws-cli.zip && /tmp/aws/install --update && rm -rf /tmp/aws*"
6060

6161
echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing CloudFormation helper scripts..."
62-
retry_critical "dnf install -y aws-cfn-bootstrap"
62+
retry_critical "CloudFormation helper scripts" "dnf install -y aws-cfn-bootstrap"
6363

6464
echo "$(date '+%Y-%m-%d %H:%M:%S') - Fetching IDE password from Secrets Manager..."
6565
IDE_PASSWORD=$(aws secretsmanager get-secret-value --secret-id "ide-password" --query SecretString --output text | jq -r .password)

infra/scripts/ide/vscode.sh

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@ retry_critical() {
1818
if command -v retry_command >/dev/null 2>&1; then
1919
retry_command 5 5 "FAIL" "$@"
2020
else
21-
eval "$*"
21+
local tool_name="$1"
22+
shift
23+
eval "$*" || { echo "💥 FATAL: $tool_name failed"; exit 1; }
2224
fi
2325
}
2426
retry_optional() {
2527
if command -v retry_command >/dev/null 2>&1; then
2628
retry_command 5 5 "LOG" "$@"
2729
else
28-
eval "$*" || echo "⚠️ Warning: $* failed (continuing)"
30+
local tool_name="$1"
31+
shift
32+
eval "$*" || echo "⚠️ Warning: $tool_name failed (continuing)"
2933
fi
3034
}
3135

@@ -46,8 +50,8 @@ echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing code-server..."
4650
codeServer=$(dnf list installed code-server 2>/dev/null | wc -l)
4751
if [ "$codeServer" -eq "0" ]; then
4852
# Install as ec2-user with retry logic - pass version as environment variable
49-
retry_critical "sudo -u ec2-user bash -c 'curl -fsSL https://code-server.dev/install.sh | sh -s -- --version $VSCODE_VERSION'"
50-
retry_critical "systemctl enable --now code-server@ec2-user"
53+
retry_critical "VS Code Server" "sudo -u ec2-user bash -c 'curl -fsSL https://code-server.dev/install.sh | sh -s -- --version $VSCODE_VERSION'"
54+
retry_critical "VS Code Server service" "systemctl enable --now code-server@ec2-user"
5155
fi
5256

5357
# Configure code-server
@@ -94,7 +98,7 @@ if [ ! -z "$EXTENSIONS" ]; then
9498
extension=$(echo "$extension" | xargs)
9599
if [ ! -z "$extension" ]; then
96100
echo "Installing extension: $extension"
97-
retry_optional "run_as_user 'code-server --install-extension $extension --force'"
101+
retry_optional "VS Code extension $extension" "run_as_user 'code-server --install-extension $extension --force'"
98102
fi
99103
done
100104
else
@@ -105,9 +109,9 @@ echo "Restarting code-server..."
105109
systemctl restart code-server@ec2-user
106110

107111
echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing Caddy..."
108-
retry_critical "dnf copr enable -y -q @caddy/caddy epel-9-x86_64"
109-
retry_critical "dnf install -y -q caddy"
110-
retry_critical "systemctl enable --now caddy"
112+
retry_critical "Caddy repository" "dnf copr enable -y -q @caddy/caddy epel-9-x86_64"
113+
retry_critical "Caddy" "dnf install -y -q caddy"
114+
retry_critical "Caddy service" "systemctl enable --now caddy"
111115

112116
tee /etc/caddy/Caddyfile <<EOF
113117
:80 {

0 commit comments

Comments
 (0)