aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.71k stars 3.94k forks source link

API Gateway (v1): Cannot provide RestApi as parent for Resource #24684

Open jwshyns opened 1 year ago

jwshyns commented 1 year ago

Describe the bug

When defining a Resource separately from a RestApi I am unable to provide the RestApi as the parent for the Resource despite documentation suggesting otherwise: image

This appears that it may have a flow-on effect when attempting to add a CognitoUserPoolsAuthorizer to the created resource, as it needs to be "attached to a RestApi".

Expected Behavior

To be able to provide a RestApi as the parent of a Resource and subsequently be able to attach a CognitoUserPoolsAuthorizer to the resource.

Current Behavior

When providing the RestApi as the parent the project cannot compile/synth.

When providing the RestApi's root resource as the parent, or creating a wrapper for RestApi that implements Amazon.CDK.AWS.APIGateway.IResource the project can compile, but fails synth with the following error:

Unhandled exception. System.Exception: Error: Resolution error: Resolution error: Resolution error: Authorizer ([REDACTED]-InfrastructureStage-dev/InfrastructureStack/Api/ApiAuthorizer) must be attached to a RestApi.
Object creation stack:
  at Function.string (C:\[REDACTED]\AppData\Local\Temp\jsii-kernel-hxXIJv\node_modules\aws-cdk-lib\core\lib\lazy.js:1:766)
  at CognitoUserPoolsAuthorizer.lazyRestApiId (C:\[REDACTED]\AppData\Local\Temp\jsii-kernel-hxXIJv\node_modules\aws-cdk-lib\aws-apigateway\lib\authorizers\cognito.js:1:1986)
  at new CognitoUserPoolsAuthorizer (C:\[REDACTED]\AppData\Local\Temp\jsii-kernel-hxXIJv\node_modules\aws-cdk-lib\aws-apigateway\lib\authorizers\cognito.js:1:793)
  at Kernel._create (C:\[REDACTED]\AppData\Local\Temp\tvnb0n21.o2d\lib\program.js:9959:29)
  at Kernel.create (C:\[REDACTED]\AppData\Local\Temp\tvnb0n21.o2d\lib\program.js:9688:29)
  at KernelHost.processRequest (C:\[REDACTED]\AppData\Local\Temp\tvnb0n21.o2d\lib\program.js:11539:36)
  at KernelHost.run (C:\[REDACTED]\AppData\Local\Temp\tvnb0n21.o2d\lib\program.js:11499:22)
  at Immediate._onImmediate (C:\[REDACTED]\AppData\Local\Temp\tvnb0n21.o2d\lib\program.js:11500:46)
  at processImmediate (node:internal/timers:464:21)..
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.Invoke(InvokeRequest request)
   at Amazon.JSII.Runtime.Services.Client.Invoke(ObjectReference objectReference, String method, Object[] arguments)
   at Amazon.JSII.Runtime.Deputy.DeputyBase.<>c__DisplayClass17_0`1.<InvokeInstanceMethod>b__1(IClient client, Object[] args)
   at Amazon.JSII.Runtime.Deputy.DeputyBase.<InvokeMethodCore>g__GetResult|18_0[T](<>c__DisplayClass18_0`1&)
   at Amazon.JSII.Runtime.Deputy.DeputyBase.InvokeMethodCore[T](JsiiMethodAttribute methodAttribute, Object[] arguments, Func`3 beginFunc, Func`3 invokeFunc)
   at Amazon.JSII.Runtime.Deputy.DeputyBase.InvokeInstanceMethod[T](Type[] parameterTypes, Object[] arguments, String methodName)
   at Amazon.CDK.Pipelines.PipelineBase.AddStage(Stage stage, IAddStageOpts options)
   at [REDACTED].StandardPipeline.TryCreateStageForEnvironment(DeploymentEnvironment deploymentEnvironment, ShellStep synthStep, EnvironmentGenerator environmentGenerator, IReadOnlyDictionary`2 deploymentEnvironments, EnvironmentManualApprovalOutput& environmentManualApprovalOutput)
   at [REDACTED].StandardPipeline..ctor(Construct scope, String id, StandardPipelineProps props)
   at Infrastructure.Program.Main(String[] args) in C:\[REDACTED]\src\Infrastructure\Program.cs:line 26

Reproduction Steps

Here's the constructor of a Stage that is used to deploy the offending resources:

public InfrastructureStage(Construct scope, string id, InfrastructureStageProps props) : base(scope,
            id, props)
        {
            var stack = new Stack(this, "InfrastructureStack", new StackProps());

            var api = new RestApi(stack, "Api", new RestApiProps());

            api.Root
                .AddResource("proxy")
                .AddProxy(new ProxyResourceOptions
                {
                    AnyMethod = true,
                    DefaultIntegration = new HttpIntegration([REDACTED]),
                    DefaultMethodOptions = new MethodOptions
                    {
                        ApiKeyRequired = true
                    }
                });

          /* Ommited for brevity */

            var apiResource = new Resource(stack, "ApiResource", new ResourceProps
            {
                Parent = api, // Doesn't accept api as a valid input because it doesn't implement Amazon.CDK.AWS.APIGateway.IResource
                PathPart = "graphql",
                DefaultIntegration = new LambdaIntegration(new BaseDotNetFunction(stack, "ApiFunction",
                    new BaseDotNetFunctionProps
                    {
                        Environment = props.Environment,
                        FunctionName = "ApiFunction",
                        CodePath = "./src/API/",
                        Handler = "API",
                        FunctionEnvironmentVariables = new FunctionEnvironmentVariables()
                    }).Function),
                DefaultMethodOptions = new MethodOptions
                {
                    AuthorizationType = AuthorizationType.COGNITO,
                    Authorizer = new CognitoUserPoolsAuthorizer(api, "ApiAuthorizer", // Providing api or stack here as scope doesn't make a difference
                        new CognitoUserPoolsAuthorizerProps
                        {
                            CognitoUserPools = new IUserPool[] {auth.UserPool.UserPool}
                        })
                }
            });
            apiResource.Node.AddDependency(distribution, api, auth);
        }

As you can see this sample does use some custom constructs, especially relating to the BaseDotNetFunction and auth (which is used in the CognitoUserPoolsAuthorizerProps to provide a user pool).

Possible Solution

Having Amazon.CDK.AWS.APIGateway.IResourceProps.Parent accept both Amazon.CDK.AWS.APIGateway.IResource and Amazon.CDK.AWS.APIGateway.RestApi or have Amazon.CDK.AWS.APIGateway.RestApi implement Amazon.CDK.AWS.APIGateway.IResource. Additionally, have CognitoUserPoolsAuthorizer properly recognise when it is attached to a RestApi.

Additional Information/Context

Of note, I am having to create the apiResource like this to resolve a cyclic dependency between a CloudFront Distribution, a Cognito UserPool, and the apiResource.

CDK CLI Version

2.69.0 (build 60a5b2a)

Framework Version

.NET 7

Node.js Version

18.15.0

OS

Windows 11 Home (10.0.22621)

Language

.NET

Language Version

C# 11

Other information

No response

damogallagher commented 9 months ago

Has a fix been identified for this issue? I am seeing a similar error in the typescipt library

pahud commented 3 months ago

I am making this a p2 feature request.

@damogallagher can you share your code snippet in TypeScript?