on project we had single Chalice project for the whole API + pure lambdas (cloudwatch events, triggers etc) and as the project had grown, lambdas generated by project became bigger and bigger. We decided to split it into several chalice project that will be combined to single API under Custom domain name. For easy management&deploy we decided to put them in CDK project. As a result I created next stack that contains declaration of all chalice constructs (component_with_stack_declaration.py):
class RestApi(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
...
code_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "src")
app1 = Chalice(self, "App1", source_dir=os.path.join(code_dir, "app1"))
app2= Chalice(self, "App2", source_dir=os.path.join(code_dir, "app2"))
...
When I ran cdk synth I got next traceback:
Creating deployment package.
Creating deployment package.
jsii.errors.JavaScriptError:
@jsii/kernel.RuntimeError: Error: section 'Resources' already contains 'Lambda1LogicalID'
at Kernel._ensureSync (C:\....\AppData\Local\Temp\tmpgg97w2wj\lib\program.js:10364:27)
at Kernel.invoke (C:\....\AppData\Local\Temp\tmpgg97w2wj\lib\program.js:9764:34)
at KernelHost.processRequest (C:\....\AppData\Local\Temp\tmpgg97w2wj\lib\program.js:11539:36)
at KernelHost.run (C:\Users\....\Local\Temp\tmpgg97w2wj\lib\program.js:11499:22)
at Immediate._onImmediate (C:\....\AppData\Local\Temp\tmpgg97w2wj\lib\program.js:11500:46)
at processImmediate (node:internal/timers:466:21)
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "B:\...\cdk_app\app.py", line 15, in <module>
app.synth()
File "B:\....\cdk_app\venv\lib\site-packages\aws_cdk\__init__.py", line 20043, in synth
return typing.cast(_CloudAssembly_c693643e, jsii.invoke(self, "synth", [options]))
File "B:\....\cdk_app\venv\lib\site-packages\jsii\_kernel\__init__.py", line 149, in wrapped
return _recursize_dereference(kernel, fn(kernel, *args, **kwargs))
File "B:\....\cdk_app\venv\lib\site-packages\jsii\_kernel\__init__.py", line 399, in invoke
response = self.provider.invoke(
File "B:\....\cdk_app\venv\lib\site-packages\jsii\_kernel\providers\process.py", line 377, in invoke
return self._process.send(request, InvokeResponse)
File "B:\....\cdk_app\venv\lib\site-packages\jsii\_kernel\providers\process.py", line 339, in send
raise RuntimeError(resp.error) from JavaScriptError(resp.stack)
RuntimeError: Error: section 'Resources' already contains 'Lambda1LogicalID'
When I looked in the generated in cdk.out and chalice.out templates i saw that this lambda is actually declared twice. Example from .sam_with_assets.json files in generated chalice.out folder:
Chalice app1
{
"AWSTemplateFormatVersion": "2010-09-09",
"Transform": "AWS::Serverless-2016-10-31",
"Outputs": {},
"Resources": {
"Lambda1LogicalID": {
"Type": "AWS::Serverless::Function",
"Properties": {
"Runtime": "python3.9",
"Handler": "chalicelib.app1.handler.lambda1",
"CodeUri": {
"Bucket": "cdk-hnb659fds-assets-XXXXXXXXXXXX-us-east-1",
"Key": "963f64d496d61b07c0f89b0c476f7ff9a0ebdc17e4f8b80484a4f957eecca97e.zip"
},
"Tags": {
"aws-chalice": "version=1.27.3:stage=dev/RestApi:app=app1"
},
...
Chalice app2
{
"AWSTemplateFormatVersion": "2010-09-09",
"Transform": "AWS::Serverless-2016-10-31",
"Outputs": {},
"Resources": {
"Lambda1LogicalID": {
"Type": "AWS::Serverless::Function",
"Properties": {
"Runtime": "python3.9",
"Handler": "chalicelib.app1.handler.lambda1",
"CodeUri": {
"Bucket": "cdk-hnb659fds-assets-XXXXXXXXXXXX-us-east-1",
"Key": "972986e72385367de3787cc5004baa8efe91c8c98335a070ee636b9f7519afd7.zip"
},
"Tags": {
"aws-chalice": "version=1.27.3:stage=dev/RestApi:app=app2"
},
...
As you can see they are identical apart aws-chalice tag and key in bucket. I checked several times within generated .zip assets, they contained correct 2 different chalice project code, but for some reason template of second chalice project (app2) contains all the same lambdas as in the first project (app1) and doesnt contain any lambdas that are declared in code of the .zip archive that is created for it. Code is stored in the next order:
cdk_app
| - app.py
| - restapi_stack
| - component_with_stack_declaration.py
| - src
| - app1
| | - .chalice
| chalicelib
| app.py
| - app2
| - .chalice
chalicelib
app.py
Maybe someone had similar issue? I would be happy to receive your feedback
I tried to place Chalice constructs in separate inner stacks of RestApi stack, but got the same result - differect assets with code but same template with same lambdas' resources (apart tags).
UPDATE 19.03.2023
I discovered that the actual issue was that i wanted to keep the same chalice stage name for all Chalice constructs, so i set as scope for them the same stack. It seems chalice construct requires to be single per stack.
I tried to do so because now in chalice construct you cant set what chalice stage name from config file to use. It would be very nice to have such possibility, because now you have to set stage name based on stack full name, what is not very friedly if you migrate the existing app to cdk.
For ex. in my case i have next path dev/RestApi/App1 as chalice stage name. I will include that in the issues in project repository
Also, I ran some tests and discovered strage behavior, even though i succeeded to deploy different lambdas, it seems that CDK creates different APIs but with the same path&methods structure as in the first Chalice construct it happens when I deploy all stack at once (--all flag). Strange is that it maps these identical APIGateway (different ids but same path&methods definition) to different lambdas. It seems for API endpoints (for APIGateway) definition in all chalice projects is used always the first chalice project.
I map a bit through the code of chalice and finally this time defilitely I find out where the problem is. Chalice builds template based on code in imported modules, but importing in python works with such logic that if you already imported some module you shoud reimport it, just import wont work for update. So what actually happens is that the first time chalice correctly import project modules it loads app.py module and chalicelib package with inner modules. And then when it checkout to different directory with same structure (and chalice requires the same structure with app.py and chalicelib) it tries to import again app.py but as we already have it in imported it doesnt update it.
The block of code where issue happens is in
chalice/cli/factory.py:This actualy can be solved very easily deleting app and chalicelib from imported sys.modules, but this works well in my case because i don't have modules/packages with same name in 2 chalice projects (appart app and chalicelib). So i found the way to fix it in more generic way in 4 lines to call after calling Chalice cdk construct declaration:
You can either put them directly in module or as I do call as method after Chalice contruct use.
It would be nice to see some kind of such update in Chalice itself. Maybe even a bit more complicate variation with tracking of all imported packages during calling
importlib.import_module('app')including third party libraries with next deletion of them from system imported modules after building app package, so that we wont have collision bettween different apps, but for now the only fix i found is the one specified higher