Skip to content

Commit 9da71f4

Browse files
author
prorhap
committed
Initial commit for appconfig code recipe
1 parent 2337656 commit 9da71f4

File tree

10 files changed

+4480
-0
lines changed

10 files changed

+4480
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.ts
2+
!*.d.ts
3+
4+
# CDK asset staging directory
5+
.cdk.staging
6+
cdk.out

devax/appconfig-featureflag/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Code Recipe for AWS AppConfig
2+
3+
AWS AppConfig is a tool that enables safe and dynamic application configuration management. This service offers a significant advantage by allowing you to adjust application behavior in real time without redeploying code. In particular, the Feature Flag functionality is highly useful for deploying new features or controlling feature usage on a per-user basis.
4+
5+
With Feature Flags, for example, you can release a new feature to specific user groups (e.g., beta users) or enable the feature for a small percentage of total traffic to test its performance. Additionally, if an issue arises, the feature can be immediately disabled to minimize risk, allowing for rapid response.
6+
7+
AppConfig integrates with various environments, such as AWS Lambda, EC2, and container-based applications. It also provides SDKs and APIs to directly manage configuration values within your code. This makes it suitable for scenarios like feature releases, A/B testing, environment-specific configuration management, and urgent feature rollbacks.
8+
9+
When deploying this project, an AppConfig resource is created to define release flags and operational flags. Additionally, you can find code examples of a Lambda function utilizing these Feature Flags.
10+
11+
12+
# How to deploy
13+
1. Clone the repository
14+
```
15+
git clone https://github.com/aws-samples/aws-kr-startup-samples.git
16+
cd devax/appconfig-featureflag
17+
```
18+
19+
2. CDK Bootstrapping
20+
```
21+
cdk bootstrap
22+
```
23+
3. Deploy
24+
25+
```
26+
cdk deploy
27+
```

