Repository: localstack/localstack

Issue:
API Gateway CreateRestApi returns incorrect timestamp format
I’m using Terraform to try to create an API Gateway REST API. Under the hood Terraform uses `aws-sdk-go`, the Go version of Amazon’s SDK. When Terraform/`aws-sdk-go` calls the endpoint to create a REST API, this is the response returned from LocalStack:

```json
{"description": null, "createdDate": "2017-12-14T23:07:15.206Z", "id": "58A-Z620A-Z560", "name": "lambda-api-hello-gw"}
```

And `aws-sdk-go` crashes on this:

```
* aws_api_gateway_rest_api.main: Error creating API Gateway:
SerializationError: failed decoding JSON RPC response
caused by: unsupported value: <nil> (*time.Time)
```

The response from real AWS, which doesn’t crash `aws-sdk-go`, is this:

```json
{"createdDate":1513282360,"endpointConfiguration":{"ipv6":false,"types":["EDGE"]},"id":"rnh2djtis0","name":"lambda-api-hello-gw"}
```

Note that `createdDate` is `"2017-12-14T23:07:15.206Z"` from LocalStack and `1513282360` from AWS. I suspect this is the problem. (For some reason, both the Python and Node AWS clients are fine with LocalStack’s response; only the Go client chokes on it.)

I’m trying to find where in the code I can convert the format of this timestamp, but I’m stumped. I can’t quite figure out how `generic_proxy.py` and `apigateway_listener.py` work together; it seems that the request from Terraform is getting proxied through LocalStack to some other process, which is a running Moto server? How does this work?

Within Moto’s code, it seems pretty clear that the offending line is here:

https://github.com/spulec/moto/blob/e75f3ef4d4f7b0fa7e14dbb4431d379b68ae5502/moto/apigateway/models.py#L311-L317

How would we go about updating this? A PR to that repo, and then an update to the `requirements.txt` in LocalStack? Or does LocalStack modify the response from Moto server, fixing issues like this?
API Gateway CreateRestApi returns incorrect timestamp format
I’m using Terraform to try to create an API Gateway REST API. Under the hood Terraform uses `aws-sdk-go`, the Go version of Amazon’s SDK. When Terraform/`aws-sdk-go` calls the endpoint to create a REST API, this is the response returned from LocalStack:

```json
{"description": null, "createdDate": "2017-12-14T23:07:15.206Z", "id": "58A-Z620A-Z560", "name": "lambda-api-hello-gw"}
```

And `aws-sdk-go` crashes on this:

```
* aws_api_gateway_rest_api.main: Error creating API Gateway:
SerializationError: failed decoding JSON RPC response
caused by: unsupported value: <nil> (*time.Time)
```

The response from real AWS, which doesn’t crash `aws-sdk-go`, is this:

```json
{"createdDate":1513282360,"endpointConfiguration":{"ipv6":false,"types":["EDGE"]},"id":"rnh2djtis0","name":"lambda-api-hello-gw"}
```

Note that `createdDate` is `"2017-12-14T23:07:15.206Z"` from LocalStack and `1513282360` from AWS. I suspect this is the problem. (For some reason, both the Python and Node AWS clients are fine with LocalStack’s response; only the Go client chokes on it.)

I’m trying to find where in the code I can convert the format of this timestamp, but I’m stumped. I can’t quite figure out how `generic_proxy.py` and `apigateway_listener.py` work together; it seems that the request from Terraform is getting proxied through LocalStack to some other process, which is a running Moto server? How does this work?

Within Moto’s code, it seems pretty clear that the offending line is here:

https://github.com/spulec/moto/blob/e75f3ef4d4f7b0fa7e14dbb4431d379b68ae5502/moto/apigateway/models.py#L311-L317

How would we go about updating this? A PR to that repo, and then an update to the `requirements.txt` in LocalStack? Or does LocalStack modify the response from Moto server, fixing issues like this?


Discussion:

Resolving patch:
diff --git a/localstack/services/apigateway/apigateway_listener.py b/localstack/services/apigateway/apigateway_listener.py
--- a/localstack/services/apigateway/apigateway_listener.py
+++ b/localstack/services/apigateway/apigateway_listener.py
@@ -2,13 +2,14 @@
 import logging
 import json
 import requests
+import dateutil.parser
 from requests.models import Response
 from flask import Response as FlaskResponse
 from localstack.constants import APPLICATION_JSON, PATH_USER_REQUEST
 from localstack.config import TEST_KINESIS_URL
 from localstack.utils import common
 from localstack.utils.aws import aws_stack
-from localstack.utils.common import to_str
+from localstack.utils.common import to_str, to_bytes
 from localstack.services.awslambda import lambda_api
 from localstack.services.kinesis import kinesis_listener
 from localstack.services.generic_proxy import ProxyListener
@@ -251,6 +252,19 @@ def forward_request(self, method, path, data, headers):
 
         return True
 
+    def return_response(self, method, path, data, headers, response):
+        try:
+            response_data = json.loads(response.content)
+            # Fix an upstream issue in Moto API Gateway, where it returns `createdDate` as a string
+            # instead of as an integer timestamp:
+            # see https://github.com/localstack/localstack/issues/511
+            if 'createdDate' in response_data and not isinstance(response_data['createdDate'], int):
+                response_data['createdDate'] = int(dateutil.parser.parse(response_data['createdDate']).strftime('%s'))
+                response._content = to_bytes(json.dumps(response_data))
+                response.headers['Content-Length'] = len(response.content)
+        except Exception:
+            pass
+
 
 # instantiate listener
 UPDATE_APIGATEWAY = ProxyListenerApiGateway()
diff --git a/localstack/services/sns/sns_listener.py b/localstack/services/sns/sns_listener.py
--- a/localstack/services/sns/sns_listener.py
+++ b/localstack/services/sns/sns_listener.py
@@ -89,7 +89,7 @@ def forward_request(self, method, path, data, headers):
 
     def return_response(self, method, path, data, headers, response):
         # This method is executed by the proxy after we've already received a
-        # response from the backend, hence we can utilize the "reponse" variable here
+        # response from the backend, hence we can utilize the "response" variable here
         if method == 'POST' and path == '/':
             req_data = urlparse.parse_qs(to_str(data))
             req_action = req_data['Action'][0]


Test patch: