Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Variables syntax collides with AWS params syntax #3184

Closed
paulSambolin opened this issue Feb 2, 2017 · 38 comments · Fixed by #8279
Closed

Variables syntax collides with AWS params syntax #3184

paulSambolin opened this issue Feb 2, 2017 · 38 comments · Fixed by #8279
Assignees

Comments

@paulSambolin
Copy link
Contributor

paulSambolin commented Feb 2, 2017

I was attempting to use !Sub as shown in the EC2 metadata file section

This example is exactly reflected on the Fn::Sub Page

Yet when I place this in the serverless.yml I get the following exception:

     unknown tag !<!Sub> in "/Users/paulsambolin/Desktop/serverless.yml"
     at line 317, column 15:
                  mode: "000644"

                      ^

     For debugging logs, run again after setting the "SLS_DEBUG=*" environment variable.

  Stack Trace --------------------------------------------

YAMLException: unknown tag !<!Sub> in "/Users/paulsambolin/Desktop/serverless.yml" at line 317, column 15:
                  mode: "000644"
                  ^
    at generateError (/Users/paulsambolin/.nvm/versions/node/v6.3.0/lib/node_modules/serverless/node_modules/js-yaml/lib/js-yaml/loader.js:162:10)
    at throwError (/Users/paulsambolin/.nvm/versions/node/v6.3.0/lib/node_modules/serverless/node_modules/js-yaml/lib/js-yaml/loader.js:168:9)
    at composeNode (/Users/paulsambolin/.nvm/versions/node/v6.3.0/lib/node_modules/serverless/node_modules/js-yaml/lib/js-yaml/loader.js:1398:7)
    at readBlockMapping (/Users/paulsambolin/.nvm/versions/node/v6.3.0/lib/node_modules/serverless/node_modules/js-yaml/lib/js-yaml/loader.js:1057:11)
    at composeNode (/Users/paulsambolin/.nvm/versions/node/v6.3.0/lib/node_modules/serverless/node_modules/js-yaml/lib/js-yaml/loader.js:1327:12)
    at readBlockMapping (/Users/paulsambolin/.nvm/versions/node/v6.3.0/lib/node_modules/serverless/node_modules/js-yaml/lib/js-yaml/loader.js:1057:11)
    at composeNode (/Users/paulsambolin/.nvm/versions/node/v6.3.0/lib/node_modules/serverless/node_modules/js-yaml/lib/js-yaml/loader.js:1327:12)
    at readBlockMapping (/Users/paulsambolin/.nvm/versions/node/v6.3.0/lib/node_modules/serverless/node_modules/js-yaml/lib/js-yaml/loader.js:1057:11)
    at composeNode (/Users/paulsambolin/.nvm/versions/node/v6.3.0/lib/node_modules/serverless/node_modules/js-yaml/lib/js-yaml/loader.js:1327:12)
    at readBlockMapping (/Users/paulsambolin/.nvm/versions/node/v6.3.0/lib/node_modules/serverless/node_modules/js-yaml/lib/js-yaml/loader.js:1057:11)
    at composeNode (/Users/paulsambolin/.nvm/versions/node/v6.3.0/lib/node_modules/serverless/node_modules/js-yaml/lib/js-yaml/loader.js:1327:12)
    at readBlockMapping (/Users/paulsambolin/.nvm/versions/node/v6.3.0/lib/node_modules/serverless/node_modules/js-yaml/lib/js-yaml/loader.js:1057:11)
    at composeNode (/Users/paulsambolin/.nvm/versions/node/v6.3.0/lib/node_modules/serverless/node_modules/js-yaml/lib/js-yaml/loader.js:1327:12)
    at readBlockMapping (/Users/paulsambolin/.nvm/versions/node/v6.3.0/lib/node_modules/serverless/node_modules/js-yaml/lib/js-yaml/loader.js:1057:11)
    at composeNode (/Users/paulsambolin/.nvm/versions/node/v6.3.0/lib/node_modules/serverless/node_modules/js-yaml/lib/js-yaml/loader.js:1327:12)
    at readBlockMapping (/Users/paulsambolin/.nvm/versions/node/v6.3.0/lib/node_modules/serverless/node_modules/js-yaml/lib/js-yaml/loader.js:1057:11)
