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

Add chalice generate-sdk command #178

Merged
merged 3 commits into from
Nov 23, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Next Release (TBD)
(`#172 <https://github.com/awslabs/chalice/pull/172>`__)
* Add support for ``DELETE`` and ``PATCH`` in ``chalice local``
(`#167 <https://github.com/awslabs/chalice/issues/167>`__)
* Add ``chalice generate-sdk`` command
(`#178 <https://github.com/awslabs/chalice/pull/178>`__)


0.4.0
Expand Down
12 changes: 12 additions & 0 deletions chalice/awsclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,18 @@ def get_function_policy(self, function_name):
policy = client.get_policy(FunctionName=function_name)
return json.loads(policy['Policy'])

def get_sdk(self, rest_api_id, stage='dev', sdk_type='javascript'):
# type: (str, str, str) -> file
"""Generate an SDK for a given SDK.

Returns a file like object that streams a zip contents for the
generated SDK.

"""
response = self._client('apigateway').get_sdk(
restApiId=rest_api_id, stageName=stage, sdkType=sdk_type)
return response['body']

def add_permission_for_apigateway(self, function_name, region_name,
account_id, rest_api_id, random_id):
# type: (str, str, str, str, str) -> None
Expand Down
46 changes: 45 additions & 1 deletion chalice/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
import json
import sys
import logging
import zipfile
import tempfile
import importlib
import shutil

import click
import botocore.exceptions
Expand All @@ -20,6 +23,7 @@
from chalice.logs import LogRetriever
from chalice import prompts
from chalice.config import Config
from chalice.awsclient import TypedAWSClient


