From 3140319237cdc67e28e76481fa97b3723ee17ff4 Mon Sep 17 00:00:00 2001 From: Kevin Deus Date: Wed, 2 Nov 2016 11:25:25 -0700 Subject: [PATCH 01/10] Copy Endpoints Python Echo sample to a new location. This will be its new home, once we clean up the docs. --- endpoints/echo/Dockerfile.container-engine | 12 ++ endpoints/echo/README.md | 185 ++++++++++++++++++ endpoints/echo/app.yaml | 12 ++ endpoints/echo/clients/echo-client.py | 60 ++++++ .../echo/clients/google-id-token-client.py | 82 ++++++++ endpoints/echo/clients/google-jwt-client.py | 92 +++++++++ .../service_to_service_gae_default/app.yaml | 7 + .../service_to_service_gae_default/main.py | 83 ++++++++ .../app.yaml | 7 + .../main.py | 98 ++++++++++ .../service_to_service_non_default/app.yaml | 7 + .../appengine_config.py | 3 + .../service_to_service_non_default/main.py | 94 +++++++++ endpoints/echo/main.py | 94 +++++++++ endpoints/echo/main_test.py | 82 ++++++++ endpoints/echo/requirements.txt | 6 + endpoints/echo/swagger.yaml | 162 +++++++++++++++ 17 files changed, 1086 insertions(+) create mode 100644 endpoints/echo/Dockerfile.container-engine create mode 100644 endpoints/echo/README.md create mode 100644 endpoints/echo/app.yaml create mode 100755 endpoints/echo/clients/echo-client.py create mode 100644 endpoints/echo/clients/google-id-token-client.py create mode 100644 endpoints/echo/clients/google-jwt-client.py create mode 100644 endpoints/echo/clients/service_to_service_gae_default/app.yaml create mode 100644 endpoints/echo/clients/service_to_service_gae_default/main.py create mode 100644 endpoints/echo/clients/service_to_service_google_id_token/app.yaml create mode 100644 endpoints/echo/clients/service_to_service_google_id_token/main.py create mode 100644 endpoints/echo/clients/service_to_service_non_default/app.yaml create mode 100644 endpoints/echo/clients/service_to_service_non_default/appengine_config.py create mode 100644 endpoints/echo/clients/service_to_service_non_default/main.py create mode 100644 endpoints/echo/main.py create mode 100644 endpoints/echo/main_test.py create mode 100644 endpoints/echo/requirements.txt create mode 100644 endpoints/echo/swagger.yaml diff --git a/endpoints/echo/Dockerfile.container-engine b/endpoints/echo/Dockerfile.container-engine new file mode 100644 index 00000000000..adadbf72888 --- /dev/null +++ b/endpoints/echo/Dockerfile.container-engine @@ -0,0 +1,12 @@ +FROM debian:jessie + +RUN apt-get update && \ + apt-get install -y python2.7 python-pip && \ + apt-get clean && \ + rm /var/lib/apt/lists/*_* + +ADD . /app +WORKDIR /app + +RUN pip install -r requirements.txt +ENTRYPOINT ["gunicorn", "-b", ":8081", "main:app"] diff --git a/endpoints/echo/README.md b/endpoints/echo/README.md new file mode 100644 index 00000000000..5276fe0d5e4 --- /dev/null +++ b/endpoints/echo/README.md @@ -0,0 +1,185 @@ +# Google Cloud Endpoints & App Engine Flexible Environment & Python + +This sample demonstrates how to use Google Cloud Endpoints on Google App Engine Flexible Environment using Python. + +This sample consists of two parts: + +1. The backend +2. The clients + +## Running locally + +### Running the backend + +For more info on running Flexible applications locally, see [the getting started documentation](https://cloud.google.com/python/getting-started/hello-world). + +Install all the dependencies: +```bash +$ virtualenv env +$ source env/bin/activate +$ pip install -r requirements.txt +``` + +Run the application: +```bash +$ python main.py +``` + +### Using the echo client + +With the app running locally, you can execute the simple echo client using: +```bash +$ python clients/echo-client.py http://localhost:8080 APIKEY helloworld +``` + +The `APIKEY` doesn't matter as the endpoint proxy is not running to do authentication. + +## Deploying to Google App Engine + +Open the `swagger.yaml` file and in the `host` property, replace +`YOUR-PROJECT-ID` with your project's ID. + +Then, deploy the sample using `gcloud`: +```bash +$ gcloud beta app deploy +``` + +Once deployed, you can access the application at https://YOUR-PROJECT-ID.appspot.com/. + +### Using the echo client + +With the project deployed, you'll need to create an API key to access the API. + +1. Open the Credentials page of the API Manager in the [Cloud Console](https://console.cloud.google.com/apis/credentials). +2. Click 'Create credentials'. +3. Select 'API Key'. +4. Choose 'Server Key' + +With the API key, you can use the echo client to access the API: +```bash +$ python clients/echo-client.py https://YOUR-PROJECT-ID.appspot.com YOUR-API-KEY helloworld +``` + +### Using the JWT client (with key file) + +The JWT client demonstrates how to use a service account to authenticate to endpoints with the service account's private key file. To use the client, you'll need both an API key (as described in the echo client section) and a service account. To create a service account: + +1. Open the Credentials page of the API Manager in the [Cloud Console](https://console.cloud.google.com/apis/credentials). +2. Click 'Create credentials'. +3. Select 'Service account key'. +4. In the 'Select service account' dropdown, select 'Create new service account'. +5. Choose 'JSON' for the key type. + +To use the service account for authentication: + +1. Update the `google_jwt`'s `x-jwks_uri` in `swagger.yaml` with your service account's email address. +2. Redeploy your application. + +Now you can use the JWT client to make requests to the API: +```bash +$ python clients/google-jwt-client.py https://YOUR-PROJECT-ID.appspot.com YOUR-API-KEY /path/to/service-account.json +``` + +### Using the ID Token client (with key file) + +The ID Token client demonstrates how to use user credentials to authenticate to endpoints. To use the client, you'll need both an API key (as described in the echo client section) and a OAuth2 client ID. To create a client ID: + +1. Open the Credentials page of the API Manager in the [Cloud Console](https://console.cloud.google.com/apis/credentials). +2. Click 'Create credentials'. +3. Select 'OAuth client ID'. +4. Choose 'Other' for the application type. + +To use the client ID for authentication: + +1. Update the `/auth/info/googleidtoken`'s `audiences` in `swagger.yaml` with your client ID. +2. Redeploy your application. + +Now you can use the client ID to make requests to the API: +```bash +$ python clients/google-id-token-client.py https://YOUR-PROJECT-ID.appspot.com YOUR-API-KEY /path/to/client-id.json +``` + +### Using the App Engine default service account client (no key file needed) + +The App Engine default service account client demonstrates how to use the Google App Engine default service account to authenticate to endpoints. +We refer to the project that serves API requests as the server project. You also need to create a client project in the [Cloud Console](https://console.cloud.google.com). The client project is running Google App Engine standard application. + +To use the App Engine default service account for authentication: + +1. Update the `gae_default_service_account`'s `x-issuer` and `x-jwks_uri` in `swagger.yaml` with your client project ID. +2. Redeploy your server application. +3. Update clients/service_to_service_gae_default/main.py, replace 'YOUR-CLIENT-PROJECT-ID' and 'YOUR-SERVER-PROJECT-ID' with your client project ID and your server project ID. +4. Upload your application to Google App Engine by invoking the following command. Note that you need to provide project ID in the command because there are two projects (server and client projects) here and gcloud needs to know which project to pick. +```bash +$ gcloud app deploy app.yaml --project=YOUR-CLIENT-PROJECT-ID +``` + +Your client app is now deployed at https://YOUR-CLIENT-PROJECT-ID.appspot.com. When you access https://YOUR-CLIENT-PROJECT-ID.appspot.com, your client calls your server project API using +the client's service account. + +### Using the service account client (no key file needed) + +The service account client demonstrates how to use a non-default service account to authenticate to endpoints. +We refer to the project that serves API requests as the server project. You also need to create a client project in the [Cloud Console](https://console.cloud.google.com). +The client project is running Google App Engine standard application. + +In the example, we use Google Cloud Identity and Access Management (IAM) API to create a JSON Web Token (JWT) for a service account, and use it to call an Endpoints API. + +To use the client, you will need to enable "Service Account Actor" role for App Engine default service account: + +1. Go to [IAM page](https://console.cloud.google.com/iam-admin/iam) of your client project. +2. For App Engine default service account, from “Role(s)” drop-down menu, select “Project”-“Service Account Actor”, and Save. + +You also need to install Google API python library because the client code (main.py) uses googleapiclient, +which is a python library that needs to be uploaded to App Engine with your application code. After you run "pip install -t lib -r requirements", +Google API python client library should have already been installed under 'lib' directory. Additional information can be found +[here](https://cloud.google.com/appengine/docs/python/tools/using-libraries-python-27#requesting_a_library). + +To use the client for authentication: + +1. Update the `google_service_account`'s `x-issuer` and `x-jwks_uri` in `swagger.yaml` with your service account email. +2. Redeploy your server application. +3. Update clients/service_to_service_non_default/main.py by replacing 'YOUR-SERVICE-ACCOUNT-EMAIL', 'YOUR-SERVER-PROJECT-ID' and 'YOUR-CLIENT-PROJECT-ID' +with your service account email, your server project ID, and your client project ID, respectively. +4. Upload your application to Google App Engine by invoking the following command. Note that you need to provide project ID in the command because there are two projects (server and client projects) here and gcloud needs to know which project to pick. +```bash +$ gcloud app deploy app.yaml --project=YOUR-CLIENT-PROJECT-ID +``` + +Your client app is now deployed at https://YOUR-CLIENT-PROJECT-ID.appspot.com. When you access https://YOUR-CLIENT-PROJECT-ID.appspot.com, your client calls your server project API using +the client's service account. + +### Using the ID token client (no key file needed) + +This example demonstrates how to authenticate to endpoints from Google App Engine default service account using Google ID token. +In the example, we first create a JSON Web Token (JWT) using the App Engine default service account. We then request a Google +ID token using the JWT, and call an Endpoints API using the Google ID token. + +We refer to the project that serves API requests as the server project. You also need to create a client project in the [Cloud Console](https://console.cloud.google.com). +The client project is running Google App Engine standard application. + +To use the client for authentication: + +1. Update the `google_id_token`'s audiences, replace `YOUR-SERVER-PROJECT-ID` with your server project ID. +2. Redeploy your server application. +3. Update clients/service_to_service_google_id_token/main.py, replace 'YOUR-CLIENT-PROJECT-ID' and 'YOUR-SERVER-PROJECT-ID' with your client project ID and your server project ID. +4. Upload your application to Google App Engine by invoking the following command. Note that you need to provide project ID in the command because there are two projects (server and client projects) here and gcloud needs to know which project to pick. +```bash +$ gcloud app deploy app.yaml --project=YOUR-CLIENT-PROJECT-ID +``` + +Your client app is now deployed at https://YOUR-CLIENT-PROJECT-ID.appspot.com. When you access https://YOUR-CLIENT-PROJECT-ID.appspot.com, your client calls your server project API from +the client's service account using Google ID token. + +## Viewing the Endpoints graphs + +By using Endpoints, you get access to several metrics that are displayed graphically in the Cloud Console. + +To view the Endpoints graphs: + +1. Go to the [Endpoints section in Cloud Console](https://console.cloud.google.com/endpoints) of the project you deployed your API to. +2. Click on your API to view more detailed information about the metrics collected. + +## Swagger UI + +The Swagger UI is an open source Swagger project that allows you to explore your API through a UI. Find out more about it on the [Swagger site](http://swagger.io/swagger-ui/). diff --git a/endpoints/echo/app.yaml b/endpoints/echo/app.yaml new file mode 100644 index 00000000000..535d368abc2 --- /dev/null +++ b/endpoints/echo/app.yaml @@ -0,0 +1,12 @@ +runtime: python +vm: true +entrypoint: gunicorn -b :$PORT main:app + +runtime_config: + python_version: 3 + +beta_settings: + # Enable Google Cloud Endpoints API management. + use_endpoints_api_management: true + # Specify the Swagger API specification. + endpoints_swagger_spec_file: swagger.yaml diff --git a/endpoints/echo/clients/echo-client.py b/endpoints/echo/clients/echo-client.py new file mode 100755 index 00000000000..28f99a0d8d3 --- /dev/null +++ b/endpoints/echo/clients/echo-client.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python + +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Example of calling a simple Google Cloud Endpoint API.""" + +import argparse + +import requests +from six.moves import urllib + + +def make_request(host, api_key, message): + """Makes a request to the auth info endpoint for Google ID tokens.""" + url = urllib.parse.urljoin(host, 'echo') + params = { + 'key': api_key + } + body = { + 'message': message + } + + response = requests.post(url, params=params, json=body) + + response.raise_for_status() + return response.text + + +def main(host, api_key, message): + response = make_request(host, api_key, message) + print(response) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument( + 'host', help='Your API host, e.g. https://your-project.appspot.com.') + parser.add_argument( + 'api_key', help='Your API key.') + parser.add_argument( + 'message', + help='Message to echo.') + + args = parser.parse_args() + + main(args.host, args.api_key, args.message) diff --git a/endpoints/echo/clients/google-id-token-client.py b/endpoints/echo/clients/google-id-token-client.py new file mode 100644 index 00000000000..e559dbe61a9 --- /dev/null +++ b/endpoints/echo/clients/google-id-token-client.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python + +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Example of calling a Google Cloud Endpoint API with an ID token obtained +using the Google OAuth2 flow.""" + +import argparse + +import oauth2client.client +import oauth2client.file +import oauth2client.tools +import requests +from six.moves import urllib + + +def get_id_token(client_secrets_file, extra_args): + storage = oauth2client.file.Storage('credentials.dat') + credentials = storage.get() + + if not credentials or credentials.invalid: + flow = oauth2client.client.flow_from_clientsecrets( + client_secrets_file, scope='email') + credentials = oauth2client.tools.run_flow( + flow, storage, flags=extra_args) + + # The ID token is used by Cloud Endpoints, not the access token. + id_token = credentials.token_response['id_token'] + + return id_token + + +def make_request(host, api_key, id_token): + """Makes a request to the auth info endpoint for Google ID tokens.""" + url = urllib.parse.urljoin(host, '/auth/info/googleidtoken') + params = { + 'key': api_key + } + headers = { + 'Authorization': 'Bearer {}'.format(id_token) + } + + response = requests.get(url, params=params, headers=headers) + + response.raise_for_status() + return response.text + + +def main(host, api_key, client_secrets_file, extra_args): + id_token = get_id_token(client_secrets_file, extra_args) + response = make_request(host, api_key, id_token) + print(response) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + parents=[oauth2client.tools.argparser]) + parser.add_argument( + 'host', help='Your API host, e.g. https://your-project.appspot.com.') + parser.add_argument( + 'api_key', help='Your API key.') + parser.add_argument( + 'client_secrets_file', + help='The path to your OAuth2 client secrets file.') + + args = parser.parse_args() + + main(args.host, args.api_key, args.client_secrets_file, args) diff --git a/endpoints/echo/clients/google-jwt-client.py b/endpoints/echo/clients/google-jwt-client.py new file mode 100644 index 00000000000..076b656f6be --- /dev/null +++ b/endpoints/echo/clients/google-jwt-client.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python + +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Example of calling a Google Cloud Endpoint API with a JWT signed by +a Google API Service Account.""" + +import argparse +import time + +import oauth2client.crypt +from oauth2client.service_account import ServiceAccountCredentials +import requests +from six.moves import urllib + + +def generate_jwt(service_account_file): + """Generates a signed JSON Web Token using a Google API Service Account.""" + credentials = ServiceAccountCredentials.from_json_keyfile_name( + service_account_file) + + now = int(time.time()) + + payload = { + 'iat': now, + 'exp': now + credentials.MAX_TOKEN_LIFETIME_SECS, + # aud must match 'audience' in the security configuration in your + # swagger spec. It can be any string. + 'aud': 'echo.endpoints.sample.google.com', + # iss must match 'issuer' in the security configuration in your + # swagger spec. It can be any string. + 'iss': 'jwt-client.endpoints.sample.google.com', + # sub and email are mapped to the user id and email respectively. + 'sub': '12345678', + 'email': 'user@example.com' + } + + signed_jwt = oauth2client.crypt.make_signed_jwt( + credentials._signer, payload, key_id=credentials._private_key_id) + + return signed_jwt + + +def make_request(host, api_key, signed_jwt): + """Makes a request to the auth info endpoint for Google JWTs.""" + url = urllib.parse.urljoin(host, '/auth/info/googlejwt') + params = { + 'key': api_key + } + headers = { + 'Authorization': 'Bearer {}'.format(signed_jwt) + } + + response = requests.get(url, params=params, headers=headers) + + response.raise_for_status() + return response.text + + +def main(host, api_key, service_account_file): + signed_jwt = generate_jwt(service_account_file) + response = make_request(host, api_key, signed_jwt) + print(response) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument( + 'host', help='Your API host, e.g. https://your-project.appspot.com.') + parser.add_argument( + 'api_key', help='Your API key.') + parser.add_argument( + 'service_account_file', + help='The path to your service account json file.') + + args = parser.parse_args() + + main(args.host, args.api_key, args.service_account_file) diff --git a/endpoints/echo/clients/service_to_service_gae_default/app.yaml b/endpoints/echo/clients/service_to_service_gae_default/app.yaml new file mode 100644 index 00000000000..f041d384c05 --- /dev/null +++ b/endpoints/echo/clients/service_to_service_gae_default/app.yaml @@ -0,0 +1,7 @@ +runtime: python27 +api_version: 1 +threadsafe: true + +handlers: +- url: /.* + script: main.app diff --git a/endpoints/echo/clients/service_to_service_gae_default/main.py b/endpoints/echo/clients/service_to_service_gae_default/main.py new file mode 100644 index 00000000000..5ff84986fa0 --- /dev/null +++ b/endpoints/echo/clients/service_to_service_gae_default/main.py @@ -0,0 +1,83 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0(the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http: // www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Example of calling a Google Cloud Endpoint API with a JWT signed by +Google App Engine Default Service Account.""" + +import base64 +import httplib +import json +import time + +from google.appengine.api import app_identity +import webapp2 + +DEFAUTL_SERVICE_ACCOUNT = 'YOUR-CLIENT-PROJECT-ID@appspot.gserviceaccount.com' +HOST = "YOUR-SERVER-PROJECT-ID.appspot.com" + + +def generate_jwt(): + """Generates a signed JSON Web Token using the Google App Engine default + service account.""" + now = int(time.time()) + + header_json = json.dumps({ + "typ": "JWT", + "alg": "RS256"}) + + payload_json = json.dumps({ + 'iat': now, + # expires after one hour. + "exp": now + 3600, + # iss is the Google App Engine default service account email. + 'iss': DEFAUTL_SERVICE_ACCOUNT, + 'sub': DEFAUTL_SERVICE_ACCOUNT, + # aud must match 'audience' in the security configuration in your + # swagger spec.It can be any string. + 'aud': 'echo.endpoints.sample.google.com', + "email": DEFAUTL_SERVICE_ACCOUNT + }) + + headerAndPayload = '{}.{}'.format( + base64.urlsafe_b64encode(header_json), + base64.urlsafe_b64encode(payload_json)) + (key_name, signature) = app_identity.sign_blob(headerAndPayload) + signed_jwt = '{}.{}'.format( + headerAndPayload, + base64.urlsafe_b64encode(signature)) + + return signed_jwt + + +def make_request(signed_jwt): + """Makes a request to the auth info endpoint for Google JWTs.""" + headers = {'Authorization': 'Bearer {}'.format(signed_jwt)} + conn = httplib.HTTPSConnection(HOST) + conn.request("GET", '/auth/info/googlejwt', None, headers) + res = conn.getresponse() + conn.close() + return res.read() + + +class MainPage(webapp2.RequestHandler): + def get(self): + self.response.headers['Content-Type'] = 'text/plain' + signed_jwt = generate_jwt() + res = make_request(signed_jwt) + self.response.write(res) + + +app = webapp2.WSGIApplication([ + ('/', MainPage), +], debug=True) diff --git a/endpoints/echo/clients/service_to_service_google_id_token/app.yaml b/endpoints/echo/clients/service_to_service_google_id_token/app.yaml new file mode 100644 index 00000000000..f041d384c05 --- /dev/null +++ b/endpoints/echo/clients/service_to_service_google_id_token/app.yaml @@ -0,0 +1,7 @@ +runtime: python27 +api_version: 1 +threadsafe: true + +handlers: +- url: /.* + script: main.app diff --git a/endpoints/echo/clients/service_to_service_google_id_token/main.py b/endpoints/echo/clients/service_to_service_google_id_token/main.py new file mode 100644 index 00000000000..fb22c5ebf4a --- /dev/null +++ b/endpoints/echo/clients/service_to_service_google_id_token/main.py @@ -0,0 +1,98 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0(the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http: // www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Example of calling a Google Cloud Endpoint API from Google App Engine +Default Service Account using Google ID token.""" + +import base64 +import httplib +import json +import time +import urllib + +from google.appengine.api import app_identity +import webapp2 + +DEFAUTL_SERVICE_ACCOUNT = "YOUR-CLIENT-PROJECT-ID@appspot.gserviceaccount.com" +HOST = "YOUR-SERVER-PROJECT-ID.appspot.com" +TARGET_AUD = "YOUR-SERVER-PROJECT-ID@appspot.gserviceaccount.com" + + +def generate_jwt(): + """Generates a signed JSON Web Token using the Google App Engine default + service account.""" + now = int(time.time()) + + header_json = json.dumps({ + "typ": "JWT", + "alg": "RS256"}) + + payload_json = json.dumps({ + "iat": now, + # expires after one hour. + "exp": now + 3600, + # iss is the Google App Engine default service account email. + "iss": DEFAUTL_SERVICE_ACCOUNT, + # scope must match 'audience' for google_id_token in the security + # configuration in your swagger spec. + "scope": TARGET_AUD, + # aud must be Google token endpoints URL. + "aud": "https://www.googleapis.com/oauth2/v4/token" + }) + + headerAndPayload = '{}.{}'.format( + base64.urlsafe_b64encode(header_json), + base64.urlsafe_b64encode(payload_json)) + (key_name, signature) = app_identity.sign_blob(headerAndPayload) + signed_jwt = '{}.{}'.format( + headerAndPayload, + base64.urlsafe_b64encode(signature)) + + return signed_jwt + + +def get_id_token(): + """Request a Google ID token using a JWT.""" + params = urllib.urlencode({ + 'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', + 'assertion': generate_jwt()}) + headers = {"Content-Type": "application/x-www-form-urlencoded"} + conn = httplib.HTTPSConnection("www.googleapis.com") + conn.request("POST", "/oauth2/v4/token", params, headers) + res = json.loads(conn.getresponse().read()) + conn.close() + return res['id_token'] + + +def make_request(token): + """Makes a request to the auth info endpoint for Google ID token.""" + headers = {'Authorization': 'Bearer {}'.format(token)} + conn = httplib.HTTPSConnection(HOST) + conn.request("GET", '/auth/info/googleidtoken', None, headers) + res = conn.getresponse() + conn.close() + return res.read() + + +class MainPage(webapp2.RequestHandler): + def get(self): + self.response.headers['Content-Type'] = 'text/plain' + token = get_id_token() + res = make_request(token) + self.response.write(res) + + +app = webapp2.WSGIApplication([ + ('/', MainPage), +], debug=True) diff --git a/endpoints/echo/clients/service_to_service_non_default/app.yaml b/endpoints/echo/clients/service_to_service_non_default/app.yaml new file mode 100644 index 00000000000..f041d384c05 --- /dev/null +++ b/endpoints/echo/clients/service_to_service_non_default/app.yaml @@ -0,0 +1,7 @@ +runtime: python27 +api_version: 1 +threadsafe: true + +handlers: +- url: /.* + script: main.app diff --git a/endpoints/echo/clients/service_to_service_non_default/appengine_config.py b/endpoints/echo/clients/service_to_service_non_default/appengine_config.py new file mode 100644 index 00000000000..f4489ff9680 --- /dev/null +++ b/endpoints/echo/clients/service_to_service_non_default/appengine_config.py @@ -0,0 +1,3 @@ +from google.appengine.ext import vendor + +vendor.add('lib') diff --git a/endpoints/echo/clients/service_to_service_non_default/main.py b/endpoints/echo/clients/service_to_service_non_default/main.py new file mode 100644 index 00000000000..504d0d03d8f --- /dev/null +++ b/endpoints/echo/clients/service_to_service_non_default/main.py @@ -0,0 +1,94 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0(the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http: // www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Example of calling a Google Cloud Endpoint API with a JWT signed by a +Service Account.""" + +import base64 +import httplib +import json +import time + +from googleapiclient.discovery import build +import httplib2 +from oauth2client.contrib.appengine import AppAssertionCredentials +import webapp2 + +SERVICE_ACCOUNT_EMAIL = "YOUR-SERVICE-ACCOUNT-EMAIL" +HOST = "YOUR-SERVER-PROJECT-ID.appspot.com" +SERVICE_ACCOUNT = \ + "projects/YOUR-CLIENT-PROJECT-ID/serviceAccounts/YOUR-SERVICE-ACCOUNT-EMAIL" + + +def generate_jwt(): + """Generates a signed JSON Web Token using a service account.""" + credentials = AppAssertionCredentials( + 'https://www.googleapis.com/auth/iam') + http_auth = credentials.authorize(httplib2.Http()) + service = build(serviceName='iam', version='v1', http=http_auth) + + now = int(time.time()) + + header_json = json.dumps({ + "typ": "JWT", + "alg": "RS256"}) + + payload_json = json.dumps({ + 'iat': now, + # expires after one hour. + "exp": now + 3600, + # iss is the service account email. + 'iss': SERVICE_ACCOUNT_EMAIL, + 'sub': SERVICE_ACCOUNT_EMAIL, + # aud must match 'audience' in the security configuration in your + # swagger spec.It can be any string. + 'aud': 'echo.endpoints.sample.google.com', + "email": SERVICE_ACCOUNT_EMAIL + }) + + headerAndPayload = '{}.{}'.format( + base64.urlsafe_b64encode(header_json), + base64.urlsafe_b64encode(payload_json)) + slist = service.projects().serviceAccounts().signBlob( + name=SERVICE_ACCOUNT, + body={'bytesToSign': base64.b64encode(headerAndPayload)}) + res = slist.execute() + signature = base64.urlsafe_b64encode( + base64.decodestring(res['signature'])) + signed_jwt = '{}.{}'.format(headerAndPayload, signature) + + return signed_jwt + + +def make_request(signed_jwt): + """Makes a request to the auth info endpoint for Google JWTs.""" + headers = {'Authorization': 'Bearer {}'.format(signed_jwt)} + conn = httplib.HTTPSConnection(HOST) + conn.request("GET", '/auth/info/googlejwt', None, headers) + res = conn.getresponse() + conn.close() + return res.read() + + +class MainPage(webapp2.RequestHandler): + def get(self): + self.response.headers['Content-Type'] = 'text/plain' + signed_jwt = generate_jwt() + res = make_request(signed_jwt) + self.response.write(res) + + +app = webapp2.WSGIApplication([ + ('/', MainPage), +], debug=True) diff --git a/endpoints/echo/main.py b/endpoints/echo/main.py new file mode 100644 index 00000000000..66de99a60ab --- /dev/null +++ b/endpoints/echo/main.py @@ -0,0 +1,94 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Endpoints sample application. + +Demonstrates how to create a simple echo API as well as how to deal with +various authentication methods. +""" + +import base64 +import json +import logging + +from flask import Flask, jsonify, request +from flask_cors import cross_origin +from six.moves import http_client + + +app = Flask(__name__) + + +def _base64_decode(encoded_str): + # Add paddings manually if necessary. + num_missed_paddings = 4 - len(encoded_str) % 4 + if num_missed_paddings != 4: + encoded_str += b'=' * num_missed_paddings + return base64.b64decode(encoded_str).decode('utf-8') + + +@app.route('/echo', methods=['POST']) +def echo(): + """Simple echo service.""" + message = request.get_json().get('message', '') + return jsonify({'message': message}) + + +def auth_info(): + """Retrieves the authenication information from Google Cloud Endpoints.""" + encoded_info = request.headers.get('X-Endpoint-API-UserInfo', None) + + if encoded_info: + info_json = _base64_decode(encoded_info) + user_info = json.loads(info_json) + else: + user_info = {'id': 'anonymous'} + + return jsonify(user_info) + + +@app.route('/auth/info/googlejwt', methods=['GET']) +def auth_info_google_jwt(): + """Auth info with Google signed JWT.""" + return auth_info() + + +@app.route('/auth/info/googleidtoken', methods=['GET']) +def auth_info_google_id_token(): + """Auth info with Google ID token.""" + return auth_info() + + +@app.route('/auth/info/firebase', methods=['GET']) +@cross_origin(send_wildcard=True) +def auth_info_firebase(): + """Auth info with Firebase auth.""" + return auth_info() + + +@app.errorhandler(http_client.INTERNAL_SERVER_ERROR) +def unexpected_error(e): + """Handle exceptions by returning swagger-compliant json.""" + logging.exception('An error occured while processing the request.') + response = jsonify({ + 'code': http_client.INTERNAL_SERVER_ERROR, + 'message': 'Exception: {}'.format(e)}) + response.status_code = http_client.INTERNAL_SERVER_ERROR + return response + + +if __name__ == '__main__': + # This is used when running locally. Gunicorn is used to run the + # application on Google App Engine. See entrypoint in app.yaml. + app.run(host='127.0.0.1', port=8080, debug=True) diff --git a/endpoints/echo/main_test.py b/endpoints/echo/main_test.py new file mode 100644 index 00000000000..6ca6b5093d3 --- /dev/null +++ b/endpoints/echo/main_test.py @@ -0,0 +1,82 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 +import json +import os + +import pytest + +import main + + +@pytest.fixture +def client(monkeypatch): + monkeypatch.chdir(os.path.dirname(main.__file__)) + main.app.testing = True + client = main.app.test_client() + return client + + +def test_echo(client): + r = client.post( + '/echo', + data='{"message": "Hello"}', + headers={ + 'Content-Type': 'application/json' + }) + + assert r.status_code == 200 + data = json.loads(r.data.decode('utf-8')) + assert data['message'] == 'Hello' + + +def test_auth_info(client): + endpoints = [ + '/auth/info/googlejwt', + '/auth/info/googleidtoken', + '/auth/info/firebase'] + + encoded_info = base64.b64encode(json.dumps({ + 'id': '123' + }).encode('utf-8')) + + for endpoint in endpoints: + r = client.get( + endpoint, + headers={ + 'Content-Type': 'application/json' + }) + + assert r.status_code == 200 + data = json.loads(r.data.decode('utf-8')) + assert data['id'] == 'anonymous' + + r = client.get( + endpoint, + headers={ + 'Content-Type': 'application/json', + 'X-Endpoint-API-UserInfo': encoded_info + }) + + assert r.status_code == 200 + data = json.loads(r.data.decode('utf-8')) + assert data['id'] == '123' + + +def test_cors(client): + r = client.options( + '/auth/info/firebase', headers={'Origin': 'example.com'}) + assert r.status_code == 200 + assert r.headers['Access-Control-Allow-Origin'] == '*' diff --git a/endpoints/echo/requirements.txt b/endpoints/echo/requirements.txt new file mode 100644 index 00000000000..45699fbc037 --- /dev/null +++ b/endpoints/echo/requirements.txt @@ -0,0 +1,6 @@ +Flask==0.11.1 +flask-cors==3.0.2 +gunicorn==19.6.0 +six==1.10.0 +pyyaml==3.12 +requests==2.11.1 diff --git a/endpoints/echo/swagger.yaml b/endpoints/echo/swagger.yaml new file mode 100644 index 00000000000..b2f6c8d5245 --- /dev/null +++ b/endpoints/echo/swagger.yaml @@ -0,0 +1,162 @@ +swagger: "2.0" +info: + description: "A simple Google Cloud Endpoints API example." + title: "Endpoints Example" + version: "1.0.0" +host: "YOUR-PROJECT-ID.appspot.com" +basePath: "/" +consumes: +- "application/json" +produces: +- "application/json" +schemes: +- "https" +paths: + "/echo": + post: + description: "Echo back a given message." + operationId: "echo" + produces: + - "application/json" + responses: + 200: + description: "Echo" + schema: + $ref: "#/definitions/echoMessage" + parameters: + - description: "Message to echo" + in: body + name: message + required: true + schema: + $ref: "#/definitions/echoMessage" + security: + - api_key: [] + "/auth/info/googlejwt": + get: + description: "Returns the requests' authentication information." + operationId: "auth_info_google_jwt" + produces: + - "application/json" + responses: + 200: + description: "Authentication info." + schema: + $ref: "#/definitions/authInfoResponse" + x-security: + - google_jwt: + audiences: + # This must match the "aud" field in the JWT. You can add multiple + # audiences to accept JWTs from multiple clients. + - "echo.endpoints.sample.google.com" + - gae_default_service_account: + audiences: + # This must match the "aud" field in the JWT. You can add multiple + # audiences to accept JWTs from multiple clients. + - "echo.endpoints.sample.google.com" + - google_service_account: + audiences: + # This must match the "aud" field in the JWT. You can add multiple + # audiences to accept JWTs from multiple clients. + - "echo.endpoints.sample.google.com" + "/auth/info/googleidtoken": + get: + description: "Returns the requests' authentication information." + operationId: "authInfoGoogleIdToken" + produces: + - "application/json" + responses: + 200: + description: "Authentication info." + schema: + $ref: "#/definitions/authInfoResponse" + x-security: + - google_id_token: + audiences: + # Your OAuth2 client's Client ID must be added here. You can add + # multiple client IDs to accept tokens from multiple clients. + - "YOUR-CLIENT-ID" + - "YOUR-SERVER-PROJECT-ID@appspot.gserviceaccount.com" + "/auth/info/firebase": + get: + description: "Returns the requests' authentication information." + operationId: "authInfoFirebase" + produces: + - "application/json" + responses: + 200: + description: "Authentication info." + schema: + $ref: "#/definitions/authInfoResponse" + x-security: + - firebase: + audiences: + - "YOUR-PROJECT-ID" + +definitions: + echoMessage: + properties: + message: + type: "string" + authInfoResponse: + properties: + id: + type: "string" + email: + type: "string" + +securityDefinitions: + # This section configures basic authentication with an API key. + api_key: + type: "apiKey" + name: "key" + in: "query" + # This section configures authentication using Google API Service Accounts + # to sign a json web token. This is mostly used for server-to-server + # communication. + google_jwt: + authorizationUrl: "" + flow: "implicit" + type: "oauth2" + # This must match the 'iss' field in the JWT. + x-issuer: "jwt-client.endpoints.sample.google.com" + # Update this with your service account's email address. + x-jwks_uri: "https://www.googleapis.com/service_accounts/v1/jwk/YOUR-SERVICE-ACCOUNT-EMAIL" + # This section configures authentication using Google App Engine default + # service account to sign a json web token. This is mostly used for + # server-to-server communication. + gae_default_service_account: + authorizationUrl: "" + flow: "implicit" + type: "oauth2" + # Replace YOUR-CLIENT-PROJECT-ID with your client project ID. + x-issuer: "YOUR-CLIENT-PROJECT-ID@appspot.gserviceaccount.com" + # Replace YOUR-CLIENT-PROJECT-ID with your client project ID. + x-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/YOUR-CLIENT-PROJECT-ID@appspot.gserviceaccount.com" + # This section configures authentication using a service account + # to sign a json web token. This is mostly used for server-to-server + # communication. + google_service_account: + authorizationUrl: "" + flow: "implicit" + type: "oauth2" + # Replace YOUR-SERVICE-ACCOUNT-EMAIL with your service account email. + x-issuer: "YOUR-SERVICE-ACCOUNT-EMAIL" + # Replace YOUR-SERVICE-ACCOUNT-EMAIL with your service account email. + x-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/YOUR-SERVICE-ACCOUNT-EMAIL" + # This section configures authentication using Google OAuth2 ID Tokens. + # ID Tokens can be obtained using OAuth2 clients, and can be used to access + # your API on behalf of a particular user. + google_id_token: + authorizationUrl: "" + flow: "implicit" + type: "oauth2" + x-issuer: "accounts.google.com" + x-jwks_uri: "https://www.googleapis.com/oauth2/v1/certs" + # This section configures authentication using Firebase Auth. + firebase: + authorizationUrl: "" + flow: "implicit" + type: "oauth2" + x-issuer: "https://securetoken.google.com/YOUR-PROJECT-ID" + x-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken@system.gserviceaccount.com" From f60c166e0ac8829620389d36fcfb0c04141ccab3 Mon Sep 17 00:00:00 2001 From: Kevin Deus Date: Wed, 2 Nov 2016 13:48:15 -0700 Subject: [PATCH 02/10] Added gke.yaml --- endpoints/echo/gke.yaml | 54 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 endpoints/echo/gke.yaml diff --git a/endpoints/echo/gke.yaml b/endpoints/echo/gke.yaml new file mode 100644 index 00000000000..0d67ca406ef --- /dev/null +++ b/endpoints/echo/gke.yaml @@ -0,0 +1,54 @@ +# Copyright 2015 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: Service +metadata: + name: esp-echo +spec: + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: esp-echo + type: LoadBalancer +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: esp-echo +spec: + replicas: 1 + template: + metadata: + labels: + app: esp-echo + spec: + containers: + - name: esp + image: b.gcr.io/endpoints/endpoints-runtime:0.3 + args: [ + "-p", "8080", + "-a", "127.0.0.1:8081", + "-s", "SERVICE_NAME", + "-v", "SERVICE_VERSION", + ] + ports: + - containerPort: 8080 + - name: echo + image: b.gcr.io/endpoints/echo:latest + ports: + - containerPort: 8081 From a6c26f386cf873de245a41128e95f186387c19aa Mon Sep 17 00:00:00 2001 From: Kevin Deus Date: Wed, 2 Nov 2016 15:24:44 -0700 Subject: [PATCH 03/10] Added HTTPS to gke.yaml --- endpoints/echo/gke.yaml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/endpoints/echo/gke.yaml b/endpoints/echo/gke.yaml index 0d67ca406ef..a8f37a1df25 100644 --- a/endpoints/echo/gke.yaml +++ b/endpoints/echo/gke.yaml @@ -1,4 +1,4 @@ -# Copyright 2015 Google Inc. All Rights Reserved. +# Copyright 2016 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. + apiVersion: v1 kind: Service metadata: @@ -22,6 +23,9 @@ spec: targetPort: 8080 protocol: TCP name: http + - port: 443 + protocol: TCP + name: https selector: app: esp-echo type: LoadBalancer @@ -37,17 +41,27 @@ spec: labels: app: esp-echo spec: + volumes: + - name: nginx-ssl + secret: + secretName: nginx-ssl containers: - name: esp image: b.gcr.io/endpoints/endpoints-runtime:0.3 args: [ "-p", "8080", + "-S", "443", "-a", "127.0.0.1:8081", "-s", "SERVICE_NAME", "-v", "SERVICE_VERSION", ] ports: - containerPort: 8080 + - containerPort: 443 + volumeMounts: + - mountPath: /etc/nginx/ssl + name: nginx-ssl + readOnly: true - name: echo image: b.gcr.io/endpoints/echo:latest ports: From 172d7bdb2f03e09f7f1a3d34eedce86f7b591cb9 Mon Sep 17 00:00:00 2001 From: Kevin Deus Date: Wed, 2 Nov 2016 15:24:56 -0700 Subject: [PATCH 04/10] Removed dockerfile --- endpoints/echo/Dockerfile.container-engine | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 endpoints/echo/Dockerfile.container-engine diff --git a/endpoints/echo/Dockerfile.container-engine b/endpoints/echo/Dockerfile.container-engine deleted file mode 100644 index adadbf72888..00000000000 --- a/endpoints/echo/Dockerfile.container-engine +++ /dev/null @@ -1,12 +0,0 @@ -FROM debian:jessie - -RUN apt-get update && \ - apt-get install -y python2.7 python-pip && \ - apt-get clean && \ - rm /var/lib/apt/lists/*_* - -ADD . /app -WORKDIR /app - -RUN pip install -r requirements.txt -ENTRYPOINT ["gunicorn", "-b", ":8081", "main:app"] From be3439b9822ade61197ba95b2c8976c8700f222b Mon Sep 17 00:00:00 2001 From: Kevin Deus Date: Wed, 2 Nov 2016 15:39:32 -0700 Subject: [PATCH 05/10] Renamed echo to getting-started --- endpoints/{echo => getting-started}/README.md | 0 endpoints/{echo => getting-started}/app.yaml | 0 endpoints/{echo => getting-started}/clients/echo-client.py | 0 .../{echo => getting-started}/clients/google-id-token-client.py | 0 endpoints/{echo => getting-started}/clients/google-jwt-client.py | 0 .../clients/service_to_service_gae_default/app.yaml | 0 .../clients/service_to_service_gae_default/main.py | 0 .../clients/service_to_service_google_id_token/app.yaml | 0 .../clients/service_to_service_google_id_token/main.py | 0 .../clients/service_to_service_non_default/app.yaml | 0 .../clients/service_to_service_non_default/appengine_config.py | 0 .../clients/service_to_service_non_default/main.py | 0 endpoints/{echo => getting-started}/gke.yaml | 0 endpoints/{echo => getting-started}/main.py | 0 endpoints/{echo => getting-started}/main_test.py | 0 endpoints/{echo => getting-started}/requirements.txt | 0 endpoints/{echo => getting-started}/swagger.yaml | 0 17 files changed, 0 insertions(+), 0 deletions(-) rename endpoints/{echo => getting-started}/README.md (100%) rename endpoints/{echo => getting-started}/app.yaml (100%) rename endpoints/{echo => getting-started}/clients/echo-client.py (100%) rename endpoints/{echo => getting-started}/clients/google-id-token-client.py (100%) rename endpoints/{echo => getting-started}/clients/google-jwt-client.py (100%) rename endpoints/{echo => getting-started}/clients/service_to_service_gae_default/app.yaml (100%) rename endpoints/{echo => getting-started}/clients/service_to_service_gae_default/main.py (100%) rename endpoints/{echo => getting-started}/clients/service_to_service_google_id_token/app.yaml (100%) rename endpoints/{echo => getting-started}/clients/service_to_service_google_id_token/main.py (100%) rename endpoints/{echo => getting-started}/clients/service_to_service_non_default/app.yaml (100%) rename endpoints/{echo => getting-started}/clients/service_to_service_non_default/appengine_config.py (100%) rename endpoints/{echo => getting-started}/clients/service_to_service_non_default/main.py (100%) rename endpoints/{echo => getting-started}/gke.yaml (100%) rename endpoints/{echo => getting-started}/main.py (100%) rename endpoints/{echo => getting-started}/main_test.py (100%) rename endpoints/{echo => getting-started}/requirements.txt (100%) rename endpoints/{echo => getting-started}/swagger.yaml (100%) diff --git a/endpoints/echo/README.md b/endpoints/getting-started/README.md similarity index 100% rename from endpoints/echo/README.md rename to endpoints/getting-started/README.md diff --git a/endpoints/echo/app.yaml b/endpoints/getting-started/app.yaml similarity index 100% rename from endpoints/echo/app.yaml rename to endpoints/getting-started/app.yaml diff --git a/endpoints/echo/clients/echo-client.py b/endpoints/getting-started/clients/echo-client.py similarity index 100% rename from endpoints/echo/clients/echo-client.py rename to endpoints/getting-started/clients/echo-client.py diff --git a/endpoints/echo/clients/google-id-token-client.py b/endpoints/getting-started/clients/google-id-token-client.py similarity index 100% rename from endpoints/echo/clients/google-id-token-client.py rename to endpoints/getting-started/clients/google-id-token-client.py diff --git a/endpoints/echo/clients/google-jwt-client.py b/endpoints/getting-started/clients/google-jwt-client.py similarity index 100% rename from endpoints/echo/clients/google-jwt-client.py rename to endpoints/getting-started/clients/google-jwt-client.py diff --git a/endpoints/echo/clients/service_to_service_gae_default/app.yaml b/endpoints/getting-started/clients/service_to_service_gae_default/app.yaml similarity index 100% rename from endpoints/echo/clients/service_to_service_gae_default/app.yaml rename to endpoints/getting-started/clients/service_to_service_gae_default/app.yaml diff --git a/endpoints/echo/clients/service_to_service_gae_default/main.py b/endpoints/getting-started/clients/service_to_service_gae_default/main.py similarity index 100% rename from endpoints/echo/clients/service_to_service_gae_default/main.py rename to endpoints/getting-started/clients/service_to_service_gae_default/main.py diff --git a/endpoints/echo/clients/service_to_service_google_id_token/app.yaml b/endpoints/getting-started/clients/service_to_service_google_id_token/app.yaml similarity index 100% rename from endpoints/echo/clients/service_to_service_google_id_token/app.yaml rename to endpoints/getting-started/clients/service_to_service_google_id_token/app.yaml diff --git a/endpoints/echo/clients/service_to_service_google_id_token/main.py b/endpoints/getting-started/clients/service_to_service_google_id_token/main.py similarity index 100% rename from endpoints/echo/clients/service_to_service_google_id_token/main.py rename to endpoints/getting-started/clients/service_to_service_google_id_token/main.py diff --git a/endpoints/echo/clients/service_to_service_non_default/app.yaml b/endpoints/getting-started/clients/service_to_service_non_default/app.yaml similarity index 100% rename from endpoints/echo/clients/service_to_service_non_default/app.yaml rename to endpoints/getting-started/clients/service_to_service_non_default/app.yaml diff --git a/endpoints/echo/clients/service_to_service_non_default/appengine_config.py b/endpoints/getting-started/clients/service_to_service_non_default/appengine_config.py similarity index 100% rename from endpoints/echo/clients/service_to_service_non_default/appengine_config.py rename to endpoints/getting-started/clients/service_to_service_non_default/appengine_config.py diff --git a/endpoints/echo/clients/service_to_service_non_default/main.py b/endpoints/getting-started/clients/service_to_service_non_default/main.py similarity index 100% rename from endpoints/echo/clients/service_to_service_non_default/main.py rename to endpoints/getting-started/clients/service_to_service_non_default/main.py diff --git a/endpoints/echo/gke.yaml b/endpoints/getting-started/gke.yaml similarity index 100% rename from endpoints/echo/gke.yaml rename to endpoints/getting-started/gke.yaml diff --git a/endpoints/echo/main.py b/endpoints/getting-started/main.py similarity index 100% rename from endpoints/echo/main.py rename to endpoints/getting-started/main.py diff --git a/endpoints/echo/main_test.py b/endpoints/getting-started/main_test.py similarity index 100% rename from endpoints/echo/main_test.py rename to endpoints/getting-started/main_test.py diff --git a/endpoints/echo/requirements.txt b/endpoints/getting-started/requirements.txt similarity index 100% rename from endpoints/echo/requirements.txt rename to endpoints/getting-started/requirements.txt diff --git a/endpoints/echo/swagger.yaml b/endpoints/getting-started/swagger.yaml similarity index 100% rename from endpoints/echo/swagger.yaml rename to endpoints/getting-started/swagger.yaml From 953e9368fda3a24d3f848531f13162d2af0af450 Mon Sep 17 00:00:00 2001 From: Kevin Deus Date: Wed, 2 Nov 2016 16:52:36 -0700 Subject: [PATCH 06/10] Fixed license URLs --- .../clients/service_to_service_gae_default/main.py | 2 +- .../clients/service_to_service_google_id_token/main.py | 2 +- .../clients/service_to_service_non_default/main.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/endpoints/getting-started/clients/service_to_service_gae_default/main.py b/endpoints/getting-started/clients/service_to_service_gae_default/main.py index 5ff84986fa0..caee4025e3d 100644 --- a/endpoints/getting-started/clients/service_to_service_gae_default/main.py +++ b/endpoints/getting-started/clients/service_to_service_gae_default/main.py @@ -4,7 +4,7 @@ # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http: // www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/endpoints/getting-started/clients/service_to_service_google_id_token/main.py b/endpoints/getting-started/clients/service_to_service_google_id_token/main.py index fb22c5ebf4a..4b07c9feee1 100644 --- a/endpoints/getting-started/clients/service_to_service_google_id_token/main.py +++ b/endpoints/getting-started/clients/service_to_service_google_id_token/main.py @@ -4,7 +4,7 @@ # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http: // www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/endpoints/getting-started/clients/service_to_service_non_default/main.py b/endpoints/getting-started/clients/service_to_service_non_default/main.py index 504d0d03d8f..b492015cdfc 100644 --- a/endpoints/getting-started/clients/service_to_service_non_default/main.py +++ b/endpoints/getting-started/clients/service_to_service_non_default/main.py @@ -4,7 +4,7 @@ # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http: // www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, From 3b5752c0daad36dfd98f6bd2a8f84487b7c114f8 Mon Sep 17 00:00:00 2001 From: Kevin Deus Date: Thu, 3 Nov 2016 10:34:13 -0700 Subject: [PATCH 07/10] Added dockerfile. --- endpoints/getting-started/Dockerfile.custom | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 endpoints/getting-started/Dockerfile.custom diff --git a/endpoints/getting-started/Dockerfile.custom b/endpoints/getting-started/Dockerfile.custom new file mode 100644 index 00000000000..332c1fbbd72 --- /dev/null +++ b/endpoints/getting-started/Dockerfile.custom @@ -0,0 +1,12 @@ +FROM gcr.io/google_appengine/python + +RUN apt-get update && \ + apt-get install -y python2.7 python-pip && \ + apt-get clean && \ + rm /var/lib/apt/lists/*_* + +ADD . /app +WORKDIR /app + +RUN pip install -r requirements.txt +ENTRYPOINT ["gunicorn", "-b", ":8081", "main:app"] From c2b04bf7b268b56deb4e6aa8f16acad89c86c0f8 Mon Sep 17 00:00:00 2001 From: Kevin Deus Date: Thu, 3 Nov 2016 10:43:22 -0700 Subject: [PATCH 08/10] Removed GAE-specific instructions. --- endpoints/getting-started/README.md | 36 ++++++++--------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/endpoints/getting-started/README.md b/endpoints/getting-started/README.md index 5276fe0d5e4..35a3e3b6902 100644 --- a/endpoints/getting-started/README.md +++ b/endpoints/getting-started/README.md @@ -1,6 +1,10 @@ -# Google Cloud Endpoints & App Engine Flexible Environment & Python +# Google Cloud Endpoints & Python -This sample demonstrates how to use Google Cloud Endpoints on Google App Engine Flexible Environment using Python. +This sample demonstrates how to use Google Cloud Endpoints using Python. + +For a complete walkthrough showing how to run this sample in different +environments, see the +[Google Cloud Endpoints Quickstarts](https://cloud.google.com/endpoints/docs/quickstarts). This sample consists of two parts: @@ -11,8 +15,6 @@ This sample consists of two parts: ### Running the backend -For more info on running Flexible applications locally, see [the getting started documentation](https://cloud.google.com/python/getting-started/hello-world). - Install all the dependencies: ```bash $ virtualenv env @@ -34,17 +36,10 @@ $ python clients/echo-client.py http://localhost:8080 APIKEY helloworld The `APIKEY` doesn't matter as the endpoint proxy is not running to do authentication. -## Deploying to Google App Engine - -Open the `swagger.yaml` file and in the `host` property, replace -`YOUR-PROJECT-ID` with your project's ID. - -Then, deploy the sample using `gcloud`: -```bash -$ gcloud beta app deploy -``` +## Deploying to Production -Once deployed, you can access the application at https://YOUR-PROJECT-ID.appspot.com/. +See the +[Google Cloud Endpoints Quickstarts](https://cloud.google.com/endpoints/docs/quickstarts). ### Using the echo client @@ -170,16 +165,3 @@ $ gcloud app deploy app.yaml --project=YOUR-CLIENT-PROJECT-ID Your client app is now deployed at https://YOUR-CLIENT-PROJECT-ID.appspot.com. When you access https://YOUR-CLIENT-PROJECT-ID.appspot.com, your client calls your server project API from the client's service account using Google ID token. - -## Viewing the Endpoints graphs - -By using Endpoints, you get access to several metrics that are displayed graphically in the Cloud Console. - -To view the Endpoints graphs: - -1. Go to the [Endpoints section in Cloud Console](https://console.cloud.google.com/endpoints) of the project you deployed your API to. -2. Click on your API to view more detailed information about the metrics collected. - -## Swagger UI - -The Swagger UI is an open source Swagger project that allows you to explore your API through a UI. Find out more about it on the [Swagger site](http://swagger.io/swagger-ui/). From 2f8fe605347ca7f729135c817551c6b1b8610bdc Mon Sep 17 00:00:00 2001 From: Kevin Deus Date: Thu, 3 Nov 2016 10:47:07 -0700 Subject: [PATCH 09/10] Updated GKE image name --- endpoints/getting-started/gke.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/endpoints/getting-started/gke.yaml b/endpoints/getting-started/gke.yaml index a8f37a1df25..3fbd189e364 100644 --- a/endpoints/getting-started/gke.yaml +++ b/endpoints/getting-started/gke.yaml @@ -63,6 +63,6 @@ spec: name: nginx-ssl readOnly: true - name: echo - image: b.gcr.io/endpoints/echo:latest + image: gcr.io/google-samples/echo-python:1.0 ports: - containerPort: 8081 From 9f97439993479997f5ca19b488f727a1f85caf9f Mon Sep 17 00:00:00 2001 From: Kevin Deus Date: Thu, 3 Nov 2016 10:50:37 -0700 Subject: [PATCH 10/10] Renamed gke.yaml to container-engine.yaml --- endpoints/getting-started/{gke.yaml => container-engine.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename endpoints/getting-started/{gke.yaml => container-engine.yaml} (100%) diff --git a/endpoints/getting-started/gke.yaml b/endpoints/getting-started/container-engine.yaml similarity index 100% rename from endpoints/getting-started/gke.yaml rename to endpoints/getting-started/container-engine.yaml