blob: 2ca746dc043e7d2b0e5d6f83fcb1f13f1117c185 [file] [log] [blame]
Alex Kleinf4dc4f52018-12-05 13:55:12 -07001# -*- coding: utf-8 -*-
2# Copyright 2018 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""The build API entry point."""
7
8from __future__ import print_function
9
Alex Klein5bcb4d22019-03-21 13:51:54 -060010import os
George Engelbrechtd3de8df2019-09-04 18:15:05 -060011import sys
Alex Kleinf4dc4f52018-12-05 13:55:12 -070012
Alex Klein69339cc2019-07-22 14:08:35 -060013from chromite.api import api_config as api_config_lib
Alex Klein2008aee2019-08-20 16:25:27 -060014from chromite.api import controller
Alex Klein146d4772019-06-20 13:48:25 -060015from chromite.api import router as router_lib
Alex Kleinf4dc4f52018-12-05 13:55:12 -070016from chromite.lib import commandline
Alex Klein2bfacb22019-02-04 11:42:17 -070017from chromite.lib import cros_build_lib
Alex Klein00b1f1e2019-02-08 13:53:42 -070018from chromite.utils import matching
Alex Kleinf4dc4f52018-12-05 13:55:12 -070019
20
Alex Kleinf4dc4f52018-12-05 13:55:12 -070021def GetParser():
Alex Klein00b1f1e2019-02-08 13:53:42 -070022 """Build the argument parser."""
Alex Kleinf4dc4f52018-12-05 13:55:12 -070023 parser = commandline.ArgumentParser(description=__doc__)
24
Alex Klein2008aee2019-08-20 16:25:27 -060025 call_group = parser.add_argument_group(
26 'API Call Options',
27 'These options are used to execute an endpoint. When making a call every '
28 'argument in this group is required.')
29 call_group.add_argument(
30 'service_method',
31 nargs='?',
32 help='The "chromite.api.Service/Method" that is being called.')
33 call_group.add_argument(
34 '--input-json',
35 type='path',
Alex Kleinf4dc4f52018-12-05 13:55:12 -070036 help='Path to the JSON serialized input argument protobuf message.')
Alex Klein2008aee2019-08-20 16:25:27 -060037 call_group.add_argument(
38 '--output-json',
39 type='path',
Alex Kleinf4dc4f52018-12-05 13:55:12 -070040 help='The path to which the result protobuf message should be written.')
Alex Klein2008aee2019-08-20 16:25:27 -060041
42 ux_group = parser.add_argument_group('Developer Options',
43 'Options to help developers.')
44 # Lists the full chromite.api.Service/Method, has both names to match
45 # whichever mental model people prefer.
46 ux_group.add_argument(
47 '--list-methods',
48 '--list-services',
49 action='store_true',
50 dest='list_services',
51 help='List the name of each registered "chromite.api.Service/Method".')
52
53 # Run configuration options.
54 test_group = parser.add_argument_group(
55 'Testing Options',
56 'These options are used to execute various tests against the API. These '
57 'options are mutually exclusive. Calling code can use these options to '
58 'validate inputs and test their handling of each return code case for '
59 'each endpoint.')
60 call_modifications = test_group.add_mutually_exclusive_group()
61 call_modifications.add_argument(
62 '--validate-only',
63 action='store_true',
64 default=False,
65 help='When set, only runs the argument validation logic. Calls produce '
66 'a return code of 0 iff the input proto comprises arguments that '
67 'are a valid call to the endpoint, or 1 otherwise.')
68 # See: api/faux.py for the mock call and error implementations.
69 call_modifications.add_argument(
70 '--mock-call',
71 action='store_true',
72 default=False,
73 help='When set, returns a valid, mock response rather than running the '
74 'endpoint. This allows API consumers to more easily test their '
75 'implementations against the version of the API being called. '
76 'This argument will always result in a return code of 0.')
77 call_modifications.add_argument(
78 '--mock-error',
79 action='store_true',
80 default=False,
81 help='When set, return a valid, mock error response rather than running '
82 'the endpoint. This allows API consumers to test their error '
83 'handling semantics against the version of the API being called. '
84 'This argument will always result in a return code of 2 iff the '
85 'endpoint ever produces a return code of 2, otherwise will always'
86 'produce a return code of 1.')
87 call_modifications.add_argument(
88 '--mock-invalid',
89 action='store_true',
90 default=False,
91 help='When set, return a mock validation error response rather than '
92 'running the endpoint. This allows API consumers to test their '
93 'validation error handling semantics against the version of the API '
94 'being called without having to understand how to construct an '
95 'invalid request. '
96 'This argument will always result in a return code of 1.')
Alex Kleinf4dc4f52018-12-05 13:55:12 -070097
98 return parser
99
100
Alex Klein00b1f1e2019-02-08 13:53:42 -0700101def _ParseArgs(argv, router):
Alex Kleinf4dc4f52018-12-05 13:55:12 -0700102 """Parse and validate arguments."""
103 parser = GetParser()
104 opts = parser.parse_args(argv)
105
Alex Klein00b1f1e2019-02-08 13:53:42 -0700106 methods = router.ListMethods()
George Engelbrechtd3de8df2019-09-04 18:15:05 -0600107
108 if opts.list_services:
Alex Klein2008aee2019-08-20 16:25:27 -0600109 # We just need to print the methods and we're done.
George Engelbrechtd3de8df2019-09-04 18:15:05 -0600110 for method in methods:
111 print(method)
112 sys.exit(0)
113
Alex Klein2008aee2019-08-20 16:25:27 -0600114 # Positional service_method argument validation.
George Engelbrechtd3de8df2019-09-04 18:15:05 -0600115 if not opts.service_method:
116 parser.error('Must pass "Service/Method".')
117
118 parts = opts.service_method.split('/')
119 if len(parts) != 2:
120 parser.error(
Alex Klein2008aee2019-08-20 16:25:27 -0600121 'Must pass the correct format: (e.g. chromite.api.SdkService/Create).'
122 'Use --list-methods to see a full list.')
George Engelbrechtd3de8df2019-09-04 18:15:05 -0600123
Alex Klein00b1f1e2019-02-08 13:53:42 -0700124 if opts.service_method not in methods:
Alex Klein00aa8072019-04-15 16:36:00 -0600125 # Unknown method, try to match against known methods and make a suggestion.
126 # This is just for developer sanity, e.g. misspellings when testing.
Alex Klein2008aee2019-08-20 16:25:27 -0600127 matched = matching.GetMostLikelyMatchedObject(
128 methods, opts.service_method, matched_score_threshold=0.6)
Alex Klein00b1f1e2019-02-08 13:53:42 -0700129 error = 'Unrecognized service name.'
130 if matched:
131 error += '\nDid you mean: \n%s' % '\n'.join(matched)
132 parser.error(error)
133
Alex Kleinf4dc4f52018-12-05 13:55:12 -0700134 opts.service = parts[0]
135 opts.method = parts[1]
136
Alex Klein2008aee2019-08-20 16:25:27 -0600137 # --input-json and --output-json validation.
138 if not opts.input_json or not opts.output_json:
139 parser.error('--input-json and --output-json are both required.')
140
Alex Klein5bcb4d22019-03-21 13:51:54 -0600141 if not os.path.exists(opts.input_json):
142 parser.error('Input file does not exist.')
143
Alex Klein2008aee2019-08-20 16:25:27 -0600144 # Build the config object from the options.
145 opts.config = api_config_lib.ApiConfig(
146 validate_only=opts.validate_only,
147 mock_call=opts.mock_call,
148 mock_error=opts.mock_error)
Alex Klein69339cc2019-07-22 14:08:35 -0600149
Alex Kleinf4dc4f52018-12-05 13:55:12 -0700150 opts.Freeze()
151 return opts
152
153
Alex Kleinf4dc4f52018-12-05 13:55:12 -0700154def main(argv):
Alex Klein146d4772019-06-20 13:48:25 -0600155 router = router_lib.GetRouter()
Alex Kleinf4dc4f52018-12-05 13:55:12 -0700156
Alex Klein00b1f1e2019-02-08 13:53:42 -0700157 opts = _ParseArgs(argv, router)
158
Alex Klein2008aee2019-08-20 16:25:27 -0600159 if opts.mock_invalid:
160 # --mock-invalid handling. We print error messages, but no output is ever
161 # set for validation errors, so we can handle it by just giving back the
162 # correct return code here.
163 return controller.RETURN_CODE_INVALID_INPUT
164
Alex Klein7a115172019-02-08 14:14:20 -0700165 try:
Alex Klein5bcb4d22019-03-21 13:51:54 -0600166 return router.Route(opts.service, opts.method, opts.input_json,
Alex Klein69339cc2019-07-22 14:08:35 -0600167 opts.output_json, opts.config)
Amin Hassani1e2dfd22019-06-24 10:34:17 -0700168 except router_lib.Error as e:
169 # Handle router_lib.Error derivatives nicely, but let anything else bubble
170 # up.
Mike Frysinger6b5c3cd2019-08-27 16:51:00 -0400171 cros_build_lib.Die(e)