From previous event:
    at __dirname (/Users/paulsambolin/.nvm/versions/node/v6.3.0/lib/node_modules/serverless/bin/serverless:15:28)
    at Object.<anonymous> (/Users/paulsambolin/.nvm/versions/node/v6.3.0/lib/node_modules/serverless/bin/serverless:27:4)
    at Module._compile (module.js:541:32)
    at Object.Module._extensions..js (module.js:550:10)
    at Module.load (module.js:458:32)
    at tryModuleLoad (module.js:417:12)
    at Function.Module._load (module.js:409:3)
    at Module.runMain (module.js:575:10)
    at run (bootstrap_node.js:352:7)
    at startup (bootstrap_node.js:144:9)
    at bootstrap_node.js:467:3

Similar issue: #8116


Proposed solution

Improve variables format regex into '\\${([^{}:]+?:[^:{}][^{}]*?)}' - This should ensure that notations ${StepFunctionArn} (as reported at #8116) or ${AWS::Whatever} are not picked as SLS variables

Additionally to ensure a solution for eventual more difficult cases (not necessary AWS related) we may consider to introduce an escape notation (so e.g. \${ is not recognized as variables start bracket)

@ProTip
Copy link

ProTip commented Feb 3, 2017

It would be great if serverless could either output yaml and preserve the tags, or render the tags properly into the long form for the json output.

@pas256
Copy link

pas256 commented Mar 10, 2017

I tried to do this very thing yesterday to discover I couldn't.

@HyperBrain
Copy link
Member

Sub clashed with the Serverless "${}" variable declaration syntax as AWS uses exactly the same syntax with Fn::Sub

@pas256
Copy link

pas256 commented Mar 10, 2017

Exactly. There are probably many ways to support both.

Ideally, serverless recognizes the Sub, and then skips variables it doesn't know about expecting CloudFormation to solve it. That sounds hard.

Another idea is to make it explicit with a prefix much like ${self:}:

Fn::Sub: 
    - "${cf:NotAServerlessVar}-abcd-${ServerlessVar}-${cf:NotAServerlessVar2}"
    - NotAServerlessVar: 123
      NotAServerlessVar2: xyz

@HyperBrain
Copy link
Member

Introducing a separate CF: prefix for vars sounds more irritating because it just would skip the variable substitution.

More intuitive would be to use double-$ to escape variable substitution. So $${NotAServerlessVar} would not be treated as variable and end up as ${NotAServerlessVar} in the CF template,

How does that sound?

@pas256
Copy link

pas256 commented Mar 10, 2017

That works too.

So

Fn::Sub: 
    - "$${NotAServerlessVar}-abcd-${ServerlessVar}-$${NotAServerlessVar2}"
    - NotAServerlessVar: 123
      NotAServerlessVar2: xyz

@pmuens
Copy link
Contributor

pmuens commented Mar 13, 2017

More intuitive would be to use double-$ to escape variable substitution. So $${NotAServerlessVar} would not be treated as variable and end up as ${NotAServerlessVar} in the CF template

Yes, that sounds like a good idea. Since cf would be AWS specific an Serverless is build to support multiple providers from the ground up...

Here's another, old discussion about escaping etc. #1728 (comment)

Furthermore you can use the variableSyntax property to define your own variable syntax: #1764

@et304383
Copy link
Contributor

et304383 commented May 5, 2017

What was ultimately done to address this? Or is escaping a $ to a CloudFormation object not supported yet?

@pkubat
Copy link

pkubat commented May 29, 2017

here is my workaround for now, exclude 'AWS::' as these are the variables that I needed for Fn::Sub.

Example

Resource:
        "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table...

serverless.yml

provider:
  name: aws
  runtime: nodejs6.10
  # Allow for cf "AWS::" variables, see https://serverless.com/framework/docs/providers/aws/guide/variables#using-custom-variable-syntax
  variableSyntax: "\\${(?!AWS::)([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}"

@pmuens
Copy link
Contributor

pmuens commented May 30, 2017

Looks good. Thanks for sharing @pkubat 👍

We currently have a PR (#3694) in the pipeline which enhances the variable syntax and could help here.

@lorengordon
Copy link
Contributor

This plugin does this pretty well, for me anyway.

https://gitlab.com/kabo/serverless-cf-vars

@sam3d
Copy link

sam3d commented Mar 22, 2019

Any updates on this being implemented natively?

@deftomat
Copy link

@sam3d not sure but we solve this by using the serverless.js instead of serverless.yml.
I can say that once you try it, you don’t want to use yaml anymore. JS file can export the promise, so you have an infinite amount of possibilities how to construct the config and this issue becomes obsolete.

I’m not sure why this is not mentioned in a docs as a recommended way how to define your stack 😉

@aaronosb
Copy link

Would definitely also appreciate an update on how to support this without adding additional plugins, I have run into this issue multiple times in the past week and would appreciate a native solution. Like the purposed double escape $$

@seriouscoderone
Copy link

I believe the workaround is to just use Ref https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html

You may have to nest a Ref inside a Fn::Join in order to get the Fn::Sub functionality.

For example:
"Fn::Sub": "arn:aws:iam::${AWS::AccountId}:root"
Becomes:
"Fn::Join": ["", [ "arn:aws:iam::", { "Ref": "AWS::AccountId" }, ":root"]]

@cyberfox1
Copy link

Everyday those people saying serverless just causes more problems than it solves, just use raw CloudFormation, make more and more sense to me.

@ksac1
Copy link

ksac1 commented Jun 25, 2019

I have this issue: An error occurred (ValidationError) when calling the ValidateTemplate operation: Template error: variable names in Fn::Sub syntax must contain only alphanumeric characters, underscores, periods, and colons
ERROR: Job failed: exit code 1

Any solution?

@et304383
Copy link
Contributor

How is this not solved yet? It's been almost 3 years.

@Ungolim
Copy link

Ungolim commented Jan 13, 2020

ping

@et304383
Copy link
Contributor

Seriously, can we just do like a double $$ and be done with it? This is getting ridiculous. I feel so dirty having to use Fn::Join with serverless.

@igorrocha
Copy link

Seriously, can we just do like a double $$ and be done with it? This is getting ridiculous. I feel so dirty having to use Fn::Join with serverless.

It really is a bit frustrating... I've solved my problem using this plugin: https://www.npmjs.com/package/serverless-pseudo-parameters

Variables declared with ${} will be handled by serverless, and variables declared with #{} will be subbed for ${} on the final template, letting CF do its work.

@milesgranger
Copy link

milesgranger commented Apr 15, 2020

I couldn't end up using serverless-pseudo-parameters because it would try to be too smart and inject another !Sub statement for my use case. 🤷‍♂️

So instead this works:

# serverless.yml
provider:
  # serverless ignores ${AWS...} and ${_...}
  variableSyntax: "\\${((?!AWS|_)[ ~:a-zA-Z0-9._@'\",\\-\\/\\(\\)]+?)}"

# later on this works as expected.
foos: 
  - !Sub
    - some-${AWS::AcountId}-${_specialVar}-string
    - _specialVar: bar

Not claiming to be the best solution, but I like it. And one can change the prefix (_) to whatever else is preferred.

@Ownmarc
Copy link

Ownmarc commented Jun 19, 2020

Edit: works with native CF template

You can escape ${} in your templates using ${!} like this:

Resource:
  - !Sub
    - "arn:aws:s3:::${BucketName}/protected/${!cognito-identity.amazonaws.com:sub}/*"
    - {"BucketName": !Ref ParamBucketName}

@et304383
Copy link
Contributor

et304383 commented Jun 19, 2020

@Ownmarc if that's working for you it's because you've defined a variableSyntax value that states that that serverless shouldn't interpret ${!var_name} as a serverless var. That's no different than the solution above your post.

@mnapoli
Copy link
Contributor

mnapoli commented Jul 29, 2020

Hi all, I think I have found a possible solution.

The serverless-pseudo-parameters plugin has been mentioned, and it has the disadvantage of having a custom/weird syntax (#{Resource.Attr}) to avoid collision with serverless variables.

However, I managed to make it work with the serverless variable syntax. Here are a few examples:

functions:
    hello:
        handler: handler.hello
        environment:
            # We can reference `AWS::` variables
            REGION: ${AWS::Region}

            # We can use !Ref inline via the `ref:` prefix:
            BUCKET_NAME: ${ref:MyBucket}

            # We can use !GetAtt inline via the `getatt:` prefix:
            BUCKET_URL: 'https://${getatt:MyBucket.DomainName}'

            # We can use mix all that together in a string:
            REGIONAL_URL: 'https://${ref:MyBucket}.s3.${AWS::Region}.amazonaws.com'

            # We can also mix all of that with actual serverless variables (e.g. `${self:provider.stage}`)

resources:
    Resources:
        MyBucket:
            Type: AWS::S3::Bucket

In my example above, although I don't explicitly write !Sub, the end result is the same: I can "subtract" variables in a string.

And all of those strings are compiled to Fn::Sub in the CloudFormation template generated by serverless.

How it works

Nothing clever. For my proof of concept, I defined the following variables via a plugin:

  • ${AWS::xxx} is compiled to #{AWS::xxx}
  • ${ref::xxx} is compiled to #{xxx}
  • ${getatt::xxx.yyy} is compiled to #{xxx.yyy}

Then, the serverless-pseudo-parameters plugin transforms these variables to !Sub calls.

(here is the PR: svdgraaf/serverless-pseudo-parameters#66)

Now, while it works, is this something that we could eventually integrate into Serverless? (this is a question for maintainers)

I will be opening the same issue in the plugin (to see if we can add support more quickly), but if we can have that officially, I'm up for giving it a try (maybe with some advice/help?). I don't like the 2 step process, maybe we can do better. Also, all those CloudFormation variables do not work locally, but in any case I don't see how we could make things work locally when we reference cloud resources (which is what !Ref and al is all about).


Edit: since the plugin also supports !Sub calls too, this is also valid and working:

functions:
    hello:
        handler: handler.hello
        environment:
            REGION: !Sub ${AWS::Region}
            BUCKET_NAME: !Sub ${ref:MyBucket}
            BUCKET_URL: !Sub 'https://${getatt:MyBucket.DomainName}'
            REGIONAL_URL: !Sub 'https://${ref:MyBucket}.s3.${AWS::Region}.amazonaws.com'

@Nemo64
Copy link

Nemo64 commented Aug 24, 2020

I want to add an alternative.

CloudFormation syntax usually begins with upper case so:

provider:
  # Allow using CloudFormation variable syntax without changing the serverless syntax
  # The differentiating factor is the capitalization of the first character
  # Upper case character means CloudFormation syntax, eg: ${AWS::Region}, ${DatabaseSecret}
  # Everything else means serverless syntax, eg: ${ssm:...}, ${self:...}
  # https://www.serverless.com/framework/docs/providers/aws/guide/variables#using-custom-variable-syntax
  variableSyntax: "\\${((?![A-Z])[ ~:a-zA-Z0-9._@'\",\\-\\/\\(\\)]+?)}"

With that configuration you can then do stuff like this:

provider:
  # [...]
  environment:
    STAGE: ${opt:stage, self:provider.stage} # use serverless syntax as usual
    MAILER_DSN: !Sub 'ses://default?region=${AWS::Region}' # use CloudFormation !Sub as usual
    DATABASE_URL: !Sub '//${AWS::Region}/${Database}' # reference other resource as long as they use the UpperCamelCase naming convention

@mnapoli
Copy link
Contributor

mnapoli commented Sep 22, 2020

@medikoo that's too bad, this issue contains 3 years of discussion about this, with plenty of solutions and workarounds discussed.

It's also very easy to find when users search !Sub, because that's maybe the main use case for supporting CloudFormation ${...} syntax.

@medikoo
Copy link
Contributor

medikoo commented Sep 23, 2020

@mnapoli that's a very valid point. Let me reopen this one and close the newer one

@medikoo medikoo reopened this Sep 23, 2020
@medikoo medikoo added bug/design Functionality design flaw needs feedback and removed enhancement labels Sep 23, 2020
@medikoo medikoo changed the title Fn::Sub and !Sub Variables syntax collides with AWS params syntax Sep 23, 2020
@medikoo
Copy link
Contributor

medikoo commented Sep 23, 2020

I've also proposed a solution in top description, which will most likely solve it for AWS case.

@mnapoli
Copy link
Contributor

mnapoli commented Sep 23, 2020

@medikoo sounds really really good!

CleanShot 2020-09-23 at 14 02 40

That means that both AWS native variables and !Sub will work. And a simple change too, I love it!

Will you implement it or would a PR help?

@medikoo
Copy link
Contributor

medikoo commented Sep 23, 2020

@mnapoli PR will definitely help 🙇 Thank you!

@mnapoli
Copy link
Contributor

mnapoli commented Sep 23, 2020

@medikoo I've opened #8279 (I had to slightly change the regex).

mnapoli added a commit to mnapoli/serverless that referenced this issue Sep 24, 2020
mnapoli added a commit to mnapoli/serverless that referenced this issue Sep 24, 2020
mnapoli added a commit to mnapoli/serverless that referenced this issue Sep 24, 2020
mnapoli added a commit to mnapoli/serverless that referenced this issue Sep 24, 2020
@logicalor
Copy link

logicalor commented Oct 25, 2020

The regex update doesn't cover situations where a variable variable may be used.

eg.

# DESIRED_PROP=propname1
custom:
  propname1: somevalue
  default: someothervalue
  wantedvalue: ${self:custom.${env:DESIRED_PROP, "default"}}

Not sure support for this is intended in Serverless, but the above syntax did resolve prior to the regex update.

@mnapoli
Copy link
Contributor

mnapoli commented Oct 26, 2020

@logicalor can you explain what the problem is?

I tested your string and it seems to be covered by the regex, like the rest:

image

@logicalor
Copy link

logicalor commented Oct 26, 2020

Hi @mnapoli ,

Your example shows that the regex only matches the inner variable within the string. My understanding is that it should match the entire string.

In v2.2.0 of serverless (ie prior to the release with the regex update), I could do something like this:

provider:
  region: ${self:custom.${env.STAGE, 'development'}.regions.${self:custom.${env.STAGE, 'development'}.defaultRegion}, env.AWS_REGION}

custom:
  development:
    defaultRegion: "0"
    regions:
      - ap-southeast-2

and serverless would interpolate the variable placeholders correctly, resulting in 'ap-southeast-2'.

v2.3.0 of serverless and after produce an error with the above.

If I modify v2.8.0 and replace the regex with the version of the regex from v2.2.0 and below, the interpolation works again.

@medikoo
Copy link
Contributor

medikoo commented Oct 28, 2020

@logicalor can you open a new bug report with all the details? (this issue was closed and it addressed other issue)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.