Passing of CF variables to functions envrionment fails on deploy to a new service instance

I added a RDS to my service and set it to pass its uri and port to functions environment like this:

provider:
  environment:
    RDS_HOST: ${cf:${env:SERVICE_NAME}-${env:STAGE}.RdsUri}
    RDS_PORT: ${cf:${env:SERVICE_NAME}-${env:STAGE}.RdsPort}
resources:
  Resources:
    ...
  Outputs:
    RdsUri:
      Description: "RDS Endpoint"
      Value:
        "Fn::GetAtt": ["Rds", "Endpoint.Address"]
    RdsPort:
      Description: "RDS Port"
      Value:
        "Fn::GetAtt": ["Rds", "Endpoint.Port"]

And it worked fine until I tried to deploy to a new service instance:

gooddata-provisioning-api - Travis CI 2017-08-01 10-50-21

I guess that Serverless tries to resolve the variable before the stack is created, right?

How I thought, when I remove the env variables, run deploy and return them back and run deploy again, it works fine. Question is, is it a bug?

No, it’s not a bug; How can you reference a variable variable that doesn’t exist?

When you first deploy the stack there are no outputs to reference, so it fails. These kinds of variables are useful if you first deploy your resource (i.e. RDS in this case) and then - in a separate stack/service - reference it in your function.

I know you can use Fn::Join inside environment variables. I’ve never tried using using Fn::GetAtt there but this might work.

provider:
  environment:
    RDS_HOST: { "Fn::GetAtt": ["Rds", "Endpoint.Address"] }
    RDS_PORT: { "Fn::GetAtt": ["Rds", "Endpoint.Port"] }

If you try it I’d love know the result.

@buggy It works like a charm, thanks!

@rowanu How would you setup automated deploy of such service, then? (If I didn’t get the advice from @buggy.) I thought Serverless creates the resources first and then resolves the variables. I found few more similar problems (one of them was a policy on some resource for the lambda role which I had to remove for the first deploy, but I don’t remember exactly), maybe Serverless could distinguish between some setup deploy and the subsequent ones?

1 Like

If it had to make that distinction between setup/not-setup deployments, then it wouldn’t be idempotent.

While what you’re describing would be convenient, there are many strange and wonderful ways such an approach can fail e.g. What if there was bug in the logic and SLS thought it was doing the setup run, and it wasn’t? What if some of your setup process completed successfully, but not all of it - should SLS do the setup run because some dependencies are missing, or should it do no setup because some are present?

The key is to separate your service and its dependencies, and not to deploy them all at once; Deploy you dependencies first, and then the thing that depends on them.

This has the added benefit that by calling out your dependencies explicitly (i.e. by putting them in another stack/service), you make things much easier to maintain and troubleshoot in the future.