TEMPLATE_APP = """\
Expand Down Expand Up @@ -277,7 +281,6 @@ def url(ctx):
config = create_config_obj(ctx)
session = create_botocore_session(profile=config.profile,
debug=ctx.obj['debug'])
from chalice.awsclient import TypedAWSClient
c = TypedAWSClient(session)
rest_api_id = c.get_rest_api_id(config.app_name)
stage_name = config.stage
Expand All @@ -288,6 +291,47 @@ def url(ctx):
)


@cli.command('generate-sdk')
@click.option('--sdk-type', default='javascript',
type=click.Choice(['javascript']))
@click.argument('outdir')
@click.pass_context
def generate_sdk(ctx, sdk_type, outdir):
# type: (click.Context, str, str) -> None
config = create_config_obj(ctx)
session = create_botocore_session(profile=config.profile,
debug=ctx.obj['debug'])
client = TypedAWSClient(session)
rest_api_id = client.get_rest_api_id(config.app_name)
stage_name = config.stage
if rest_api_id is None:
click.echo("Could not find API ID, has this application "
"been deployed?")
raise click.Abort()
zip_stream = client.get_sdk(rest_api_id, stage=stage_name,
sdk_type=sdk_type)
tmpdir = tempfile.mkdtemp()
with open(os.path.join(tmpdir, 'sdk.zip'), 'wb') as f:
f.write(zip_stream.read())
tmp_extract = os.path.join(tmpdir, 'extracted')
with zipfile.ZipFile(os.path.join(tmpdir, 'sdk.zip')) as z:
z.extractall(tmp_extract)
# The extract zip dir will have a single directory:
# ['apiGateway-js-sdk']
dirnames = os.listdir(tmp_extract)
if len(dirnames) == 1:
full_dirname = os.path.join(tmp_extract, dirnames[0])
if os.path.isdir(full_dirname):
final_dirname = '%s-js-sdk' % config.app_name
full_renamed_name = os.path.join(tmp_extract, final_dirname)
os.rename(full_dirname, full_renamed_name)
shutil.move(full_renamed_name, outdir)
return
click.echo("The downloaded SDK had an unexpected directory structure: %s"
% (', '.join(dirnames)))
raise click.Abort()


def run_local_server(app_obj):
# type: (Chalice) -> None
from chalice import local
Expand Down
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Topics
topics/configfile
topics/multifile
topics/logging
topics/sdks


API Reference
Expand Down
164 changes: 164 additions & 0 deletions docs/source/topics/sdks.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
SDK Generation
==============

The ``@app.route(...)`` information you provide chalice allows
it to create corresponding routes in API Gateway. One of the benefits of this
approach is that we can leverage API Gateway's SDK generation process.
Chalice offers a ``chalice generate-sdk`` command that will automatically
generate an SDK based on your declared routes.

.. note::
The only supported language at this time is javascript.

Keep in mind that chalice itself does not have any logic for generating
SDKs. The SDK generation happens service side in `API Gateway`_, the
``chalice generate-sdk`` is just a high level wrapper around that
functionality.

To generate an SDK for a chalice app, run this command from the project
directory::

$ chalice generate-sdk /tmp/sdk

You should now have a generated javascript sdk in ``/tmp/sdk``.
API Gateway includes a ``README.md`` as part of its SDK generation
which contains details on how to use the javascript SDK.

Example
-------

Suppose we have the following chalice app:

.. code-block:: python

from chalice import Chalice

app = Chalice(app_name='sdktest')

@app.route('/', cors=True)
def index():
return {'hello': 'world'}

@app.route('/foo', cors=True)
def foo():
return {'foo': True}

@app.route('/hello/{name}', cors=True)
def hello_name(name):
return {'hello': name}

@app.route('/users/{user_id}', methods=['PUT'], cors=True)
def update_user(user_id):
return {"msg": "fake updated user", "userId": user_id}


Let's generate a javascript SDK and test it out in the browser.
Run the following command from the project dir::

$ chalice generate-sdk /tmp/sdkdemo
$ cd /tmp/sdkdemo
$ ls -la
-rw-r--r-- 1 jamessar r 3227 Nov 21 17:06 README.md
-rw-r--r-- 1 jamessar r 9243 Nov 21 17:06 apigClient.js
drwxr-xr-x 6 jamessar r 204 Nov 21 17:06 lib

You should now be able to follow the instructions from API Gateway in the
``README.md`` file. Below is a snippet that shows how the generated
javascript SDK methods correspond to the ``@app.route()`` calls in chalice.

.. code-block:: html

<script type="text/javascript">
// Below are examples of how the javascript SDK methods
// correspond to chalice @app.routes()
var apigClient = apigClientFactory.newClient();

// @app.route('/')
apigClient.rootGet().then(result => {
document.getElementById('root-get').innerHTML = JSON.stringify(result.data);
});

// @app.route('/foo')
apigClient.fooGet().then(result => {
document.getElementById('foo-get').innerHTML = JSON.stringify(result.data);
});

// @app.route('/hello/{name}')
apigClient.helloNameGet({name: 'jimmy'}).then(result => {
document.getElementById('helloname-get').innerHTML = JSON.stringify(result.data);
});

// @app.route('/users/{user_id}', methods=['PUT'])
apigClient.usersUserIdPut({user_id: '123'}, 'body content').then(result => {
document.getElementById('users-userid-put').innerHTML = JSON.stringify(result.data);
});
</script>





Example HTML File
~~~~~~~~~~~~~~~~~

If you want to try out the example above, you can use the following index.html
page to test:

.. code-block:: html

<!DOCTYPE html>
<html lang="en">
<head>
<title>SDK Test</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css">
<script type="text/javascript" src="lib/axios/dist/axios.standalone.js"></script>
<script type="text/javascript" src="lib/CryptoJS/rollups/hmac-sha256.js"></script>
<script type="text/javascript" src="lib/CryptoJS/rollups/sha256.js"></script>
<script type="text/javascript" src="lib/CryptoJS/components/hmac.js"></script>
<script type="text/javascript" src="lib/CryptoJS/components/enc-base64.js"></script>
<script type="text/javascript" src="lib/url-template/url-template.js"></script>
<script type="text/javascript" src="lib/apiGatewayCore/sigV4Client.js"></script>
<script type="text/javascript" src="lib/apiGatewayCore/apiGatewayClient.js"></script>
<script type="text/javascript" src="lib/apiGatewayCore/simpleHttpClient.js"></script>
<script type="text/javascript" src="lib/apiGatewayCore/utils.js"></script>
<script type="text/javascript" src="apigClient.js"></script>


<script type="text/javascript">
// Below are examples of how the javascript SDK methods
// correspond to chalice @app.routes()
var apigClient = apigClientFactory.newClient();

// @app.route('/')
apigClient.rootGet().then(result => {
document.getElementById('root-get').innerHTML = JSON.stringify(result.data);
});

// @app.route('/foo')
apigClient.fooGet().then(result => {
document.getElementById('foo-get').innerHTML = JSON.stringify(result.data);
});

// @app.route('/hello/{name}')
apigClient.helloNameGet({name: 'jimmy'}).then(result => {
document.getElementById('helloname-get').innerHTML = JSON.stringify(result.data);
});

// @app.route('/users/{user_id}', methods=['PUT'])
apigClient.usersUserIdPut({user_id: '123'}, 'body content').then(result => {
document.getElementById('users-userid-put').innerHTML = JSON.stringify(result.data);
});
</script>
</head>
<body>
<div><h5>result of rootGet()</h5><pre id="root-get"></pre></div>
<div><h5>result of fooGet()</h5><pre id="foo-get"></pre></div>
<div><h5>result of helloNameGet({name: 'jimmy'})</h5><pre id="helloname-get"></pre></div>
<div><h5>result of usersUserIdPut({user_id: '123'})</h5><pre id="users-userid-put"></pre></div>
</body>
</html>


.. _API Gateway: http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-generate-sdk.html
12 changes: 12 additions & 0 deletions tests/functional/test_awsclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,3 +412,15 @@ def test_can_add_permission_when_policy_does_not_exist(self, stubbed_session):
TypedAWSClient(stubbed_session).add_permission_for_apigateway_if_needed(
'name', 'us-west-2', '123', 'rest-api-id', 'random-id')
stubbed_session.verify_stubs()

def test_get_sdk(self, stubbed_session):
apig = stubbed_session.stub('apigateway')
apig.get_sdk(
restApiId='rest-api-id',
stageName='dev',
sdkType='javascript').returns({'body': 'foo'})
stubbed_session.activate_stubs()
awsclient = TypedAWSClient(stubbed_session)
response = awsclient.get_sdk('rest-api-id', 'dev', 'javascript')
stubbed_session.verify_stubs()
assert response == 'foo'