Yan Cui
I help clients go faster for less using serverless technologies.
This article is brought to you by
Step Functions, EventBridge, MSK, DynamoDB…stop hacking together AWS services and get back to building!
CloudFormation supports a number of intrinsic functions and Fn::Join
(or !Join
) is often used to construct parameterised names and paths.
The Serverless framework, for instance, uses it extensively. A quick look in a CloudFormation it generates I can see Fn::Join
used for:
- IAM policy names
- IAM role names
- IAM principals
- API Gateway URIs
- Resource ARNs
and many more.
But it’s not just the frameworks that are using Fn::Join
heavily. They also show up in our own code all the time as well. For example, to construct the ARN for a resource, or the URI for an API Gateway endpoint.
I find these very hard to comprehend, and my protip for you today is to use Fn::Sub
(or the !Sub
shorthand) instead.
Many folks would use Fn::Sub
when they need to reference pseudo parameters such as AWS::Region
and AWS::AccountId
, for example:
!Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:vpc/${vpc}'
But Fn::Sub
also lets you provide your own parameters. For example:
!Sub - 'arn:aws:s3:::${Bucket}/*' - { Bucket: Ref MyBucket }
The advantage over Fn::Join
is that you can see the pattern of the interpolated string. Whereas with Fn::Join
you’ll have to construct the pattern in your mind, which requires far more cognitive energy.
!Join - '' - - 'arn:aws:s3:::' - !Ref MyBucket - '/*'
Here are a few side-by-side comparisons to drive home the message.
Example 1: IAM role name
RoleName: # hello-world-dev-{region}-lambdaRole !Join - '-' - - 'hello-world' - 'dev' - !Ref 'AWS::Region' - 'lambdaRole'
with Fn::Sub
:
PolicyName: !Sub 'hello-world-dev-${AWS::Region}-lambdaRole
Example 2: API Gateway integration URI
Uri: # arn:{partition}:apigateway:{region}:.../{lambda}/invocations !Join - '' - - 'arn:' - Ref: AWS::Partition - ':apigateway:' - Ref: AWS::Region - ':lambda:path/2015-03-31/functions/' - !GetAtt 'HelloLambdaFunction.Arn' - '/invocations'
with Fn::Sub
:
Uri: !Sub - 'arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015/03/31/functions/${Function}/invocations' - { Function: !GetAtt 'HelloLambdaFunction.Arn' }
Example 3: Lambda permission for API Gateway
SourceArn: # arn:{partition}:execute-api:{region}:.../*/* !Join: - '' - - 'arn:' - Ref: AWS::Partition - ':execute-api:' - Ref: AWS::Region - ':' - Ref: AWS::AccountId - ':' - Ref: ApiGatewayRestApi - '/*/*'
with Fn::Sub
:
SourceArn: !Sub - 'arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/*' - { RestApi: Ref: ApiGatewayRestApi }
Suffice to say that the Fn::Sub
version is easier to understand in every case! Now that you have seen what Fn::Sub
can do, I hope you will prefer it to Fn::Join
going forward.
Finally, if you’re using the Serverless framework and need more expressive power than the intrinsic functions can offer, then check out this plugin. It lets you use a number of “extrinsic” functions such as Fn::Substring
or Fn::StartsWith
anywhere in your serverless.yml
.
Whenever you’re ready, here are 3 ways I can help you:
- Production-Ready Serverless: Join 20+ AWS Heroes & Community Builders and 1000+ other students in levelling up your serverless game. This is your one-stop shop for quickly levelling up your serverless skills.
- I help clients launch product ideas, improve their development processes and upskill their teams. If you’d like to work together, then let’s get in touch.
- Join my community on Discord, ask questions, and join the discussion on all things AWS and Serverless.