blob: 5455241b0c6ed35ac447e582e58883fa9525986e [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001#!/usr/bin/env python
2"""
3This is a helper script for making pRPC API calls during local development.
4
5Usage examples:
6
7To test an anonymous request to your own local monorail server:
81. Run 'make serve' in another shell
92. `./api/test_call monorail.Projects ListComponents
10 '{"project_name": "monorail", "include_admin_info": true}'`
11
12To test a signed in request to your own local monorail server:
131. Run 'make serve' in another shell
142. `./api/test_call monorail.Projects ListComponents
15 '{"project_name": "monorail", "include_admin_info": true}'
16 --test_account=test@example.com`
17Note that test account email address must always end in @example.com.
18
19To test an anonymous request to your monorail staging server:
201. Deploy your staging server version, e.g., 12345-76697e9-tainted-jrobbins.
212. Visit your staging server in a new incognito window and view source
22 to find the XSRF token for the anonymous user in JS var CS_env['token'].
233. `./api/test_call monorail.Projects ListComponents
24 '{"project_name": "monorail", "include_admin_info": true}'
25 --host=12345-76697e9-tainted-jrobbins-dot-monorail-staging.appspot.com
26 --xsrf-token='THE_ANON_TOKEN'`
27
28To test a signed-in request to your monorail staging server using
29the client_id for monorail-staging and your own account:
301. Make sure that you have a role in the monorail-staging project.
312. Have your account allowlisted by email address.
323. Download the monorail-staging app credientials via
33 `gcloud --project=monorail-staging auth login`.
344. `./api/test_call monorail.Projects ListComponents
35 '{"project_name": "monorail", "include_admin_info": true}'
36 --host=12345-76697e9-tainted-jrobbins-dot-monorail-staging.appspot.com
37 --use-app-credentials`
38
39To test a signed-in request to your monorail staging server using
40a service account client secrets file that you download:
41(Note: This is not recommended for prod because downloading secrets
42is a bad practice.)
431. Create a service account via the Cloud Console for any project.
44 Choose "IAM & Admin" > "Service accounts".
45 Press "+ Create Service Account".
46 Fill in the form and submit it to save a service account .json file
47 to your local disk. Keep this file private.
482. File an issue on /p/monorail to allowlist your client_id and/or
49 client_email. Or, author a CL yourself to add it to the allowlist.
503. `./api/test_call monorail.Projects ListComponents
51 '{"project_name": "monorail", "include_admin_info": true}'
52 --host=12345-76697e9-tainted-jrobbins-dot-monorail-staging.appspot.com
53 --service-account=FILENAME_OF_SERVICE_ACCOUNT_JSON_FILE`
54"""
55
56import argparse
57import errno
58import json
59import logging
60import os
61import sys
62
63
64monorail_dir = os.path.dirname(os.path.abspath(__file__ + '/..'))
65third_party_path = os.path.join(monorail_dir, 'third_party')
66if third_party_path not in sys.path:
67 sys.path.insert(0, third_party_path)
68
69import httplib2
70from oauth2client.client import GoogleCredentials
71
72
73URL_BASE = 'http://localhost:8080/prpc/'
74OAUTH_SCOPE = 'https://www.googleapis.com/auth/userinfo.email'
75
76def make_http(args):
77 """Return an httplib2.Http object, with or without oauth."""
78 http = httplib2.Http()
79 credentials = None
80 if args.use_app_credentials:
81 credentials = GoogleCredentials.get_application_default()
82 if args.service_account:
83 credentials = GoogleCredentials.from_stream(args.service_account)
84 logging.debug('Will request as user %r', credentials.service_account_email)
85
86 if credentials:
87 credentials = credentials.create_scoped([OAUTH_SCOPE])
88 logging.debug('Will request as client %r', credentials.client_id)
89 if not args.host:
90 print(('[ERROR] OAuth on localhost will always see user '
91 'example@example.com, so we do not support that.\n'
92 'Instead, add --server=YOUR_STAGING_SERVER, '
93 'or use --test_account=USER@example.com.'))
94 sys.exit(1)
95
96 http = credentials.authorize(http)
97
98 return http
99
100def make_call(service, method, json_body, args):
101 """Call the server and print the response contents."""
102 body = json.loads(json_body)
103
104 url_base = URL_BASE
105 if args.host:
106 url_base = 'https://%s/prpc/' % args.host
107 url = '%s%s/%s' % (url_base, service, method)
108 logging.debug('Request URL: %s', url)
109
110 http = make_http(args)
111 headers = {
112 'Content-Type': 'application/json',
113 'Accept': 'application/json',
114 }
115 if args.test_account:
116 headers['x-test-account'] = args.test_account
117 if args.xsrf_token:
118 headers['x-xsrf-token'] = args.xsrf_token
119 body = json.dumps(body)
120
121 logging.debug('Body: %r' % body)
122 try:
123 response, contents = http.request(
124 url, method='POST', body=body, headers=headers)
125 logging.info('Received response: %s', contents)
126 except httplib2.HttpLib2Error as e:
127 if hasattr(e.reason, 'errno') and e.reason.errno == errno.ECONNREFUSED:
128 print('[Error] Could not reach server. Is it running?')
129 else:
130 raise e
131
132
133if __name__ == '__main__':
134 parser = argparse.ArgumentParser(description='Process some integers.')
135 parser.add_argument('service', help='pRPC service name.')
136 parser.add_argument('method', help='pRPC method name.')
137 parser.add_argument('json_body', help='pRPC HTTP body in valid JSON.')
138 parser.add_argument('--test-account',
139 help='Test account to use, in the form of an email.')
140 parser.add_argument('--xsrf-token', help='Custom XSRF token.')
141 parser.add_argument('--host', help='remote server FQDN.')
142 parser.add_argument(
143 '--use-app-credentials',
144 help='Use credentials of a GAE app that you are signed into via gcloud.',
145 action='store_true')
146 parser.add_argument(
147 '--service-account', help='Service account credentials JSON file name.')
148 parser.add_argument('-v', '--verbose', action='store_true')
149 args = parser.parse_args()
150
151 if args.verbose:
152 log_level = logging.DEBUG
153 else:
154 log_level = logging.INFO
155 logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level)
156
157 make_call(args.service, args.method, args.json_body, args)