Deploying the API

So far, we've defined the API and its resource, but these changes are not in production until we create a deployment. To enable the API in production, we should deploy it.

If you have read the CloudFormation documentation, you might have seen the AWS::ApiGateway::Deployment resource. It creates an API Gateway deployment perfectly. However, it has a caveat; it creates an immutable resource, so in order to redeploy the latest changes whenever you update the CloudFormation stack, you must add a new Deployment resource. We do not know why such a bad design decision has been taken, but obviously, this resource does not meet our automation needs.

To overcome this issue, we will leverage a very nice feature of CloudFormation: Lambda backed custom resources. We will use a small Lambda function that will be triggered whenever the CloudFormation stack is updated and will invoke the AWS API to create a deployment. Specially for this book, the necessary Node.js code is created for every region and uploaded to S3 buckets, and we will be using it directly in our template.

If you want to check the content of the code, you can download it from https://s3.amazonaws.com/serverless-arch-us-east-1/serverless.zip.

We can first create the necessary role and the Lambda function, and then we can place it at the beginning of the Resources section:

"DeploymentLambdaRole": { 
  "Type": "AWS::IAM::Role", 
  "Properties": { 
    "AssumeRolePolicyDocument": { 
      "Version": "2012-10-17", 
      "Statement": [ 
        { 
          "Effect": "Allow", 
          "Principal": { 
            "Service": [ 
              "lambda.amazonaws.com" 
            ] 
          }, 
          "Action": [ 
            "sts:AssumeRole" 
          ] 
        } 
     ] 
   }, 
  "Path": "/", 
    "ManagedPolicyArns": [ 
      "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole" 
    ], 
    "Policies": [ 
      { 
        "PolicyName": "LambdaExecutionPolicy", 
        "PolicyDocument": { 
          "Version": "2012-10-17", 
          "Statement": [ 
            { 
              "Effect": "Allow", 
              "Action": [ 
                "lambda:PublishVersion", 
                "apigateway:POST" 
              ], 
              "Resource": [ 
                "*" 
              ]   
            } 
          ] 
        } 
      } 
    ] 
} }, "DeploymentLambda": { "Type": "AWS::Lambda::Function", "Properties": { "Role": { "Fn::GetAtt": [ "PublishNewVersionRole", "Arn" ] }, "Handler": "index.handler", "Runtime": "nodejs4.3", "Code": { "S3Bucket": { "Fn::Sub": "serverless-arch-${AWS::Region}" }, "S3Key": "serverless.zip" } } }

We will skip explaining this part step by step, but as you can understand with your current AWS experience, here, we create an IAM role that grants apigateway:POST and lambda:PublishVersion permissions. You might ask at this point why we also granted the lambda:PublishVersion permission, which we have never seen so far. That is because in the following stages, we will also version our Lambda functions for the rollback capability, and this Lambda function will also help us for that. To not repeat the same process, we just prepare the IAM role for this feature.

After we create the necessary Lambda function, we can add the custom resource to the tail of the Resources section:

"ApiDeployment": { 
      "DependsOn": [ 
        "TestGetMethod" 
      ], 
      "Type": "Custom::ApiDeployment", 
      "Properties": { 
        "ServiceToken": { 
          "Fn::GetAtt": [ 
            "DeploymentLambda", 
            "Arn" 
          ] 
        }, 
        "RestApiId": { 
          "Ref": "RestApi" 
        }, 
        "StageName": "production", 
        "DeploymentTime": { 
          "Ref": "DeploymentTime" 
        } 
      } 
    } 

This is the standard syntax for Lambda-backed custom resources. Only ServiceToken is mandatory, and it should be the ARN of the Lambda function. Other parameters are passed to the Lambda function in the event and our Lambda function consumes them to create a new API deployment via API calls and returns the result. A Lambda-backed custom resource is an advanced topic of CloudFormation. Here, we do not dive deep into the details since it is directly related to our topic, but if you are more interested in the topic, it is recommended that you read the AWS documentation: (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources-lambda.html).

