Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CMDCT-4184 cdk-side prepping cdk to import #15044

Merged
merged 15 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:
SKIP_PREFLIGHT_CHECK: true
- id: endpoint
run: |
APPLICATION_ENDPOINT=$(./output.sh ui ApplicationEndpointUrl $STAGE_PREFIX$branch_name)
APPLICATION_ENDPOINT=$(./output.sh seds-$STAGE_PREFIX$branch_name CloudFrontUrl)
echo "application_endpoint=$APPLICATION_ENDPOINT" >> $GITHUB_OUTPUT
echo "## Application Endpoint" >> $GITHUB_STEP_SUMMARY
echo "<$APPLICATION_ENDPOINT>" >> $GITHUB_STEP_SUMMARY
Expand Down
83 changes: 44 additions & 39 deletions deployment/app.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,62 @@
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { EmptyParentStack } from "./stacks/empty/parent";
import { ImportsIncludedParentStack} from "./stacks/imports_included/parent";
import { ParentStack } from "./stacks/parent";
import { determineDeploymentConfig } from "./deployment-config";
import { getSecret } from "./utils/secrets-manager";
import { getDeploymentConfigParameters } from "./utils/systems-manager";

async function main() {
try {
const app = new cdk.App({
defaultStackSynthesizer: new cdk.DefaultStackSynthesizer(
JSON.parse((await getSecret("cdkSynthesizerConfig"))!)
),
});
const app = new cdk.App({
defaultStackSynthesizer: new cdk.DefaultStackSynthesizer(
JSON.parse((await getSecret("cdkSynthesizerConfig"))!)
),
});

const stage = app.node.getContext("stage");
const config = await determineDeploymentConfig(stage);
const stage = app.node.getContext("stage");
const config = await determineDeploymentConfig(stage);

const parametersToFetch = {
cloudfrontCertificateArn: {
name: "cloudfront/certificateArn",
useDefault: true,
},
cloudfrontDomainName: {
name: "cloudfront/domainName",
useDefault: false,
},
vpnIpSetArn: { name: "vpnIpSetArn", useDefault: true },
vpnIpv6SetArn: { name: "vpnIpv6SetArn", useDefault: true },
hostedZoneId: { name: "route53/hostedZoneId", useDefault: true },
domainName: { name: "route53/domainName", useDefault: true },
};
const parametersToFetch = {
cloudfrontCertificateArn: {
name: "cloudfront/certificateArn",
useDefault: true,
},
cloudfrontDomainName: {
name: "cloudfront/domainName",
useDefault: false,
},
vpnIpSetArn: { name: "vpnIpSetArn", useDefault: true },
vpnIpv6SetArn: { name: "vpnIpv6SetArn", useDefault: true },
hostedZoneId: { name: "route53/hostedZoneId", useDefault: true },
domainName: { name: "route53/domainName", useDefault: true },
};

const deploymentConfigParameters = await getDeploymentConfigParameters(
parametersToFetch,
stage
);
const deploymentConfigParameters = await getDeploymentConfigParameters(
parametersToFetch,
stage
);

cdk.Tags.of(app).add("STAGE", stage);
cdk.Tags.of(app).add("PROJECT", config.project);
cdk.Tags.of(app).add("STAGE", stage);
cdk.Tags.of(app).add("PROJECT", config.project);

new ParentStack(app, `${config.project}-${stage}`, {
...config,
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
deploymentConfigParameters,
});
} catch (error) {
console.error("Error:", error);
process.exit(1);
let correctParentStack;
if (process.env.IMPORT_VARIANT == "empty") {
correctParentStack = EmptyParentStack
} else if (process.env.IMPORT_VARIANT == "imports_included") {
correctParentStack = ImportsIncludedParentStack
} else {
correctParentStack = ParentStack
}
new correctParentStack(app, `${config.project}-${stage}`, {
...config,
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
deploymentConfigParameters,
});
}

main();
119 changes: 119 additions & 0 deletions deployment/import_instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Import Instructions

## From `pete-sls` branch:

1. Deploy sls to get it ready for deletion with retained resources configured for import

```
./run deploy --stage <YOUR_BRANCH_NAME>
```

2. Collect information about the resources we're going to be importing into the new cdk stack.
```
cloudfront.Distribution -
cognito.UserPool -
```

3. Destroy sls

```
./run destroy --stage <YOUR_BRANCH_NAME>
```

:warning: Make sure that all sls associated stacks have been fully destroyed before proceeding.


## From `jon-cdk` branch:


1. Create just the new cdk stack without anything inside of it.

```bash
IMPORT_VARIANT=empty ./run deploy --stage <YOUR_BRANCH_NAME>
```

2. Now import all the serverless ejected resources.

```bash
IMPORT_VARIANT=imports_included PROJECT=seds cdk import --context stage=<YOUR_BRANCH_NAME> --force
```
As this import occurs you'll have to provide the information you gathered just before destroying the serverless stacks. For the dynamo tables the default should be correct but read closely to be sure.

3. Run a deploy on that same imported resource set.

```bash
IMPORT_VARIANT=imports_included ./run deploy --stage <YOUR_BRANCH_NAME>
```

4. Run a full deploy by kicking off the full cdk deploy.

```bash
./run deploy --stage <YOUR_BRANCH_NAME>
```


5. Find the Cloudfront Url in the Github Action's logs (or in the outputs section of your Cloudformation Stack). Visit the site and confirm that you can login and use the application. :tada: Congrats, you did it!


## What if it all goes pear shaped?

### If during the middle of the migration, things begin to break and we need to reinstate the serverless stack, we need a way to bring the Cloudfront Distribution back into the newly rebuilt serverless stack. Fortunately this is possible if you follow these steps.

:grey_exclamation: These instructions are specific to reimporting a Cloudfront Distribution but the same pattern should also apply to any other imported resources that need un-importing should the need arise.

1) Get the Cloudfront Distribution unaffiliated with any Cloudformation stack. If it's already been successfully imported into the new cdk stack then you'll need to destroy the cdk stack to eject it from that stack.
```
# this assumes you're on `jon-cdk` branch
./run destroy --stage <YOUR_BRANCH_NAME>
```

