Pull request #2: AWS Setup

Merge in DCF/mold_cost_calculator from AWS-Setup to master

* commit 'ec05c201f2cdcc8ec085353ebca369eda3085a3f':
  Revert static file fix
  Try another fix
  Try another fix
  Try fix for Font files
  Fix stack name
  Add a db handler
  Remove Mangum
  Update the API Gateway Resource Policy
  Update lambda configuration
  Update stack name
  Change the App Shortname
  Initial jenkins version
  Core App changes to make it AWS compatible
This commit is contained in:
Gopinathan, Anup Raj
2025-06-25 15:24:16 +00:00
17 changed files with 5145 additions and 20 deletions
+2 -1
View File
@@ -11,4 +11,5 @@ DB_NAME=mold_cost_development
# --- Application Configuration ---
SECRET_KEY='a_different_secret_key_for_development'
FLASK_DEBUG=1
FLASK_DEBUG=1
HOST_ENV=local # Set AWS for AWS deployments
+10
View File
@@ -123,3 +123,13 @@ powerapps/
fix_ssl_*.sh
quick_ssl_*.sh
download_static_files.sh
# CDK asset staging directory
.cdk.staging
cdk.out
# Miscellaneous
*.js
!jest.config.js
*.d.ts
node_modules
Vendored
+98
View File
@@ -0,0 +1,98 @@
@Library('GlobalJenkinsLibraryNew') _
def cdkDeployAccount = [
'poc': '615817712084',
'dev': '030215556060',
'uat': '534745294648',
'prd': '022438277857',
]
def credentialsIDs = [
'poc' : 'ProductDesign Jenkins Deployment POC',
'dev' : 'digital-creation Development Jenkins Deployment',
'uat' : 'digital-creation UAT Jenkins Deployment',
'prd' : 'digital-creation PRD Jenkins Deployment',
]
def mccDatabasePassword = [
'poc': 'MCC_POC_DATABASE_PASSWORD',
'dev': 'MCC_DEV_DATABASE_PASSWORD',
'uat': 'MCC_UAT_DATABASE_PASSWORD',
'prd': 'MCC_PRD_DATABASE_PASSWORD',
]
def mccDatabase = [
'poc': 'devmccmccdb-devmccmccdbwriterfe3d6e74-nzeetdanw6sx.c8uwkt51amck.eu-west-1.rds.amazonaws.com/mccdb',
'dev': 'dev-mld-mccdb.cluster-c8uwkt51amck.eu-west-1.rds.amazonaws.com/mccdb',
'uat': 'devmccmccdb-devmccmccdbwriterfe3d6e74-nzeetdanw6sx.c8uwkt51amck.eu-west-1.rds.amazonaws.com/mccdb',
'prd': 'devmccmccdb-devmccmccdbwriterfe3d6e74-nzeetdanw6sx.c8uwkt51amck.eu-west-1.rds.amazonaws.com/mccdb',
]
def stageChoices = ['poc', 'dev', 'uat']
if (env.BRANCH_NAME == 'main' || env.BRANCH_NAME == 'master') {
stageChoices = ['poc', 'dev', 'uat', 'prd']
}
def now = new Date()
pipeline {
environment {
DEPLOY_ENV = "${params.STAGE}"
HOME = "${WORKSPACE}"
CDK_HOME = "${WORKSPACE}"
CDK_DEPLOY_ACCOUNT = "${cdkDeployAccount[DEPLOY_ENV]}"
CDK_DEPLOY_REGION = 'eu-west-1'
CDK_QUALIFIER = "${DEPLOY_ENV}-mld"
STACK_NAME = 'MoldCostCalc'
POSTGRES_USER = 'mccAdmin'
POSTGRES_PASSWORD_ENV = "${mccDatabasePassword[DEPLOY_ENV]}"
FLASK_APP = 'run.py'
FLASK_ENV = 'production'
DATABASE_URL = "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${mccDatabase[DEPLOY_ENV]}"
HOST_ENV = 'AWS'
}
agent {
label 'ubuntu_node-20_docker-20'
}
parameters {
choice(name: 'ACTION', choices: ['Analysis', 'Deploy'], description: 'Deployment Type')
choice(name: 'STAGE', choices: stageChoices, description: 'Environment to deploy. Must match with the value in CDK.JSON context')
}
options {
disableConcurrentBuilds()
parallelsAlwaysFailFast()
timestamps()
buildDiscarder(logRotator(numToKeepStr: '3'))
}
stages {
stage('Deploy') {
when {
expression { return params.ACTION == 'Deploy' }
}
steps {
dir('infra') {
withCredentials([
string(credentialsId: "${POSTGRES_PASSWORD_ENV}", variable: 'POSTGRES_PASSWORD'),
aws(credentialsId: credentialsIDs[env.DEPLOY_ENV], accessKeyVariable: 'AWS_ACCESS_KEY_ID', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY')
]) {
withNvm {
sh 'npm install'
sh "npx aws-cdk@2.1019.1 bootstrap aws://${env.CDK_DEPLOY_ACCOUNT}/${env.CDK_DEPLOY_REGION} --context config=${env.DEPLOY_ENV} --toolkit-stack-name ${env.DEPLOY_ENV}${env.STACK_NAME} --change-set-name changeset-${env.DEPLOY_ENV}-${currentBuild.number} --qualifier ${env.CDK_QUALIFIER}"
sh "npx aws-cdk@2.1019.1 deploy --all --context config=${env.DEPLOY_ENV} --toolkit-stack-name ${env.DEPLOY_ENV}${env.STACK_NAME} --change-set-name ${env.STACK_NAME}-changeset-${env.DEPLOY_ENV}-${currentBuild.number} --require-approval never"
}
}
}
}
}
}
post {
always {
cleanWs()
}
}
}
+140
View File
@@ -0,0 +1,140 @@
import {
Stack,
StackProps,
aws_apigateway as apigateway,
aws_lambda,
aws_ec2,
Duration,
} from 'aws-cdk-lib';
import * as aws_lambda_python_alpha from '@aws-cdk/aws-lambda-python-alpha';
import { Construct } from 'constructs';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { join } from 'path';
import { Runtime } from 'aws-cdk-lib/aws-lambda';
import { BuildConfig } from '../utils/build-config';
import { Period } from 'aws-cdk-lib/aws-apigateway';
import { RetentionDays } from 'aws-cdk-lib/aws-logs';
import { VpcEndpoint, VpcEndpointService } from 'aws-cdk-lib/aws-ec2';
import { PolicyStatement, PolicyDocument, Effect, AnyPrincipal } from 'aws-cdk-lib/aws-iam';
export class MccAPIInfraStack extends Stack {
public readonly api: apigateway.RestApi;
constructor(
scope: Construct,
id: string,
buildConfig: BuildConfig,
props: StackProps,
) {
super(scope, id, props);
const stackName = `${buildConfig.Environment}${buildConfig.ApplicationShortName}`;
const vpc = aws_ec2.Vpc.fromLookup(this, `${stackName}Vpc`, {
vpcId: buildConfig.Parameters.VPC,
});
const executeApiVpcEndpointSecurityGroup = new aws_ec2.SecurityGroup(this, `${stackName}ExecuteApiVPCEsg`, {
vpc,
description: 'Security group for API Gateway VPC Endpoint',
allowAllOutbound: true,
});
executeApiVpcEndpointSecurityGroup.addIngressRule(
aws_ec2.Peer.ipv4('10.0.0.0/8'),
aws_ec2.Port.tcp(443),
'Allow inbound HTTPS traffic from 10.0.0.0/8'
);
const executeApiVpcEndpoint = new aws_ec2.InterfaceVpcEndpoint(this, `${stackName}ExecuteApiVPCE`, {
vpc,
service: aws_ec2.InterfaceVpcEndpointAwsService.APIGATEWAY,
privateDnsEnabled: true,
subnets: { subnetType: aws_ec2.SubnetType.PRIVATE_WITH_EGRESS },
securityGroups: [executeApiVpcEndpointSecurityGroup]
});
const lambdaFunction = new aws_lambda_python_alpha.PythonFunction(this, `${stackName}MCCApp`, {
runtime: Runtime.PYTHON_3_11,
handler: 'lambda_handler',
timeout: Duration.seconds(30),
entry: join(__dirname, '../../src'),
index: 'run.py',
memorySize: 256,
vpc,
logRetention: RetentionDays.ONE_WEEK,
environment: {
POSTGRES_USER: process.env.POSTGRES_USER!,
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD!,
POSTGRES_DB: 'mccdb',
FLASK_APP: process.env.FLASK_APP!,
FLASK_ENV: process.env.FLASK_ENV!,
DATABASE_URL: process.env.DATABASE_URL!,
HOST_ENV: process.env.HOST_ENV!,
},
});
const lambdaDBUpgradeFunction = new aws_lambda_python_alpha.PythonFunction(this, `${stackName}MCCAppDBUpgrade`, {
runtime: Runtime.PYTHON_3_11,
handler: 'lambda_handler_upgrade_db',
timeout: Duration.minutes(5),
entry: join(__dirname, '../../src'),
index: 'run.py',
memorySize: 1024,
vpc,
logRetention: RetentionDays.ONE_WEEK,
functionName: `${stackName}MCCAppDBUpgrade`,
environment: {
POSTGRES_USER: process.env.POSTGRES_USER!,
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD!,
POSTGRES_DB: 'mccdb',
FLASK_APP: process.env.FLASK_APP!,
FLASK_ENV: process.env.FLASK_ENV!,
DATABASE_URL: process.env.DATABASE_URL!,
HOST_ENV: process.env.HOST_ENV!,
},
});
const api = new apigateway.LambdaRestApi(this, `${stackName}MCCAPI`, {
handler: lambdaFunction,
proxy: true,
deployOptions: {
stageName: buildConfig.Environment,
},
restApiName: `${buildConfig.ApplicationName} API`,
description: `API for ${buildConfig.ApplicationName} in ${buildConfig.Environment}`,
endpointConfiguration: {
types: [apigateway.EndpointType.PRIVATE],
vpcEndpoints: [executeApiVpcEndpoint],
},
policy: new PolicyDocument({
statements: [
new PolicyStatement({
effect: Effect.ALLOW,
principals: [new AnyPrincipal()],
actions: ['execute-api:Invoke'],
resources: ['execute-api:/*/*/*'],
// Optionally, restrict by source VPC or VPC endpoint
conditions: {
StringEquals: { 'aws:SourceVpce': executeApiVpcEndpoint.vpcEndpointId }
}
}),
new PolicyStatement({
effect: Effect.DENY,
principals: [new AnyPrincipal()],
actions: ['execute-api:Invoke'],
resources: ['execute-api:/*/*/*'],
// Optionally, restrict by source VPC or VPC endpoint
conditions: {
StringNotEquals: { 'aws:SourceVpce': executeApiVpcEndpoint.vpcEndpointId }
}
}),
],
}),
});
this.api = api;
}
}
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { getConfig, BuildConfig } from '../utils/build-config';
import { MccAPIInfraStack } from './apiStack';
import { MccDBInfraStack } from './rdsStack';
const app = new cdk.App();
const buildConfig: BuildConfig = getConfig(app);
const qualifier = process.env.CDK_QUALIFIER;
const stackName = `${buildConfig.Environment}${buildConfig.ApplicationShortName}`;
cdk.Tags.of(app).add('App', buildConfig.ApplicationName);
cdk.Tags.of(app).add('Environment', buildConfig.Environment);
cdk.Tags.of(app).add('AppManagerCFNStackKey', stackName);
cdk.Tags.of(app).add('purpose', `${buildConfig.Environment}-${buildConfig.ApplicationShortName}`);
const env: cdk.Environment = {
account: buildConfig.Account ?? process.env.CDK_DEPLOY_ACCOUNT ?? process.env.CDK_DEFAULT_ACCOUNT,
region: buildConfig.Region ?? process.env.CDK_DEPLOY_REGION ?? process.env.CDK_DEFAULT_REGION,
};
const apiStack = new MccAPIInfraStack(app, `${stackName}MCCAPI`, buildConfig, {
env,
description: `API stack for ${buildConfig.ApplicationName} in ${buildConfig.Environment}`,
synthesizer: new cdk.DefaultStackSynthesizer({
qualifier,
}),
});
const dbStack = new MccDBInfraStack(app, `${stackName}MCCDB`, buildConfig, {
env,
description: `Database stack for ${buildConfig.ApplicationName} in ${buildConfig.Environment}`,
synthesizer: new cdk.DefaultStackSynthesizer({
qualifier,
}),
});
+79
View File
@@ -0,0 +1,79 @@
import {
Stack,
aws_ec2 as ec2,
aws_rds as rds,
StackProps,
RemovalPolicy,
} from 'aws-cdk-lib';
import { Port } from 'aws-cdk-lib/aws-ec2';
import { Construct } from 'constructs';
import { BuildConfig } from '../utils/build-config';
import { ClusterInstance } from 'aws-cdk-lib/aws-rds';
export class MccDBInfraStack extends Stack {
public readonly mccDB: rds.DatabaseCluster;
public readonly mccDBSecurityGroup: ec2.SecurityGroup;
credentials = rds.Credentials.fromGeneratedSecret('mccAdmin');
constructor(scope: Construct, id: string, buildConfig: BuildConfig, props: StackProps) {
super(scope, id, props);
const vpc = ec2.Vpc.fromLookup(
this,
`${buildConfig.Environment}-${buildConfig.ApplicationShortName}MccVPC`,
{
vpcId: buildConfig.Parameters.VPC,
}
);
const dbSecurityGroup = new ec2.SecurityGroup(
this,
`${buildConfig.Environment}${buildConfig.ApplicationShortName}MccDBSecurityGroup`,
{
vpc: vpc,
securityGroupName: `${buildConfig.Environment}-${buildConfig.ApplicationShortName}DBSecurityGroup`,
}
);
const mccDB = new rds.DatabaseCluster(this, `${buildConfig.Environment}${buildConfig.ApplicationShortName}MccDB`, {
engine: rds.DatabaseClusterEngine.auroraPostgres({
version: rds.AuroraPostgresEngineVersion.VER_17_4,
}) ,
vpc,
writer: ClusterInstance.serverlessV2('writer') ,
credentials: this.credentials,
defaultDatabaseName: 'mccdb',
securityGroups: [dbSecurityGroup],
serverlessV2MinCapacity: 0,
serverlessV2MaxCapacity: 64,
enableDataApi: true,
clusterIdentifier: `${buildConfig.Environment}-${buildConfig.ApplicationShortName}-mccdb`,
removalPolicy: RemovalPolicy.RETAIN
});
const lambdaSecurityGroup = new ec2.SecurityGroup(
this,
`${buildConfig.Environment}${buildConfig.ApplicationShortName}MccDBLambdaSecurityGroup`,
{
vpc: vpc,
securityGroupName: `${buildConfig.Environment}-${buildConfig.ApplicationShortName}DBLambdaSecurityGroup`,
}
);
dbSecurityGroup.addIngressRule(
lambdaSecurityGroup,
Port.tcp(mccDB.clusterEndpoint.port),
'Allow connection from Lambda Function to DB'
);
dbSecurityGroup.addIngressRule(
ec2.Peer.ipv4('10.0.0.0/8'),
Port.tcp(mccDB.clusterEndpoint.port),
'Allow connection from internal network'
);
this.mccDB = mccDB;
this.mccDBSecurityGroup = lambdaSecurityGroup;
}
}
+34
View File
@@ -0,0 +1,34 @@
{
"vpc-provider:account=030215556060:filter.vpc-id=vpc-02e23456a3e6064f1:region=eu-west-1:returnAsymmetricSubnets=true": {
"vpcId": "vpc-02e23456a3e6064f1",
"vpcCidrBlock": "10.157.7.0/24",
"ownerAccountId": "030215556060",
"availabilityZones": [],
"subnetGroups": [
{
"name": "Private",
"type": "Private",
"subnets": [
{
"subnetId": "subnet-014953bcd8175abe5",
"cidr": "10.157.7.0/25",
"availabilityZone": "eu-west-1a",
"routeTableId": "rtb-0e60c84be1611577e"
},
{
"subnetId": "subnet-0fb151177f9b7d430",
"cidr": "10.157.7.128/26",
"availabilityZone": "eu-west-1b",
"routeTableId": "rtb-09dc6ac1e26eb92ba"
},
{
"subnetId": "subnet-0696d516a7e905674",
"cidr": "10.157.7.192/26",
"availabilityZone": "eu-west-1c",
"routeTableId": "rtb-04cc2504f623b4d2e"
}
]
}
]
}
}
+110
View File
@@ -0,0 +1,110 @@
{
"app": "npx ts-node --prefer-ts-exts bin/infra.ts",
"watch": {
"include": [
"**"
],
"exclude": [
"README.md",
"cdk*.json",
"**/*.d.ts",
"**/*.js",
"tsconfig.json",
"package*.json",
"yarn.lock",
"node_modules",
"test"
]
},
"context": {
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": [
"aws",
"aws-cn"
],
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
"@aws-cdk/core:enablePartitionLiterals": true,
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
"@aws-cdk/aws-route53-patters:useCertificate": true,
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
"@aws-cdk/aws-redshift:columnId": true,
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
"@aws-cdk/aws-kms:aliasNameRef": true,
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
"@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false,
"@aws-cdk/aws-ecs:disableEcsImdsBlocking": true,
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
"@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true,
"@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true,
"@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
"@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
"@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true,
"@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true,
"@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true,
"@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true,
"@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true,
"@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true,
"@aws-cdk/core:enableAdditionalMetadataCollection": true,
"@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": false,
"@aws-cdk/aws-s3:setUniqueReplicationRoleName": true,
"@aws-cdk/aws-events:requireEventBusPolicySid": true,
"@aws-cdk/core:aspectPrioritiesMutating": true,
"@aws-cdk/aws-dynamodb:retainTableReplica": true,
"@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": true,
"@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": true,
"@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": true,
"@aws-cdk/aws-s3:publicAccessBlockedByDefault": true,
"@aws-cdk/aws-lambda:useCdkManagedLogGroup": true,
"dev": {
"Account": "030215556060",
"Region": "eu-west-1",
"ApplicationName": "Mold Cost Calculator",
"ApplicationShortName": "mld",
"Environment": "dev",
"Version": "1.0.0",
"Parameters": {
"VPC": "vpc-02e23456a3e6064f1",
"TenantID": "3bfeb222-e42c-4535-aace-ea6f7751369b",
"ClientID": "bc56643f-7002-4df9-ada5-1d79f55da6e3"
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
module.exports = {
testEnvironment: 'node',
roots: ['<rootDir>/test'],
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.tsx?$': 'ts-jest'
}
};
+4470
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
{
"name": "infra",
"version": "0.1.0",
"bin": {
"infra": "bin/infra.js"
},
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"test": "jest",
"cdk": "cdk"
},
"devDependencies": {
"@types/jest": "^29.5.14",
"@types/node": "22.7.9",
"aws-cdk": "2.1019.1",
"jest": "^29.7.0",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "~5.6.3"
},
"dependencies": {
"@aws-cdk/aws-lambda-python-alpha": "^2.202.0-alpha.0",
"aws-cdk-lib": "2.202.0",
"constructs": "^10.0.0"
}
}
+31
View File
@@ -0,0 +1,31 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": [
"es2022"
],
"declaration": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": false,
"inlineSourceMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"typeRoots": [
"./node_modules/@types"
]
},
"exclude": [
"node_modules",
"cdk.out"
]
}
+64
View File
@@ -0,0 +1,64 @@
import * as cdk from 'aws-cdk-lib';
interface BuildParameters {
readonly VPC: string;
readonly TenantID: string;
readonly ClientID: string;
}
interface BuildConfig {
readonly Account: string;
readonly Region: string;
readonly ApplicationName: string;
readonly ApplicationShortName: string;
readonly Environment: string;
readonly Version: string;
readonly Parameters: BuildParameters;
}
const getValidStringValueOfKey = (buildConfig: any, key: string): string => {
if (!buildConfig[key] || buildConfig[key].trim().length === 0) {
throw new Error(key + ' does not exist or is empty in build config');
}
return buildConfig[key];
};
const getValidNumberValueOfKey = (buildConfig: any, key: string): number => {
if (!buildConfig[key]) {
throw new Error(key + ' does not exist or is empty in build config');
}
return buildConfig[key];
};
const getConfig = (app: cdk.App): BuildConfig => {
const env = app.node.tryGetContext('config');
if (!env) {
throw new Error('Context variable missing on CDK command. Pass in as "-c config=env"');
}
const buildConfig: any = app.node.tryGetContext(env);
if (!buildConfig) {
throw new Error('Wrong Context variable passed on CDK command. Pass in as "-c config=env"');
}
const validBuildConfig: BuildConfig = {
Account: getValidStringValueOfKey(buildConfig, 'Account'),
Region: getValidStringValueOfKey(buildConfig, 'Region'),
Version: getValidStringValueOfKey(buildConfig, 'Version'),
ApplicationName: getValidStringValueOfKey(buildConfig, 'ApplicationName'),
ApplicationShortName: getValidStringValueOfKey(buildConfig, 'ApplicationShortName'),
Environment: getValidStringValueOfKey(buildConfig, 'Environment'),
Parameters: {
VPC: getValidStringValueOfKey(buildConfig['Parameters'], 'VPC'),
TenantID: getValidStringValueOfKey(buildConfig['Parameters'], 'TenantID'),
ClientID: getValidStringValueOfKey(buildConfig['Parameters'], 'ClientID')
},
};
return validBuildConfig;
};
export { BuildConfig, BuildParameters, getConfig };
+13 -10
View File
@@ -35,15 +35,17 @@ def create_app(config_class=None):
if config_class is None:
config_class = get_config()
app.config.from_object(config_class)
# Ensure SECRET_KEY is set from environment if not present
if not hasattr(config_class, 'SECRET_KEY') or not config_class.SECRET_KEY:
env_secret = os.environ.get('SECRET_KEY')
if not env_secret:
raise ValueError("SECRET_KEY environment variable is not set")
config_class.SECRET_KEY = env_secret
app.config['SECRET_KEY'] = config_class.SECRET_KEY.encode('utf-8')
# Initialize configuration
config_class.init_app(app)
# Ensure secret key is properly set
if not app.config.get('SECRET_KEY'):
raise ValueError("SECRET_KEY must be set in the configuration")
app.config['SECRET_KEY'] = app.config['SECRET_KEY'].encode('utf-8')
# Ensure instance folder exists
try:
os.makedirs(app.instance_path)
@@ -51,7 +53,8 @@ def create_app(config_class=None):
pass
# Configure logging
configure_logging(app)
if os.environ.get('HOST_ENV') != 'AWS':
configure_logging(app)
# Initialize extensions with retry logic
max_retries = 3
@@ -117,8 +120,8 @@ def create_app(config_class=None):
handler.flush()
# Debug: write all registered endpoints to a file
with open('logs/endpoints.log', 'w') as f:
for rule in app.url_map.iter_rules():
f.write(f"{rule.endpoint} {rule.methods} {rule.rule}\n")
# with open('logs/endpoints.log', 'w') as f:
# for rule in app.url_map.iter_rules():
# f.write(f"{rule.endpoint} {rule.methods} {rule.rule}\n")
return app
+9 -8
View File
@@ -164,14 +164,15 @@ class Config:
app.config['SQLALCHEMY_DATABASE_URI'] = cls.SQLALCHEMY_DATABASE_URI
# Configure logging
if not os.path.exists('logs'):
os.mkdir('logs')
file_handler = RotatingFileHandler('logs/app.log',
maxBytes=10240,
backupCount=10)
file_handler.setFormatter(logging.Formatter(cls.LOG_FORMAT))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
if os.environ.get('HOST_ENV') != 'AWS':
if not os.path.exists('logs'):
os.mkdir('logs')
file_handler = RotatingFileHandler('logs/app.log',
maxBytes=10240,
backupCount=10)
file_handler.setFormatter(logging.Formatter(cls.LOG_FORMAT))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('Application startup')
Binary file not shown.
+11 -1
View File
@@ -5,6 +5,8 @@ import os
import logging
from logging.handlers import RotatingFileHandler
from flask import render_template
from flask_migrate import upgrade
import awsgi
app = create_app()
@@ -39,4 +41,12 @@ if not app.debug:
app.logger.info('Mold Cost Calculator startup')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5004)
app.run(host='0.0.0.0', port=5004)
def lambda_handler(event, context):
return awsgi.response(app, event, context)
def lambda_handler_upgrade_db(event, context):
with app.app_context():
upgrade()
return {"statusCode": 200, "body": "Database upgraded successfully"}