Tibberzz
Tibberzz

Reputation: 551

Cloud Formation Function from Custom Resource

I have a CF template where I supply a parameter and a conditional checks it to determine how to construct a bucket name. If it's prod, it should be "name". If it's not prod, it should be "name_environment" like this:

# # # # # # # # # # # # # # # #
#                             #
#  Input Parameters           #
#  Prefix: t3st-acc0un7-123   #
#   Stage: dev                #
#                             #
#  Expected S3 Name Output    #
#  t3st-acc0un7-123-dev       #
#  t3st-acc0un7-123-dev-2     #
#                             #
# # # # # # # # # # # # # # # #
#                             #
#  Input Parameters           #
#  Prefix: t3st-acc0un7-123   #
#   Stage: prod               #
#                             #
#  Expected S3 Name Output    #
#  t3st-acc0un7-123           #
#  t3st-acc0un7-123-2         #
#                             #
# # # # # # # # # # # # # # # #

Here's my template that does this:

Parameters:
  Prefix:
    Type: String
    Default: t3st-acc0un7-123
  Stage:
    Type: String
    AllowedPattern: "([a-z]|[0-9])+"

Conditions:
  IsProdStage:
    Fn::Equals:
    - !Ref Stage
    - prod

Resources:
  TestBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: 
          Fn::If:
          - IsProdStage
          - !Ref Prefix
          - !Join
            - '-'
            - 
              - !Ref Prefix
              - !Ref Stage

  TestBucket2:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: 
        Fn::If:
        - IsProdStage
        - !Join
          - '-'
          -
            - !Ref Prefix
            - '2'
        - !Join
          - '-'
          - 
            - !Ref Prefix
            - !Ref Stage
            - '2'

In the first sample template, the conditional and join logic are duplicated. I basically want to store the value of the conditional somewhere to call from each subsequent function, instead of duplicating the logic.

In this next example, I try to use custom resources to call a dummy lambda (because ServiceToken is required) so I can set a Value property on the TestCustomResource custom resource based on the condition & input and read from it from the other resources I create.

Parameters:
  Prefix:
    Type: String
    Default: t3st-acc0un7-123
  Stage:
    Type: String
    AllowedPattern: "([a-z]|[0-9])+"

Conditions:
  IsProdStage:
    Fn::Equals:
    - !Ref Stage
    - prod

Resources:
  TestBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: !GetAtt TestCustomResource.Value

  TestBucket2:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Join
        - '-'
        -
          - !GetAtt TestCustomResource.Value
          - 2

  TestCustomResource:
    Type: Custom::Codswallop
    Properties:
      ServiceToken: !GetAtt DummyLambda.Arn
      Value:
        Fn::If:
        - IsProdStage
        - !Ref Prefix
        - !Join
          - '-'
          - 
            - !Ref Prefix
            - !Ref Stage

  DummyLambda:
    Type: "AWS::Lambda::Function"
    Properties:
      Code: 
        ZipFile: >
          print("")
      Handler: lambda_function.lambda_handler
      Role: !GetAtt DummyRole.Arn
      Runtime: python3.6

  DummyRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
            - Effect: Allow
              Principal:
                Service:
                  - lambda.amazonaws.com
              Action:
                - sts:AssumeRole
      RoleName: DummyRole

I know it's a little hacky with the dummy lambda, but this seems like it would be very useful functionality to be able to store a computed value for use around the template. The second example gives an error like so: TestCustomResource - Custom Resource failed to stabilize in expected time.

(It's possible the answer is to use multiple templates that depend on previous CF output values or nested templates.)

Upvotes: 0

Views: 798

Answers (1)

John Rotenstein
John Rotenstein

Reputation: 269101

Do not use a Custom Resource for fake purposes.

The function needs to "call back" to CloudFormation when the operation is done. Your custom resource has no code, so it will never call back, so your template will never complete.

If your first example works, stick with it. Your second option (aside from not working) is difficult for future IT people to understand and maintain.

Always go for easy future maintenance over elegant hacks. (Spoken by a person who has had to maintain other people's elegant hacks.)

Upvotes: 1

Related Questions