aws / aws/aws-cdk

(apiGateway): path may have some resources are already exist in api_gateway, the CDK still will create all resources for the path.

Open
#31,093 1 comment 0 reactions 0 assignees View on GitHub
@aws-cdk/aws-apigateway effort/medium feature-request p2
Dominant language
TypeScript
Stars
12.9k
Forks
4.6k
Avg merge
2d 3h
Merged PRs (30d)
83

Description

### Describe the bug

Hi team, there are some very tricky errors when we use `RestApi.from_rest_api_attributes` and 'resource.resource_for_path(path)'.

We are using Python lib.

Our team wants to add methods and resources to an existing API on several stacks.

First, we are using `from_rest_api_attributes` to get an existing restApi:
`api = aws_apigateway.RestApi.from_rest_api_attributes(
scope=scope,
id='Api',
rest_api_id=api_id,
root_resource_id=root_resource_id
)`

then we want to add those resources on the A stack:
`
res = api.root.resource_for_path('/api/test/list')
res.add_method("get")
res = api.root.resource_for_path('/api/test/get')
res.add_method("get")
`

and add resources on the B stack:
`
res = api.root.resource_for_path('/api/test/update')
res.add_method("get")
res = api.root.resource_for_path('/api/test/delete')
res.add_method("get")
`
Result: The A stack was deployed successfully, but the B stack was deployed failed by '/api, /test resource already exists'.

The reason was that in [resourceForPath](https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/aws-apigateway/lib/resource.ts#L349), CDK is using [getResource](https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/aws-apigateway/lib/resource.ts#L369) to determine if `api` resource is a child of `/`, and recursive execute each part of the path. but how each resource knows their children, is when resource init they will add themselves to the parent resoure's child list. like: https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/aws-apigateway/lib/resource.ts#L446. because this, if we only know the root resource, we can't know the all children of the root resource.

So to fix this we found two solutions, but only one is working.

Solution One:
create our customized resourceForPath method like:

```python
def get_resource_from_api( apigateway: str, path: str) -> Resource: # apigateway are like: "rest_id:root_resources_id"
if ':' in apigateway and apigateway[0] != ':' and apigateway[-1] != ':':
rest_id, root_resources_id = apigateway.split(':')
all_exist_resources = self.api_gateway_client.client.get_resources(restApiId=rest_id)
return self.get_resource_from_exist_api(all_exist_resources, path, api.root, 0)
return api.root.resource_for_path(path)

def get_resource_from_exist_api(all_exist_resources: dict, path: str, root: any, index) -> Resource:
if index > len(path.split('/')):
return root
if path.startswith('/'):
return get_resource_from_exist_api(all_exist_resources, path[1:], root, index+1)
parts = path.split('/')
part_path = '/' + '/'.join(parts[:index])
part_path_resource_id = _get_part_path_resource_id_from_api_resources(all_exist_resources, part_path)

if part_path_resource_id:
print(f"${part_path} exist, id is ${part_path_resource_id}")
resource = Resource.from_resource_attributes(self, id=part_path+"resource_id", path=part_path, resource_id=part_path_resource_id,
rest_api=self.api)
if not part_path_resource_id:
print(f"${part_path} not exist, create it")
resource = root.add_resource(parts[index-1])
all_exist_resources = None
return self.get_resource_from_exist_api(all_exist_resources, path, resource, index + 1)

def _get_part_path_resource_id_from_api_resources(all_exist_resources: dict, path: str) -> str | None:
if all_exist_resources:
for item in all_exist_resources['items']:
if item['path'] == path:
return item['id']
return None
```

But this solution failed when we try updated the path resources on the stack, for example:

In the first deployment, we create path `/test/v1`, and this stack newly creates and manages each resource.

second deployment, we update `/test/v1` to `test/v2`, and the code will think the `/test` resource already exists, so will remove this resource from the stack, so all resources belonging to `/test/v2` will disappear.

We try to use the `resource. stack` to confirm if this resource is managed by this stack so we can re-handler it, but this `stack` param doesn't work as we expect, so we don't know how to fix it.

Solution Two(accepted):

we consider maybe we can use a separate stack to create and manage all path resources and apigateway, so in our deployment stack, we can just import an existing path and add a method for it.

for example, we have an existing path `/test/v1` resource id is `545etf`, and we use `545etf` as the root_resourse_id set on `from_rest_api_attributes` to get API gateway, so we can add method directly to the root resource, code is like:

```python
api = aws_apigateway.RestApi.from_rest_api_attributes(
scope=scope,
id='Api',
rest_api_id=api_id,
root_resource_id='545etf'
)
resource = api.root.resource_for_path("/")
resource.add_method("get")
```

But, this solution also failed, because even if we set the root_resource to `545etf`, we can get the correct resource id on the `api.root.resource_id`, but the `api.root.path` result is just `/`. so the method was added succeed on the apigateway side, but the lambda will trigger by a wrong apigateway path and cause the error.

The reason is in CDK we just [create new resource](https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/aws-apigateway/lib/restapi.ts#L798) for this root_resource_id, not to find exist one.
we found a complex way to avoid this error, the final workaround is like:

```python
api = aws_apigateway.RestApi.from_rest_api_attributes(
scope=scope,
id='Api',
rest_api_id=api_id,
root_resource_id='545etf'
)
resource = api.root.resource_for_path("/")
root_path = api_gateway_client.client.get_resource(restApiId=rest_id,
resourceId=`545etf`)['path']
if root_path != '/':
full_path = root_path + item.path if item.path != '/' else root_path
res = Resource.from_resource_attributes(self, id=full_path + "resource_id", path=full_path,
resource_id=res.resource_id,
rest_api=cast(aws_apigateway.RestApi,API))
```

Option 2 is working normally for me now, but we think it's valuable to raise an issue with your team and see if you have a better solution, or if these two errors can be fixed by the CDK side.

### Regression Issue

- [ ] Select this option if this issue appears to be a regression.

### Last Known Working CDK Version

_No response_

### Expected Behavior

`Resource.resource_for_path` method can GETS or create all resources leading up to the specified path.

when we set a different root resource id on from_rest_api_attributes, we can get the correct path when calling `api. root.path`.

### Current Behavior

`Resource.resource_for_path` method will creates all resources leading up to the specified path, even some resources are already existed.

when we set a different root resource id on from_rest_api_attributes, the `api.root.path` always return `/`.

### Reproduction Steps

follow the code on the bug description.

### Possible Solution

_No response_

### Additional Information/Context

_No response_

### CDK CLI Version

2.113.0

### Framework Version

_No response_

### Node.js Version

v20.12.2

### OS

mac 14.6

### Language

Python

### Language Version

python 3.10

### Other information

_No response_

Contributor guide

Open the contributing guide

Research direction

Start with resource.ts at resourceForPath and getResource, then inspect restapi.ts around the from_rest_api_attributes handling described in the issue. Reproduce the existing-resource case and the non-root imported resource case using the provided Python examples. Done means existing path resources are reused without duplicate-resource failures and an imported root resource reports its actual API Gateway path.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, python, typescript
Domain
api, cloud, infrastructure
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.