If your application grows and you hit the limits of CloudFormation, you can write other custom resources and keep your infrastructure in CloudFormation.

After this step, we can deploy the project again, and this time, our API should be publicly available. To see the URL of your API, you can navigate to the AWS Console. If you want to see it via CLI, you can run the following command:

$ aws cloudformation describe-stack-resources --region us-east-1
--stack-name serverlessbook

This will print the created resources for our stack. Among them, you can see the AWS::ApiGateway::RestApi resource, such as the following JSON:

{ 
  "StackId": "arn:aws:cloudformation:us-east-
1:423915886527:stack/serverlessbook/8bb69620-9dd6-11e6-9003-50d5cd24fac6", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::ApiGateway::RestApi", "Timestamp": "2016-11-13T15:06:14.375Z", "StackName": "serverlessbook", "PhysicalResourceId": "eciv8og4wj", "LogicalResourceId": "RestApi" }

Here, PhysicalResourceId is our API's ID and the resulting URL would then be https://eciv8og4wj.execute-api.us-east-1.amazonaws.com/production.

Let's try to access our first endpoint using CURL in the verbose mode:

    $ curl -v  https://eciv8og4wj.execute-api.us-east-1.amazonaws.com/
production/test?value=hello+world
* Hostname was NOT found in DNS cache * Trying 52.222.157.193... * Connected to eciv8og4wj.execute-api.us-east-1.amazonaws.com (52.222.157.193) port 443 (#0) * successfully set certificate verify locations: * CAfile: none CApath: /etc/ssl/certs * SSLv3, TLS handshake, Client hello (1): * SSLv3, TLS handshake, Server hello (2): * SSLv3, TLS handshake, CERT (11): * SSLv3, TLS handshake, Server key exchange (12): * SSLv3, TLS handshake, Server finished (14): * SSLv3, TLS handshake, Client key exchange (16): * SSLv3, TLS change cipher, Client hello (1): * SSLv3, TLS handshake, Finished (20): * SSLv3, TLS change cipher, Client hello (1): * SSLv3, TLS handshake, Finished (20): * SSL connection using ECDHE-RSA-AES128-GCM-SHA256 * Server certificate: * subject: C=US; ST=Washington; L=Seattle; O=Amazon.com, Inc.; CN=*.execute-api.us-east-1.amazonaws.com * start date: 2016-06-08 00:00:00 GMT * expire date: 2017-07-08 23:59:59 GMT * subjectAltName: eciv8og4wj.execute-api.us-east-1.amazonaws.com matched * issuer: C=US; O=Symantec Corporation; OU=Symantec Trust Network; CN=Symantec Class 3 Secure Server CA - G4 * SSL certificate verify ok. > GET /production/test?value=hello+world HTTP/1.1 > User-Agent: curl/7.35.0 > Host: eciv8og4wj.execute-api.us-east-1.amazonaws.com > Accept: */* > < HTTP/1.1 200 OK < Content-Type: application/json < Content-Length: 23 < Connection: keep-alive < Date: Mon, 21 Nov 2016 21:16:15 GMT < x-amzn-RequestId: bc4e5395-b02f-11e6-91ae-fd48641b4f02 < X-Amzn-Trace-Id: Root=1-5833641f-d12570a2d1e70be03bd61c8f < X-Cache: Miss from cloudfront < Via: 1.1 ec27b2a550cb7db6ef54f74603010b29.cloudfront.net (CloudFront) < X-Amz-Cf-Id: qN1K2jmEyeBMPSrPIUejXyTVwj8BhxZTNm4CCiYaITnTw52WDTlewg== < * Connection #0 to host eciv8og4wj.execute-api.us-east-1.amazonaws.com left intact {"value":"hello world"}

Great! Our first serverless REST API is online and working.