| #!/usr/bin/env python |
| """ |
| This is a helper script for making pRPC API calls during local development. |
| |
| Usage examples: |
| |
| To test an anonymous request to your own local monorail server: |
| 1. Run 'make serve' in another shell |
| 2. `./api/test_call monorail.Projects ListComponents |
| '{"project_name": "monorail", "include_admin_info": true}'` |
| |
| To test a signed in request to your own local monorail server: |
| 1. Run 'make serve' in another shell |
| 2. `./api/test_call monorail.Projects ListComponents |
| '{"project_name": "monorail", "include_admin_info": true}' |
| --test_account=test@example.com` |
| Note that test account email address must always end in @example.com. |
| |
| To test an anonymous request to your monorail staging server: |
| 1. Deploy your staging server version, e.g., 12345-76697e9-tainted-jrobbins. |
| 2. Visit your staging server in a new incognito window and view source |
| to find the XSRF token for the anonymous user in JS var CS_env['token']. |
| 3. `./api/test_call monorail.Projects ListComponents |
| '{"project_name": "monorail", "include_admin_info": true}' |
| --host=12345-76697e9-tainted-jrobbins-dot-monorail-staging.appspot.com |
| --xsrf-token='THE_ANON_TOKEN'` |
| |
| To test a signed-in request to your monorail staging server using |
| the client_id for monorail-staging and your own account: |
| 1. Make sure that you have a role in the monorail-staging project. |
| 2. Have your account allowlisted by email address. |
| 3. Download the monorail-staging app credientials via |
| `gcloud --project=monorail-staging auth login`. |
| 4. `./api/test_call monorail.Projects ListComponents |
| '{"project_name": "monorail", "include_admin_info": true}' |
| --host=12345-76697e9-tainted-jrobbins-dot-monorail-staging.appspot.com |
| --use-app-credentials` |
| |
| To test a signed-in request to your monorail staging server using |
| a service account client secrets file that you download: |
| (Note: This is not recommended for prod because downloading secrets |
| is a bad practice.) |
| 1. Create a service account via the Cloud Console for any project. |
| Choose "IAM & Admin" > "Service accounts". |
| Press "+ Create Service Account". |
| Fill in the form and submit it to save a service account .json file |
| to your local disk. Keep this file private. |
| 2. File an issue on /p/monorail to allowlist your client_id and/or |
| client_email. Or, author a CL yourself to add it to the allowlist. |
| 3. `./api/test_call monorail.Projects ListComponents |
| '{"project_name": "monorail", "include_admin_info": true}' |
| --host=12345-76697e9-tainted-jrobbins-dot-monorail-staging.appspot.com |
| --service-account=FILENAME_OF_SERVICE_ACCOUNT_JSON_FILE` |
| """ |
| |
| import argparse |
| import errno |
| import json |
| import logging |
| import os |
| import sys |
| |
| |
| monorail_dir = os.path.dirname(os.path.abspath(__file__ + '/..')) |
| third_party_path = os.path.join(monorail_dir, 'third_party') |
| if third_party_path not in sys.path: |
| sys.path.insert(0, third_party_path) |
| |
| import httplib2 |
| from oauth2client.client import GoogleCredentials |
| |
| |
| URL_BASE = 'http://localhost:8080/prpc/' |
| OAUTH_SCOPE = 'https://www.googleapis.com/auth/userinfo.email' |
| |
| def make_http(args): |
| """Return an httplib2.Http object, with or without oauth.""" |
| http = httplib2.Http() |
| credentials = None |
| if args.use_app_credentials: |
| credentials = GoogleCredentials.get_application_default() |
| if args.service_account: |
| credentials = GoogleCredentials.from_stream(args.service_account) |
| logging.debug('Will request as user %r', credentials.service_account_email) |
| |
| if credentials: |
| credentials = credentials.create_scoped([OAUTH_SCOPE]) |
| logging.debug('Will request as client %r', credentials.client_id) |
| if not args.host: |
| print(('[ERROR] OAuth on localhost will always see user ' |
| 'example@example.com, so we do not support that.\n' |
| 'Instead, add --server=YOUR_STAGING_SERVER, ' |
| 'or use --test_account=USER@example.com.')) |
| sys.exit(1) |
| |
| http = credentials.authorize(http) |
| |
| return http |
| |
| def make_call(service, method, json_body, args): |
| """Call the server and print the response contents.""" |
| body = json.loads(json_body) |
| |
| url_base = URL_BASE |
| if args.host: |
| url_base = 'https://%s/prpc/' % args.host |
| url = '%s%s/%s' % (url_base, service, method) |
| logging.debug('Request URL: %s', url) |
| |
| http = make_http(args) |
| headers = { |
| 'Content-Type': 'application/json', |
| 'Accept': 'application/json', |
| } |
| if args.test_account: |
| headers['x-test-account'] = args.test_account |
| if args.xsrf_token: |
| headers['x-xsrf-token'] = args.xsrf_token |
| body = json.dumps(body) |
| |
| logging.debug('Body: %r' % body) |
| try: |
| response, contents = http.request( |
| url, method='POST', body=body, headers=headers) |
| logging.info('Received response: %s', contents) |
| except httplib2.HttpLib2Error as e: |
| if hasattr(e.reason, 'errno') and e.reason.errno == errno.ECONNREFUSED: |
| print('[Error] Could not reach server. Is it running?') |
| else: |
| raise e |
| |
| |
| if __name__ == '__main__': |
| parser = argparse.ArgumentParser(description='Process some integers.') |
| parser.add_argument('service', help='pRPC service name.') |
| parser.add_argument('method', help='pRPC method name.') |
| parser.add_argument('json_body', help='pRPC HTTP body in valid JSON.') |
| parser.add_argument('--test-account', |
| help='Test account to use, in the form of an email.') |
| parser.add_argument('--xsrf-token', help='Custom XSRF token.') |
| parser.add_argument('--host', help='remote server FQDN.') |
| parser.add_argument( |
| '--use-app-credentials', |
| help='Use credentials of a GAE app that you are signed into via gcloud.', |
| action='store_true') |
| parser.add_argument( |
| '--service-account', help='Service account credentials JSON file name.') |
| parser.add_argument('-v', '--verbose', action='store_true') |
| args = parser.parse_args() |
| |
| if args.verbose: |
| log_level = logging.DEBUG |
| else: |
| log_level = logging.INFO |
| logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level) |
| |
| make_call(args.service, args.method, args.json_body, args) |