2) Now you need switch to `pete-sls` branch and comment out any CloudfrontDistribution and dependent configuration.
These are the necessary changes: https://github.com/Enterprise-CMCS/macpro-mdct-seds/commit/8eb551f980a37355729dc1795f5d229987699c84

3) Deploy serverless stack (without CloudfrontDistribution) via Github Action (necessary because of permissions limitations) by pushing up changes made in the last step.

4) Now you need to import the CloudfrontDistribution to the existing ui-XXXXX stack created by the last step. First you'll need to get the existing stack's template and save it to a local file.
```
aws cloudformation get-template --stack-name ui-cmdct-4188-sls | jq '.TemplateBody' > deployment/cfn_template.json
```

5) Now open up the file you just created (deployment/cfn_template.json) and add the following Cloudfront Distribution config to it's resources section:
```json
"CloudFrontDistribution": {
"Type": "AWS::CloudFront::Distribution",
"DeletionPolicy": "Retain",
"Properties": {
"DistributionConfig": {
"CustomOrigin": {
"DNSName": "www.example.com",
"OriginProtocolPolicy": "http-only",
"OriginSSLProtocols": [
"TLSv1"
]
},
"Enabled": true,
"DefaultCacheBehavior": {
"CachePolicyId": "Managed-CachingDisabled",
"TargetOriginId": "some_target_origin_id",
"ViewerProtocolPolicy": "allow-all"
}
}
}
},
```

6) Now open up the AWS console and navigate to the Cloudformation console's show page for the particular ui-XXXXX stack.

7) Import the stack by doing the following:
- Under `Stack Actions` select `Import resources into stack`
- In the `Specify template` section choose upload a template file and upload the one we just created: `deployment/cfn_template.json`
- In the `Identify resources` section you'll have to provide the ID of the incoming Cloudfront Distribution
- Next and confirm until it begins the import

8) Once the import is complete, take a breath.

9) Revert the changes where you commented out the Serverless definition of Cloudfront Distribution (undo step 2).

10) Verify that the Serverless definition now contains the Cloudfront Distribution then deploy via Github Action.

