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

Intrinsic Function !Join does not work on DependsOn property #259

Closed
voigt opened this issue Jan 19, 2018 · 3 comments
Closed

Intrinsic Function !Join does not work on DependsOn property #259

voigt opened this issue Jan 19, 2018 · 3 comments

Comments

@voigt
Copy link

voigt commented Jan 19, 2018

Hey Guys,

I have the following SAM configuration, which works quite well so far:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: "Fancy Description"

Mappings:
  Constants:
    ServerlessService:
      Version: 0.0.1

Globals:
  # Sets global settings for ALL Lambda functions
  Function:
    Runtime: nodejs6.10
    Timeout: 30
    Handler: index.handle

Resources:
  ServerlessService:
    Type: AWS::Serverless::Api
    Properties:
      StageName: test
      DefinitionBody:
          'Fn::Transform':
            Name: 'AWS::Include'
            Parameters:
              Location: s3://<ServerlessService-S3-Bucket>/swagger.yml

  ServerlessServiceDefaultUsagePlan:
    Type: AWS::ApiGateway::UsagePlan
    DependsOn: ServerlessServicetestStage
    Properties:
      ApiStages:
        - ApiId: !Ref ServerlessService
          Stage: test
      Description: Default Serverless-Service Usage Plan
      Quota:
        Limit: 5000
        Period: MONTH
      Throttle:
        BurstLimit: 40
        RateLimit: 20
      UsagePlanName: serverless-service-default-plan

  ServerlessServiceFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ../packages/lambda/functions/serverlessServiceFunction
      Events:
        ProxyApiRoot:
          Type: Api
          Properties:
            RestApiId: !Ref ServerlessService
            Path: /path/with/{params}
            Method: POST

Please notice, that its only working, because I'm using the undocumented "feature" (or should I say hack?) as recommended here.

So have a closer look at this part:

ServerlessServiceDefaultUsagePlan:
    Type: AWS::ApiGateway::UsagePlan
    DependsOn: ServerlessServicetestStage
    Properties:
...

Note, that the Resource ServerlessServicetestStage seems to be newly created by SAM (or what ever) and is never specified in the yaml file. Its very unintuitive to come up with this Resource name! Its really not nice, but as long as it works I don't complain.

But here is my issue: as I want to make the stage test at least a bit more flexible I tried to utilize the !Join intrinsic function.

  ServerlessServiceDefaultUsagePlan:
    Type: AWS::ApiGateway::UsagePlan
    DependsOn: !Join
      - ''
      - - 'ServerlessService'
        - 'test'
        - 'Stage'
    Properties:
    ...

Which fails with

Failed to create the changeset: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure
 state Status: FAILED. Reason: Template format error: DependsOn must be a string or list of strings.

The ultimate goal is to achieve this:

  ServerlessServiceDefaultUsagePlan:
    Type: AWS::ApiGateway::UsagePlan
    DependsOn: !Join
      - ''
      - - 'ServerlessService'
        - !Sub ${StageName}
        - 'Stage'
    Properties:
    ...

It seems that !Join does not work on DependsOn.

I have the feeling that I'm failing to do a dirty workaround of an even dirtier workaround. What am I missing? What is the cleanest way to have a parameter like this:

Parameters:
  StageName:
    Description: API Stage Name
    Type: String
    Default: test

And use !Sub ${StageName} where a Stage Name is needed?

I read #32 and #97. Both got closed without a proper solution of the issue that a UsagePlan is depending on an APIGateway. Help is very appreciated.

@sanathkr
Copy link
Contributor

sanathkr commented Jan 19, 2018

Hey, so the stage resource name you are constructing is not a hack. That's how SAM constructs stage names and we won't change that behavior in future. We have documented this here. Changing a resource's name would make CloudFormation delete/re-create the resource. So SAM will never change the resource naming structure.

DependsOn does not work with intrinsic functions. This is a CloudFormation limitation that SAM cannot unfortunately circumvent. We are working on two things that will fix the problem for you:

  1. UsagePlans as first class SAM supported entities - Support API Gateway Logging Metrics, Usage Plans, EndpointConfiguration, and CORS #248
  2. Better mechanism to refer to SAM-generated resources - Ex: !Ref ServerlessService.Stage will resolve to !Ref ServerlessServicetestStage internally. We introduced some flavor of this for Safe Lambda Deployments (ability to refer to Lambda Version created by SAM - !Ref MyFunction.Version)

At the moment, I think you should continue using the DependsOn approach without the worry that this is a hack.

@jakob-karnowski
Copy link

SAM will never change the resource naming structure

I also found out, that the logical ID of a stage is the concatenation logicalIdOfRestApi+stageName+"Stage".
However since today I experience, that the naming structure has changed to logicalIdOfRestApi+"Stage".

This is why the following works for me. My Api has the logical name Api and I use DependsOn with ApiStage:

...
Parameters:
  ApiKeyId:
    Description: "The ID of an existing ApiKey"
    Type: String
  ApiName:
    Description: "The Name of the Api"
    Type: String
  StageName:
    Description: "The stage to be deployed to"
    Type: String
    Default: production

Resources:
  FooFunction:
    Type: AWS::Serverless::Function
...

  Api:
    Type: AWS::Serverless::Api
    Properties:
      Name: !Ref ApiName
      StageName: !Ref StageName
      DefinitionBody:
        swagger: "2.0"
        schemes:
        - "https"
        paths:
          '/scaled':
            get:
              responses: {}
              x-amazon-apigateway-integration:
                uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${FooFunction.Arn}/invocations
                passthroughBehavior: "when_no_match"
                httpMethod: "POST"
                type: aws_proxy
              security:
                - api_key: []
        securityDefinitions:
          api_key:
            type: "apiKey"
            name: "x-api-key"
            in: "header"
        x-amazon-apigateway-api-key-source : "HEADER"

  ServiceUsagePlan:
    Type: "AWS::ApiGateway::UsagePlan"
    DependsOn: ApiStage
    Properties:
      ApiStages:
        - ApiId:
            Ref: Api
          Stage: !Ref StageName
  ServiceUsagePlanKey:
    Type: "AWS::ApiGateway::UsagePlanKey"
    DependsOn: ServiceUsagePlan
    Properties :
      KeyId: !Ref ApiKeyId
      KeyType: API_KEY
      UsagePlanId:
        Ref: ServiceUsagePlan
...

@david-katz david-katz mentioned this issue Nov 19, 2018
4 tasks
@deuscapturus
Copy link

Workaround

This seems to work.

  TaskService:
    Type: AWS::ECS::Service
    #DependsOn: !If [WaitBeforeECSService, 'TaskServiceWaitCondition', !Ref 'AWS::NoValue']
    Metadata:
      DependsOn: !If [WaitBeforeECSService, !Ref 'TaskServiceWaitCondition', !Ref 'AWS::NoValue']

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

No branches or pull requests

5 participants