Yan Cui
I help clients go faster for less using serverless technologies.
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.