winglang / wing

A programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡
https://winglang.io
Other
4.68k stars 181 forks source link

`cloud.Api` endpoint that uses `api.url` deploys to simulator but not to AWS #2740

Closed Chriscbr closed 3 months ago

Chriscbr commented 1 year ago

I tried this:

bring cloud;

let api = new cloud.Api();
api.get("/route", inflight (req: cloud.ApiRequest): cloud.ApiResponse => {
  log(api.url);
  return cloud.ApiResponse {
    status: 200,
    body: Json "ok"
  };
});

This happened:

wing test -t sim api_url.w passes (it simulates the app, and runs all 0 of 0 tests)

But wing test -t tf-aws api_url.w fails when it tries to run terraform apply:

Error: Cycle: aws_api_gateway_deployment.root_env0_cloudApi_api_deployment_FABCA7C9, aws_api_gateway_rest_api.root_env0_cloudApi_api_3A5A95BE, aws_api_gateway_stage.root_env0_cloudApi_api_stage_0D945F2C, aws_lambda_function.root_env0_cloudApi_cloudApiOnRequest3915c5ec_DC2C0B9C

I expected this:

Either the app should deploy, or I should get some error at compile time

Is there a workaround?

No response

Component

SDK

Wing Version

0.18.1

Wing Console Version

No response

Node.js Version

18.14.1

Platform(s)

MacOS

Anything else?

Here's a breakdown of the dependency cycle:

  1. In order to log api.url, the AWS Lambda function that is created has an environment variable which depends on the API Gateway stage invoke url:
"environment": {
  "variables": {
    "CLOUD_API_C8499363": "${aws_api_gateway_stage.root_env0_cloudApi_api_stage_0D945F2C.invoke_url}",
    "WING_FUNCTION_NAME": "cloud-Api-OnRequest-3915c5ec-c8195997"
  }
},
  1. The API Gateway stage depends to the API Gateway deployment ID
  2. The API Gateway deployment depends on the API Gateway rest api ID
  3. The API Gateway rest api has the OpenAPI spec for the all endpoints, which refers to the ARN of the AWS Lambda function

Community Notes

Chriscbr commented 1 year ago

One solution to break up the dependency cycle could be to store the API Gateway URL in some kind of SSM parameter which is then retrieved dynamically at runtime perhaps?

Chriscbr commented 1 year ago

Actually, maybe it makes sense to just lift and mirror this dependency cycle error in preflight (through the addDependency feature of constructs) so it's detected at compile time, and then force the user to resolve this (either remove api.url from their code, or avoid the cycle using a workaround like the one suggested above).

Chriscbr commented 1 year ago

I'd be curious if this problem is the same in AWS CDK / CloudFormation / SST

ekeren commented 1 year ago

The API Gateway rest api has the OpenAPI spec for the all endpoints, which refers to the ARN of the AWS Lambda function

Why does the openApi spec needs to have a reference to the AWS lambda ARN?

Chriscbr commented 1 year ago

The API Gateway rest api has the OpenAPI spec for the all endpoints, which refers to the ARN of the AWS Lambda function

Why does the openApi spec needs to have a reference to the AWS lambda ARN?

That's a good question... it looks like it's needed to specify the integration between the API endpoint and the lambda function, so that AWS can configure all requests to a particular route (like GET /hello) go to that lambda. This is the code I found in our SDK:

https://github.com/winglang/wing/blob/643004cf615410bb9c0a3e2bc8966f891f54819c/libs/wingsdk/src/target-tf-aws/api.ts#L437-L454

ekeren commented 1 year ago

Actually, maybe it makes sense to just lift and mirror this dependency cycle error in preflight (through the addDependency feature of constructs) so it's detected at compile time, and then force the user to resolve this (either remove api.url from their code, or avoid the cycle using a workaround like the one suggested above).

I like this approach, but will the user have indication for this problem when he compiles to sim as well ?

marciocadev commented 11 months ago

It seems that what is causing trouble in capturing the URL when the destination is AWS is that the gateway is only created after the lambda is deployed, so we don't have the URL reference in the lambda.

The OpenAPI is generating the problem since it creates the integration between the gateway and the lambda, and it requires the lambda's reference.

To fix it, we would probably have to remove the OpenAPI and first create the Gateway, then the lambda, and finally create the integration between them.

marciocadev commented 11 months ago

This code works, the function that doesn't have integration with the gateway can retrieve the URL

bring cloud;

let api = new cloud.Api();
api.get("/route", inflight (req: cloud.ApiRequest): cloud.ApiResponse => {
  return cloud.ApiResponse {
    status: 200,
    body: Json "ok"
  };
});

new cloud.Function(inflight(e:str) => {
  log("${api.url}");
});
github-actions[bot] commented 9 months ago

Hi,

This issue hasn't seen activity in 60 days. Therefore, we are marking this issue as stale for now. It will be closed after 7 days. Feel free to re-open this issue when there's an update or relevant information to be added. Thanks!

ekeren commented 9 months ago

I don't think think this should be p1

Do you have an estimated hard is it to show the error of tf-awscompilation

staycoolcall911 commented 9 months ago

Moved to p2

github-actions[bot] commented 7 months ago

Hi,

This issue hasn't seen activity in 60 days. Therefore, we are marking this issue as stale for now. It will be closed after 7 days. Feel free to re-open this issue when there's an update or relevant information to be added. Thanks!

Chriscbr commented 3 months ago

I figured I'd try one more time to see if there's any way to unravel this cycle cleanly, on AWS at least (since it's not an inherent modeling problem).

I went down the route of SSM parameters initially (as it's a general solution to the problem of runtime code referencing deploy-time information), but it requires more work to build out, so I've put a pin on it for now. But the idea would be to have dependencies generated like so:

  1. The API Gateway spec references the lambda ARN
  2. The lambda environment variable references the SSM parameter ARN
  3. The SSM parameter would store (and depend on) the API Gateway URL

This looks like it's just a longer cycle, but the trick is that in step (2), the SSM parameter name can be computed at compile time through the construct ID. So the AWS Lambda doesn't need to take any dependencies on the API Gateway nor the SSM parameter in the Terraform configuration.

The issue implementing this is that it requires the inflight code to change. Instead of the Lambda reading the environment variable directly, it would have to call the SSM service to obtain the parameter value at runtime. Maybe this logic is handled as part of the cloud.Api inflight client? Or maybe it's a responsibility of the Wing compiler / framework? I'm not totally sure yet.

For now I came up with another possible workaround: https://github.com/winglang/wing/pull/5637

MarkMcCulloh commented 3 months ago

Maybe I'm mistaken since I haven't done much with API gateway, but could we use https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_integration instead of the openapi body at the API level to define the routes? This should remove the circular dep.

instead of (-> is depends on)

lambda -> stage -> deployment -> api -> (X)lambda

we'd get

integration -> api + integration -> lambda -> stage -> deployment -> api

eladb commented 3 months ago

Related https://github.com/winglang/wing/issues/5601

monadabot commented 3 months ago

Congrats! :rocket: This was released in Wing 0.57.11.