blob: 8c2e139dce9ecceb2d135ab025a1034536dd8bfd [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
Michael Mortensen3e86c1e2019-11-21 15:51:54 -070018from chromite.lib import cros_logging as logging
19from chromite.lib import tee
Alex Klein00b1f1e2019-02-08 13:53:42 -070020from chromite.utils import matching
Alex Kleinf4dc4f52018-12-05 13:55:12 -070021
22
Alex Kleinf4dc4f52018-12-05 13:55:12 -070023def GetParser():
Alex Klein00b1f1e2019-02-08 13:53:42 -070024 """Build the argument parser."""
Alex Kleinf4dc4f52018-12-05 13:55:12 -070025 parser = commandline.ArgumentParser(description=__doc__)
26
Alex Klein2008aee2019-08-20 16:25:27 -060027 call_group = parser.add_argument_group(
28 'API Call Options',
29 'These options are used to execute an endpoint. When making a call every '
30 'argument in this group is required.')
31 call_group.add_argument(
32 'service_method',
33 nargs='?',
34 help='The "chromite.api.Service/Method" that is being called.')
35 call_group.add_argument(
36 '--input-json',
37 type='path',
Alex Kleinf4dc4f52018-12-05 13:55:12 -070038 help='Path to the JSON serialized input argument protobuf message.')
Alex Klein2008aee2019-08-20 16:25:27 -060039 call_group.add_argument(
40 '--output-json',
41 type='path',
Alex Kleinf4dc4f52018-12-05 13:55:12 -070042 help='The path to which the result protobuf message should be written.')
Michael Mortensen3e86c1e2019-11-21 15:51:54 -070043 call_group.add_argument(
44 '--tee-log',
45 type='path',
46 help='The path to which stdout and stderr should be teed to.')
Alex Klein2008aee2019-08-20 16:25:27 -060047
48 ux_group = parser.add_argument_group('Developer Options',
49 'Options to help developers.')
50 # Lists the full chromite.api.Service/Method, has both names to match
51 # whichever mental model people prefer.
52 ux_group.add_argument(
53 '--list-methods',
54 '--list-services',
55 action='store_true',
56 dest='list_services',
57 help='List the name of each registered "chromite.api.Service/Method".')
58
59 # Run configuration options.
60 test_group = parser.add_argument_group(
61 'Testing Options',
62 'These options are used to execute various tests against the API. These '
63 'options are mutually exclusive. Calling code can use these options to '
64 'validate inputs and test their handling of each return code case for '
65 'each endpoint.')
66 call_modifications = test_group.add_mutually_exclusive_group()
67 call_modifications.add_argument(
68 '--validate-only',
69 action='store_true',
70 default=False,
71 help='When set, only runs the argument validation logic. Calls produce '
72 'a return code of 0 iff the input proto comprises arguments that '
73 'are a valid call to the endpoint, or 1 otherwise.')
74 # See: api/faux.py for the mock call and error implementations.
75 call_modifications.add_argument(
76 '--mock-call',
77 action='store_true',
78 default=False,
79 help='When set, returns a valid, mock response rather than running the '
80 'endpoint. This allows API consumers to more easily test their '
81 'implementations against the version of the API being called. '
82 'This argument will always result in a return code of 0.')
83 call_modifications.add_argument(
84 '--mock-error',
85 action='store_true',
86 default=False,
87 help='When set, return a valid, mock error response rather than running '
88 'the endpoint. This allows API consumers to test their error '
89 'handling semantics against the version of the API being called. '
90 'This argument will always result in a return code of 2 iff the '
91 'endpoint ever produces a return code of 2, otherwise will always'
92 'produce a return code of 1.')
93 call_modifications.add_argument(
94 '--mock-invalid',
95 action='store_true',
96 default=False,
97 help='When set, return a mock validation error response rather than '
98 'running the endpoint. This allows API consumers to test their '
99 'validation error handling semantics against the version of the API '
100 'being called without having to understand how to construct an '
101 'invalid request. '
102 'This argument will always result in a return code of 1.')
Alex Kleinf4dc4f52018-12-05 13:55:12 -0700103
104 return parser
105
106
Alex Klein00b1f1e2019-02-08 13:53:42 -0700107def _ParseArgs(argv, router):
Alex Kleinf4dc4f52018-12-05 13:55:12 -0700108 """Parse and validate arguments."""
109 parser = GetParser()
Alex Klein7cc434f2019-12-17 14:58:57 -0700110 opts, unknown = parser.parse_known_args(
111 argv, namespace=commandline.ArgumentNamespace())
112 parser.DoPostParseSetup(opts, unknown)
113
114 if unknown:
115 logging.warning('Unknown args ignored: %s', ' '.join(unknown))
Alex Kleinf4dc4f52018-12-05 13:55:12 -0700116
Alex Klein00b1f1e2019-02-08 13:53:42 -0700117 methods = router.ListMethods()
George Engelbrechtd3de8df2019-09-04 18:15:05 -0600118
119 if opts.list_services:
Alex Klein2008aee2019-08-20 16:25:27 -0600120 # We just need to print the methods and we're done.
George Engelbrechtd3de8df2019-09-04 18:15:05 -0600121 for method in methods:
122 print(method)
123 sys.exit(0)
124
Alex Klein2008aee2019-08-20 16:25:27 -0600125 # Positional service_method argument validation.
George Engelbrechtd3de8df2019-09-04 18:15:05 -0600126 if not opts.service_method:
127 parser.error('Must pass "Service/Method".')
128
129 parts = opts.service_method.split('/')
130 if len(parts) != 2:
131 parser.error(
Alex Klein2008aee2019-08-20 16:25:27 -0600132 'Must pass the correct format: (e.g. chromite.api.SdkService/Create).'
133 'Use --list-methods to see a full list.')
George Engelbrechtd3de8df2019-09-04 18:15:05 -0600134
Alex Klein00b1f1e2019-02-08 13:53:42 -0700135 if opts.service_method not in methods:
Alex Klein00aa8072019-04-15 16:36:00 -0600136 # Unknown method, try to match against known methods and make a suggestion.
137 # This is just for developer sanity, e.g. misspellings when testing.
Alex Klein2008aee2019-08-20 16:25:27 -0600138 matched = matching.GetMostLikelyMatchedObject(
139 methods, opts.service_method, matched_score_threshold=0.6)
Alex Klein00b1f1e2019-02-08 13:53:42 -0700140 error = 'Unrecognized service name.'
141 if matched:
142 error += '\nDid you mean: \n%s' % '\n'.join(matched)
143 parser.error(error)
144
Alex Kleinf4dc4f52018-12-05 13:55:12 -0700145 opts.service = parts[0]
146 opts.method = parts[1]
147
Alex Klein2008aee2019-08-20 16:25:27 -0600148 # --input-json and --output-json validation.
149 if not opts.input_json or not opts.output_json:
150 parser.error('--input-json and --output-json are both required.')
151
Alex Klein5bcb4d22019-03-21 13:51:54 -0600152 if not os.path.exists(opts.input_json):
153 parser.error('Input file does not exist.')
154
Alex Klein2008aee2019-08-20 16:25:27 -0600155 # Build the config object from the options.
156 opts.config = api_config_lib.ApiConfig(
157 validate_only=opts.validate_only,
158 mock_call=opts.mock_call,
159 mock_error=opts.mock_error)
Alex Klein69339cc2019-07-22 14:08:35 -0600160
Alex Kleinf4dc4f52018-12-05 13:55:12 -0700161 opts.Freeze()
162 return opts
163
164
Alex Kleinf4dc4f52018-12-05 13:55:12 -0700165def main(argv):
Michael Mortensen3e86c1e2019-11-21 15:51:54 -0700166 with cros_build_lib.ContextManagerStack() as stack:
Alex Kleinf4dc4f52018-12-05 13:55:12 -0700167
Michael Mortensen3e86c1e2019-11-21 15:51:54 -0700168 router = router_lib.GetRouter()
169 opts = _ParseArgs(argv, router)
Alex Klein00b1f1e2019-02-08 13:53:42 -0700170
Michael Mortensen3e86c1e2019-11-21 15:51:54 -0700171 if opts.tee_log:
172 stack.Add(tee.Tee, opts.tee_log)
173 logging.info('Teeing stdout and stderr to %s', opts.tee_log)
Michael Mortensena0515d92020-01-02 11:39:34 -0700174 tee_log_env_value = os.environ.get('BUILD_API_TEE_LOG_FILE')
175 if tee_log_env_value:
176 stack.Add(tee.Tee, tee_log_env_value)
177 logging.info('Teeing stdout and stderr to env path %s', tee_log_env_value)
Alex Klein2008aee2019-08-20 16:25:27 -0600178
Michael Mortensen3e86c1e2019-11-21 15:51:54 -0700179 if opts.mock_invalid:
180 # --mock-invalid handling. We print error messages, but no output is ever
181 # set for validation errors, so we can handle it by just giving back the
182 # correct return code here.
183 return controller.RETURN_CODE_INVALID_INPUT
184
185 try:
186 return router.Route(opts.service, opts.method, opts.input_json,
187 opts.output_json, opts.config)
188 except router_lib.Error as e:
189 # Handle router_lib.Error derivatives nicely, but let anything else bubble
190 # up.
191 cros_build_lib.Die(e)