devax/appconfig-featureflag/cdk.json

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
{
2+
"app": "npx ts-node --prefer-ts-exts bin/backend.ts",
3+
"watch": {
4+
"include": [
5+
"**"
6+
],
7+
"exclude": [
8+
"README.md",
9+
"cdk*.json",
10+
"**/*.d.ts",
11+
"**/*.js",
12+
"tsconfig.json",
13+
"package*.json",
14+
"yarn.lock",
15+
"node_modules",
16+
"test"
17+
]
18+
},
19+
"context": {
20+
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
21+
"@aws-cdk/core:checkSecretUsage": true,
22+
"@aws-cdk/core:target-partitions": [
23+
"aws",
24+
"aws-cn"
25+
],
26+
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
27+
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
28+
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
29+
"@aws-cdk/aws-iam:minimizePolicies": true,
30+
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
31+
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
32+
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
33+
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
34+
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
35+
"@aws-cdk/core:enablePartitionLiterals": true,
36+
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
37+
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
38+
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
39+
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
40+
"@aws-cdk/aws-route53-patters:useCertificate": true,
41+
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
42+
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
43+
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
44+
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
45+
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
46+
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
47+
"@aws-cdk/aws-redshift:columnId": true,
48+
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
49+
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
50+
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
51+
"@aws-cdk/aws-kms:aliasNameRef": true,
52+
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
53+
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
54+
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
55+
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
56+
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
57+
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
58+
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
59+
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
60+
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
61+
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
62+
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
63+
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
64+
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
65+
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
66+
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
67+
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
68+
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
69+
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
70+
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
71+
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
72+
"@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true,
73+
"@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true,
74+
"@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
75+
"@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
76+
"@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
77+
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true,
78+
"@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true
79+
}
80+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"flags": {
3+
"pagenation": {
4+
"name": "pagenation"
5+
},
6+
"showcoursel": {
7+
"name": "showcoursel"
8+
}
9+
},
10+
"values": {
11+
"pagenation": {
12+
"enabled": true,
13+
"attributeValues": {
14+
"number": 10
15+
}
16+
},
17+
"showcoursel": {
18+
"enabled": false
19+
}
20+
},
21+
"version": "1"
22+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module.exports = {
2+
testEnvironment: 'node',
3+
roots: ['<rootDir>/test'],
4+
testMatch: ['**/*.test.ts'],
5+
transform: {
6+
'^.+\\.tsx?$': 'ts-jest'
7+
}
8+
};
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { createRequire } from 'module';
2+
const require = createRequire(import.meta.url);
3+
4+
const http = require('http');
5+
const { DynamoDB } = require("@aws-sdk/client-dynamodb");
6+
const { ScanCommand } = require("@aws-sdk/lib-dynamodb");
7+
const { DynamoDBDocument } = require("@aws-sdk/lib-dynamodb");
8+
9+
const client = new DynamoDB({});
10+
const docClient = DynamoDBDocument.from(client);
11+
export const handler = async (event) => {
12+
//promise to fetch AppConfig data
13+
const res = await new Promise((resolve, reject) => {
14+
http.get(
15+
"http://localhost:2772/applications/AWSomeCribRentals/environments/Beta/configurations/CardFeatureFlag",
16+
resolve
17+
);
18+
});
19+
//get data from AppConfig
20+
let configData = await new Promise((resolve, reject) => {
21+
let data = '';
22+
res.on('data', chunk => data += chunk);
23+
res.on('error', err => reject(err));
24+
res.on('end', () => resolve(data));
25+
});
26+
//parse that data as JSON
27+
const parsedConfigData = JSON.parse(configData);
28+
const DynamoParams = {
29+
TableName: 'AWSCribsRentalMansions'
30+
};
31+
//fetch data from Dynamo DB
32+
async function listItems() {
33+
try {
34+
const data = await docClient.send(new ScanCommand(DynamoParams));
35+
return data;
36+
} catch (err) {
37+
console.log(err);
38+
return err;
39+
}
40+
}
41+
const data = await listItems();
42+
43+
//figure out how long the loop will be based on whether or not pagination is enabled
44+
let runUntilVar = 0;
45+
46+
if (parsedConfigData.pagination.enabled == true) {
47+
runUntilVar = parsedConfigData.pagination.number;
48+
}
49+
else {
50+
runUntilVar = data.Items.length;
51+
}
52+
53+
//create carousel layout if showcarousel is enabled and static image if not
54+
if (parsedConfigData.showcarousel.enabled == true) {
55+
let returnhtml = ``;
56+
try {
57+
for (let i = 0; i < runUntilVar; i++) {
58+
returnhtml += `<div class="col-md-4 mt-4">
59+
<div class="card profile-card-5">
60+
<div class="card-img-block">
61+
<div class="slideshow-container">`;
62+
for (let j = 0; j < data.Items[i].Image.length; j++) {
63+
returnhtml += `<div class="mySlides` + (i + 1) + `">
64+
<img class="card-img-top" style="height: 300px;" src="` + data.Items[i].Image[j].name + `" style="width:100%">
65+
</div>`;
66+
}
67+
returnhtml += `</div>`;
68+
69+
if (data.Items[i].Image.length > 1) {
70+
returnhtml += `<a class="prev" onclick="plusSlides(-1, ` + i + `)">&#10094;</a>
71+
<a class="next" onclick="plusSlides(1, ` + i + `)">&#10095;</a>`;
72+
}
73+
74+
returnhtml += `</div>
75+
<div class="card-body pt-0">
76+
<h5 class="card-title">` + data.Items[i].Name + ` <span style="font-size: 0.7em;color:rgb(255, 64, 64)">(` + data.Items[i].Location + `)</span></h5>
77+
<p class="card-text">` + data.Items[i].Description + `</p>
78+
<a class="btn btn-primary"style="display: inline" href="#">Check Availability</a>
79+
<span style="float: right;cursor: pointer;" onclick="favoriteStar(this)"><span class="fa fa-star"></span></span>
80+
</div>
81+
</div>
82+
</div>`;
83+
}
84+
return {
85+
statusCode: 200,
86+
body: returnhtml,
87+
};
88+
} catch (err) {
89+
console.log(err);
90+
return {
91+
error: err
92+
}
93+
}
94+
} else {
95+
let returnhtml = ``;
96+
try {
97+
for (let i = 0; i < runUntilVar; i++) {
98+
returnhtml += `<div class="col-md-4 mt-4">
99+
<div class="card profile-card-5">
100+
<div class="card-img-block">
101+
<img class="card-img-top" style="height: 300px;" src="` + data.Items[i].Image[0].name + `" style="width:100%"
102+
alt="Card image cap" style="height: 300px;">
103+
</div>
104+
<div class="card-body pt-0">
105+
<h5 class="card-title">` + data.Items[i].Name + ` <span style="font-size: 0.7em;color:rgb(255, 64, 64)">(` + data.Items[i].Location + `)</span></h5>
106+
<p class="card-text">` + data.Items[i].Description + `</p>
107+
<a class="btn btn-primary"style="display: inline" href="#">Check Availability</a>
108+
<span style="float: right;cursor: pointer;" onclick="favoriteStar(this)"><span class="fa fa-star"></span></span>
109+
</div>
110+
</div>
111+
</div>`;
112+
}
113+
return {
114+
statusCode: 200,
115+
body: returnhtml,
116+
};
117+
} catch (err) {
118+
console.log(err);
119+
return {
120+
error: err
121+
};
122+
}
123+
}
124+
};

0 commit comments

Comments
 (0)