blob: 2ca746dc043e7d2b0e5d6f83fcb1f13f1117c185 [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright 2018 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""The build API entry point."""
from __future__ import print_function
import os
import sys
from chromite.api import api_config as api_config_lib
from chromite.api import controller
from chromite.api import router as router_lib
from chromite.lib import commandline
from chromite.lib import cros_build_lib
from chromite.utils import matching
def GetParser():
"""Build the argument parser."""
parser = commandline.ArgumentParser(description=__doc__)
call_group = parser.add_argument_group(
'API Call Options',
'These options are used to execute an endpoint. When making a call every '
'argument in this group is required.')
call_group.add_argument(
'service_method',
nargs='?',
help='The "chromite.api.Service/Method" that is being called.')
call_group.add_argument(
'--input-json',
type='path',
help='Path to the JSON serialized input argument protobuf message.')
call_group.add_argument(
'--output-json',
type='path',
help='The path to which the result protobuf message should be written.')
ux_group = parser.add_argument_group('Developer Options',
'Options to help developers.')
# Lists the full chromite.api.Service/Method, has both names to match
# whichever mental model people prefer.
ux_group.add_argument(
'--list-methods',
'--list-services',
action='store_true',
dest='list_services',
help='List the name of each registered "chromite.api.Service/Method".')
# Run configuration options.
test_group = parser.add_argument_group(
'Testing Options',
'These options are used to execute various tests against the API. These '
'options are mutually exclusive. Calling code can use these options to '
'validate inputs and test their handling of each return code case for '
'each endpoint.')
call_modifications = test_group.add_mutually_exclusive_group()
call_modifications.add_argument(
'--validate-only',
action='store_true',
default=False,
help='When set, only runs the argument validation logic. Calls produce '
'a return code of 0 iff the input proto comprises arguments that '
'are a valid call to the endpoint, or 1 otherwise.')
# See: api/faux.py for the mock call and error implementations.
call_modifications.add_argument(
'--mock-call',
action='store_true',
default=False,
help='When set, returns a valid, mock response rather than running the '
'endpoint. This allows API consumers to more easily test their '
'implementations against the version of the API being called. '
'This argument will always result in a return code of 0.')
call_modifications.add_argument(
'--mock-error',
action='store_true',
default=False,
help='When set, return a valid, mock error response rather than running '
'the endpoint. This allows API consumers to test their error '
'handling semantics against the version of the API being called. '
'This argument will always result in a return code of 2 iff the '
'endpoint ever produces a return code of 2, otherwise will always'
'produce a return code of 1.')
call_modifications.add_argument(
'--mock-invalid',
action='store_true',
default=False,
help='When set, return a mock validation error response rather than '
'running the endpoint. This allows API consumers to test their '
'validation error handling semantics against the version of the API '
'being called without having to understand how to construct an '
'invalid request. '
'This argument will always result in a return code of 1.')
return parser
def _ParseArgs(argv, router):
"""Parse and validate arguments."""
parser = GetParser()
opts = parser.parse_args(argv)
methods = router.ListMethods()
if opts.list_services:
# We just need to print the methods and we're done.
for method in methods:
print(method)
sys.exit(0)
# Positional service_method argument validation.
if not opts.service_method:
parser.error('Must pass "Service/Method".')
parts = opts.service_method.split('/')
if len(parts) != 2:
parser.error(
'Must pass the correct format: (e.g. chromite.api.SdkService/Create).'
'Use --list-methods to see a full list.')
if opts.service_method not in methods:
# Unknown method, try to match against known methods and make a suggestion.
# This is just for developer sanity, e.g. misspellings when testing.
matched = matching.GetMostLikelyMatchedObject(
methods, opts.service_method, matched_score_threshold=0.6)
error = 'Unrecognized service name.'
if matched:
error += '\nDid you mean: \n%s' % '\n'.join(matched)
parser.error(error)
opts.service = parts[0]
opts.method = parts[1]
# --input-json and --output-json validation.
if not opts.input_json or not opts.output_json:
parser.error('--input-json and --output-json are both required.')
if not os.path.exists(opts.input_json):
parser.error('Input file does not exist.')
# Build the config object from the options.
opts.config = api_config_lib.ApiConfig(
validate_only=opts.validate_only,
mock_call=opts.mock_call,
mock_error=opts.mock_error)
opts.Freeze()
return opts
def main(argv):
router = router_lib.GetRouter()
opts = _ParseArgs(argv, router)
if opts.mock_invalid:
# --mock-invalid handling. We print error messages, but no output is ever
# set for validation errors, so we can handle it by just giving back the
# correct return code here.
return controller.RETURN_CODE_INVALID_INPUT
try:
return router.Route(opts.service, opts.method, opts.input_json,
opts.output_json, opts.config)
except router_lib.Error as e:
# Handle router_lib.Error derivatives nicely, but let anything else bubble
# up.
cros_build_lib.Die(e)