-
Notifications
You must be signed in to change notification settings - Fork 5
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
Best approach to modify CDK to use additional services #481
Comments
TIL you can import cloudformation into CDK, seems like this may be my best bet as I'm far more familiar with CDK. Would still love to hear others opinions! |
Importing the cloudformation CDK does work, though because the names of the constructs like S3 buckets and etc change everytime you'll have to play with regex if you need to make any modifications to them e.g assign roles. It honestly may be easier to fork this repo and make changes to the original cdk in Here is an example if anyone decides to go down this route:
|
Ultimately ended up just copying the cdk from the adapter and modifying it. The above method seems nice but is too much of a headache, likely caused some of the issues I experienced in #485 |
It is believed that only manual resource creation and integration is being used at the moment. // svelte.config.js
{
// ...
adapter: adapter({
// ...
architecture: 'lambda-s3',
extends: (lambda, s3, cloudfront) => {
new aws.cognito.UserPoolClient(this, 'UserPoolClient', {
userPool: 'UserPool',
generateSecret: false,
userPoolClientName: 'UserPoolClient'
// ...
})
// ...
}
})
} Naturally, they should be separated into separate files. // svelte.config.js
import { extendsCDK } from './extendsCDK.js'
{
// ...
adapter: adapter({
// ...
architecture: 'lambda-s3',
extends: extendsCDK
})
} // extendsCDK.ts
import type { ExtendsLambdaS3 } from '@jill64/sveltekit-adapter-aws/types'
export const extendsCDK: ExtendsLambdaS3 = (lambda, s3, cloudfront) => {
new aws.cognito.UserPoolClient(this, 'UserPoolClient', {
userPool: 'UserPool',
generateSecret: false,
userPoolClientName: 'UserPoolClient'
// ...
})
// ...
} I have considered merging CloudFormation templates, but it seems a bit too challenging for me. |
I think the above would work well, especially for simple stacks. The main issue I ran into (which I believe this method will run into aswell) is that it seems to be far more difficult to make modifications to a stack/cloud formation template, from another stack. I had to make changes to the constructs in the generated stack and trying to do that from another stack, was annoying and ultimately didn't work. I'm not a pro but that is my experience from my attempt above. For advanced stacks If I was able to modify the generated stack and it not be overwritten every time I run I'll upload the two CDK's I made as an example, both should of done basically the same thing (sorry for the messy code, I wasn't planning to post this publicly anytime soon) Attempt 1 - Importing a cloudformation templateI believe if you go the route of using 2 stacks you'll ultimately end up importing two CDK's or cloud formation templates. Interactions between them is difficult (from my limited experience) import * as cdk from 'aws-cdk-lib';
import * as cfninc from 'aws-cdk-lib/cloudformation-include';
import * as lambda from "aws-cdk-lib/aws-lambda";
import { Construct } from 'constructs';
import {
PolicyStatement,
Effect,
Role,
ServicePrincipal,
} from "aws-cdk-lib/aws-iam";
import * as cognito from 'aws-cdk-lib/aws-cognito';
import * as cr from 'aws-cdk-lib/custom-resources';
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
import * as path from 'path';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as fs from 'fs';
import * as cd from 'aws-cdk-lib/aws-cloudfront';
export class SveleteStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, {...props,analyticsReporting: false} );
const filePath = path.resolve(__dirname, '../../build/cdk.out/faulty-bedrock.template.json');
//Get template from Svelete build
const template = new cfninc.CfnInclude(this, 'Template', {
templateFile: filePath,
});
const mySecret = new secretsmanager.Secret(this, 'MySecret', {
secretName: 'AUTH_SECRET',
generateSecretString: {
passwordLength: 32,
excludePunctuation: true,
},
});
const [lambdaName, s3BucketName, cloudDistributionName, cdkDeploymentaName] = getCFResources();
//get CustomCDKBucketDeployment lambda that was genereated by Svelete
const cfnCdkLambda = template.getResource(cdkDeploymentaName) as lambda.CfnFunction;
const cdklambdaFunc = lambda.Function.fromFunctionName(this, cdkDeploymentaName, cfnCdkLambda.ref);
//get server lambda that was genereated by Svelete
const cfnLambda = template.getResource(lambdaName) as lambda.CfnFunction;
const lambdaFunc = lambda.Function.fromFunctionName(this, lambdaName, cfnLambda.ref);
cfnLambda.addDependency(cfnCdkLambda);
//get S3 that was genereated by Svelete
const cfnBucket = template.getResource(s3BucketName) as s3.CfnBucket;
const bucket = s3.Bucket.fromBucketName(this, s3BucketName, cfnBucket.ref);
cfnBucket.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY);
//get S3 that was genereated by Svelete
const cfnCloudDistribution = template.getResource(cloudDistributionName) as cd.CfnDistribution;
const callBackURL:string = "https://" + cfnCloudDistribution.attrDomainName + "/api/auth/callback/cognito"
console.log("call back URL:", callBackURL);
const userpool = new cognito.UserPool(this, 'user-pool', {
signInAliases: {
email: true,
},
selfSignUpEnabled: true,
standardAttributes: {
familyName: {
mutable: false,
required: false,
},
address: {
mutable: true,
required: false,
},
},
customAttributes: {
'createdAt': new cognito.DateTimeAttribute(),
'isAdmin': new cognito.BooleanAttribute({
mutable: false,
}),
},
passwordPolicy: {
minLength: 8,
requireLowercase: true,
requireUppercase: true,
requireDigits: true,
requireSymbols: false,
},
accountRecovery: cognito.AccountRecovery.EMAIL_ONLY,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
const appClient = userpool.addClient('app-client', {
userPoolClientName: 'app-client',
authFlows: {
userPassword: true,
},
generateSecret: true,
oAuth: {
callbackUrls: [
callBackURL,
],
},
});
//Cognito policy
const cognitoPolicy = new PolicyStatement({
effect: Effect.ALLOW,
actions: ["cognito-idp:SignUp", "cognito-idp:InitiateAuth"], // See: https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazoncognitouserpools.html
//resources: ["arn:aws:cognito-idp:*::userpool/YOUR_USER_POOL_ID"],
resources: ["*"],
});
//create bedrock policy
const bedrockAccessPolicy = new PolicyStatement({
effect: Effect.ALLOW,
actions: ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"], // See: https://docs.aws.amazon.com/ja_jp/service-authorization/latest/reference/list_amazonbedrock.html
resources: ["arn:aws:bedrock:*::foundation-model/anthropic.claude-v2"],
});
//Add these policies to the role used by that lambda
lambdaFunc.addToRolePolicy(bedrockAccessPolicy);
lambdaFunc.addToRolePolicy(cognitoPolicy);
// Grant read permissions to the Lambda function's execution role
lambdaFunc.addToRolePolicy(new PolicyStatement({
actions: ['s3:GetObject', "s3:ListBucket"],
resources: ["*"],
}));
console.log("bucket arn:", bucket.bucketArn);
// Define the Lambda function to update environment variables
const updateEnvVarsLambda = new lambda.Function(this, 'UpdateEnvVarsLambda', {
runtime: lambda.Runtime.NODEJS_18_X,
handler: 'index.handler',
code: lambda.Code.fromInline('exports.handler = async (event) => {}'),
});
// Grant necessary permissions to the updateEnvVarsLambda function
updateEnvVarsLambda.addToRolePolicy(new PolicyStatement({
actions: ['lambda:UpdateFunctionConfiguration'],
resources: ['*'], // Replace with the ARN of the target Lambda function if more restrictive permissions are desired
}));
// Grant read permissions to the Lambda function's execution role
updateEnvVarsLambda.addToRolePolicy(new PolicyStatement({
actions: ['s3:GetObject'],
resources: [bucket.arnForObjects('*')],
}));
console.log("bucket arn:", bucket.bucketArn);
// Define the CloudFormation custom resource to trigger the Lambda function
const updateEnvVars = new cr.AwsCustomResource(this, 'UpdateEnvVarsCustomResource', {
onCreate: {
service: 'Lambda',
action: 'updateFunctionConfiguration',
parameters: {
FunctionName: lambdaFunc.functionName, // Replace with the name of the existing Lambda function
Environment: {
Variables: {
COGNITO_USER_POOL_ID: userpool.userPoolId,
COGNITO_CLIENT_ID: appClient.userPoolClientId,
COGNITO_CLIENT_SECRET: appClient.userPoolClientSecret.unsafeUnwrap(),
COGNITO_ISSUER: "https://cognito-idp." + this.region + ".amazonaws.com/" + userpool.userPoolId,
AUTH_TRUST_HOST: "true",
AUTH_SECRET: mySecret.secretValue.unsafeUnwrap() //should use secrets manager TODO
},
},
},
physicalResourceId: cr.PhysicalResourceId.of(Date.now().toString()),
},
onUpdate: {
service: 'Lambda',
action: 'updateFunctionConfiguration',
parameters: {
FunctionName: lambdaFunc.functionName, // Replace with the name of the existing Lambda function
Environment: {
Variables: {
COGNITO_USER_POOL_ID: userpool.userPoolId,
COGNITO_CLIENT_ID: appClient.userPoolClientId,
COGNITO_CLIENT_SECRET: appClient.userPoolClientSecret.unsafeUnwrap(), //TODO: Bad practice, ctf app so not critical
COGNITO_ISSUER: "https://cognito-idp." + this.region + ".amazonaws.com/" + userpool.userPoolId,
AUTH_TRUST_HOST: "true",
//AUTH_SECRET: mySecret.secretValue.unsafeUnwrap(), //TODO: Bad practice, ctf app so not critical
AUTH_SECRET:"efb724599037a553a63ccf4aa24ce4f847b4eddff8ecf99da641379fcd924c36"
},
},
},
physicalResourceId: cr.PhysicalResourceId.of(Date.now().toString()),
},
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({ resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE }),
});
updateEnvVars.node.addDependency(cfnCdkLambda);
// Loop through all constructs in the stack
template.node.findAll().forEach(construct => {
if (construct.node.id !== cdkDeploymentaName){
construct.node.addDependency(cdklambdaFunc);
}
});
this.node.findAll().forEach(construct => {
if (construct.node.id !== cdkDeploymentaName){
construct.node.addDependency(cdklambdaFunc);
}
});
}
}
// Function to find the first resource name that matches the specified criteria
function findResourceName(regex: RegExp, template: string, namePrefix: string): string{
const match: RegExpMatchArray | null = template.match(regex);
if (match) {
return match[1].toString();
}
throw new Error(`Resource starting with prefix '${namePrefix}' not found`);
}
function getCFResources():[string, string, string, string] {
// Load CloudFormation template file
const filePath = path.resolve(__dirname, '../../build/cdk.out/faulty-bedrock.template.json');
const template: string = fs.readFileSync(filePath, 'utf-8');
// Regular expressions for matching Lambda function name and S3 bucket name
const lambdaRegex: RegExp = /"(Server[\w\d]+)":\s*{\s*"Type"\s*:\s*"AWS::Lambda::Function"/i;
const s3Regex: RegExp = /"(Bucket[\w\d]+)":\s*{\s*"Type"\s*:\s*"AWS::S3::Bucket"/i;
const cdRegex: RegExp = /"(CloudFront[\w\d]+)":\s*{\s*"Type"\s*:\s*"AWS::CloudFront::Distribution"/i;
const cdkDeploymentRex: RegExp = /"(CustomCDKBucketDeployment[\w\d]+)":\s*{\s*"Type"\s*:\s*"AWS::Lambda::Function"/i;
// Find Lambda function name and S3 bucket name
const lambdaName: string = findResourceName(lambdaRegex, template, "server");
const s3BucketName: string = findResourceName(s3Regex, template, "cdk");
const cloudDistributionName: string = findResourceName(cdRegex, template, "cdk");
const cdkDeploymentaName: string = findResourceName(cdkDeploymentRex, template, "cdkDeployment");
console.log("Lambda function name:", lambdaName);
console.log("S3 bucket name:", s3BucketName);
console.log("cloud Distribution name:", cloudDistributionName);
console.log("CDK Deployment function name:", cdkDeploymentaName);
return [lambdaName, s3BucketName, cloudDistributionName, cdkDeploymentaName]
}
Attempt 2 - Modifying the generated CDKimport {
CfnOutput,
Duration,
Fn,
Stack,
StackProps,
aws_certificatemanager,
aws_cloudfront,
aws_cloudfront_origins,
aws_lambda,
aws_s3,
aws_s3_deployment,
aws_cognito,
custom_resources,
aws_secretsmanager,
aws_iam,
RemovalPolicy
} from 'aws-cdk-lib'
import { Construct } from 'constructs'
import {
appPath,
bridgeAuthToken,
certificateArn,
domainName,
environment,
memorySize
} from '../../build/external/params';
import * as path from 'path';
import * as lambda from "aws-cdk-lib/aws-lambda";
export class CDKStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props)
//Start of new code
const buildDir = path.resolve(__dirname, '../../build/');
const mySecret = new aws_secretsmanager.Secret(this, 'MySecret', {
secretName: 'AUTH_SECRET',
generateSecretString: {
passwordLength: 32,
excludePunctuation: true,
},
});
const cognitoDomain = generateRandomString(12);
const userpool = new aws_cognito.UserPool(this, 'user-pool', {
signInAliases: {
email: true,
},
selfSignUpEnabled: true,
standardAttributes: {
familyName: {
mutable: false,
required: false,
},
address: {
mutable: true,
required: false,
},
},
customAttributes: {
'createdAt': new aws_cognito.DateTimeAttribute(),
'isAdmin': new aws_cognito.BooleanAttribute({
mutable: false,
}),
},
passwordPolicy: {
minLength: 8,
requireLowercase: true,
requireUppercase: true,
requireDigits: true,
requireSymbols: false,
},
accountRecovery: aws_cognito.AccountRecovery.EMAIL_ONLY,
removalPolicy: RemovalPolicy.DESTROY,
});
// Check if the user pool already has a domain configured
const existingDomain = userpool.node.tryFindChild('MyUserPoolDomain') as aws_cognito.CfnUserPoolDomain | undefined;
// If the domain doesn't exist, add it to the user pool
if (!existingDomain) {
userpool.addDomain('MyCognitoDomain',{
cognitoDomain: {
domainPrefix: cognitoDomain
},
});
}
const lambdaPolicy = new aws_iam.PolicyDocument({
statements: [
// Allow Lambda to invoke Cognito
new aws_iam.PolicyStatement({
actions: [
//'cognito-idp:AdminCreateUser',
//'cognito-idp:AdminUpdateUserAttributes',
"cognito-idp:SignUp",
"cognito-idp:InitiateAuth",
"cognito-idp:RespondToAuthChallenge",
"cognito-idp:ConfirmSignUp",
"cognito-idp:GlobalSignOut",
"cognito-idp:GetUser",
"cognito-idp:UpdateUserAttributes",
"cognito-idp:ForgotPassword",
"cognito-idp:ConfirmForgotPassword"
// See: https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazoncognitouserpools.html
],
resources: ['*'], // Be cautious with using '*' as it grants access to all resources
}),
// Allow Lambda to invoke Bedrock
new aws_iam.PolicyStatement({
actions: [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream",
], // See: https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazoncognitouserpools.html
//resources: ["arn:aws:cognito-idp:*::userpool/YOUR_USER_POOL_ID"],
resources: ['*'], //TODO lockdown to claude model Replace '*' with the ARN of the Bedrock resource if possible
}),
new aws_iam.PolicyStatement({
actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'],
resources: ['arn:aws:logs:*:*:*'],
}),
],
});
// Step 2: Create an IAM role for the Lambda function
const lambdaRole = new aws_iam.Role(this, 'LambdaRole', {
assumedBy: new aws_iam.ServicePrincipal('lambda.amazonaws.com'),
});
// Step 3: Attach the IAM policy to the IAM role
lambdaRole.attachInlinePolicy(new aws_iam.Policy(this, 'LambdaPolicy', { document: lambdaPolicy }));
//end of new code
const lambdaFunc = new aws_lambda.Function(this, 'Server', {
runtime: aws_lambda.Runtime.NODEJS_18_X,
code: aws_lambda.Code.fromAsset(buildDir.toString() + '/lambda'),
handler: 'server.handler',
architecture: aws_lambda.Architecture.ARM_64,
memorySize,
timeout: Duration.seconds(30),
environment,
role: lambdaRole
});
const lambdaURL = lambdaFunc.addFunctionUrl({
authType: aws_lambda.FunctionUrlAuthType.NONE,
invokeMode: aws_lambda.InvokeMode.RESPONSE_STREAM
})
const certificate = certificateArn
? aws_certificatemanager.Certificate.fromCertificateArn(
this,
'CertificateManagerCertificate',
certificateArn
)
: undefined
const s3 = new aws_s3.Bucket(this, 'Bucket', {
transferAcceleration: true
})
const cf2 = new aws_cloudfront.Function(this, 'CF2', {
code: aws_cloudfront.FunctionCode.fromFile({
filePath: buildDir.toString() +'/cf2/index.js'
})
})
const behaviorBase = {
viewerProtocolPolicy:
aws_cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
originRequestPolicy:
aws_cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
functionAssociations: [
{
function: cf2,
eventType: aws_cloudfront.FunctionEventType.VIEWER_REQUEST
}
]
}
const cdn = new aws_cloudfront.Distribution(this, 'CloudFront', {
domainNames: domainName ? [domainName] : undefined,
certificate,
defaultBehavior: {
...behaviorBase,
allowedMethods: aws_cloudfront.AllowedMethods.ALLOW_ALL,
cachePolicy: aws_cloudfront.CachePolicy.CACHING_DISABLED,
origin: new aws_cloudfront_origins.HttpOrigin(
Fn.select(2, Fn.split('/', lambdaURL.url)),
{
protocolPolicy: aws_cloudfront.OriginProtocolPolicy.HTTPS_ONLY,
originSslProtocols: [aws_cloudfront.OriginSslPolicy.TLS_V1_2],
customHeaders: {
'Bridge-Authorization': `Plain ${bridgeAuthToken}`
}
}
)
},
httpVersion: aws_cloudfront.HttpVersion.HTTP2_AND_3,
additionalBehaviors: {
[appPath]: {
...behaviorBase,
origin: new aws_cloudfront_origins.S3Origin(s3)
}
}
})
new aws_s3_deployment.BucketDeployment(this, 'S3Deploy', {
sources: [aws_s3_deployment.Source.asset(buildDir.toString() + '/s3')],
destinationBucket: s3,
distribution: cdn
})
const cdnName: string = "https://" + cdn.domainName ?? cdn.distributionDomainName;
console.log("cdnName = " + cdnName);
const appClient = userpool.addClient('app-client', {
userPoolClientName: 'app-client',
authFlows: {
userPassword: true,
},
generateSecret: true,
oAuth: {
callbackUrls: [
cdnName + "/auth/callback/cognito",
cdnName + ":5173/auth/callback/cognito",
cdnName + ":5000/auth/callback/cognito",
cdnName + ":3000/auth/callback/cognito",
'http://localhost:5173/auth/callback/cognito',
'https://localhost:5173/auth/callback/cognito',
'http://localhost:5000/auth/callback/cognito',
'https://localhost:5000/auth/callback/cognito',
'http://localhost:3000/auth/callback/cognito',
'https://localhost:3000/auth/callback/cognito',
],
flows: {
authorizationCodeGrant: true,
}
},
});
//Can't use this because it creates a circular dependency, have to use the "UpdateEnvVarsLambda" as a workaround
/*
lambdaFunc.addEnvironment("COGNITO_USER_POOL_ID", userpool.userPoolId);
lambdaFunc.addEnvironment("COGNITO_CLIENT_ID", appClient.userPoolClientId);
lambdaFunc.addEnvironment("COGNITO_CLIENT_SECRET", appClient.userPoolClientSecret.unsafeUnwrap());
lambdaFunc.addEnvironment("COGNITO_ISSUER", "https://cognito-idp." + this.region + ".amazonaws.com/" + userpool.userPoolId);
lambdaFunc.addEnvironment("AUTH_TRUST_HOST", "true");
lambdaFunc.addEnvironment("AUTH_SECRET", "12344567890");
//lambdaFunc.addEnvironment("AUTH_SECRET", mySecret.secretValue.unsafeUnwrap()); //TODO: Bad practice, ctf app so not critical
*/
// Define the Lambda function to update environment variables
const updateEnvVarsLambda = new lambda.Function(this, 'UpdateEnvVarsLambda', {
runtime: lambda.Runtime.NODEJS_18_X,
handler: 'index.handler',
code: lambda.Code.fromInline('exports.handler = async (event) => {}'),
});
// Grant necessary permissions to the updateEnvVarsLambda function
updateEnvVarsLambda.addToRolePolicy(new aws_iam.PolicyStatement({
actions: ['lambda:UpdateFunctionConfiguration'],
resources: ['*'], // Replace with the ARN of the target Lambda function if more restrictive permissions are desired
}));
const environmentVariables: { [key: string]: string } = {
"COGNITO_USER_POOL_ID": userpool.userPoolId,
"COGNITO_CLIENT_ID": appClient.userPoolClientId,
"COGNITO_CLIENT_SECRET": appClient.userPoolClientSecret.unsafeUnwrap(),
"COGNITO_ISSUER": "https://cognito-idp." + this.region + ".amazonaws.com/" + userpool.userPoolId,
"AUTH_TRUST_HOST": "true",
"AUTH_SECRET": "<REDACTED", //just an example, not a production app.
//"AUTH_SECRET": mySecret.secretValue.unsafeUnwrap(), //TODO: Bad practice: ctf app so not critical
};
// Define the CloudFormation custom resource to trigger the Lambda function
const updateEnvVars = new custom_resources.AwsCustomResource(this, 'UpdateEnvVarsCustomResource', {
onCreate: {
service: 'Lambda',
action: 'updateFunctionConfiguration',
parameters: {
FunctionName: lambdaFunc.functionName, // Replace with the name of the existing Lambda function
Environment: {
Variables: environmentVariables,
},
},
physicalResourceId: custom_resources.PhysicalResourceId.of(Date.now().toString()),
},
onUpdate: {
service: 'Lambda',
action: 'updateFunctionConfiguration',
parameters: {
FunctionName: lambdaFunc.functionName, // Replace with the name of the existing Lambda function
Environment: {
Variables: environmentVariables,
},
},
physicalResourceId: custom_resources.PhysicalResourceId.of(Date.now().toString()),
},
policy: custom_resources.AwsCustomResourcePolicy.fromSdkCalls({ resources: custom_resources.AwsCustomResourcePolicy.ANY_RESOURCE }),
});
updateEnvVars.node.addDependency(lambdaFunc);
if (domainName) {
new CfnOutput(this, 'Deployed URL', {
description: 'Deployed URL',
value: `https://${domainName}`
})
}
new CfnOutput(this, 'CloudFront URL', {
description: 'CloudFront URL',
value: `https://${cdn.distributionDomainName}`
})
}
}
function generateRandomString(length: number): string {
const charset = 'abcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * charset.length);
result += charset[randomIndex];
}
return result;
} |
In hindsight for attempt 1 I'm pretty sure I was using the wrong names to retrieve the constructs from the cloudformation template, so there would be no need for |
I think you would want to extend via CDK. Have some way to allow the adapter to inject other stacks / files into the one cdk application or maybe allow specification of an alternative cdk app file that can import the Svelte Stack Also there are easy ways to share resources across stacks, you can export and import resources from separate stacks using CfnOutput and Fn.ImportValue. If you haven't tried this before this works pretty well except for:
cdk deploy can output json with output values for use in other build process. |
Hello,
Svelte & cloudformation noob here, I've been working on an demo app that uses AWS bedrock and cognito. However after using this adapter I realized it generates a cloudformation template and not the CDK code, so I'm a bit stuck on the best way to incorporate bedrock and cognito.
What would you suggest?
The text was updated successfully, but these errors were encountered: