From f48cdbbb5308cde8d946574f501823b21140a68b Mon Sep 17 00:00:00 2001 From: Roberto Luna-Rojas Date: Fri, 7 Nov 2025 20:38:39 +0100 Subject: [PATCH 1/3] AWS | OSS | DBTop Monitoring Tool | Deployment script WIP --- conf/.gitignore | 2 ++ conf/deploy.sh | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 conf/.gitignore create mode 100755 conf/deploy.sh diff --git a/conf/.gitignore b/conf/.gitignore new file mode 100644 index 0000000..7bd20a6 --- /dev/null +++ b/conf/.gitignore @@ -0,0 +1,2 @@ +*.log +*.txt \ No newline at end of file diff --git a/conf/deploy.sh b/conf/deploy.sh new file mode 100755 index 0000000..3b49f43 --- /dev/null +++ b/conf/deploy.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +set -e + +gum style \ + --foreground 212 --border-foreground 212 --border double \ + --align center --width 50 --margin "1 2" --padding "2 4" \ + "DBTop Monitoring Tool" + +export AWS_REGION=$(aws configure get region --profile default) +export AWS_REGION=$(gum input --placeholder ${AWS_REGION} --value ${AWS_REGION}) + +# If AWS_REGION is in Americas I want to use the emoji 🌎 or Europe, Middle East and Africa 🌍 or Asia and Oceania 🌏 +# Function to determine region emoji based on AWS region +get_region_emoji() { + local region=$1 + case $region in + # Americas + us-east-1|us-east-2|us-west-1|us-west-2|ca-central-1|sa-east-1|mx-central-1) + echo "🌎" + ;; + # Europe, Middle East and Africa + eu-central-1|eu-west-1|eu-west-2|eu-west-3|eu-north-1|eu-south-1|me-south-1|af-south-1) + echo "🌍" + ;; + # Asia and Oceania + ap-east-1|ap-southeast-1|ap-southeast-2|ap-southeast-3|ap-northeast-1|ap-northeast-2|ap-northeast-3|ap-south-1) + echo "🌏" + ;; + *) + echo "πŸ—ΊοΈ" + ;; + esac +} + +REGION_EMOJI="$(get_region_emoji ${AWS_REGION}) ${AWS_REGION}" + +gum spin --spinner dot --title "Deploying DBTop to AWS Region: ${REGION_EMOJI}" -- sleep 2 + +Elasticache_RBAC_User_Name="info-ping-user" + +# Check if user exists +if aws elasticache describe-users --user-id ${Elasticache_RBAC_User_Name} --region ${AWS_REGION} >/dev/null 2>&1; then + echo "User ${Elasticache_RBAC_User_Name} already exists in region ${REGION_EMOJI}" +else + [ ! -f password.txt ] && gum input --password > password.txt + + if [ ! -f password.txt ] || [ $(cat password.txt | wc -c) -lt 16 ]; then + openssl rand -base64 32 > password.txt + fi + + echo "Creating user ${Elasticache_RBAC_User_Name} in region ${REGION_EMOJI}" + Elasticache_RBAC_User_File="../logs/elasticache-rbac-user.json" + aws elasticache create-user \ + --user-id ${Elasticache_RBAC_User_Name} \ + --user-name ${Elasticache_RBAC_User_Name} \ + --engine valkey \ + --authentication-mode Type=password,Passwords=$(cat password.txt) \ + --access-string "on ~* +info +ping" \ + --region ${AWS_REGION} > ${Elasticache_RBAC_User_File} + cat ${Elasticache_RBAC_User_File} | jq -C . +fi + +# aws cloudformation create-stack \ +# --stack-name "db-top-solution" \ +# --template-body file://DBTopMonitoringSolution.template \ +# --parameters +# ParameterKey=Username,ParameterValue=email@example.com \ +# ParameterKey=VPCParam,ParameterValue=vpc-abc123456789 \ +# ParameterKey=SubnetParam,ParameterValue=subnet-abc123456789 \ +# ParameterKey=InstanceType,ParameterValue=t4g.large \ +# ParameterKey=PublicAccess,ParameterValue=true \ +# ParameterKey=SGInboundAccess,ParameterValue=0.0.0.0/0 \ +# ParameterKey=GitHubRepository,ParameterValue=aws-samples \ +# --region us-east-1 \ +# --capabilities CAPABILITY_NAMED_IAM From cd5b1f362a37bf0488b2b6f78a8fdeee4907093e Mon Sep 17 00:00:00 2001 From: Roberto Luna-Rojas Date: Fri, 7 Nov 2025 21:12:13 +0100 Subject: [PATCH 2/3] AWS | OSS | DBTop Monitoring Tool | Deployment script WIP --- conf/DBTopMonitoringSolution.yaml | 426 ++++++++++++++++++++++++++++++ conf/deploy.sh | 111 +++++++- 2 files changed, 523 insertions(+), 14 deletions(-) create mode 100644 conf/DBTopMonitoringSolution.yaml diff --git a/conf/DBTopMonitoringSolution.yaml b/conf/DBTopMonitoringSolution.yaml new file mode 100644 index 0000000..504d5b3 --- /dev/null +++ b/conf/DBTopMonitoringSolution.yaml @@ -0,0 +1,426 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: "DBTop Monitoring Solution - (uksb-fsqa5yre5y)." +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - + Label: + default: "Application Update - Disclaimer" + Parameters: + - ApplicationUpdateEnabled + - ApplicationUpdateUrl + + - + Label: + default: "General Configuration" + Parameters: + - Username + - AMIId + - InstanceType + - GitHubRepository + + - + Label: + default: "Network Configuration" + Parameters: + - VPCParam + - SubnetParam + - PublicAccess + - SGInboundAccess + + ParameterLabels: + Username: + default: "Specify username for application access, temporary credentials will be sent by email." + AMIId: + default: "Specify AWS Linux AMI to be used for Application Deployment." + InstanceType: + default: "Specify instance type for Application Deployment." + VPCParam: + default: "Select VPC for Application Deployment." + SubnetParam: + default: "Select Subnet for Application Deployment, this subnet needs internet outbound access to reach AWS APIs." + PublicAccess: + default: "The deployment will assign private IP Address by default to access the application, you can assign Public IP Address to access the application in case you need it, Select (true) to assign Public IP Address." + SGInboundAccess: + default: "Specify CIDR inbound access rule, this will grant network access for the application." + GitHubRepository: + default: "AWS Github Repository source used for deployment." + ApplicationUpdateEnabled: + default: "Disclaimer : This Application could able to verify new versions, it will help to keep update with most popular features. Customer can review the code and validate data scope. Selecting (true) I acknowledge that this Application will access to url to verify new versions." + ApplicationUpdateUrl: + default: "URL used to verify new application versions." + + +Parameters: + + VPCParam: + Type: AWS::EC2::VPC::Id + Description: Select VPC + + SubnetParam: + Type: AWS::EC2::Subnet::Id + Description: Select Subnet + + AMIId: + Type: AWS::SSM::Parameter::Value + Description: AWS AMI + Default: '/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64' + + Username: + Type: String + Description: Username (email) + AllowedPattern: "\\w[-\\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\\.)+[A-Za-z]{2,14}" + + InstanceType: + Type: String + Description: InstanceType + Default: t3a.medium + AllowedValues: + - t3a.micro + - t3a.small + - t3a.medium + - t3a.large + - t3a.xlarge + PublicAccess: + Type: String + Description: Assign Public IP Address + Default: "false" + AllowedValues: + - "true" + - "false" + + SGInboundAccess: + Type: String + Description: CIDR (0.0.0.0/0) + + GitHubRepository: + Type: String + Description: AWS Github Repository + Default: aws-samples + + ApplicationUpdateUrl: + Type: String + Description: URL + Default: https://version.code.ds.wwcs.aws.dev/ + + ApplicationUpdateEnabled: + Type: String + Description: Option + Default: "true" + AllowedValues: + - "true" + - "false" + +Conditions: + isPublic: !Equals [ !Ref PublicAccess, true] + +Resources: + InstanceProfile: + Type: "AWS::IAM::InstanceProfile" + DependsOn: IAMRoleEC2 + Properties: + Path: "/" + Roles: [!Ref IAMRoleEC2] + + + IAMPolicyEc2: + Type: AWS::IAM::ManagedPolicy + Properties: + ManagedPolicyName: !Join [ "-", ["policy-ec2-db-top-solution", !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]]] + PolicyDocument: !Sub | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "logs:GetLogEvents" + ], + "Resource": [ + "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:RDSOSMetrics:log-stream:*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "cloudwatch:GetMetricData" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "rds:DescribeDBInstances" + ], + "Resource": "arn:aws:rds:${AWS::Region}:${AWS::AccountId}:db:*" + }, + { + "Effect": "Allow", + "Action": [ + "rds:DescribeDBShardGroups" + ], + "Resource": "arn:aws:rds:${AWS::Region}:${AWS::AccountId}:shard-group:*" + }, + { + "Effect": "Allow", + "Action": [ + "rds:DescribeDBClusters" + ], + "Resource": "arn:aws:rds:${AWS::Region}:${AWS::AccountId}:cluster:*" + }, + { + "Effect": "Allow", + "Action": [ + "elasticache:DescribeReplicationGroups" + ], + "Resource": "arn:aws:elasticache:${AWS::Region}:${AWS::AccountId}:replicationgroup:*" + }, + { + "Effect": "Allow", + "Action": [ + "elasticache:DescribeServerlessCaches" + ], + "Resource": "arn:aws:elasticache:${AWS::Region}:${AWS::AccountId}:serverlesscache:*" + }, + { + "Effect": "Allow", + "Action": "memorydb:DescribeClusters", + "Resource": "arn:aws:memorydb:${AWS::Region}:${AWS::AccountId}:cluster/*" + }, + { + "Effect": "Allow", + "Action": "docdb-elastic:GetCluster", + "Resource": "arn:aws:docdb-elastic:${AWS::Region}:${AWS::AccountId}:cluster/*" + }, + { + "Effect": "Allow", + "Action": "docdb-elastic:ListClusters", + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": "dynamodb:DescribeTable", + "Resource": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/*" + }, + { + "Effect": "Allow", + "Action": "dynamodb:ListTables", + "Resource": "*" + } + ] + } + + + IAMRoleEC2: + Type: "AWS::IAM::Role" + DependsOn: IAMPolicyEc2 + Properties: + Path: "/" + RoleName: !Join [ "-", ["role-ec2-db-top-solution", !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]]] + AssumeRolePolicyDocument: "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}" + MaxSessionDuration: 3600 + Description: "Allows EC2 instance to call AWS services on your behalf." + ManagedPolicyArns: + - !Ref IAMPolicyEc2 + - "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" + + IAMRoleCognito: + Type: "AWS::IAM::Role" + Properties: + Path: "/" + RoleName: !Join [ "-", ["role-cognito-db-top-solution", !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]]] + AssumeRolePolicyDocument: "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"cognito-idp.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}" + MaxSessionDuration: 3600 + Description: "Allows Cognito to use SMS MFA on your behalf." + Policies: + - PolicyName: "CognitoPolicy" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Action: + - "sns:publish" + Resource: "*" + + EC2Instance: + Type: "AWS::EC2::Instance" + DependsOn: [CognitoUserPool,IAMRoleEC2] + Properties: + ImageId: !Ref AMIId + InstanceType: !Ref InstanceType + Tenancy: "default" + EbsOptimized: true + SourceDestCheck: true + BlockDeviceMappings: + - + DeviceName: "/dev/xvda" + Ebs: + Encrypted: false + VolumeSize: 20 + VolumeType: "gp2" + DeleteOnTermination: true + IamInstanceProfile: !Ref InstanceProfile + NetworkInterfaces: + - AssociatePublicIpAddress: !Ref PublicAccess + DeviceIndex: "0" + GroupSet: + - Ref: VPCSecurityGroup + SubnetId: + Ref: SubnetParam + UserData: + Fn::Base64: + !Sub | + #!/bin/bash + sudo mkdir -p /aws/apps + + cd /tmp + sudo yum install -y git + git clone https://github.com/${GitHubRepository}/db-top-monitoring.git + cd db-top-monitoring + sudo cp -r server frontend conf /aws/apps + + echo '{ "aws_region": "${AWS::Region}","aws_cognito_user_pool_id": "${CognitoUserPool}","aws_cognito_user_pool_web_client_id": "${CognitoUserPoolClient}","aws_api_port": 3000, "aws_token_expiration":24, "aws_application_update_url" : "${ApplicationUpdateUrl}", "aws_application_update_enabled" : "${ApplicationUpdateEnabled}" }' > /aws/apps/conf/aws-exports.json + cd /aws/apps + sudo -u ec2-user sh conf/setup.sh 2>&1 | tee /tmp/setup.log + + sudo sed -i 's/GitHubRepository/${GitHubRepository}/g' /aws/apps/conf/update.sh + + Tags: + - + Key: "Name" + Value: !Join [ "-", ["ec2-db-top-solution", !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]]] + + + VPCSecurityGroup: + Type: "AWS::EC2::SecurityGroup" + Properties: + GroupDescription: !Join [ "_", ["sg_security_group_db_top_solution", !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]]] + GroupName: !Join [ "_", ["sg_security_group_db_top_solution", !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]]] + VpcId: !Ref VPCParam + SecurityGroupIngress: + - + CidrIp: !Ref SGInboundAccess + FromPort: 443 + IpProtocol: "tcp" + ToPort: 443 + + SecurityGroupEgress: + - + CidrIp: "0.0.0.0/0" + IpProtocol: "-1" + + + CognitoUserPool: + Type: "AWS::Cognito::UserPool" + Properties: + UserPoolName: !Join [ "-", ["AwsDbTopSolutionUserPool", !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]]] + Policies: + PasswordPolicy: + MinimumLength: 8 + RequireUppercase: true + RequireLowercase: true + RequireNumbers: true + RequireSymbols: true + TemporaryPasswordValidityDays: 7 + LambdaConfig: {} + AutoVerifiedAttributes: + - "email" + UsernameAttributes: + - "email" + MfaConfiguration: "OPTIONAL" + SmsConfiguration: + SnsCallerArn: !GetAtt IAMRoleCognito.Arn + SnsRegion: !Ref AWS::Region + EmailConfiguration: + EmailSendingAccount: "COGNITO_DEFAULT" + AdminCreateUserConfig: + AllowAdminCreateUserOnly: true + UserPoolTags: {} + AccountRecoverySetting: + RecoveryMechanisms: + - + Priority: 1 + Name: "verified_email" + UsernameConfiguration: + CaseSensitive: false + VerificationMessageTemplate: + DefaultEmailOption: "CONFIRM_WITH_CODE" + + CognitoUserPoolClient: + Type: "AWS::Cognito::UserPoolClient" + Properties: + UserPoolId: !Ref CognitoUserPool + ClientName: !Join [ "-", ["AwsDbTopSolutionUserPoolClient", !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]]] + RefreshTokenValidity: 1 + ReadAttributes: + - "address" + - "birthdate" + - "email" + - "email_verified" + - "family_name" + - "gender" + - "given_name" + - "locale" + - "middle_name" + - "name" + - "nickname" + - "phone_number" + - "phone_number_verified" + - "picture" + - "preferred_username" + - "profile" + - "updated_at" + - "website" + - "zoneinfo" + WriteAttributes: + - "address" + - "birthdate" + - "email" + - "family_name" + - "gender" + - "given_name" + - "locale" + - "middle_name" + - "name" + - "nickname" + - "phone_number" + - "picture" + - "preferred_username" + - "profile" + - "updated_at" + - "website" + - "zoneinfo" + ExplicitAuthFlows: + - "ALLOW_REFRESH_TOKEN_AUTH" + - "ALLOW_USER_SRP_AUTH" + PreventUserExistenceErrors: "ENABLED" + AllowedOAuthFlowsUserPoolClient: false + IdTokenValidity: 1440 + AccessTokenValidity: 1440 + TokenValidityUnits: + AccessToken: "minutes" + IdToken: "minutes" + RefreshToken: "days" + + CognitoUserPoolUser: + Type: "AWS::Cognito::UserPoolUser" + Properties: + Username: !Ref Username + UserPoolId: !Ref CognitoUserPool + UserAttributes: + - + Name: "email_verified" + Value: "true" + - + Name: "email" + Value: !Ref Username + +Outputs: + PublicAppURL: + Description: Public Endpoint + Value: !Join [ "", ["https://", !GetAtt EC2Instance.PublicIp]] + Condition: isPublic + PrivateAppURL: + Description: Private Endpoint + Value: !Join [ "", ["https://", !GetAtt EC2Instance.PrivateIp]] + diff --git a/conf/deploy.sh b/conf/deploy.sh index 3b49f43..b98ee52 100755 --- a/conf/deploy.sh +++ b/conf/deploy.sh @@ -44,7 +44,6 @@ if aws elasticache describe-users --user-id ${Elasticache_RBAC_User_Name} --regi echo "User ${Elasticache_RBAC_User_Name} already exists in region ${REGION_EMOJI}" else [ ! -f password.txt ] && gum input --password > password.txt - if [ ! -f password.txt ] || [ $(cat password.txt | wc -c) -lt 16 ]; then openssl rand -base64 32 > password.txt fi @@ -61,16 +60,100 @@ else cat ${Elasticache_RBAC_User_File} | jq -C . fi -# aws cloudformation create-stack \ -# --stack-name "db-top-solution" \ -# --template-body file://DBTopMonitoringSolution.template \ -# --parameters -# ParameterKey=Username,ParameterValue=email@example.com \ -# ParameterKey=VPCParam,ParameterValue=vpc-abc123456789 \ -# ParameterKey=SubnetParam,ParameterValue=subnet-abc123456789 \ -# ParameterKey=InstanceType,ParameterValue=t4g.large \ -# ParameterKey=PublicAccess,ParameterValue=true \ -# ParameterKey=SGInboundAccess,ParameterValue=0.0.0.0/0 \ -# ParameterKey=GitHubRepository,ParameterValue=aws-samples \ -# --region us-east-1 \ -# --capabilities CAPABILITY_NAMED_IAM +STACK_NAME="db-top-solution" +STACK_NAME=$(gum input --placeholder ${STACK_NAME} --value ${STACK_NAME}) + +USER_EMAIL="email@example.com" +USER_EMAIL=$(gum input --placeholder ${USER_EMAIL} --value ${USER_EMAIL}) + +VPC_PARAM="vpc-abc123456789" +VPC_PARAM=$(gum input --placeholder ${VPC_PARAM} --value ${VPC_PARAM}) + +# Check if VPC exists in the specified region +if ! aws ec2 describe-vpcs --vpc-ids ${VPC_PARAM} --region ${AWS_REGION} >/dev/null 2>&1; then + echo "Error: VPC ${VPC_PARAM} does not exist in region ${REGION_EMOJI}" + # Get list of VPCs and format for gum choice + VPC_LIST=$(aws ec2 describe-vpcs --region ${AWS_REGION} --query 'Vpcs[*].[VpcId,Tags[?Key==`Name`].Value | [0],CidrBlock]' --output text | awk '{printf "%s (%s) - %s\n", $1, $2, $3}') + # Let user select VPC using gum choice + VPC_SELECTION=$(echo "${VPC_LIST}" | gum choose) + # Extract VPC ID from selection + VPC_PARAM=$(echo ${VPC_SELECTION} | cut -d' ' -f1) + echo "Selected VPC: ${VPC_PARAM}" +fi + +SUBNET_PARAM="subnet-abc123456789" +SUBNET_PARAM=$(gum input --placeholder ${SUBNET_PARAM} --value ${SUBNET_PARAM}) + +# Check if subnet exists and is public +check_subnet_public() { + local subnet_id=$1 + local vpc_id=$2 + local region=$3 + + # Check if subnet exists and get route table info + if ! aws ec2 describe-subnets --subnet-ids ${subnet_id} --region ${region} >/dev/null 2>&1; then + return 1 + fi + + # Check if subnet has route to internet gateway or NAT gateway + local route_tables=$(aws ec2 describe-route-tables --filters "Name=vpc-id,Values=${vpc_id}" "Name=association.subnet-id,Values=${subnet_id}" --region ${region} --query 'RouteTables[*].Routes[?DestinationCidrBlock==`0.0.0.0/0`].[GatewayId,NatGatewayId]' --output text) + + if [[ -n "${route_tables}" ]]; then + return 0 + fi + return 1 +} + +# Check if provided subnet is valid and public +if ! check_subnet_public ${SUBNET_PARAM} ${VPC_PARAM} ${AWS_REGION}; then + echo "Error: Subnet ${SUBNET_PARAM} is not public or does not exist in VPC ${VPC_PARAM} region ${REGION_EMOJI}" + + # Get list of public subnets with NAT gateway + SUBNET_LIST=$(aws ec2 describe-subnets \ + --filters "Name=vpc-id,Values=${VPC_PARAM}" \ + --region ${AWS_REGION} \ + --query 'Subnets[*].[SubnetId,Tags[?Key==`Name`].Value | [0],CidrBlock]' \ + --output text | while read subnet_id name cidr; do + if check_subnet_public ${subnet_id} ${VPC_PARAM} ${AWS_REGION}; then + echo "${subnet_id} (${name:-Unnamed}) - ${cidr}" + fi + done) + + # Let user select subnet using gum choice + SUBNET_SELECTION=$(echo "${SUBNET_LIST}" | gum choose) + # Extract subnet ID from selection + SUBNET_PARAM=$(echo ${SUBNET_SELECTION} | cut -d' ' -f1) + echo "Selected Subnet: ${SUBNET_PARAM}" +fi + +INSTANCE_TYPE="t4g.large" +INSTANCE_TYPE=$(gum input --placeholder ${INSTANCE_TYPE} --value ${INSTANCE_TYPE}) + +# Get allowed instance types from CloudFormation template +ALLOWED_INSTANCES=$(cat DBTopMonitoringSolution.yaml | yq -r '.Parameters.InstanceType.AllowedValues[]') + +# Check if provided instance type is in allowed list +if ! echo "${ALLOWED_INSTANCES}" | grep -q "^${INSTANCE_TYPE}$"; then + echo "Error: Instance type ${INSTANCE_TYPE} is not allowed" + # Let user select from allowed instances using gum choice + INSTANCE_TYPE=$(echo "${ALLOWED_INSTANCES}" | gum choose) + echo "Selected Instance Type: ${INSTANCE_TYPE}" +fi + +PUBLIC_ACCESS="true" +SG_INBOUND_ACCESS="0.0.0.0/0" +GITHUB_REPOSITORY="aws-samples" + +aws cloudformation create-stack \ + --stack-name "${STACK_NAME}" \ + --template-body file://DBTopMonitoringSolution.yaml \ + --parameters \ + ParameterKey=Username,ParameterValue=${USER_EMAIL} \ + ParameterKey=VPCParam,ParameterValue=${VPC_PARAM} \ + ParameterKey=SubnetParam,ParameterValue=${SUBNET_PARAM} \ + ParameterKey=InstanceType,ParameterValue=${INSTANCE_TYPE} \ + ParameterKey=PublicAccess,ParameterValue=${PUBLIC_ACCESS} \ + ParameterKey=SGInboundAccess,ParameterValue=${SG_INBOUND_ACCESS} \ + ParameterKey=GitHubRepository,ParameterValue=${GITHUB_REPOSITORY} \ + --region ${AWS_REGION} \ + --capabilities CAPABILITY_NAMED_IAM From 0a03634636d7cd8868030f7409f106cbdc4667b8 Mon Sep 17 00:00:00 2001 From: Roberto Luna-Rojas Date: Fri, 7 Nov 2025 21:47:28 +0100 Subject: [PATCH 3/3] AWS | OSS | DBTop Monitoring Tool | Deployment script WIP --- conf/deploy.sh | 55 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/conf/deploy.sh b/conf/deploy.sh index b98ee52..4249c81 100755 --- a/conf/deploy.sh +++ b/conf/deploy.sh @@ -62,9 +62,11 @@ fi STACK_NAME="db-top-solution" STACK_NAME=$(gum input --placeholder ${STACK_NAME} --value ${STACK_NAME}) +echo "Stack name: ${STACK_NAME}" USER_EMAIL="email@example.com" USER_EMAIL=$(gum input --placeholder ${USER_EMAIL} --value ${USER_EMAIL}) +echo "User email: ${USER_EMAIL}" VPC_PARAM="vpc-abc123456789" VPC_PARAM=$(gum input --placeholder ${VPC_PARAM} --value ${VPC_PARAM}) @@ -78,8 +80,8 @@ if ! aws ec2 describe-vpcs --vpc-ids ${VPC_PARAM} --region ${AWS_REGION} >/dev/n VPC_SELECTION=$(echo "${VPC_LIST}" | gum choose) # Extract VPC ID from selection VPC_PARAM=$(echo ${VPC_SELECTION} | cut -d' ' -f1) - echo "Selected VPC: ${VPC_PARAM}" fi +echo "Selected VPC: ${VPC_PARAM}" SUBNET_PARAM="subnet-abc123456789" SUBNET_PARAM=$(gum input --placeholder ${SUBNET_PARAM} --value ${SUBNET_PARAM}) @@ -123,8 +125,8 @@ if ! check_subnet_public ${SUBNET_PARAM} ${VPC_PARAM} ${AWS_REGION}; then SUBNET_SELECTION=$(echo "${SUBNET_LIST}" | gum choose) # Extract subnet ID from selection SUBNET_PARAM=$(echo ${SUBNET_SELECTION} | cut -d' ' -f1) - echo "Selected Subnet: ${SUBNET_PARAM}" fi +echo "Selected Subnet: ${SUBNET_PARAM}" INSTANCE_TYPE="t4g.large" INSTANCE_TYPE=$(gum input --placeholder ${INSTANCE_TYPE} --value ${INSTANCE_TYPE}) @@ -137,23 +139,44 @@ if ! echo "${ALLOWED_INSTANCES}" | grep -q "^${INSTANCE_TYPE}$"; then echo "Error: Instance type ${INSTANCE_TYPE} is not allowed" # Let user select from allowed instances using gum choice INSTANCE_TYPE=$(echo "${ALLOWED_INSTANCES}" | gum choose) - echo "Selected Instance Type: ${INSTANCE_TYPE}" fi +echo "Selected Instance Type: ${INSTANCE_TYPE}" PUBLIC_ACCESS="true" SG_INBOUND_ACCESS="0.0.0.0/0" GITHUB_REPOSITORY="aws-samples" -aws cloudformation create-stack \ - --stack-name "${STACK_NAME}" \ - --template-body file://DBTopMonitoringSolution.yaml \ - --parameters \ - ParameterKey=Username,ParameterValue=${USER_EMAIL} \ - ParameterKey=VPCParam,ParameterValue=${VPC_PARAM} \ - ParameterKey=SubnetParam,ParameterValue=${SUBNET_PARAM} \ - ParameterKey=InstanceType,ParameterValue=${INSTANCE_TYPE} \ - ParameterKey=PublicAccess,ParameterValue=${PUBLIC_ACCESS} \ - ParameterKey=SGInboundAccess,ParameterValue=${SG_INBOUND_ACCESS} \ - ParameterKey=GitHubRepository,ParameterValue=${GITHUB_REPOSITORY} \ - --region ${AWS_REGION} \ - --capabilities CAPABILITY_NAMED_IAM +# Check if stack already exists +if aws cloudformation describe-stacks --stack-name "${STACK_NAME}" --region ${AWS_REGION} >/dev/null 2>&1; then + echo "Stack ${STACK_NAME} already exists in region ${REGION_EMOJI}" + + # Get stack outputs + STACK_OUTPUTS=$(aws cloudformation describe-stacks \ + --stack-name "${STACK_NAME}" \ + --region ${AWS_REGION} \ + --query 'Stacks[0].Outputs[]' \ + --output json) + + # Display stack description + echo "Stack ${STACK_NAME} details:" + echo "${STACK_OUTPUTS}" | jq -r '.[] | "\(.Description): \(.OutputValue)"' + + # Get public URL from outputs + PUBLIC_URL=$(echo "${STACK_OUTPUTS}" | jq -r '.[] | select(.OutputKey=="PublicAppURL") | .OutputValue') + echo -e "\nPublic URL: ${PUBLIC_URL}" +else + echo "Creating stack ${STACK_NAME} in region ${REGION_EMOJI}" + aws cloudformation create-stack \ + --stack-name "${STACK_NAME}" \ + --template-body file://DBTopMonitoringSolution.yaml \ + --parameters \ + ParameterKey=Username,ParameterValue=${USER_EMAIL} \ + ParameterKey=VPCParam,ParameterValue=${VPC_PARAM} \ + ParameterKey=SubnetParam,ParameterValue=${SUBNET_PARAM} \ + ParameterKey=InstanceType,ParameterValue=${INSTANCE_TYPE} \ + ParameterKey=PublicAccess,ParameterValue=${PUBLIC_ACCESS} \ + ParameterKey=SGInboundAccess,ParameterValue=${SG_INBOUND_ACCESS} \ + ParameterKey=GitHubRepository,ParameterValue=${GITHUB_REPOSITORY} \ + --region ${AWS_REGION} \ + --capabilities CAPABILITY_NAMED_IAM > ../logs/stack-${STACK_NAME}.json +fi