11) Verify that Cloudfront Distribution is back into the stack and appropriately pointing at the application.
10 changes: 1 addition & 9 deletions deployment/stacks/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export function createApiComponents(props: CreateApiComponentsProps) {
BOOTSTRAP_BROKER_STRING_TLS: brokerString,
stage,
...Object.fromEntries(
tables.map((table) => [`${table.id}Name`, table.name])
tables.map((table) => [`${table.id}TableName`, table.name])
),
};

Expand Down Expand Up @@ -218,14 +218,6 @@ export function createApiComponents(props: CreateApiComponentsProps) {
tables: dataConnectTables,
});

new Lambda(scope, "exportToExcel", {
entry: "services/app-api/export/exportToExcel.js",
handler: "main",
path: "/export/export-to-excel",
method: "POST",
...commonProps,
});

new Lambda(scope, "getUserById", {
entry: "services/app-api/handlers/users/get/getUserById.js",
handler: "main",
Expand Down
35 changes: 19 additions & 16 deletions deployment/stacks/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,26 +88,29 @@ export function createDataComponents(props: CreateDataComponentsProps) {
"service-role/AWSLambdaVPCAccessExecutionRole"
),
],
inlinePolicies: {
DynamoPolicy: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
"dynamodb:DescribeTable",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
],
resources: ["*"],
})
]
})
},
permissionsBoundary: props.iamPermissionsBoundary,
path: props.iamPath,
});

lambdaApiRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
"dynamodb:DescribeTable",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
],
resources: ["*"],
})
);

// TODO: test deploy and watch performance with this using lambda.Function vs lambda_nodejs.NodejsFunction
const seedDataFunction = new lambda_nodejs.NodejsFunction(scope, "seedData", {
entry: "services/database/handlers/seed/seed.js",
Expand Down
16 changes: 16 additions & 0 deletions deployment/stacks/empty/parent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Construct } from "constructs";
import {
Stack,
StackProps,
} from "aws-cdk-lib";
import { DeploymentConfigProperties } from "../../deployment-config";

export class EmptyParentStack extends Stack {
constructor(
scope: Construct,
id: string,
props: StackProps & DeploymentConfigProperties
) {
super(scope, id, props);
}
}
69 changes: 69 additions & 0 deletions deployment/stacks/imports_included/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Construct } from "constructs";
import { aws_dynamodb as dynamodb } from "aws-cdk-lib";
import { DynamoDBTable } from "../../constructs/dynamodb-table";

interface CreateDataComponentsProps {
scope: Construct;
stage: string;
isDev: boolean;
}

export function createDataComponents(props: CreateDataComponentsProps) {
const { scope, stage, isDev } = props;

new DynamoDBTable(scope, "FormAnswers", {
stage,
isDev,
name: "form-answers",
partitionKey: {
name: "answer_entry",
type: dynamodb.AttributeType.STRING,
},
gsi: {
indexName: "state-form-index",
partitionKey: {
name: "state_form",
type: dynamodb.AttributeType.STRING,
},
},
})
new DynamoDBTable(scope, "FormQuestions", {
stage,
isDev,
name: "form-questions",
partitionKey: { name: "question", type: dynamodb.AttributeType.STRING },
})
new DynamoDBTable(scope, "FormTemplates", {
stage,
isDev,
name: "form-templates",
partitionKey: { name: "year", type: dynamodb.AttributeType.NUMBER },
})
new DynamoDBTable(scope, "Forms", {
stage,
isDev,
name: "forms",
partitionKey: { name: "form", type: dynamodb.AttributeType.STRING },
})
new DynamoDBTable(scope, "StateForms", {
stage,
isDev,
name: "state-forms",
partitionKey: {
name: "state_form",
type: dynamodb.AttributeType.STRING,
},
})
new DynamoDBTable(scope, "States", {
stage,
isDev,
name: "states",
partitionKey: { name: "state_id", type: dynamodb.AttributeType.STRING },
})
new DynamoDBTable(scope, "AuthUser", {
stage,
isDev,
name: "auth-user",
partitionKey: { name: "userId", type: dynamodb.AttributeType.STRING },
})
}
Loading